diff --git a/.gitignore b/.gitignore
index 7ee001fcc8..6d26a3347a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,25 +1,44 @@
-*.DS_Store
-*.iml
-*.xml
-*.txt
-*.dio
+# Created by .ignore support plugin (hsz.mobi)
+### Java template
+# Compiled class file
+*.class
+
+# Log file
 *.log
 
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+.DS_Store
+.idea/JavaEdge.iml
+.idea/compiler.xml
 .idea/inspectionProfiles/
+.idea/misc.xml
+.idea/modules.xml
+.idea/vcs.xml
+.idea/workspace.xml
+Python/.DS_Store
+大数据/.DS_Store
+操作系统/.DS_Store
+数据存储/.DS_Store
+面试题系列/.DS_Store
+
 .metals/metals.h2.db
+.metals/metals.log
 .vscode/settings.json
 
-Spring/feign.md
-TODO/MySQL会丢数据吗？.md
-TODO/【阿里最新数据库面试题】MySQL主从一致性.md
-TODO/【阿里数据库面试题解】MySQL高可用原理.md
-TODO/MySQL执行更新语句时做了什么?.md
-TODO/为何阿里不推荐MySQL使用join？.md
-TODO/【阿里MySQL面试题】内部临时表.md
-TODO/MySQL数据查询太多会OOM吗？.md
-TODO/有了InnoDB，Memory存储引擎还有意义吗？.md
-TODO/MySQL执行insert会如何加锁?.md
-TODO/MySQL的自增id竟然用到头了怎么办？.md
-TODO/MySQL全局锁和表锁.md
-TODO/MySQL事务是怎么实现隔离的？.md
-
diff --git "a/Git/Git\345\267\245\344\275\234:\351\235\242\350\257\225\345\277\205\347\237\245\345\277\205\344\274\232.md" "b/Git/Git\345\267\245\344\275\234:\351\235\242\350\257\225\345\277\205\347\237\245\345\277\205\344\274\232.md"
index 3abed05a64..ba9faa1c77 100644
--- "a/Git/Git\345\267\245\344\275\234:\351\235\242\350\257\225\345\277\205\347\237\245\345\277\205\344\274\232.md"
+++ "b/Git/Git\345\267\245\344\275\234:\351\235\242\350\257\225\345\277\205\347\237\245\345\277\205\344\274\232.md"
@@ -234,8 +234,8 @@ git://协议较为快速和有效,但是有时必须使用http协议,比如你
 在默认情况下，Git会把"Git URL"里最后一级目录名的'.git'的后辍去掉,做为新克隆(clone)项目的目录名: (例如. git clone [http://git.kernel.org/linux/kernel/git/torvalds/linux-2.6.git](https://link.jianshu.com?t=http://git.kernel.org/linux/kernel/git/torvalds/linux-2.6.git) 会建立一个目录叫'linux-2.6')
 另外，如果访问一个Git URL需要用法名和密码，可以在Git URL前加上用户名，并在它们之间加上@符合以表示分割，然后执行git clone命令，git会提示你输入密码。
 示例
-git clone [v_JavaEdge@http://www.kernel.org/pub/scm/git/git.git](https://link.jianshu.com?t=http://v_JavaEdge@http://www.kernel.org/pub/scm/git/git.git)
-这样将以作为[v_JavaEdge](https://link.jianshu.com?t=http://v_JavaEdge)用户名访问[http://www.kernel.org/pub/scm/git/git.git](https://link.jianshu.com?t=http://www.kernel.org/pub/scm/git/git.git)，然后按回车键执行git clone命令，git会提示你输入密码。
+git clone [v_shishusheng@http://www.kernel.org/pub/scm/git/git.git](https://link.jianshu.com?t=http://v_shishusheng@http://www.kernel.org/pub/scm/git/git.git)
+这样将以作为[v_shishusheng](https://link.jianshu.com?t=http://v_shishusheng)用户名访问[http://www.kernel.org/pub/scm/git/git.git](https://link.jianshu.com?t=http://www.kernel.org/pub/scm/git/git.git)，然后按回车键执行git clone命令，git会提示你输入密码。
 另外，我们可以通过-b <name>来指定要克隆的分支名，比如
 $ git clone -b master2 ../server .
 表示克隆名为master2的这个分支，如果省略-b <name>表示克隆master分支。
diff --git "a/Git/Git\347\211\210\346\234\254\345\233\236\351\200\200\346\226\271\346\263\225\350\256\272(\345\217\257\350\203\275\350\247\243\345\206\263\344\275\240101%\351\201\207\345\210\260\347\232\204Git\347\211\210\346\234\254\351\227\256\351\242\230).md" "b/Git/Git\347\211\210\346\234\254\345\233\236\351\200\200\346\226\271\346\263\225\350\256\272(\345\217\257\350\203\275\350\247\243\345\206\263\344\275\240101%\351\201\207\345\210\260\347\232\204Git\347\211\210\346\234\254\351\227\256\351\242\230).md"
deleted file mode 100644
index 8d5f27d5c5..0000000000
--- "a/Git/Git\347\211\210\346\234\254\345\233\236\351\200\200\346\226\271\346\263\225\350\256\272(\345\217\257\350\203\275\350\247\243\345\206\263\344\275\240101%\351\201\207\345\210\260\347\232\204Git\347\211\210\346\234\254\351\227\256\351\242\230).md"
+++ /dev/null
@@ -1,120 +0,0 @@
-# 1 本地回退
-你在本地做了错误的 commit，先找到要回退的版本的`commit id`：
-
-```bash
-git reflog 
-```
-![](https://img-blog.csdnimg.cn/20200414142250436.png)
-接着回退版本:
-
-```bash
-git reset --hard cac0
-```
-> cac0就是你要回退的版本的`commit id`的前面几位
-
-回退到某次提交。回退到的指定提交以后的提交都会从提交日志上消失。
-
-> 工作区和暂存区的内容都会被重置到指定提交的时候，如果不加`--hard`则只移动`HEAD`指针，不影响工作区和暂存区的内容。
-
-结合`git reflog`找回提交日志上看不到的版本历史，撤回某次操作前的状态
-这个方法可以对你的回退操作进行回退，因为这时候`git log`已经找不到历史提交的hash值了。
-
-#  2 远程回退
-## 2.1 回退自己的远程分支
-你的错误commit已经推送到远程分支，就需要回滚远程分支。
-- 首先要回退本地分支：
-
-```bash
-git reflog
-git reset --hard cac0
-```
-![](https://img-blog.csdnimg.cn/20200414142459436.png)
-- 由于本地分支回滚后，版本将落后远程分支，必须使用强制推送覆盖远程分支，否则后面将无法推送到远程分支。
-
-```bash
-git push -f
-```
-![](https://img-blog.csdnimg.cn/20200414142539953.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-- 注意修正为`git push -f origin branch_name`
-![](https://img-blog.csdnimg.cn/20200414142624784.png)
-
-## 2.2 回退公共远程分支
-如果你回退公共远程分支，把别人的提交给丢掉了怎么办？
-> 本人毕业时在前东家 hw 经常干的蠢事。
-
-### 分析
-假如你的远程master分支情况是这样的:
-
-```bash
-A1–A2–B1
-```
-> A、B分别代表两个人
-> A1、A2、B1代表各自的提交
-> 所有人的本地分支都已经更新到最新版本，和远程分支一致
-
-这时发现A2这次commit有误，你用reset回滚远程分支master到A1，那么理想状态是你的同事一拉代码git pull，他们的master分支也回滚了
-然而现实却是，你的同事会看到下面的提示：
-
-```bash
-$ git status
-On branch master
-Your branch is ahead of 'origin/master' by 2 commits.
-  (use "git push" to publish your local commits)
-nothing to commit, working directory clean
-```
-也就是说，你的同事的分支并没有主动回退，而是比远程分支超前了两次提交，因为远程分支回退了。
-不幸的是，现实中，我们经常遇到的都是猪一样的队友，他们一看到下面提示：
-
-```bash
-$ git status
-On branch master
-Your branch is ahead of 'origin/master' by 2 commits.
-  (use "git push" to publish your local commits)
-nothing to commit, working directory clean
-```
-就习惯性的git push一下，或者他们直接用的SourceTree这样的图形界面工具，一看到界面上显示的是推送的提示就直接点了推送按钮，卧槽，辛辛苦苦回滚的版本就这样轻松的被你猪一样的队友给还原了，所以，只要有一个队友push之后，远程master又变成了：
-
-```bash
-A1 – A2 – B1
-```
-
-这就是分布式，每个人都有副本。
-
-用另外一种方法来回退版本。
-
-# 3 公共远程回退
-使用git reset回退公共远程分支的版本后，需要其他所有人手动用远程master分支覆盖本地master分支，显然，这不是优雅的回退方法。
-
-```bash
-git revert HEAD                     //撤销最近一次提交
-git revert HEAD~1                   //撤销上上次的提交，注意：数字从0开始
-git revert 0ffaacc                  //撤销0ffaacc这次提交
-```
-git revert 命令意思是撤销某次提交。它会产生一个新的提交，虽然代码回退了，但是版本依然是向前的，所以，当你用revert回退之后，所有人pull之后，他们的代码也自动的回退了。但是，要注意以下几点：
-
-- revert 是撤销一次提交，所以后面的commit id是你需要回滚到的版本的前一次提交
-- 使用revert HEAD是撤销最近的一次提交，如果你最近一次提交是用revert命令产生的，那么你再执行一次，就相当于撤销了上次的撤销操作，换句话说，你连续执行两次revert HEAD命令，就跟没执行是一样的
-- 使用revert HEAD~1 表示撤销最近2次提交，这个数字是从0开始的，如果你之前撤销过产生了commi id，那么也会计算在内的
-- 如果使用 revert 撤销的不是最近一次提交，那么一定会有代码冲突，需要你合并代码，合并代码只需要把当前的代码全部去掉，保留之前版本的代码就可以了
-- git revert 命令的好处就是不会丢掉别人的提交，即使你撤销后覆盖了别人的提交，他更新代码后，可以在本地用 reset 向前回滚，找到自己的代码，然后拉一下分支，再回来合并上去就可以找回被你覆盖的提交了。
-
-#  4 revert 合并代码，解决冲突
-使用revert命令，如果不是撤销的最近一次提交，那么一定会有冲突，如下所示：
-
-```bash
-<<<<<<< HEAD
-全部清空
-第一次提交
-=======
-全部清空
->>>>>>> parent of c24cde7... 全部清空
-```
-解决冲突很简单，因为我们只想回到某次提交，因此需要把当前最新的代码去掉即可，也就是HEAD标记的代码：
-
-```bash
-<<<<<<< HEAD
-全部清空
-第一次提交
-=======
-```
-把上面部分代码去掉就可以了，然后再提交一次代码就可以解决冲突了。
\ No newline at end of file
diff --git "a/Go/Go-\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\200)-\344\273\213\347\273\215.md" "b/Go/Go-\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\200)-\344\273\213\347\273\215.md"
new file mode 100644
index 0000000000..753a7bc490
--- /dev/null
+++ "b/Go/Go-\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\200)-\344\273\213\347\273\215.md"
@@ -0,0 +1,39 @@
+# 1 导读
+![](https://upload-images.jianshu.io/upload_images/4685968-8067e131b74cf883.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-05849479768d9a59.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-a94edab973aa9a90.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5646f82218592cee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-a9a6d876127e4602.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5e4078206028a1ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-4d073cfef6322d14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-a7b692cf53c4b34a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-830265d690b8bc04.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-1b2ed4e4ee24d0be.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-f6ce781b6a033ef9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-e64d8a1c7a7105d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-0cd7ec2892ae20b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-31a8ec633fce04f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-06d96554e4264a78.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+ ![](https://upload-images.jianshu.io/upload_images/4685968-a4183e137d911e18.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-da6ffcadddb67c63.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 2 安装与环境
+- 官网
+https://golang.org/
+![](https://upload-images.jianshu.io/upload_images/4685968-ba6f181f533c7958.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 中文官网
+https://studygolang.com/
+![](https://upload-images.jianshu.io/upload_images/4685968-a749c9fa21a7d644.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-48407483cfcb14f9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-18d11b08613794a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# X 交流学习
+![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+
+
+## [博客](https://blog.csdn.net/qq_33589510)
+
+
+
+## [Github](https://github.com/Wasabi1234)
diff --git "a/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\344\270\211) - \346\214\201\344\271\205\345\214\226\344\272\221\346\226\207\344\273\266\344\277\241\346\201\257.md" "b/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\344\270\211) - \346\214\201\344\271\205\345\214\226\344\272\221\346\226\207\344\273\266\344\277\241\346\201\257.md"
new file mode 100644
index 0000000000..f664fcc153
--- /dev/null
+++ "b/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\344\270\211) - \346\214\201\344\271\205\345\214\226\344\272\221\346\226\207\344\273\266\344\277\241\346\201\257.md"	
@@ -0,0 +1,71 @@
+# 0 [相关源码](https://github.com/Wasabi1234/Go-Cloud-Store)
+
+# 1 MySQL基础
+## 简介
+为避免每次重启系统从零开始的问题,需要将数据持久化.
+![](https://upload-images.jianshu.io/upload_images/16782311-d59a5f2a87c4a342.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 服务架构变迁
+![](https://upload-images.jianshu.io/upload_images/16782311-7888a1be823355ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 安装配置
+### 安装模式
+- 单点
+- 主从
+
+![](https://upload-images.jianshu.io/upload_images/16782311-a588e4e352f3b543.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 多主
+
+# 2 文件表设计
+- 新建数据库
+![](https://upload-images.jianshu.io/upload_images/16782311-93d994cc266e86d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 新建文件表
+![](https://upload-images.jianshu.io/upload_images/16782311-03abc93755e61f19.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 分库分表
+### 水平分表
+![](https://upload-images.jianshu.io/upload_images/16782311-1cd30450d73cbafa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-1dcc2a59e1813009.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## Golang 操作MySQL
+![](https://upload-images.jianshu.io/upload_images/16782311-e96ba8777c17421c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 4 持久化元数据到文件表
+![](https://upload-images.jianshu.io/upload_images/16782311-c495ccb60d234cd8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![](https://upload-images.jianshu.io/upload_images/16782311-86b0de25058fd99a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 5  从文件表中获取元数据
+- file.go
+![](https://upload-images.jianshu.io/upload_images/16782311-df37fab7de1abb14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- handler.go
+![](https://upload-images.jianshu.io/upload_images/16782311-f716d1c342913724.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- filemeta.go
+![](https://upload-images.jianshu.io/upload_images/16782311-a58f2e525809ead1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- test over!
+![](https://upload-images.jianshu.io/upload_images/16782311-2dd492a053782a40.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 6 Go - MySQL使用小结
+## 通过sqI.DB来管理数据库连接对象
+## 通过sql.Open来创建协程安全的sql.DB对象
+## 优先使用Prepared Statement
+
+###  1 MySQL特点与应用场景
+### 2 主从架构与文件表设计逻辑
+### 3 Golang与MySQL的亲密接触
+
+# X 交流学习
+![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+
+
+## [博客](https://blog.csdn.net/qq_33589510)
+
+
+
+## [Github](https://github.com/Wasabi1234)
+
+
diff --git "a/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\345\233\233) - \350\264\246\345\217\267\347\263\273\347\273\237\344\270\216\351\211\264\346\235\203.md" "b/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\345\233\233) - \350\264\246\345\217\267\347\263\273\347\273\237\344\270\216\351\211\264\346\235\203.md"
new file mode 100644
index 0000000000..b55cb016fc
--- /dev/null
+++ "b/Go/Go\345\256\236\346\210\230\344\272\221\345\255\230\345\202\250(\345\233\233) - \350\264\246\345\217\267\347\263\273\347\273\237\344\270\216\351\211\264\346\235\203.md"	
@@ -0,0 +1,63 @@
+# 0 [相关源码](https://github.com/Wasabi1234/Go-Cloud-Store)
+修改代码部分直接查看github commit历史即可!
+
+加入用户系统后架构升级说明； 快速实现用户注册/登录/信息查询功能; 快速实现用户资源隔离存储及安全鉴权功能。
+
+# 1 帐号系统介绍与用户表设计
+
+## 1.1  账号系统的功能
+支持用户注册/登录
+支持用户Session鉴权
+用户数据资源隔离
+
+![在系统架构中的地位](https://upload-images.jianshu.io/upload_images/16782311-2392dcd62cfa1819.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 1.2 MySQL用户表的设计
+![](https://upload-images.jianshu.io/upload_images/16782311-228a0c7eca1e1093.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+
+# 2  实现用户注册接口
+- 注册页面
+![](https://upload-images.jianshu.io/upload_images/16782311-cdd0eba9d8ff9d87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 成功入库
+![](https://upload-images.jianshu.io/upload_images/16782311-4c0e6e59e6c0c98e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 3 实现用户登录接口
+![](https://upload-images.jianshu.io/upload_images/16782311-bbe121950f743239.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 添加路由规则,注意静态路由
+![](https://upload-images.jianshu.io/upload_images/16782311-40dc32688dcdb765.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 登录界面
+![](https://upload-images.jianshu.io/upload_images/16782311-29e162e74da73cde.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 4  实现用户信息查询接口
+除了注册登录,其他接口都需要验证token信息
+
+- 登录后的重定向界面
+![](https://upload-images.jianshu.io/upload_images/16782311-838a12d892b7c542.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 5 接口梳理小结
+## 用户注册
+![](https://upload-images.jianshu.io/upload_images/16782311-71e82a1ae3a10986.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 用户登录
+![](https://upload-images.jianshu.io/upload_images/16782311-c473e82fd21b0783.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 用户信息查询
+![](https://upload-images.jianshu.io/upload_images/16782311-53f80ac192de5dc7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 拦截验证token
+业务接口需要验证请求合法性
+![](https://upload-images.jianshu.io/upload_images/16782311-b2a446b19d8fae0c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+# 6 编码实战：快速实现访问鉴权接口+小结
+
+## 6.2 小结
+###  MySQL 用户表的设计
+###  注册/登录/查询接口的实现
+###  验证Token的拦截器
+
diff --git "a/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\211)---\345\206\205\345\273\272\345\256\271\345\231\250.md" "b/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\211)---\345\206\205\345\273\272\345\256\271\345\231\250.md"
new file mode 100644
index 0000000000..e0b5d9e31f
--- /dev/null
+++ "b/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\270\211)---\345\206\205\345\273\272\345\256\271\345\231\250.md"
@@ -0,0 +1,90 @@
+# 1 数组
+数组是单一类型元素的编号序列，该单一类型称为元素类型。元素的数量称为长度且为非负数。
+
+>数组类型 = "[" 数组长度 "]" 元素类型 .
+数组长度 = 表达式 .
+元素类型 = 类型 .
+
+长度是数组类型的一部分，其求值结果必须可表示为 `int` 类型的非负
+数组 `a` 的长度可使用内建函数 [`len`]获取， 其元素可通过整数[下标]0 到 `len(a)-1` 寻址
+ 数组类型总是一维的，但可组合构成多维的类型。
+```
+[32]byte
+[2*N] struct { x, y int32 }
+[1000]*float64
+[3][5]int
+[2][2][2]float64  // 等价于[2]([2]([2]float64))
+```
+## 构造数组
+![](https://upload-images.jianshu.io/upload_images/4685968-21b6511cb0771db4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![运行结果](https://upload-images.jianshu.io/upload_images/4685968-c93e33633eec5e22.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 遍历
+- 首先想到这样
+![](https://upload-images.jianshu.io/upload_images/4685968-c59b16f93675b8a1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 但是通常使用 range关键字遍历
+![](https://upload-images.jianshu.io/upload_images/4685968-30c512bd06035fb5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-621aaf1e5fa8cdb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 为什么要用 range
+![](https://upload-images.jianshu.io/upload_images/4685968-8eae12986ac725c9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 数组是值类型
+![](https://upload-images.jianshu.io/upload_images/4685968-2befddde7ce6ce8c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-f8b18c079a3e1e52.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![运行结果](https://upload-images.jianshu.io/upload_images/4685968-a2a3ac6f37c0b7b4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-f137f07f12479533.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+要想传递引用可修改数组,可传指针参数
+![](https://upload-images.jianshu.io/upload_images/4685968-3130ca70a71a94d7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 2 切片 (Slice) 的概念
+切片是数组连续段的引用及包含此数组的元素的编号序列。 切片类型表示元素类型为数组的所有切片的集。未初始化切片的值为 nil。
+
+>切片类型 = "[" "]" 元素类型 .
+
+类似于数组，切片是可索引的且拥有一个长度
+切片 `s` 的长度可通过内建函数 [`len`]获取；不同于数组的是，切片可`在执行过程中被改变`， 其元素可通过整数（§[下标]） 0 到 `len(s)-1` 寻址。 给定元素的切片下标可能小于它在其基本数组中的下标。
+
+切片一旦初始化，就总是伴随着一个包含其元素的基本数组。
+因此，切片与其数组及其它本数组的切片共享存储
+ 与此相反，不同的数组总是表示其不同的存储
+
+切片的基本数组可扩展其切片的结尾
+- **容量** 是该扩展的量度
+它是切片的长度和切片往后数组的长度之和；长度达到其容量的切片可通过从原切片 （§[Slices](http://docscn.studygolang.com/ref/spec.old#Slices)）‘切下’一个新的来创建。 切片 `a` 的容量可使用内建函数 [`cap(a)`](http://docscn.studygolang.com/ref/spec.old#%E9%95%BF%E5%BA%A6%E4%B8%8E%E5%AE%B9%E9%87%8F) 获取。
+
+给定元素类型 `T` 的一个新的，已初始化的切片值使用内建函数 [`make`](http://docscn.studygolang.com/ref/spec.old#%E5%88%9B%E5%BB%BA%E5%88%87%E7%89%87%E3%80%81%E6%98%A0%E5%B0%84%E4%B8%8E%E4%BF%A1%E9%81%93)创建， 它需要一个切片类型和指定其长度与可选容量的形参：
+```
+make([]T, length)
+make([]T, length, capacity)
+```
+调用 make将分配一个被返回的切片值所引用的，新的、隐藏的数组。即，执行
+```
+make([]T, length, capacity)
+```
+产生切片与分配数组后再对其进行切片相同，因此这两个例子的结果为相同的切片：
+```
+make([]int, 50, 100)
+new([100]int)[0:50]
+```
+类似于数组，切片总是一维的，但可组合构造更高维的对象
+元素为数组的数组，根据其构造，其内部数组的长度始终相同
+然而元素为切片的切片（或元素为数组的切片），其长度会动态地改变
+此外，其内部的切片必须单独地（通过 make）分配
+
+
+## 基本定义
+![](https://upload-images.jianshu.io/upload_images/4685968-ead1ce9561545a4a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-e41c5f608c014cc9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![运行结果](https://upload-images.jianshu.io/upload_images/4685968-12c9801aca625402.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 运行可修改
+![](https://upload-images.jianshu.io/upload_images/4685968-2c896e39b6aea368.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# X 交流学习
+![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+
+
+## [博客](https://blog.csdn.net/qq_33589510)
+
+
+
+## [Github](https://github.com/Wasabi1234)
+
diff --git "a/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\272\214)---\345\237\272\347\241\200\350\257\255\346\263\225.md" "b/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\272\214)---\345\237\272\347\241\200\350\257\255\346\263\225.md"
new file mode 100644
index 0000000000..be174d2255
--- /dev/null
+++ "b/Go/Go\350\257\255\350\250\200\345\256\236\346\210\230(\344\272\214)---\345\237\272\347\241\200\350\257\255\346\263\225.md"
@@ -0,0 +1,130 @@
+# 1 变量定义
+![](https://upload-images.jianshu.io/upload_images/4685968-dcc545a0c25312cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+var 语句定义了一个变量的列表；跟函数的参数列表一样，类型在后面。
+就像在这个例子中看到的一样，`var` 语句可以定义在包或函数级别。
+
+![](https://upload-images.jianshu.io/upload_images/4685968-c761b0b8887cb33b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-24cbfe3978b0a256.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+变量一旦定义了,就必须使用到,否则报错
+![](https://upload-images.jianshu.io/upload_images/4685968-d9849076af8bd39e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+根据变量内容即可判断变量类型,无须再显式声明
+![](https://upload-images.jianshu.io/upload_images/4685968-c033e5c0f088b243.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-49d94f156dd5b46e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-399b556331d40c82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 总结
+![](https://upload-images.jianshu.io/upload_images/4685968-159e6852758c709a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-d1adadf8cea48117.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 2 内置变量类型
+![](https://upload-images.jianshu.io/upload_images/4685968-ab2098c4512d04c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 复数机制
+![](https://upload-images.jianshu.io/upload_images/4685968-a3af538c25f3a461.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+验证欧拉公式
+![](https://upload-images.jianshu.io/upload_images/4685968-e316fa9f439a6702.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 强制类型转换
+![sqrt源码](https://upload-images.jianshu.io/upload_images/4685968-eb4bae006a5dee01.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+传参 int 直接报错
+![](https://upload-images.jianshu.io/upload_images/4685968-01e1a1da01217af9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+因为没有隐式转换,只有显式的强制类型转换
+![](https://upload-images.jianshu.io/upload_images/4685968-c22fda2c24ab0e0d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-82cd955ce6fe0838.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 3 常量与枚举
+![](https://upload-images.jianshu.io/upload_images/4685968-8de62a5eb0bf7618.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![常量的定义](https://upload-images.jianshu.io/upload_images/4685968-793b802cb1aeb54d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-f5bd95e7786dbed5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+使用 iota 在 const 块定义中实现自增值
+![iota = 0](https://upload-images.jianshu.io/upload_images/4685968-727bc25d5d409b51.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-26d00599bd2cab78.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![内存进制大小数值](https://upload-images.jianshu.io/upload_images/4685968-d052ac5984efae3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![使用常量定义枚举类型](https://upload-images.jianshu.io/upload_images/4685968-61754fdce22e95a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 常量定义要点回顾
+![](https://upload-images.jianshu.io/upload_images/4685968-38af7e8d959d8bcf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 4 条件语句
+## if
+"If"语句根据一个布尔表达式的值指定两个分支的条件来执行。 若该表达式求值为true，则执行"if"分支，否则执行"else"分支
+```
+If语句 = "if" [ 简单语句 ";" ] 表达式 块 [ "else" ( If语句 | 块 ) ] .
+```
+```
+if x > max {
+	x = max
+}
+```
+![](https://upload-images.jianshu.io/upload_images/4685968-1030048192aa8c60.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+工具方法,该方法有两个返回值哦!
+![](https://upload-images.jianshu.io/upload_images/4685968-d455cae29784ba11.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![文件位置](https://upload-images.jianshu.io/upload_images/4685968-0f10aeb7c16461b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-b3966cbf6a39a7dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-023df8ffbbbdd0a6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## switch
+"Switch"语句提供多路执行。表达式或类型说明符与"switch"中的"cases"相比较从而决定执行哪一分支。
+```
+Switch语句 = 表达式选择语句 | 类型选择语句 
+```
+![](https://upload-images.jianshu.io/upload_images/4685968-43a05a5d91b99c5b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-c1f3b76493c2ba18.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-aa6666c6a42f0616.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-84520d3418ef0700.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-34463d922ce507b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 循环
+![](https://upload-images.jianshu.io/upload_images/4685968-afd40ac5b52b975d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-3994aac2770e4490.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-0a56457977716208.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ead89acd1f82240d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-12979092e66a0dc5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-4c16122e1ce2a270.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-db220303bd7260ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-52430de72c205b31.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-28865bac63f52d6b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 5 函数
+- 参数 参数类型,
+![](https://upload-images.jianshu.io/upload_images/4685968-7ede8751dc54c5fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-341698877c01a08c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- Go的函数可以返回多个值
+![多返回值](https://upload-images.jianshu.io/upload_images/4685968-822efa5637e6f0b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 若参数过多,这样并不是一种清晰的写法
+![](https://upload-images.jianshu.io/upload_images/4685968-fdd54a3889db81c3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+建议如下最佳实践!
+![](https://upload-images.jianshu.io/upload_images/4685968-704fe54da0cece27.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 多返回值函数,只接收一个返回值
+![](https://upload-images.jianshu.io/upload_images/4685968-c582a9c2b3fb2933.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 对于多返回值参数,一般可用于返回`值 + error`
+![](https://upload-images.jianshu.io/upload_images/4685968-c850ba0c797cd2cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+比如这样,控制台输出就很难看,因为error直接程序中断了
+![](https://upload-images.jianshu.io/upload_images/4685968-88ec2aed632194da.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-bf666abd6bf34d57.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-d3e2f0d88b15b07a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-c5d32e7246e69988.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 亦可写成匿名函数
+![](https://upload-images.jianshu.io/upload_images/4685968-212c829f3b098030.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-50f02de0b6e82784.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+无默认参数,有可变参数,可变参数类型其实是[] type 类型
+![](https://upload-images.jianshu.io/upload_images/4685968-98976a75ee0cbe25.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-91c8fb4da3fff9f0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-f516bfbbed050cf0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#  6 指针
+![](https://upload-images.jianshu.io/upload_images/4685968-48f45af0ade89920.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 6.1 参数传递
+![](https://upload-images.jianshu.io/upload_images/4685968-fae13000a0e0e46d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-cbdaefbf665f5318.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-433a6d23da7d5e90.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![1](https://upload-images.jianshu.io/upload_images/4685968-878f29183b7cc5f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![2](https://upload-images.jianshu.io/upload_images/4685968-37024340d43a58eb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![3](https://upload-images.jianshu.io/upload_images/4685968-e03a16755081e3f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-26869ed2467509bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-c7b7a157d43769e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+当然啦,还有最简单的
+![](https://upload-images.jianshu.io/upload_images/4685968-b0283ef0cae97b30.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+# X 交流学习
+![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+
+
+## [博客](https://blog.csdn.net/qq_33589510)
+
+
+
+## [Github](https://github.com/Wasabi1234)
+
diff --git "a/Go/Go\350\257\255\350\250\200\347\232\204\351\235\242\345\220\221\345\257\271\350\261\241.md" "b/Go/Go\350\257\255\350\250\200\347\232\204\351\235\242\345\220\221\345\257\271\350\261\241.md"
new file mode 100644
index 0000000000..05a4e56b40
--- /dev/null
+++ "b/Go/Go\350\257\255\350\250\200\347\232\204\351\235\242\345\220\221\345\257\271\350\261\241.md"
@@ -0,0 +1,33 @@
+go语言仅支持封装，不支持继承和多态。
+go语言没有class，只有struct。
+
+# 结构的定义
+![](https://img-blog.csdnimg.cn/20201223155119118.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- go 语言即使是指针，不像 C语言使用`->`也可一直使用`.`引用下去![](https://img-blog.csdnimg.cn/20201223192039227.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- go 没有构造器，但可使用工厂函数
+![](https://img-blog.csdnimg.cn/20201223193155572.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+那么这到底创建在了堆还是栈呢？
+不需要知道。如果返回了没人用就在栈上
+如果返回了有人用，就在堆，并参与到 GC。
+所以没必要知道，编译器自己都知道。
+
+# 参数前后区别
+- 参数定义在函数名前后有啥区别呢？
+![](https://img-blog.csdnimg.cn/20201223194005570.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- 使用区别
+![](https://img-blog.csdnimg.cn/20201223194043874.png)
+Go都是值传递，记住了，和 Java 一样。
+
+# 使用指针作为方法接收者
+![](https://img-blog.csdnimg.cn/20201223200025882.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- 只有使用指针才可以改变结构内容
+- nil 指针也可调用方法（Go 很特殊）
+
+# 值接收者 V.S 指针接收者
+- 要改变内容必须使用指针接收者
+- 结构过大也考虑使用指针接收者
+- 一致性:如有指针接收者,最好都是指针接收者
+
+
+值接收者才是go语言特有的！
+值/指针接收者均可接收值/指针
\ No newline at end of file
diff --git "a/Go/\360\237\247\247\347\263\273\347\273\237/Go\345\256\236\346\210\230\360\237\247\247\347\263\273\347\273\237(\344\270\211) - \346\236\266\346\236\204\350\256\276\350\256\241.md" "b/Go/\360\237\247\247\347\263\273\347\273\237/Go\345\256\236\346\210\230\360\237\247\247\347\263\273\347\273\237(\344\270\211) - \346\236\266\346\236\204\350\256\276\350\256\241.md"
new file mode 100644
index 0000000000..32bb0cbda8
--- /dev/null
+++ "b/Go/\360\237\247\247\347\263\273\347\273\237/Go\345\256\236\346\210\230\360\237\247\247\347\263\273\347\273\237(\344\270\211) - \346\236\266\346\236\204\350\256\276\350\256\241.md"	
@@ -0,0 +1,226 @@
+# 1 代码架构的意义
+![](https://img-blog.csdnimg.cn/20190621180825370.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+代码架构就是详细设计中的核心内容!
+
+## 1.1 代码架构承上启下,决定软件质量
+◆ 承上
+说明业务逻辑和业务领域模型
+◆ 本身
+保证代码有更好的可读性和可维护性、可扩展性
+◆ 启下
+承载代码运行的硬件部署架构
+
+# 2 代码架构的操作
+## 2.1 业务逻辑表达
+向上沟通,提供交互入口
+## 2.2 自身业务逻辑及技术实现
+向下沟通,保存运行状态
+
+# 3 代码架构的设计
+
+先看一下DDD和分层架构的相关知识。
+## 3.1 DDD
+DDD（Domain Driven Design，领域驱动设计）作为一种软件开发方法，它可以帮助我们设计高质量的软件模型。在正确实现的情况下，我们通过DDD完成的设计恰恰就是软件的工作方式。
+
+UL（Ubiquitous Language，通用语言）是团队共享的语言，是DDD中最具威力的特性之一。不管你在团队中的角色如何，只要你是团队的一员，你都将使用UL。由于UL的重要性，所以需要让每个概念在各自的上下文中是清晰无歧义的，于是DDD在战略设计上提出了模式BC（Bounded Context，限界上下文）。UL和BC同时构成了DDD的两大支柱，并且它们是相辅相成的，即UL都有其确定的上下文含义，而BC中的每个概念都有唯一的含义。
+
+一个业务领域划分成若干个BC，它们之间通过Context Map进行集成。BC是一个显式的边界，领域模型便存在于这个边界之内。领域模型是关于某个特定业务领域的软件模型。通常，领域模型通过对象模型来实现，这些对象同时包含了数据和行为，并且表达了准确的业务含义。
+
+从广义上来讲，领域即是一个组织所做的事情以及其中所包含的一切，表示整个业务系统。
+由于“领域模型”包含了“领域”这个词，我们可能会认为应该为整个业务系统创建一个单一的、内聚的和全功能式的模型。然而，这并不是我们使用DDD的目标。正好相反，领域模型存在于BC内。
+在微服务架构实践中，人们大量地使用了DDD中的概念和技术：
+
+微服务中应该首先建立UL，然后再讨论领域模型。
+一个微服务最大不要超过一个BC，否则微服务内会存在有歧义的领域概念。
+一个微服务最小不要小于一个聚合，否则会引入分布式事务的复杂度。
+微服务的划分过程类似于BC的划分过程，每个微服务都有一个领域模型。
+微服务间的集成可以通过Context Map来完成，比如ACL（Anticorruption Layer，防腐层）。
+微服务间最好采用Domain Event（领域事件）来进行交互，使得微服务可以保持松耦合。
+.
+
+下面介绍最为流行的分层代码架构
+## 3.1 分层架构简介
+
+
+分层架构的一个重要原则是每层只能与位于其下方的层发生耦合。
+分层架构可以简单分为两种
+- 严格分层架构
+某层只能与位于其直接下方的层发生耦合
+- 松散分层架构
+松散分层架构中，则允许某层与它的任意下方层发生耦合。
+
+分层架构的好处是显而易见的。首先，由于层间松散的耦合关系，使得我们可以专注于本层的设计，而不必关心其他层的设计，也不必担心自己的设计会影响其它层，对提高软件质量大有裨益。其次，分层架构使得程序结构清晰，升级和维护都变得十分容易，更改某层的具体实现代码，只要本层的接口保持稳定，其他层可以不必修改。即使本层的接口发生变化，也只影响相邻的上层，修改工作量小且错误可以控制，不会带来意外的风险。
+要保持程序分层架构的优点，就必须坚持层间的松散耦合关系。设计程序时，应先划分出可能的层次，以及此层次提供的接口和需要的接口。设计某层时，应尽量保持层间的隔离，仅使用下层提供的接口。
+
+## 3.2 分层架构的优点
+单一职责
+高内聚低耦合
+提高复用性
+
+开发人员可以只关注整个结构中的某一层。
+可以很容易的用新的实现来替换原有层次的实现。
+可以降低层与层之间的依赖。
+有利于标准化。
+利于各层逻辑的复用。
+
+
+## 3.3 分层架构的缺陷
+“金无足赤，人无完人”，分层架构也不可避免具有一些缺陷：
+
+降低了系统的性能。这是显然的，因为增加了中间层，不过可以通过缓存机制来改善。
+可能会导致级联的修改。这种修改尤其体现在自上而下的方向，不过可以通过依赖倒置来改善。
+
+在每个BC中为了凸显领域模型，DDD中提出了分层架构模式
+
+最基本的为三层架构
+## 3.4 三层架构
+表现层
+业务逻辑层
+数据持久层
+
+但是职责定义并不明确,耦合度高
+
+所以我们项目使用四层逻辑分层架构
+## 3.5 逻辑分层架构
+![](https://img-blog.csdnimg.cn/20190621235019157.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+- User Interface - 用户接口
+人机交互,为用户界面层（或表示层），负责向用户显示信息和解释用户命令。这里指的用户可以是另一个计算机系统，不一定是使用用户界面的人。
+
+- Application - 应用层
+定义软件要完成的任务，并且指挥表达领域概念的对象来解决问题。这一层所负责的工作对业务来说意义重大，也是与其它系统的应用层进行交互的必要渠道。应用层要尽量简单，不包含业务规则或者知识，而只为下一层中的领域对象协调任务，分配工作，使它们互相协作。它没有反映业务情况的状态，但是却可以具有另外一种状态，为用户或程序显示某个任务的进度。
+
+- Domain - 领域层（或模型层）
+表达业务概念，业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的，但是反映业务情况的状态是由本层控制并且使用的。领域层是业务软件的核心，领域模型位于这一层。
+
+- Infrastructure - 基础实施层
+向其他层提供通用的技术能力：为应用层传递消息，为领域层提供持久化机制，为用户界面层绘制屏幕组件，等等。基础设施层还能够通过架构框架来支持四个层次间的交互模式。
+
+
+传统的四层架构都是限定型松散分层架构，即Infrastructure层的任意上层都可以访问该层（“L”型），而其它层遵守严格分层架构
+
+在四层架构模式的实践中，对于分层的本地化定义主要为：
+- User Interface层主要是Restful消息处理，配置文件解析，等等。
+- Application层主要是多进程管理及调度，多线程管理及调度，多协程调度和状态机管理，等等。
+- Domain层主要是领域模型的实现，包括领域对象的确立，这些对象的生命周期管理及关系，领域服务的定义，领域事件的发布，等等。
+- Infrastructure层主要是业务平台，编程框架，第三方库的封装，基础算法，等等。
+
+> 严格意义上来说，User Interface指的是用户界面，Restful消息和配置文件解析等处理应该放在Application层，User Interface层没有的话就空缺。但User Interface也可以理解为用户接口，所以将Restful消息和配置文件解析等处理放在User Interface层也行。
+
+## 3.6 物理分层
+- 魔改四层的六层架构
+![](https://img-blog.csdnimg.cn/2019062201340683.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+◆ 用户接口
+◆ 应用服务层接口:
+◆ 核心层
+- 应用服务实现层
+- 领域层
+- 数据访问层
+
+◆ 基础设施层
+
+
+- 架构设计图
+![](https://img-blog.csdnimg.cn/20190622013814347.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+# 4 Go 语言规范
+## 4.1 包名
+◆ 完整包名组成:引入路径+包名
+◆ 源代码中的包名称
+- 可以和文件夹名称不一致,建议尽量一致
+- 同一文件夹中所有源文件中的包名必须一致
+◆ 代码引用时使用包名,而非文件夹名称
+◆ 源代码导入的是文件夹路径名称
+- 非包名
+- 非文件名
+## 4.2 源代码文件名
+◆ 文件名称只是约定描述性的,并无任何编程含义
+
+
+# 5 🧧系统 - 代码结构
+![](https://img-blog.csdnimg.cn/20190622101718233.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+![](https://img-blog.csdnimg.cn/20190622102431607.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+# 6 🧧系统 - 包结构
+![](https://img-blog.csdnimg.cn/20190622103131713.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+# 7 包设计规范
+## 7.1 apis包 - 用户接口层
+◆ 文件名称可以描述其业务含义的单词
+◆ 定义外部交互逻辑和交互形式: UI、RESTful接口
+◆ 不涉及任何业务,随时可以替换为其他形式的交互方式
+◆ services构造和初始化
+
+## 7.2 services包 - 应用层接口
+◆ 文件名称使用可以描述其业务含义
+◆ 需要对外暴露
+- DTO、 service interface
+- 枚举、常数等
+
+## 7.3 core包 - 应用层/领域层/数据访问层
+◆ 文件名称使用可以描述
+业务含义 +分层名称
+◆ Service实现
+Domain、Dao、 PO
+
+# 8 Go的包管理
+## 8.1 历史
+go get => vendor => go modules
+◆ go get无版本概念
+◆ vendor曲线救国,但仍未版本化
+◆ go1.11 modules开启版本依赖新大门
+
+> 详细过程推荐阅读
+[Go 包管理的前世今生](https://www.infoq.cn/article/history-go-package-management)
+
+## 8.2 Go modules
+通过GO 1.11 MODULE环境变量来开启或者关闭,默认是auto
+
+◆ off/on/auto
+关闭,开启,自动识别
+
+◆ 使用module后,GOPATH失去了部分意义
+
+◆ 要用module ,第一步将项目从GOPATH中移出去
+
+## 8.3 go.mod 文件
+go.mod文件来管理依赖,定义模块依赖
+◆ go.mod文件放在项目根目录
+◆ go.mod文件面向行,由指令+参数组成
+◆ 注释使用//
+
+### 8.3.1 go.mod 主要指令
+◆ module:定义当前模块和包路径
+◆ require: 定义依赖的模块和版本
+◆ exclude: 排除特定模块和版本的使用
+◆ replace:模块源的替换
+
+### 8.3.2 go.mod 命令
+go.mod文件用go mod命令来创建和维护
+
+◆ 命令格式
+```
+go mod <命令> [可选参数]
+```
+◆ 8个子命令
+- init ,tidy,vendor,verify
+- download,edit,graph ,why
+
+### 8.3.3 实战演示
+####  8.3.3.1 使用go mod init 创建和初始化go.mod文件
+```
+ go mod init javaedge.com/GoDemo
+```
+![](https://img-blog.csdnimg.cn/20190622113300232.png)
+- 生成文件
+![](https://img-blog.csdnimg.cn/20190622113357360.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+####  8.3.3.2 go get引入依赖
+![](https://img-blog.csdnimg.cn/20190622200239750.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+####  8.3.3.3 tidy子命令更新模块依赖
+对于已存在项目进行module化,即可使用该命令
+会自动添加依赖的包,使用go build更新依赖
+# 参考
+[DDD分层架构的三种模式](https://www.jianshu.com/p/a775836c7e25)
diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/AbstractQueuedSynchronizer\345\216\237\347\220\206\350\247\243\346\236\220.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/AbstractQueuedSynchronizer\345\216\237\347\220\206\350\247\243\346\236\220.md"
deleted file mode 100644
index 75c4862c41..0000000000
--- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/AbstractQueuedSynchronizer\345\216\237\347\220\206\350\247\243\346\236\220.md"
+++ /dev/null
@@ -1,448 +0,0 @@
-AbstractQueuedSynchronizer 抽象同步队列简称 AQS ，它是实现同步器的基础组件，
-并发包中锁的底层就是使用 AQS 实现的.
-大多数开发者可能永远不会直接使用AQS ，但是知道其原理对于架构设计还是很有帮助的,而且要理解ReentrantLock、CountDownLatch等高级锁我们必须搞懂 AQS.
-
-# 1 整体感知
-## 1.1 架构图
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMjA3XzIwMjAwMjA5MTk1MDI3NjQ4LnBuZw?x-oss-process=image/format,png)
-AQS框架大致分为五层，自上而下由浅入深，从AQS对外暴露的API到底层基础数据.
-
-
-当有自定义同步器接入时，只需重写第一层所需要的部分方法即可，不需要关注底层具体的实现流程。当自定义同步器进行加锁或者解锁操作时，先经过第一层的API进入AQS内部方法，然后经过第二层进行锁的获取，接着对于获取锁失败的流程，进入第三层和第四层的等待队列处理，而这些处理方式均依赖于第五层的基础数据提供层。
-
-AQS 本身就是一套锁的框架，它定义了获得锁和释放锁的代码结构，所以如果要新建锁，只要继承 AQS，并实现相应方法即可。
-
-## 1.2 类设计
-该类提供了一种框架，用于实现依赖于先进先出（FIFO）等待队列的阻塞锁和相关的同步器（信号量，事件等）。此类的设计旨在为大多数依赖单个原子int值表示 state 的同步器提供切实有用的基础。子类必须定义更改此 state 的 protected 方法，并定义该 state 对于 acquired 或 released 此对象而言意味着什么。鉴于这些，此类中的其他方法将执行全局的排队和阻塞机制。子类可以维护其他状态字段，但是就同步而言，仅跟踪使用方法 *getState*，*setState* 和 *compareAndSetState* 操作的原子更新的int值。
-子类应定义为用于实现其所在类的同步属性的非公共内部帮助器类。
-
-子类应定义为用于实现其所在类的同步属性的非 public 内部辅助类。类AbstractQueuedSynchronizer不实现任何同步接口。 相反，它定义了诸如*acquireInterruptible*之类的方法，可以通过具体的锁和相关的同步器适当地调用这些方法来实现其 public 方法。
-
-此类支持默认的排他模式和共享模式:
-- 当以独占方式进行获取时，其他线程尝试进行的获取将无法成功
-- 由多个线程获取的共享模式可能（但不一定）成功
-
-该类不理解这些差异，只是从机制的意义上说，当共享模式获取成功时，下一个等待线程（如果存在）也必须确定它是否也可以获取。在不同模式下等待的线程们共享相同的FIFO队列。 通常，实现的子类仅支持这些模式之一，但也可以同时出现,比如在ReadWriteLock.仅支持排他模式或共享模式的子类无需定义支持未使用模式的方法.
-
-此类定义了一个内嵌的 **ConditionObject** 类，可由支持独占模式的子类用作Condition 的实现，该子类的 *isHeldExclusively* 方法报告相对于当前线程是否独占同步，使用当前 *getState* 值调用的方法 *release* 会完全释放此对象 ，并获得给定的此保存状态值，最终将该对象恢复为其先前的获取状态。否则，没有AbstractQueuedSynchronizer方***创建这样的条件，因此，如果无法满足此约束，请不要使用它。ConditionObject的行为当然取决于其同步器实现的语义。
-
-此类提供了内部队列的检查，检测和监视方法，以及条件对象的类似方法。 可以根据需要使用 AQS 将它们导出到类中以实现其同步机制。
-
-此类的序列化仅存储基础原子整数维护状态，因此反序列化的对象具有空线程队列。 需要序列化性的典型子类将定义一个readObject方法，该方法在反序列化时将其恢复为已知的初始状态。
-
-# 2 用法
-要将此类用作同步器的基础，使用*getState* *setState*和/或*compareAndSetState*检查和/或修改同步状态，以重新定义以下方法（如适用）
-- tryAcquire
-- tryRelease
-- tryAcquireShared
-- tryReleaseShared
-- isHeldExclusively
-
-默认情况下，这些方法中的每一个都会抛 *UnsupportedOperationException*。
-这些方法的实现必须在内部是线程安全的，并且通常应简短且不阻塞。 定义这些方法是使用此类的**唯一**受支持的方法。 所有其他方法都被声明为final，因为它们不能独立变化。
-
-从 AQS 继承的方法对跟踪拥有排他同步器的线程很有用。 鼓励使用它们-这将启用监视和诊断工具，以帮助用户确定哪些线程持有锁。
-
-虽然此类基于内部的FIFO队列，它也不会自动执行FIFO获取策略。 独占同步的核心采用以下形式：
-- Acquire
-```java
-while (!tryAcquire(arg)) {
-     如果线程尚未入队，则将其加入队列；
-     可能阻塞当前线程；
-}
-```
--  Release
-
-```java
-if (tryRelease(arg))
-    取消阻塞第一个入队的线程;
-```
-共享模式与此相似，但可能涉及级联的signal。
-
-acquire 中的检查是在入队前被调用，所以新获取的线程可能会在被阻塞和排队的其他线程之前插入。但若需要，可以定义tryAcquire、tryAcquireShared以通过内部调用一或多种检查方法来禁用插入，从而提供公平的FIFO获取顺序。 
-
-特别是，若 hasQueuedPredecessors()(公平同步器专门设计的一种方法）返回true，则大多数公平同步器都可以定义tryAcquire返回false.
-
-- 公平与否取决于如下一行代码：
-```java
-if (c == 0) {
-    if (!hasQueuedPredecessors() &&
-        compareAndSetState(0, acquires)) {
-        setExclusiveOwnerThread(current);
-        return true;
-    }
-}
-```
-### hasQueuedPredecessors
-```java
-public final boolean hasQueuedPredecessors() {
-    // The correctness of this depends on head being initialized
-    // before tail and on head.next being accurate if the current
-    // thread is first in queue.
-    Node t = tail; // Read fields in reverse initialization order
-    Node h = head;
-    // s代表等待队列的第一个节点
-    Node s;
-    // (s = h.next) == null 说明此时有另一个线程正在尝试成为头节点，详见AQS的acquireQueued方法
-    // s.thread != Thread.currentThread()：此线程不是等待的头节点
-    return h != t &&
-        ((s = h.next) == null || s.thread != Thread.currentThread());
-}
-```
-
-
-
-对于默认的插入（也称为贪婪，放弃和convoey-avoidance）策略，吞吐量和可伸缩性通常最高。 尽管不能保证这是公平的或避免饥饿，但允许较早排队的线程在较晚排队的线程之前进行重新竞争，并且每个重新争用都有一次机会可以毫无偏向地成功竞争过进入的线程。 
-同样，尽管获取通常无需自旋，但在阻塞前，它们可能会执行tryAcquire的多次调用，并插入其他任务。 如果仅短暂地保持排他同步，则这将带来自旋的大部分好处，而如果不进行排他同步，则不会带来很多负担。 如果需要的话，可以通过在调用之前使用“fast-path”检查来获取方法来增强此功能，并可能预先检查*hasContended*()和/或*hasQueuedThreads()*,以便仅在同步器可能不存在争用的情况下这样做。
-
-此类为同步提供了有效且可扩展的基础，部分是通过将其使用范围规范化到可以依赖于int状态，acquire 和 release 参数以及内部的FIFO等待队列的同步器。 当这还不够时，可以使用原子类、自定义队列类和锁支持阻塞支持从较低级别构建同步器。
-
-# 3 使用案例
-这里是一个不可重入的排他锁，它使用值0表示解锁状态，使用值1表示锁定状态。虽然不可重入锁并不严格要求记录当前所有者线程，但是这个类这样做是为了更容易监视使用情况。它还支持条件，并暴露其中一个检测方法:
-
-```java
-class Mutex implements Lock, java.io.Serializable {
-
-  // 我们内部的辅助类
-  private static class Sync extends AbstractQueuedSynchronizer {
-    // 报告是否处于锁定状态
-    protected boolean isHeldExclusively() {
-      return getState() == 1;
-    }
-
-    // 如果 state 是 0,获取锁
-    public boolean tryAcquire(int acquires) {
-      assert acquires == 1; // Otherwise unused
-      if (compareAndSetState(0, 1)) {
-        setExclusiveOwnerThread(Thread.currentThread());
-        return true;
-      }
-      return false;
-    }
-
-    // 通过将 state 置 0 来释放锁
-    protected boolean tryRelease(int releases) {
-      assert releases == 1; // Otherwise unused
-      if (getState() == 0) throw new IllegalMonitorStateException();
-      setExclusiveOwnerThread(null);
-      setState(0);
-      return true;
-    }
-
-    //  提供一个 Condition
-    Condition newCondition() { return new ConditionObject(); }
-
-    // 反序列化属性
-    private void readObject(ObjectInputStream s)
-        throws IOException, ClassNotFoundException {
-      s.defaultReadObject();
-      setState(0); // 重置到解锁状态
-    }
-  }
-
-  // 同步对象完成所有的工作。我们只是期待它.
-  private final Sync sync = new Sync();
-
-  public void lock()                { sync.acquire(1); }
-  public boolean tryLock()          { return sync.tryAcquire(1); }
-  public void unlock()              { sync.release(1); }
-  public Condition newCondition()   { return sync.newCondition(); }
-  public boolean isLocked()         { return sync.isHeldExclusively(); }
-  public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
-  public void lockInterruptibly() throws InterruptedException {
-    sync.acquireInterruptibly(1);
-  }
-  public boolean tryLock(long timeout, TimeUnit unit)
-      throws InterruptedException {
-    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
-  }
-}
-```
-
-这是一个闩锁类，它类似于*CountDownLatch*，只是它只需要一个单信号就可以触发。因为锁存器是非独占的，所以它使用共享的获取和释放方法。
-
-```java
- class BooleanLatch {
-
-   private static class Sync extends AbstractQueuedSynchronizer {
-     boolean isSignalled() { return getState() != 0; }
-
-     protected int tryAcquireShared(int ignore) {
-       return isSignalled() ? 1 : -1;
-     }
-
-     protected boolean tryReleaseShared(int ignore) {
-       setState(1);
-       return true;
-     }
-   }
-
-   private final Sync sync = new Sync();
-   public boolean isSignalled() { return sync.isSignalled(); }
-   public void signal()         { sync.releaseShared(1); }
-   public void await() throws InterruptedException {
-     sync.acquireSharedInterruptibly(1);
-   }
- }
-```
-
-# 4 基本属性与框架
-## 4.1 继承体系图
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTU4XzIwMjAwMjEwMjMyNTQwMzUwLnBuZw?x-oss-process=image/format,png)
-## 4.2 定义
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDk2XzIwMjAwMjEwMjMxMTIwMTMzLnBuZw?x-oss-process=image/format,png)
-
-可知 AQS 是一个抽象类，生来就是被各种子类锁继承的。继承自AbstractOwnableSynchronizer，其作用就是为了知道当前是哪个线程获得了锁，便于后续的监控
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDYyXzIwMjAwMjEwMjMyMzE4MzcyLnBuZw?x-oss-process=image/format,png)
-
-
-## 4.3 属性
-### 4.3.1 状态信息
-- volatile 修饰，对于可重入锁，每次获得锁 +1，释放锁 -1
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMTEwXzIwMjAwMjEwMjMyOTI0MTczLnBuZw?x-oss-process=image/format,png)
-- 可以通过 *getState* 得到同步状态的当前值。该操作具有 volatile 读的内存语义。
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTM5XzIwMjAwMjEwMjMzMjM0OTE0LnBuZw?x-oss-process=image/format,png)
-- setState 设置同步状态的值。该操作具有 volatile 写的内存语义
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDc4XzIwMjAwMjEwMjMzOTI2NjQ3LnBuZw?x-oss-process=image/format,png)
-- compareAndSetState 如果当前状态值等于期望值，则以原子方式将同步状态设置为给定的更新值。此操作具有 volatile 读和写的内存语义
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDMyXzIwMjAwMjEwMjM1MjM1NDAzLnBuZw?x-oss-process=image/format,png)
-- 自旋比使用定时挂起更快。粗略估计足以在非常短的超时时间内提高响应能力，当设置等待时间时才会用到这个属性
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDY0XzIwMjAwMjExMDAzOTIzNjQxLnBuZw?x-oss-process=image/format,png)
-
-这写方法都是Final的，子类无法重写。
-- 独占模式
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODIzXzIwMjAwMjExMDIyNTI0NjM5LnBuZw?x-oss-process=image/format,png)
-- 共享模式
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDQzXzIwMjAwMjExMDIyNTUxODMwLnBuZw?x-oss-process=image/format,png)
-### 4.3.2  同步队列
-- CLH 队列( FIFO)
-![](https://img-blog.csdnimg.cn/2020100800492945.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/22686302fc050911b9dc9cdaf672934b.png)
-
-- 作用
-阻塞获取不到锁(独占锁)的线程，并在适当时机从队首释放这些线程。
-
-同步队列底层数据结构是个双向链表。
-
-- 等待队列的头，延迟初始化。 除初始化外，只能通过 *setHead* 方法修改
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODc4XzIwMjAwMjExMDIzMjU4OTU4LnBuZw?x-oss-process=image/format,png)
-注意：如果head存在，则其waitStatus保证不会是 *CANCELLED*
-
-- 等待队列的尾部，延迟初始化。 仅通过方法 *enq* 修改以添加新的等待节点
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDk0XzIwMjAwMjExMDIzNTU3MTkyLnBuZw?x-oss-process=image/format,png)
-### 4.3.4 条件队列
-#### 为什么需要条件队列？
-同步队列并非所有场景都能cover，遇到锁 + 队列结合的场景时，就需要 Lock + Condition，先使用 Lock 决定：
-- 哪些线程可以获得锁
-- 哪些线程需要到同步队列里排队阻塞
-
-获得锁的多个线程在碰到队列满或空时，可使用 Condition 来管理这些线程，让这些线程阻塞等待，然后在合适的时机后，被正常唤醒。
-
-**同步队列 + 条件队列的协作多被用在锁 + 队列场景。**
-#### 作用
-AQS 的内部类，结合锁实现线程同步。存放调用条件变量的 await 方法后被阻塞的线程
-
-- 实现了 Condition 接口，而 Condition 接口就相当于 Object 的各种监控方法
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMTEwXzIwMjAwMjExMDI0MDUzNi5wbmc?x-oss-process=image/format,png)
-需要使用时，直接 new ConditionObject()。
-
-### 4.3.5 Node
-同步队列和条件队列的共用节点。
-入队时，用 Node 把线程包装一下，然后把 Node 放入两个队列中，我们看下 Node 的数据结构，如下：
-####  4.3.5.1 模式
-- 共享模式
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDUyXzIwMjAwMjExMDI1NTQxMTYyLnBuZw?x-oss-process=image/format,png)
-- 独占模式
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTAzXzIwMjAwMjExMDI1NjE2NzkyLnBuZw?x-oss-process=image/format,png)
-
-####  4.3.5.2  waitstatus - 等待状态
-```java
-volatile int waitStatus;
-```
-仅能为如下值：
-##### SIGNAL
-- 同步队列中的节点在自旋获取锁时，如果前一个节点的状态是 `SIGNAL`，那么自己就直接被阻塞，否则一直自旋
-- 该节点的后继节点会被（或很快）阻塞（通过park），因此当前节点释放或取消时必须unpark其后继节点。为避免竞争，acquire方法必须首先指示它们需要一个 signal，然后重试原子获取，然后在失败时阻塞。
-```java
-static final int SIGNAL    = -1;
-```
-
-##### CANCELLED
-表示线程获取锁的请求已被取消了：
-```java
-static final int CANCELLED =  1;
-```
-可能由于超时或中断，该节点被取消。
-
-节点永远不会离开此状态，此为一种终极状态。具有 cancelled 节点的线程永远不会再次阻塞。
-##### CONDITION
-该节点当前在条件队列，当节点从同步队列被转移到条件队列，状态就会被更改该态：
-```java
-static final int CONDITION = -2;
-```
-在被转移之前，它不会用作同步队列的节点，此时状态将置0（该值的使用与该字段的其他用途无关，仅是简化了机制）。
-
-##### PROPAGATE
-线程处在 `SHARED` 情景下，该字段才会启用。
-
-指示下一个**acquireShared**应该无条件传播，共享模式下，该状态的线程处Runnable态
-```java
-static final int PROPAGATE = -3;
-```
-*releaseShared* 应该传播到其他节点。 在*doReleaseShared*中对此进行了设置（仅适用于头节点），以确保传播继续进行，即使此后进行了其他操作也是如此。
-##### 0
-初始化时的默认值。
-##### 小结
-这些值是以数字方式排列，极大方便了开发者的使用。我们在平时开发也可以定义一些有特殊意义的常量值。
-
-非负值表示节点不需要 signal。 因此，大多数代码并不需要检查特定值，检查符号即可。
-
-- 对于普通的同步节点，该字段初始化为0
-- 对于条件节点，该字段初始化为`CONDITION`
-
-使用CAS（或在可能的情况下进行无条件的 volatile 写）对其进行修改。
-
-注意两个状态的区别
-- state 是锁的状态，int 型，子类继承 AQS 时，都是要根据 state 字段来判断有无得到锁
-- waitStatus 是节点（Node）的状态
-
-#### 4.3.5.3 数据结构
-##### 前驱节点
-- 链接到当前节点/线程所依赖的用来检查 *waitStatus* 的前驱节点
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODY0XzIwMjAwMjEyMDMyNjI1NjYxLnBuZw?x-oss-process=image/format,png)
-
-在入队期间赋值，并且仅在出队时将其清空（为了GC）。
-
-此外，在取消一个前驱结点后，在找到一个未取消的节点后会短路，这将始终存在，因为头节点永远不会被取消：只有成功 acquire 后，一个节点才会变为头。
-
-取消的线程永远不会成功获取，并且线程只会取消自身，不会取消任何其他节点。
-
-##### 后继节点
-链接到后继节点，当前节点/线程在释放时将其unpark。 在入队时赋值，在绕过已取消的前驱节点时进行调整，在出队时置null（为了GC）。 
-入队操作直到附加后才赋值前驱节点的`next`字段，因此看到`next`字段为 null，并不一定意味该节点位于队尾（有时间间隙）。 
-
-但若`next == null`，则可从队尾开始扫描`prev`以进行再次检查。
-```java
-// 若节点通过从tail向前搜索发现在在同步队列上，则返回 true
-// 仅在调用了 isOnSyncQueue 且有需要时才调用
-private boolean findNodeFromTail(Node node) {
-    Node t = tail;
-    for (;;) {
-        if (t == node)
-            return true;
-        if (t == null)
-            return false;
-        t = t.prev;
-    }
-}
-```
-```java
-final boolean isOnSyncQueue(Node node) {
-    if (node.waitStatus == Node.CONDITION || node.prev == null)
-        return false;
-    if (node.next != null) // If has successor, it must be on queue
-        return true;
-    /**
-     * node.prev 可以非null，但还没有在队列中，因为将它放在队列中的 CAS 可能会失败。
-     * 所以必须从队尾向前遍历以确保它确实成功了。
-     * 在调用此方法时，它将始终靠近tail，并且除非 CAS 失败（这不太可能）
-     * 否则它会在那里，因此几乎不会遍历太多
-     */    
-    return findNodeFromTail(node);
-}
-```
-已取消节点的`next`字段设置为指向节点本身而不是null，以使isOnSyncQueue更轻松。
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDg3XzIwMjAwMjEyMDM1NTAzNjAyLnBuZw?x-oss-process=image/format,png)
-- 使该节点入队的线程。 在构造时初始化，使用后消亡。![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODcwXzIwMjAwMjEyMjAwMTI3OTkwLnBuZw?x-oss-process=image/format,png)
-
-在同步队列中，nextWaiter 表示当前节点是独占模式还是共享模式
-在条件队列中，nextWaiter 表示下一个节点元素
-
-链接到在条件队列等待的下一个节点，或者链接到特殊值`SHARED`。 由于条件队列仅在以独占模式保存时才被访问，因此我们只需要一个简单的链接队列即可在节点等待条件时保存节点。 然后将它们转移到队列中以重新获取。 并且由于条件只能是独占的，因此我们使用特殊值来表示共享模式来保存字段。![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDUxXzIwMjAwMjEyMjAxMjMyODMucG5n?x-oss-process=image/format,png)
-# 5 Condition 接口
-JDK5 时提供。
-- 条件队列 ConditionObject 实现了 Condition 接口
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDc1XzIwMjAwMjEyMjA0NjMxMTMzLnBuZw?x-oss-process=image/format,png)
-- 本节就让我们一起来研究之
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDgwXzIwMjAwMjEyMjA1MzQ2NzIyLnBuZw?x-oss-process=image/format,png)
-
-Condition 将对象监视方法（wait，notify和notifyAll）分解为不同的对象，从而通过与任意Lock实现结合使用，从而使每个对象具有多个wait-sets。 当 Lock 替换了 synchronized 方法和语句的使用，Condition 就可以替换了Object监视器方法的使用。
-
-Condition 的实现可以提供与 Object 监视方法不同的行为和语义，例如保证通知的顺序，或者在执行通知时不需要保持锁定。 如果实现提供了这种专门的语义，则实现必须记录这些语义。
-
-Condition实例只是普通对象，它们本身可以用作 synchronized 语句中的目标，并且可以调用自己的监视器 wait 和 notification 方法。 获取 Condition 实例的监视器锁或使用其监视器方法与获取与该条件相关联的锁或使用其 await 和 signal 方法没有特定的关系。 建议避免混淆，除非可能在自己的实现中，否则不要以这种方式使用 Condition 实例。
-
-```java
- class BoundedBuffer {
-   final Lock lock = new ReentrantLock();
-   final Condition notFull  = lock.newCondition(); 
-   final Condition notEmpty = lock.newCondition(); 
-
-   final Object[] items = new Object[100];
-   int putptr, takeptr, count;
-
-   public void put(Object x) throws InterruptedException {
-     lock.lock();
-     try {
-       while (count == items.length)
-         notFull.await();
-       items[putptr] = x;
-       if (++putptr == items.length) putptr = 0;
-       ++count;
-       notEmpty.signal();
-     } finally {
-       lock.unlock();
-     }
-   }
-
-   public Object take() throws InterruptedException {
-     lock.lock();
-     try {
-       while (count == 0)
-         notEmpty.await();
-       Object x = items[takeptr];
-       if (++takeptr == items.length) takeptr = 0;
-       --count;
-       notFull.signal();
-       return x;
-     } finally {
-       lock.unlock();
-     }
-   }
- }
-```
-（ArrayBlockingQueue类提供了此功能，因此没有理由实现此示例用法类。）
-定义出一些方法，这些方法奠定了条件队列的基础
-## API
-### await
-- 使当前线程等待，直到被 signalled 或被中断
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTkwXzIwMjAwMjEyMjExNzU3ODYyLnBuZw?x-oss-process=image/format,png)
-
-与此 Condition 相关联的锁被原子释放，并且出于线程调度目的，当前线程被禁用，并且处于休眠状态，直到发生以下四种情况之一：
-- 其它线程为此 Condition 调用了 signal 方法，并且当前线程恰好被选择为要唤醒的线程
-- 其它线程为此 Condition 调用了 signalAll 方法
-- 其它线程中断了当前线程，并且当前线程支持被中断
-- 发生“虚假唤醒”。
-
-在所有情况下，在此方法可以返回之前，必须重新获取与此 Condition 关联的锁，才能真正被唤醒。当线程返回时，可以保证保持此锁。
-### await 超时时间
-- 使当前线程等待，直到被 signal 或中断，或经过指定的等待时间
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDczXzIwMjAwMjEyMjIxMjU3NTcyLnBuZw?x-oss-process=image/format,png)
-
-此方法在行为上等效于：
- 
-
-```java
-awaitNanos(unit.toNanos(time)) > 0
-```
-所以，虽然入参可以是任意单位的时间，但其实仍会转化成纳秒
-### awaitNanos
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyOTg0XzIwMjAwMjEyMjIxOTMxNTU5LnBuZw?x-oss-process=image/format,png)
-注意这里选择纳秒是为了避免计算剩余等待时间时的截断误差
-
-
-### signal()
-- 唤醒条件队列中的一个线程，在被唤醒前必须先获得锁
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkzMDYzXzIwMjAwMjEyMjMwNTQ3NjMzLnBuZw?x-oss-process=image/format,png)
-### signalAll()
-- 唤醒条件队列中的所有线程
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMTMvNTA4ODc1NV8xNTgxNTMzMTkyODkyXzIwMjAwMjEyMjMwNjUzNTM5LnBuZw?x-oss-process=image/format,png)
\ No newline at end of file
diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/FurureTask.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/FurureTask.md"
index 149a2a9585..3c1bbd6ce7 100644
--- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/FurureTask.md"
+++ "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/FurureTask.md"
@@ -1,26 +1,32 @@
-# 1 简介
-- 使用继承方式的好处是方便传参，可以在子类里面添加成员变量，通过 set 方法设置参数或者通过构造函数进行传递
+# 1 引导语
+研究源码，一般我们都从整体以及实例先入手，再研究细节，不至于一开始就“深陷其中而"当局者迷".
+本文，我们来看最后一种有返回值的线程创建方式。
+
+- 使用继承方式的好处是方便传参，可以在子类里面添加成员变量,通过 set 方法设置参数或者通过构造函数进行传递
 - 使用 Runnable 方式，则只能使用主线程里面被声明为 final 变量
 
-不好的地方是 Java 不支持多继承，如果继承了 Thread 类，那么子类不能再继承其他 ，而 Runable接口则没有这个限制 。而且 Thread 类和 Runnable 接口都不允许声明检查型异常，也不能定义返回值。没有返回值这点稍微有点麻烦。前两种方式都没办法拿到任务的返回结果，但今天的主角 FutureTask 却可以。
+不好的地方是 Java 不支持多继承，如果继承了 Thread 类，那么子类不能再继承其他 ，而 Runable接口则没有这个限制 。而且 Thread 类和 Runnable 接口都不允许声明检查型异常，也不能定义返回值。没有返回值这点稍微有点麻烦。前两种方式都没办法拿到任务的返回结果，但今天的主角 FutureTask 却可以.
 
 不能声明抛出检查型异常则更麻烦一些。run()方法意味着必须捕获并处理检查型异常。即使小心地保存了异常信息（在捕获异常时）以便稍后检查，但也不能保证这个 Runnable 对象的所有使用者都读取异常信息。你也可以修改Runnable实现的getter，让它们都能抛出任务执行中的异常。但这种方法除了繁琐也不是十分安全可靠，你不能强迫使用者调用这些方法，程序员很可能会调用join()方法等待线程结束然后就不管了。
 
 但是现在不用担心了，以上的问题终于在1.5中解决了。Callable接口和Future接口的引入以及他们对线程池的支持优雅地解决了这两个问题。
 # 2 案例
 先看一个demo，了解 FutureTask 相关组件是如何使用的
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY3MjUwXzIwMjAwMjA3MjIxNjM4OTk2LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177167250_20200207221638996.png)
+CallerTask 类实现了 Callable 接口的 call() 方法 。在 main 函数内首先创建FutrueTask对 象(构造函数为 CallerTask 实例), 然后使用创建的 FutureTask 作为任务创建了一个线程并且启动它， 最后通过 futureTask.get()等待任务执行完毕并返回结果.
+
+
 # 3 Callable
 Callable函数式接口定义了唯一方法 - call().
 我们可以在Callable的实现中声明强类型的返回值，甚至是抛出异常。同时，利用call()方法直接返回结果的能力，省去读取值时的类型转换。
 - 源码定义
 
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NjQ1XzIwMjAwMjAyMjA0MjA0MjIyLnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166645_20200202204204222.png)
 注意到返回值是一个泛型，使用的时候，不会直接使用 Callable，而是和 FutureTask 协同.
 
 # 4 Future
 - Callable 可以返回线程的执行结果，在获取结果时，就需要用到 Future 接口.
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2OTIwXzIwMjAwMjA0MDQwMDA3MzMucG5n?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166920_2020020404000733.png)
 
 Future是 Java5 中引入的接口，当提交一个Callable对象给线程池时，将得到一个Future对象，并且它和传入的Callable有相同的结果类型声明。
 
@@ -34,7 +40,7 @@ Future表示异步计算的结果。提供了一些方法来检查计算是否
 
 ## 4.1 Future API
 ### 4.1.1 cancel - 尝试取消执行任务
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2Njc4XzIwMjAwMjA0MDIxOTEwMTI1LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166678_20200204021910125.png)
 一个比较复杂的方法,当任务处于不同状态时,该方法有不同响应:
 - 任务 已经完成 / 已经取消 / 由于某些其他原因无法被取消,该尝试会直接失败
 - 尝试成功,且此时任务尚未开始，调用后是可以取消成功的
@@ -45,42 +51,34 @@ Future表示异步计算的结果。提供了一些方法来检查计算是否
 如果此方法返回 true，则随后对 *isCancelled* 的调用将始终返回 true.
 
 ### 4.1.2 isCancelled - 是否被取消
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2ODM0XzIwMjAwMjA0MDMwMzU2OTM1LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166834_20200204030356935.png)
 如果此任务在正常完成之前被取消，则返回true.
 
 ### 4.1.3 isDone - 是否完成
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NTc0XzIwMjAwMjA0MDMxMDA1NDg4LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166574_20200204031005488.png)
 如果此任务完成，则返回true.
 
 完成可能是由于正常终止，异常或取消引起的，在所有这些情况下，此方法都将返回true.
 
 ### 4.1.4 get - 获取结果
-等待任务完成，然后获取其结果。
-![](https://img-blog.csdnimg.cn/20210615233538777.png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166906_20200204031206355.png)
+等待任务完成，然后获取其结果.
 
-若：
-- 任务被取消，抛 *CancellationException* 
-- 当前线程在等待时被中断，抛 *InterruptedException* 
-- 任务抛出了异常，抛 *ExecutionException*
+- 如果任务被取消，抛 *CancellationException* 
+- 如果当前线程在等待时被中断，抛 *InterruptedException* 
+- 如果任务抛出了异常,抛 *ExecutionException*
 
 ### 4.1.5 timed get - 超时获取
-- 必要时最多等待给定时间以完成任务，然后获取其结果（如果有的话）。
-![](https://img-blog.csdnimg.cn/20210615233634165.png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166633_20200204033827757.png)
+必要时最多等待给定时间以完成任务，然后获取其结果（如果有的话）。
 - 抛CancellationException 如果任务被取消
 - 抛 ExecutionException 如果任务抛了异常
 - 抛InterruptedException 如果当前线程在等待时被中断
 - 抛TimeoutException 如果等待超时了
 
-两个get()方法都是阻塞的，若被调用时，任务还没有执行完，则调用get()方法的线程会阻塞，直到任务执行完才会被唤醒。所以future.get()是会阻塞当前调用线程。
-- 阻塞异步线程
-![](https://img-blog.csdnimg.cn/20210420165206559.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-![](https://img-blog.csdnimg.cn/2021042016492740.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 阻塞主线程
-![](https://img-blog.csdnimg.cn/20210420165240137.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+需要注意：这两个get()方法都是阻塞式的，如果被调用的时候，任务还没有执行完，那么调用get()方法的线程会阻塞，直到任务执行完才会被唤醒。
 
-![](https://img-blog.csdnimg.cn/20210420165122388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-Future 接口定义了许多对任务进行管理的 API,极大地方便了我们的开发调控。
+Future 接口定义了许多对任务进行管理的 API,极大地方便了我们的开发调控.
 
 # 5 RunnableFuture
 Java6 时提供的持有 Runnable 性质的 Future.
@@ -88,7 +86,7 @@ Java6 时提供的持有 Runnable 性质的 Future.
 成功执行run方法导致Future的完成，并允许访问其结果.
 
 RunnableFuture接口比较简单，就是继承了 Runnable 和 Future 接口。只提供一个*run*方法
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2OTE2XzIwMjAwMjA0MDQwMjM0NzM1LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166916_20200204040234735.png)
 
 现在,我们应该都知道,创建任务有两种方式
 - 无返回值的 Runnable
@@ -99,8 +97,8 @@ RunnableFuture接口比较简单，就是继承了 Runnable 和 Future 接口。
 所以铺垫了这么多,本文的主角 FutureTask 来了!
 
 # 6 FutureTask
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NTk5XzIwMjAwMjAyMjA1MzM1MzA3LnBuZw?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2OTQ3XzIwMjAwMjA4MjI0MjEzNDYucG5n?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166599_20200202205335307.png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166947_2020020822421346.png)
 前面的Future是一个接口，而 FutureTask 才是一个实实在在的工具类,是线程运行的具体任务.
 - 实现了 RunnableFuture 接口
 - 也就是实现了 Runnnable 接口,即FutureTask 本身就是个 Runnnable
@@ -114,7 +112,7 @@ RunnableFuture接口比较简单，就是继承了 Runnable 和 Future 接口。
 
 
 - 注意这些常量字段的定义方式,遵循避免魔鬼数字的编程规约.
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2ODg4XzIwMjAwMjA4MTM0OTU2MTQ1LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166888_20200208134956145.png)
 
 -  NEW
 线程任务创建,开始状态
@@ -139,28 +137,28 @@ RunnableFuture接口比较简单，就是继承了 Runnable 和 Future 接口。
 
 ### 6.1.2 其他属性
 - 组合的 callable,这样就具备了转化 Callable 和 Runnable 的功能
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2ODQ3XzIwMjAwMjA4MjI0MDM0NzU0LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166847_20200208224034754.png)
 - 从ge()返回或抛出异常的结果,非volatile，受状态读/写保护
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NTYzXzIwMjAwMjA4MjI0MzQzNjQ2LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166563_20200208224343646.png)
 - 运行 callable 的线程； 在run()期间进行CAS
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NzkyXzIwMjAwMjA4MjI1NDU3Mzc2LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166792_20200208225457376.png)
 - 记录调用 get 方法时被等待的线程 - 栈形式
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NjI5XzIwMjAwMjA4MjI1NzM1NzE5LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166629_20200208225735719.png)
 从属性上我们明显看到 Callable 是作为 FutureTask 的属性之一，这也就让 FutureTask 接着我们看下 FutureTask 的构造器，看看两者是如何转化的。
 
 ## 6.2 构造方法
 ### 6.2.1 Callable 参数
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY3MTI4XzIwMjAwMjA4MjMwMjIyNDg2LnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177167128_20200208230222486.png)
 ### 6.2.2 Runnable 参数
 为协调 callable 属性,辅助result 参数
 
 Runnable 是没有返回值的，所以 result 一般没有用，置为 null 即可,正如 JDK 所推荐写法
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2ODcwXzIwMjAwMjA4MjMxNTEwMzEwLnBuZw?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2OTcwXzIwMjAwMjA4MjMwMzM2MjIxLnBuZw?x-oss-process=image/format,png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166870_20200208231510310.png)
+![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166970_20200208230336221.png)
 -  Executors.callable 方法负责将 runnable 适配成 callable.
- ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY2NzE0XzIwMjAwMjA4MjMyMDUxMjQ2LnBuZw?x-oss-process=image/format,png)
+ ![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177166714_20200208232051246.png)
  - 通过转化类 RunnableAdapter进行适配
- ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAyMDAyMDgvNTA4ODc1NV8xNTgxMTc3MTY3MzE0XzIwMjAwMjA4MjMyMjExNjE2LnBuZw?x-oss-process=image/format,png)
+ ![](https://uploadfiles.nowcoder.com/files/20200208/5088755_1581177167314_20200208232211616.png)
 
 ### 6.2.3 小结
 我们可以学习这里的适配器模式，目标是要把 Runnable 适配成 Callable，那么我们首先要实现 Callable 接口，并且在 Callable 的 call 方法里面调用被适配对象即 Runnable的方法即可.
@@ -236,12 +234,12 @@ private int awaitDone(boolean timed, long nanos)
 }
 ```
 
-get 是一种阻塞式方法,当发现任务还在进行中，没有完成时，就会阻塞当前进程，等待任务完成后再返回结果值。
-
-阻塞底层使用的是 LockSupport.park 方法，使当前线程进入 `WAITING` 或 `TIMED_WAITING` 态。
+get 是一种阻塞式方法,当发现任务还在进行中，没有完成时，就会阻塞当前进程，等待任务完成后再返回结果值.
+阻塞底层使用的是 LockSupport.park 方法，使当前线程进入 `WAITING` 或 `TIMED_WAITING` 态.
 
 ## 6.4 run
-该方法可被直接调用，也可由线程池调用
+该方法可被直接调用,也可由线程池调用
+
 ```java
 public void run() {
     // 状态非 NEW 或当前任务已有线程在执行，直接返回
@@ -277,9 +275,12 @@ public void run() {
 }
 ```
 
-run 方法没有返回值，通过给 outcome 属性赋值（set(result)），get 时就能从 outcome 属性中拿到返回值。
+run 方法我们再说明几点：
+
+run 方法是没有返回值的，通过给 outcome 属性赋值（set(result)），get 时就能从 outcome 属性中拿到返回值；
 FutureTask 两种构造器，最终都转化成了 Callable，所以在 run 方法执行的时候，只需要执行 Callable 的 call 方法即可，在执行 c.call() 代码时，如果入参是 Runnable 的话， 调用路径为 c.call() -> RunnableAdapter.call() -> Runnable.run()，如果入参是 Callable 的话，直接调用。
 ## 6.5 cancel
+
 ```java
 // 取消任务，如果正在运行，尝试去打断
 public boolean cancel(boolean mayInterruptIfRunning) {
@@ -308,4 +309,4 @@ public boolean cancel(boolean mayInterruptIfRunning) {
 ```
 
 # 7 总结
-FutureTask 统一了 Runnnable 和 Callable，方便了我们后续对线程池的使用。
\ No newline at end of file
+FutureTask 统一了 Runnnable 和 Callable，方便了我们后续对线程池的使用.
\ No newline at end of file
diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/CountDownLatch.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/JDK\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230 - CountDownLatch.md"
similarity index 100%
rename from "JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/CountDownLatch.md"
rename to "JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/JDK\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230 - CountDownLatch.md"
diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/Java\351\233\206\345\220\210\344\271\213HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/Java\351\233\206\345\220\210\344\271\213HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md"
deleted file mode 100644
index 59050eb978..0000000000
--- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/Java\351\233\206\345\220\210\344\271\213HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md"
+++ /dev/null
@@ -1,715 +0,0 @@
-# 1 概述
-HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长.
-	
-HashMap是非线程安全的,只适用于单线程环境,多线程环境可以采用并发包下的`concurrentHashMap`
-    
-HashMap 实现了Serializable接口，因此它支持序列化，实现了Cloneable接口，能被克隆
-	
-HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变.
-	
-Java8中又对此类底层实现进行了优化，比如引入了红黑树的结构以解决哈希碰撞
-　
-# 2 HashMap的数据结构
-在Java中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造,HashMap也不例外.
-HashMap实际上是一个"链表散列"的数据结构,即数组和链表的结合体.
-
-![HashMap的结构](https://img-blog.csdnimg.cn/img_convert/c5ac3aacefa3b745d9d2fa48c577b11d.png)
-HashMap的主结构类似于一个数组,添加值时通过`key`确定储存位置.
-每个位置是一个Entry的数据结构,该结构可组成链表.
-当发生冲突时,相同hash值的键值对会组成链表.
-这种`数组+链表`的组合形式大部分情况下都能有不错的性能效果,Java6、7就是这样设计的.
-然而,在极端情况下,一组（比如经过精心设计的）键值对都发生了冲突，这时的哈希结构就会退化成一个链表，使HashMap性能急剧下降.
-
-所以在Java8中,HashMap的结构实现变为数组+链表+红黑树
-![Java8 HashMap的结构](https://img-blog.csdnimg.cn/img_convert/7668e49d6bb167520dcf09ab09537378.png)
-可以看出,HashMap底层就是一个数组结构
-数组中的每一项又是一个链表
-当新建一个HashMap时,就会初始化一个数组.
-
-# 3 三大集合与迭代子
-HashMap使用三大集合和三种迭代子来轮询其Key、Value和Entry对象
-```java
-public class HashMapExam {
-    public static void main(String[] args) {
-        Map<Integer, String> map = new HashMap<>(16);
-        for (int i = 0; i < 15; i++) {
-            map.put(i, new String(new char[]{(char) ('A'+ i)}));
-        }
-
-        System.out.println("======keySet=======");
-        Set<Integer> set = map.keySet();
-        Iterator<Integer> iterator = set.iterator();
-        while (iterator.hasNext()) {
-            System.out.println(iterator.next());
-        }
-
-        System.out.println("======values=======");
-        Collection<String> values = map.values();
-        Iterator<String> stringIterator=values.iterator();
-        while (stringIterator.hasNext()) {
-            System.out.println(stringIterator.next());
-        }
-
-        System.out.println("======entrySet=======");
-        for (Map.Entry<Integer, String> entry : map.entrySet()) {
-            System.out.println(entry);
-        }
-    }
-}
-```
-
-# 4 源码分析
-```java
-    //默认的初始容量16,且实际容量是2的整数幂 
-    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
-    
-    //最大容量(传入容量过大将被这个值替换)
-    static final int MAXIMUM_CAPACITY = 1 << 30;
-     
-    // 默认加载因子为0.75(当表达到3/4满时,才会再散列),这个因子在时间和空间代价之间达到了平衡.更高的因子可以降低表所需的空间,但是会增加查找代价,而查找是最频繁操作
-    static final float DEFAULT_LOAD_FACTOR = 0.75f;
-
-	//桶的树化阈值：即 链表转成红黑树的阈值，在存储数据时，当链表长度 >= 8时，则将链表转换成红黑树
-    static final int TREEIFY_THRESHOLD = 8;
-   // 桶的链表还原阈值：即 红黑树转为链表的阈值，当在扩容（resize（））时（HashMap的数据存储位置会重新计算），在重新计算存储位置后，当原有的红黑树内数量 <= 6时，则将 红黑树转换成链表
-    static final int UNTREEIFY_THRESHOLD = 6;
-   //最小树形化容量阈值：即 当哈希表中的容量 > 该值时，才允许树形化链表 （即 将链表 转换成红黑树）
-```
-因为红黑树的平均查找长度是log(n)，长度为8的时候，平均查找长度为3，如果继续使用链表，平均查找长度为8/2=4，这才有转换为树的必要
-链表长度如果是小于等于6，6/2=3，虽然速度也很快的，但是转化为树结构和生成树的时间并不会太短
-
-还有选择6和8，中间有个差值7可以有效防止链表和树频繁转换
-假设一下，如果设计成链表个数超过8则链表转换成树结构，链表个数小于8则树结构转换成链表，如果一个HashMap不停的插入、删除元素，链表个数在8左右徘徊，就会频繁的发生树转链表、链表转树，效率会很低。
-```
-    // 为了避免扩容/树形化选择的冲突，这个值不能小于 4 * TREEIFY_THRESHOLD
-    // 小于该值时使用的是扩容哦!!!
-    static final int MIN_TREEIFY_CAPACITY = 64;
-    
-    // 存储数据的Node数组,长度是2的幂.    
-    // HashMap采用链表法解决冲突，每一个Node本质上是一个单向链表 
-    //HashMap底层存储的数据结构,是一个Node数组.上面得知Node类为元素维护了一个单向链表.至此,HashMap存储的数据结构也就很清晰了:维护了一个数组,每个数组又维护了一个单向链表.之所以这么设计,考虑到遇到哈希冲突的时候,同index的value值就用单向链表来维护
-    //与 JDK 1.7 的对比（Entry类），仅仅只是换了名字
-    transient Node<K,V>[] table;
-
-    // HashMap的底层数组中已用槽的数量 
-    transient int size;
-    // HashMap的阈值，用于判断是否需要调整HashMap的容量（threshold = 容量*加载因子） 
-    int threshold;
-    
-    // 负载因子实际大小
-    final float loadFactor;
-    
-    // HashMap被改变的次数 
-    transient int modCount;
-    
-    // 指定“容量大小”和“加载因子”的构造函数,是最基础的构造函数
-    public HashMap(int initialCapacity, float loadFactor) {
-        if (initialCapacity < 0)
-            throw new IllegalArgumentException("Illegal initial capacity: " +
-                                               initialCapacity);
-        // HashMap的最大容量只能是MAXIMUM_CAPACITY                                       
-        if (initialCapacity > MAXIMUM_CAPACITY)
-            initialCapacity = MAXIMUM_CAPACITY;
-        //负载因子须大于0
-        if (loadFactor <= 0 || Float.isNaN(loadFactor))
-            throw new IllegalArgumentException("Illegal load factor: " +
-                                               loadFactor);
-        // 设置"负载因子"                                        
-        this.loadFactor = loadFactor;
-        // 设置"HashMap阈值",当HashMap中存储数据的数量达到threshold时,就需将HashMap的容量加倍    
-        this.threshold = tableSizeFor(initialCapacity);
-    }
-```
-
-- 上面的tableSizeFor有何用?
-tableSizeFor方法保证函数返回值是大于等于给定参数initialCapacity最小的2的幂次方的数值
-```
-    static final int tableSizeFor(int cap) {
-        int n = cap - 1;
-        n |= n >>> 1;
-        n |= n >>> 2;
-        n |= n >>> 4;
-        n |= n >>> 8;
-        n |= n >>> 16;
-        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
-    }
-```
- 可以看出该方法是一系列的二进制位操作
-
->a |= b 等同于 a = a|b
-
-逐行分析
-- `int n = cap - 1`
-给定的cap 减 1,为了避免参数cap本来就是2的幂次方,这样一来,经过后续操作，cap将会变成2 * cap,是不符合我们预期的
-
-- `n |= n >>> 1`
-n >>> 1 : n无符号右移1位,即n二进制最高位的1右移一位
-n | (n >>> 1) 导致 n二进制的高2位值为1
-目前n的高1~2位均为1
-- `n |= n >>> 2`
-n继续无符号右移2位
-n | (n >>> 2) 导致n二进制表示的高3~4位经过运算值均为1
-目前n的高1~4位均为1
-- `n |= n >>> 4`
-n继续无符号右移4位
-n | (n >>> 4) 导致n二进制表示的高5~8位经过运算值均为1
-目前n的高1~8位均为1
-- `n |= n >>> 8`
-n继续无符号右移8位
-n | (n >>> 8) 导致n二进制表示的高9~16位经过运算值均为1
-目前n的高1~16位均为1	
-
-可以看出,无论给定cap(cap < MAXIMUM_CAPACITY )的值是多少,经过以上运算,其值的二进制所有位都会是1.再将其加1,这时候这个值一定是2的幂次方.
-当然如果经过运算值大于MAXIMUM_CAPACITY,直接选用MAXIMUM_CAPACITY.
-![例子](https://img-blog.csdnimg.cn/img_convert/e58fef4c158d1c978777f5aa40ebee5e.png)
-至此tableSizeFor如何保证cap为2的幂次方已经显而易见了,那么问题来了
-
-## 4.1 **为什么cap要保持为2的幂次方？**
-主要与HashMap中的数据存储有关.
-
-在Java8中,HashMap中key的Hash值由Hash(key)方法计得
-![](https://img-blog.csdnimg.cn/img_convert/0882f5d36b225a33c5e17666f5fb6695.png)
-
-HashMap中存储数据table的index是由key的Hash值决定的.
-在HashMap存储数据时,我们期望数据能均匀分布,以防止哈希冲突.
-自然而然我们就会想到去用`%`取余操作来实现我们这一构想
-
->取余(%)操作 : 如果除数是2的幂次则等价于与其除数减一的与(&)操作.
-
-这也就解释了为什么一定要求cap要为2的幂次方.再来看看table的index的计算规则：
-![](https://img-blog.csdnimg.cn/img_convert/d8df9e11f3a143218a08f515ba6e805e.png)
- 等价于:
-```
- index = e.hash % newCap
-```
-采用二进制位操作&,相对于%,能够提高运算效率,这就是cap的值被要求为2幂次的原因
-![](https://img-blog.csdnimg.cn/img_convert/6fcd40c3f37371a2a9ebb2a209f3be58.png)
-![数据结构 & 参数与 JDK 7 / 8](https://img-blog.csdnimg.cn/img_convert/3d946e0e1bd86c8ae41c1d6857377445.png)
-## 4.2 **Node类**
-
-```
-static class Node<K,V> implements Map.Entry<K,V> {
-        final int hash;
-        final K key;
-        V value;
-        Node<K,V> next;
-
-        Node(int hash, K key, V value, Node<K,V> next) {
-            this.hash = hash;
-            this.key = key;
-            this.value = value;
-            this.next = next;
-        }
-
-        public final K getKey()        { return key; }
-        public final V getValue()      { return value; }
-        public final String toString() { return key + "=" + value; }
-
-        public final int hashCode() {
-            return Objects.hashCode(key) ^ Objects.hashCode(value);
-        }
-
-        public final V setValue(V newValue) {
-            V oldValue = value;
-            value = newValue;
-            return oldValue;
-        }
-
-        public final boolean equals(Object o) {
-            if (o == this)
-                return true;
-            if (o instanceof Map.Entry) {
-                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
-                if (Objects.equals(key, e.getKey()) &&
-                    Objects.equals(value, e.getValue()))
-                    return true;
-            }
-            return false;
-        }
-    }
-```
-Node<K,V> 类是HashMap中的静态内部类,实现Map.Entry<K,V>接口.定义了key键、value值、next节点,也就是说元素之间构成了单向链表.
-
-## 4.3 TreeNode
-```
-static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
-        TreeNode<K,V> parent;  // red-black tree links
-        TreeNode<K,V> left;
-        TreeNode<K,V> right;
-        TreeNode<K,V> prev;    // needed to unlink next upon deletion
-        boolean red;
-        TreeNode(int hash, K key, V val, Node<K,V> next) {}
-
-        // 返回当前节点的根节点  
-        final TreeNode<K,V> root() {  
-          for (TreeNode<K,V> r = this, p;;) {  
-            if ((p = r.parent) == null)  
-                return r;  
-            r = p;  
-        }  
-    } 
- }
-```
-红黑树结构包含前、后、左、右节点，以及标志是否为红黑树的字段
-此结构是Java8新加的
-
-## 4.4 hash方法
-Java 8中的散列值优化函数
-![](https://img-blog.csdnimg.cn/img_convert/5a02fb83605c5435e8585b2c37175e69.png)
-只做一次16位右位移异或
-key.hashCode()函数调用的是key键值类型自带的哈希函数，返回int型散列值
-
-理论上散列值是一个int型，如果直接拿散列值作为下标访问HashMap主数组的话，考虑到2进制32位带符号的int范围大概40亿的映射空间。只要哈希函数映射得比较均匀松散，一般应用是很难出现碰撞的。
-但问题是一个40亿长度的数组，内存是放不下的.HashMap扩容之前的数组初始大小才16,所以这个散列值是不能直接拿来用的.
-用之前还要先做对数组的长度取模运算，得到的余数才能用来访问数组下标
-源码中模运算就是把散列值和数组长度做一个"与"操作，
-![](https://img-blog.csdnimg.cn/img_convert/d92c8b227a759d2c17736bc2b6403d57.png)
-这也正好解释了为什么HashMap的数组长度要取2的整次幂
-因为这样（数组长度-1）正好相当于一个“低位掩码”
-“与”操作的结果就是散列值的高位全部归零，只保留低位值，用来做数组下标访问
-
-以初始长度16为例，16-1=15
-2进制表示是00000000 00000000 00001111
-和某散列值做“与”操作如下，结果就是截取了最低的四位值
-![](https://img-blog.csdnimg.cn/img_convert/553e83380e5587fd4e40724b373d79e4.png)
-但这时候问题就来了,这样就算我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重
-
-这时候“扰动函数”的价值就体现出来了
-![](https://img-blog.csdnimg.cn/img_convert/b2051fb82b033621ca8d154861ff5c15.png)
-右位移16位，正好是32位一半，自己的高半区和低半区做异或，就是为了混合原始hashCode的高位和低位，以此来加大低位的随机性
-而且混合后的低位掺杂了高位的部分特征，这样高位的信息也被变相保留下来。
-
-index的运算规则是
-```
-e.hash & (newCap - 1)
-```
-newCap是2的幂,所以newCap - 1的高位全0
-
-若e.hash值只用自身的hashcode,index只会和e.hash的低位做&操作.这样一来,index的值就只有低位参与运算,高位毫无存在感,从而会带来哈希冲突的风险
-所以在计算key的hashCode时,用其自身hashCode与其低16位做异或操作
-这也就让高位参与到index的计算中来了,即降低了哈希冲突的风险又不会带来太大的性能问题
-
-## 4.5 Put方法
-![](https://img-blog.csdnimg.cn/img_convert/da29710b339ad70260610e2ed358305f.png)
-
-![](https://img-blog.csdnimg.cn/img_convert/977877dab11a1fcd23dffadecbe6b2cd.png)
-
-![HashMap-put(k,v)](https://img-blog.csdnimg.cn/img_convert/0df25cf27e264797c7f1451e11c927f1.png)
-①.判断键值对数组table[i]是否为空或为null，否则执行resize()进行扩容
-
-②.根据键值key计算hash值得到插入的数组索引i，如果table[i]==null，直接新建节点添加，转向⑥，如果table[i]不为空，转向③
-
-③.判断table[i]的首个元素是否和key一样，如果相同直接覆盖value，否则转向④，这里的相同指的是hashCode以及equals
-
-④.判断table[i] 是否为treeNode，即table[i] 是否是红黑树，如果是红黑树，则直接在树中插入键值对，否则转向⑤
-
-⑤.遍历table[i]，判断链表长度是否大于8，大于8的话把链表转换为红黑树，在红黑树中执行插入操作，否则进行链表的插入操作；遍历过程中若发现key已经存在直接覆盖value即可
-
-⑥.插入成功后，判断实际存在的键值对数量size是否超多了最大容量threshold，如果超过，执行resize()扩容
-```
- public V put(K key, V value) {
-        // 对key的hashCode()做hash
-        return putVal(hash(key), key, value, false, true);
-    }
-    
-final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
-        Node<K,V>[] tab; Node<K,V> p; int n, i;
-        // 步骤① tab为空则调用resize()初始化创建
-        if ((tab = table) == null || (n = tab.length) == 0)         
-            n = (tab = resize()).length;
-        // 步骤② 计算index,并对null做处理  
-        //tab[i = (n - 1) & hash对应下标的第一个节点   
-        if ((p = tab[i = (n - 1) & hash]) == null)
-            // 无哈希冲突的情况下,将value直接封装为Node并赋值
-            tab[i] = newNode(hash, key, value, null);
-        else {
-            Node<K,V> e; K k;
-            // 步骤③ 节点的key相同,直接覆盖节点
-            if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
-                e = p;
-            // 步骤④ 判断该链为红黑树    
-            else if (p instanceof TreeNode)
-                 // p是红黑树类型，则调用putTreeVal方式赋值
-                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
-            // 步骤⑤ p非红黑树类型,该链为链表    
-            else {
-                // index 相同的情况下
-                for (int binCount = 0; ; ++binCount) {
-                    if ((e = p.next) == null) {
-                        // 如果p的next为空,将新的value值添加至链表后面
-                        p.next = newNode(hash, key, value, null);
-                        if (binCount >= TREEIFY_THRESHOLD - 1)
-                            // 如果链表长度大于8,链表转化为红黑树,执行插入
-                            treeifyBin(tab, hash);
-                        break;
-                    }
-                    // key相同则跳出循环
-                    if (e.hash == hash &&  ((k = e.key) == key || (key != null && key.equals(k))))
-                        break;
-                    //就是移动指针方便继续取 p.next
-    
-                    p = e;
-                }
-            }
-            if (e != null) { // existing mapping for key
-                V oldValue = e.value;
-                //根据规则选择是否覆盖value
-                if (!onlyIfAbsent || oldValue == null)
-                    e.value = value;
-                afterNodeAccess(e);
-                return oldValue;
-            }
-        }
-        ++modCount;
-        // 步骤⑥:超过最大容量,就扩容
-        if (++size > threshold)
-            // size大于加载因子,扩容
-            resize();
-        afterNodeInsertion(evict);
-        return null;
-    }
-```
-在构造函数中最多也只是设置了initialCapacity、loadFactor的值,并没有初始化table,table的初始化工作是在put方法中进行的.
-## 4.6 resize
-![](https://img-blog.csdnimg.cn/img_convert/24a22ebcda082c0c13df1c2193ee18d6.png)
-扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,内部的数组无法装载更多的元素时,就需要扩大数组的长度.
-当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组
-```
-   /**
-     * 该函数有2种使用情况：1.初始化哈希表 2.当前数组容量过小，需扩容
-     */
-final Node<K,V>[] resize() {
-        Node<K,V>[] oldTab = table;
-        int oldCap = (oldTab == null) ? 0 : oldTab.length;
-        int oldThr = threshold;
-        int newCap, newThr = 0;
-
-        // 针对情况2：若扩容前的数组容量超过最大值，则不再扩充
-        if (oldCap > 0) {
-            if (oldCap >= MAXIMUM_CAPACITY) {
-                threshold = Integer.MAX_VALUE;
-                return oldTab;
-            }
-            // 针对情况2：若无超过最大值，就扩充为原来的2倍
-            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
-                     oldCap >= DEFAULT_INITIAL_CAPACITY)
-                //newCap设置为oldCap的2倍并小于MAXIMUM_CAPACITY，且大于默认值, 新的threshold增加为原来的2倍
-                newThr = oldThr << 1; // double threshold
-        }
-     
-        // 针对情况1：初始化哈希表（采用指定 or 默认值）
-        else if (oldThr > 0) // initial capacity was placed in threshold
-            // threshold>0, 将threshold设置为newCap,所以要用tableSizeFor方法保证threshold是2的幂次方
-            newCap = oldThr;
-        else {               // zero initial threshold signifies using defaults
-            // 默认初始化
-            newCap = DEFAULT_INITIAL_CAPACITY;
-            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
-        }
-
-        // 计算新的resize上限
-        if (newThr == 0) {
-            // newThr为0，newThr = newCap * 0.75
-            float ft = (float)newCap * loadFactor;
-            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
-                      (int)ft : Integer.MAX_VALUE);
-        }
-        threshold = newThr;
-        @SuppressWarnings({"rawtypes","unchecked"})
-            // 新生成一个table数组
-            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
-        table = newTab;
-        if (oldTab != null) {
-            // oldTab 复制到 newTab
-            for (int j = 0; j < oldCap; ++j) {
-                Node<K,V> e;
-                if ((e = oldTab[j]) != null) {
-                    oldTab[j] = null;
-                    if (e.next == null)
-                       // 链表只有一个节点，直接赋值
-                       //为什么要重新Hash呢？因为长度扩大以后，Hash的规则也随之改变。
-                        newTab[e.hash & (newCap - 1)] = e;
-                    else if (e instanceof TreeNode)
-                        // e为红黑树的情况
-                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
-                    else { // preserve order链表优化重hash的代码块
-                        Node<K,V> loHead = null, loTail = null;
-                        Node<K,V> hiHead = null, hiTail = null;
-                        Node<K,V> next;
-                        do {
-                            next = e.next;
-                            // 原索引
-                            if ((e.hash & oldCap) == 0) {
-                                if (loTail == null)
-                                    loHead = e;
-                                else
-                                    loTail.next = e;
-                                loTail = e;
-                            }
-                            // 原索引 + oldCap
-                            else {
-                                if (hiTail == null)
-                                    hiHead = e;
-                                else
-                                    hiTail.next = e;
-                                hiTail = e;
-                            }
-                        } while ((e = next) != null);
-                        // 原索引放到bucket里
-                        if (loTail != null) {
-                            loTail.next = null;
-                            newTab[j] = loHead;
-                        }
-                        // 原索引+oldCap放到bucket里
-                        if (hiTail != null) {
-                            hiTail.next = null;
-                            newTab[j + oldCap] = hiHead;
-                        }
-                    }
-                }
-            }
-        }
-        return newTab;
-    }
-```
-![图片发自简书App](https://img-blog.csdnimg.cn/img_convert/dd74408150f2d30938f08296a6501d71.png)
-
-
-
-## 4.7 remove方法
-remove(key) 方法 和 remove(key, value) 方法都是通过调用removeNode的方法来实现删除元素的
-```java
- final Node<K,V> removeNode(int hash, Object key, Object value,
-                               boolean matchValue, boolean movable) {
-        Node<K,V>[] tab; Node<K,V> p; int n, index;
-        if ((tab = table) != null && (n = tab.length) > 0 &&
-            (p = tab[index = (n - 1) & hash]) != null) {
-            Node<K,V> node = null, e; K k; V v;
-            if (p.hash == hash &&
-                ((k = p.key) == key || (key != null && key.equals(k))))
-                // index 元素只有一个元素
-                node = p;
-            else if ((e = p.next) != null) {
-                if (p instanceof TreeNode)
-                    // index处是一个红黑树
-                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
-                else {
-                    // index处是一个链表，遍历链表返回node
-                    do {
-                        if (e.hash == hash &&
-                            ((k = e.key) == key ||
-                             (key != null && key.equals(k)))) {
-                            node = e;
-                            break;
-                        }
-                        p = e;
-                    } while ((e = e.next) != null);
-                }
-            }
-            // 分不同情形删除节点
-            if (node != null && (!matchValue || (v = node.value) == value ||
-                                 (value != null && value.equals(v)))) {
-                if (node instanceof TreeNode)
-                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
-                else if (node == p)
-                    tab[index] = node.next;
-                else
-                    p.next = node.next;
-                ++modCount;
-                --size;
-                afterNodeRemoval(node);
-                return node;
-            }
-        }
-        return null;
-    }
-```
-## 4.8 get
-```java
-/**
-   * 函数原型
-   * 作用：根据键key，向HashMap获取对应的值
-   */ 
-   map.get(key)；
-
-
- /**
-   * 源码分析
-   */ 
-   public V get(Object key) {
-    Node<K,V> e;
-    // 1. 计算需获取数据的hash值
-    // 2. 通过getNode（）获取所查询的数据 ->>分析1
-    // 3. 获取后，判断数据是否为空
-    return (e = getNode(hash(key), key)) == null ? null : e.value;
-}
-
-/**
-   * 分析1：getNode(hash(key), key))
-   */ 
-final Node<K,V> getNode(int hash, Object key) {
-    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
-
-    // 1. 计算存放在数组table中的位置
-    if ((tab = table) != null && (n = tab.length) > 0 &&
-        (first = tab[(n - 1) & hash]) != null) {
-
-        // 4. 通过该函数，依次在数组、红黑树、链表中查找（通过equals（）判断）
-        // a. 先在数组中找，若存在，则直接返回
-        if (first.hash == hash && // always check first node
-            ((k = first.key) == key || (key != null && key.equals(k))))
-            return first;
-
-        // b. 若数组中没有，则到红黑树中寻找
-        if ((e = first.next) != null) {
-            // 在树中get
-            if (first instanceof TreeNode)
-                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
-
-            // c. 若红黑树中也没有，则通过遍历，到链表中寻找
-            do {
-                if (e.hash == hash &&
-                    ((k = e.key) == key || (key != null && key.equals(k))))
-                    return e;
-            } while ((e = e.next) != null);
-        }
-    }
-    return null;
-}
-```
-> 在JDK1.7及以前的版本中，HashMap里是没有红黑树的实现的，在JDK1.8中加入了红黑树是为了防止哈希表碰撞攻击，当链表链长度为8时，及时转成红黑树，提高map的效率
-
-如果某个桶中的记录过大的话（当前是TREEIFY_THRESHOLD = 8），HashMap会动态的使用一个专门的treemap实现来替换掉它。这样做的结果会更好，是O(logn)，而不是糟糕的O(n)。它是如何工作的？
-前面产生冲突的那些KEY对应的记录只是简单的追加到一个链表后面，这些记录只能通过遍历来进行查找。但是超过这个阈值后HashMap开始将列表升级成一个二叉树，使用哈希值作为树的分支变量，如果两个哈希值不等，但指向同一个桶的话，较大的那个会插入到右子树里。如果哈希值相等，HashMap希望key值最好是实现了Comparable接口的，这样它可以按照顺序来进行插入。这对HashMap的key来说并不是必须的，不过如果实现了当然最好。如果没有实现这个接口，在出现严重的哈希碰撞的时候，你就并别指望能获得性能提升了。
-
-这个性能提升有什么用处？比方说恶意的程序，如果它知道我们用的是哈希算法，它可能会发送大量的请求，导致产生严重的哈希碰撞。然后不停的访问这些key就能显著的影响服务器的性能，这样就形成了一次拒绝服务攻击（DoS）。JDK 8中从O(n)到O(logn)的飞跃，可以有效地防止类似的攻击，同时也让HashMap性能的可预测性稍微增强了一些
-```java
-/**
-   * 源码分析：resize(2 * table.length)
-   * 作用：当容量不足时（容量 > 阈值），则扩容（扩到2倍）
-   */ 
-   void resize(int newCapacity) {  
-
-    // 1. 保存旧数组（old table） 
-    Entry[] oldTable = table;  
-
-    // 2. 保存旧容量（old capacity ），即数组长度
-    int oldCapacity = oldTable.length; 
-
-    // 3. 若旧容量已经是系统默认最大容量了，那么将阈值设置成整型的最大值，退出    
-    if (oldCapacity == MAXIMUM_CAPACITY) {  
-        threshold = Integer.MAX_VALUE;  
-        return;  
-    }  
-
-    // 4. 根据新容量（2倍容量）新建1个数组，即新table  
-    Entry[] newTable = new Entry[newCapacity];  
-
-    // 5. （重点分析）将旧数组上的数据（键值对）转移到新table中，从而完成扩容 ->>分析1.1 
-    transfer(newTable); 
-
-    // 6. 新数组table引用到HashMap的table属性上
-    table = newTable;  
-
-    // 7. 重新设置阈值  
-    threshold = (int)(newCapacity * loadFactor); 
-} 
-
- /**
-   * 分析1.1：transfer(newTable); 
-   * 作用：将旧数组上的数据（键值对）转移到新table中，从而完成扩容
-   * 过程：按旧链表的正序遍历链表、在新链表的头部依次插入
-   */ 
-void transfer(Entry[] newTable) {
-      // 1. src引用了旧数组
-      Entry[] src = table; 
-
-      // 2. 获取新数组的大小 = 获取新容量大小                 
-      int newCapacity = newTable.length;
-
-      // 3. 通过遍历 旧数组，将旧数组上的数据（键值对）转移到新数组中
-      for (int j = 0; j < src.length; j++) { 
-          // 3.1 取得旧数组的每个元素  
-          Entry<K,V> e = src[j];           
-          if (e != null) {
-              // 3.2 释放旧数组的对象引用（for循环后，旧数组不再引用任何对象）
-              src[j] = null; 
-
-              do { 
-                  // 3.3 遍历 以该数组元素为首 的链表
-                  // 注：转移链表时，因是单链表，故要保存下1个结点，否则转移后链表会断开
-                  Entry<K,V> next = e.next; 
-                 // 3.3 重新计算每个元素的存储位置
-                 int i = indexFor(e.hash, newCapacity); 
-                 // 3.4 将元素放在数组上：采用单链表的头插入方式 = 在链表头上存放数据 = 将数组位置的原有数据放在后1个指针、将需放入的数据放到数组位置中
-                 // 即 扩容后，可能出现逆序：按旧链表的正序遍历链表、在新链表的头部依次插入
-                 e.next = newTable[i]; 
-                 newTable[i] = e;  
-                 // 访问下1个Entry链上的元素，如此不断循环，直到遍历完该链表上的所有节点
-                 e = next;             
-             } while (e != null);
-             // 如此不断循环，直到遍历完数组上的所有数据元素
-         }
-     }
- }
-```
-从上面可看出：在扩容resize（）过程中，在将旧数组上的数据 转移到 新数组上时，转移数据操作 = 按旧链表的正序遍历链表、在新链表的头部依次插入，即在转移数据、扩容后，容易出现链表逆序的情况
-
->`设重新计算存储位置后不变，即扩容前 = 1->2->3，扩容后 = 3->2->1`
-
-此时若并发执行 put 操作，一旦出现扩容情况，则 容易出现 环形链表，从而在获取数据、遍历链表时 形成死循环（Infinite Loop），即死锁
-![](https://img-blog.csdnimg.cn/img_convert/cd3bbd816bcefb360280b591d5cf41cf.png)
-![image.png](https://img-blog.csdnimg.cn/img_convert/cb6354c50e5af1ef24c1ab36b802217e.png)
-![](https://img-blog.csdnimg.cn/img_convert/5e35431f5f3c179e389f5528e1b03d42.png)
-![为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键](https://img-blog.csdnimg.cn/img_convert/38321f907a385a91616ff972204237d4.png)
-## 4.9 getOrDefault
-getOrDefault() 方法获取指定 key 对应对 value，如果找不到 key ，则返回设置的默认值。
-![](https://img-blog.csdnimg.cn/55e1bdebe6cd4fc9aaa22d6662c501e4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-# 5 单线程rehash
-单线程情况下，rehash无问题
-[![HashMap rehash single thread](https://img-blog.csdnimg.cn/img_convert/50437495a5eda75989d04de58316b1f7.png)](http://www.jasongj.com/img/java/concurrenthashmap/single_thread_rehash.png)
-# 6 多线程并发下的rehash
-
-这里假设有两个线程同时执行了put操作并引发了rehash，执行了transfer方法，并假设线程一进入transfer方法并执行完next = e.next后，因为线程调度所分配时间片用完而“暂停”，此时线程二完成了transfer方法的执行。此时状态如下。
-
-[![HashMap rehash multi thread step 1](https://img-blog.csdnimg.cn/img_convert/a5aaefdb773217a54d29cbe9809e2aa9.png)](http://www.jasongj.com/img/java/concurrenthashmap/multi_thread_rehash_1.png)
-接着线程1被唤醒，继续执行第一轮循环的剩余部分
-```
-e.next = newTable[1] = null
-newTable[1] = e = key(5)
-e = next = key(9)
-```
-结果如下图所示
-[![HashMap rehash multi thread step 2](https://img-blog.csdnimg.cn/img_convert/a261863e3e27077ba7b3223a3914f0de.png)](http://www.jasongj.com/img/java/concurrenthashmap/multi_thread_rehash_2.png) 
-
-接着执行下一轮循环，结果状态图如下所示
-[![HashMap rehash multi thread step 3](https://img-blog.csdnimg.cn/img_convert/03cea3cdb5a9477ca25e98ee6f37cf43.png)](http://www.jasongj.com/img/java/concurrenthashmap/multi_thread_rehash_3.png) 
-
-继续下一轮循环，结果状态图如下所示
-[![HashMap rehash multi thread step 4](https://img-blog.csdnimg.cn/img_convert/22e59112a7635a44dff4fa42a7e6a840.png)](http://www.jasongj.com/img/java/concurrenthashmap/multi_thread_rehash_4.png) 
-
-此时循环链表形成，并且key(11)无法加入到线程1的新数组。在下一次访问该链表时会出现死循环。
-# 7 Fast-fail
-## 产生原因
-
-在使用迭代器的过程中如果HashMap被修改，那么`ConcurrentModificationException`将被抛出，也即Fast-fail策略。
-
-当HashMap的iterator()方法被调用时，会构造并返回一个新的EntryIterator对象，并将EntryIterator的expectedModCount设置为HashMap的modCount（该变量记录了HashMap被修改的次数）。
-```
-HashIterator() {
-  expectedModCount = modCount;
-  if (size > 0) { // advance to first entry
-  Entry[] t = table;
-  while (index < t.length && (next = t[index++]) == null)
-    ;
-  }
-}
-```
-
-
-在通过该Iterator的next方法访问下一个Entry时，它会先检查自己的expectedModCount与HashMap的modCount是否相等，如果不相等，说明HashMap被修改，直接抛出`ConcurrentModificationException`。该Iterator的remove方法也会做类似的检查。该异常的抛出意在提醒用户及早意识到线程安全问题。
-
-## 线程安全解决方案
-单线程条件下，为避免出现`ConcurrentModificationException`，需要保证只通过HashMap本身或者只通过Iterator去修改数据，不能在Iterator使用结束之前使用HashMap本身的方法修改数据。因为通过Iterator删除数据时，HashMap的modCount和Iterator的expectedModCount都会自增，不影响二者的相等性。如果是增加数据，只能通过HashMap本身的方法完成，此时如果要继续遍历数据，需要重新调用iterator()方法从而重新构造出一个新的Iterator，使得新Iterator的expectedModCount与更新后的HashMap的modCount相等。
-
-多线程条件下，可使用`Collections.synchronizedMap`方法构造出一个同步Map，或者直接使用线程安全的ConcurrentHashMap。
\ No newline at end of file
diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock\346\272\220\347\240\201\350\247\243\346\236\220.md"
index ad32141823..a1b5860985 100644
--- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock\346\272\220\347\240\201\350\247\243\346\236\220.md"
+++ "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ReentrantLock\346\272\220\347\240\201\350\247\243\346\236\220.md"
@@ -74,79 +74,90 @@ ReentrantLock 就负责实现这些接口，使用时，直接调用的也是这
 ## 4.2 FairSync - 公平锁
 只实现 *lock* 和 *tryAcquire* 两个方法
 ### 4.2.1 lock
-lock 方法加锁成功，直接返回，所以可以继续执行业务逻辑。
 - 公平模式的 lock
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzE3LzE3MjIxYzJhNGEyZmViNzY?x-oss-process=image/format,png)
+
 直接调用 acquire，而没有像非公平模式先试图获取，因为这样可能导致违反“公平”的语义：在已等待在队列中的线程之前获取了锁。
-*acquire* 是 AQS 的方法，表示先尝试获得锁，失败之后进入同步队列阻塞等待。
+*acquire* 是 AQS 的方法，表示先尝试获得锁，失败之后进入同步队列阻塞等待，详情见本专栏的上一文
 ### 4.2.2 tryAcquire
+公平模式的 *tryAcquire*。不要授予访问权限，除非递归调用或没有等待线程或是第一个调用的。
 - 该方法是 AQS 在 acquire 方法中留给子类去具体实现的
-![](https://img-blog.csdnimg.cn/20210705225313380.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-#### 公平模式
-不要授予访问权限，除非递归调用或没有等待线程或是第一个调用的。
+![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzE3LzE3MjIxYzJhMjJmYmVkZWM?x-oss-process=image/format,png)
+
+话不多说，看源码：
 ```java
 protected final boolean tryAcquire(int acquires) {
-	// 获取当前的线程
+	 // 获取当前的线程
     final Thread current = Thread.currentThread();
-    // 获取 state 锁的状态（volatile 读语义）
+    // 获取 state 锁的状态
     int c = getState();
     // state == 0 => 尚无线程获取锁
     if (c == 0) {
-    	// 判断 AQS 的同步对列里是否有线程等待
+    	// 判断 AQS 的同步对列里是否有线程等待，若没有则直接 CAS 获取锁
         if (!hasQueuedPredecessors() &&
-        	// 若没有则直接 CAS（保证原子性，线程安全） 获取锁
             compareAndSetState(0, acquires)) {
             // 获取锁成功，设置独占线程
             setExclusiveOwnerThread(current);
             return true;
         }
     }
-    // 已经获取锁的是否为当前的线程？
+    // 判断已经获取锁是否为当前的线程
     else if (current == getExclusiveOwnerThread()) {
     	// 锁的重入, 即 state 加 1
         int nextc = c + acquires;
         if (nextc < 0)
             throw new Error("Maximum lock count exceeded");
-        // 已经获取 lock，所以这里不考虑并发       
         setState(nextc);
         return true;
     }
     return false;
 }
 ```
-和Sync#nonfairTryAcquire类似，唯一不同的是当发现锁未被占用时，使用 **hasQueuedPredecessors** 确保了公平性。
+和 Sync 的 *nonfairTryAcquire* 方法实现类似，唯一不同的是当发现锁未被占用时，使用 *hasQueuedPredecessors* 确保了公平性。
 #### hasQueuedPredecessors
-判断当前线程是不是属于同步队列的头节点的下一个节点(头节点是释放锁的节点)：
-- 是(返回false)，符合FIFO，可以获得锁
-- 不是(返回true)，则继续等待
+会判断当前线程是不是属于同步队列的头节点的下一个节点(头节点是释放锁的节点)
+- 如果是(返回false)，符合FIFO，可以获得锁
+- 如果不是(返回true)，则继续等待
 ```java
-public final boolean hasQueuedPredecessors() {
-    // 这种方法的正确性取决于头在尾之前初始化和头初始化。如果当前线程是队列中的第一个线程，则next是精确的
-    Node t = tail; // 按反初始化顺序读取字段
-    Node h = head;
-    Node s;
-    return h != t &&
-        ((s = h.next) == null || s.thread != Thread.currentThread());
-}
+    public final boolean hasQueuedPredecessors() {
+        // 这种方法的正确性取决于头在尾之前初始化和头初始化。如果当前线程是队列中的第一个线程，则next是精确的
+        Node t = tail; // 按反初始化顺序读取字段
+        Node h = head;
+        Node s;
+        return h != t &&
+            ((s = h.next) == null || s.thread != Thread.currentThread());
+    }
 ```
+
+
+
 # 5 nonfairTryAcquire
-执行非公平的 *tryLock*。 *tryAcquire* 是在子类中实现的，但是都需要对*trylock* 方法进行非公平的尝试。
+执行非公平的 *tryLock*。 
+*tryAcquire* 是在子类中实现的，但是都需要对*trylock* 方法进行非公平的尝试。
+
 ```java
 final boolean nonfairTryAcquire(int acquires) {
+	// 获取当前的线程
     final Thread current = Thread.currentThread();
+    // 获取 AQS 中的 state 字段
     int c = getState();
+    // state 为 0，表示同步器的锁尚未被持有
     if (c == 0) {
-    	// 这里可能有竞争，所以可能失败
+    	// CAS state 获取锁(这里可能有竞争，所以可能失败)
         if (compareAndSetState(0, acquires)) {
         	// 获取锁成功, 设置获取独占锁的线程
             setExclusiveOwnerThread(current);
+            // 直接返回 true
             return true;
         }
     }
+    // 判断现在获取独占锁的线程是否为当前线程（可重入锁的体现）
     else if (current == getExclusiveOwnerThread()) {
+    	// state 计数加1(重入获取锁)
         int nextc = c + acquires;
-        if (nextc < 0)
-            throw new Error("Maximum lock count exceeded"); 
+        if (nextc < 0) // 整型溢出
+            throw new Error("Maximum lock count exceeded");
+        // 已经获取 lock，所以这里不考虑并发    
         setState(nextc);
         return true;
     }
@@ -154,7 +165,6 @@ final boolean nonfairTryAcquire(int acquires) {
 }
 ```
 无参的 *tryLock* 调用的就是此方法
-
 # 6  tryLock
 ##  6.1 无参
 Lock 接口中定义的方法。
@@ -166,6 +176,19 @@ Lock 接口中定义的方法。
 如果当前线程已经持有该锁，那么持有计数将增加1，方法返回true。
 如果锁被另一个线程持有，那么这个方法将立即返回值false。
 
+- 典型的使用方法
+```java
+ Lock lock = ...;
+ if (lock.tryLock()) {
+   try {
+     // manipulate protected state
+   } finally {
+     lock.unlock();
+   }
+ } else {
+   // 执行可选的操作
+ }
+```
 ## 6.2 有参
 - 提供了超时时间的入参，在时间内，仍没有得到锁，会返回 false
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzE3LzE3MjIxYzJhMzZhNTAwMzQ?x-oss-process=image/format,png)
@@ -191,4 +214,6 @@ protected final boolean tryRelease(int releases) {
     setState(c);
     return free;
 }
-```
\ No newline at end of file
+```
+# 8 总结
+AQS 搭建了整个锁架构，子类锁的实现只需要根据场景，实现 AQS 对应的方法即可。
\ No newline at end of file
diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ThreadLocal.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ThreadLocal.md"
deleted file mode 100644
index 5f5a4c37b5..0000000000
--- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/ThreadLocal.md"
+++ /dev/null
@@ -1,246 +0,0 @@
-
-# 1 前言
-
-此类提供线程本地变量，与普通变量不同，因为每个访问一个变量（通过其get或set方法）的线程都有其自己的，独立初始化的变量副本。 
-ThreadLocal   实例通常是期望将状态与线程(例如，用户ID或事务ID)关联的类中的 private static 字段。
-
-例如，下面的类生成每个线程本地的唯一标识符。线程的ID是在第一次调用ThreadId.get() 时赋值的，并且在以后的调用中保持不变。
-
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdiMDdiMDMzNw?x-oss-process=image/format,png) 
- 
-只要线程是活跃的并且 ThreadLocal 实例是可访问的，则每个线程都对其线程本地变量的副本持有隐式的引用。线程消失后，线程本地实例的所有副本都会被 GC（除非存在对这些副本的其他引用）。
-
-# 2 继续体系
-- 继承?不存在的,这其实也是 java.lang 包下的工具类，但是 ThreadLocal 定义带有泛型，说明可以储存任意格式的数据。
-![](https://img-blog.csdnimg.cn/20210615235535658.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
- 
-# 3 属性
-ThreadLocal 依赖于附加到每个线程（Thread.threadLocals和InheritableThreadLocals）的线程线性探测哈希表。
-
-## threadLocalHashCode
-ThreadLocal 对象充当key，通过 threadLocalHashCode 进行搜索。这是一个自定义哈希码(仅在ThreadLocalMaps 中有用)，它消除了在相同线程使用连续构造的threadlocal的常见情况下的冲突，而在不太常见的情况下仍然表现良好。
-
-ThreadLocal 通过这样的 hashCode，计算当前 ThreadLocal 在 ThreadLocalMap 中的索引
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdiNDcxM2Q2Mw?x-oss-process=image/format,png) 
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdhZjZjMDQwMg?x-oss-process=image/format,png) 
-
-- 连续生成的哈希码之间的差值，该值的设定参考文章[ThreadLocal的hash算法（关于 0x61c88647）](https://juejin.im/post/5cced289f265da03804380f2)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdiNmUwNjA0NQ?x-oss-process=image/format,png) 
-
-- 注意 static 修饰。ThreadLocalMap 会被 set 多个 ThreadLocal ，而多个 ThreadLocal 就根据 threadLocalHashCode 区分
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91c2VyLWdvbGQtY2RuLnhpdHUuaW8vMjAyMC81LzcvMTcxZWZhYzdkMTkzOWUwMw?x-oss-process=image/format,png) 
-# 4 ThreadLocalMap
-自定义的哈希表，仅适用于维护线程本地的值。没有操作导出到ThreadLocal类之外。
-该类包私有，允许在 Thread 类中的字段声明。为帮助处理非常长的使用寿命，哈希表节点使用 WeakReferences 作为key。
-但由于不使用引用队列，因此仅在表空间不足时，才保证删除过时的节点。
-```java
-static class ThreadLocalMap {
-
-        /**
-         * 此哈希表中的节点使用其主引用字段作为key（始终是一个 ThreadLocal 对象），继承了 WeakReference。 
-         * 空键（即entry.get（）== null）意味着不再引用该键，因此可以从表中删除该节点。             
-         * 在下面的代码中，此类节点称为 "stale entries"
-         */
-        static class Entry extends WeakReference<ThreadLocal<?>> {
-            /** 与此 ThreadLocal 关联的值 */
-            Object value;
-
-            Entry(ThreadLocal<?> k, Object v) {
-                super(k);
-                value = v;
-            }
-        }
-        
-        private static final int INITIAL_CAPACITY = 16;
-
-        private Entry[] table;
-        
-        private int size = 0;
-
-        private int threshold; // 默认为 0
-```
-## 特点
-- key 是 ThreadLocal 的引用
-- value 是 ThreadLocal 保存的值
-- 数组的数据结构
-# 5 set
-## 5.1 ThreadLocal#set
-将此线程本地变量的当前线程副本设置为指定值。子类无需重写此方法，而仅依靠initialValue方法设置线程本地变量的值。
-![](https://img-blog.csdnimg.cn/20210616000550930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-### 执行流程
-1. 获取当前线程
-2. 获取线程所对应的ThreadLocalMap。每个线程都是独立的，所以该方法天然线程安全
-3. 判断 map 是否为 null
-    - 否，K.V 对赋值，k 为this（即当前的 ThreaLocal 对象）
-    - 是，初始化一个 ThreadLocalMap 来维护 K.V 对
-
-来具体看看ThreadLocalMap中的 set
-
-## 5.2 ThreadLocalMap#set
-```java
-private void set(ThreadLocal<?> key, Object value) {
-    // 新引用指向 table
-    Entry[] tab = table;
-    int len = tab.length;
-    // 获取对应 ThreadLocal 在table 中的索引
-    int i = key.threadLocalHashCode & (len-1);
-
-    /**
-     * 从该下标开始循环遍历
-     * 1、如遇相同key，则直接替换value
-     * 2、如果该key已经被回收失效，则替换该失效的key
-     */
-    for (Entry e = tab[i];
-         e != null; 
-         e = tab[i = nextIndex(i, len)]) {
-        ThreadLocal<?> k = e.get();
-        // 找到内存地址一样的 ThreadLocal，直接替换
-        if (k == key) {
-            e.value = value;
-            return;
-        }
-        // 若 k 为 null,说明 ThreadLocal 被清理了,则替换当前失效的 k
-        if (k == null) {
-            replaceStaleEntry(key, value, i);
-            return;
-        }
-    }
-    // 找到空位，创建节点并插入
-    tab[i] = new Entry(key, value);
-    // table内元素size自增
-    int sz = ++size;
-    // 达到阈值(数组大小的三分之二)时，执行扩容
-    if (!cleanSomeSlots(i, sz) && sz >= threshold)
-        rehash();
-}
-```
-注意通过 hashCode 计算的索引位置 i 处如果已经有值了，会从 i 开始，通过 +1 不断的往后寻找，直到找到索引位置为空的地方，把当前 ThreadLocal 作为 key 放进去。
-
-# 6 get 
-```java
-public T get() {
-    // 获取当前线程
-    Thread t = Thread.currentThread();
-    // 获取当前线程对应的ThreadLocalMap
-    ThreadLocalMap map = getMap(t);
-
-    // 如果map不为空
-    if (map != null) {
-        // 取得当前ThreadLocal对象对应的Entry
-        ThreadLocalMap.Entry e = map.getEntry(this);
-        // 如果不为空，读取当前 ThreadLocal 中保存的值
-        if (e != null) {
-            @SuppressWarnings("unchecked")
-            T result = (T)e.value;
-            return result;
-        }
-    }
-    // 否则都执行 setInitialValue
-    return setInitialValue();
-}
-```
-### setInitialValue
-```java
-private T setInitialValue() {
-    // 获取初始值，一般是子类重写
-    T value = initialValue();
-
-    // 获取当前线程
-    Thread t = Thread.currentThread();
-
-    // 获取当前线程对应的ThreadLocalMap
-    ThreadLocalMap map = getMap(t);
-
-    // 如果map不为null
-    if (map != null)
-
-        // 调用ThreadLocalMap的set方法进行赋值
-        map.set(this, value);
-
-    // 否则创建个ThreadLocalMap进行赋值
-    else
-        createMap(t, value);
-    return value;
-}
-```
-
-接着我们来看下 
-## ThreadLocalMap#getEntry
-```java
-// 得到当前 thradLocal 对应的值，值的类型是由 thradLocal 的泛型决定的
-// 由于 thradLocalMap set 时解决数组索引位置冲突的逻辑，导致 thradLocalMap get 时的逻辑也是对应的
-// 首先尝试根据 hashcode 取模数组大小-1 = 索引位置 i 寻找，找不到的话，自旋把 i+1，直到找到索引位置不为空为止
-private Entry getEntry(ThreadLocal<?> key) {
-    // 计算索引位置：ThreadLocal 的 hashCode 取模数组大小-1
-    int i = key.threadLocalHashCode & (table.length - 1);
-    Entry e = table[i];
-    // e 不为空，并且 e 的 ThreadLocal 的内存地址和 key 相同，直接返回，否则就是没有找到，继续通过 getEntryAfterMiss 方法找
-    if (e != null && e.get() == key)
-        return e;
-    else
-    // 这个取数据的逻辑，是因为 set 时数组索引位置冲突造成的  
-        return getEntryAfterMiss(key, i, e);
-}
-// 自旋 i+1，直到找到为止
-private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
-    Entry[] tab = table;
-    int len = tab.length;
-    // 在大量使用不同 key 的 ThreadLocal 时，其实还蛮耗性能的
-    while (e != null) {
-        ThreadLocal<?> k = e.get();
-        // 内存地址一样，表示找到了
-        if (k == key)
-            return e;
-        // 删除没用的 key
-        if (k == null)
-            expungeStaleEntry(i);
-        // 继续使索引位置 + 1
-        else
-            i = nextIndex(i, len);
-        e = tab[i];
-    }
-    return null;
-}
-```
-# 6 扩容
-ThreadLocalMap 中的 ThreadLocal 的个数超过阈值时，ThreadLocalMap 就要开始扩容了，我们一起来看下扩容的逻辑：
-```java
-private void resize() {
-    // 拿出旧的数组
-    Entry[] oldTab = table;
-    int oldLen = oldTab.length;
-    // 新数组的大小为老数组的两倍
-    int newLen = oldLen * 2;
-    // 初始化新数组
-    Entry[] newTab = new Entry[newLen];
-    int count = 0;
-    // 老数组的值拷贝到新数组上
-    for (int j = 0; j < oldLen; ++j) {
-        Entry e = oldTab[j];
-        if (e != null) {
-            ThreadLocal<?> k = e.get();
-            if (k == null) {
-                e.value = null; // Help the GC
-            } else {
-                // 计算 ThreadLocal 在新数组中的位置
-                int h = k.threadLocalHashCode & (newLen - 1);
-                // 如果索引 h 的位置值不为空，往后+1，直到找到值为空的索引位置
-                while (newTab[h] != null)
-                    h = nextIndex(h, newLen);
-                // 给新数组赋值
-                newTab[h] = e;
-                count++;
-            }
-        }
-    }
-    // 给新数组初始化下次扩容阈值，为数组长度的三分之二
-    setThreshold(newLen);
-    size = count;
-    table = newTab;
-}
-```
-扩容时是绝对没有线程安全问题的，因为 ThreadLocalMap 是线程的一个属性，一个线程同一时刻只能对 ThreadLocalMap 进行操作，因为同一个线程执行业务逻辑必然是串行的，那么操作 ThreadLocalMap 必然也是串行的。
-# 7 总结
-我们在写中间件的时候经常会用到，比如说流程引擎中上下文的传递，调用链ID的传递等。
\ No newline at end of file
diff --git "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/\343\200\220\346\255\273\347\243\225JDK\346\272\220\347\240\201\343\200\221ThreadPoolExecutor\346\272\220\347\240\201\344\277\235\345\247\206\347\272\247\350\257\246\350\247\243.md" "b/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/\343\200\220\346\255\273\347\243\225JDK\346\272\220\347\240\201\343\200\221ThreadPoolExecutor\346\272\220\347\240\201\344\277\235\345\247\206\347\272\247\350\257\246\350\247\243.md"
deleted file mode 100644
index 6968385400..0000000000
--- "a/JDK/JDK\346\272\220\347\240\201\350\247\243\346\236\220/\343\200\220\346\255\273\347\243\225JDK\346\272\220\347\240\201\343\200\221ThreadPoolExecutor\346\272\220\347\240\201\344\277\235\345\247\206\347\272\247\350\257\246\350\247\243.md"
+++ /dev/null
@@ -1,686 +0,0 @@
-位运算表示线程池状态，因为位运算是改变当前值的一种高效手段。
-
-# 属性
-##  线程池状态
-Integer 有32位：
-- 最左边3位表示线程池状态，可表示从0至7的8个不同数值
-- 最右边29位表工作线程数
-```java
-private static final int COUNT_BITS = Integer.SIZE - 3;
-```
-
-
-线程池的状态用高3位表示，其中包括了符号位。五种状态的十进制值按从小到大依次排序为：
-
-```bash
-RUNNING < SHUTDOWN < STOP < TIDYING <TERMINATED
-```
-这就能通过比较值的大小来确定线程池的状态。
-
-源码中经常出现通过isRunning判断
-![](https://img-blog.csdnimg.cn/20210713164331545.png)
-
-000-1111111111111111111111111;
-类似于子网掩码，用于与运算。得到左3位 or 右29位
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzMzA5XzQ2ODU5NjgtNmM3Zjg5NDExNjk3MmEyZi5wbmc?x-oss-process=image/format,png)
-
-- 111 - 0000000000000000000000000000(十进制: -536, 870, 912);
-该状态表示：线程池能接受新任务
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzEzXzQ2ODU5NjgtZTgzOGM1NmI5OTU0NGU5My5wbmc?x-oss-process=image/format,png)
-- 000 - 0000000000000000000000000(十进制: 0);
-不再接受新任务，但可继续执行队列中的任务
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzMDI1XzQ2ODU5NjgtZDM0NDc2NzhiNzdjODUxZi5wbmc?x-oss-process=image/format,png)
-
-- 001 - 00000000000000000000000000(十进制: 536,870， 912);
-全面拒绝，并中断正在处理的任务
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODcyXzQ2ODU5NjgtNmMzOTlhMjhkZTBmMzc1Yi5wbmc?x-oss-process=image/format,png)
-
-- 010 - 00000000000000000000000000.(十进制值: 1, 073, 741, 824);
-所有任务已经被终止
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODE0XzQ2ODU5NjgtZTEzNDFjY2FlM2Q3MzQ3My5wbmc?x-oss-process=image/format,png)
-
-- 101 - 000000000000000000000000000(十进制值: 1, 610,612, 736)
-已清理完现场
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzEzXzQ2ODU5NjgtMmI4M2VmMmEwZjI4YjU3MC5wbmc?x-oss-process=image/format,png)
-
-### 与运算
-000 - 11111111111111111111 位掩码，用于后续的掩码参与计算。
-```java
-private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
-```
-
-比如：001 - 000000000000000000000100011 表 35 个工作线程。
-掩码取反：111 - 00000000000000000000000，即得到左边3位001;
-表示线程池当前处于**STOP**状态
-
-```java
-private static int runStateOf(int c)     { return c & ~CAPACITY; }
-```
-同理掩码 000 - 11111111111111111111，得到右29位，即工作线程数：
-```java
-private static int workerCountOf(int c)  { return c & CAPACITY; }
-```
-
-
-把左3位与右29位或运算,合并成一个值
-
-```java
-private static int ctlOf(int rs, int wc) { return rs | wc; }
-```
-
-线程池状态控制器，保存了线程池的状态、工作线程数
-```java
-private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
-```
-
-我们都知道`Executor`接口有且只有一个方法`execute()`，通过参数传入待执行线程的对象。
-## execute
-线程池执行任务
-```java
-public void execute(Runnable command) {
-    if (command == null)
-        throw new NullPointerException();
-    /**
-     * 分 3 步：
-     * 2. 如果一个任务可以成功排队，那么仍需double-check我们是否应该添加一个线程（因为自上次检查以来现有线程已死亡）或池在进入此方法后关闭.所以我们重新检查状态，并在必要时回滚.如果停止，或者如果没有，则启动一个新线程
-     * 3. 如果我们无法将任务排队，则尝试添加一个新线程。如果失败，我们知道我们已经关闭或饱和，因此拒绝该任务
-     */
-    int c = ctl.get();
-
-    // 1. 若工作线程数 < 核心线程数,则创建新线程并执行当前任务
-    if (workerCountOf(c) < corePoolSize) {
-        if (addWorker(command, true))
-	        return;
-         // 若创建失败,为防止外部已经在线程池中加入新任务,在此重新获取一下
-         c = ctl.get();
-     }
-```
-`execute`方法在不同的阶段有三次`addWorker`的尝试动作。
-```java
-// 若 工作线程数 >=核心线程数 或线程创建失败,则将当前任务放到工作队列中
-// 只有线程池处于 RUNNING 态,才执行后半句 : 置入队列
-if (isRunning(c) && workQueue.offer(command)) {
-    int recheck = ctl.get();
-    
-    // 只有线程池处于 RUNNING 态,才执行后半句 : 置入队列
-    if (! isRunning(recheck) && remove(command))
-        reject(command);
-    // 若之前的线程已被消费完,新建一个线程
-    else if (workerCountOf(recheck) == 0)
-        addWorker(null, false);
-// 核心线程和队列都已满,尝试创建一个新线程
-}
-else if (!addWorker(command, false))
-    // 抛出RejectedExecutionException异常
-    // 若 addWorker 返回是 false,即创建失败,则唤醒拒绝策略.
-    reject(command);
-}
-```
-发生拒绝的理由有两个
-( 1 )线程池状态为非RUNNING状态
-(2)等待队列已满。
-
-下面继续分析`addWorker`
-
-## addWorker 源码解析
-原子性地检查 runState 和 workerCount，通过返回 false 来防止在不应该添加线程时出现误报。
-
-根据当前线程池状态，检查是否可以添加新的线程：
-- 若可
-则创建并启动任务;若一切正常则返回true;
-- 返回false的可能原因：
-1. 线程池没有处`RUNNING`态
-2. 线程工厂创建新的任务线程失败
-### 参数
-- firstTask
-外部启动线程池时需要构造的第一个线程，它是线程的母体
-- core
-新增工作线程时的判断指标
-     - true
-需要判断当前`RUNNING`态的线程是否少于`corePoolsize`
-    - false
-需要判断当前`RUNNING`态的线程是否少于`maximumPoolsize`
-### JDK8源码
-```java
-private boolean addWorker(Runnable firstTask, boolean core) {
-	// 1. 不需要任务预定义的语法标签，响应下文的continue retry
-	// 	快速退出多层嵌套循环
-    retry:
-    // 外自旋,判断线程池的运行状态
-    for (;;) {
-        int c = ctl.get();
-        int rs = runStateOf(c);
-		// 2. 若RUNNING态,则条件为false,不执行后面判断
-		//  若STOP及以上状态,或firstTask初始线程非空,或队列为空
-		//  都会直接返回创建失败
-        // Check if queue empty only if necessary.
-        if (rs >= SHUTDOWN &&
-            ! (rs == SHUTDOWN &&
-               firstTask == null &&
-               ! workQueue.isEmpty()))
-            return false;
-
-        for (;;) {
-            int wc = workerCountOf(c);
-            // 若超过最大允许线程数，则不能再添加新线程
-            if (wc >= CAPACITY ||
-                wc >= (core ? corePoolSize : maximumPoolSize))
-                return false;
-            // 3. 将当前活动线程数+1
-            if (compareAndIncrementWorkerCount(c))
-                break retry;
-			// 线程池状态和工作线程数是可变化的，需经常读取最新值
-            c = ctl.get();  // Re-read ctl
-            // 若已关闭，则再次从retry 标签处进入，在第2处再做判断(第4处)
-            if (runStateOf(c) != rs)
-                continue retry;
-            //如果线程池还是RUNNING态,说明仅仅是第3处失败
-//继续循环执行(第5外)    
-            // else CAS failed due to workerCount change; retry inner loop
-        }
-    }
-    
-	// 开始创建工作线程
-    boolean workerStarted = false;
-    boolean workerAdded = false;
-    Worker w = null;
-    try {
-        // 利用Worker 构造方法中的线程池工厂创建线程,并封装成工作线程Worker对象
-        // 和 AQS 有关！！！
-        w = new Worker(firstTask);
-        // 6. 注意这是Worker中的属性对象thread
-        final Thread t = w.thread;
-        if (t != null) {
-        	// 在进行ThreadpoolExecutor的敏感操作时
-        	// 都需要持有主锁，避免在添加和启动线程时被干扰
-            final ReentrantLock mainLock = this.mainLock;
-            mainLock.lock();
-            try {
-                // Recheck while holding lock.
-                // Back out on ThreadFactory failure or if
-                // shut down before lock acquired.
-                int rs = runStateOf(ctl.get());
-				// 当线程池状态为RUNNING 或SHUTDOWN
-				// 且firstTask 初始线程为空时
-                if (rs < SHUTDOWN ||
-                    (rs == SHUTDOWN && firstTask == null)) {
-                    if (t.isAlive()) // precheck that t is startable
-                        throw new IllegalThreadStateException();
-                    workers.add(w);
-                    int s = workers.size();
-                    // 整个线程池在运行期间的最大并发任务个数
-                    if (s > largestPoolSize)
-                    	// 更新为工作线程的个数
-                        largestPoolSize = s;
-                    // 新增工作线程成功    
-                    workerAdded = true;
-                }
-            } finally {
-                mainLock.unlock();
-            }
-            if (workerAdded) {
-            	// 看到亲切迷人的start方法了!
-            	// 这并非线程池的execute 的command 参数指向的线程
-                t.start();
-                workerStarted = true;
-            }
-        }
-    } finally {
-	    // 线程启动失败，把刚才第3处加，上的工作线程计数再减-回去
-        if (! workerStarted)
-            addWorkerFailed(w);
-    }
-    return workerStarted;
-}
-```
-#### 第1处
-配合循环语句出现的标签，类似于goto语法作用。label 定义时，必须把标签和冒号的组合语句紧紧相邻定义在循环体之前，否则编译报错。目的是在实现多重循环时能够快速退出到任何一层。出发点似乎非常贴心，但在大型软件项目中，滥用标签行跳转的后果将是无法维护的！
-
-
-在 **workerCount** 加1成功后，直接退出两层循环。
-
-#### 第2处,这样的表达式不利于阅读,应如是
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzg1XzQ2ODU5NjgtMDg2ZTlkNWY5ZGEyYWZkNC5wbmc?x-oss-process=image/format,png)
-
-#### 第3处
-与第1处的标签呼应,`AtomicInteger`对象的加1操作是原子性的。`break retry`表 直接跳出与`retry` 相邻的这个循环体
- 
-#### 第4处
-此`continue`跳转至标签处,继续执行循环.
-如果条件为false,则说明线程池还处于运行状态,即继续在`for(;)`循环内执行.
-
-#### 第5处
-`compareAndIncrementWorkerCount `方法执行失败的概率非常低.
-即使失败,再次执行时成功的概率也是极高的,类似于自旋原理.
-这里是先加1,创建失败再减1,这是轻量处理并发创建线程的方式;
-如果先创建线程,成功再加1,当发现超出限制后再销毁线程,那么这样的处理方式明显比前者代价要大.
-
-#### 第6处
-`Worker `对象是工作线程的核心类实现。它实现了`Runnable`接口,并把本对象作为参数输入给`run()`中的`runWorker (this)`。所以内部属性线程`thread`在`start`的时候，即会调用`runWorker`。
-
-```java
-private final class Worker
-    extends AbstractQueuedSynchronizer
-    implements Runnable
-{
-    /**
-     * This class will never be serialized, but we provide a
-     * serialVersionUID to suppress a javac warning.
-     */
-    private static final long serialVersionUID = 6138294804551838833L;
-
-    /** Thread this worker is running in.  Null if factory fails. */
-    final Thread thread;
-    /** Initial task to run.  Possibly null. */
-    Runnable firstTask;
-    /** Per-thread task counter */
-    volatile long completedTasks;
-
-    /**
-     * Creates with given first task and thread from ThreadFactory.
-     * @param firstTask the first task (null if none)
-     */
-    Worker(Runnable firstTask) {
-        setState(-1); // 直到调用runWorker前，禁止被中断
-        this.firstTask = firstTask;
-        this.thread = getThreadFactory().newThread(this);
-    }
-
-    /** 将主线程的 run 循环委托给外部的 runWorker 执行 */
-    public void run() {
-        runWorker(this);
-    }
-
-    // Lock methods
-    //
-    // The value 0 represents the unlocked state.
-    // The value 1 represents the locked state.
-
-    protected boolean isHeldExclusively() {
-        return getState() != 0;
-    }
-
-    protected boolean tryAcquire(int unused) {
-        if (compareAndSetState(0, 1)) {
-            setExclusiveOwnerThread(Thread.currentThread());
-            return true;
-        }
-        return false;
-    }
-
-    protected boolean tryRelease(int unused) {
-        setExclusiveOwnerThread(null);
-        setState(0);
-        return true;
-    }
-
-    public void lock()        { acquire(1); }
-    public boolean tryLock()  { return tryAcquire(1); }
-    public void unlock()      { release(1); }
-    public boolean isLocked() { return isHeldExclusively(); }
-
-    void interruptIfStarted() {
-        Thread t;
-        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
-            try {
-                t.interrupt();
-            } catch (SecurityException ignore) {
-            }
-        }
-    }
-}
-```
-#### setState(-1)是为何
-设置个简单的状态，检查状态以防止中断。在调用停止线程池时会判断state 字段，决定是否中断之。
-![](https://img-blog.csdnimg.cn/20210713174701301.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20210713175625198.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20210713175645371.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20210713175718745.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-#### t 到底是谁？
-![](https://img-blog.csdnimg.cn/20210713180707150.png)
-# 源码分析
-```java
-   /**
-	 * 检查是否可以根据当前池状态和给定的边界（核心或最大)
-     * 添加新工作线程。如果是这样,工作线程数量会相应调整，如果可能的话,一个新的工作线程创建并启动
-     * 将firstTask作为其运行的第一项任务。
-     * 如果池已停止此方法返回false
-     * 如果线程工厂在被访问时未能创建线程,也返回false
-     * 如果线程创建失败，或者是由于线程工厂返回null，或者由于异常（通常是在调用Thread.start（）后的OOM）），我们干净地回滚。
-	 */
-    private boolean addWorker(Runnable firstTask, boolean core) { 
-    /**
-     * Check if queue empty only if necessary.
-     * 
-     * 如果线程池已关闭，并满足以下条件之一，那么不创建新的 worker：
-     *      1. 线程池状态大于 SHUTDOWN，也就是 STOP, TIDYING, 或 TERMINATED
-     *      2. firstTask != null
-     *      3. workQueue.isEmpty()
-     * 简单分析下：
-     *      状态控制的问题，当线程池处于 SHUTDOWN ，不允许提交任务，但是已有任务继续执行
-     *      当状态大于 SHUTDOWN ，不允许提交任务，且中断正在执行任务
-     *      多说一句：若线程池处于 SHUTDOWN，但 firstTask 为 null，且 workQueue 非空，是允许创建 worker 的
-     *  
-     */
-            if (rs >= SHUTDOWN &&
-                ! (rs == SHUTDOWN &&
-                   firstTask == null &&
-                   ! workQueue.isEmpty()))
-                return false;
-
-            for (;;) {
-                int wc = workerCountOf(c);
-                if (wc >= CAPACITY ||
-                    wc >= (core ? corePoolSize : maximumPoolSize))
-                    return false;
-                // 如果成功，那么就是所有创建线程前的条件校验都满足了，准备创建线程执行任务
-                // 这里失败的话，说明有其他线程也在尝试往线程池中创建线程
-                if (compareAndIncrementWorkerCount(c))
-                    break retry;
-                // 由于有并发，重新再读取一下 ctl
-                c = ctl.get();  // Re-read ctl
-                // 正常如果是 CAS 失败的话，进到下一个里层的for循环就可以了
-                // 可如果是因为其他线程的操作，导致线程池的状态发生了变更，如有其他线程关闭了这个线程池
-                // 那么需要回到外层的for循环
-                if (runStateOf(c) != rs)
-                    continue retry;
-                // else CAS failed due to workerCount change; retry inner loop
-            }
-        }
-
-     /* *
-        * 到这里，我们认为在当前这个时刻，可以开始创建线程来执行任务
-        */
-         
-        // worker 是否已经启动
-        boolean workerStarted = false;
-        // 是否已将这个 worker 添加到 workers 这个 HashSet 中
-        boolean workerAdded = false;
-        Worker w = null;
-        try {
-           // 把 firstTask 传给 worker 的构造方法
-            w = new Worker(firstTask);
-            // 取 worker 中的线程对象，Worker的构造方法会调用 ThreadFactory 来创建一个新的线程
-            final Thread t = w.thread;
-            if (t != null) {
-               //先加锁
-                final ReentrantLock mainLock = this.mainLock;
-                // 这个是整个类的全局锁，持有这个锁才能让下面的操作“顺理成章”，
-                // 因为关闭一个线程池需要这个锁，至少我持有锁的期间，线程池不会被关闭
-                mainLock.lock();
-                try {
-                    // Recheck while holding lock.
-                    // Back out on ThreadFactory failure or if
-                    // shut down before lock acquired.
-                    int rs = runStateOf(ctl.get());
-
-                    // 小于 SHUTTDOWN 即 RUNNING
-                    // 如果等于 SHUTDOWN，不接受新的任务，但是会继续执行等待队列中的任务
-                    if (rs < SHUTDOWN ||
-                        (rs == SHUTDOWN && firstTask == null)) {
-                        // worker 里面的 thread 不能是已启动的
-                        if (t.isAlive()) // precheck that t is startable
-                            throw new IllegalThreadStateException();
-                        // 加到 workers 这个 HashSet 中
-                        workers.add(w);
-                        int s = workers.size();
-                        if (s > largestPoolSize)
-                            largestPoolSize = s;
-                        workerAdded = true;
-                    }
-                } finally {
-                    mainLock.unlock();
-                }
-               // 若添加成功
-                if (workerAdded) {
-                    // 启动线程
-                    t.start();
-                    workerStarted = true;
-                }
-            }
-        } finally {
-            // 若线程没有启动，做一些清理工作，若前面 workCount 加了 1，将其减掉
-            if (! workerStarted)
-                addWorkerFailed(w);
-        }
-        // 返回线程是否启动成功
-        return workerStarted;
-    }
-```
-看下  `addWorkFailed` 
-![](https://img-blog.csdnimg.cn/20210714141244398.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-
-![记录 workers 中的个数的最大值,因为 workers 是不断增加减少的，通过这个值可以知道线程池的大小曾经达到的最大值](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzA4XzQ2ODU5NjgtMDc4NDcyYjY4MmZjYzljZC5wbmc?x-oss-process=image/format,png)
-继续看
-### runWorker
-```java
-//  worker 线程启动后调用,while 循环(即自旋!)不断从等待队列获取任务并执行
-//  worker 初始化时，可指定 firstTask，那么第一个任务也就可以不需要从队列中获取
-final void runWorker(Worker w) {
-    Thread wt = Thread.currentThread();
-    // 该线程的第一个任务(若有)
-    Runnable task = w.firstTask;
-    w.firstTask = null;
-    // 允许中断
-    w.unlock(); 
- 
-    boolean completedAbruptly = true;
-    try {
-        // 循环调用 getTask 获取任务
-        while (task != null || (task = getTask()) != null) {
-            w.lock();          
-            // 若线程池状态大于等于 STOP，那么意味着该线程也要中断
-              /**
-               * 若线程池STOP，请确保线程 已被中断
-               * 如果没有，请确保线程未被中断
-               * 这需要在第二种情况下进行重新检查，以便在关中断时处理shutdownNow竞争
-               */
-            if ((runStateAtLeast(ctl.get(), STOP) ||
-                 (Thread.interrupted() &&
-                  runStateAtLeast(ctl.get(), STOP))) &&
-                !wt.isInterrupted())
-                wt.interrupt();
-            try {
-                // 这是一个钩子方法，留给需要的子类实现
-                beforeExecute(wt, task);
-                Throwable thrown = null;
-                try {
-                    // 到这里终于可以执行任务了
-                    task.run();
-                } catch (RuntimeException x) {
-                    thrown = x; throw x;
-                } catch (Error x) {
-                    thrown = x; throw x;
-                } catch (Throwable x) {
-                    // 这里不允许抛出 Throwable，所以转换为 Error
-                    thrown = x; throw new Error(x);
-                } finally {
-                    // 也是一个钩子方法，将 task 和异常作为参数，留给需要的子类实现
-                    afterExecute(task, thrown);
-                }
-            } finally {
-                // 置空 task，准备 getTask 下一个任务
-                task = null;
-                // 累加完成的任务数
-                w.completedTasks++;
-                // 释放掉 worker 的独占锁
-                w.unlock();
-            }
-        }
-        completedAbruptly = false;
-    } finally {
-        // 到这里，需要执行线程关闭
-        // 1. 说明 getTask 返回 null，也就是说，这个 worker 的使命结束了，执行关闭
-        // 2. 任务执行过程中发生了异常
-        //    第一种情况，已经在代码处理了将 workCount 减 1，这个在 getTask 方法分析中说
-        //    第二种情况，workCount 没有进行处理，所以需要在 processWorkerExit 中处理
-        processWorkerExit(w, completedAbruptly);
-    }
-}
-```
-看看
-### getTask()
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzgwXzQ2ODU5NjgtNWU5NDc3MzE5M2Q5Y2Y0OS5wbmc?x-oss-process=image/format,png)
-```java
-// 此方法有三种可能
-// 1. 阻塞直到获取到任务返回。默认 corePoolSize 之内的线程是不会被回收的，它们会一直等待任务
-// 2. 超时退出。keepAliveTime 起作用的时候，也就是如果这么多时间内都没有任务，那么应该执行关闭
-// 3. 如果发生了以下条件，须返回 null
-//     池中有大于 maximumPoolSize 个 workers 存在(通过调用 setMaximumPoolSize 进行设置)
-//     线程池处于 SHUTDOWN，而且 workQueue 是空的，前面说了，这种不再接受新的任务
-//     线程池处于 STOP，不仅不接受新的线程，连 workQueue 中的线程也不再执行
-private Runnable getTask() {
-    boolean timedOut = false; // Did the last poll() time out?
-
-   for (;;) {
-   // 允许核心线程数内的线程回收，或当前线程数超过了核心线程数，那么有可能发生超时关闭
- 
-            // 这里 break，是为了不往下执行后一个 if (compareAndDecrementWorkerCount(c))
-            // 两个 if 一起看：如果当前线程数 wc > maximumPoolSize，或者超时，都返回 null
-            // 那这里的问题来了，wc > maximumPoolSize 的情况，为什么要返回 null？
-            // 换句话说，返回 null 意味着关闭线程。
-            // 那是因为有可能开发者调用了 setMaximumPoolSize 将线程池的 maximumPoolSize 调小了
-        
-            // 如果此 worker 发生了中断，采取的方案是重试
-            // 解释下为什么会发生中断，这个读者要去看 setMaximumPoolSize 方法，
-            // 如果开发者将 maximumPoolSize 调小了，导致其小于当前的 workers 数量，
-            // 那么意味着超出的部分线程要被关闭。重新进入 for 循环，自然会有部分线程会返回 null
-            int c = ctl.get();
-            int rs = runStateOf(c);
-
-            // Check if queue empty only if necessary.
-            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
-                // CAS 操作，减少工作线程数
-                decrementWorkerCount();
-                return null;
-            }
-
-            int wc = workerCountOf(c);
-
-            // Are workers subject to culling?
-            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
-
-            if ((wc > maximumPoolSize || (timed && timedOut))
-                && (wc > 1 || workQueue.isEmpty())) {
-                if (compareAndDecrementWorkerCount(c))
-                    return null;
-                continue;
-            }
-
-            try {
-                Runnable r = timed ?
-                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
-                    workQueue.take();
-                if (r != null)
-                    return r;
-                timedOut = true;
-            } catch (InterruptedException retry) {
-            // 如果此 worker 发生了中断，采取的方案是重试
-            // 解释下为什么会发生中断，这个读者要去看 setMaximumPoolSize 方法，
-            // 如果开发者将 maximumPoolSize 调小了，导致其小于当前的 workers 数量，
-            // 那么意味着超出的部分线程要被关闭。重新进入 for 循环，自然会有部分线程会返回 null
-                timedOut = false;
-            }
-        }
-}
-```
-到这里，基本上也说完了整个流程，回到 execute(Runnable command) 方法，看看各个分支，我把代码贴过来一下：
-```java
-    public void execute(Runnable command) {
-        if (command == null)
-            throw new NullPointerException();
-        //表示 “线程池状态” 和 “线程数” 的整数
-        int c = ctl.get();
-        // 如果当前线程数少于核心线程数，直接添加一个 worker 执行任务，
-        // 创建一个新的线程，并把当前任务 command 作为这个线程的第一个任务(firstTask)
-        if (workerCountOf(c) < corePoolSize) {
-        // 添加任务成功，即结束
-        // 执行的结果，会包装到 FutureTask 
-        // 返回 false 代表线程池不允许提交任务
-            if (addWorker(command, true))
-                return;
-           
-            c = ctl.get();
-        }
-        // 到这说明，要么当前线程数大于等于核心线程数，要么刚刚 addWorker 失败
- 
-        // 如果线程池处于 RUNNING ，把这个任务添加到任务队列 workQueue 中
-        if (isRunning(c) && workQueue.offer(command)) {
-            /* 若任务进入 workQueue，我们是否需要开启新的线程
-             * 线程数在 [0, corePoolSize) 是无条件开启新线程的
-             * 若线程数已经大于等于 corePoolSize，则将任务添加到队列中，然后进到这里
-             */
-            int recheck = ctl.get();
-            // 若线程池不处于 RUNNING ，则移除已经入队的这个任务，并且执行拒绝策略
-            if (! isRunning(recheck) && remove(command))
-                reject(command);
-            // 若线程池还是 RUNNING ，且线程数为 0，则开启新的线程
-            // 这块代码的真正意图：担心任务提交到队列中了，但是线程都关闭了
-            else if (workerCountOf(recheck) == 0)
-                addWorker(null, false);
-        }
-        // 若 workQueue 满，到该分支
-        // 以 maximumPoolSize 为界创建新 worker，
-        // 若失败，说明当前线程数已经达到 maximumPoolSize，执行拒绝策略
-        else if (!addWorker(command, false))
-            reject(command);
-    }
-```
-**工作线程**:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行.我们可以从Worker类的run()方法里看到这点
-
-```java
-  public void run() {
-        try {
-            Runnable task = firstTask;
-            firstTask = null;
-            while (task != null || (task = getTask()) != null) {
-                runTask(task);
-                task = null;
-            }
-        } finally {
-            workerDone(this);
-        }
-    }
- boolean workerStarted = false;
-        boolean workerAdded = false;
-        Worker w = null;
-        try {
-            w = new Worker(firstTask);
-
-            final Thread t = w.thread;
-            if (t != null) {
-               //先加锁
-                final ReentrantLock mainLock = this.mainLock;
-                mainLock.lock();
-                try {
-                    // Recheck while holding lock.
-                    // Back out on ThreadFactory failure or if
-                    // shut down before lock acquired.
-                    int rs = runStateOf(ctl.get());
-
-                    if (rs < SHUTDOWN ||
-                        (rs == SHUTDOWN && firstTask == null)) {
-                        if (t.isAlive()) // precheck that t is startable
-                            throw new IllegalThreadStateException();
-                        workers.add(w);
-                        int s = workers.size();
-                        if (s > largestPoolSize)
-                            largestPoolSize = s;
-                        workerAdded = true;
-                    }
-                } finally {
-                    mainLock.unlock();
-                }
-                if (workerAdded) {
-                    t.start();
-                    workerStarted = true;
-                }
-            }
-        } finally {
-            if (! workerStarted)
-                addWorkerFailed(w);
-        }
-        return workerStarted;
-    }
-```
-线程池中的线程执行任务分两种情况
- - 在execute()方法中创建一个线程时,会让这个线程执行当前任务
- - 这个线程执行完上图中 1 的任务后,会反复从BlockingQueue获取任务来执行
\ No newline at end of file
diff --git "a/JDK/JVM/JVM\345\206\205\345\255\230\346\250\241\345\236\213.md" "b/JDK/JVM/JVM\345\256\236\346\210\230---\345\206\205\345\255\230\346\250\241\345\236\213.md"
similarity index 98%
rename from "JDK/JVM/JVM\345\206\205\345\255\230\346\250\241\345\236\213.md"
rename to "JDK/JVM/JVM\345\256\236\346\210\230---\345\206\205\345\255\230\346\250\241\345\236\213.md"
index 949dedcae2..0768ba81f8 100644
--- "a/JDK/JVM/JVM\345\206\205\345\255\230\346\250\241\345\236\213.md"
+++ "b/JDK/JVM/JVM\345\256\236\346\210\230---\345\206\205\345\255\230\346\250\241\345\236\213.md"
@@ -352,6 +352,17 @@ Runtime类封装java应用运行时的环境，每个java应用程序都有一
 
 对于在jvm中注册的多个关闭钩子，他们会并发执行，jvm并不能保证他们的执行顺序。 
 
+# 参考
+《码出高效》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
+
+
 
 
 
diff --git "a/JDK/JVM/JVM\345\236\203\345\234\276\346\224\266\351\233\206GC\347\256\227\346\263\225.md" "b/JDK/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\347\256\227\346\263\225.md"
similarity index 100%
rename from "JDK/JVM/JVM\345\236\203\345\234\276\346\224\266\351\233\206GC\347\256\227\346\263\225.md"
rename to "JDK/JVM/JVM\345\256\236\346\210\230---\345\236\203\345\234\276\346\224\266\351\233\206\347\256\227\346\263\225.md"
diff --git "a/JDK/JVM/JVM\345\256\236\346\210\230-\345\236\203\345\234\276\346\224\266\351\233\206\345\231\250.md" "b/JDK/JVM/JVM\345\256\236\346\210\230-\345\236\203\345\234\276\346\224\266\351\233\206\345\231\250.md"
index 49d2bd534a..7ea122e237 100644
--- "a/JDK/JVM/JVM\345\256\236\346\210\230-\345\236\203\345\234\276\346\224\266\351\233\206\345\231\250.md"
+++ "b/JDK/JVM/JVM\345\256\236\346\210\230-\345\236\203\345\234\276\346\224\266\351\233\206\345\231\250.md"
@@ -52,8 +52,9 @@ Parallel Scavenge和ParNew一样都是并行的多线程、新生代收集器,
 - ParNew收集器追求降低GC时用户线程的停顿时间,适合交互式应用,良好的反应速度提升用户体验.
 - Parallel Scavenge追求CPU吞吐量,能够在较短的时间内完成指定任务,因此适合不需要太多交互的后台运算.
 
-> 吞吐量：指用户线程运行时间占CPU总时间的比例。CPU总时间包括 : 用户线程运行时间 和 GC线程运行的时间. 
-> 因此，吞吐量越高表示用户线程运行时间越长，从而用户线程能够被快速处理完。
+>吞吐量是指用户线程运行时间占CPU总时间的比例.
+>CPU总时间包括 : 用户线程运行时间 和 GC线程运行的时间. 
+> 因此,吞吐量越高表示用户线程运行时间越长,从而用户线程能够被快速处理完.
 
 - 降低停顿时间的两种方式 
 1.在多CPU环境中使用多条GC线程,从而垃圾回收的时间减少,从而用户线程停顿的时间也减少; 
diff --git "a/JDK/JVM/JVM\347\232\204\346\240\210\344\270\212\345\210\206\351\205\215\344\270\216\351\200\203\351\200\270\345\210\206\346\236\220\357\274\210Escape Analysis\357\274\211.md" "b/JDK/JVM/JVM\347\232\204\346\240\210\344\270\212\345\210\206\351\205\215\344\270\216\351\200\203\351\200\270\345\210\206\346\236\220\357\274\210Escape Analysis\357\274\211.md"
new file mode 100644
index 0000000000..99703f884f
--- /dev/null
+++ "b/JDK/JVM/JVM\347\232\204\346\240\210\344\270\212\345\210\206\351\205\215\344\270\216\351\200\203\351\200\270\345\210\206\346\236\220\357\274\210Escape Analysis\357\274\211.md"	
@@ -0,0 +1,90 @@
+# 1 逃逸分析
+JVM中较前沿的优化技术，它与类型继承关系分析一样，并非直接优化代码，而是为其他优化措施提供依据的分析技术。
+
+## 1.1 基本原理
+分析对象动态作用域，当一个对象在方法里面被定义后，它可能
+- 被外部方法所引用
+例如作为调用参数传递给其他方法，称为方法逃逸
+- 被外部线程访问
+譬如赋值给可以在其他线程中访问的实例变量，称为线程逃逸
+
+从不逃逸 =》方法逃逸 =》线程逃逸，称为对象由低到高的不同逃逸程度。 
+
+如果能证明一个对象不会逃逸到方法或线程外（即别的方法或线程无法通过任何途径访问到该对象），或逃逸程度较低（只逃逸出方法而不逃逸出线程），则可能为这个对象实例采取不同程度的优化，如： 
+
+# 2 栈上分配（Stack Allocations）
+> 由于复杂度等原因，HotSpot中目前暂时还没有做这项优化，但一些其他的虚拟机（如Excelsior JET）使用了该优化。 
+
+JVM中，Java堆上分配创建对象的内存空间是常识，Java堆中的对象对各线程共享可见，只要持有该对象的引用，就可访问到堆中存储的对象数据。
+虚拟机的GC子系统会回收堆中不再使用的对象，但回收动作无论是标记筛选出可回收对象，还是回收和整理内存，都需耗费大量资源。
+如果确定一个对象不会逃逸出线程，那让该对象在栈上分配内存是个不错主意，对象所占用内存空间就可随栈帧出栈而销毁。
+在一般应用中，完全不会逃逸的局部对象和不会逃逸出线程的对象所占的比例很大，如果能使用栈上分配，那大量对象就会随方法结束而自动销毁，GC子系统压力会下降很多。栈上分配可支持方法逃逸，但不能支持线程逃逸。 
+
+# 3 标量替换（Scalar Replacement）
+若一个数据已经无法再分解成更小数据来表示，JVM中基础数据类型都不能再进一步分解，这些数据可被称为标量。
+相对的，如果一个数据可以继续分解，那它就被称为聚合量（Aggregate），Java 中的对象就是典型的聚合量。如果把一个Java对象拆散，根据程序访问的情况，将其用到的成员变量恢复为原始类型来访问，该过程就称为标量替换。
+假如逃逸分析能够证明一个对象不会被方法外部访问，并且该对象可被分解，那么程序真正执行时将可能不去创建该对象，而改为直接创建它的若干个被这方法使用的成员变量代替。
+将对象拆分后，除可让对象的成员变量在栈上 （栈上存储的数据，很大机会被虚拟机分配至物理机器的高速寄存器中存储）分配和读写外，还可为后续进步优化创建条件。
+标量替换可视作栈上分配一种特例，实现更简单（不用考虑对象完整结构的分配），但对逃逸程度的要求更高，它不允许对象逃逸出方法范围内。 
+
+
+# 4 同步消除（Synchronization Elimination）
+线程同步本身是一个相对耗时过程，如果逃逸分析能确定一个变量不会逃逸出线程，无法被其他线程访问，那么该变量读写肯定不会有竞争， 对该变量实施的同步措施也可安全消除。 
+关于逃逸分析的研究论文早在1999年就已经发表，但到JDK 6，HotSpot才开始支持初步逃逸分析，到现在这优化技术尚未足够成熟。
+不成熟的原因主要是逃逸分析的计算成本非常高，甚至不能保证逃逸分析带来的性能收益会高于它的消耗。要百分之百准确地判断一个对象是否会逃逸，需要进行一系列复杂的数据流敏感的过程间分析，才能确定程序各个分支执行时对此对象的影响。
+前面介绍即时编译、提前编译优劣势时提到了过程间分析这种大压力的分析算法正是即时编译的弱项。可以试想一下，如果逃逸分析完毕后发现几乎找不到几个不逃逸的对象， 那这些运行期耗用的时间就白白浪费了，所以目前虚拟机只能采用不那么准确，但时间压力相对较小的算法来完成分析。
+C和C++原生支持栈上分配（不使用new即可），而C#也支持值类型，可以自然做到标量替换（但并不会对引用类型做这种优化）。
+在灵活运用栈内存方面，确实是Java的弱项。
+在现在仍处于实验阶段的Valhalla项目里，设计了新的inline关键字用于定义Java的内联类型， 目的是实现与C#中值类型相对标的功能。有了这个标识与约束，以后逃逸分析做起来就会简单很多。 
+
+下面通过一系列Java伪代码的变化过程来模拟逃逸分析是如何工作的，向读者展示逃逸分析能够实现的效果。
+初始代码如下所示：
+
+```sql
+// 完全未优化代码 
+public int test(int x) { 
+	int xx = x + 2; 
+	Point p = new Point(xx, 42); 
+	return p.getX(); 
+}
+```
+此处省略了Point类的代码。第一步，将Point的构造函数和getX()方法进行内联优化：
+
+```sql
+ // 步骤1：构造函数内联后的样子 
+public int test(int x) { 
+	int xx = x + 2; 
+	Point p = point_memory_alloc(); // 在堆中分配P对象的示意方法 
+	p.x = xx; // Point构造函数被内联后的样子 
+	p.y = 42 
+	return p.x; // Point::getX()被内联后的样子 
+} 
+```
+
+第二步，经过逃逸分析，发现在整个test()方法的范围内Point对象实例不会发生任何程度的逃逸， 这样可以对它进行标量替换优化，把其内部的x和y直接置换出来，分解为test()方法内的局部变量，从 而避免Point对象实例被实际创建，优化后的结果如下所示：
+
+```java
+ // 步骤2：标量替换后的样子 
+ public int test(int x) { 
+ 	int xx = x + 2; 
+ 	int px = xx; 
+ 	int py = 42 
+ 	return px; 
+ } 
+```
+
+第三步，通过数据流分析，发现py的值其实对方法不会造成任何影响，那就可以放心地去做无效 代码消除得到最终优化结果，如下所示：
+
+```java
+// 步骤3：做无效代码消除后的样子 
+public int test(int x) { 
+	return x + 2; 
+}
+```
+
+从测试结果来看，实施逃逸分析后的程序在MicroBenchmarks中往往能得到不错的成绩，但是在实际的应用程序中，尤其是大型程序中反而发现实施逃逸分析可能出现效果不稳定的情况，或分析过程耗时但却无法有效判别出非逃逸对象而导致性能（即时编译的收益）下降，所以曾经在很长的一段时 间里，即使是服务端编译器，也默认不开启逃逸分析（从JDK 6 Update 23开始，服务端编译器中开始才默认开启逃逸分析。
+），甚至在某些版本（如JDK 6 Update 18）中还曾完全禁止这项优化，一直到JDK 7时这项优化才成为服务端编译器默认开启的选项。如果有需要，或者确认对程序运行有益，用户也可以使用参数-XX：+DoEscapeAnalysis来手动开启逃逸分析， 开启之后可以通过参数-XX：+PrintEscapeAnalysis来查看分析结果。有了逃逸分析支持之后，用户可使用参数-XX：+EliminateAllocations来开启标量替换，使用+XX：+EliminateLocks来开启同步消 除，使用参数-XX：+PrintEliminateAllocations查看标量的替换情况。 
+尽管目前逃逸分析技术仍在发展之中，未完全成熟，但它是即时编译器优化技术的一个重要前进 方向，在日后的Java虚拟机中，逃逸分析技术肯定会支撑起一系列更实用、有效的优化技术。
+
+# 参考
+《深入理解 Java 虚拟机》
\ No newline at end of file
diff --git "a/JDK/JVM/JVM\351\200\203\351\200\270\345\210\206\346\236\220.md" "b/JDK/JVM/JVM\351\200\203\351\200\270\345\210\206\346\236\220.md"
deleted file mode 100644
index e65e59102a..0000000000
--- "a/JDK/JVM/JVM\351\200\203\351\200\270\345\210\206\346\236\220.md"
+++ /dev/null
@@ -1,215 +0,0 @@
-1 逃逸分析
-
-
-JVM中高深的优化技术，如同类继承关系分析，该技术并非直接去优化代码，而是一种为其他优化措施提供依据的分析技术。
-
-分析对象的动态作用域，当某对象在方法里被定义后，它可能
-
-*   **方法**逃逸
-    
-    被外部方法引用，例如作为参数传递给其他方法
-    
-*   **线程**逃逸
-    
-    被外部线程访问，例如赋值给可以在其他线程中访问的实例变量
-    
-
-  
-
-所以 Java 对象由低到高的逃逸程度即为：
-
-*   **不逃逸 =》**
-    
-*   **方法逃逸 =》**
-    
-*   **线程逃逸**
-    
-
-  
-
-若能确定一个对象
-
-*   不会逃逸到方法或线程外（即其它方法、线程无法访问到该对象）
-    
-*   或逃逸程度较低（只逃逸出方法而不逃逸出线程）
-    
-
-则可为该对象实例采取不同程度的优化方案。
-
-2 优化方案
-
-  
-
-  
-
-  
-
-  
-
-2.1  栈上分配（Stack Allocations）
-
-
-  
-
-> 由于复杂度等原因，HotSpot中目前暂时还没有做这项优化，但一些其他的虚拟机（如Excelsior JET）使用了该优化。
-
-JVM的GC模块会回收堆中不再使用的对象，但如下回收动作
-
-*   标记筛选出可回收对象
-    
-*   回收和整理内存
-    
-
-都需耗费大量资源。  
-若确定一个对象不会逃逸出线程，那让该对象在栈上分配内存就是个不错主意，对象所占用内存空间就可随栈帧出栈而销毁。  
-  
-
-在一般应用中，完全不会逃逸的局部对象和不会逃逸出线程的对象所占比例很大，若能使用栈上分配，则大量对象就会随方法结束而自动销毁，GC系统压力会下降很多。
-
-**栈上分配可支持方法逃逸，但不能支持线程逃逸。**
-
-
-  
-
-2.2 标量替换（Scalar Replacement）
-
-
-2.2.1 标量
-
-
-  
-
-若一个数据已经无法再分解成更小数据，JVM中的原始数据类型（如 int、long 等数值类型及 reference 类型）都不能再进一步分解，这些数据即为标量。
-
-
-2.2.2 聚合量
-
-
-  
-
-若一个数据可继续分解，则称为聚合量（Aggregate），比如 Java 对象就是聚合量。
-
-
-2.2.3 标量替换
-
-
-  
-
-把一个Java对象拆散，根据程序访问情况，将其用到的成员变量恢复为原始类型来访问。
-
-  
-假如逃逸分析能证明一个对象不会被方法外部访问，并且该对象可被分解，那么程序真正执行时将可能不去创建该对象，而改为直接创建它的若干个被这方法使用的成员变量。  
-将对象拆分后：
-
-*   可让对象的成员变量在栈上 （栈上存储的数据，很大概率会被JVM分配至物理机器的高速寄存器中存储）分配和读写
-    
-*   为后续进步优化创建条件
-    
-
-2.2.4 适用场景
-
-  
-
-标量替换可视为栈上分配一种特例，实现更简单（不用考虑对象完整结构的分配），但对逃逸程度的要求更高，它不允许对象逃逸出方法范围内。
-
-  
-
-  
-
-2.3 同步消除（Synchronization Elimination）
-
-
-  
-
-线程同步是个相对耗时的过程，若逃逸分析能确定一个变量不会逃逸出线程，即不会被其他线程访问，则该变量的读写肯定不会有线程竞争， 也可安全消除对该变量实施的同步措施。
-
-> 逃逸分析的论文在1999年就已发表，但到JDK 6，HotSpot才开始初步支持逃逸分析，至今该也尚未成熟，主要因为逃逸分析的计算成本高到无法保证带来的性能收益会高于它的消耗。要百分百准确判断一个对象是否会逃逸，需进行一系列复杂数据流敏感的过程间分析，才能确定程序各个分支执行时对此对象的影响。过程间分析这种大压力的分析算法正是即时编译的弱项。试想，若逃逸分析完毕后发现几乎找不到几个不逃逸的对象， 那这些运行期耗用的时间就白费了，所以目前JVM只能采用不那么准确，但时间压力相对较小的算法来完成分析。
-
-C和C++原生支持栈上分配（不使用new即可），灵活运用栈内存方面，Java的确是弱势群体。  
-在现在仍处于实验阶段的Valhalla项目，设计了新的inline关键字用于定义Java的内联类型， 对标C#的值类型。有了该标识与约束，以后逃逸分析做起来就会简单很多。
-
-3 代码实战验证
-
-  
-
-  
-
-  
-
-  
-
-3.1 全无优化的代码 
-
-  
-
-```
-public int test(int x) {   int xx = x + 2;   Point p = new Point(xx, 42);   return p.getX(); }
-```
-
-  
-
-  
-
-  
-
-3.2 优化step1：内联构造器和getX()方法
-
-  
-
-```
-public int test(int x) {   int xx = x + 2;  // 在堆中分配P对象   Point p = point\_memory\_alloc();  // Point构造器被内联后    p.x = xx;   p.y = 42;  // Point::getX()被内联后   return p.x;}
-```
-
-  
-
-  
-
-  
-
-优化step2：标量替换
-
-
-  
-
-逃逸分析后，发现在整个test()方法的范围内Point对象实例不会发生任何程度逃逸， 便可对它进行标量替换：把其内部的x和y直接置换出来，分解为test()方法内的局部变量，从而避免了Point对象实例的创建
-
-```
-public int test(int x) {    int xx = x + 2;    int px = xx;    int py = 42    return px; }
-```
-
-
-step3：无效代码消除
-
-
-数据流分析，发现py的值其实对方法不会造成任何影响，那就可以放心地去做无效代码消除得到最终优化结果，如下所示：
-
-```
-public int test(int x) {   return x + 2; }
-```
-
-观察测试结果，实施逃逸分析后的程序在MicroBenchmarks中往往能得到不错的成绩，但在实际应用程序中，尤其是大型程序中反而发现实施逃逸分析可能出现效果不稳定，或分析过程耗时但却无法有效判别出非逃逸对象而导致性能（即时编译的收益）下降，所以曾经在很长的一段时间，即使是服务端编译器，也默认不开启逃逸分析（从JDK 6 Update 23开始，服务端编译器中开始才默认开启逃逸分析。），甚至在某些版本（如JDK 6 Update 18）中还曾完全禁止这项优化，一直到JDK 7时这项优化才成为服务端编译器默认开启的选项。
-
-若有需要或确认对程序有益，可使用参数：
-
-*   **-XX：+DoEscapeAnalysis **手动开启逃逸分析
-    
-
-开启后可通过参数：
-
-*   **-XX：+PrintEscapeAnalysis **查看分析结果
-    
-
-有逃逸分析支持后，用户可使用如下参数：
-
-*   **-XX：+EliminateAllocations **开启标量替换
-    
-*   **+XX：+EliminateLocks **开启同步消除
-    
-*   **-XX：+PrintEliminateAllocations **查看标量的替换情况
-    
-
- 让我们一起期待该JIT优化技术之逃逸分析的发展。
-
-> 参考
-> 
-> 《深入理解 Java 虚拟机》
\ No newline at end of file
diff --git "a/JDK/JVM/Java\345\206\205\345\255\230\346\250\241\345\236\213\346\267\261\345\205\245\350\257\246\350\247\243(JMM).md" "b/JDK/JVM/Java\345\206\205\345\255\230\346\250\241\345\236\213\346\267\261\345\205\245\350\257\246\350\247\243(JMM).md"
deleted file mode 100644
index 1dbb544b41..0000000000
--- "a/JDK/JVM/Java\345\206\205\345\255\230\346\250\241\345\236\213\346\267\261\345\205\245\350\257\246\350\247\243(JMM).md"
+++ /dev/null
@@ -1,128 +0,0 @@
-# 前言
-定义俩共享变量及俩方法：
-- 第一个方法，
-- 第二个方法
-- （r1，r2）的可能值有哪些？
-![](https://img-blog.csdnimg.cn/05139ccfbb40447a869632ff35959841.png)
-
-在单线程环境下，可先调用第一个方法，最终（r1，r2）为（1，0）
-也可以先调用第二个方法，最终为（0，2）。
-
-![](https://img-blog.csdnimg.cn/20200404214401993.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-# 1 Java内存模型的意义
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTkyYTFmZGY0OGJlMTllMDYucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTIzZTVlOWE0OWFkZWI1YTEucG5n?x-oss-process=image/format,png)
-JMM 与硬件内存架构对应关系![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTVlMTM3NGEwYWJmOWM5MjkucG5n?x-oss-process=image/format,png)
-JMM抽象结构图
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQ0ZWE4ODQzYTg4YTk0MGQucG5n?x-oss-process=image/format,png)
-内存模型描述程序的可能行为。
-
-Java虚拟机规范中试图定义一种Java内存模型，`来屏蔽掉各种硬件和os的内存访问差异`，规定：
-- 线程如何、何时能看到其他线程修改过的共享变量的值
-- 必要时，如何同步地访问共享变量
-
-以实现让Java程序在各种平台下都能达到一致性的内存访问效果。
-
-JMM通过检查执行跟踪中的每个读操作，并根据某些规则检查该读操作观察到的写操作是否有效来工作。
-
-只要程序的所有执行产生的结果都可由JMM预测。具体实现者任意实现，包括操作的重新排序和删除不必要的同步。
-
-JMM决定了在程序的每个点上可以读取什么值。
-## 1.1 共享变量（Shared Variables）
-可在线程之间共享的内存称为`共享内存或堆内存`。所有实例字段、静态字段和数组元素都存储在堆内存。
-不包括局部变量与方法参数，因为这些是线程私有的，不存在共享。
-
-对同一变量的两次访问(读或写)，若有一个是写请求，则是冲突的！
-# 2 主内存与工作内存
-工作内存缓存
-![](https://img-blog.csdnimg.cn/20191014024209488.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-JMM的主要是定义了`各个变量的访问规则`，在JVM中的如下底层细节：
-- 将变量存储到内存
-- 从内存中取出变量值
-
-为获得较好执行效率，JMM并未限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互，也没有限制即时编译器调整代码执行顺序这类权限。
-
-JMM规定：
-- 所有变量都存储在主内存(Main Memory)
-- 每条线程有自己的工作内存(Working Memory)
-保存了该线程使用到的`变量的主内存副本拷贝`(线程所访问对象的引用或者对象中某个在线程访问到的字段，不会是整个对象的拷贝)
-线程对变量的所有操作(读，赋值等)都必须在工作内存进行，不能直接读写主内存中的变量
-volatile变量依然有工作内存的拷贝,，是他特殊的操作顺序性规定，看起来如同直接在主内存读写
-不同线程间，无法直接访问对方工作内存中的变量，线程间变量值的传递均要通过主内存
-
-线程、主内存、工作内存三者的交互关系：
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTEyMjA5YjEyZDU3OGEyZWQucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJiM2QzN2MxNTVjZDgyZDgucG5n?x-oss-process=image/format,png)
-
-JVM模型与JMM不是同一层次的内存划分，基本毫无关系的，硬要对应起来，从变量，内存，工作内存的定义来看
-- 主内存 《=》Java堆中的对象实例数据部分
-- 工作内存 《=》虚拟机栈中的部分区域
- 
-从更底层的层次来看：
-- 主内存直接对应物理硬件的内存
-- 为更好的运行速度，虚拟机（甚至硬件系统的本身的优化措施）可能会让工作内存优先存储于寄存器和高速缓存器，因为程序运行时主要访问读写的是工作内存
-# 3 内存间同步操作
-## 3.1 线程操作的定义
-###  操作定义
-write要写的变量以及要写的值。
-read要读的变量以及可见的写入值(由此，我们可以确定可见的值)。
-lock要锁定的管程(监视器monitor)。
-unlock要解锁的管程。
-外部操作(socket等等..)
-启动和终止
-### 程序顺序
-如果一个程序没有数据竞争，那么程序的所有执行看起来都是顺序一致的
-
-本规范只涉及线程间的操作;
-一个变量如何从主内存拷贝到工作内存,从工作内存同步回主内存的实现细节
-
-JMM 本身已经定义实现了以下8种操作来完成，且都具备`原子性`
-- lock(锁定)
-作用于主内存变量，把一个变量标识为一条线程独占的状态
-- unlock(解锁)
-作用于主内存变量，把一个处于锁定状态的变量释放,释放后的变量才可以被其它线程锁定
-unlock之前必须将变量值同步回主内存
-- read(读取)
-作用于主内存变量，把一个变量的值从主内存传输到工作内存，以便随后的load
-- load(载入)
-作用于工作内存变量，把read从主内存中得到的变量值放入工作内存的变量副本
-- use(使用)
-作用于工作内存变量，把工作内存中一个变量的值传递给执行引擎，每当虚拟机遇到一个需要使用到的变量的值得字节码指令时将会执行这个操作
-- assign(赋值)
-作用于工作内存变量，把一个从执行引擎接收到的值赋给工作内存的变量，每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
-- store(存储)
-作用于工作内存变量，把工作内存中一个变量的值传送到主内存，以便随后的write操作使用
-- write(写入)
-作用于主内存变量，把store操作从工作内存中得到的值放入主内存的变量中
-
-- 把一个变量从主内存`复制`到工作内存
-就要顺序执行read和load
-
-- 把变量从工作内存`同步`回主内存
-就要顺序地执行store和write操作
-
-JMM只要求上述两个操作必须`按序执行`，而没有保证连续执行
-也就是说read/load之间、store/write之间可以插入其它指令
-如对主内存中的变量a,b访问时，一种可能出现的顺序是read a->readb->loadb->load a
-
-JMM规定执行上述八种基础操作时必须满足如下
-## 3.1 同步规则
-◆ 对于监视器 m 的解锁与所有后续操作对于 m 的加锁 `同步`(之前的操作保持可见)
-◆对 volatile变量v的写入，与所有其他线程后续对v的读同步
-
-◆ `启动` 线程的操作与线程中的第一个操作同步
-◆ 对于每个属性写入默认值(0， false, null)与每个线程对其进行的操作同步
-◆ 线程 T1的最后操作与线程T2发现线程T1已经结束同步。( isAlive ,join可以判断线程是否终结)
-◆ 如果线程 T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步通过抛出*InterruptedException*异常，或者调用*Thread.interrupted*或*Thread.isInterrupted*
-
-- 不允许read/load、store/write操作之一单独出现
-不允许一个变量从主内存读取了但工作内存不接收，或从工作内存发起回写但主内存不接收
-- 不允许一个线程丢弃它的最近的assign
-即变量在工作内存中改变(为工作内存变量赋值)后必须把该变化同步回主内存
-- 新变量只能在主内存“诞生”，不允许在工作内存直接使用一个未被初始化(load或assign)的变量
-换话说就是一个变量在实施use，store之前，必须先执行过assign和load
-- 如果一个变量事先没有被load锁定，则不允许对它执行unlock，也不允许去unlock一个被其它线程锁定的变量
-- 对一个变量执行unloack前，必须把此变量同步回主内存中(执行store，write)
-
-> 参考
-> - https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.1
\ No newline at end of file
diff --git "a/JDK/JVM/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md" "b/JDK/JVM/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md"
index 59c68099d5..fd762cc410 100644
--- "a/JDK/JVM/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md"
+++ "b/JDK/JVM/Java\346\200\247\350\203\275\350\260\203\344\274\230\345\267\245\345\205\267\344\271\213JDK\345\221\275\344\273\244\350\241\214.md"
@@ -1,30 +1,21 @@
 ## 1.1 jps
-类似Linux的ps，但jps只列出Java的进程。可方便查看Java进程的启动类、传入参数和JVM参数。直接运行，不加参数，列出Java程序的进程ID及Main函数名称。
+类似Linux的ps,但是jps只用于列出Java的进程
+可以方便查看Java进程的启动类,传入参数和JVM参数等
+直接运行,不加参数,列出Java程序的进程ID以及Main函数等名称
 ![jps命令本质也是Java程序](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTljMjE4OWRlZDljYmQ1M2UucG5n?x-oss-process=image/format,png)
-- -m 输出传递给Java进程的参数![](https://img-blog.csdnimg.cn/20210117135731422.png)
-
-- -l 输出主函数的完整路径![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJkOGY1NzU2NWQzNDY1NDMucG5n?x-oss-process=image/format,png)
-- -q 只输出进程ID![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWUzODhhY2U5MmYzMDNkYWYucG5n?x-oss-process=image/format,png)
-- -v 显示传递给jvm的参数![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTlhM2JhYjkzZjk0Y2U2YzgucG5n?x-oss-process=image/format,png)
+![-m 输出传递给Java进程的参数](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTE1ZDAzZGJkNWY4MDIxMDkucG5n?x-oss-process=image/format,png)
+![-l 输出主函数的完整路径](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJkOGY1NzU2NWQzNDY1NDMucG5n?x-oss-process=image/format,png)
+![-q 只输出进程ID](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWUzODhhY2U5MmYzMDNkYWYucG5n?x-oss-process=image/format,png)
+![-v 显示传递给jvm的参数](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTlhM2JhYjkzZjk0Y2U2YzgucG5n?x-oss-process=image/format,png)
 ## 1.2 jstat
-观察Java应用程序运行时信息的工具，详细查看堆使用情况以及GC情况
-- jstat -options
-![](https://img-blog.csdnimg.cn/20210117142257563.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
+用于观察Java应用程序运行时信息的工具,详细查看堆使用情况以及GC情况
+![jstat -options](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWZiYzg1NWU5NDk3MTljNzcucG5n?x-oss-process=image/format,png)
 ### 1.2.1 jstat -class pid
 显示加载class的数量及所占空间等信息
-- -compiler -t:显示JIT编译的信息
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTcyM2Y5ZjA4MjMyMjcyMDQucG5n?x-oss-process=image/format,png)
+![-compiler -t:显示JIT编译的信息](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTcyM2Y5ZjA4MjMyMjcyMDQucG5n?x-oss-process=image/format,png)
 ### 1.2.2 -gc pid
-显示gc信息，查看gc的次数及时间
-![](https://img-blog.csdnimg.cn/20210117144320641.png)
-
-```shell
-➜  ~ jstat -gc 87552
- S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT    CGC    CGCT     GCT
-25088.0 20992.0  0.0   20992.0 500224.0 56227.0   363008.0   35238.1   76672.0 72902.5 10368.0 9590.5      9    0.078   3      0.162   -          -    0.239
-```
-
+可以显示gc的信息，查看gc的次数及时间
+![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTZiODFlOTZjOWNlNGExMjIucG5n?x-oss-process=image/format,png)
 ### 1.2.3 -gccapacity
 比-gc多了各个代的最大值和最小值
 ![jstat -gccapacity 3661](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LThmMGMwNTY4YTgzM2M5MzkucG5n?x-oss-process=image/format,png)
@@ -44,28 +35,25 @@
 ![-gcoldcapacity展现老年代的容量信息](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTc0NjhlMDA4YTUwZGMyMDQucG5n?x-oss-process=image/format,png)
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTA0OWRjZjBhYzRkNjM0YjYucG5n?x-oss-process=image/format,png)
 ### -gcutil
-相比于-gc 参数，只显示使用率而非使用量了。
 显示GC回收相关信息
-![](https://img-blog.csdnimg.cn/20210117144845233.png)
-
+![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTNhYmVkNDJmOTdiOWZmYzQucG5n?x-oss-process=image/format,png)
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWY5MDZjZTFhZDA2MDA3YTAucG5n?x-oss-process=image/format,png)
 ### -printcompilation
 当前VM执行的信息
 ![jstat -printcompilation 3661](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWMxMzk3NmMzZjE4OTViYWIucG5n?x-oss-process=image/format,png)
 ### 还可以同时加两个数
-- 输出进程4798的ClassLoader信息,每1秒统计一次,共输出2次
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTBjMjcyNGI5MzIwOWU4ZjIucG5n?x-oss-process=image/format,png)
+![输出进程4798的ClassLoader信息,每1秒统计一次,共输出2次](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTBjMjcyNGI5MzIwOWU4ZjIucG5n?x-oss-process=image/format,png)
 ## 1.3 jinfo
 `jinfo <option> <pid>`
 查看正在运行的Java程序的扩展参数，甚至在运行时修改部分参数
-### 查看运行时参数
-```bash
+## 查看运行时参数
+```
 jinfo -flag MaxTenuringThreshold 31518
 -XX:MaxTenuringThreshold=15
 ```
 ![](https://img-blog.csdnimg.cn/20200216234802960.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 在运行时修改参数值
-```bash
+## 在运行时修改参数值
+```
 > jinfo -flag PrintGCDetails 31518
 -XX:-PrintGCDetails
 
@@ -78,12 +66,10 @@ jinfo -flag MaxTenuringThreshold 31518
 ```
 
 ## 1.4 jmap
-生成堆快照和对象的统计信息。
+生成堆快照和对象的统计信息
+![-histo 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量. ](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTUwMWQ2ZTFlMjBiMTExOTkucG5n?x-oss-process=image/format,png)
 
--histo 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量. 
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTUwMWQ2ZTFlMjBiMTExOTkucG5n?x-oss-process=image/format,png)
-- -dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的，假如指定live选项,那么只输出活的对象到文件. 
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTlhYzUyYmNiZTYzZmNmNDIucG5n?x-oss-process=image/format,png)
+![-dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的，假如指定live选项,那么只输出活的对象到文件. ](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTlhYzUyYmNiZTYzZmNmNDIucG5n?x-oss-process=image/format,png)
 
 获得堆快照文件之后，我们可以使用多种工具对文件进行分析，例如jhat，visual vm等。
 ## 1.5 jhat
@@ -91,68 +77,16 @@ jinfo -flag MaxTenuringThreshold 31518
 ![jhat heap.hprof ](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTMyMGI3YmQ3Njc4ODAyYjkucG5n?x-oss-process=image/format,png)
 jhat在分析完成之后，使用HTTP服务器展示其分析结果，在浏览器中访问[http://127.0.0.1:7000/](https://link.jianshu.com?t=http://127.0.0.1:7000/)即可得到分析结果。
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWI2ZDNkNWM2OTI2ZTIxOTIucG5n?x-oss-process=image/format,png)
-## 1.6 jstack
-导出Java应用程序的线程堆栈。
-```bash
-jstack -l <pid>
-```
+## 1.6b jstack
+导出Java应用程序的线程堆栈
+`jstack -l <pid>`
+
+
 jstack可以检测死锁，下例通过一个简单例子演示jstack检测死锁的功能。java代码如下：
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWI3OGE4NWQ0YzhjNGM2YzEucG5n?x-oss-process=image/format,png)
-
-- 使用jps命令查看进程号为11468，然后使用jstack -l 11468 > a.txt命令把堆栈信息打印到文件中![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWMzZGNmMDE4OGIyNDU0NDEucG5n?x-oss-process=image/format,png)
+![使用jps命令查看进程号为11468，然后使用jstack -l 11468 > a.txt命令把堆栈信息打印到文件中](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWMzZGNmMDE4OGIyNDU0NDEucG5n?x-oss-process=image/format,png)
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTU0YWZlMTRmMzA2Y2Q5NTYucG5n?x-oss-process=image/format,png)
-从该输出信息可知：
-- 在输出的最后一段，有明确的"Found one Java-level deadlock"输出，所以通过jstack可检测死锁
-- 输出中包含了所有线程，除了我们的north,sorth线程外，还有"Attach Listener", "C2 CompilerThread0", "C2 CompilerThread1"等
-- 每个线程下面都会输出当前状态，以及这个线程当前持有锁以及等待锁，当持有与等待造成循环等待时，将导致死锁
-
-再看一个案例：
-![](https://img-blog.csdnimg.cn/20210519145034718.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210519145230528.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## 1.7 jcmd
-执行 JVM 相关分析命令（整合命令）。
-
-```bash
-JFR.stop
-JFR.start
-JFR.dump
-JFR.check
-VM.native_memory
-VM.check_commercial_features
-VM.unlock_commercial_features
-ManagementAgent.stop
-ManagementAgent.start_local
-ManagementAgent.start
-VM.classloader_stats
-GC.rotate_log
-Thread.print
-GC.class_stats
-GC.class_histogram
-GC.heap_dump
-GC.finalizer_info
-GC.heap_info
-GC.run_finalization
-GC.run
-VM.uptime
-VM.dynlibs
-VM.flags
-VM.system_properties
-VM.command_line
-VM.version
-help
-```
-###  VM.version![](https://img-blog.csdnimg.cn/20210117150450437.png)
-
-## jrunscript/jjs
-执行 js 命令 
-
-## jconsole
-![](https://img-blog.csdnimg.cn/20210117151711200.png)
-![](https://img-blog.csdnimg.cn/20210117151650772.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210117152211879.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210117152221938.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210117152231546.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210117152246493.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-## jvisualVM
-![](https://img-blog.csdnimg.cn/20210117152646321.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
\ No newline at end of file
+从这个输出可以知道：
+1、在输出的最后一段，有明确的"Found one Java-level deadlock"输出，所以通过jstack命令我们可以检测死锁
+2、输出中包含了所有线程，除了我们的north,sorth线程外，还有"Attach Listener", "C2 CompilerThread0", "C2 CompilerThread1"等等；
+3、每个线程下面都会输出当前状态，以及这个线程当前持有锁以及等待锁，当持有与等待造成循环等待时，将导致死锁
diff --git "a/JDK/JVM/Jprofile\350\247\243\346\236\220dump\346\226\207\344\273\266\344\275\277\347\224\250\350\257\246\350\247\243.md" "b/JDK/JVM/Jprofile\350\247\243\346\236\220dump\346\226\207\344\273\266\344\275\277\347\224\250\350\257\246\350\247\243.md"
deleted file mode 100644
index a2c6a21772..0000000000
--- "a/JDK/JVM/Jprofile\350\247\243\346\236\220dump\346\226\207\344\273\266\344\275\277\347\224\250\350\257\246\350\247\243.md"
+++ /dev/null
@@ -1,213 +0,0 @@
-# 1 Jprofile简介
-- [官网](https://www.ej-technologies.com/products/jprofiler/overview.html)
-![在这里插入图片描述](https://img-blog.csdnimg.cn/20200229024741487.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 下载对应的系统版本即可
-![](https://img-blog.csdnimg.cn/20200229024854171.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-性能查看工具JProfiler，可用于查看java执行效率，查看线程状态，查看内存占用与内存对象，还可以分析dump日志.
-
-
-# 2 功能简介
-- 选择attach to a locally running jvm
-![](https://img-blog.csdnimg.cn/20200229025200914.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 选择需要查看运行的jvm，双击或者点击start
-![](https://img-blog.csdnimg.cn/20200229025304638.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 等待进度完成，弹出模式选择
-![](https://img-blog.csdnimg.cn/20200229025406155.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-   - Instrumentation模式记录所有的信息。包括方法执行次数等Sampling模式则只支持部分功能，不纪录方法调用次数等，并且更为安全
-由于纪录信息很多，java运行会变的比正常执行慢很多，sampling模式则不会
-	- 常规使用选择sampling模式即可，当需要调查方法执行次数才需要选择Instrumentation模式，模式切换需要重启jprofiler
-
-- 点击OK
-![](https://img-blog.csdnimg.cn/20200229025604706.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 选择Live Momory可以查看内存中的对象和大小
-![](https://img-blog.csdnimg.cn/202002290257375.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 选择cpu views点击下图框中的按钮来纪录cpu的执行时间
-![](https://img-blog.csdnimg.cn/20200229025934939.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 这时候可以在外部对需要录的jvm操作进行记录了,得出的结果可以轻松看出方法执行调用过程与消耗时间比例：
-
-- 根据cpu截图的信息，可以找到效率低的地方进行处理，如果是Instrumentation模式则在时间位置会显示调用次数
-
-
-在Thread界面则可以实时查看线程运行状态，黄色的是wait 红色是block 绿色的是runnable蓝色是网络和I/O请求状态
-![](https://img-blog.csdnimg.cn/20200229030202768.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-选择ThreadDumps,可以录制瞬时线程的调用堆栈信息，如下图所示：
-![](https://img-blog.csdnimg.cn/20200229030320692.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-#  3 dump 文件分析
-## 3.1 dump 生成
-### JProfiler 在线 
-
-当JProfiler连接到JVM之后选择Heap Walker，选择Take snapshot图标，然后等待即可
-![](https://img-blog.csdnimg.cn/20200229030546342.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20200229030602936.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-如果内存很大，jprofiler万一参数设置的不正确打不开就需要要重新生成，内存小的时候无所谓
-
-### 使用JProfiler生成文件
-当JProfiler连接到JVM之后选择菜单上的Profiling->save HPROF snapshot 弹出下拉框保存即可，这时候生成的文件就可以一直保存在文件上
-
-### jmap
-
-```bash
-jmap -dump:format=b,file=文件名 pid
-
-windows下不用[]，路径要加引号
-
-jmap -dump:format=b,file="D:\a.dump" 8632
-```
-命令中文件名就是要保存的dump文件路径， pid就是当前jvm进程的id
-
-
-### JVM启动参数
-在发生outofmemory的时候自动生成dump文件:
-
-```bash
--XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\heapdump
-```
-Pah后面是一个存在的可访问的路径，将改参数放入jvm启动参数可以在发生内存outofmemory的时候自动生成dump文件，但是正式环境使用的时候不要加这个参数，不然在内存快满的时候总是会生成dump而导致jvm卡半天，需要调试的时候才需要加这个参数
-
-注意：通过WAS生成的PHD文件dump不能分析出出问题的模板，因为PHD文件不包含对象的值内容，无法根据PHD文件找到出问题的模板，所以PHD文件没有太大的参考价值
-
-## 3.2 dump文件分析
-dump文件生成后，将dump压缩传输到本地，不管当前dump的后缀名是什么，直接改成*.hprof，就可以直接用jprofiler打开了
-
-打开的过程时间可能会很长，主要是要对dump进行预处理，计算什么的，注意 这个过程不能点skip，否则就不太好定位大文件
-- 直接打开`.hprof`文件
-![](https://img-blog.csdnimg.cn/20200229003455158.png)
-- 注意如下过程,中途可以喝一杯☕️,不要作死手滑点击了 skip!
-![](https://img-blog.csdnimg.cn/20200227174740284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20200229002551182.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20200229002604848.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20200229002723570.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20200229002943350.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/2020022900330228.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20200229032220529.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-这样界面的时候下面可以开始进行操作了!
-
-# 4 模块功能点详解
-也可以使用工具栏中的“转到开始”按钮访问第一个数据集
-
-## 4.1 内存视图 Memory Views
-JProfiler的内存视图部分可以提供动态的内存使用状况更新视图和显示关于内存分配状况信息的视图。所有的视图都有几个聚集层并且能够显示现有存在的对象和作为垃圾回收的对象。
-- 所有对象 All Objects
-显示类或在状况统计和尺码信息堆上所有对象的包。你可以标记当前值并显示差异值。
-- 记录对象 Record Objects 
-显示类或所有已记录对象的包。你可以标记出当前值并且显示差异值。
-- 分配访问树 Allocation Call Tree 
-显示一棵请求树或者方法、类、包或对已选择类有带注释的分配信息的J2EE组件。
-- 分配热点 Allocation Hot Spots 
-显示一个列表，包括方法、类、包或分配已选类的J2EE组件。你可以标注当前值并且显示差异值。对于每个热点都可以显示它的跟踪记录树。
-- 类追踪器 Class Tracker 
-类跟踪视图可以包含任意数量的图表，显示选定的类和包的实例与时间。
-
-
-
-
-## 4.2 堆遍历 Heap Walker
-### 使用背景
-在视图中找到增长快速的对象类型，在memory视图中找到Concurrenthashmap---点右键----选择“Show Selectiion In Heap Walker”，切换到HeapWarker 视图；切换前会弹出选项页面，注意一定要选择“Select recorded  objects”，这样Heap Walker会在刚刚的那段记录中进行分析；否则，会分析tomcat的所有内存对象，这样既耗时又不准确；
-
-
-
-在JProfiler的堆遍历器(Heap Walker)中，你可以对堆的状况进行快照并且可以通过选择步骤下寻找感兴趣的对象。堆遍历器有五个视图：
-- 类 Classes 
-显示所有类和它们的实例，可以右击具体的类"Used Selected Instance"实现进一步跟踪。
-- 分配 Allocations 
-为所有记录对象显示分配树和分配热点。
-- 索引 References 
-为单个对象和“显示到垃圾回收根目录的路径”提供索引图的显示功能。还能提供合并输入视图和输出视图的功能。
-- 时间 Time 
-显示一个对已记录对象的解决时间的柱状图。
-- 检查 Inspections 
-显示了一个数量的操作，将分析当前对象集在某种条件下的子集，实质是一个筛选的过程。
-
-### 在HeapWalker中，找到泄漏的对象
-HeapWarker 会分析内存中的所有对象，包括对象的引用、创建、大小和数量.
-通过切换到References页签，可以看到这个类的具体对象实例。 为了在这些内存对象中，找到泄漏的对象（应该被回收），可以在该对象上点击右键，选择“Use Selected Instances”缩小对象范围
-![](https://img-blog.csdnimg.cn/2020022904291115.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-###  通过引用分析该对象
-References 可以看到该对象的的引用关系，选项显示引用的类型
-![](https://img-blog.csdnimg.cn/20200229043146569.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- incoming
-显示这个对象被谁引用
-- outcoming
-显示这个对象引用的其他对象
-
-选择“Show In Graph”将引用关系使用图形方式展现；
-
-- 选中该对象，点击`Show Paths To GC Root`，会找到引用的根节点
-![](https://img-blog.csdnimg.cn/20200229043837414.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-### 通过创建分析该对象
-如果还不能定位内存泄露的地方，我们可以尝试使用Allocations页签，该页签显示对象是如何创建出来的；
-我们可以从创建方法开始检查，检查所有用到该对象的地方，直到找到泄漏位置；
-## 图表 Graph
-你需要在references视图和biggest视图手动添加对象到图表，它可以显示对象的传入和传出引用，能方便的找到垃圾收集器根源。
-
-tips：在工具栏点击"Go To Start"可以使堆内存重新计数，也就是回到初始状态。
-
-　　
-
-## CPU 视图 CPU Views
-　　JProfiler 提供不同的方法来记录访问树以优化性能和细节。线程或者线程组以及线程状况可以被所有的视图选择。所有的视图都可以聚集到方法、类、包或J2EE组件等不同层上。CPU视图部分包括：
-
-　　
-
-访问树 Call Tree 
-显示一个积累的自顶向下的树，树中包含所有在JVM中已记录的访问队列。JDBC,JMS和JNDI服务请求都被注释在请求树中。请求树可以根据Servlet和JSP对URL的不同需要进行拆分。
-热点 Hot Spots 
-显示消耗时间最多的方法的列表。对每个热点都能够显示回溯树。该热点可以按照方法请求，JDBC，JMS和JNDI服务请求以及按照URL请求来进行计算。
-访问图 Call Graph 
-显示一个从已选方法、类、包或J2EE组件开始的访问队列的图。
-方法统计 Method Statistis
-显示一段时间内记录的方法的调用时间细节。
-## 线程视图 Thread Views
-　　JProfiler通过对线程历史的监控判断其运行状态，并监控是否有线程阻塞产生，还能将一个线程所管理的方法以树状形式呈现。对线程剖析，JProfiler提供以下视图:
-
-　　
-
-线程历史 Thread History 
-显示一个与线程活动和线程状态在一起的活动时间表。
-线程监控 Thread Monitor 
-显示一个列表，包括所有的活动线程以及它们目前的活动状况。
-线程转储 Thread Dumps 
-显示所有线程的堆栈跟踪。
-## 监控器视图 Monitor Views
-　　JProfiler提供了不同的监控器视图，如下所示:
-
-　　
-
-当前锁定图表 Current Locking Graph 
-显示JVM中的当前锁定情况。
-当前监视器 Current Monitors 
-显示当前正在等待或阻塞中的线程操作。
-锁定历史图表 Locking History Graph 
-显示记录在JVM中的锁定历史。
-监控器历史 Monitor History 
-显示等待或者阻塞的历史。
-监控器使用统计 Monitor Usage Statistics 
-计算统计监控器监控的数据。
-## VM遥感勘测技术视图 VM Telemetry Views
-　　观察JVM的内部状态，JProfiler提供了不同的遥感勘测视图，如下所示:
-
-　　
-
-内存 Memory 
-显示堆栈的使用状况和堆栈尺寸大小活动时间表。
-记录的对象 Recorded Objects 
-显示一张关于活动对象与数组的图表的活动时间表。
-记录的生产量 Recorded Throughput 
-显示一段时间累计的JVM生产和释放的活动时间表。
-垃圾回收活动 GC Activity
-显示一张关于垃圾回收活动的活动时间表。
-类 Classes 
-显示一个与已装载类的图表的活动时间表。
-线程 Threads 
-显示一个与动态线程图表的活动时间表。
-CPU负载 CPU Load 
-显示一段时间中CPU的负载图表。
-
-
-> 参考
-> - [使用JProfiler进行内存分析](https://www.cnblogs.com/onmyway20xx/p/3963735.html)
\ No newline at end of file
diff --git "a/JDK/JVM/\344\270\200\344\270\252\347\272\277\347\250\213 OOM \345\220\216\357\274\214\345\205\266\344\273\226\347\272\277\347\250\213\350\277\230\350\203\275\350\277\220\350\241\214\345\220\227\357\274\237.md" "b/JDK/JVM/\344\270\200\344\270\252\347\272\277\347\250\213 OOM \345\220\216\357\274\214\345\205\266\344\273\226\347\272\277\347\250\213\350\277\230\350\203\275\350\277\220\350\241\214\345\220\227\357\274\237.md"
deleted file mode 100644
index ac577f29cb..0000000000
--- "a/JDK/JVM/\344\270\200\344\270\252\347\272\277\347\250\213 OOM \345\220\216\357\274\214\345\205\266\344\273\226\347\272\277\347\250\213\350\277\230\350\203\275\350\277\220\350\241\214\345\220\227\357\274\237.md"	
+++ /dev/null
@@ -1,208 +0,0 @@
-![](https://img-blog.csdnimg.cn/20210628172622676.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-由于面试官仅提到OOM，但 Java 的OOM又分很多类型的呀：
-- 堆溢出（“java.lang.OutOfMemoryError: Java heap space”）
-- 永久代溢出（“java.lang.OutOfMemoryError:Permgen space”）
-- 不能创建线程（“java.lang.OutOfMemoryError:Unable to create new native thread”）
-
-	OOM在《Java虚拟机规范》里，除程序计数器，虚拟机内存的其他几个运行时区域都可能发生OOM，那本文的目的是啥呢？
-- 通过代码验证《Java虚拟机规范》中描述的各个运行时区域储存的内容
-- 在工作中遇到实际的内存溢出异常时，能根据异常的提示信息迅速得知是哪个区域的内存溢出，知道怎样的代码可能会导致这些区域内存溢出，以及出现这些异常后该如何处理。
-
-本文代码均由笔者在基于OpenJDK 8中的HotSpot虚拟机上进行过实际测试。
-
-# 1 Java堆溢出
-Java堆用于储存对象实例，只要不断地创建对象，并且保证GC Roots到对象之间有可达路径来避免GC机制清除这些对象，则随对象数量增加，总容量触及最大堆的容量限制后就会产生内存溢出异常。
-
-限制Java堆的大小20MB，不可扩展
-```bash
--XX：+HeapDumpOnOutOf-MemoryError
-```
-可以让虚拟机在出现内存溢出异常的时候Dump出当前的内存堆转储快照。
-## 案例1
-![](https://img-blog.csdnimg.cn/20210628172310407.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 报错
-![](https://img-blog.csdnimg.cn/20210628172350886.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-Java堆内存的OOM是实际应用中最常见的内存溢出异常场景。出现Java堆内存溢出时，异常堆栈信息“java.lang.OutOfMemoryError”会跟随进一步提示“Java heap space”。 
-
-那既然发生了，**如何解决这个内存区域的异常呢**？
-一般先通过内存映像分析工具（如jprofile）对Dump出来的堆转储快照进行分析。
-第一步首先确认内存中导致OOM的对象是否是必要的，即先分清楚到底是
-- 内存泄漏（Memory Leak）
-- 还是内存溢出（Memory Overflow）
-
-- 下图是使用 jprofile打开的堆转储快照文件（java_pid44526.hprof） 
-![](https://img-blog.csdnimg.cn/20210628174931619.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-若是内存泄漏，可查看泄漏对象到GC Roots的引用链，找到泄漏对象是通过怎样的引用路径、与哪些GC Roots相关联，才导致垃圾收集器无法回收它们，根据泄漏对象的类型信息以及它到GC Roots引用链的信息，一般可以比较准确地定位到这些对象创建的位置，进而找出产生内存泄漏的代码的具体位置。
-
-若不是内存泄漏，即就是内存中的对象确实都必须存活，则应：
-1. 检查JVM堆参数（-Xmx与-Xms）的设置，与机器内存对比，看是否还有向上调整的空间
-2. 再检查代码是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况，尽量减少程序运 行期的内存消耗
-
-以上是处理Java堆内存问题的简略思路。
-
-## 案例 2
-JVM启动参数设置：
-```bash
--Xms5m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
-```
-![](https://img-blog.csdnimg.cn/20210628191435587.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210628191613580.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-- JVM堆空间的变化
-![](https://img-blog.csdnimg.cn/20210628191655896.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-堆的使用大小，突然抖动！说明当一个线程抛OOM后，它所占据的内存资源会全部被释放掉，而不会影响其他线程的正常运行！
-所以一个线程溢出后，进程里的其他线程还能照常运行。
-发生OOM的线程一般情况下会死亡，也就是会被终结掉，该线程持有的对象占用的heap都会被gc了，释放内存。因为发生OOM之前要进行gc，就算其他线程能够正常工作，也会因为频繁gc产生较大的影响。
-
-堆溢出和栈溢出，结论是一样的。
-# 2 虚拟机栈/本地方法栈溢出
-由于**HotSpot JVM并不区分虚拟机栈和本地方法栈**，因此HotSpot的`-Xoss`参数（设置本地方法栈的大小）虽然存在，但无任何效果，栈容量只能由`-Xss`参数设定。
-
-关于虚拟机栈和本地方法栈，《Java虚拟机规范》描述如下异常： 
-1. 若线程请求的栈深度大于虚拟机所允许的最大深度，将抛出StackOverflowError异常
-2. 若虚拟机的栈内存允许动态扩展，当扩展栈容量无法申请到足够的内存时，将抛出 OutOfMemoryError异常
-
-《Java虚拟机规范》明确允许JVM实现自行选择是否支持栈的动态扩展，而HotSpot虚拟机的选择是**不支持扩展**，所以除非在创建线程申请内存时就因无法获得足够内存而出现OOM，否则在线程运行时是不会因为扩展而导致内存溢出的，只会因为栈容量无法容纳新的栈帧而导致StackOverflowError。 
-
-## 如何验证呢？
-做俩实验，先在单线程操作，尝试下面两种行为是否能让HotSpot OOM： 
-### 使用`-Xss`减少栈内存容量
-- 示例
-![](https://img-blog.csdnimg.cn/20210628195430820.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 结果
-![](https://img-blog.csdnimg.cn/20210628210011222.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-抛StackOverflowError异常，异常出现时输出的堆栈深度相应缩小。
-
-不同版本的Java虚拟机和不同的操作系统，栈容量最小值可能会有所限制，这主要取决于操作系统内存分页大小。譬如上述方法中的参数-Xss160k可以正常用于62位macOS系统下的JDK 8，但若用于64位Windows系统下的JDK 11，则会提示栈容量最小不能低于180K，而在Linux下这个值则可能是228K，如果低于这个最小限制，HotSpot虚拟器启动时会给出如下提示：
-
-```bash
-The stack size specified is too small, Specify at
-```
-
-### 定义大量局部变量，增大此方法帧中本地变量表的长度
-- 示例
-![](https://img-blog.csdnimg.cn/20210628210928936.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 结果
-![](https://img-blog.csdnimg.cn/20210628210958901.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)所以无论是由于栈帧太或虚拟机栈容量太小，当新的栈帧内存无法分配时， HotSpot 都抛SOF。可若在允许动态扩展栈容量大小的虚拟机上，相同代码则会导致不同情况。
-
-若测试时不限于单线程，而是不断新建线程，在HotSpot上也会产生OOM。但这样产生OOM和栈空间是否足够不存在直接的关系，主要取决于os本身内存使用状态。甚至说这种情况下，给每个线程的栈分配的内存越大，反而越容易产生OOM。 
-不难理解，os分配给每个进程的内存有限制，比如32位Windows的单个进程最大内存限制为2G。HotSpot提供参数可以控制Java堆和方法区这两部分的内存的最大值，那剩余的内存即为2G（os限制）减去最大堆容量，再减去最大方法区容量，由于程序计数器消耗内存很小，可忽略，若把直接内存和虚拟机进程本身耗费的内存也去掉，剩下的内存就由虚拟机栈和本地方法栈来分配了。因此为每个线程分配到的栈内存越大，可以建立的线程数量越少，建立线程时就越容易把剩下的内存耗尽：
-- 示例
-![](https://img-blog.csdnimg.cn/20210628215114730.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 结果
-```bash
-Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread
-```
-出现SOF时，会有明确错误堆栈可供分析，相对容易定位问题。如果使用HotSpot虚拟机默认参数，栈深度在大多数情况下（因为每个方法压入栈的帧大小并不是一样的）到达1000~2000没有问题，对于正常的方法调用（包括不能做尾递归优化的递归调用），这个深度应该完全够用。但如果是建立过多线程导致的内存溢出，在不能减少线程数量或者更换64位虚拟机的情况下，就只能通过减少最大堆和减少栈容量换取更多的线程。这种通过“减少内存”手段解决内存溢出的方式，如果没有这方面处理经验，一般比较难以想到。也是由于这种问题较为隐蔽，从 JDK 7起，以上提示信息中“unable to create native thread”后面，虚拟机会特别注明原因可能是“possibly
-
-```bash
-#define OS_NATIVE_THREAD_CREATION_FAILED_MSG 	
-	"unable to create native thread: possibly out of memory or process/resource limits reached"
-```
-# 3 方法区和运行时常量池溢出
-运行时常量池是方法区的一部分，所以这两个区域的溢出测试可以放到一起。
-
-HotSpot从JDK 7开始逐步“去永久代”，在JDK 8中完全使用元空间代替永久代，那么方法区使用“永久代”还是“元空间”来实现，对程序有何影响呢。 
-
-String::intern()是一个本地方法：若字符串常量池中已经包含一个等于此String对象的字符串，则返回代表池中这个字符串的String对象的引用；否则，会将此String对象包含的字符串添加到常量池，并且返回此String对象的引用。
-
-在JDK6或之前HotSpot虚拟机，常量池都是分配在永久代，可以通过如下两个参数：
-![](https://img-blog.csdnimg.cn/20210628222603375.png)
-限制永久代的大小，即可间接限制其中常量池的容量，
-- 实例
-![](https://img-blog.csdnimg.cn/20210628223514617.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- 结果
-
-```bash
-Exception in thread "main" java.lang.OutOfMemoryError: PermGen space 
-	at java.lang.String.intern(Native Method) 
-	at org.fenixsoft.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java: 18)
-```
-可见，运行时常量池溢出时，在OutOfMemoryError异常后面跟随的提示信息是“PermGen space”，说明运行时常量池的确是属于方法区（即JDK 6的HotSpot虚拟机中的永久代）的 一部分。
-
-而使用JDK 7或更高版本的JDK来运行这段程序并不会得到相同的结果，无论是在JDK 7中继续使 用-XX：MaxPermSize参数或者在JDK 8及以上版本使用-XX：MaxMeta-spaceSize参数把方法区容量同样限制在6MB，也都不会重现JDK 6中的溢出异常，循环将一直进行下去，永不停歇。
-这种变化是因为自JDK 7起，原本存放在永久代的字符串常量池被移至Java堆，所以在JDK 7及以上版 本，限制方法区的容量对该测试用例来说是毫无意义。
-
-这时候使用-Xmx参数限制最大堆到6MB就能看到以下两种运行结果之一，具体取决于哪里的对象分配时产生了溢出：
-
-```bash
-// OOM异常一： Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
-at java.base/java.lang.Integer.toString(Integer.java:440) 
-at java.base/java.lang.String.valueOf(String.java:3058) 
-at RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:12) 
-
-// OOM异常二： Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.base/java.util.HashMap.resize(HashMap.java:699) 
-at java.base/java.util.HashMap.putVal(HashMap.java:658) 
-at java.base/java.util.HashMap.put(HashMap.java:607) 
-at java.base/java.util.HashSet.add(HashSet.java:220) 
-at RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java from InputFile-Object:14)
-```
-
-字符串常量池的实现位置还有很多趣事：
-![](https://img-blog.csdnimg.cn/20210629230502171.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-JDK 6中运行，结果是两个false
-JDK 7中运行，一个true和一个false
-![](https://img-blog.csdnimg.cn/20210629231042658.png)
-因为JDK6的intern()会把首次遇到的字符串实例复制到永久代的字符串常量池中，返回的也是永久代里这个字符串实例的引用，而由StringBuilder创建的字符串对象实例在 Java 堆，所以不可能是同一个引用，结果将返回false。
-
-JDK 7及以后的intern()无需再拷贝字符串的实例到永久代，字符串常量池已移到Java堆，只需在常量池里记录一下首次出现的实例引用，因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。
-
-str2比较返回false，这是因为“java”这个字符串在执行String-Builder.toString()之前就已经出现过了，字符串常量池中已经有它的引用，不符合intern()方法要求“首次遇到”的原则，而“计算机软件”这个字符串则是首次 出现的，因此结果返回true！
-
-对于方法区的测试，基本的思路是运行时产生大量类去填满方法区，直到溢出。虽然直接使用Java SE API也可动态产生类（如反射时的 GeneratedConstructorAccessor和动态代理），但操作麻烦。
-借助了CGLib直接操作字节码运行时生成大量动态类。 当前的很多主流框架，如Spring、Hibernate对类进行增强时，都会使用到 CGLib字节码增强，当增强的类越多，就需要越大的方法区以保证动态生成的新类型可以载入内存。
-很多运行于JVM的动态语言（例如Groovy）通常都会持续创建新类型来支撑语言的动态性，随着这类动态语言的流行，与如下代码相似的溢出场景也越来越容易遇到
-
-在JDK 7中的运行结果： 
-```bash
-Caused by: java.lang.OutOfMemoryError: PermGen space 
-	at java.lang.ClassLoader.defineClass1(Native Method) 
-	at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632) 
-	at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
-```
-
-JDK8及以后：可以使用
-
-```bash
--XX:MetaspaceSize=10M
--XX:MaxMetaspaceSize=10M
-```
-设置元空间初始大小以及最大可分配大小。
-1.如果不指定元空间的大小，默认情况下，元空间最大的大小是系统内存的大小，元空间一直扩大，虚拟机可能会消耗完所有的可用系统内存。
-2.如果元空间内存不够用，就会报OOM。
-3.默认情况下，对应一个64位的服务端JVM来说，其默认的-XX:MetaspaceSize值为21MB，这就是初始的高水位线，一旦元空间的大小触及这个高水位线，就会触发Full GC并会卸载没有用的类，然后高水位线的值将会被重置。
-4.从第3点可以知道，如果初始化的高水位线设置过低，会频繁的触发Full GC，高水位线会被多次调整。所以为了避免频繁GC以及调整高水位线，建议将-XX:MetaspaceSize设置为较高的值，而-XX:MaxMetaspaceSize不进行设置。
-
-
-JDK8 运行结果：
-![](https://img-blog.csdnimg.cn/20210630161717101.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-一个类如果要被gc，要达成的条件比较苛刻。在经常运行时生成大量动态类的场景，就应该特别关注这些类的回收状况。
-这类场景除了之前提到的程序使用了CGLib字节码增强和动态语言外，常见的还有：
-- 大量JSP或动态产生JSP 文件的应用（JSP第一次运行时需要编译为Java类）
-- 基于OSGi的应用（即使是同一个类文件，被不同的加载器加载也会视为不同的类）
-
-JDK8后，永久代完全废弃，而使用元空间作为其替代者。在默认设置下，前面列举的那些正常的动态创建新类型的测试用例已经很难再迫使虚拟机产生方法区OOM。
-为了让使用者有预防实际应用里出现类似于如上代码那样的破坏性操作，HotSpot还是提供了一些参数作为元空间的防御措施：
-- -XX:MetaspaceSize
-指定元空间的初始空间大小，以字节为单位，达到该值就会触发垃圾收集进行类型卸载，同时收集器会对该值进行调整。如果释放了大量的空间，就适当降低该值，如果释放了很少空间，则在不超过-XX:MaxMetaspaceSize（如果设置了的话）的情况下，适当提高该值
-- -XX:MaxMetaspaceSize
-设置元空间最大值，默认-1，即不限制，或者说只受限于本地内存的大小
-- -XX:MinMetaspaceFreeRatio
-在GC后控制最小的元空间剩余容量的百分比，可减少因为元空间不足导致的GC频率
-- -XX:Max-MetaspaceFreeRatio
-控制最大的元空间剩余容量的百分比
-
-# 本机直接内存溢出
-直接内存（Direct Memory）的容量大小可通过`-XX:MaxDirectMemorySize`指定，若不指定，则默认与Java堆最大值（`-Xmx`）一致。
-
-这里越过DirectByteBuffer类，直接通过反射获取Unsafe实例进行内存分配。
-Unsafe类的getUnsafe()指定只有引导类加载器才会返回实例，体现了设计者希望只有虚拟机标准类库里面的类才能使用Unsafe，JDK10时才将Unsafe的部分功能通过VarHandle开放给外部。
-因为虽然使用DirectByteBuffer分配内存也会抛OOM，但它抛异常时并未真正向os申请分配内存，而是通过计算得知内存无法分配，就在代码里手动抛了OOM，真正申请分配内存的方法是**Unsafe::allocateMemory()**
-- 使用unsafe分配本机内存
-![](https://img-blog.csdnimg.cn/20210630163836454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 结果
-![](https://img-blog.csdnimg.cn/2021063016375144.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-由直接内存导致的内存溢出，一个明显的特征是在Heap Dump文件中不会看见有什么明显异常，若发现内存溢出之后产生的Dump文件很小，而程序中又直接或间接使用了 DirectMemory（比如使用NIO），则该考虑直接内存了。
\ No newline at end of file
diff --git "a/JDK/JVM/\345\244\215\344\271\240\357\274\232GC\345\210\206\347\261\273.md" "b/JDK/JVM/\345\244\215\344\271\240\357\274\232GC\345\210\206\347\261\273.md"
deleted file mode 100644
index fe5b5ef69a..0000000000
--- "a/JDK/JVM/\345\244\215\344\271\240\357\274\232GC\345\210\206\347\261\273.md"
+++ /dev/null
@@ -1,340 +0,0 @@
-
-## 复习：GC分类
-
-针对 HotSpot VM 的实现，它里面的 GC 按照回收区域又分为两大种类型：一种是部分收集（Partial GC），一种是整堆收集（Full GC）
-
-- 部分收集（Partial GC）：不是完整收集整个 Java 堆的垃圾收集。其中又分为：
-
-  - 新生代收集（Minor GC / Young GC）：只是新生代（Eden / S0, S1）的垃圾收集
-  - 老年代收集（Major GC / Old GC）：只是老年代的垃圾收集。目前，只有 CMS GC 会有单独收集老年代的行为。<mark>注意，很多时候 Major GC 会和 Full GC 混淆使用，需要具体分辨是老年代回收还是整堆回收。</mark>
-- 混合收集（Mixed GC）：收集整个新生代以及部分老年代的垃圾收集。目前，只有 G1 GC 会有这种行为
-- 整堆收集（Full GC）：收集整个 java 堆和方法区的垃圾收集。
-
-1. 新生代收集：只有当Eden区满的时候就会进行新生代收集，所以新生代收集和S0区域和S1区域情况无关
-
-2. 老年代收集和新生代收集的关系：进行老年代收集之前会先进行一次年轻代的垃圾收集，原因如下：一个比较大的对象无法放入新生代，那它自然会往老年代去放，如果老年代也放不下，那会先进行一次新生代的垃圾收集，之后尝试往新生代放，如果还是放不下，才会进行老年代的垃圾收集，之后在往老年代去放，这是一个过程，我来说明一下为什么需要往老年代放，但是放不下，而进行新生代垃圾收集的原因，这是因为新生代垃圾收集比老年代垃圾收集更加简单，这样做可以节省性能
-
-3. 进行垃圾收集的时候，堆包含新生代、老年代、元空间/永久代：可以看出Heap后面包含着新生代、老年代、元空间，但是我们设置堆空间大小的时候设置的只是新生代、老年代而已，元空间是分开设置的
-
-4. 哪些情况会触发Full GC：
-- 老年代空间不足
-- 方法区空间不足
-- 显示调用System.gc()
-- Minior GC进入老年代的数据的平均大小 大于 老年代的可用内存
-- 大对象直接进入老年代，而老年代的可用空间不足
-
-
-
-## 不同GC分类的GC细节
-
-用例代码:
-
-```Java
-/**
- *  -XX:+PrintCommandLineFlags
- *
- *  -XX:+UseSerialGC:表明新生代使用Serial GC ，同时老年代使用Serial Old GC
- *
- *  -XX:+UseParNewGC：标明新生代使用ParNew GC
- *
- *  -XX:+UseParallelGC:表明新生代使用Parallel GC
- *  -XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC
- *  说明：二者可以相互激活
- *
- *  -XX:+UseConcMarkSweepGC：表明老年代使用CMS GC。同时，年轻代会触发对ParNew 的使用
- * @author shkstart
- * @create 17:19
- */
-public class GCUseTest {
-    public static void main(String[] args) {
-        ArrayList<byte[]> list = new ArrayList<>();
-
-        while(true){
-            byte[] arr = new byte[1024 * 10];//10kb
-            list.add(arr);
-//            try {
-//                Thread.sleep(5);
-//            } catch (InterruptedException e) {
-//                e.printStackTrace();
-//            }
-        }
-    }
-} 
-```
-
-### 老年代使用CMS GC
-
-**GC设置方法**：参数中使用-XX:+UseConcMarkSweepGC，说明老年代使用CMS GC，同时年轻代也会触发对ParNew的使用，因此添加该参数之后，新生代使用ParNew GC，而老年代使用CMS GC，整体是并发垃圾收集，主打低延迟
-
-![image-20220419202643](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202551.png)
-
-打印出来的GC细节：
-
-![image-20220419211943](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419211943.png)
-
-
-
-### 新生代使用Serial GC
-
- **GC设置方法**：参数中使用-XX:+UseSerialGC，说明新生代使用Serial GC，同时老年代也会触发对Serial Old GC的使用，因此添加该参数之后，新生代使用Serial GC，而老年代使用Serial Old GC，整体是串行垃圾收集
-
-![image-20220419212907](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419212907.png)
-
- 打印出来的GC细节：
-
-![image-20220419212940](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419212940.png)
-
-DefNew代表新生代使用Serial GC，然后Tenured代表老年代使用Serial Old GC
-
-## GC 日志分类
-
-### MinorGC
-
-MinorGC（或 young GC 或 YGC）日志：
-
-```java
-[GC (Allocation Failure) [PSYoungGen: 31744K->2192K (36864K) ] 31744K->2200K (121856K), 0.0139308 secs] [Times: user=0.05 sys=0.01, real=0.01 secs]
-```
-
-![image-20220419202643](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202643.png)
-
-![image-20220419202718](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202718.png)
-
-### FullGC
-
-```java
-[Full GC (Metadata GC Threshold) [PSYoungGen: 5104K->0K (132096K) ] [Par01dGen: 416K->5453K (50176K) ]5520K->5453K (182272K), [Metaspace: 20637K->20637K (1067008K) ], 0.0245883 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]
-```
-
-![image-20220419202740](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202740.png)
-
-![image-20220419202804](http://pan.icebule.top/%E6%9C%89%E9%81%93%E4%BA%91%E7%AC%94%E8%AE%B0%E5%9B%BE%E5%BA%8A/%E4%B8%AA%E4%BA%BA%E7%AC%94%E8%AE%B0/JAVA/JVM/20220419202804.png)
-
-## GC 日志结构剖析
-
-### 透过日志看垃圾收集器
-
-- Serial 收集器：新生代显示 "[DefNew"，即 Default New Generation
-
-- ParNew 收集器：新生代显示 "[ParNew"，即 Parallel New Generation
-
-- Parallel Scavenge 收集器：新生代显示"[PSYoungGen"，JDK1.7 使用的即 PSYoungGen
-
-- Parallel Old 收集器：老年代显示"[ParoldGen"
-
-- G1 收集器：显示”garbage-first heap“
-
-### 透过日志看 GC 原因
-
-- Allocation Failure：表明本次引起 GC 的原因是因为新生代中没有足够的区域存放需要分配的数据
-- Metadata GCThreshold：Metaspace 区不够用了
-- FErgonomics：JVM 自适应调整导致的 GC
-- System：调用了 System.gc()方法
-
-### 透过日志看 GC 前后情况
-
-通过图示，我们可以发现 GC 日志格式的规律一般都是：GC 前内存占用-＞ GC 后内存占用（该区域内存总大小）
-
-```java
-[PSYoungGen: 5986K->696K (8704K) ] 5986K->704K (9216K)
-```
-
-- 中括号内：GC 回收前年轻代堆大小，回收后大小，（年轻代堆总大小）
-
-- 括号外：GC 回收前年轻代和老年代大小，回收后大小，（年轻代和老年代总大小）
-
-<mark>注意</mark>：Minor GC 堆内存总容量 = 9/10 年轻代 + 老年代。原因是 Survivor 区只计算 from 部分，而 JVM 默认年轻代中 Eden 区和 Survivor 区的比例关系，Eden:S0:S1=8:1:1。
-
-### 透过日志看 GC 时间
-
-GC 日志中有三个时间：user，sys 和 real
-
-- user：进程执行用户态代码（核心之外）所使用的时间。这是执行此进程所使用的实际 CPU 时间，其他进程和此进程阻塞的时间并不包括在内。在垃圾收集的情况下，表示 GC 线程执行所使用的 CPU 总时间。
-- sys：进程在内核态消耗的 CPU 时间，即在内核执行系统调用或等待系统事件所使用的 CPU 时间
-- real：程序从开始到结束所用的时钟时间。这个时间包括其他进程使用的时间片和进程阻塞的时间（比如等待 I/O 完成）。对于并行 gc，这个数字应该接近（用户时间＋系统时间）除以垃圾收集器使用的线程数。
-
-由于多核的原因，一般的 GC 事件中，real time 是小于 sys time ＋ user time 的，因为一般是多个线程并发的去做 GC，所以 real time 是要小于 sys ＋ user time 的。如果 real ＞ sys ＋ user 的话，则你的应用可能存在下列问题：IO 负载非常重或 CPU 不够用。
-
-## Minor GC 日志解析
-
-### 日志格式
-
-```Java
-2021-09-06T08:44:49.453+0800: 4.396: [GC (Allocation Failure) [PSYoungGen: 76800K->8433K(89600K)] 76800K->8449K(294400K), 0.0060231 secs] [Times: user=0.02 sys=0.01, real=0.01 secs]
-```
-
-### 日志解析
-
-#### 2021-09-06T08:44:49.453+0800
-
-日志打印时间 日期格式 如  2013-05-04T21:53:59.234+0800
-
-添加-XX:+PrintGCDateStamps参数
-
-#### 4.396
-
-gc 发生时，Java 虚拟机启动以来经过的秒数
-
-添加-XX:+PrintGCTimeStamps该参数
-
-#### [GC (Allocation Failure)
-
-发生了一次垃圾回收，这是一次 Minor GC。它不区分新生代 GC 还是老年代 GC，括号里的内容是 gc 发生的原因，这里 Allocation Failure 的原因是新生代中没有足够区域能够存放需要分配的数据而失败。
-
-#### [PSYoungGen: 76800K->8433K(89600K)]
-
-**PSYoungGen**：表示GC发生的区域，区域名称与使用的GC收集器是密切相关的
-
-- **Serial收集器**：Default New Generation 显示Defnew
-- **ParNew收集器**：ParNew
-- **Parallel Scanvenge收集器**：PSYoung
-- 老年代和新生代同理，也是和收集器名称相关
-
-**76800K->8433K(89600K)**：GC前该内存区域已使用容量->GC后盖区域容量(该区域总容量)
-
-- 如果是新生代，总容量则会显示整个新生代内存的9/10，即eden+from/to区
-- 如果是老年代，总容量则是全身内存大小，无变化
-
-#### 76800K->8449K(294400K)
-
-虽然本次是Minor GC，只会进行新生代的垃圾收集，但是也肯定会打印堆中总容量相关信息
-
-在显示完区域容量GC的情况之后，会接着显示整个堆内存区域的GC情况：GC前堆内存已使用容量->GC后堆内存容量（堆内存总容量），并且堆内存总容量 = 9/10 新生代 + 老年代，然后堆内存总容量肯定小于初始化的内存大小
-
-#### ,0.0088371
-
-整个GC所花费的时间，单位是秒
-
-#### [Times：user=0.02 sys=0.01,real=0.01 secs]
-
-- **user**：指CPU工作在用户态所花费的时间
-- **sys**：指CPU工作在内核态所花费的时间
-- **real**：指在此次事件中所花费的总时间
-
-## Full GC 日志解析
-
-### 日志格式
-
-```Java
-2021-09-06T08:44:49.453+0800: 4.396: [Full GC (Metadata GC Threshold) [PSYoungGen: 10082K->0K(89600K)] [ParOldGen: 32K->9638K(204800K)] 10114K->9638K(294400K), [Metaspace: 20158K->20156K(1067008K)], 0.0149928 secs] [Times: user=0.06 sys=0.02, real=0.02 secs]
-```
-
-### 日志解析
-
-#### 2020-11-20T17:19:43.794-0800
-
-日志打印时间 日期格式 如  2013-05-04T21:53:59.234+0800
-
-添加-XX:+PrintGCDateStamps参数
-
-#### 1.351
-
-gc 发生时，Java 虚拟机启动以来经过的秒数
-
-添加-XX:+PrintGCTimeStamps该参数
-
-#### Full GC(Metadata GCThreshold)
-
-括号中是gc发生的原因，原因：Metaspace区不够用了。
-除此之外，还有另外两种情况会引起Full GC，如下：
-
-1. Full GC(FErgonomics)
-   原因：JVM自适应调整导致的GC
-2. Full GC（System）
-   原因：调用了System.gc()方法
-
-#### [PSYoungGen: 100082K->0K(89600K)]
-
-**PSYoungGen**：表示GC发生的区域，区域名称与使用的GC收集器是密切相关的
-
-- **Serial收集器**：Default New Generation 显示DefNew
-- **ParNew收集器**：ParNew
-- **Parallel Scanvenge收集器**：PSYoungGen
-- 老年代和新生代同理，也是和收集器名称相关
-
-**10082K->0K(89600K)**：GC前该内存区域已使用容量->GC该区域容量(该区域总容量)
-
-- 如果是新生代，总容量会显示整个新生代内存的9/10，即eden+from/to区
-
-- 如果是老年代，总容量则是全部内存大小，无变化
-
-#### ParOldGen：32K->9638K(204800K)
-
-老年代区域没有发生GC，因此本次GC是metaspace引起的
-
-#### 10114K->9638K(294400K),
-
-在显示完区域容量GC的情况之后，会接着显示整个堆内存区域的GC情况：GC前堆内存已使用容量->GC后堆内存容量（堆内存总容量），并且堆内存总容量 = 9/10 新生代 + 老年代，然后堆内存总容量肯定小于初始化的内存大小
-
-#### [Meatspace:20158K->20156K(1067008K)],
-
-metaspace GC 回收2K空间
-
-
-
-## 论证FullGC是否会回收元空间/永久代垃圾
-
-```Java
-/**
- * jdk6/7中：
- * -XX:PermSize=10m -XX:MaxPermSize=10m
- * <p>
- * jdk8中：
- * -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
- *
- * @author IceBlue
- * @create 2020  22:24
- */
-public class OOMTest extends ClassLoader {
-    public static void main(String[] args) {
-        int j = 0;
-        try {
-            for (int i = 0; i < 100000; i++) {
-                OOMTest test = new OOMTest();
-                //创建ClassWriter对象，用于生成类的二进制字节码
-                ClassWriter classWriter = new ClassWriter(0);
-                //指明版本号，修饰符，类名，包名，父类，接口
-                classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
-                //返回byte[]
-                byte[] code = classWriter.toByteArray();
-                //类的加载
-                test.defineClass("Class" + i, code, 0, code.length);//Class对象
-            	test = null;
-                j++;
-            }
-        } finally {
-            System.out.println(j);
-        }
-    }
-}
-```
-
-输出结果：
-
-```
-[GC (Metadata GC Threshold) [PSYoungGen: 10485K->1544K(152576K)] 10485K->1552K(500736K), 0.0011517 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
-[Full GC (Metadata GC Threshold) [PSYoungGen: 1544K->0K(152576K)] [ParOldGen: 8K->658K(236544K)] 1552K->658K(389120K), [Metaspace: 3923K->3320K(1056768K)], 0.0051012 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
-[GC (Metadata GC Threshold) [PSYoungGen: 5243K->832K(152576K)] 5902K->1490K(389120K), 0.0009536 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
-
--------省略N行-------
-
-[Full GC (Last ditch collection) [PSYoungGen: 0K->0K(2427904K)] [ParOldGen: 824K->824K(5568000K)] 824K->824K(7995904K), [Metaspace: 3655K->3655K(1056768K)], 0.0041177 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
-Heap
- PSYoungGen      total 2427904K, used 0K [0x0000000755f80000, 0x00000007ef080000, 0x00000007ffe00000)
-  eden space 2426880K, 0% used [0x0000000755f80000,0x0000000755f80000,0x00000007ea180000)
-  from space 1024K, 0% used [0x00000007ea180000,0x00000007ea180000,0x00000007ea280000)
-  to   space 1536K, 0% used [0x00000007eef00000,0x00000007eef00000,0x00000007ef080000)
- ParOldGen       total 5568000K, used 824K [0x0000000602200000, 0x0000000755f80000, 0x0000000755f80000)
-  object space 5568000K, 0% used [0x0000000602200000,0x00000006022ce328,0x0000000755f80000)
- Metaspace       used 3655K, capacity 4508K, committed 9728K, reserved 1056768K
-  class space    used 394K, capacity 396K, committed 2048K, reserved 1048576K
-
-进程已结束,退出代码0
-
-```
-
-通过不断地动态生成类对象,输出GC日志
-
-根据GC日志我们可以看出当元空间容量耗尽时,会触发FullGC,而每次FullGC之前,至会进行一次MinorGC,而MinorGC只会回收新生代空间;
-
-只有在FullGC时,才会对新生代,老年代,永久代/元空间全部进行垃圾收集
\ No newline at end of file
diff --git "a/JDK/JVM/\347\274\226\350\257\221JDK13.md" "b/JDK/JVM/\347\274\226\350\257\221JDK13.md"
index 07e0b72daa..d3e6a1fc5d 100644
--- "a/JDK/JVM/\347\274\226\350\257\221JDK13.md"
+++ "b/JDK/JVM/\347\274\226\350\257\221JDK13.md"
@@ -13,7 +13,7 @@ $ brew install freetype ccache
 
 # 2 下载源码
 通过Mercurial代码管理版本管理工具从Repository中直接获取源码(Repository为http://hg.openjdk.java.net)
-![](https://img-blog.csdnimg.cn/20191017041658679.png?x-oss-process=maimage/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191017041658679.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 
 # 3 自动检测依赖
 进入解压后的文件夹，然后运行`bash ./configure`。这是一项检测所需要的依赖是否安装好了的脚本。只需要根据其提供的错误提示，将相应错误修改完成即可。
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Iterator\350\277\255\344\273\243\345\231\250\345\210\260\345\272\225\346\230\257\344\273\200\344\271\210?.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Iterator\350\277\255\344\273\243\345\231\250\345\210\260\345\272\225\346\230\257\344\273\200\344\271\210?.md"
deleted file mode 100644
index 09ae906da6..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Iterator\350\277\255\344\273\243\345\231\250\345\210\260\345\272\225\346\230\257\344\273\200\344\271\210?.md"
+++ /dev/null
@@ -1,109 +0,0 @@
-我们常使用 JDK 提供的迭代接口进行 Java 集合的迭代。
-```java
-Iterator iterator = list.iterator();
-while (iterator.hasNext()) {
-    String string = iterator.next();
-    //do something
-}
-```
-迭代可以简单地理解为遍历，是一个标准化遍历各类容器里面的所有对象的方法类。Iterator 模式是用于遍历集合类的标准访问方法。它可以把访问逻辑从不同类型集合类中抽象出来，从而避免向客户端暴露集合内部结构。 
-
-在没有迭代器时，我们都这么处理：
-数组处理：
-```java
-int[] arrays = new int[10];
-for(int i = 0 ; i < arrays.length ; i++){
-     int a = arrays[i];
-     // do sth
-}
-```
-ArrayList处理：
-```java
-List<String> list = new ArrayList<String>();
-for(int i = 0 ; i < list.size() ;  i++){
-   String string = list.get(i);
-   // do sth
-}
-```
-这些方式，都需要事先知道集合内部结构，访问代码和集合结构本身紧耦合，无法将访问逻辑从集合类和客户端代码中分离。同时每一种集合对应一种遍历方法，客户端代码无法复用。 
-
-实际应用中，若需要将上面将两个集合进行整合，则很麻烦。所以为解决如上问题， Iterator 模式诞生了。
-它总是用同一种逻辑遍历集合，从而客户端无需再维护集合内部结构，所有内部状态都由 Iterator 维护。客户端不直接和集合类交互，它只控制 Iterator，向它发送”向前”，”向后”，”取当前元素”的命令，即可实现对客户端透明地遍历整个集合。
-
-# java.util.Iterator
-在 Java 中 Iterator 为一个接口，它只提供迭代的基本规则，在 JDK 中他是这样定义的：对 collection 进行迭代的迭代器。
-![](https://img-blog.csdnimg.cn/20210627215010751.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-迭代器取代了Java集合框架中的 Enumeration。迭代器与枚举有两点不同：
-1. 迭代器允许调用者利用定义良好的语义在迭代期间，从迭代器所指向的 collection 移除元素
-2. 优化方法名
-
-其接口定义如下：
-![](https://img-blog.csdnimg.cn/20210627215245597.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-```java
-Object next()：返回迭代器刚越过的元素的引用，返回值是 Object，需要强制转换成自己需要的类型
-
-boolean hasNext()：判断容器内是否还有可供访问的元素
-
-void remove()：删除迭代器刚越过的元素
-```
-一般只需使用 next()、hasNext() 即可完成迭代：
-```java
-for(Iterator it = c.iterator(); it.hasNext(); ) {
- 　　Object o = it.next();
- 　　 // do sth
-}
-```
-所以Iterator一大优点是无需知道集合内部结构。集合的内部结构、状态都由 Iterator 维护，通过统一的方法 hasNext()、next() 来判断、获取下一个元素，至于具体的内部实现我们就不用关心了。
-# 各个集合的 Iterator 实现
-ArrayList 的内部实现采用数组，所以我们只需要记录相应位置的索引即可。
-
-## ArrayList 的 Iterator 实现
-在 ArrayList 内部首先是定义一个内部类 Itr，该内部类实现 Iterator 接口，如下：
-![](https://img-blog.csdnimg.cn/20210703230958705.png)
-
-- ArrayList#iterator() ：返回的是 Itr() 内部类
-![](https://img-blog.csdnimg.cn/20210703231158870.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 成员变量
-在 Itr 内部定义了三个 int 型的变量：
-- cursor
-下一个元素的索引位置
-- lastRet
-上一个元素的索引位置
-```java
-int cursor;             
-int lastRet = -1;     
-int expectedModCount = modCount;
-```
-所以lastRet 一直比 cursor 小 1。所以 hasNext() 实现很简单：
-![](https://img-blog.csdnimg.cn/20210703231439532.png)
-### next()
-实现其实也是比较简单，只要返回 cursor 索引位置处的元素即可，然后更新cursor、lastRet ：
-```java
-public E next() {
-    checkForComodification();
-    // 记录索引位置
-    int i = cursor;
-    // 如果获取元素大于集合元素个数，则抛出异常
-    if (i >= size)
-        throw new NoSuchElementException();
-    Object[] elementData = ArrayList.this.elementData;
-    if (i >= elementData.length)
-        throw new ConcurrentModificationException();
-    // cursor + 1    
-    cursor = i + 1;
-    // lastRet + 1 且返回cursor处元素
-    return (E) elementData[lastRet = i];
-}   
-```
-checkForComodification() 主要判断集合的修改次数是否合法，即判断遍历过程中集合是否被修改过。
-modCount 用于记录 ArrayList 集合的修改次数，初始化为 0。每当集合被修改一次（结构上面的修改，内部update不算），如 add、remove 等方法，modCount + 1。
-所以若 modCount 不变，则表示集合内容未被修改。该机制主要用于实现 ArrayList 集合的快速失败机制。所以要保证在遍历过程中不出错误，我们就应该保证在遍历过程中不会对集合产生结构上的修改（当然 remove 方法除外），出现了异常错误，我们就应该认真检查程序是否出错而不是 catch 后不做处理。
-![](https://img-blog.csdnimg.cn/20210703233049909.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### remove() 
-调用 ArrayList 本身的 remove() 方法删除 lastRet 位置元素，然后修改 modCount 即可。
-![](https://img-blog.csdnimg.cn/20210703233234428.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- SubList.this#remove(lastRet)
-![](https://img-blog.csdnimg.cn/20210703233837589.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- ArrayList#remove
-![](https://img-blog.csdnimg.cn/20210703233933396.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\346\226\260\347\211\271\346\200\247\344\271\213Lambda\350\241\250\350\276\276\345\274\217&Stream\346\265\201&\346\226\271\346\263\225\345\274\225\347\224\250.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\346\226\260\347\211\271\346\200\247\344\271\213Lambda\350\241\250\350\276\276\345\274\217&Stream\346\265\201&\346\226\271\346\263\225\345\274\225\347\224\250.md"
deleted file mode 100644
index 1801de8086..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\346\226\260\347\211\271\346\200\247\344\271\213Lambda\350\241\250\350\276\276\345\274\217&Stream\346\265\201&\346\226\271\346\263\225\345\274\225\347\224\250.md"
+++ /dev/null
@@ -1,1842 +0,0 @@
-> 集合优化了对象的存储，而流和对象的处理有关。
-
-流是一系列与特定存储机制无关的元素——实际上，流并没有“存储”之说。
-
-利用流，无需迭代集合中的元素，就可以提取和操作它们。这些管道通常被组合在一起，在流上形成一条操作管道。
-
-大多数情况下，将对象存储在集合是为了处理他们，因此你将会发现编程焦点从集合转移到了流。流的一个核心好处是，它使得**程序更加短小并且更易理解**。当 Lambda 表达式和方法引用和流一起使用的时候会让人感觉自成一体。流使得 Java 8 更添魅力。
-
-假如你要随机展示 5 至 20 之间不重复的整数并进行排序。实际上，你的关注点首先是创建一个有序集合。围绕这个集合进行后续操作。但使用流式编程，就可以简单陈述你想做什么：
-```java
-// streams/Randoms.java
-import java.util.*;
-public class Randoms {
-    public static void main(String[] args) {
-        new Random(47)
-            .ints(5, 20)
-            .distinct()
-            .limit(7)
-            .sorted()
-            .forEach(System.out::println);
-    }
-}
-```
-
-输出结果：
-
-```
-6
-10
-13
-16
-17
-18
-19
-```
-
-首先，我们给 **Random** 对象一个种子（以便程序再次运行时产生相同的输出）。`ints()` 方法产生一个流并且 `ints()` 方法有多种方式的重载 — 两个参数限定了数值产生的边界。这将生成一个整数流。我们可以使用中间流操作（intermediate stream operation） `distinct()` 来获取它们的非重复值，然后使用 `limit()` 方法获取前 7 个元素。接下来，我们使用 `sorted()` 方法排序。最终使用 `forEach()` 方法遍历输出，它根据传递给它的函数对每个流对象执行操作。在这里，我们传递了一个可以在控制台显示每个元素的方法引用。`System.out::println` 。
-
-注意 `Randoms.java` 中没有声明任何变量。流可以在不使用赋值或可变数据的情况下对有状态的系统建模，这非常有用。
-
-声明式编程（Declarative programming）是一种：声明要做什么，而非怎么做的编程风格。正如我们在函数式编程中所看到的。**注意**，命令式编程的形式更难以理解。代码示例：
-
-```java
-// streams/ImperativeRandoms.java
-import java.util.*;
-public class ImperativeRandoms {
-    public static void main(String[] args) {
-        Random rand = new Random(47);
-        SortedSet<Integer> rints = new TreeSet<>();
-        while(rints.size() < 7) {
-            int r = rand.nextInt(20);
-            if(r < 5) continue;
-            rints.add(r);
-        }
-        System.out.println(rints);
-    }
-}
-```
-
-输出结果：
-
-```
-[7, 8, 9, 11, 13, 15, 18]
-```
-
-在 `Randoms.java` 中，我们无需定义任何变量，但在这里我们定义了 3 个变量： `rand`，`rints` 和 `r`。由于 `nextInt()` 方法没有下限的原因（其内置的下限永远为 0），这段代码实现起来更复杂。所以我们要生成额外的值来过滤小于 5 的结果。
-
-**注意**，你必须要研究程序的真正意图，而在 `Randoms.java` 中，代码只是告诉了你它正在做什么。这种语义清晰性也是 Java 8 的流式编程更受推崇的重要原因。
-
-在 `ImperativeRandoms.java` 中显式地编写迭代机制称为外部迭代。而在 `Randoms.java` 中，流式编程采用内部迭代，这是流式编程的核心特性之一。这种机制使得编写的代码可读性更强，也更能利用多核处理器的优势。通过放弃对迭代过程的控制，我们把控制权交给并行化机制。我们将在[并发编程](24-Concurrent-Programming.md)一章中学习这部分内容。
-
-另一个重要方面，流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟，流使我们能够表示非常大（甚至无限）的序列，而不需要考虑内存问题。
-
-<!-- Java 8 Stream Support -->
-
-## 流支持
-
-Java 设计者面临着这样一个难题：现存的大量类库不仅为 Java 所用，同时也被应用在整个 Java 生态圈数百万行的代码中。如何将一个全新的流的概念融入到现有类库中呢？
-
-比如在 **Random** 中添加更多的方法。只要不改变原有的方法，现有代码就不会受到干扰。
-
-问题是，接口部分怎么改造呢？特别是涉及集合类接口的部分。如果你想把一个集合转换为流，直接向接口添加新方法会破坏所有老的接口实现类。
-
-Java 8 采用的解决方案是：在[接口](10-Interfaces.md)中添加被 `default`（`默认`）修饰的方法。通过这种方案，设计者们可以将流式（*stream*）方法平滑地嵌入到现有类中。流方法预置的操作几乎已满足了我们平常所有的需求。流操作的类型有三种：创建流，修改流元素（中间操作， Intermediate Operations），消费流元素（终端操作， Terminal Operations）。最后一种类型通常意味着收集流元素（通常是到集合中）。
-
-下面我们来看下每种类型的流操作。
-
-<!-- Stream Creation -->
-## 流创建
-
-你可以通过 `Stream.of()` 很容易地将一组元素转化成为流（`Bubble` 类在本章的后面定义）：
-
-```java
-// streams/StreamOf.java
-import java.util.stream.*;
-public class StreamOf {
-    public static void main(String[] args) {
-        Stream.of(new Bubble(1), new Bubble(2), new Bubble(3))
-            .forEach(System.out::println);
-        Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!")
-            .forEach(System.out::print);
-        System.out.println();
-        Stream.of(3.14159, 2.718, 1.618)
-            .forEach(System.out::println);
-    }
-}
-```
-
-输出结果：
-
-```
-Bubble(1)
-Bubble(2)
-Bubble(3)
-It's a wonderful day for pie!
-3.14159
-2.718
-1.618
-```
-
-每个集合都可通过 `stream()` 产生一个流。示例：
-
-```java
-import java.util.*;
-import java.util.stream.*;
-public class CollectionToStream {
-    public static void main(String[] args) {
-        List<Bubble> bubbles = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));
-        System.out.println(bubbles.stream()
-            .mapToInt(b -> b.i)
-            .sum());
-        
-        Set<String> w = new HashSet<>(Arrays.asList("It's a wonderful day for pie!".split(" ")));
-        w.stream()
-         .map(x -> x + " ")
-         .forEach(System.out::print);
-        System.out.println();
-        
-        Map<String, Double> m = new HashMap<>();
-        m.put("pi", 3.14159);
-        m.put("e", 2.718);
-        m.put("phi", 1.618);
-        m.entrySet().stream()
-                    .map(e -> e.getKey() + ": " + e.getValue())
-                    .forEach(System.out::println);
-    }
-}
-```
-
-输出结果：
-
-```
-6
-a pie! It's for wonderful day
-phi: 1.618
-e: 2.718
-pi: 3.14159
-```
-
-- 创建 `List<Bubble>` 对象后，只需简单调用**所有集合中都有**的 `stream()`。
-- 中间操作 `map()` 会获取流中的所有元素，并且对流中元素应用操作从而产生新的元素，并将其传递到后续的流中。通常 `map()` 会获取对象并产生新的对象，但在这里产生了特殊的用于数值类型的流。例如，`mapToInt()` 方法将一个对象流（object stream）转换成为包含整型数字的 `IntStream`。
-- 通过调用字符串的 `split()`来获取元素用于定义变量 `w`。
-- 为了从 **Map** 集合中产生流数据，我们首先调用 `entrySet()` 产生一个对象流，每个对象都包含一个 `key` 键以及与其相关联的 `value` 值。然后分别调用 `getKey()` 和 `getValue()` 获取值。
-
-### 随机数流
-
-`Random` 类被一组生成流的方法增强了。代码示例：
-
-```java
-// streams/RandomGenerators.java
-import java.util.*;
-import java.util.stream.*;
-public class RandomGenerators {
-    public static <T> void show(Stream<T> stream) {
-        stream
-        .limit(4)
-        .forEach(System.out::println);
-        System.out.println("++++++++");
-    }
-    
-    public static void main(String[] args) {
-        Random rand = new Random(47);
-        show(rand.ints().boxed());
-        show(rand.longs().boxed());
-        show(rand.doubles().boxed());
-        // 控制上限和下限：
-        show(rand.ints(10, 20).boxed());
-        show(rand.longs(50, 100).boxed());
-        show(rand.doubles(20, 30).boxed());
-        // 控制流大小：
-        show(rand.ints(2).boxed());
-        show(rand.longs(2).boxed());
-        show(rand.doubles(2).boxed());
-        // 控制流的大小和界限
-        show(rand.ints(3, 3, 9).boxed());
-        show(rand.longs(3, 12, 22).boxed());
-        show(rand.doubles(3, 11.5, 12.3).boxed());
-    }
-}
-```
-
-输出结果：
-
-```
--1172028779
-1717241110
--2014573909
-229403722
-++++++++
-2955289354441303771
-3476817843704654257
--8917117694134521474
-4941259272818818752
-++++++++
-0.2613610344283964
-0.0508673570556899
-0.8037155449603999
-0.7620665811558285
-++++++++
-16
-10
-11
-12
-++++++++
-65
-99
-54
-58
-++++++++
-29.86777681078574
-24.83968447804611
-20.09247112332014
-24.046793846338723
-++++++++
-1169976606
-1947946283
-++++++++
-2970202997824602425
--2325326920272830366
-++++++++
-0.7024254510631527
-0.6648552384607359
-++++++++
-6
-7
-7
-++++++++
-17
-12
-20
-++++++++
-12.27872414236691
-11.732085449736195
-12.196509449817267
-++++++++
-```
-
-为了消除冗余代码，我创建了一个泛型方法 `show(Stream<T> stream)` （在讲解泛型之前就使用这个特性，确实有点作弊，但是回报是值得的）。类型参数 `T` 可以是任何类型，所以这个方法对 **Integer**、**Long** 和 **Double** 类型都生效。但是 **Random** 类只能生成基本类型 **int**， **long**， **double** 的流。幸运的是， `boxed()` 流操作将会自动地把基本类型包装成为对应的装箱类型，从而使得 `show()` 能够接受流。
-
-我们可以使用 **Random** 为任意对象集合创建 **Supplier**。如下是一个文本文件提供字符串对象的例子。
-
-Cheese.dat 文件内容：
-
-```
-// streams/Cheese.dat
-Not much of a cheese shop really, is it?
-Finest in the district, sir.
-And what leads you to that conclusion?
-Well, it's so clean.
-It's certainly uncontaminated by cheese.
-```
-
-我们通过 **File** 类将 Cheese.dat 文件的所有行读取到 `List<String>` 中。代码示例：
-
-```java
-// streams/RandomWords.java
-import java.util.*;
-import java.util.stream.*;
-import java.util.function.*;
-import java.io.*;
-import java.nio.file.*;
-public class RandomWords implements Supplier<String> {
-    List<String> words = new ArrayList<>();
-    Random rand = new Random(47);
-    RandomWords(String fname) throws IOException {
-        List<String> lines = Files.readAllLines(Paths.get(fname));
-        // 略过第一行
-        for (String line : lines.subList(1, lines.size())) {
-            for (String word : line.split("[ .?,]+"))
-                words.add(word.toLowerCase());
-        }
-    }
-    public String get() {
-        return words.get(rand.nextInt(words.size()));
-    }
-    @Override
-    public String toString() {
-        return words.stream()
-            .collect(Collectors.joining(" "));
-    }
-    public static void main(String[] args) throws Exception {
-        System.out.println(
-            Stream.generate(new RandomWords("Cheese.dat"))
-                .limit(10)
-                .collect(Collectors.joining(" ")));
-    }
-}
-```
-
-输出结果：
-
-```
-it shop sir the much cheese by conclusion district is
-```
-
-在这里你可以看到更为复杂的 `split()` 运用。在构造器中，每一行都被 `split()` 通过空格或者被方括号包裹的任意标点符号进行分割。在结束方括号后面的 `+` 代表 `+` 前面的东西可以出现一次或者多次。
-
-我们注意到在构造函数中循环体使用命令式编程（外部迭代）。在以后的例子中，你甚至会看到我们如何消除这一点。这种旧的形式虽不是特别糟糕，但使用流会让人感觉更好。
-
-在 `toString()` 和主方法中你看到了 `collect()` 收集操作，它根据参数来组合所有流中的元素。
-
-当你使用 **Collectors.**`joining()`，你将会得到一个 `String` 类型的结果，每个元素都根据 `joining()` 的参数来进行分割。还有许多不同的 `Collectors` 用于产生不同的结果。
-
-在主方法中，我们提前看到了 **Stream.**`generate()` 的用法，它可以把任意  `Supplier<T>` 用于生成 `T` 类型的流。
-
-
-### int 类型的范围
-
-`IntStream` 类提供了  `range()` 方法用于生成整型序列的流。编写循环时，这个方法会更加便利：
-
-```java
-// streams/Ranges.java
-import static java.util.stream.IntStream.*;
-public class Ranges {
-    public static void main(String[] args) {
-        // 传统方法:
-        int result = 0;
-        for (int i = 10; i < 20; i++)
-            result += i;
-        System.out.println(result);
-        // for-in 循环:
-        result = 0;
-        for (int i : range(10, 20).toArray())
-            result += i;
-        System.out.println(result);
-        // 使用流:
-        System.out.println(range(10, 20).sum());
-    }
-}
-```
-
-输出结果：
-
-```
-145
-145
-145
-```
-
-在主方法中的第一种方式是我们传统编写 `for` 循环的方式；第二种方式，我们使用 `range()` 创建了流并将其转化为数组，然后在 `for-in` 代码块中使用。但是，如果你能像第三种方法那样全程使用流是更好的。我们对范围中的数字进行求和。在流中可以很方便的使用 `sum()` 操作求和。
-
-注意 **IntStream.**`range()` 相比 `onjava.Range.range()` 拥有更多的限制。这是由于其可选的第三个参数，后者允许步长大于 1，并且可以从大到小来生成。
-
-实用小功能 `repeat()` 可以用来替换简单的 `for` 循环。代码示例：
-
-```java
-// onjava/Repeat.java
-package onjava;
-import static java.util.stream.IntStream.*;
-public class Repeat {
-    public static void repeat(int n, Runnable action) {
-        range(0, n).forEach(i -> action.run());
-    }
-}
-```
-
-其产生的循环更加清晰：
-
-```java
-// streams/Looping.java
-import static onjava.Repeat.*;
-public class Looping {
-    static void hi() {
-        System.out.println("Hi!");
-    }
-    public static void main(String[] args) {
-        repeat(3, () -> System.out.println("Looping!"));
-        repeat(2, Looping::hi);
-    }
-}
-```
-
-输出结果：
-
-```
-Looping!
-Looping!
-Looping!
-Hi!
-Hi!
-```
-
-原则上，在代码中包含并解释 `repeat()` 并不值得。诚然它是一个相当透明的工具，但结果取决于你的团队和公司的运作方式。
-
-### generate()
-
-参照 `RandomWords.java` 中 **Stream.**`generate()` 搭配 `Supplier<T>` 使用的例子。代码示例：
-
-```java
-// streams/Generator.java
-import java.util.*;
-import java.util.function.*;
-import java.util.stream.*;
-
-public class Generator implements Supplier<String> {
-    Random rand = new Random(47);
-    char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
-    
-    public String get() {
-        return "" + letters[rand.nextInt(letters.length)];
-    }
-    
-    public static void main(String[] args) {
-        String word = Stream.generate(new Generator())
-                            .limit(30)
-                            .collect(Collectors.joining());
-        System.out.println(word);
-    }
-}
-```
-
-输出结果：
-
-```
-YNZBRNYGCFOWZNTCQRGSEGZMMJMROE
-```
-
-使用 `Random.nextInt()` 方法来挑选字母表中的大写字母。`Random.nextInt()` 的参数代表可以接受的最大的随机数范围，所以使用数组边界是经过深思熟虑的。
-
-如果要创建包含相同对象的流，只需要传递一个生成那些对象的 `lambda` 到 `generate()` 中：
-
-```java
-// streams/Duplicator.java
-import java.util.stream.*;
-public class Duplicator {
-    public static void main(String[] args) {
-        Stream.generate(() -> "duplicate")
-              .limit(3)
-              .forEach(System.out::println);
-    }
-}
-```
-
-输出结果：
-
-```
-duplicate
-duplicate
-duplicate
-```
-
-如下是在本章之前例子中使用过的 `Bubble` 类。**注意**它包含了自己的静态生成器（Static generator）方法。
-
-```java
-// streams/Bubble.java
-import java.util.function.*;
-public class Bubble {
-    public final int i;
-    
-    public Bubble(int n) {
-        i = n;
-    }
-    
-    @Override
-    public String toString() {
-        return "Bubble(" + i + ")";
-    }
-    
-    private static int count = 0;
-    public static Bubble bubbler() {
-        return new Bubble(count++);
-    }
-}
-```
-
-由于 `bubbler()` 与 `Supplier<Bubble>` 是接口兼容的，我们可以将其方法引用直接传递给 **Stream.**`generate()`：
-
-```java
-// streams/Bubbles.java
-import java.util.stream.*;
-public class Bubbles {
-    public static void main(String[] args) {
-        Stream.generate(Bubble::bubbler)
-              .limit(5)
-              .forEach(System.out::println);
-    }
-}
-```
-
-输出结果：
-
-```
-Bubble(0)
-Bubble(1)
-Bubble(2)
-Bubble(3)
-Bubble(4)
-```
-
-这是创建单独工厂类（Separate Factory class）的另一种方式。在很多方面它更加整洁，但是这是一个对于代码组织和品味的问题——你总是可以创建一个完全不同的工厂类。
-
-### iterate()
-
-**Stream.**`iterate()` 以种子（第一个参数）开头，并将其传给方法（第二个参数）。方法的结果将添加到流，并存储作为第一个参数用于下次调用 `iterate()`，依次类推。我们可以利用 `iterate()` 生成一个斐波那契数列。代码示例：
-
-```java
-// streams/Fibonacci.java
-import java.util.stream.*;
-public class Fibonacci {
-    int x = 1;
-    
-    Stream<Integer> numbers() {
-        return Stream.iterate(0, i -> {
-            int result = x + i;
-            x = i;
-            return result;
-        });
-    }
-    
-    public static void main(String[] args) {
-        new Fibonacci().numbers()
-                       .skip(20) // 过滤前 20 个
-                       .limit(10) // 然后取 10 个
-                       .forEach(System.out::println);
-    }
-}
-```
-
-输出结果：
-
-```
-6765
-10946
-17711
-28657
-46368
-75025
-121393
-196418
-317811
-514229
-```
-
-斐波那契数列将数列中最后两个元素进行求和以产生下一个元素。`iterate()` 只能记忆结果，因此我们需要利用一个变量 `x` 追踪另外一个元素。
-
-在主方法中，我们使用了一个之前没有见过的 `skip()` 操作。它根据参数丢弃指定数量的流元素。在这里，我们丢弃了前 20 个元素。
-
-### 流的建造者模式
-
-在建造者设计模式（也称构造器模式）中，首先创建一个 `builder` 对象，传递给它多个构造器信息，最后执行“构造”。**Stream** 库提供了这样的 `Builder`。在这里，我们重新审视文件读取并将其转换成为单词流的过程。代码示例：
-
-```java
-// streams/FileToWordsBuilder.java
-import java.io.*;
-import java.nio.file.*;
-import java.util.stream.*;
-
-public class FileToWordsBuilder {
-    Stream.Builder<String> builder = Stream.builder();
-    
-    public FileToWordsBuilder(String filePath) throws Exception {
-        Files.lines(Paths.get(filePath))
-             .skip(1) // 略过开头的注释行
-             .forEach(line -> {
-                  for (String w : line.split("[ .?,]+"))
-                      builder.add(w);
-              });
-    }
-    
-    Stream<String> stream() {
-        return builder.build();
-    }
-    
-    public static void main(String[] args) throws Exception {
-        new FileToWordsBuilder("Cheese.dat")
-            .stream()
-            .limit(7)
-            .map(w -> w + " ")
-            .forEach(System.out::print);
-    }
-}
-```
-
-输出结果：
-
-```
-Not much of a cheese shop really
-```
-
-**注意**，构造器会添加文件中的所有单词（除了第一行，它是包含文件路径信息的注释），但是其并没有调用 `build()`。只要你不调用 `stream()` 方法，就可以继续向 `builder` 对象中添加单词。
-
-在该类的更完整形式中，你可以添加一个标志位用于查看 `build()` 是否被调用，并且可能的话增加一个可以添加更多单词的方法。在 `Stream.Builder` 调用 `build()` 方法后继续尝试添加单词会产生一个异常。
-
-### Arrays
-
-`Arrays` 类中含有一个名为 `stream()` 的静态方法用于把数组转换成为流。我们可以重写 `interfaces/Machine.java` 中的主方法用于创建一个流，并将 `execute()` 应用于每一个元素。代码示例：
-
-```java
-// streams/Machine2.java
-import java.util.*;
-import onjava.Operations;
-public class Machine2 {
-    public static void main(String[] args) {
-        Arrays.stream(new Operations[] {
-            () -> Operations.show("Bing"),
-            () -> Operations.show("Crack"),
-            () -> Operations.show("Twist"),
-            () -> Operations.show("Pop")
-        }).forEach(Operations::execute);
-    }
-}
-```
-
-输出结果：
-
-```
-Bing
-Crack
-Twist
-Pop
-```
-
-`new Operations[]` 表达式动态创建了 `Operations` 对象的数组。
-
-`stream()` 同样可以产生 **IntStream**，**LongStream** 和 **DoubleStream**。
-
-```java
-// streams/ArrayStreams.java
-import java.util.*;
-import java.util.stream.*;
-
-public class ArrayStreams {
-    public static void main(String[] args) {
-        Arrays.stream(new double[] { 3.14159, 2.718, 1.618 })
-            .forEach(n -> System.out.format("%f ", n));
-        System.out.println();
-        
-        Arrays.stream(new int[] { 1, 3, 5 })
-            .forEach(n -> System.out.format("%d ", n));
-        System.out.println();
-        
-        Arrays.stream(new long[] { 11, 22, 44, 66 })
-            .forEach(n -> System.out.format("%d ", n));
-        System.out.println();
-        
-        // 选择一个子域:
-        Arrays.stream(new int[] { 1, 3, 5, 7, 15, 28, 37 }, 3, 6)
-            .forEach(n -> System.out.format("%d ", n));
-    }
-}
-```
-
-输出结果：
-
-```
-3.141590 2.718000 1.618000
-1 3 5
-11 22 44 66
-7 15 28
-```
-
-最后一次 `stream()` 的调用有两个额外的参数。第一个参数告诉 `stream()` 从数组的哪个位置开始选择元素，第二个参数用于告知在哪里停止。每种不同类型的 `stream()` 都有类似的操作。
-
-### 正则表达式
-
-Java 的正则表达式将在[字符串](18-Strings.md)这一章节详细介绍。Java 8 在 `java.util.regex.Pattern` 中增加了一个新的方法 `splitAsStream()`。这个方法可以根据传入的公式将字符序列转化为流。但是有一个限制，输入只能是 **CharSequence**，因此不能将流作为 `splitAsStream()` 的参数。
-
-我们再一次查看将文件处理为单词流的过程。这一次，我们使用流将文件分割为单独的字符串，接着使用正则表达式将字符串转化为单词流。
-
-```java
-// streams/FileToWordsRegexp.java
-import java.io.*;
-import java.nio.file.*;
-import java.util.stream.*;
-import java.util.regex.Pattern;
-public class FileToWordsRegexp {
-    private String all;
-    public FileToWordsRegexp(String filePath) throws Exception {
-        all = Files.lines(Paths.get(filePath))
-        .skip(1) // First (comment) line
-        .collect(Collectors.joining(" "));
-    }
-    public Stream<String> stream() {
-        return Pattern
-        .compile("[ .,?]+").splitAsStream(all);
-    }
-    public static void
-    main(String[] args) throws Exception {
-        FileToWordsRegexp fw = new FileToWordsRegexp("Cheese.dat");
-        fw.stream()
-          .limit(7)
-          .map(w -> w + " ")
-          .forEach(System.out::print);
-        fw.stream()
-          .skip(7)
-          .limit(2)
-          .map(w -> w + " ")
-          .forEach(System.out::print);
-    }
-}
-```
-
-输出结果：
-
-```
-Not much of a cheese shop really is it
-```
-
-在构造器中我们读取了文件中的所有内容（跳过第一行注释，并将其转化成为单行字符串）。现在，当你调用 `stream()` 的时候，可以像往常一样获取一个流，但这次你可以多次调用 `stream()` 在已存储的字符串中创建一个新的流。这里有个限制，整个文件必须存储在内存中；在大多数情况下这并不是什么问题，但是这损失了流操作非常重要的优势：
-
-1. 流“不需要存储”。当然它们需要一些内部存储，但是这只是序列的一小部分，和持有整个序列并不相同。
-2. 它们是懒加载计算的。
-
-幸运的是，我们稍后就会知道如何解决这个问题。
-
-<!-- Intermediate Operations -->
-
-## 中间操作
-
-中间操作用于从一个流中获取对象，并将对象作为另一个流从后端输出，以连接到其他操作。
-
-### 跟踪和调试
-
-`peek()` 操作的目的是帮助调试。它允许你无修改地查看流中的元素。代码示例：
-
-```java
-// streams/Peeking.java
-class Peeking {
-    public static void main(String[] args) throws Exception {
-        FileToWords.stream("Cheese.dat")
-        .skip(21)
-        .limit(4)
-        .map(w -> w + " ")
-        .peek(System.out::print)
-        .map(String::toUpperCase)
-        .peek(System.out::print)
-        .map(String::toLowerCase)
-        .forEach(System.out::print);
-    }
-}
-```
-
-输出结果：
-
-```
-Well WELL well it IT it s S s so SO so
-```
-
-`FileToWords` 稍后定义，但它的功能实现貌似和之前我们看到的差不多：产生字符串对象的流。之后在其通过管道时调用 `peek()` 进行处理。
-
-因为 `peek()` 符合无返回值的 **Consumer** 函数式接口，所以我们只能观察，无法使用不同的元素来替换流中的对象。
-
-### 流元素排序
-
-在 `Randoms.java` 中，我们熟识了 `sorted()` 的默认比较器实现。其实它还有另一种形式的实现：传入一个 **Comparator** 参数。代码示例：
-
-```java
-// streams/SortedComparator.java
-import java.util.*;
-public class SortedComparator {
-    public static void main(String[] args) throws Exception {
-        FileToWords.stream("Cheese.dat")
-        .skip(10)
-        .limit(10)
-        .sorted(Comparator.reverseOrder())
-        .map(w -> w + " ")
-        .forEach(System.out::print);
-    }
-}
-```
-
-输出结果：
-
-```
-you what to the that sir leads in district And
-```
-
-`sorted()` 预设了一些默认的比较器。这里我们使用的是反转“自然排序”。当然你也可以把 Lambda 函数作为参数传递给 `sorted()`。
-
-### 移除元素
-
-* `distinct()`：在 `Randoms.java` 类中的 `distinct()` 可用于消除流中的重复元素。相比创建一个 **Set** 集合，该方法的工作量要少得多。
-
-* `filter(Predicate)`：过滤操作会保留与传递进去的过滤器函数计算结果为 `true` 的元素。
-
-在下例中，`isPrime()` 作为过滤器函数，用于检测质数。
-
-```java
-import java.util.stream.*;
-import static java.util.stream.LongStream.*;
-public class Prime {
-    public static Boolean isPrime(long n) {
-        return rangeClosed(2, (long)Math.sqrt(n))
-        .noneMatch(i -> n % i == 0);
-    }
-    public LongStream numbers() {
-        return iterate(2, i -> i + 1)
-        .filter(Prime::isPrime);
-    }
-    public static void main(String[] args) {
-        new Prime().numbers()
-        .limit(10)
-        .forEach(n -> System.out.format("%d ", n));
-        System.out.println();
-        new Prime().numbers()
-        .skip(90)
-        .limit(10)
-        .forEach(n -> System.out.format("%d ", n));
-    }
-}
-```
-
-输出结果：
-
-```
-2 3 5 7 11 13 17 19 23 29
-467 479 487 491 499 503 509 521 523 541
-```
-
-`rangeClosed()` 包含了上限值。如果不能整除，即余数不等于 0，则 `noneMatch()` 操作返回 `true`，如果出现任何等于 0 的结果则返回 `false`。 `noneMatch()` 操作一旦有失败就会退出。
-
-### 应用函数到元素
-
-- `map(Function)`：将函数操作应用在输入流的元素中，并将返回值传递到输出流中。
-
-- `mapToInt(ToIntFunction)`：操作同上，但结果是 **IntStream**。
-
-- `mapToLong(ToLongFunction)`：操作同上，但结果是 **LongStream**。
-
-- `mapToDouble(ToDoubleFunction)`：操作同上，但结果是 **DoubleStream**。
-
-在这里，我们使用 `map()` 映射多种函数到一个字符串流中。代码示例：
-
-```java
-// streams/FunctionMap.java
-import java.util.*;
-import java.util.stream.*;
-import java.util.function.*;
-class FunctionMap {
-    static String[] elements = { "12", "", "23", "45" };
-    static Stream<String>
-    testStream() {
-        return Arrays.stream(elements);
-    }
-    static void test(String descr, Function<String, String> func) {
-        System.out.println(" ---( " + descr + " )---");
-        testStream()
-        .map(func)
-        .forEach(System.out::println);
-    }
-    public static void main(String[] args) {
-        test("add brackets", s -> "[" + s + "]");
-        test("Increment", s -> {
-            try {
-                return Integer.parseInt(s) + 1 + "";
-            }
-            catch(NumberFormatException e) {
-                return s;
-            }
-        }
-        );
-        test("Replace", s -> s.replace("2", "9"));
-        test("Take last digit", s -> s.length() > 0 ?
-        s.charAt(s.length() - 1) + "" : s);
-    }
-}
-```
-
-输出结果：
-
-```
----( add brackets )---
-[12]
-[]
-[23]
-[45]
----( Increment )---
-13
-24
-46
----( Replace )---
-19
-93
-45
----( Take last digit )---
-2
-3
-5
-```
-
-在上面的自增示例中，我们使用 `Integer.parseInt()` 尝试将一个字符串转化为整数。如果字符串不能转化成为整数就会抛出 **NumberFormatException** 异常，我们只须回过头来将原始字符串放回到输出流中。
-
-在以上例子中，`map()` 将一个字符串映射为另一个字符串，但是我们完全可以产生和接收类型完全不同的类型，从而改变流的数据类型。下面代码示例：
-
-```java
-// streams/FunctionMap2.java
-// Different input and output types （不同的输入输出类型）
-import java.util.*;
-import java.util.stream.*;
-class Numbered {
-    final int n;
-    Numbered(int n) {
-        this.n = n;
-    }
-    @Override
-    public String toString() {
-        return "Numbered(" + n + ")";
-    }
-}
-class FunctionMap2 {
-    public static void main(String[] args) {
-        Stream.of(1, 5, 7, 9, 11, 13)
-        .map(Numbered::new)
-        .forEach(System.out::println);
-    }
-}
-```
-
-输出结果：
-
-```
-Numbered(1)
-Numbered(5)
-Numbered(7)
-Numbered(9)
-Numbered(11)
-Numbered(13)
-```
-
-我们将获取到的整数通过构造器 `Numbered::new` 转化成为 `Numbered` 类型。
-
-如果使用 **Function** 返回的结果是数值类型的一种，我们必须使用合适的 `mapTo数值类型` 进行替代。代码示例：
-
-```java
-// streams/FunctionMap3.java
-// Producing numeric output streams（ 产生数值输出流）
-import java.util.*;
-import java.util.stream.*;
-class FunctionMap3 {
-    public static void main(String[] args) {
-        Stream.of("5", "7", "9")
-        .mapToInt(Integer::parseInt)
-        .forEach(n -> System.out.format("%d ", n));
-        System.out.println();
-        Stream.of("17", "19", "23")
-        .mapToLong(Long::parseLong)
-        .forEach(n -> System.out.format("%d ", n));
-        System.out.println();
-        Stream.of("17", "1.9", ".23")
-        .mapToDouble(Double::parseDouble)
-        .forEach(n -> System.out.format("%f ", n));
-    }
-}
-```
-
-输出结果：
-
-```
-5 7 9
-17 19 23
-17.000000 1.900000 0.230000
-```
-
-遗憾的是，Java 设计者并没有尽最大努力去消除基本类型。
-
-### 在 `map()` 中组合流
-假设现有一个传入的元素流，并且打算对流元素使用 `map()` 函数。现在你已经找到了一些可爱并独一无二的函数功能，但问题来了：这个函数功能是产生一个流。我们想要产生一个元素流，而实际却产生了一个元素流的流。
-
-`flatMap()` 做了两件事：
-- 将产生流的函数应用在每个元素上（与 `map()` 所做的相同）
-- 然后将每个流都扁平化为元素
-
-因而最终产生的仅是元素。
-
-`flatMap(Function)`：当 `Function` 产生流时使用。
-
-`flatMapToInt(Function)`：当 `Function` 产生 `IntStream` 时使用。
-
-`flatMapToLong(Function)`：当 `Function` 产生 `LongStream` 时使用。
-
-`flatMapToDouble(Function)`：当 `Function` 产生 `DoubleStream` 时使用。
-
-为了弄清其工作原理，我们从传入一个刻意设计的函数给  `map()` 开始。该函数接受一个整数并产生一个字符串流：
-![](https://img-blog.csdnimg.cn/29e0507209fe478f9cc9e1a4e8607c86.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-我们天真地希望能够得到字符串流，但实际得到的却是“Head”流的流。
-可使用 `flatMap()` 解决：
-![](https://img-blog.csdnimg.cn/ef3d02412fe9421c94572bea489b0482.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-从map返回的每个流都会自动扁平为组成它的字符串。
-
-现在从一个整数流开始，然后使用每个整数去创建更多的随机数。
-![](https://img-blog.csdnimg.cn/fd12336ec98f45de924bf36d7da930ad.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
- `concat()`以参数顺序组合两个流。 如此，我们在每个随机 `Integer` 流的末尾添加一个 -1 作为标记。你可以看到最终流确实是从一组扁平流中创建的。
-
-因为 `rand.ints()` 产生的是一个 `IntStream`，所以必须使用 `flatMap()`、`concat()` 和 `of()` 的特定整数形式。
-
-将文件划分为单词流。
-最后使用到的是 **FileToWordsRegexp.java**，它的问题是需要将整个文件读入行列表中 —— 显然需要存储该列表。而我们真正想要的是创建一个不需要中间存储层的单词流。
-
-下面，我们再使用 ` flatMap()` 来解决这个问题：
-![](https://img-blog.csdnimg.cn/3ac1b73f2dd34700972a38578eae8070.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-`stream()` 现在是个静态方法，因为它可自己完成整个流创建过程。
-
-**注意**：`\\W+` 是一个正则表达式，表示非单词字符，`+` 表示可出现一或多次。小写形式的 `\\w` 表示“单词字符”。
-
-之前遇到的问题是 `Pattern.compile().splitAsStream()` 产生的结果为流，这意味着当只想要一个简单的单词流时，在传入的行流（stream of lines）上调用 `map()` 会产生一个单词流的流。
-好在 `flatMap()`  可将**元素流的流**扁平化为一个**简单的元素流**。或者，可使用 `String.split()` 生成一个数组，其可以被 `Arrays.stream()` 转化成为流：
-```java
-.flatMap(line -> Arrays.stream(line.split("\\W+"))))
-```
-有了真正的、而非 `FileToWordsRegexp.java` 中基于集合存储的流，我们每次使用都必须从头创建，因为流不能被复用：
-![](https://img-blog.csdnimg.cn/2e91abc8794944bdb9aba4a352ba8992.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-在 `System.out.format()` 中的 `%s` 表明参数为 **String** 类型。
-
-## Optional类
-若在一个空流取元素会发生什么？我们喜欢为了“happy path”而将流连接起来，并假设流不会被中断。在流中放置 `null` 就是个很好的中断方法。那么是否有某种对象，可作为流元素的持有者，即使查看的元素不存在也能友好地提示我们（即不会粗暴地抛异常）？
-
-**Optional** 就可以。一些标准流操作返回 **Optional** 对象，因为它们**并不能保证预期结果一定存在**：
-- `findFirst()`
-返回一个包含第一个元素的 **Optional** 对象，若流为空则返回 **Optional.empty**
-- `findAny()`
-返回包含任意元素的 **Optional** 对象，若流为空则返回 **Optional.empty**
-- `max()` 和 `min()`
-返回一个包含最大值或者最小值的 **Optional** 对象，若流为空则返回 **Optional.empty**
-
- `reduce()` 不再以 `identity` 形式开头，而是将其返回值包装在 **Optional** 中。（`identity` 对象成为其他形式的 `reduce()` 的默认结果，因此不存在空结果的风险）
-
-对于数字流 **IntStream**、**LongStream** 和 **DoubleStream**，`average()` 会将结果包装在 **Optional** 以防止流为空。
-
-以下是对空流进行所有这些操作的简单测试：
-```java
-class OptionalsFromEmptyStreams {
-    public static void main(String[] args) {
-        System.out.println(Stream.<String>empty()
-             .findFirst());
-        System.out.println(Stream.<String>empty()
-             .findAny());
-        System.out.println(Stream.<String>empty()
-             .max(String.CASE_INSENSITIVE_ORDER));
-        System.out.println(Stream.<String>empty()
-             .min(String.CASE_INSENSITIVE_ORDER));
-        System.out.println(Stream.<String>empty()
-             .reduce((s1, s2) -> s1 + s2));
-        System.out.println(IntStream.empty()
-             .average());
-    }
-}
-
-Optional.empty
-Optional.empty
-Optional.empty
-Optional.empty
-Optional.empty
-OptionalDouble.empty
-```
-
-当流为空的时候你会获得一个 **Optional.empty** 对象，而不是抛异常。**Optional** 的 `toString()` 方法可以用于展示有用信息。
-
-空流是通过 `Stream.<String>empty()` 创建的。如果你在没有任何上下文环境的情况下调用 `Stream.empty()`，Java 并不知道它的数据类型；这个语法解决了这个问题。如果编译器拥有了足够的上下文信息，比如：
-```java
-Stream<String> s = Stream.empty();
-```
-就可以在调用 `empty()` 时推断类型。
-
-**Optional** 的两个基本用法：
-```java
-class OptionalBasics {
-    static void test(Optional<String> optString) {
-        if(optString.isPresent())
-            System.out.println(optString.get()); 
-        else
-            System.out.println("Nothing inside!");
-    }
-    public static void main(String[] args) {
-        test(Stream.of("Epithets").findFirst());
-        test(Stream.<String>empty().findFirst());
-    }
-}
-
-Epithets
-Nothing inside!
-```
-
-当你接收到 **Optional** 对象时，应首先调用 `isPresent()` 检查其中是否包含元素。如果存在，可使用 `get()` 获取。
-
-### 便利函数
-有许多便利函数可以解包 **Optional** ，这简化了上述“对所包含的对象的检查和执行操作”的过程：
-
-- `ifPresent(Consumer)`：当值存在时调用 **Consumer**，否则什么也不做。
-- `orElse(otherObject)`：如果值存在则直接返回，否则生成 **otherObject**。
-- `orElseGet(Supplier)`：如果值存在则直接返回，否则使用 **Supplier** 函数生成一个可替代对象。
-- `orElseThrow(Supplier)`：如果值存在直接返回，否则使用 **Supplier** 函数生成一个异常。
-
-如下是针对不同便利函数的简单演示：
-```java
-public class Optionals {
-    static void basics(Optional<String> optString) {
-        if(optString.isPresent())
-            System.out.println(optString.get()); 
-        else
-            System.out.println("Nothing inside!");
-    }
-    static void ifPresent(Optional<String> optString) {
-        optString.ifPresent(System.out::println);
-    }
-    static void orElse(Optional<String> optString) {
-        System.out.println(optString.orElse("Nada"));
-    }
-    static void orElseGet(Optional<String> optString) {
-        System.out.println(
-        optString.orElseGet(() -> "Generated"));
-    }
-    static void orElseThrow(Optional<String> optString) {
-        try {
-            System.out.println(optString.orElseThrow(
-            () -> new Exception("Supplied")));
-        } catch(Exception e) {
-            System.out.println("Caught " + e);
-        }
-    }
-    static void test(String testName, Consumer<Optional<String>> cos) {
-        System.out.println(" === " + testName + " === ");
-        cos.accept(Stream.of("Epithets").findFirst());
-        cos.accept(Stream.<String>empty().findFirst());
-    }
-    public static void main(String[] args) {
-        test("basics", Optionals::basics);
-        test("ifPresent", Optionals::ifPresent);
-        test("orElse", Optionals::orElse);
-        test("orElseGet", Optionals::orElseGet);
-        test("orElseThrow", Optionals::orElseThrow);
-    }
-}
-
-=== basics ===
-Epithets
-Nothing inside!
-=== ifPresent ===
-Epithets
-=== orElse ===
-Epithets
-Nada
-=== orElseGet ===
-Epithets
-Generated
-=== orElseThrow ===
-Epithets
-Caught java.lang.Exception: Supplied
-```
-
-`test()` 通过传入所有方法都适用的 **Consumer** 来避免重复代码。
-
-`orElseThrow()` 通过 **catch** 关键字来捕获抛出的异常。
-### 创建 Optional
-当我们在自己的代码中加入 **Optional** 时，可以使用下面 3 个静态方法：
-- `empty()`：生成一个空 **Optional**。
-- `of(value)`：将一个非空值包装到 **Optional** 里。
-- `ofNullable(value)`：针对一个可能为空的值，为空时自动生成 **Optional.empty**，否则将值包装在 **Optional** 中。
-
-代码示例：
-```java
-class CreatingOptionals {
-    static void test(String testName, Optional<String> opt) {
-        System.out.println(" === " + testName + " === ");
-        System.out.println(opt.orElse("Null"));
-    }
-    public static void main(String[] args) {
-        test("empty", Optional.empty());
-        test("of", Optional.of("Howdy"));
-        try {
-            test("of", Optional.of(null));
-        } catch(Exception e) {
-            System.out.println(e);
-        }
-        test("ofNullable", Optional.ofNullable("Hi"));
-        test("ofNullable", Optional.ofNullable(null));
-    }
-}
-
-=== empty ===
-Null
-=== of ===
-Howdy
-java.lang.NullPointerException
-=== ofNullable ===
-Hi
-=== ofNullable ===
-Null
-```
-
-我们不能通过传递 `null` 到 `of()` 来创建 `Optional` 对象。最安全的方法是， 使用 `ofNullable()` 来优雅地处理 `null`。
-
-### Optional 对象操作
-当我们的流管道生成了 **Optional** 对象，如下方法可使得 **Optional** 的后续能做更多操作：
-
-- `filter(Predicate)`：将 **Predicate** 应用于 **Optional** 中的内容并返回结果。当 **Optional** 不满足 **Predicate** 时返回空。如果 **Optional** 为空，则直接返回。
-
-- `map(Function)`：如果 **Optional** 不为空，应用 **Function**  于 **Optional** 中的内容，并返回结果。否则直接返回 **Optional.empty**。
-
-- `flatMap(Function)`：同 `map()`，但是提供的映射函数将结果包装在 **Optional** 对象中，因此 `flatMap()` 不会在最后进行任何包装。
-
-以上方法都不适用于数值型 **Optional**。
-一般来说，流的 `filter()` 会在 **Predicate** 返回 `false` 时移除流元素。
-而 `Optional.filter()` 在失败时不会删除 **Optional**，而是将其保留下来，并转化为空。
-
-```java
-class OptionalFilter {
-    static String[] elements = {
-            "Foo", "", "Bar", "Baz", "Bingo"
-    };
-    static Stream<String> testStream() {
-        return Arrays.stream(elements);
-    }
-    static void test(String descr, Predicate<String> pred) {
-        System.out.println(" ---( " + descr + " )---");
-        for(int i = 0; i <= elements.length; i++) {
-            System.out.println(
-                    testStream()
-                            .skip(i)
-                            .findFirst()
-                            .filter(pred));
-        }
-    }
-    public static void main(String[] args) {
-        test("true", str -> true);
-        test("false", str -> false);
-        test("str != \"\"", str -> str != "");
-        test("str.length() == 3", str -> str.length() == 3);
-        test("startsWith(\"B\")",
-                str -> str.startsWith("B"));
-    }
-}
-```
-即使输出看起来像流，特别是 `test()` 中的 for 循环。每一次的 for 循环时重新启动流，然后根据 for 循环的索引跳过指定个数的元素，这就是它最终在流中的每个连续元素上的结果。接下来调用 `findFirst()` 获取剩余元素中的第一个元素，结果会包装在 **Optional** 中。
-
-**注意**，不同于普通 for 循环，这里的索引值范围并不是 `i < elements.length`， 而是 `i <= elements.length`。所以最后一个元素实际上超出了流。方便的是，这将自动成为 **Optional.empty**。
-
-同 `map()` 一样 ， `Optional.map()` 应用于函数。它仅在 **Optional** 不为空时才应用映射函数，并将 **Optional** 的内容提取到映射函数。代码示例：
-
-```java
-class OptionalMap {
-    static String[] elements = {"12", "", "23", "45"};
-
-    static Stream<String> testStream() {
-        return Arrays.stream(elements);
-    }
-
-    static void test(String descr, Function<String, String> func) {
-        System.out.println(" ---( " + descr + " )---");
-        for (int i = 0; i <= elements.length; i++) {
-            System.out.println(
-                    testStream()
-                            .skip(i)
-                            .findFirst() // Produces an Optional
-                            .map(func));
-        }
-    }
-
-    public static void main(String[] args) {
-        // If Optional is not empty, map() first extracts
-        // the contents which it then passes
-        // to the function:
-        test("Add brackets", s -> "[" + s + "]");
-        test("Increment", s -> {
-            try {
-                return Integer.parseInt(s) + 1 + "";
-            } catch (NumberFormatException e) {
-                return s;
-            }
-        });
-        test("Replace", s -> s.replace("2", "9"));
-        test("Take last digit", s -> s.length() > 0 ?
-                s.charAt(s.length() - 1) + "" : s);
-    }
-    // After the function is finished, map() wraps the
-    // result in an Optional before returning it:
-}
-```
-映射函数的返回结果会自动包装成为 **Optional**。**Optional.empty** 会被直接跳过。
-
-**Optional** 的 `flatMap()` 应用于已生成 **Optional** 的映射函数，所以 `flatMap()` 不会像 `map()` 那样将结果封装在 **Optional** 中。代码示例：
-
-```java
-// streams/OptionalFlatMap.java
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.function.Function;
-import java.util.stream.Stream;
-
-class OptionalFlatMap {
-    static String[] elements = {"12", "", "23", "45"};
-
-    static Stream<String> testStream() {
-        return Arrays.stream(elements);
-    }
-
-    static void test(String descr,
-                     Function<String, Optional<String>> func) {
-        System.out.println(" ---( " + descr + " )---");
-        for (int i = 0; i <= elements.length; i++) {
-            System.out.println(
-                    testStream()
-                            .skip(i)
-                            .findFirst()
-                            .flatMap(func));
-        }
-    }
-
-    public static void main(String[] args) {
-        test("Add brackets",
-                s -> Optional.of("[" + s + "]"));
-        test("Increment", s -> {
-            try {
-                return Optional.of(
-                        Integer.parseInt(s) + 1 + "");
-            } catch (NumberFormatException e) {
-                return Optional.of(s);
-            }
-        });
-        test("Replace",
-                s -> Optional.of(s.replace("2", "9")));
-        test("Take last digit",
-                s -> Optional.of(s.length() > 0 ?
-                        s.charAt(s.length() - 1) + ""
-                        : s));
-    }
-}
-```
-同 `map()`，`flatMap()` 将提取非空 **Optional** 的内容并将其应用在映射函数。唯一的区别就是 `flatMap()` 不会把结果包装在 **Optional** 中，因为映射函数已经被包装过了。在如上示例中，我们已经在每一个映射函数中显式地完成了包装，但是很显然 `Optional.flatMap()` 是为那些自己已经生成 **Optional** 的函数而设计的。
-
-### Optional 流
-假设你的生成器可能产生 `null` 值，那么当用它来创建流时，你会想到用  **Optional** 包装元素：
-![](https://img-blog.csdnimg.cn/20210701144821407.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-使用这个流时，必须清楚如何解包 **Optional**：
-![](https://img-blog.csdnimg.cn/20210701145701858.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-输出结果：
-![](https://img-blog.csdnimg.cn/20210701145528863.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-由于每种情况都需要定义“空值”的含义，所以通常我们要为每个应用程序采用不同的方法。
-
-## 终端操作
-以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说，终端操作总是我们在流管道中所做的最后一件事。
-
-### 数组
-- `toArray()`：将流转换成适当类型的数组
-- `toArray(generator)`：在特殊情况下，生成自定义类型的数组
-
-假设需复用流产生的随机数:
-![](https://img-blog.csdnimg.cn/20210701150017569.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-这样每次调用 `rands()` 的时候可以重复获取相同的整数流。
-
-### 循环
-- `forEach(Consumer)`常见如 `System.out::println` 作为 **Consumer** 函数。
-- `forEachOrdered(Consumer)`： 保证 `forEach` 按照原始流顺序操作。
-
-第一种形式：无序操作，仅在引入并行流时才有意义。 `parallel()`：可实现多处理器并行操作。实现原理为将流分割为多个（通常数目为 CPU 核心数）并在不同处理器上分别执行操作。因为我们采用的是内部迭代，而不是外部迭代，所以这是可能实现的。
-
-下例引入 `parallel()` 来帮助理解 `forEachOrdered(Consumer)` 的作用和使用场景：
-```java
-// streams/ForEach.java
-import java.util.*;
-import java.util.stream.*;
-import static streams.RandInts.*;
-public class ForEach {
-    static final int SZ = 14;
-    public static void main(String[] args) {
-        rands().limit(SZ)
-                .forEach(n -> System.out.format("%d ", n));
-        System.out.println();
-        rands().limit(SZ)
-                .parallel()
-                .forEach(n -> System.out.format("%d ", n));
-        System.out.println();
-        rands().limit(SZ)
-                .parallel()
-                .forEachOrdered(n -> System.out.format("%d ", n));
-    }
-}
-```
-为了方便测试不同大小的数组，我们抽离出了 `SZ` 变量。结果很有趣：在第一个流中，未使用 `parallel()` ，所以 `rands()` 按照元素迭代出现的顺序显示结果；在第二个流中，引入`parallel()` ，即便流很小，输出的结果顺序也和前面不一样。这是由于多处理器并行操作的原因。多次运行测试，结果均不同。多处理器并行操作带来的非确定性因素造成了这样的结果。
-
-在最后一个流中，同时使用了 `parallel()` 和 `forEachOrdered()` 来强制保持原始流顺序。因此，对非并行流使用 `forEachOrdered()` 是没有任何影响的。
-
-### 集合
-- `collect(Collector)`：使用 **Collector** 收集流元素到结果集合中。
-- `collect(Supplier, BiConsumer, BiConsumer)`：同上，第一个参数 **Supplier** 创建了一个新结果集合，第二个参数 **BiConsumer** 将下一个元素包含到结果中，第三个参数 **BiConsumer** 用于将两个值组合起来。
-
-假设我们现在为保证元素有序，将元素存储在 **TreeSet**。**Collectors** 没有特定的 `toTreeSet()`，但可以通过将集合的构造器引用传递给 `Collectors.toCollection()`，从而构建任意类型的集合。
-
-比如，将一个文件中的单词收集到 **TreeSet**：
-![](https://img-blog.csdnimg.cn/2021060515055616.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-我们也可以在流中生成 **Map**。代码示例：
-
-```java
-// streams/MapCollector.java
-import java.util.*;
-import java.util.stream.*;
-class Pair {
-    public final Character c;
-    public final Integer i;
-    Pair(Character c, Integer i) {
-        this.c = c;
-        this.i = i;
-    }
-    public Character getC() { return c; }
-    public Integer getI() { return i; }
-    @Override
-    public String toString() {
-        return "Pair(" + c + ", " + i + ")";
-    }
-}
-class RandomPair {
-    Random rand = new Random(47);
-    // An infinite iterator of random capital letters:
-    Iterator<Character> capChars = rand.ints(65,91)
-            .mapToObj(i -> (char)i)
-            .iterator();
-    public Stream<Pair> stream() {
-        return rand.ints(100, 1000).distinct()
-                .mapToObj(i -> new Pair(capChars.next(), i));
-    }
-}
-public class MapCollector {
-    public static void main(String[] args) {
-        Map<Integer, Character> map =
-                new RandomPair().stream()
-                        .limit(8)
-                        .collect(
-                                Collectors.toMap(Pair::getI, Pair::getC));
-        System.out.println(map);
-    }
-}
-```
-
-输出结果：
-
-```
-{688=W, 309=C, 293=B, 761=N, 858=N, 668=G, 622=F, 751=N}
-```
-
-**Pair** 只是一个基础的数据对象。**RandomPair** 创建了随机生成的 **Pair** 对象流。在 Java 中，我们不能直接以某种方式组合两个流。所以这里创建了一个整数流，并且使用 `mapToObj()` 将其转化成为 **Pair** 流。 **capChars** 随机生成的大写字母迭代器从流开始，然后 `iterator()` 允许我们在 `stream()` 中使用它。就我所知，这是组合多个流以生成新的对象流的唯一方法。
-
-在这里，我们只使用最简单形式的 `Collectors.toMap()`，这个方法值需要一个可以从流中获取键值对的函数。还有其他重载形式，其中一种形式是在遇到键值冲突时，需要一个函数来处理这种情况。
-
-大多数情况下，`java.util.stream.Collectors` 中预设的 **Collector** 就能满足我们的要求。
-还可以使用第二种形式的 `collect()`。 
-```java
-// streams/SpecialCollector.java
-import java.util.*;
-import java.util.stream.*;
-public class SpecialCollector {
-    public static void main(String[] args) throws Exception {
-        ArrayList<String> words =
-                FileToWords.stream("Cheese.dat")
-                        .collect(ArrayList::new,
-                                ArrayList::add,
-                                ArrayList::addAll);
-        words.stream()
-                .filter(s -> s.equals("cheese"))
-                .forEach(System.out::println);
-    }
-}
-```
-
-输出结果：
-
-```
-cheese
-cheese
-```
-
-在这里， **ArrayList** 的方法已经执行了你所需要的操作，但是似乎更有可能的是，如果你必须使用这种形式的 `collect()`，则必须自己创建特殊的定义。
-
-#### 对List根据一个或多个字段分组
-项目中遇到了需要对list进行分组的场景，根据List中entity的某字段或者多个字段进行分组，形成Map，然后根据map进行相关的业务操作。
-##### 根据一个字段进行分组
-```java
-public class ListGroupBy {<!-- -->
-    public static void main(String[] args) {<!-- -->
-        List<Score> scoreList = new ArrayList<>();
-        scoreList.add(new Score().setStudentId("001").setScoreYear("2018").setScore(100.0));
-        scoreList.add(new Score().setStudentId("001").setScoreYear("2019").setScore(59.5));
-        scoreList.add(new Score().setStudentId("001").setScoreYear("2019").setScore(99.0));
-        scoreList.add(new Score().setStudentId("002").setScoreYear("2018").setScore(99.6));
-        //根据scoreYear字段进行分组
-        Map<String, List<Score>> map = scoreList.stream().collect(
-                Collectors.groupingBy(
-                        score -> score.getScoreYear()
-                ));
-        System.out.println(JSONUtil.toJsonPrettyStr(map));
-    }
-}
-```
-结果：
-
-```java
-{<!-- -->
-    "2019": [
-        {<!-- -->
-            "studentId": "001",
-            "score": 59.5,
-            "scoreYear": "2019"
-        },
-        {<!-- -->
-            "studentId": "001",
-            "score": 99,
-            "scoreYear": "2019"
-        }
-    ],
-    "2018": [
-        {<!-- -->
-            "studentId": "001",
-            "score": 100,
-            "scoreYear": "2018"
-        },
-        {<!-- -->
-            "studentId": "002",
-            "score": 99.6,
-            "scoreYear": "2018"
-        }
-    ]
-}
-```
-##### 根据多个字段进行分组
-将
-
-```java
-//根据scoreYear字段进行分组
-Map<String, List<Score>> map = scoreList.stream().collect(
-    Collectors.groupingBy(
-        score -> score.getScoreYear()
-    ));
-```
- 改为
- 
-
-```java
-//根据scoreYear和studentId字段进行分组
-        Map<String, List<Score>> map = scoreList.stream().collect(
-                Collectors.groupingBy(
-                        score -> score.getScoreYear()+'-'+score.getStudentId()
-                ));
-```
-结果：
-
-```java
-{<!-- -->
-    "2019-001": [
-        {<!-- -->
-            "studentId": "001",
-            "score": 59.5,
-            "scoreYear": "2019"
-        },
-        {<!-- -->
-            "studentId": "001",
-            "score": 99,
-            "scoreYear": "2019"
-        }
-    ],
-    "2018-001": [
-        {<!-- -->
-            "studentId": "001",
-            "score": 100,
-            "scoreYear": "2018"
-        }
-    ],
-    "2018-002": [
-        {<!-- -->
-            "studentId": "002",
-            "score": 99.6,
-            "scoreYear": "2018"
-        }
-    ]
-}
-```
-
-
-
-<!-- Combining All Stream Elements -->
-
-### 组合
-
-- `reduce(BinaryOperator)`：使用 **BinaryOperator** 来组合所有流中的元素。因为流可能为空，其返回值为 **Optional**。
-- `reduce(identity, BinaryOperator)`：功能同上，但是使用 **identity** 作为其组合的初始值。因此如果流为空，**identity** 就是结果。
-- `reduce(identity, BiFunction, BinaryOperator)`：更复杂的使用形式（暂不介绍），这里把它包含在内，因为它可以提高效率。通常，我们可以显式地组合 `map()` 和 `reduce()` 来更简单的表达它。
-
-下面来看下 `reduce` 的代码示例：
-
-```java
-// streams/Reduce.java
-import java.util.*;
-import java.util.stream.*;
-class Frobnitz {
-    int size;
-    Frobnitz(int sz) { size = sz; }
-    @Override
-    public String toString() {
-        return "Frobnitz(" + size + ")";
-    }
-    // Generator:
-    static Random rand = new Random(47);
-    static final int BOUND = 100;
-    static Frobnitz supply() {
-        return new Frobnitz(rand.nextInt(BOUND));
-    }
-}
-public class Reduce {
-    public static void main(String[] args) {
-        Stream.generate(Frobnitz::supply)
-                .limit(10)
-                .peek(System.out::println)
-                .reduce((fr0, fr1) -> fr0.size < 50 ? fr0 : fr1)
-                .ifPresent(System.out::println);
-    }
-}
-```
-
-输出结果：
-
-```
-Frobnitz(58)
-Frobnitz(55)
-Frobnitz(93)
-Frobnitz(61)
-Frobnitz(61)
-Frobnitz(29)
-Frobnitz(68)
-Frobnitz(0)
-Frobnitz(22)
-Frobnitz(7)
-Frobnitz(29)
-```
-
-**Frobnitz** 包含了一个名为 `supply()` 的生成器；因为这个方法对于 `Supplier<Frobnitz>` 是签名兼容的，我们可以将其方法引用传递给 `Stream.generate()`（这种签名兼容性被称作结构一致性）。无“初始值”的 `reduce()`方法返回值是 **Optional** 类型。`Optional.ifPresent()` 只有在结果非空的时候才会调用 `Consumer<Frobnitz>` （`println` 方法可以被调用是因为 **Frobnitz** 可以通过 `toString()` 方法转换成 **String**）。
-
-Lambda 表达式中的第一个参数 `fr0` 是上一次调用 `reduce()` 的结果。而第二个参数 `fr1` 是从流传递过来的值。
-
-`reduce()` 中的 Lambda 表达式使用了三元表达式来获取结果，当其长度小于 50 的时候获取 `fr0` 否则获取序列中的下一个值 `fr1`。当取得第一个长度小于 50 的 `Frobnitz`，只要得到结果就会忽略其他。这是个非常奇怪的约束， 也确实让我们对 `reduce()` 有了更多的了解。
-
-<!-- Matching -->
-### 匹配
-
-- `allMatch(Predicate)` ：如果流的每个元素根据提供的 **Predicate** 都返回 true 时，结果返回为 true。在第一个 false 时，则停止执行计算。
-- `anyMatch(Predicate)`：如果流中的任意一个元素根据提供的 **Predicate** 返回 true 时，结果返回为 true。在第一个 false 是停止执行计算。
-- `noneMatch(Predicate)`：如果流的每个元素根据提供的 **Predicate** 都返回 false 时，结果返回为 true。在第一个 true 时停止执行计算。
-
-我们已经在 `Prime.java` 中看到了 `noneMatch()` 的示例；`allMatch()` 和 `anyMatch()` 的用法基本上是等同的。下面我们来探究一下短路行为。为了消除冗余代码，我们创建了 `show()`。首先我们必须知道如何统一地描述这三个匹配器的操作，然后再将其转换为 **Matcher** 接口。代码示例：
-
-```java
-// streams/Matching.java
-// Demonstrates short-circuiting of *Match() operations
-import java.util.stream.*;
-import java.util.function.*;
-import static streams.RandInts.*;
-
-interface Matcher extends BiPredicate<Stream<Integer>, Predicate<Integer>> {}
-        
-public class Matching {
-    static void show(Matcher match, int val) {
-        System.out.println(
-                match.test(
-                        IntStream.rangeClosed(1, 9)
-                                .boxed()
-                                .peek(n -> System.out.format("%d ", n)),
-                        n -> n < val));
-    }
-    public static void main(String[] args) {
-        show(Stream::allMatch, 10);
-        show(Stream::allMatch, 4);
-        show(Stream::anyMatch, 2);
-        show(Stream::anyMatch, 0);
-        show(Stream::noneMatch, 5);
-        show(Stream::noneMatch, 0);
-    }
-}
-```
-
-输出结果：
-
-```
-1 2 3 4 5 6 7 8 9 true
-1 2 3 4 false
-1 true
-1 2 3 4 5 6 7 8 9 false
-1 false
-1 2 3 4 5 6 7 8 9 true
-```
-
-**BiPredicate** 是一个二元谓词，它只能接受两个参数且只返回 true 或者 false。它的第一个参数是我们要测试的流，第二个参数是一个谓词 **Predicate**。**Matcher** 适用于所有的 **Stream::\*Match** 方法，所以我们可以传递每一个到 `show()` 中。`match.test()` 的调用会被转换成 **Stream::\*Match** 函数的调用。
-
-`show()` 获取两个参数，**Matcher** 匹配器和用于表示谓词测试 **n < val** 中最大值的 **val**。这个方法生成一个1-9之间的整数流。`peek()` 是用于向我们展示测试在短路之前的情况。从输出中可以看到每次都发生了短路。
-
-### 查找
-
-- `findFirst()`：返回第一个流元素的 **Optional**，如果流为空返回 **Optional.empty**。
-- `findAny(`：返回含有任意流元素的 **Optional**，如果流为空返回 **Optional.empty**。
-
-代码示例：
-
-```java
-// streams/SelectElement.java
-import java.util.*;
-import java.util.stream.*;
-import static streams.RandInts.*;
-public class SelectElement {
-    public static void main(String[] args) {
-        System.out.println(rands().findFirst().getAsInt());
-        System.out.println(
-                rands().parallel().findFirst().getAsInt());
-        System.out.println(rands().findAny().getAsInt());
-        System.out.println(
-                rands().parallel().findAny().getAsInt());
-    }
-}
-```
-
-输出结果：
-
-```
-258
-258
-258
-242
-```
-
-`findFirst()` 无论流是否为并行化的，总是会选择流中的第一个元素。对于非并行流，`findAny()`会选择流中的第一个元素（即使从定义上来看是选择任意元素）。在这个例子中，我们使用 `parallel()` 来并行流从而引入 `findAny()` 选择非第一个流元素的可能性。
-
-如果必须选择流中最后一个元素，那就使用 `reduce()`。代码示例：
-
-```java
-// streams/LastElement.java
-import java.util.*;
-import java.util.stream.*;
-public class LastElement {
-    public static void main(String[] args) {
-        OptionalInt last = IntStream.range(10, 20)
-                .reduce((n1, n2) -> n2);
-        System.out.println(last.orElse(-1));
-        // Non-numeric object:
-        Optional<String> lastobj =
-                Stream.of("one", "two", "three")
-                        .reduce((n1, n2) -> n2);
-        System.out.println(
-                lastobj.orElse("Nothing there!"));
-    }
-}
-```
-
-输出结果：
-
-```
-19
-three
-```
-
-`reduce()`  的参数只是用最后一个元素替换了最后两个元素，最终只生成最后一个元素。如果为数字流，你必须使用相近的数字 **Optional** 类型（ numeric optional type），否则使用 **Optional** 类型，就像上例中的 `Optional<String>`。
-
-<!-- Informational -->
-
-### 信息
-
-- `count()`：流中的元素个数。
-- `max(Comparator)`：根据所传入的 **Comparator** 所决定的“最大”元素。
-- `min(Comparator)`：根据所传入的 **Comparator** 所决定的“最小”元素。
-
-**String** 类型有预设的 **Comparator** 实现。代码示例：
-
-```java
-// streams/Informational.java
-import java.util.stream.*;
-import java.util.function.*;
-public class Informational {
-    public static void
-    main(String[] args) throws Exception {
-        System.out.println(
-                FileToWords.stream("Cheese.dat").count());
-        System.out.println(
-                FileToWords.stream("Cheese.dat")
-                        .min(String.CASE_INSENSITIVE_ORDER)
-                        .orElse("NONE"));
-        System.out.println(
-                FileToWords.stream("Cheese.dat")
-                        .max(String.CASE_INSENSITIVE_ORDER)
-                        .orElse("NONE"));
-    }
-}
-```
-
-输出结果：
-
-```
-32
-a
-you
-```
-
-`min()` 和 `max()` 的返回类型为 **Optional**，这需要我们使用 `orElse()`来解包。
-
-<!-- Information for Numeric Streams -->
-### 数字流信息
-
-- `average()` ：求取流元素平均值。
-- `max()` 和 `min()`：数值流操作无需 **Comparator**。
-- `sum()`：对所有流元素进行求和。
-- `summaryStatistics()`：生成可能有用的数据。目前并不太清楚这个方法存在的必要性，因为我们其实可以用更直接的方法获得需要的数据。
-
-```java
-// streams/NumericStreamInfo.java
-import java.util.stream.*;
-import static streams.RandInts.*;
-public class NumericStreamInfo {
-    public static void main(String[] args) {
-        System.out.println(rands().average().getAsDouble());
-        System.out.println(rands().max().getAsInt());
-        System.out.println(rands().min().getAsInt());
-        System.out.println(rands().sum());
-        System.out.println(rands().summaryStatistics());
-    }
-}
-```
-
-输出结果：
-
-```
-507.94
-998
-8
-50794
-IntSummaryStatistics{count=100, sum=50794, min=8, average=507.940000, max=998}
-```
-
-上例操作对于 **LongStream** 和 **DoubleStream** 同样适用。
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\346\227\245\346\234\237\346\227\266\351\227\264API.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\346\227\245\346\234\237\346\227\266\351\227\264\345\244\204\347\220\206\346\234\200\344\275\263\345\256\236\350\267\265.md"
similarity index 100%
rename from "JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\346\227\245\346\234\237\346\227\266\351\227\264API.md"
rename to "JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\346\227\245\346\234\237\346\227\266\351\227\264\345\244\204\347\220\206\346\234\200\344\275\263\345\256\236\350\267\265.md"
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\347\232\204\346\226\260\346\227\245\346\234\237API.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\347\232\204\346\226\260\346\227\245\346\234\237API.md"
deleted file mode 100644
index ac8029e012..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\347\232\204\346\226\260\346\227\245\346\234\237API.md"
+++ /dev/null
@@ -1,212 +0,0 @@
-# 1 为什么需要新的日期和时间库？
-Java开发人员的一个长期烦恼是对普通开发人员的日期和时间用例的支持不足。
-
-例如，现有的类（例如java.util.Date和SimpleDateFormatter）是非线程安全的，从而导致用户潜在的并发问题，这不是一般开发人员在编写日期处理代码时会期望处理的问题。
-一些日期和时间类还表现出相当差的API设计。例如，年份java.util.Date从1900开始，月份从1开始，天从0开始，这不是很直观。
-
-这些问题以及其他一些问题导致第三方日期和时间库（例如Joda-Time）的欣欣向荣。
-
-为了解决这些问题并在JDK内核中提供更好的支持，针对Java SE 8设计了一个新的没有这些问题的日期和时间API。该项目由Joda-Time（Stephen Colebourne）和Oracle的作者在JSR 310下共同领导，出现在Java SE 8软件包中java.time。
-
-
-# 2 核心思想
-
-## 不可变值类
-Java现有格式化程序的严重缺陷之一是它们不是线程安全的。这给开发人员带来了负担，使其需要以线程安全的方式使用它们并在其日常处理日期处理代码的过程中考虑并发问题。新的API通过确保其所有核心类都是不可变的并表示定义明确的值来避免此问题。
-
-## 域驱动
-新的API模型与代表不同的用例类域非常精确Date和Time严密。这与以前的Java库不同，后者在这方面很差。例如，java.util.Date在时间轴上表示一个时刻（一个自UNIX纪元以来的毫秒数的包装器），但如果调用toString()，结果表明它具有时区，从而引起开发人员之间的困惑。
-
-这种对域驱动设计的重视在清晰度和易理解性方面提供了长期利益，但是当从以前的API移植到Java SE 8时，您可能需要考虑应用程序的域日期模型。
-
-## 按时间顺序分隔
-新的API使人们可以使用不同的日历系统来满足世界某些地区（例如日本或泰国）用户的需求，而这些用户不一定遵循ISO-8601。这样做不会给大多数开发人员带来额外负担，他们只需要使用标准的时间顺序即可。
-
-
-# 3 LocalDate、LocalTime、LocalDateTime
-## 3.1 相比 Date 的优势
--  Date 和 SimpleDateFormatter 非线程安全，而 LocalDate 和 LocalTime 和 String 一样，是final类型 - 线程安全且不能被修改。
--  Date 月份从0开始,一月是0，十二月是11。LocalDate 月份和星期都改成了 enum ，不会再用错。
-- Date是一个“万能接口”，它包含日期、时间，还有毫秒数。如果你只需要日期或时间那么有一些数据就没啥用。在新的Java 8中，日期和时间被明确划分为 LocalDate 和 LocalTime，LocalDate无法包含时间，LocalTime无法包含日期。当然，LocalDateTime才能同时包含日期和时间。
-- Date 推算时间(比如往前推几天/ 往后推几天/ 计算某年是否闰年/ 推算某年某月的第一天、最后一天、第一个星期一等等)要结合Calendar要写好多代码，十分恶心！
-
-两个都是本地的，因为它们从观察者的角度表示日期和时间，例如桌子上的日历或墙上的时钟。
-
-还有一种称为复合类`LocalDateTime`，这是一个LocalDate和LocalTime的配对。 
-![](https://img-blog.csdnimg.cn/20201009175215947.png#pic_center)
-
-时区将不同观察者的上下文区分开来，在这里放在一边；不需要上下文时，应使用这些本地类。这些类甚至可以用于表示具有一致时区的分布式系统上的时间。
-
-
-## 常用 API
-### now()
-获取在默认的时区系统时钟内的当前日期。该方法将查询默认时区内的系统时钟，以获取当前日期。
-使用该方法将防止使用测试用的备用时钟，因为时钟是硬编码的。
-![](https://img-blog.csdnimg.cn/20201102142033421.png#pic_center)
-
-方便的加减年月日，而不必亲自计算！
-![](https://img-blog.csdnimg.cn/20201102142431847.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-### plusMonths
-返回此副本LocalDate添加了几个月的指定数目。
-此方法将分三步指定金额的几个月字段：
--  将输入的月数加到month-of-year字段
-- 校验结果日期是否无效
-- 调整 day-of-month ，如果有必要的最后有效日期
-
-例如，2007-03-31加一个月会导致无效日期2007年4月31日。并非返回一个无效结果，而是 2007-04-30才是最后有效日期。调用实例的不可变性不会被该方法影响。
-![](https://img-blog.csdnimg.cn/20201102142220122.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-# 4 创建对象
-## 工厂方法
-新API中的所有核心类都是通过熟练的工厂方法构造。
-- 当通过其构成域构造值时，称为工厂of
-- 从其他类型转换时，工厂称为from
-- 也有将字符串作为参数的解析方法。
-![](https://img-blog.csdnimg.cn/20201009212940598.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFF00,t_70#pic_center)
-## getter约定
-- 为了从Java SE 8类获取值，使用了标准的Java getter约定，如下：
-![](https://img-blog.csdnimg.cn/2020100921304067.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFF00,t_70#pic_centerr)
-## 更改对象值
-也可以更改对象值以执行计算。因为新API中所有核心类都是不可变的，所以将调用这些方法with并返回新对象，而不是使用setter。也有基于不同字段的计算方法。 
-![](https://img-blog.csdnimg.cn/20201009213918638.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_25,color_FFFF00,t_70#pic_center)
-## 调整器
-新的API还具有调整器的概念—一块代码块，可用于包装通用处理逻辑。可以编写一个WithAdjuster，用于设置一个或多个字段，也可编写一个PlusAdjuster用于添加或减去某些字段。值类还可以充当调节器，在这种情况下，它们将更新它们表示的字段的值。内置调节器由新的API定义，但是如果您有想要重用的特定业务逻辑，则可以编写自己的调节器。
-
-```java
-import static java.time.temporal.TemporalAdjusters.*;
-
-LocalDateTime timePoint = ...
-foo = timePoint.with(lastDayOfMonth());
-bar = timePoint.with(previousOrSame(ChronoUnit.WEDNESDAY));
-
-// 使用值类作为调整器
-timePoint.with(LocalTime.now()); 
-```
-
-# 5 截断
-新的API通过提供表示日期，时间和带时间的日期的类型来支持不同的精确度时间点，但是显然，精确度的概念比此精确度更高。
-
-该truncatedTo方法存在支持这种使用情况下，它可以让你的值截断到字段，如下
-
-```java
-LocalTime truncatedTime = time.truncatedTo(ChronoUnit.SECONDS);
-```
-
-# 6 时区
-我们之前查看的本地类抽象了时区引入的复杂性。时区是一组规则，对应于标准时间相同的区域。大约有40个。时区由它们相对于协调世界时（UTC，Coordinated Universal Time）的偏移量定义。它们大致同步移动，但有一定差异。
-
-时区可用两个标识符来表示：缩写，例如“ PLT”，更长的例如“ Asia / Karachi”。在设计应用程序时，应考虑哪种情况适合使用时区，什么时候需要偏移量。 
-
-- `ZoneId`是区域的标识符。每个ZoneId规则都对应一些规则，这些规则定义了该位置的时区。在设计软件时，如果考虑使用诸如“ PLT”或“ Asia / Karachi”之类的字符串，则应改用该域类。一个示例用例是存储用户对其时区的偏好。
-![](https://img-blog.csdnimg.cn/20201009215908334.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFF00,t_70#pic_centerr)
-
-- `ZoneOffset`是格林威治/ UTC与时区之间的差异的时间段。可在特定的`ZoneId`，在特定时间被解析，如清单7所示。  
-
-```java
-ZoneOffset offset = ZoneOffset.of("+2:00");
-```
-
-# 7 时区类 
-`ZonedDateTime`是具有完全限定时区的日期和时间。这样可以解决任何时间点的偏移。
-最佳实践：若要表示日期和时间而不依赖特定服务器的上下文，则应使用`ZonedDateTime`。  
-
-```java
-ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Paris]");
-```
-
-`OffsetDateTime`是具有已解决偏移量的日期和时间。这对于将数据序列化到数据库中很有用，如果服务器在不同时区，则还应该用作记录时间戳的序列化格式。
-
-`OffsetTime` 是具有确定的偏移量的时间，如下：
-
-
-```java
-OffsetTime time = OffsetTime.now();
-// changes offset, while keeping the same point on the timeline
-OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(
-    offset);
-// changes the offset, and updates the point on the timeline
-OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(
-    offset);
-// Can also create new object with altered fields as before
-changeTimeWithNewOffset
- .withHour(3)
- .plusSeconds(2);
-OffsetTime time = OffsetTime.now();
-// changes offset, while keeping the same point on the timeline
-OffsetTime sameTimeDifferentOffset = time.withOffsetSameInstant(
-    offset);
-// changes the offset, and updates the point on the timeline
-OffsetTime changeTimeWithNewOffset = time.withOffsetSameLocal(
-    offset);
-// Can also create new object with altered fields as before
-changeTimeWithNewOffset
- .withHour(3)
- .plusSeconds(2);
-```
-
-Java中已有一个时区类，java.util.TimeZone但Java SE 8并没有使用它，因为所有JSR 310类都是不可变的并且时区是可变的。
-
-# 8 时间段（period）
-Period代表诸如“ 3个月零一天”的值，它是时间线上的距离。这与到目前为止我们讨论过的其他类形成了鲜明的对比，它们是时间轴上的重点。
-
-```java
-// 3 年, 2 月, 1 天
-Period period = Period.of(3, 2, 1);
-
-// 使用 period 修改日期值
-LocalDate newDate = oldDate.plus(period);
-ZonedDateTime newDateTime = oldDateTime.minus(period);
-// Components of a Period are represented by ChronoUnit values
-assertEquals(1, period.get(ChronoUnit.DAYS)); 
-// 3 years, 2 months, 1 day
-Period period = Period.of(3, 2, 1);
-
-// You can modify the values of dates using periods
-LocalDate newDate = oldDate.plus(period);
-ZonedDateTime newDateTime = oldDateTime.minus(period);
-// Components of a Period are represented by ChronoUnit values
-assertEquals(1, period.get(ChronoUnit.DAYS)); 
-```
-
-# 9 持续时间（Duration）
-Duration是时间线上按时间度量的距离，它实现了与相似的目的Period，但精度不同：
-
-```java
-// 3 s 和 5 ns 的 Duration 
-Duration duration = Duration.ofSeconds(3, 5);
-Duration oneDay = Duration.between(today, yesterday);
-// A duration of 3 seconds and 5 nanoseconds
-Duration duration = Duration.ofSeconds(3, 5);
-Duration oneDay = Duration.between(today, yesterday);
-```
-
-可以对Duration实例执行常规的加，减和“ with”运算，还可以使用修改日期或时间的值Duration。
-
-# 10 年表
-为了满足使用非ISO日历系统的开发人员的需求，Java SE 8引入了Chronology，代表日历系统，并充当日历系统中时间点的工厂。也有一些接口对应于核心时间点类，但通过
-
-
-```java
-Chronology:
-ChronoLocalDate
-ChronoLocalDateTime
-ChronoZonedDateTime
-Chronology:
-ChronoLocalDate
-ChronoLocalDateTime
-ChronoZonedDateTime
-```
-
-这些类仅适用于正在开发高度国际化的应用程序且需要考虑本地日历系统的开发人员，没有这些要求的开发人员不应使用它们。有些日历系统甚至没有一个月或一周的概念，因此需要通过非常通用的字段API进行计算。
-
-# 11 其余的API
-Java SE 8还具有一些其他常见用例的类。有一个MonthDay类，其中包含一对Month和Day，对于表示生日非常有用。该YearMonth类涵盖了信用卡开始日期和到期日期的用例以及人们没有指定日期的场景。
-
-Java SE 8中的JDBC将支持这些新类型，但不会更改公共JDBC API。现有的泛型setObject和getObject方法就足够了。
-
-这些类型可以映射到特定于供应商的数据库类型或ANSI SQL类型。
-![](https://img-blog.csdnimg.cn/20201009230518144.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-# 12 总结
-
-Java SE 8在java.time中附带一个新的日期和时间API，为开发人员提供了大大改善的安全性和功能。新的API很好地建模了该领域，并提供了用于对各种开发人员用例进行建模的大量类。
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\351\233\206\345\220\210\346\272\220\347\240\201\350\247\243\346\236\220-Hashtable\346\272\220\347\240\201\345\211\226\346\236\220.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\351\233\206\345\220\210\346\272\220\347\240\201\350\247\243\346\236\220-Hashtable\346\272\220\347\240\201\345\211\226\346\236\220.md"
index a4a829979c..47eb49416a 100644
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\351\233\206\345\220\210\346\272\220\347\240\201\350\247\243\346\236\220-Hashtable\346\272\220\347\240\201\345\211\226\346\236\220.md"
+++ "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\351\233\206\345\220\210\346\272\220\347\240\201\350\247\243\346\236\220-Hashtable\346\272\220\347\240\201\345\211\226\346\236\220.md"
@@ -16,7 +16,7 @@ NOTE: This class is obsolete.  New implementations should
 ```
 这个类的方法如下（全是抽象方法）：
 
-```java
+```
 public abstract  
 class Dictionary<K,V> {  
     
@@ -33,7 +33,7 @@ class Dictionary<K,V> {
 ```
 ##成员变量
 
-```java
+```
    //存储键值对的桶数组  
    private transient Entry<K,V>[] table;
 
@@ -52,7 +52,7 @@ class Dictionary<K,V> {
 成员变量跟HashMap基本类似，但是HashMap更加规范，HashMap内部还定义了一些常量，比如默认的负载因子，默认的容量，最大容量等等。
 ##构造方法
 
-```java
+```
 //可指定初始容量和加载因子  
 public Hashtable(int initialCapacity, float loadFactor) {
         if (initialCapacity < 0)  
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\344\270\216\347\272\277\347\250\213.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\344\270\216\347\272\277\347\250\213.md"
index 44310f6ea2..eb873095d4 100644
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\344\270\216\347\272\277\347\250\213.md"
+++ "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\344\270\216\347\272\277\347\250\213.md"
@@ -1,16 +1,16 @@
-并发不一定要依赖多线程（如PHP的多进程并发），但在Java中谈论并发，大多数都与线程脱不开关系。
+并发不一定要依赖多线程（如PHP的多进程并发），但在Java中谈论并发，大多数都与线程脱不开关系
 # 线程的实现
 线程是CPU调度的基本单位。
 
 Thread类与大部分的Java API有显著的差别，它的所有关键方法都是声明为Native的。
 意味着这个方法没有使用或无法使用平台无关的手段来实现。
-# 内核线程(Kernel-Level Thread，KLT)
+# 内核线程(Kernel-Lever Thread,KLT)
 直接由操作系统内核(Kermel,下称内核)支持的线程
 由内核来完成线程切换，内核通过操纵调度器(Sheduler) 对线程进行调度，并负责将线程的任务映射到各个处理器上。
 每个内核线程可以视为内核的一个分身,这样OS就有能力同时处理多件事情，支持多线程的内核就叫做多线程内核(Multi-Threads Kernel )。
 
-程序一般不会直接去使用KLT，而使用KLT的一种高级接口即轻量级进程(Light Weight Process，LWP)，即我们通常意义上所讲的线程，由于每个LWP都由一个KLT支持，因此只有先支持KLT，才能有LWP。这1：1的关系称为`一对一的线程模型`。
-![KLT与LWP之间1：1的关系](https://img-blog.csdnimg.cn/img_convert/0e73bbc9abbaf006ede71ad793a18f7e.png)
+程序一般不会直接去使用KLT，而使用KLT的一种高级接口即轻量级进程(Light Weight Process,LWP)，即我们通常意义上所讲的线程，由于每个LWP都由一个KLT支持，因此只有先支持KLT，才能有LWP。这1：1的关系称为`一对一的线程模型`。
+![KLT与LWP之间1：1的关系](https://upload-images.jianshu.io/upload_images/4685968-497d1cfc86f6b084.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 局限性
 由于是基于KLT实现的，所以各种线程操作，如创建、析构及同步，都需要进行系统调用。而系统调用的代价相对较高，需要在用户态和内核态中来回切换
 其次，每个LWP都需要有一个KLT的支持，因此LWP要消耗一定的内核资源（如KLT的栈空间），因此一个系统支持LWP的数量是有限的
@@ -19,26 +19,23 @@ Thread类与大部分的Java API有显著的差别，它的所有关键方法都
 # 用户线程混合轻量级进程
 
 # Java线程的实现
-用户线程还是完全建立在用户空间中，因此用户线程的创建、切换、析构等操作依然廉价，并且可以支持大规模的用户线程并发。
-
-os提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁，这样可以使用内核提供的线程调度功能及处理器映射，并且用户线程的系统调用要通过轻量级线程来完成，大大降低了整个进程被完全阻塞的风险。
+用户线程还是完全建立在用户空间中，因此用户线程的创建、切换、析构等操作依然廉价，并
+且可以支持大规模的用户线程并发
+操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁，这样可以使用内核提供的线程调度功能及处理器映射，并且用户线程的系统调用要通过轻量级线程来完成，大大降低了整个进程被完全阻塞的风险。
 在这种混合模式中，用户线程与轻量级进程的数量比是不定的，即为N :M 的关系
-![用户线程与轮量级进程之间N :M 的关系](https://img-blog.csdnimg.cn/img_convert/357820c4bfd3f180b02e36d2fed85e13.png)
-许多Unix 系列的os，如Solaris、HP-UX 等都提供了N: M 的线程模型实现。
-# Java 线程
+![用户线程与轮量级进程之间N :M 的关系](https://upload-images.jianshu.io/upload_images/4685968-b64e81020899c37e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+许多UN1X 系列的操作系统，如Solaris、HP-UX 等都提供了N: M 的线程模型实现。
+#.Java 线程
 JDK 1.2 之前是基于称为“绿色线程”(Green-Threads )的用户线程实现
 在JDK 1.2 中替换为基于操作系统原生线程模型来实现
 因此，在目前的JDK 版本中，操作系统支持怎样的线程模型，在很大程度上决定了Java 虚拟机的线程是怎样映射的，这点在不同的平台上没有办法达成一致，虚拟机规范中也并未限定Java 线程需要使用哪种线程模型来实现。
 线程模型只对线程的并发规模和操作成本产生影响，对Java 程序的编码和运行过程来说，这些差异都是透明的。
-对于Sun JDK 来说，它的Windows 版与Linux版都是使用一对一的线程模型实现的，一条Java线程就映射到一条轻量级进程之中，因为Windows 和Linux系统提供的线程模型就是一对一的。
+对于Siun JDK 来说，它的Windows 版与Linux版都是使用一对一的线程模型实现的，一条Java线程就映射到一条轻量级进程之中，因为Windows 和Linux系统提供的线程模型就是一对一的
 而在Solaris 平台中，由于操作系统的线程特性可以同时支持一对一(通过Bound
-Threads或Alternate Libthread实现)及多对多( 通过LWP/Thread Based Synchronization实现) 的线程模型，因此在Solaris 版的JDK 中也对应提供了两个平台专有的虚拟机参数：
-```java
--XX:+UseLWPSynchronization （默认值) 
--XX:+UseBoyndThreads
-```
-
-明确指定虚拟机使用哪种线程模型。
+Threaids或Alternate Libthread实现)及多对多( 通过LWP/Thread Based Synchronization
+实现) 的线程模型，因此在Solaris 版的JDK 中也对应提供了两个平台专有的虚拟桃参数:
+-XX:+UseLWPSynchronization （默认值) 和-XX:+UseBoyndThreads 来明确指定虚拟
+机使用哪种线程模型。
 # Java线程调度
 - 线程调度
 系统为线程分配处理器使用权的过程，主要调度方式有两种
@@ -48,15 +45,17 @@ Threads或Alternate Libthread实现)及多对多( 通过LWP/Thread Based Synchro
 使用协同式调度的多线程系统，线程执行时间由线程本身控制，线程把自己工作执行完后，要主动通知系统切换到另外一个线程上。
 协同式多线程
 - 最大好处
-实现简单，而且由于线程要把自己的事情干完后才进行线程切换,切换操作对线程自己是可知的，所以没有什么线程同步的问题
+实现简单，而且由于线程要把自己的事情干完后才进行线程切换,切换操作对线程白己是可知的，所以没有什么线程同步的问题
 - 坏处也很明显
  线程执行时间不可控制
 
-使用抢占式调度的多线程系统，那么每个线程将由系统来分配执行时间，线程的切换不由线程本身决定，在这种实现线程调度的方式下，线程执行时间系统可控的。Java使用的线程调度方式就是抢占式调度。
+使用抢占式调度的多线程系统，那么每个线程将由系统来分配执行时间，线程的切换不由线程本身决定，在这种实现线程调度的方式下，线程执行时间系统可控的
+Java使用的线程调度方式就是抢占式调度
 
-虽然Java线程调度是系统自动完成的，但是我们还是可以“建议”系统给某些线程多分配一点执行时间，可以通过设置线程优先级来完成。Java 语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY 至Thread.MAX_PRIORITY )，在两个线程同时处于Ready 状态时，优先级越高的线程越容易被系统选择执行。
+虽然Java线程调度是系统自动完成的，但是我们还是可“建议”系统给某些线程多分配一点执行时间，可以通过设置线程优先级来完成。Java 语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY 至Thread.MAX_PRIORITY )，在两个线程同时处于Ready 状态时，优先级越高的线程越容易被系统选择执行。
 
-Java 的线程是通过映射到系统的原生线程上来实现的，所以线程调度最终还是取决于OS，虽然现在很多OS都提供线程优先级的概念，但是并不见得能与Java线程的优先级对应，如Solaris中有2147483648 (2^32 )种优先级，但Windows中就只有7种，比Java 线程优先级多的系统还好说，中间留下一点空位就可以了,但比Java线程优先级少的系统，就不得不出现几个优先级相同的情况了
+Java 的线程是通过映射到系统的原生线程上来实现的，所以线程调度最终还是取决于OS，虽然现在很多OS都提供线程优先级的概念，但是并不见得能与Java线程的优先级对应，如Solaris中有2147483648 (232 )种优先级，但Windows中就只有7种，比Java 线程优先级多的系统还好说，中间留下一点空位就可以了,但比Java线程优先级少的系统，就不得不出现几个优先级相同的情况了
 
 不仅仅是说在一些平台上不同的优先级实际会变得相同这一点，还有其他情况让我们不能太依赖优先级:优先级可能会被系统自行改变。
-例如，在Windows 系统中存在一个称为“优先级推进器”(Priority Boosting，当然它可以被关闭掉) 的功能，它的大致作用就是当系统发现一个线程执行得特别“勤奋努力”的话，可能会越过线程优先级去为它分配执行时间。因此，我们不能在程序中通过优先级完全准确地判断一组状态都为Ready 的线程将会先执行哪一个。
\ No newline at end of file
+例如，在Windows 系统中存在一个称为“优先级推进器”(Priority Boosting，当然它可以被
+关闭掉) 的功能，它的大致作用就是当系统发现一个线程执行得特别“勤奋努力”的话，可能会越过线程优先级去为它分配执行时间。因此，我们不能在程序中通过优先级完全准确地判断一组状态都为Ready 的线程将会先执行哪一个
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217jdk\345\222\214cglib.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217jdk\345\222\214cglib.md"
new file mode 100644
index 0000000000..a1942194e9
--- /dev/null
+++ "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217jdk\345\222\214cglib.md"
@@ -0,0 +1,225 @@
+
+# JDK动态代理
+## 实例
+jdk中实现**InvocationHandler**接口的类就是个**拦截器类**，还使用了些反射的相关概念。
+假如写了个请求到action，经过拦截器，然后才会到action。
+
+- **拦截器的作用**
+控制目标对象的目标方法的执行。拦截器就像一个过滤网，一层层的过滤，只要满足一定条件，才能继续向后执行。
+
+- 拦截器的具体操作步骤：
+1.引入类：目标类和一些扩展方法相关的类。
+2.赋值：调用构造函数给相关对象赋值
+3.合并逻辑处理：在invoke方法中把所有的逻辑结合在一起。最终决定目标方法是否被调用。
+
+下面看具体的代码实例：
+```java
+目标接口类：
+package com.sss.designPattern.proxy.dynamicProxy.jdkDynamicProxy;  
+  
+/** 
+ * 目标接口： 
+ * 包含目标方法的声明 
+ */  
+public interface TargetInterface {  
+    /** 
+     * 目标方法 
+     */  
+    void business();  
+}  
+
+目标类：
+package com.sss.designPattern.proxy.dynamicProxy.jdkDynamicProxy;  
+  /** 
+ * 被代理的类 
+ * 目标对象类 
+ * 实现目标接口. 
+ * 继而实现目标方法。 
+ */  
+public class TargetObject implements TargetInterface {  
+  
+    /** 
+     * 目标方法(即目标操作) 
+     */  
+    @Override  
+    public void business() {  
+        System.out.println("business");  
+    }  
+  
+}  
+
+拦截器：
+package com.sss.designPattern.proxy.dynamicProxy.jdkDynamicProxy;  
+  
+import java.lang.reflect.InvocationHandler;  
+import java.lang.reflect.Method;  
+  
+/** 
+ * 动态代理-拦截器 
+ */  
+public class MyInterceptor implements InvocationHandler {  
+    private Object target;//目标类  
+  
+    public MyInterceptor(Object target) {  
+        this.target = target;  
+    }  
+  
+    /** 
+     * args 目标方法的参数 
+     * method 目标方法 
+     */  
+    @Override  
+    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
+        System.out.println("aaaaa");//切面方法a();  
+        //。。。  
+        method.invoke(this.target, args);//调用目标类的目标方法  
+        //。。。  
+        System.out.println("bbbbb");//切面方法f();  
+        return null;  
+    }  
+}  
+
+具体通过调用代理对象，来调用目标对象的目标方法的具体测试：
+[java] view plain copy
+package com.sss.designPattern.proxy.dynamicProxy.jdkDynamicProxy;  
+  
+import java.lang.reflect.Proxy;  
+  
+public class MainTest {  
+    public static void main(String[] args) {  
+        //目标对象  
+        TargetObject target = new TargetObject();  
+        //拦截器  
+        MyInterceptor myInterceptor = new MyInterceptor(target);  
+  
+        /* 
+         *  Proxy.newProxyInstance参数： 
+         *  1、目标类的类加载器 
+         *  2、目标类的所有的接口 
+         *  3、拦截器 
+         */  
+        //代理对象，调用系统方法自动生成  
+        TargetInterface proxyObj = (TargetInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), myInterceptor);  
+        proxyObj.business();  
+    }  
+}  
+```
+看完代码实例，要**对这个动态代理进一步理解，要考虑到以下的问题。
+1、代理对象是由谁产生的？**
+JVM，不像上次的静态代理，我们自己得new个代理对象出来。
+**2、代理对象实现了什么接口？**
+实现的接口是目标对象实现的接口。
+同静态代理中代理对象实现的接口。那个继承关系图还是相同的。
+代理对象和目标对象都实现一个共同的接口。就是这个接口。
+所以Proxy.newProxyInstance()方法返回的类型就是这个接口类型。
+**3、代理对象的方法体是什么？**
+代理对象的方法体中的内容就是拦截器中invoke方法中的内容。所有代理对象的处理逻辑，控制是否执行目标对象的目标方法。都是在这个方法里面处理的。
+**4、拦截器中的invoke方法中的method参数是在什么时候赋值的？**
+在客户端，代理对象调用目标方法的时候，此实例中为：proxyObj.business()；实际上进入的是拦截器中的invoke方法，这个时候
+，拦截器中的invoke方法中的method参数会被赋值。
+
+**最后，为啥这个方式叫做jdk动态代理呢？**
+因为这个动态代理对象是用jdk的相关代码生成的，所以这个叫**jdk动态代理**。
+后面的cglib动态代理，就是因为要用到cglib的jar包，所以才叫**cglib动态代理**。
+
+了解了一个，那么另一个也就差不多啦。就继续往下看吧。
+
+为什么要使用这个**cglib来实现这个动态代理**呢？因为spring框架要用。
+
+具体的代码实现如下：
+```java
+目标对象类：
+package com.sss.designPattern.proxy.dynamicProxy.cglbDynamicProxy;  
+  
+/** 
+ * 被代理的类 
+ * 目标对象类 
+ */  
+public class TargetObject {  
+  
+    /** 
+     * 目标方法(即目标操作) 
+     */  
+    public void business() {  
+        System.out.println("business");  
+    }  
+  
+}  
+
+拦截器类：
+package com.sss.designPattern.proxy.dynamicProxy.cglbDynamicProxy;  
+  
+import net.sf.cglib.proxy.Enhancer;  
+import net.sf.cglib.proxy.MethodInterceptor;  
+import net.sf.cglib.proxy.MethodProxy;  
+  
+import java.lang.reflect.Method;  
+  
+/** 
+ * 动态代理-拦截器 
+ */  
+public class MyInterceptor implements MethodInterceptor {  
+    private Object target;//目标类  
+  
+    public MyInterceptor(Object target) {  
+        this.target = target;  
+    }  
+  
+    /** 
+     * 返回代理对象 
+     * 具体实现，暂时先不追究。 
+     */  
+    public Object createProxy() {  
+        Enhancer enhancer = new Enhancer();  
+        enhancer.setCallback(this);//回调函数  拦截器  
+        //设置代理对象的父类,可以看到代理对象是目标对象的子类。所以这个接口类就可以省略了。  
+        enhancer.setSuperclass(this.target.getClass());  
+        return enhancer.create();  
+    }  
+  
+    /** 
+     * args 目标方法的参数 
+     * method 目标方法 
+     */  
+    @Override  
+    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {  
+        System.out.println("aaaaa");//切面方法a();  
+        //。。。  
+        method.invoke(this.target, objects);//调用目标类的目标方法  
+        //。。。  
+        System.out.println("bbbbb");//切面方法f();  
+        return null;  
+    }  
+}  
+
+测试类：
+[java] view plain copy
+package com.sss.designPattern.proxy.dynamicProxy.cglbDynamicProxy;  
+  
+public class MainTest {  
+    public static void main(String[] args) {  
+        //目标对象  
+        TargetObject target = new TargetObject();  
+        //拦截器  
+        MyInterceptor myInterceptor = new MyInterceptor(target);  
+        //代理对象，调用cglib系统方法自动生成  
+        //注意：代理类是目标类的子类。  
+        TargetObject proxyObj = (TargetObject) myInterceptor.createProxy();  
+        proxyObj.business();  
+    }  
+}  
+
+区别：
+首先从文件数上来说，cglib比jdk实现的少了个接口类。因为cglib返回的代理对象是目标对象的子类。而jdk产生的代理对象和目标对象都实现了一个公共接口。
+
+
+```
+动态代理分为两种：  
+   *  jdk的动态代理  
+      *  代理对象和目标对象实现了共同的接口  
+      *  拦截器必须实现InvocationHanlder接口  
+  
+   *  cglib的动态代理  
+      *  代理对象是目标对象的子类  
+      *  拦截器必须实现MethodInterceptor接口  
+      *  hibernate中session.load采用的是cglib实现的  
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217\344\271\213JDK\345\222\214Cglib.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217\344\271\213JDK\345\222\214Cglib.md"
deleted file mode 100644
index 7b266e94e1..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\212\250\346\200\201\344\273\243\347\220\206\346\250\241\345\274\217\344\271\213JDK\345\222\214Cglib.md"
+++ /dev/null
@@ -1,273 +0,0 @@
-# 动态代理 V.S 静态代理
-- Proxy类的代码被固定下来，不会因为业务的逐渐庞大而庞大
-- 可以实现AOP编程，这是静态代理无法实现的
-- 解耦，如果用在web业务下，可以实现数据层和业务层的分离
-- 动态代理的优势就是实现无侵入式的代码扩展。
-   
-静态代理这个模式本身有个大问题，若类方法数量越来越多的时候，代理类的代码量十分庞大的。所以引入动态代理
-
-# 动态代理
-Java中动态代理的实现的关键：
-- Proxy
-- InvocationHandler
-
-### InvocationHandler#invoke
-- method
-调用的方法，即需要执行的方法
-- args
-方法的参数
-- proxy
-代理类的实例
-![](https://img-blog.csdnimg.cn/20210610160350321.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# JDK动态代理
-JDK动态代理模式里有个拦截器，在JDK中，只要实现了**InvocationHandler**接口的类就是一个**拦截器类**。
-
-假如写了个请求到action，经过拦截器，然后才会到action，执行后续操作。
-拦截器就像一个过滤网，一层层过滤，只有满足一定条件，才能继续向后执行。
-**拦截器的作用**：控制目标对象的目标方法的执行。
-
-拦截器的具体操作步骤：
-1. 引入类
-目标类和一些扩展方法相关的类
-2. 赋值
-调用构造器，给相关对象赋值
-3. 合并逻辑处理
-在invoke方法中把所有的逻辑结合在一起。最终决定目标方法是否被调用
-
-## 示例
-![](https://img-blog.csdnimg.cn/20210610164051885.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210610164339779.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210610165337886.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210610171204750.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-思考如下问题：
-#### 代理对象由谁产生
-JVM，不像静态代理，我们得自己new个代理对象。
-#### 代理对象实现了什么接口
-实现的接口是目标对象实现的接口。
-同静态代理中代理对象实现的接口。那个继承关系图还是相同的。
-代理对象和目标对象都实现一个共同的接口。就是这个接口。
-所以Proxy.newProxyInstance()方法返回的类型就是这个接口类型。
-#### 代理对象的方法体是什么
-代理对象的方法体中的内容就是拦截器中invoke方法中的内容。
-
-所有代理对象的处理逻辑，控制是否执行目标对象的目标方法。都是在这个方法里面处理的。
-
-#### 拦截器中的invoke方法中的method参数是在什么时候赋值的
-在客户端，代理对象调用目标方法的时候，此实例中为：
-```java
-proxyObj.business()；
-```
-
-实际上进入的是拦截器中的invoke方法，这时拦截器中的invoke方法中的method参数会被赋值。
-
-#### 为啥这叫JDK动态代理
-因为该动态代理对象是用JDK相关代码生成。
-
-很多同学对动态代理迷糊，在于`proxyObj.business();`理解错了，至少是被表面所迷惑，没有发现这个proxyObj和Proxy之间的联系，一度纠结最后调用的这个`business()`是怎么和`invoke()`联系上的，而invoke又怎么知道business存在的。
-
-其实上面的true和class $Proxy0就能解决很多的疑问，再加上下面将要说的$Proxy0的源码，完全可以解决动态代理的疑惑了。 
-
-我们并没有显式调用invoke()，但是这个方法确实执行了。下面分析： 
-
-从Client中的代码看，可以从newProxyInstance这个方法作为突破口，我们先来看一下Proxy类中newProxyInstance方法的源代码： 
-```java
-public static Object newProxyInstance(ClassLoader loader,
-                                      Class<?>[] interfaces,
-                                      InvocationHandler h) {
-    final Class<?>[] intfs = interfaces.clone();
-    final SecurityManager sm = System.getSecurityManager();
-    if (sm != null) {
-        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
-    }
-
-    /*
-     * 查找或生成指定的代理类
-     * 创建代理类$Proxy0
-     * $Proxy0类实现了interfaces的接口,并继承了Proxy类
-     */
-    Class<?> cl = getProxyClass0(loader, intfs);
-
-    /*
-     * 使用指定的调用处理程序调用其构造器
-     */
-    try {
-        if (sm != null) {
-            checkNewProxyPermission(Reflection.getCallerClass(), cl);
-        }
-        // 形参为InvocationHandler类型的构造器
-        final Constructor<?> cons = cl.getConstructor(constructorParams);
-        final InvocationHandler ih = h;
-        if (!Modifier.isPublic(cl.getModifiers())) {
-            AccessController.doPrivileged(new PrivilegedAction<Void>() {
-                public Void run() {
-                    cons.setAccessible(true);
-                    return null;
-                }
-            });
-        }
-        return cons.newInstance(new Object[]{h});
-    } ...
-}
-```
-
-Proxy.newProxyInstance做了如下事： 
-- 根据参数loader和interfaces调用方法 getProxyClass(loader, interfaces)创建代理类`$Proxy0`。`$Proxy0`类 实现了interfaces的接口，并继承了Proxy类
-- 实例化`$Proxy0`，并在构造器把DynamicSubject传过去，接着`$Proxy0`调用父类Proxy的构造器，为h赋值
-
-$Proxy0的源码：
-```java
-package com.sun.proxy;
-
-public final class $Proxy0 extends Proxy implements TargetInterface {
-    private static Method m1;
-    private static Method m3;
-    private static Method m2;
-    private static Method m0;
-
-    public $Proxy0(InvocationHandler var1) throws  {
-        super(var1);
-    }
-
-    public final boolean equals(Object var1) throws  {
-        try {
-            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
-        }...
-    }
-
-    public final void business() throws  {
-        try {
-            super.h.invoke(this, m3, (Object[])null);
-        }...
-    }
-
-    public final String toString() throws  {
-        try {
-            return (String)super.h.invoke(this, m2, (Object[])null);
-        }...
-    }
-
-    public final int hashCode() throws  {
-        try {
-            return (Integer)super.h.invoke(this, m0, (Object[])null);
-        }...
-    }
-
-    static {
-        try {
-            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
-            m3 = Class.forName("com.javaedge.design.pattern.structural.proxy.dynamicproxy.jdkdynamicproxy.TargetInterface").getMethod("business");
-            m2 = Class.forName("java.lang.Object").getMethod("toString");
-            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
-        }...
-    }
-}
-
-```
-接着把得到的`$Proxy0`实例强转成TargetInterface，并将引用赋给TargetInterface。当执行proxyObj.business()，就调用了`$Proxy0`类中的business()方法，进而调用父类Proxy中的h的invoke()方法。即`InvocationHandler.invoke()`。 
-
-Proxy#getProxyClass返回的是Proxy的Class类，而非“被代理类的Class类”！
-![](https://img-blog.csdnimg.cn/20210610194616555.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# cglib动态代理
-就是因为要用到cglib的jar包，所以叫**cglib动态代理**。
-
-为什么要使用这个**cglib来实现这个动态代理**呢？因为spring框架要用。
-
-具体的代码实现如下：
-```java
-目标对象类：
-package com.sss.designPattern.proxy.dynamicProxy.cglbDynamicProxy;  
-  
-/** 
- * 被代理的类 
- * 目标对象类 
- */  
-public class TargetObject {  
-  
-    /** 
-     * 目标方法(即目标操作) 
-     */  
-    public void business() {  
-        System.out.println("business");  
-    }  
-  
-}  
-
-拦截器类：
-package com.sss.designPattern.proxy.dynamicProxy.cglbDynamicProxy;  
-  
-import net.sf.cglib.proxy.Enhancer;  
-import net.sf.cglib.proxy.MethodInterceptor;  
-import net.sf.cglib.proxy.MethodProxy;  
-  
-import java.lang.reflect.Method;  
-  
-/** 
- * 动态代理-拦截器 
- */  
-public class MyInterceptor implements MethodInterceptor {  
-    private Object target;//目标类  
-  
-    public MyInterceptor(Object target) {  
-        this.target = target;  
-    }  
-  
-    /** 
-     * 返回代理对象 
-     * 具体实现，暂时先不追究。 
-     */  
-    public Object createProxy() {  
-        Enhancer enhancer = new Enhancer();  
-        enhancer.setCallback(this);//回调函数  拦截器  
-        //设置代理对象的父类,可以看到代理对象是目标对象的子类。所以这个接口类就可以省略了。  
-        enhancer.setSuperclass(this.target.getClass());  
-        return enhancer.create();  
-    }  
-  
-    /** 
-     * args 目标方法的参数 
-     * method 目标方法 
-     */  
-    @Override  
-    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {  
-        System.out.println("aaaaa");//切面方法a();  
-        //。。。  
-        method.invoke(this.target, objects);//调用目标类的目标方法  
-        //。。。  
-        System.out.println("bbbbb");//切面方法f();  
-        return null;  
-    }  
-}  
-
-测试类：
-package com.sss.designPattern.proxy.dynamicProxy.cglbDynamicProxy;  
-  
-public class MainTest {  
-    public static void main(String[] args) {  
-        //目标对象  
-        TargetObject target = new TargetObject();  
-        //拦截器  
-        MyInterceptor myInterceptor = new MyInterceptor(target);  
-        //代理对象，调用cglib系统方法自动生成  
-        //注意：代理类是目标类的子类。  
-        TargetObject proxyObj = (TargetObject) myInterceptor.createProxy();  
-        proxyObj.business();  
-    }  
-}  
-```
-区别：
-首先从文件数上来说，cglib比jdk实现的少了个接口类。因为cglib返回的代理对象是目标对象的子类。而jdk产生的代理对象和目标对象都实现了一个公共接口。
-
-动态代理分为两种：  
-   *  jdk的动态代理  
-      *  代理对象和目标对象实现了共同的接口  
-      *  拦截器必须实现InvocationHanlder接口  
-  
-   *  cglib的动态代理  
-      *  代理对象是目标对象的子类  
-      *  拦截器必须实现MethodInterceptor接口  
-      *  hibernate中session.load采用的是cglib实现的  
-
-> 参考
-> - https://blog.csdn.net/zcc_0015/article/details/22695647
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\217\215\345\260\204\351\201\207\345\210\260\346\263\233\345\236\213\346\227\266\347\232\204bug.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\217\215\345\260\204\351\201\207\345\210\260\346\263\233\345\236\213\346\227\266\347\232\204bug.md"
deleted file mode 100644
index c16c89e15f..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\217\215\345\260\204\351\201\207\345\210\260\346\263\233\345\236\213\346\227\266\347\232\204bug.md"
+++ /dev/null
@@ -1,125 +0,0 @@
-#  1 当反射遇见重载
-重载**level**方法，入参分别是**int**和**Integer**。
-![](https://img-blog.csdnimg.cn/2021063020523870.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)若不使用反射，选用哪个重载方法很清晰，比如：
-- 传入**666**就走int参数重载
-- 传入`Integer.valueOf(“666”)`走Integer重载
-
-那反射调用方法也是根据入参类型确定使用哪个重载方法吗？
-使用`getDeclaredMethod`获取 `grade`方法，然后传入`Integer.valueOf(“36”)`
-![](https://img-blog.csdnimg.cn/20210630205520594.png)
-结果是：
-![](https://img-blog.csdnimg.cn/20210630205642767.png)
-因为反射进行方法调用是通过
-## 方法签名
-来确定方法。本例的`getDeclaredMethod`传入的参数类型`Integer.TYPE`其实代表`int`。
-![](https://img-blog.csdnimg.cn/2021063020590337.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-所以不管传包装类型还是基本类型，最终都是调用int入参重载方法。
-
-将`Integer.TYPE`改为`Integer.class`，则实际执行的参数类型就是Integer了。且无论传包装类型还是基本类型，最终都调用Integer入参重载方法。
-
-综上，反射调用方法，是以反射获取方法时传入的方法名和参数类型来确定调用的方法。
-
-# 2 泛型的类型擦除
-泛型允许SE使用类型参数替代精确类型，实例化时再指明具体类型。利于代码复用，将一套代码应用到多种数据类型。
-
-泛型的类型检测，可以在编译时检查很多泛型编码错误。但由于历史兼容性而妥协的泛型类型擦除方案，在运行时还有很多坑。
-
-## 案例
-现在期望在类的字段内容变动时记录日志，于是SE想到定义一个泛型父类，并在父类中定义一个统一的日志记录方法，子类可继承该方法。上线后总有日志重复记录。
-
-- 父类
-![](https://img-blog.csdnimg.cn/20210701102521658.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 子类1
-![](https://img-blog.csdnimg.cn/20210701102727240.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 通过反射调用子类方法：
-![](https://img-blog.csdnimg.cn/20210701103212354.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-虽Base.value正确设置为了JavaEdge，但父类setValue调用了两次，计数器显示2
-![](https://img-blog.csdnimg.cn/20210701103441331.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-两次调用Base.setValue，是因为getMethods找到了两个`setValue`：
-![](https://img-blog.csdnimg.cn/20210701104024581.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-![](https://img-blog.csdnimg.cn/20210701103932953.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 子类重写父类方法失败原因
-- 子类未指定String泛型参数，父类的泛型方法`setValue(T value)`泛型擦除后是`setValue(Object value)`，于是子类入参String的`setValue`被当作新方法
-- 子类的`setValue`未加`@Override`注解，编译器未能检测到重写失败
-![](https://img-blog.csdnimg.cn/20210701104847761.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-有的同学会认为是因为反射API使用错误导致而非重写失败：
-- `getMethods`
-得到**当前类和父类**的所有`public`方法
-- `getDeclaredMethods`
-获得**当前类**所有的public、protected、package和private方法
-
-于是用`getDeclaredMethods`替换`getMethods`：
-![](https://img-blog.csdnimg.cn/20210701110017589.png)![](https://img-blog.csdnimg.cn/20210701110341844.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-虽然这样做可以规避重复记录日志，但未解决子类重写父类方法失败的问题
-- 使用Sub1时还是会发现有俩个`setValue`
-![](https://img-blog.csdnimg.cn/20210701111541648.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-于是，终于明白还得重新实现**Sub2**，继承Base时将**String**作为泛型T类型，并使用 **@Override** 注解 **setValue**
-![](https://img-blog.csdnimg.cn/20210701131719235.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- 但还是出现重复日志
-![](https://img-blog.csdnimg.cn/20210701131952964.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-Sub2的`setValue`竟然调用了两次，难道是JDK反射有Bug！`getDeclaredMethods`查找到的方法肯定来自`Sub2`；而且Sub2看起来也就一个setValue，怎么会重复？
-
-调试发现，Child2类其实有俩`setValue`：入参分别是String、Object。
-![](https://img-blog.csdnimg.cn/20210701133219777.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-这就是因为**泛型类型擦除**。
-
-## 反射下的泛型擦除“天坑”
-Java泛型类型在编译后被擦除为**Object**。子类虽指定父类泛型T类型是**String**，但编译后T会被擦除成为Object，所以父类`setValue`入参是**Object**，value也是**Object**。
-若**Sub2.setValue**想重写父类，那入参也须为**Object**。所以，编译器会为我们生成一个桥接方法。
-Sub2类的class字节码：
-```bash
-➜  genericandinheritance git:(master) ✗ javap -c Sub2.class
-Compiled from "GenericAndInheritanceApplication.java"
-class com.javaedge.oop.genericandinheritance.Sub2 extends com.javaedge.oop.genericandinheritance.Base<java.lang.String> {
-  com.javaedge.oop.genericandinheritance.Sub2();
-    Code:
-       0: aload_0
-       1: invokespecial #1                  // Method com/javaedge/oop/genericandinheritance/Base."<init>":()V
-       4: return
-
-  public void setValue(java.lang.String);
-    Code:
-       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
-       3: ldc           #3                  // String call Sub2.setValue
-       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
-       8: aload_0
-       9: aload_1
-      10: invokespecial #5                  // Method com/javaedge/oop/genericandinheritance/Base.setValue:(Ljava/lang/Object;)V
-      13: return
-
-  public void setValue(java.lang.Object);
-    Code:
-       0: aload_0
-       1: aload_1
-       2: checkcast     #6                  // class java/lang/String
-       // 入参为Object的setValue在内部调用了入参为String的setValue方法
-       5: invokevirtual #7                  // Method setValue:(Ljava/lang/String;)V
-       8: return
-}
-```
-若编译器未帮我们实现该桥接方法，则Sub2重写的是父类泛型类型擦除后、入参是Object的setValue。这两个方法的参数，一个String一个Object，显然不符Java重写。
-
-入参为Object的桥接方法上标记了`public  synthetic bridge`：
-- synthetic代表由编译器生成的不可见代码
-- bridge代表这是泛型类型擦除后生成的桥接代码
-![](https://img-blog.csdnimg.cn/20210701135257552.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## 修正
-知道了桥接方法的存在，现在就该知道如何修正代码了。
-- 通过**getDeclaredMethods**获取所有方法后，还得加上非isBridge这个过滤条件：
-![](https://img-blog.csdnimg.cn/20210701143227136.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 结果
-![](https://img-blog.csdnimg.cn/20210701143616524.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-> 参考
-> - https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/index.html
-> - https://docs.oracle.com/javase/tutorial/reflect/index.html
-> - https://docs.oracle.com/javase/8/docs/technotes/guides/language/annotations.html
-> - https://docs.oracle.com/javase/tutorial/java/annotations/index.html
-> - https://docs.oracle.com/javase/8/docs/technotes/guides/language/generics.html
-> - https://docs.oracle.com/javase/tutorial/java/generics/index.html
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\257\271\350\261\241\345\272\217\345\210\227\345\214\226\345\272\225\345\261\202\345\216\237\347\220\206\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\257\271\350\261\241\345\272\217\345\210\227\345\214\226\345\272\225\345\261\202\345\216\237\347\220\206\346\272\220\347\240\201\350\247\243\346\236\220.md"
index 18d6d00333..3891599d22 100644
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\257\271\350\261\241\345\272\217\345\210\227\345\214\226\345\272\225\345\261\202\345\216\237\347\220\206\346\272\220\347\240\201\350\247\243\346\236\220.md"
+++ "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\257\271\350\261\241\345\272\217\345\210\227\345\214\226\345\272\225\345\261\202\345\216\237\347\220\206\346\272\220\347\240\201\350\247\243\346\236\220.md"
@@ -15,7 +15,7 @@ package test;
 import java.io.*;
 
 /**
- * @author v_JavaEdge
+ * @author v_shishusheng
  * @date 2018/2/7
  */
 public class SerializableTest {
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\274\202\345\270\270\346\234\272\345\210\266\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\274\202\345\270\270\346\234\272\345\210\266\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
deleted file mode 100644
index 94d0c23c1e..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\345\274\202\345\270\270\346\234\272\345\210\266\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
+++ /dev/null
@@ -1,2091 +0,0 @@
-**Java 的基本理念是“结构不佳的代码不能运行”。**
-
-改进的错误恢复机制是提高代码健壮性的最强有力的方式。错误恢复在我们所编写的每一个程序中都是基本的要素，但是在 Java 中它显得格外重要，因为 Java 的主要目标之一就是创建供他人使用的程序构件。
-
-发现错误的理想时机是在编译期。然而，编译期并不能找出所有错误，余下问题必须在运行时解决。这就需要错误源能通过某种方式，把适当的信息传递给知道如何正确处理这个问题的接收者。
-
-> 要想创建健壮的系统，它的每一个构件都必须是健壮的。
-
-Java 使用异常来提供一致的错误报告模型，使得构件能够与客户端代码可靠地沟通问题。
-
-- Java 中的异常处理的目的
-通过使用少于目前数量的代码来简化大型、可靠的程序的生成，并且通过这种方式可以使你更加确信你的应用中没有未处理的错误。异常的相关知识学起来并非艰涩难懂，并且它属于那种可以使你的项目受益明显、立竿见影的特性。
-
-因为异常处理是 Java 中唯一官方的错误报告机制，并且通过编译器强制执行，所以不学习异常处理的话，也就只能写出那么些例子了。本章重在如何编写正确的异常处理程序，当方法出问题的时候，如何产生自定义的异常。
-
-# 1 异常概念
-## 1.1 历史中所谓的“异常”
-C 以及其他早期语言常常具有多种错误处理模式，这些模式往往建立在约定俗成的基础上，并不属于语言规范。
-
-通常是会返回某个特殊值或者设置某个标志，并且假定接收者将对这个返回值或标志进行检查，以判定是否发生了错误。
-然而，随着时间的推移，人们发现，高傲的程序员们在使用程序库的时候更倾向于认为：“对，错误也许会发生，但那是别人造成的，不关我的事”。所以，程序员不去检查错误情形也就不足为奇了（对某些错误情形的检查确实无聊）。如果的确在每次调用方法的时候都彻底地进行错误检查，代码很可能会变得难以阅读。
-正是由于程序员还仍然用这些方式拼凑系统，所以他们拒绝承认这样一个事实：对于构造大型、健壮、可维护的程序而言，这种错误处理模式已经成为了主要障碍。
-
-## 解决方案
-强制规定形式，来消除错误处理过程中的随心所欲。
-这种做法由来已久，对异常处理的实现可以追溯到1960s的操作系统，甚至BASIC中的“on error goto”语句。C++的异常处理机制基于 Ada，Java 中的异常处理机制则建立在 C++ 的基础上。
-
-“异常”这个词有“我对此感到意外”的意思。问题出现你也许不清楚如何处理，但你知道不该置之不理，你要停下来看看是不是有地方能够处理这个问题。
-只是在当前的环境中还没有足够的信息来解决这个问题，所以就把这个问题提交到一个更高级别的环境中，在那里将作出正确的决定。
-
-异常往往能降低错误处理代码的复杂度。
-如果不使用异常，那么就必须检查特定的错误，并在程序中的许多地方去处理它。而如果使用异常，那就不必在方法调用处检查，因为异常机制将保证能够捕获这个错误。理想情况下，只需在一个地方处理错误，即所谓的异常处理程序。
-这种方式不仅节省代码，而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之，与以前的错误处理方法相比，异常机制使代码的阅读、编写和调试工作更加井井有条。
-
-# 2 基本异常
-注意把异常情形与普通问题相区分：
-- 普通问题
-在当前环境下能得到足够的信息，总能处理这个错误
-- 异常情形（exceptional condition）
-阻止当前方法或作用域继续执行，就不能继续下去了，因为在当前环境下无法获得必要的信息来解决问题。你所能做的就是从当前环境跳出，并且把问题提交给上一级环境。这就是抛出异常时所发生的事情。
-
-## 简单的例子
-除法就是一个除数有可能为 0，所以先进行检查很有必要。
-但除数为 0 代表的究竟是什么意思呢？通过当前正在解决的问题环境，或许能知道该如何处理除数为 0 的情况。但如果这是一个意料之外的值，你也不清楚该如何处理，那就要抛出异常，而不是顺着原来的路径继续执行下去。
-
-## 抛出异常后
-1. 同 Java 中其他对象的创建一样，将使用 new 在堆上创建异常对象
-2. 当前的执行路径（无法继续）被终止，并且从当前环境中弹出对异常对象的引用
-3. 异常处理机制接管程序，并开始寻找一个恰当的地方来继续执行程序。这个恰当的地方就是异常处理程序，它的任务是将程序从错误状态中恢复，以使程序能要么换一种方式运行，要么继续运行下去。
-
-## 抛出异常的简单例子
-对于对象引用 t，传给你可能尚未初始化。所以在使用这个对象引用调用其方法之前，会先对引用进行检查。可以创建一个代表错误信息的对象，并且将它从当前环境中“抛出”，这样就把错误信息传播到了“更大”的环境中。
-这被称为*抛出一个异常*，看起来像这样：
-
-```java
-if(t == null)
-    throw new NullPointerException();
-```
-
-这就抛出了异常，于是在当前环境下就不必再为这个问题操心了，它将在别的地方得到处理。
-
-## 异常模型的观点
-- 异常使得我们可以将每件事都当作一个事务来考虑，而异常可以看护着这些事务的底线，事务的基本保障是我们所需的在分布式计算中的异常处理。事务是计算机中的合同法，如果出了什么问题，我们只需要放弃整个计算
-- 我们还可以将异常看作是一种内建的撤销系统，因为（在细心使用的情况下）我们在程序中可以拥有各种不同的撤销点。如果程序的某部分失败了，异常将撤销到程序中某个已知的稳定点上。
-
-异常最重要的方面之一就是如果发生问题，它们将不允许程序沿着其正常的路径继续走下去。在 C 和 C++ 这样的语言中，这可真是个问题，尤其是 C，它没有任何办法可以强制程序在出现问题时停止在某条路径上运行下去，因此我们有可能会较长时间地忽略问题，从而会陷入完全不恰当的状态中。异常允许我们（如果没有其他手段）强制程序停止运行，并告诉我们出现了什么问题，或者（理想状态下）强制程序处理问题，并返回到稳定状态。
-
-## 异常参数
-与使用 Java 中的其他对象一样，用 new 在堆上创建异常对象，这也伴随着存储空间的分配和构造器的调用。
-所有标准异常类都有两个构造器：
-- 无参构造器
-- 接受字符串作为参数，以便能把相关信息放入异常对象构造器
-```java
-throw new NullPointerException("t = null");
-```
-
-关键字 **throw** 将产生许多有趣的结果。在使用 **new** 创建了异常对象之后，此对象的引用将传给 **throw**。
-尽管异常对象的类型通常与方法设计的返回类型不同，但从效果上看，它就像是从方法“返回”的。可以简单地把异常处理看成一种不同的返回机制，当然若过分强调这种类比的话，就会有麻烦了。
-另外还能用抛出异常的方式从当前的作用域退出。在这两种情况下，将会返回一个异常对象，然后退出方法或作用域。
-
-抛出异常与方法正常返回的相似之处到此为止。因为异常返回的“地点”与普通方法调用返回的“地点”完全不同。
-异常将在一个恰当的异常处理程序中得到解决，它的位置可能离异常被抛出的地方很远，也可能会跨越方法调用栈的许多层级。
-
-此外，能够抛出任意类型的 **Throwable** 对象，它是异常类型的根类。通常，对于不同类型错误，要抛相应异常。错误信息可以保存在异常对象内部或者用异常类的名称来暗示。上一层环境通过这些信息来决定如何处理异常。
-
-> 通常，唯一的信息只有异常的类型名，而在异常对象内部没有任何有意义的信息
-
-# 3 异常捕获
-
-- 首先要理解监控区域（guarded region）的概念
-它是一段可能产生异常的代码，并且后面跟着处理这些异常的代码。
-
-## 3.1 try 块
-如果在方法内部抛异常（或在方法内部调用的其他方法抛异常），该方法将在抛异常过程中结束。要是不希望方法就此结束，可在方法内设置一个特殊的块来捕获异常。因为在这个块里“尝试”各种（可能产生异常的）方法调用，所以称为 try 块。它是跟在 try 关键字之后的普通程序块：
-
-```java
-try {
-    // Code that might generate exceptions
-}
-```
-### 意义
-对于不支持异常处理的程序语言，要想仔细检查错误，就得在每个方法调用的前后加上设置和错误检查的代码，甚至在每次调用同一方法时也得这么做。
-有了异常处理机制，可以把所有动作都放在 try 块，然后只需在一个地方就可以捕获所有异常。这意味着你的代码将更容易编写和阅读，因为代码的意图和错误检查不是混淆在一起。
-
-## 3.2 异常处理程序 - catch 块
-抛出的异常必须在 异常处理程序 得到处理。针对每个要捕获的异常，得准备相应的处理程序。
-异常处理程序紧跟在 try 块之后，以关键字 catch 表示：
-```java
-try {
-    // Code that might generate exceptions
-} catch(Type1 id1) {
-    // Handle exceptions of Type1
-} catch(Type2 id2) {
-    // Handle exceptions of Type2
-} catch(Type3 id3) {
-    // Handle exceptions of Type3
-}
-```
-
-每个 catch （异常处理程序）看起来就像是接收且仅接收一个特殊类型的参数的方法。可以在处理程序的内部使用标识符（id1，id2 等等），这与方法参数的使用很相似。有时可能用不到标识符，因为异常的类型已经给了你足够的信息来对异常进行处理，但标识符并不可以省略。
-
-异常处理程序必须紧跟在 try 块后。当异常被抛出时，异常处理机制将负责搜寻参数与异常类型相匹配的第一个处理程序。然后进入 catch 子句执行，此时认为异常得到了处理。
-一旦 catch 子句结束，则处理程序的查找过程结束。注意，只有匹配的 catch 子句才能得到执行。
-
-### 意义
-注意在 try 块的内部，许多不同的方法调用可能会产生类型相同的异常，而你只需要提供一个针对此类型的异常处理程序。
-
-## 3.3 终止与恢复
-异常处理理论上有两种基本模型。
-### 3.3.1 终止模型
-Java 和 C++所支持的模型。
-将假设错误非常严重，以至于程序无法返回到异常发生的地方继续执行。一旦异常被抛出，就表明错误无法挽回。
-
-### 3.3.2 恢复模型
-异常处理程序的工作是修正错误，然后重新尝试调用出问题的方法，并认为第二次能成功。对于恢复模型，通常希望异常被处理之后能继续执行程序。
-如果想要用 Java 实现类似恢复的行为
-- 那么在遇见错误时就不能抛出异常，而是调用方法来修正该错误
-- 或者把 try 块放在 while 循环里，这样就不断地进入 try 块，直到得到满意的结果
-
-#### 缺陷
-在过去，使用支持恢复模型异常处理的操作系统的程序员们最终还是转向使用类似“终止模型”的代码，并且忽略恢复行为。所以虽然恢复模型开始显得很吸引人，但不是很实用。其中的主要原因可能是它所导致的耦合：恢复性的处理程序需要了解异常抛出的地点，这势必要包含依赖于抛出位置的非通用性代码。这增加了代码编写和维护的困难，对于异常可能会从许多地方抛出的大型程序来说，更是如此。
-
-# 4 自定义异常
-Java 提供的异常体系不可能预见所有的希望加以报告的错误，所以可自己定义异常类。
-
-必须从已有的异常类继承，最好是选择意思相近的异常类继承（不过并不容易找）。建立新的异常类型最简单的方法就是让编译器为你产生
-## 无参构造器
-
-```java
-class SimpleException extends Exception {}
-
-public class InheritingExceptions {
-    public void f() throws SimpleException {
-        System.out.println(
-                "Throw SimpleException from f()");
-        throw new SimpleException();
-    }
-    public static void main(String[] args) {
-        InheritingExceptions sed =
-                new InheritingExceptions();
-        try {
-            sed.f();
-        } catch(SimpleException e) {
-            System.out.println("Caught it!");
-        }
-    }
-}
-```
-
-输出为：
-
-```
-Throw SimpleException from f()
-Caught it!
-```
-
-编译器创建了无参构造器，它将自动调用基类的无参构造器。**对异常来说，最重要的部分就是类名**。
-
-## 字符串参数的构造器
-
-```java
-class MyException extends Exception {
-    MyException() {}
-    MyException(String msg) { super(msg); }
-}
-public class FullConstructors {
-    public static void f() throws MyException {
-        System.out.println("Throwing MyException from f()");
-        throw new MyException();
-    }
-    public static void g() throws MyException {
-        System.out.println("Throwing MyException from g()");
-        throw new MyException("Originated in g()");
-    }
-    public static void main(String[] args) {
-        try {
-            f();
-        } catch(MyException e) {
-            e.printStackTrace(System.out);
-        }
-        try {
-            g();
-        } catch(MyException e) {
-            e.printStackTrace(System.out);
-        }
-    }
-}
-```
-
-输出为：
-
-```
-Throwing MyException from f()
-MyException
-    at FullConstructors.f(FullConstructors.java:11)
-    at
-FullConstructors.main(FullConstructors.java:19)
-Throwing MyException from g()
-MyException: Originated in g()
-    at FullConstructors.g(FullConstructors.java:15)
-    at
-FullConstructors.main(FullConstructors.java:24)
-```
-
-新增的代码非常简短：两个构造器定义了 MyException 类型对象的创建方式。对于第二个构造器，使用 super 关键字明确调用了其基类构造器，它接受一个字符串作为参数。
-
-在异常处理程序中，调用了在 Throwable 类声明（Exception 即从此类继承）的 printStackTrace() 方法。就像从输出中看到的，它将打印“从方法调用处直到异常抛出处”的方法调用序列。这里，信息被发送到了 System.out，并自动地被捕获和显示在输出中。但是，如果调用默认版本：
-
-```java
-e.printStackTrace();
-```
-
-信息就会被输出到标准错误流。
-
-## 4.1 记录日志
-- 使用 java.util.logging 工具将输出记录到日志
-![](https://img-blog.csdnimg.cn/20200601011616774.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 输出
-![](https://img-blog.csdnimg.cn/20200601011710120.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-直接调用与日志记录消息的级别相关联的方法，这里是 severe()。
-为了产生日志记录消息，要获取异常抛出处的栈轨迹，但是 printStackTrace() 不会默认地产生字符串。为了获取字符串，我们需要使用重载的 printStackTrace() 方法，它接受一个 java.io.PrintWriter 对象作为参数（PrintWriter 会在。如果我们将一个 java.io.StringWriter 对象传递给这个 PrintWriter 的构造器，那么通过调用 toString() 方法，就可以将输出抽取为一个 String。
-
-尽管由于 LoggingException 将所有记录日志的基础设施都构建在异常自身中，使得它所使用的方式非常方便，并因此不需要客户端程序员的干预就可以自动运行。
-
-### 捕获和记录其他人编写的异常
-必须在异常处理程序中生成日志消息
-![](https://img-blog.csdnimg.cn/20200601012419331.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 结果
-![](https://img-blog.csdnimg.cn/20200601012506770.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-### 加入额外构造器和成员
-
-```java
-class MyException2 extends Exception {
-    private int x;
-    MyException2() {}
-    MyException2(String msg) { super(msg); }
-    MyException2(String msg, int x) {
-        super(msg);
-        this.x = x;
-    }
-    public int val() { return x; }
-    @Override
-    public String getMessage() {
-        return "Detail Message: "+ x
-                + " "+ super.getMessage();
-    }
-}
-public class ExtraFeatures {
-    public static void f() throws MyException2 {
-        System.out.println(
-                "Throwing MyException2 from f()");
-        throw new MyException2();
-    }
-    public static void g() throws MyException2 {
-        System.out.println(
-                "Throwing MyException2 from g()");
-        throw new MyException2("Originated in g()");
-    }
-    public static void h() throws MyException2 {
-        System.out.println(
-                "Throwing MyException2 from h()");
-        throw new MyException2("Originated in h()", 47);
-    }
-    public static void main(String[] args) {
-        try {
-            f();
-        } catch(MyException2 e) {
-            e.printStackTrace(System.out);
-        }
-        try {
-            g();
-        } catch(MyException2 e) {
-            e.printStackTrace(System.out);
-        }
-        try {
-            h();
-        } catch(MyException2 e) {
-            e.printStackTrace(System.out);
-            System.out.println("e.val() = " + e.val());
-        }
-    }
-}
-```
-
-输出为：
-
-```
-Throwing MyException2 from f()
-MyException2: Detail Message: 0 null
-at ExtraFeatures.f(ExtraFeatures.java:24)
-at ExtraFeatures.main(ExtraFeatures.java:38)
-Throwing MyException2 from g()
-MyException2: Detail Message: 0 Originated in g()
-at ExtraFeatures.g(ExtraFeatures.java:29)
-at ExtraFeatures.main(ExtraFeatures.java:43)
-Throwing MyException2 from h()
-MyException2: Detail Message: 47 Originated in h()
-at ExtraFeatures.h(ExtraFeatures.java:34)
-at ExtraFeatures.main(ExtraFeatures.java:48)
-e.val() = 47
-```
-
-新的异常添加了字段 x 以及设定 x 值的构造器和读取数据的方法。此外，还覆盖了 Throwable.
-getMessage() 方法，以产生更详细的信息。对于异常类来说，getMessage() 方法有点类似于 toString() 方法。
-
-> 既然异常也是对象的一种，所以可以继续修改这个异常类，以得到更强的功能。但要记住，使用程序包的客户端程序员可能仅仅只是查看一下抛出的异常类型，其他的就不管了（大多数 Java 库里的异常都是这么用的），所以对异常所添加的其他功能也许根本用不上。
-
-# 5 异常声明
-Java 鼓励把方法可能会抛出的异常告知使用此方法的客户端程序员。这是种优雅的做法，它使得调用者能确切知道写什么样的代码可以捕获所有潜在的异常。
-
-当然，如果提供了源代码，客户端程序员可以在源代码中查找 throw 语句来获知相关信息，然而程序库通常并不与源代码一起发布。为了预防这样的问题，Java 提供了相应的语法（并强制使用这个语法），使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型，然后客户端程序员就可以进行相应的处理。这就是异常说明，它属于方法声明的一部分，紧跟在形式参数列表之后。
-
-异常说明使用了附加的关键字 throws，后面接一个所有潜在异常类型的列表，所以方法定义可能看起来像这样：
-```java
-void f() throws TooBig, TooSmall, DivZero { // ...
-```
-但要是这样写：
-```java
-void f() { // ...
-```
-就表示此方法不会抛任何异常。
-
-代码必须与异常说明保持一致。如果方法里的代码产生了异常却没有进行处理，编译器会发现这个问题并提醒你：
-- 要么处理这个异常
-- 要么就在异常说明中表明此方法将产生异常
-
-通过这种自顶向下强制执行的异常说明机制，Java 在编译时就可以保证一定的异常正确性。
-
-## “作弊”的地方
-可以声明方法将抛异常，实际上却不抛出。编译器相信了这个声明，并强制此方法的用户像真的抛出异常那样使用这个方法。这样做的好处是，为异常先占个位子，以后就可以抛出这种异常而不用修改已有的代码。在定义抽象基类和接口时这种能力很重要，这样派生类或接口实现就能够抛出这些预先声明的异常。
-
-**这种在编译时被强制检查的异常称为被检查的异常。**
-
-# 6 捕获所有异常
-## 6.1  如何使用 Exception 类型
-可以只写一个异常处理程序来捕获所有类型的异常。
-通过捕获异常类型的基类 Exception 即可：
-```java
-catch(Exception e) {
-    System.out.println("Caught an exception");
-}
-```
-### 最佳实践
-它会捕获所有异常，所以最好把它**放在处理程序列表的末尾**，以防它抢在其他处理程序之前先把异常捕获了。
-
-### 从 Throwable 继承的方法
-因为 Exception 是与编程有关的所有异常类的基类，不含太多具体信息，但可以调用它从其基类 Throwable 继承的方法：
-- String getMessage()
-![](https://img-blog.csdnimg.cn/20200601013145686.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20200601013121753.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-![](https://img-blog.csdnimg.cn/20200601013208907.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-用来获取详细信息，或用本地语言表示的详细信息。
-
-```java
-String toString()
-```
-
-返回对 Throwable 的简单描述，要是有详细信息的话，也会把它包含在内。
-
-```java
-void printStackTrace()
-void printStackTrace(PrintStream)
-void printStackTrace(java.io.PrintWriter)
-```
-
-打印 Throwable 和 Throwable 的调用栈轨迹。调用栈显示了“把你带到异常抛出地点”的方法调用序列。其中第一个版本输出到标准错误，后两个版本允许选择要输出的流。
-
-```java
-Throwable fillInStackTrace()
-```
-
-用于在 Throwable 对象的内部记录栈帧的当前状态。这在程序重新抛出错误或异常时很有用。
-
-此外，也可以使用 Throwable 从其基类 Object（也是所有类的基类）继承的方法。对于异常来说，getClass）也许是个很好用的方法，它将返回一个表示此对象类型的对象。然后可以使用 getName）方法查询这个 Class 对象包含包信息的名称，或者使用只产生类名称的 getSimpleName() 方法。
-
-### 使用案例
-
-```java
-public class ExceptionMethods {
-    public static void main(String[] args) {
-        try {
-            throw new Exception("My Exception");
-        } catch(Exception e) {
-            System.out.println("Caught Exception");
-            System.out.println(
-                    "getMessage():" + e.getMessage());
-            System.out.println("getLocalizedMessage():" +
-                    e.getLocalizedMessage());
-            System.out.println("toString():" + e);
-            System.out.println("printStackTrace():");
-            e.printStackTrace(System.out);
-        }
-    }
-}
-```
-
-输出为：
-
-```java
-Caught Exception
-getMessage():My Exception
-getLocalizedMessage():My Exception
-toString():java.lang.Exception: My Exception
-printStackTrace():
-java.lang.Exception: My Exception
-at
-ExceptionMethods.main(ExceptionMethods.java:7)
-```
-
-可以发现每个方法都比前一个提供了更多的信息一一实际上它们每一个都是前一个的超集。
-
-## 多重捕获
-如果有一组具有相同基类的异常，你想使用同一方式进行捕获，那你直接 catch 它们的基类型。但是，如果这些异常没有共同的基类型，在 Java 7 之前，你必须为每一个类型编写一个 catch：
-
-```java
-// exceptions/SameHandler.java
-class EBase1 extends Exception {}
-class Except1 extends EBase1 {}
-class EBase2 extends Exception {}
-class Except2 extends EBase2 {}
-class EBase3 extends Exception {}
-class Except3 extends EBase3 {}
-class EBase4 extends Exception {}
-class Except4 extends EBase4 {}
-
-public class SameHandler {
-    void x() throws Except1, Except2, Except3, Except4 {}
-    void process() {}
-    void f() {
-        try {
-            x();
-        } catch(Except1 e) {
-            process();
-        } catch(Except2 e) {
-            process();
-        } catch(Except3 e) {
-            process();
-        } catch(Except4 e) {
-            process();
-        }
-    }
-}
-```
-
-通过 Java 7 的多重捕获机制，你可以使用“或”将不同类型的异常组合起来，只需要一行 catch 语句：
-
-```java
-// exceptions/MultiCatch.java
-public class MultiCatch {
-    void x() throws Except1, Except2, Except3, Except4 {}
-    void process() {}
-    void f() {
-        try {
-            x();
-            
-        } catch(Except1 | Except2 | Except3 | Except4 e) {
-            process();
-        }
-    }
-}
-```
-
-或者以其他的组合方式：
-
-```java
-// exceptions/MultiCatch2.java
-public class MultiCatch2 {
-    void x() throws Except1, Except2, Except3, Except4 {}
-    void process1() {}
-    void process2() {}
-    void f() {
-        try {
-            x();
-        } catch(Except1 | Except2 e) {
-            process1();
-        } catch(Except3 | Except4 e) {
-            process2();
-        }
-    }
-}
-```
-
-这对书写更整洁的代码很有帮助。
-
-## 栈轨迹
-
-printStackTrace() 方法所提供的信息可以通过 getStackTrace() 方法来直接访问，这个方法将返回一个由栈轨迹中的元素所构成的数组，其中每一个元素都表示栈中的一桢。元素 0 是栈顶元素，并且是调用序列中的最后一个方法调用（这个 Throwable 被创建和抛出之处）。数组中的最后一个元素和栈底是调用序列中的第一个方法调用。下面的程序是一个简单的演示示例：
-
-```java
-// exceptions/WhoCalled.java
-// Programmatic access to stack trace information
-public class WhoCalled {
-    static void f() {
-// Generate an exception to fill in the stack trace
-        try {
-            throw new Exception();
-        } catch(Exception e) {
-            for(StackTraceElement ste : e.getStackTrace())
-                System.out.println(ste.getMethodName());
-        }
-    }
-    static void g() { f(); }
-    static void h() { g(); }
-    public static void main(String[] args) {
-        f();
-        System.out.println("*******");
-        g();
-        System.out.println("*******");
-        h();
-    }
-}
-```
-
-输出为：
-
-```
-f
-main
-*******
-f
-g
-main
-*******
-f
-g
-h
-main
-```
-
-这里，我们只打印了方法名，但实际上还可以打印整个 StackTraceElement，它包含其他附加的信息。
-
-## 重新抛出异常
-
-有时希望把刚捕获的异常重新抛出，尤其是在使用 Exception 捕获所有异常的时候。既然已经得到了对当前异常对象的引用，可以直接把它重新抛出：
-
-```java
-catch(Exception e) {
-    System.out.println("An exception was thrown");
-    throw e;
-}
-```
-
-重抛异常会把异常抛给上一级环境中的异常处理程序，同一个 `try` 块的后续 `catch` 子句将被忽略。此外，异常对象的所有信息都得以保持，所以较高一级环境中捕获此异常的处理程序可以从这个异常对象中得到所有信息。
-
-若只是把当前异常对象重新抛出，那么 
-- `printStackTrace()` 方法显示的将是原来异常抛出点的调用栈信息，而非重新抛出点的信息。
-![](https://img-blog.csdnimg.cn/20201112135659805.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-要想更新该信息，可调用 `filInStackTrace()` 方法，这将返回一个 Throwable 对象，它是通过把当前调用栈信息填入原来那个异常对象而建立的。
-![](https://img-blog.csdnimg.cn/20201112135854256.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-就像这样：
-
-```java
-public class Rethrowing {
-    public static void f() throws Exception {
-        System.out.println(
-                "originating the exception in f()");
-        throw new Exception("thrown from f()");
-    }
-    public static void g() throws Exception {
-        try {
-            f();
-        } catch(Exception e) {
-            System.out.println(
-                    "Inside g(), e.printStackTrace()");
-            e.printStackTrace(System.out);
-            throw e;
-        }
-    }
-    public static void h() throws Exception {
-        try {
-            f();
-        } catch(Exception e) {
-            System.out.println(
-                    "Inside h(), e.printStackTrace()");
-            e.printStackTrace(System.out);
-            throw (Exception)e.fillInStackTrace();
-        }
-    }
-    public static void main(String[] args) {
-        try {
-            g();
-        } catch(Exception e) {
-            System.out.println("main: printStackTrace()");
-            e.printStackTrace(System.out);
-        }
-        try {
-            h();
-        } catch(Exception e) {
-            System.out.println("main: printStackTrace()");
-            e.printStackTrace(System.out);
-        }
-    }
-}
-```
-
-输出为：
-
-```java
-originating the exception in f()
-Inside g(), e.printStackTrace()
-java.lang.Exception: thrown from f()
-at Rethrowing.f(Rethrowing.java:8)
-at Rethrowing.g(Rethrowing.java:12)
-at Rethrowing.main(Rethrowing.java:32)
-main: printStackTrace()
-java.lang.Exception: thrown from f()
-at Rethrowing.f(Rethrowing.java:8)
-at Rethrowing.g(Rethrowing.java:12)
-at Rethrowing.main(Rethrowing.java:32)
-originating the exception in f()
-Inside h(), e.printStackTrace()
-java.lang.Exception: thrown from f()
-at Rethrowing.f(Rethrowing.java:8)
-at Rethrowing.h(Rethrowing.java:22)
-at Rethrowing.main(Rethrowing.java:38)
-main: printStackTrace()
-java.lang.Exception: thrown from f()
-at Rethrowing.h(Rethrowing.java:27)
-at Rethrowing.main(Rethrowing.java:38)
-```
-
-调用 `fillInStackTrace` 的那一行就成了异常的新发生地。
-
-有可能在捕获异常之后抛出另一种异常。这么做的话，得到的效果类似于使用 filInStackTrace()，有关原来异常发生点的信息会丢失，剩下的是与新的抛出点有关的信息：
-
-```java
-// exceptions/RethrowNew.java
-// Rethrow a different object from the one you caught
-class OneException extends Exception {
-    OneException(String s) { super(s); }
-}
-class TwoException extends Exception {
-    TwoException(String s) { super(s); }
-}
-public class RethrowNew {
-    public static void f() throws OneException {
-        System.out.println(
-                "originating the exception in f()");
-        throw new OneException("thrown from f()");
-    }
-    public static void main(String[] args) {
-        try {
-            try {
-                f();
-            } catch(OneException e) {
-                System.out.println(
-                        "Caught in inner try, e.printStackTrace()");
-                e.printStackTrace(System.out);
-                throw new TwoException("from inner try");
-            }
-        } catch(TwoException e) {
-            System.out.println(
-                    "Caught in outer try, e.printStackTrace()");
-            e.printStackTrace(System.out);
-        }
-    }
-}
-```
-
-输出为：
-
-```java
-originating the exception in f()
-Caught in inner try, e.printStackTrace()
-OneException: thrown from f()
-at RethrowNew.f(RethrowNew.java:16)
-at RethrowNew.main(RethrowNew.java:21)
-Caught in outer try, e.printStackTrace()
-TwoException: from inner try
-at RethrowNew.main(RethrowNew.java:26)
-```
-
-最后那个异常仅知道自己来自 main()，而对 f() 一无所知。
-
-永远不必为清理前一个异常对象而担心，或者说为异常对象的清理而担心。它们都是用 new 在堆上创建的对象，所以垃圾回收器会自动把它们清理掉。
-
-### 精准的重新抛出异常
-
-在 Java 7 前，若遇到异常，则只能重新抛出该类型的异常。这导致在 Java 7 中修复的代码不精确。所以在 Java 7 前，这无法编译：
-
-```java
-class BaseException extends Exception {}
-class DerivedException extends BaseException {}
-
-public class PreciseRethrow {
-    void catcher() throws DerivedException {
-        try {
-            throw new DerivedException();
-        } catch(BaseException e) {
-            throw e;
-        }
-    }
-}
-```
-
-因为 catch 捕获了一个 BaseException，编译器强迫你声明 catcher() 抛出 BaseException，即使它实际上抛出了更具体的 DerivedException。从 Java 7 开始，这段代码就可以编译，这是一个很小但很有用的修复。
-
-### 异常链
-想要在捕获一个异常后抛出另一个异常，并且希望把原始异常的信息保存下来，这被称为异常链。
-JDK1.4 前，程序员必须自己编写代码保存原始异常的信息。现在所有 Throwable 的子类在构造器中都可接收一个 cause 对象作为参数，表示原始异常，这样通过把原始异常传递给新的异常，使得即使在当前位置创建并抛出了新的异常，也能通过这个异常链追踪到异常最初发生的位置。
-
-在 Throwable 的子类中，只有三种基本的异常类提供了带 cause 参数的构造器：
-- Error（用于 Java 虚拟机报告系统错误）
-- Exception
-- RuntimeException
-
-如果要把其他类型的异常链接起来，应该使用 initCause() 而非构造器。
-
-### 运行时动态地向 DymamicFields 对象添加字段
-```java
-// A Class that dynamically adds fields to itself to
-// demonstrate exception chaining
-class DynamicFieldsException extends Exception {}
-public class DynamicFields {
-    private Object[][] fields;
-    public DynamicFields(int initialSize) {
-        fields = new Object[initialSize][2];
-        for(int i = 0; i < initialSize; i++)
-            fields[i] = new Object[] { null, null };
-    }
-    @Override
-    public String toString() {
-        StringBuilder result = new StringBuilder();
-        for(Object[] obj : fields) {
-            result.append(obj[0]);
-            result.append(": ");
-            result.append(obj[1]);
-            result.append("\n");
-        }
-        return result.toString();
-    }
-    private int hasField(String id) {
-        for(int i = 0; i < fields.length; i++)
-            if(id.equals(fields[i][0]))
-                return i;
-        return -1;
-    }
-    private int getFieldNumber(String id)
-            throws NoSuchFieldException {
-        int fieldNum = hasField(id);
-        if(fieldNum == -1)
-            throw new NoSuchFieldException();
-        return fieldNum;
-    }
-    private int makeField(String id) {
-        for(int i = 0; i < fields.length; i++)
-            if(fields[i][0] == null) {
-                fields[i][0] = id;
-                return i;
-            }
-// No empty fields. Add one:
-        Object[][] tmp = new Object[fields.length + 1][2];
-        for(int i = 0; i < fields.length; i++)
-            tmp[i] = fields[i];
-        for(int i = fields.length; i < tmp.length; i++)
-            tmp[i] = new Object[] { null, null };
-        fields = tmp;
-// Recursive call with expanded fields:
-        return makeField(id);
-    }
-    public Object
-    getField(String id) throws NoSuchFieldException {
-        return fields[getFieldNumber(id)][1];
-    }
-    public Object setField(String id, Object value)
-            throws DynamicFieldsException {
-        if(value == null) {
-// Most exceptions don't have a "cause"
-// constructor. In these cases you must use
-// initCause(), available in all
-// Throwable subclasses.
-            DynamicFieldsException dfe =
-                    new DynamicFieldsException();
-            dfe.initCause(new NullPointerException());
-            throw dfe;
-        }
-        int fieldNumber = hasField(id);
-        if(fieldNumber == -1)
-            fieldNumber = makeField(id);
-        Object result = null;
-        try {
-            result = getField(id); // Get old value
-        } catch(NoSuchFieldException e) {
-// Use constructor that takes "cause":
-            throw new RuntimeException(e);
-        }
-        fields[fieldNumber][1] = value;
-        return result;
-    }
-    public static void main(String[] args) {
-        DynamicFields df = new DynamicFields(3);
-        System.out.println(df);
-        try {
-            df.setField("d", "A value for d");
-            df.setField("number", 47);
-            df.setField("number2", 48);
-            System.out.println(df);
-            df.setField("d", "A new value for d");
-            df.setField("number3", 11);
-            System.out.println("df: " + df);
-            System.out.println("df.getField(\"d\") : "
-                    + df.getField("d"));
-            Object field =
-                    df.setField("d", null); // Exception
-        } catch(NoSuchFieldException |
-                DynamicFieldsException e) {
-            e.printStackTrace(System.out);
-        }
-    }
-}
-```
-
-输出为：
-
-```java
-null: null
-null: null
-null: null
-d: A value for d
-number: 47
-number2: 48
-df: d: A new value for d
-number: 47
-number2: 48
-number3: 11
-df.getField("d") : A new value for d
-DynamicFieldsException
-at
-DynamicFields.setField(DynamicFields.java:65)
-at DynamicFields.main(DynamicFields.java:97)
-Caused by: java.lang.NullPointerException
-at
-DynamicFields.setField(DynamicFields.java:67)
-... 1 more
-```
-
-每个 DynamicFields 对象都含有一个数组，其元素是“成对的对象”。第一个对象表示字段标识符（一个字符串），第二个表示字段值，值的类型可以是除基本类型外的任意类型。当创建对象的时候，要合理估计一下需要多少字段。当调用 setField() 方法的时候，它将试图通过标识修改已有字段值，否则就建一个新的字段，并把值放入。如果空间不够了，将建立一个更长的数组，并把原来数组的元素复制进去。如果你试图为字段设置一个空值，将抛出一个 DynamicFieldsException 异常，它是通过使用 initCause() 方法把 NullPointerException 对象插入而建立的。
-
-至于返回值，setField() 将用 getField() 方法把此位置的旧值取出，这个操作可能会抛出 NoSuchFieldException 异常。如果客户端程序员调用了 getField() 方法，那么他就有责任处理这个可能抛出的 NoSuchFieldException 异常，但如果异常是从 setField0 方法抛出，这种情况将被视为编程错误，所以就使用接受 cause 参数的构造器把 NoSuchFieldException 异常转换为 RuntimeException 异常。
-
-主方法中的 catch 子句看起来不同 - 它使用相同的子句处理两种不同类型的异常，并结合“或（|）”符号。此 Java 7 功能有助于减少代码重复，并使你更容易指定要捕获的确切类型，而不是简单地捕获基本类型。你可以通过这种方式组合多种异常类型。
-
-# Java 标准异常
-Throwable 这个 Java 类被用来表示任何可以作为异常被抛出的类。Throwable 对象可分为两种类型（指从 Throwable 继承而得到的类型）：Error 用来表示编译时和系统错误（除特殊情况外，一般不用你关心）；Exception 是可以被抛出的基本类型，在 Java 类库、用户方法以及运行时故障中都可能抛出 Exception 型异常。所以 Java 程序员关心的基类型通常是 Exception。要想对异常有全面的了解，最好去浏览一下 HTML 格式的 Java 文档（可以从 java.sun.com 下载）。为了对不同的异常有个感性的认识，这么做是值得的。但很快你就会发现，这些异常除了名称外其实都差不多。同时，Java 中异常的数目在持续增加，所以在书中简单罗列它们毫无意义。所使用的第三方类库也可能会有自己的异常。对异常来说，关键是理解概念以及如何使用。
-
-异常的基本的概念是用名称代表发生的问题，并且异常的名称应该可以望文知意。异常并非全是在 java.lang 包里定义的；有些异常是用来支持其他像 util、net 和 io 这样的程序包，这些异常可以通过它们的完整名称或者从它们的父类中看出端倪。比如，所有的输入/输出异常都是从 java.io.IOException 继承而来的。
-
-### 特例：RuntimeException
-```java
-if(t == null)
-    throw new NullPointerException();
-```
-如果必须对传递给方法的每个引用都检查其是否为 null（因为无法确定调用者是否传入了非法引用），这听起来着实吓人。幸运的是，这不必由你亲自来做，它属于 Java 的标准运行时检测的一部分。如果对 null 引用进行调用，Java 会自动抛出 NullPointerException 异常，所以上述代码是多余的。
-
-属于运行时异常的类型有很多，它们会自动被 JVM 抛出，所以不必在异常说明中列出来。这些异常都是继承自 RuntimeException 。这构成了一组具有相同特征和行为的异常类型。并且，也不再需要在异常说明中声明方法将抛出 RuntimeException 类型的异常（或者任何从 RuntimeException 继承的异常），它们也被称为“不受检查异常”。这种异常属于错误，将被自动捕获，就不用你亲自动手了。要是自己去检查 RuntimeException 的话，代码就显得太混乱了。不过尽管通常不用捕获 RuntimeException 异常，但还是可以在代码中抛出 RuntimeException 类型的异常。
-
-RuntimeException 代表的是编程错误：
-1. 无法预料的错误
-比如从你控制范围之外传递进来的 null 引用
-2. 作为程序员，应该在代码中进行检查错误
-比如 ArrayIndexOutOfBoundsException，就得注意一下数组大小。在一个地方发生的异常，常常会在另一个地方导致错误。
-
-在这些情况下使用异常很有好处，它们能给调试带来便利。
-如果不捕获这种异常会发生什么事？
-因为编译器没有在这个问题上对异常说明进行强制检查，RuntimeException 类型的异常也许会穿越所有的执行路径直达 main() 方法，而不会被捕获。
-要明白到底发生了什么，可以试试下面的例子：
-```java
-public class NeverCaught {
-    static void f() {
-        throw new RuntimeException("From f()");
-    }
-    static void g() {
-        f();
-    }
-    public static void main(String[] args) {
-        g();
-    }
-}
-```
-
-输出结果为：
-
-```java
-___[ Error Output ]___
-Exception in thread "main" java.lang.RuntimeException:
-From f()
-at NeverCaught.f(NeverCaught.java:7)
-at NeverCaught.g(NeverCaught.java:10)
-at NeverCaught.main(NeverCaught.java:13)
-```
-
-如果 RuntimeException 没有被捕获而直达 main()，那么在程序退出前将调用异常的 printStackTrace() 方法。
-
-你会发现，RuntimeException 是个特例。对于这种异常，编译器无需异常说明，其输出被报告给了 System.err。
-
-只能在代码中忽略 RuntimeException 类型的异常，因为所有受检查类型异常的处理都是由编译器强制实施的。
-
-不应把 Java 的异常处理机制当成是单一用途的工具。它被设计用来处理一些烦人的运行时错误，这些错误往往是由代码控制能力之外的因素导致的；然而，它对于发现某些编译器无法检测到的编程错误，也是非常重要的。
-
-## 使用 finally 进行清理
-
-有一些代码片段，可能会希望无论 try 块中的异常是否抛出，它们都能得到执行。这通常适用于内存回收之外的情况（因为回收由垃圾回收器完成），为了达到这个效果，可以在异常处理程序后面加上 finally 子句。完整的异常处理程序看起来像这样：
-
-```java
-try {
-// The guarded region: Dangerous activities
-// that might throw A, B, or C
-} catch(A a1) {
-// Handler for situation A
-} catch(B b1) {
-// Handler for situation B
-} catch(C c1) {
-// Handler for situation C
-} finally {
-// Activities that happen every time
-}
-```
-
-为了证明 finally 子句总能运行，可以试试下面这个程序：
-
-```java
-// exceptions/FinallyWorks.java
-// The finally clause is always executed
-class ThreeException extends Exception {}
-public class FinallyWorks {
-    static int count = 0;
-    public static void main(String[] args) {
-        while(true) {
-            try {
-				// Post-increment is zero first time:
-                if(count++ == 0)
-                    throw new ThreeException();
-                System.out.println("No exception");
-            } catch(ThreeException e) {
-                System.out.println("ThreeException");
-            } finally {
-                System.out.println("In finally clause");
-                if(count == 2) break; // out of "while"
-            }
-        }
-    }
-}
-```
-
-输出为：
-
-```
-ThreeException
-In finally clause
-No exception
-In finally clause
-```
-
-可以从输出中发现，无论异常是否被抛出，finally 子句总能被执行。这个程序也给了我们一些思路，当 Java 中的异常不允许我们回到异常抛出的地点时，那么该如何应对呢？如果把 try 块放在循环里，就建立了一个“程序继续执行之前必须要达到”的条件。还可以加入一个 static 类型的计数器或者别的装置，使循环在放弃以前能尝试一定的次数。这将使程序的健壮性更上一个台阶。
-
-### finally 用来做什么？
-
-对于没有垃圾回收和析构函数自动调用机制的语言来说，finally 非常重要。它能使程序员保证：无论 try 块里发生了什么，内存总能得到释放。但 Java 有垃圾回收机制，所以内存释放不再是问题。而且，Java 也没有析构函数可供调用。那么，Java 在什么情况下才能用到 finally 呢？
-
-当要把除内存之外的资源恢复到它们的初始状态时，就要用到 finally 子句。这种需要清理的资源包括：已经打开的文件或网络连接，在屏幕上画的图形，甚至可以是外部世界的某个开关，如下面例子所示：
-
-```java
-// exceptions/Switch.java
-public class Switch {
-    private boolean state = false;
-    public boolean read() { return state; }
-    public void on() {
-        state = true;
-        System.out.println(this);
-    }
-    public void off() {
-        state = false;
-        System.out.println(this);
-    }
-    @Override
-    public String toString() {
-        return state ? "on" : "off";
-    }
-}
-// exceptions/OnOffException1.java
-public class OnOffException1 extends Exception {}
-// exceptions/OnOffException2.java
-public class OnOffException2 extends Exception {}
-// exceptions/OnOffSwitch.java
-// Why use finally?
-public class OnOffSwitch {
-    private static Switch sw = new Switch();
-    public static void f()
-            throws OnOffException1, OnOffException2 {}
-    public static void main(String[] args) {
-        try {
-            sw.on();
-			// Code that can throw exceptions...
-            f();
-            sw.off();
-        } catch(OnOffException1 e) {
-            System.out.println("OnOffException1");
-            sw.off();
-        } catch(OnOffException2 e) {
-            System.out.println("OnOffException2");
-            sw.off();
-        }
-    }
-}
-```
-
-输出为：
-
-```
-on
-off
-```
-
-程序的目的是要确保 main() 结束的时候开关必须是关闭的，所以在每个 try 块和异常处理程序的末尾都加入了对 sw.offo 方法的调用。但也可能有这种情况：异常被抛出，但没被处理程序捕获，这时 sw.off() 就得不到调用。但是有了 finally，只要把 try 块中的清理代码移放在一处即可：
-
-```java
-// exceptions/WithFinally.java
-// Finally Guarantees cleanup
-public class WithFinally {
-    static Switch sw = new Switch();
-    public static void main(String[] args) {
-        try {
-            sw.on();
-			// Code that can throw exceptions...
-            OnOffSwitch.f();
-        } catch(OnOffException1 e) {
-            System.out.println("OnOffException1");
-        } catch(OnOffException2 e) {
-            System.out.println("OnOffException2");
-        } finally {
-            sw.off();
-        }
-    }
-}
-```
-
-输出为：
-
-```java
-on
-off
-```
-
-这里 sw.off() 被移到一处，并且保证在任何情况下都能得到执行。
-
-甚至在异常没有被当前的异常处理程序捕获的情况下，异常处理机制也会在跳到更高一层的异常处理程序之前，执行 finally 子句：
-
-```java
-// exceptions/AlwaysFinally.java
-// Finally is always executed
-class FourException extends Exception {}
-public class AlwaysFinally {
-    public static void main(String[] args) {
-        System.out.println("Entering first try block");
-        try {
-            System.out.println("Entering second try block");
-            try {
-                throw new FourException();
-            } finally {
-                System.out.println("finally in 2nd try block");
-            }
-        } catch(FourException e) {
-            System.out.println(
-                    "Caught FourException in 1st try block");
-        } finally {
-            System.out.println("finally in 1st try block");
-        }
-    }
-}
-```
-
-输出为：
-
-```java
-Entering first try block
-Entering second try block
-finally in 2nd try block
-Caught FourException in 1st try block
-finally in 1st try block
-```
-
-当涉及 break 和 continue 语句的时候，finally 子句也会得到执行。请注意，如果把 finally 子句和带标签的 break 及 continue 配合使用，在 Java 里就没必要使用 goto 语句了。
-
-### 在 return 中使用 finally
-
-因为 finally 子句总是会执行，所以可以从一个方法内的多个点返回，仍然能保证重要的清理工作会执行：
-
-```java
-// exceptions/MultipleReturns.java
-public class MultipleReturns {
-    public static void f(int i) {
-        System.out.println(
-                "Initialization that requires cleanup");
-        try {
-            System.out.println("Point 1");
-            if(i == 1) return;
-            System.out.println("Point 2");
-            if(i == 2) return;
-            System.out.println("Point 3");
-            if(i == 3) return;
-            System.out.println("End");
-            return;
-        } finally {
-            System.out.println("Performing cleanup");
-        }
-    }
-    public static void main(String[] args) {
-        for(int i = 1; i <= 4; i++)
-            f(i);
-    }
-}
-```
-
-输出为：
-
-```java
-Initialization that requires cleanup
-Point 1
-Performing cleanup
-Initialization that requires cleanup
-Point 1
-Point 2
-Performing cleanup
-Initialization that requires cleanup
-Point 1
-Point 2
-Point 3
-Performing cleanup
-Initialization that requires cleanup
-Point 1
-Point 2
-Point 3
-End
-Performing cleanup
-```
-
-从输出中可以看出，从何处返回无关紧要，finally 子句永远会执行。
-
-### 缺憾：异常丢失
-
-遗憾的是，Java 的异常实现也有瑕疵。异常作为程序出错的标志，决不应该被忽略，但它还是有可能被轻易地忽略。用某些特殊的方式使用 finally 子句，就会发生这种情况：
-
-```java
-// exceptions/LostMessage.java
-// How an exception can be lost
-class VeryImportantException extends Exception {
-    @Override
-    public String toString() {
-        return "A very important exception!";
-    }
-}
-class HoHumException extends Exception {
-    @Override
-    public String toString() {
-        return "A trivial exception";
-    }
-}
-public class LostMessage {
-    void f() throws VeryImportantException {
-        throw new VeryImportantException();
-    }
-    void dispose() throws HoHumException {
-        throw new HoHumException();
-    }
-    public static void main(String[] args) {
-        try {
-            LostMessage lm = new LostMessage();
-            try {
-                lm.f();
-            } finally {
-                lm.dispose();
-            }
-        } catch(VeryImportantException | HoHumException e) {
-            System.out.println(e);
-        }
-    }
-}
-```
-
-输出为：
-
-```
-A trivial exception
-```
-
-从输出中可以看到，VeryImportantException 不见了，它被 finally 子句里的 HoHumException 所取代。这是相当严重的缺陷，因为异常可能会以一种比前面例子所示更微妙和难以察党的方式完全丢失。相比之下，C++把“前一个异常还没处理就抛出下一个异常”的情形看成是糟糕的编程错误。也许在 Java 的未来版本中会修正这个问题（另一方面，要把所有抛出异常的方法，如上例中的 dispose() 方法，全部打包放到 try-catch 子句里面）。
-
-一种更加简单的丢失异常的方式是从 finally 子句中返回：
-
-```java
-// exceptions/ExceptionSilencer.java
-public class ExceptionSilencer {
-    public static void main(String[] args) {
-        try {
-            throw new RuntimeException();
-        } finally {
-            // Using 'return' inside the finally block
-            // will silence any thrown exception.
-            return;
-        }
-    }
-}
-```
-
-如果运行这个程序，就会看到即使方法里抛出了异常，它也不会产生任何输出。
-
-<!-- Exception Restrictions -->
-
-## 异常限制
-
-当覆盖方法的时候，只能抛出在基类方法的异常说明里列出的那些异常。这个限制很有用，因为这意味着，若当基类使用的代码应用到其派生类对象的时候，一样能够工作（当然，这是面向对象的基本概念），异常也不例外。
-
-下面例子演示了这种（在编译时）施加在异常上面的限制：
-
-```java
-// exceptions/StormyInning.java
-// Overridden methods can throw only the exceptions
-// specified in their base-class versions, or exceptions
-// derived from the base-class exceptions
-class BaseballException extends Exception {}
-class Foul extends BaseballException {}
-class Strike extends BaseballException {}
-abstract class Inning {
-    Inning() throws BaseballException {}
-    public void event() throws BaseballException {
-// Doesn't actually have to throw anything
-    }
-    public abstract void atBat() throws Strike, Foul;
-    public void walk() {} // Throws no checked exceptions
-}
-class StormException extends Exception {}
-class RainedOut extends StormException {}
-class PopFoul extends Foul {}
-interface Storm {
-    void event() throws RainedOut;
-    void rainHard() throws RainedOut;
-}
-public class StormyInning extends Inning implements Storm {
-    // OK to add new exceptions for constructors, but you
-// must deal with the base constructor exceptions:
-    public StormyInning()
-            throws RainedOut, BaseballException {}
-    public StormyInning(String s)
-            throws BaseballException {}
-    // Regular methods must conform to base class:
-//- void walk() throws PopFoul {} //Compile error
-// Interface CANNOT add exceptions to existing
-// methods from the base class:
-//- public void event() throws RainedOut {}
-// If the method doesn't already exist in the
-// base class, the exception is OK:
-    @Override
-    public void rainHard() throws RainedOut {}
-    // You can choose to not throw any exceptions,
-// even if the base version does:
-    @Override
-    public void event() {}
-    // Overridden methods can throw inherited exceptions:
-    @Override
-    public void atBat() throws PopFoul {}
-    public static void main(String[] args) {
-        try {
-            StormyInning si = new StormyInning();
-            si.atBat();
-        } catch(PopFoul e) {
-            System.out.println("Pop foul");
-        } catch(RainedOut e) {
-            System.out.println("Rained out");
-        } catch(BaseballException e) {
-            System.out.println("Generic baseball exception");
-        }
-// Strike not thrown in derived version.
-        try {
-// What happens if you upcast?
-            Inning i = new StormyInning();
-            i.atBat();
-// You must catch the exceptions from the
-// base-class version of the method:
-        } catch(Strike e) {
-            System.out.println("Strike");
-        } catch(Foul e) {
-            System.out.println("Foul");
-        } catch(RainedOut e) {
-            System.out.println("Rained out");
-        } catch(BaseballException e) {
-            System.out.println("Generic baseball exception");
-        }
-    }
-}
-```
-
-在 Inning 类中，可以看到构造器和 event() 方法都声明将抛出异常，但实际上没有抛出。这种方式使你能强制用户去捕获可能在覆盖后的 event() 版本中增加的异常，所以它很合理。这对于抽象方法同样成立，比如 atBat()。
-
-接口 Storm 包含了一个在 Inning 中定义的方法 event() 和一个不在 Inning 中定义的方法 rainHard()。这两个方法都抛出新的异常 RainedOut，如果 StormyInning 类在扩展 Inning 类的同时又实现了 Storm 接口，那么 Storm 里的 event() 方法就不能改变在 Inning 中的 event（方法的异常接口。否则的话，在使用基类的时候就不能判断是否捕获了正确的异常，所以这也很合理。当然，如果接口里定义的方法不是来自于基类，比如 rainHard()，那么此方法抛出什么样的异常都没有问题。
-
-异常限制对构造器不起作用。你会发现 StormyInning 的构造器可以抛出任何异常，而不必理会基类构造器所抛出的异常。然而，因为基类构造器必须以这样或那样的方式被调用（这里默认构造器将自动被调用），派生类构造器的异常说明必须包含基类构造器的异常说明。
-
-派生类构造器不能捕获基类构造器抛出的异常。
-
-StormyInning.walk() 不能通过编译是因为它抛出了异常，而 Inning.walk() 并没有声明此异常。如果编译器允许这么做的话，就可以在调用 Inning.walk() 的时候不用做异常处理了，而且当把它替换成 Inning 的派生类的对象时，这个方法就有可能会抛出异常，于是程序就失灵了。通过强制派生类遵守基类方法的异常说明，对象的可替换性得到了保证。
-
-覆盖后的 event() 方法表明，派生类方法可以不抛出任何异常，即使它是基类所定义的异常。同样这是因为，假使基类的方法会抛出异常，这样做也不会破坏已有的程序，所以也没有问题。类似的情况出现在 atBat() 身上，它抛出的是 PopFoul，这个异常是继承自“会被基类的 atBat() 抛出”的 Foul，这样，如果你写的代码是同 Inning 打交道，并且调用了它的 atBat() 的话，那么肯定能捕获 Foul，而 PopFoul 是由 Foul 派生出来的，因此异常处理程序也能捕获 PopFoul。
-
-最后一个值得注意的地方是 main()。这里可以看到，如果处理的刚好是 Stormylnning 对象的话，编译器只会强制要求你捕获这个类所抛出的异常。但是如果将它向上转型成基类型，那么编译器就会（正确地）要求你捕获基类的异常。所有这些限制都是为了能产生更为强壮的异常处理代码。
-
-尽管在继承过程中，编译器会对异常说明做强制要求，但异常说明本身并不属于方法类型的一部分，方法类型是由方法的名字与参数的类型组成的。因此，不能基于异常说明来重载方法。此外，一个出现在基类方法的异常说明中的异常，不一定会出现在派生类方法的异常说明里。这点同继承的规则明显不同，在继承中，基类的方法必须出现在派生类里，换句话说，在继承和覆盖的过程中，某个特定方法的“异常说明的接口”不是变大了而是变小了——这恰好和类接口在继承时的情形相反。
-
-<!-- Constructors -->
-
-## 构造器
-
-有一点很重要，即你要时刻询问自己“如果异常发生了，所有东西能被正确的清理吗？"尽管大多数情况下是非常安全的，但涉及构造器时，问题就出现了。构造器会把对象设置成安全的初始状态，但还会有别的动作，比如打开一个文件，这样的动作只有在对象使用完毕并且用户调用了特殊的清理方法之后才能得以清理。如果在构造器内抛出了异常，这些清理行为也许就不能正常工作了。这意味着在编写构造器时要格外细心。
-
-你也许会认为使用 finally 就可以解决问题。但问题并非如此简单，因为 finally 会每次都执行清理代码。如果构造器在其执行过程中半途而废，也许该对象的某些部分还没有被成功创建，而这些部分在 finaly 子句中却是要被清理的。
-
-在下面的例子中，建立了一个 InputFile 类，它能打开一个文件并且每次读取其中的一行。这里使用了 Java 标准输入/输出库中的 FileReader 和 BufferedReader 类（将在 [附录：I/O 流 ](./Appendix-IO-Streams.md) 中讨论），这些类的基本用法很简单，你应该很容易明白：
-
-```java
-// exceptions/InputFile.java
-// Paying attention to exceptions in constructors
-import java.io.*;
-public class InputFile {
-    private BufferedReader in;
-    public InputFile(String fname) throws Exception {
-        try {
-            in = new BufferedReader(new FileReader(fname));
-            // Other code that might throw exceptions
-        } catch(FileNotFoundException e) {
-            System.out.println("Could not open " + fname);
-            // Wasn't open, so don't close it
-            throw e;
-        } catch(Exception e) {
-            // All other exceptions must close it
-            try {
-                in.close();
-            } catch(IOException e2) {
-                System.out.println("in.close() unsuccessful");
-            }
-            throw e; // Rethrow
-        } finally {
-        // Don't close it here!!!
-        }
-    }
-    public String getLine() {
-        String s;
-        try {
-            s = in.readLine();
-        } catch(IOException e) {
-            throw new RuntimeException("readLine() failed");
-        }
-        return s;
-    }
-    public void dispose() {
-        try {
-            in.close();
-            System.out.println("dispose() successful");
-        } catch(IOException e2) {
-            throw new RuntimeException("in.close() failed");
-        }
-    }
-}
-```
-
-InputFile 的构造器接受字符串作为参数，该字符串表示所要打开的文件名。在 try 块中，会使用此文件名建立 FileReader 对象。FileReader 对象本身用处并不大，但可以用它来建立 BufferedReader 对象。注意，使用 InputFile 的好处之一是把两步操作合而为一。
-
-如果 FileReader 的构造器失败了，将抛出 FileNotFoundException 异常。对于这个异常，并不需要关闭文件，因为这个文件还没有被打开。而任何其他捕获异常的 catch 子句必须关闭文件，因为在它们捕获到异常之时，文件已经打开了（当然，如果还有其他方法能抛出 FileNotFoundException，这个方法就显得有些投机取巧了。这时，通常必须把这些方法分别放到各自的 try 块里），close() 方法也可能会抛出异常，所以尽管它已经在另一个 catch 子句块里了，还是要再用一层 try-catch，这对 Java 编译器而言只不过是多了一对花括号。在本地做完处理之后，异常被重新抛出，对于构造器而言这么做是很合适的，因为你总不希望去误导调用方，让他认为“这个对象已经创建完毕，可以使用了”。
-
-在本例中，由于 finally 会在每次完成构造器之后都执行一遍，因此它实在不该是调用 close() 关闭文件的地方。我们希望文件在 InputFlle 对象的整个生命周期内都处于打开状态。
-
-getLine() 方法会返回表示文件下一行内容的字符串。它调用了能抛出异常的 readLine()，但是这个异常已经在方法内得到处理，因此 getLine() 不会抛出任何异常。在设计异常时有一个问题：应该把异常全部放在这一层处理；还是先处理一部分，然后再向上层抛出相同的（或新的）异常；又或者是不做任何处理直接向上层抛出。如果用法恰当的话，直接向上层抛出的确能简化编程。在这里，getLine() 方法将异常转换为 RuntimeException，表示一个编程错误。
-
-用户在不再需要 InputFile 对象时，就必须调用 dispose() 方法，这将释放 BufferedReader 和/或 FileReader 对象所占用的系统资源（比如文件句柄），在使用完 InputFile 对象之前是不会调用它的。可能你会考虑把上述功能放到 finalize() 里面，但我在 [封装](./Housekeeping.md) 讲过，你不知道 finalize() 会不会被调用（即使能确定它将被调用，也不知道在什么时候调用），这也是 Java 的缺陷：除了内存的清理之外，所有的清理都不会自动发生。所以必须告诉客户端程序员，这是他们的责任。
-
-对于在构造阶段可能会抛出异常，并且要求清理的类，最安全的使用方式是使用嵌套的 try 子句：
-
-```java
-// exceptions/Cleanup.java
-// Guaranteeing proper cleanup of a resource
-public class Cleanup {
-    public static void main(String[] args) {
-        try {
-            InputFile in = new InputFile("Cleanup.java");
-            try {
-                String s;
-                int i = 1;
-                while((s = in.getLine()) != null)
-                    ; // Perform line-by-line processing here...
-            } catch(Exception e) {
-                System.out.println("Caught Exception in main");
-                e.printStackTrace(System.out);
-            } finally {
-                in.dispose();
-            }
-        } catch(Exception e) {
-            System.out.println(
-                    "InputFile construction failed");
-        }
-    }
-}
-```
-
-输出为：
-
-```
-dispose() successful
-```
-
-请仔细观察这里的逻辑：对 InputFile 对象的构造在其自己的 try 语句块中有效，如果构造失败，将进入外部的 catch 子句，而 dispose() 方法不会被调用。但是，如果构造成功，我们肯定想确保对象能够被清理，因此在构造之后立即创建了一个新的 try 语句块。执行清理的 finally 与内部的 try 语句块相关联。在这种方式中，finally 子句在构造失败时是不会执行的，而在构造成功时将总是执行。
-
-这种通用的清理惯用法在构造器不抛出任何异常时也应该运用，其基本规则是：在创建需要清理的对象之后，立即进入一个 try-finally 语句块：
-
-```java
-// exceptions/CleanupIdiom.java
-// Disposable objects must be followed by a try-finally
-class NeedsCleanup { // Construction can't fail
-    private static long counter = 1;
-    private final long id = counter++;
-    public void dispose() {
-        System.out.println(
-                "NeedsCleanup " + id + " disposed");
-    }
-}
-class ConstructionException extends Exception {}
-class NeedsCleanup2 extends NeedsCleanup {
-    // Construction can fail:
-    NeedsCleanup2() throws ConstructionException {}
-}
-public class CleanupIdiom {
-    public static void main(String[] args) {
-        // [1]:
-        NeedsCleanup nc1 = new NeedsCleanup();
-        try {
-        // ...
-        } finally {
-            nc1.dispose();
-        }
-        // [2]:
-        // If construction cannot fail,
-        // you can group objects:
-        NeedsCleanup nc2 = new NeedsCleanup();
-        NeedsCleanup nc3 = new NeedsCleanup();
-        try {
-        // ...
-        } finally {
-            nc3.dispose(); // Reverse order of construction
-            nc2.dispose();
-        }
-        // [3]:
-        // If construction can fail you must guard each one:
-        try {
-            NeedsCleanup2 nc4 = new NeedsCleanup2();
-            try {
-                NeedsCleanup2 nc5 = new NeedsCleanup2();
-                try {
-                // ...
-                } finally {
-                    nc5.dispose();
-                }
-            } catch(ConstructionException e) { // nc5 const.
-                System.out.println(e);
-            } finally {
-                nc4.dispose();
-            }
-        } catch(ConstructionException e) { // nc4 const.
-            System.out.println(e);
-        }
-    }
-}
-```
-
-输出为：
-
-```
-NeedsCleanup 1 disposed
-NeedsCleanup 3 disposed
-NeedsCleanup 2 disposed
-NeedsCleanup 5 disposed
-NeedsCleanup 4 disposed
-```
-
-- [1] 相当简单，遵循了在可去除对象之后紧跟 try-finally 的原则。如果对象构造不会失败，就不需要任何 catch。
-- [2] 为了构造和清理，可以看到将具有不能失败的构造器的对象分组在一起。
-- [3] 展示了如何处理那些具有可以失败的构造器，且需要清理的对象。为了正确处理这种情况，事情变得很棘手，因为对于每一个构造，都必须包含在其自己的 try-finally 语句块中，并且每一个对象构造必须都跟随一个 try-finally 语句块以确保清理。
-
-本例中的异常处理的棘手程度，对于应该创建不能失败的构造器是一个有力的论据，尽管这么做并非总是可行。
-
-注意，如果 dispose() 可以抛出异常，那么你可能需要额外的 try 语句块。基本上，你应该仔细考虑所有的可能性，并确保正确处理每一种情况。
-
-<!-- Try-With-Resources -->
-
-## Try-With-Resources 用法
-
-上一节的内容可能让你有些头疼。在考虑所有可能失败的方法时，找出放置所有 try-catch-finally 块的位置变得令人生畏。确保没有任何故障路径，使系统远离不稳定状态，这非常具有挑战性。
-
-InputFile.java 是一个特别棘手的情况，因为文件被打开（包含所有可能的异常），然后它在对象的生命周期中保持打开状态。每次调用 getLine() 都会导致异常，因此可以调用 dispose() 方法。这是一个很好的例子，因为它显示了事物的混乱程度。它还表明你应该尝试最好不要那样设计代码（当然，你经常会遇到这种你无法选择的代码设计的情况，因此你必须仍然理解它）。
-
-InputFile.java 一个更好的实现方式是如果构造函数读取文件并在内部缓冲它 —— 这样，文件的打开，读取和关闭都发生在构造函数中。或者，如果读取和存储文件不切实际，你可以改为生成 Stream。理想情况下，你可以设计成如下的样子：
-
-```java
-// exceptions/InputFile2.java
-import java.io.*;
-import java.nio.file.*;
-import java.util.stream.*;
-public class InputFile2 {
-    private String fname;
-
-    public InputFile2(String fname) {
-        this.fname = fname;
-    }
-
-    public Stream<String> getLines() throws IOException {
-        return Files.lines(Paths.get(fname));
-    }
-
-    public static void
-    main(String[] args) throws IOException {
-        new InputFile2("InputFile2.java").getLines()
-                .skip(15)
-                .limit(1)
-                .forEach(System.out::println);
-    }
-}
-```
-
-输出为：
-
-```
-main(String[] args) throws IOException {
-```
-
-现在，getLines() 全权负责打开文件并创建 Stream。
-
-你不能总是轻易地回避这个问题。有时会有以下问题：
-
-1. 需要资源清理
-2. 需要在特定的时刻进行资源清理，比如你离开作用域的时候（在通常情况下意味着通过异常进行清理）。
-
-一个常见的例子是 jav.io.FileInputstream（将会在 [附录：I/O 流 ](./Appendix-IO-Streams.md) 中提到）。要正确使用它，你必须编写一些棘手的样板代码：
-
-```java
-// exceptions/MessyExceptions.java
-import java.io.*;
-public class MessyExceptions {
-    public static void main(String[] args) {
-        InputStream in = null;
-        try {
-            in = new FileInputStream(
-                    new File("MessyExceptions.java"));
-            int contents = in.read();
-            // Process contents
-        } catch(IOException e) {
-            // Handle the error
-        } finally {
-            if(in != null) {
-                try {
-                    in.close();
-                } catch(IOException e) {
-                    // Handle the close() error
-                }
-            }
-        }
-    }
-}
-```
-
-当 finally 子句有自己的 try 块时，感觉事情变得过于复杂。
-
-幸运的是，Java 7 引入了 try-with-resources 语法，它可以非常清楚地简化上面的代码：
-
-```java
-// exceptions/TryWithResources.java
-import java.io.*;
-public class TryWithResources {
-    public static void main(String[] args) {
-        try(
-                InputStream in = new FileInputStream(
-                        new File("TryWithResources.java"))
-        ) {
-            int contents = in.read();
-            // Process contents
-        } catch(IOException e) {
-            // Handle the error
-        }
-    }
-}
-```
-
-在 Java 7 之前，try 总是后面跟着一个 {，但是现在可以跟一个带括号的定义 - 这里是我们创建的 FileInputStream 对象。括号内的部分称为资源规范头（resource specification header）。现在可用于整个 try 块的其余部分。更重要的是，无论你如何退出 try 块（正常或异常），都会执行前一个 finally 子句的等价物，但不会编写那些杂乱而棘手的代码。这是一项重要的改进。
-
-它是如何工作的？在 try-with-resources 定义子句中创建的对象（在括号内）必须实现 java.lang.AutoCloseable 接口，这个接口有一个方法：close()。当在 Java 7 中引入 AutoCloseable 时，许多接口和类被修改以实现它；查看 Javadocs 中的 AutoCloseable，可以找到所有实现该接口的类列表，其中包括 Stream 对象：
-
-```java
-// exceptions/StreamsAreAutoCloseable.java
-import java.io.*;
-import java.nio.file.*;
-import java.util.stream.*;
-public class StreamsAreAutoCloseable {
-    public static void
-    main(String[] args) throws IOException{
-        try(
-                Stream<String> in = Files.lines(
-                        Paths.get("StreamsAreAutoCloseable.java"));
-                PrintWriter outfile = new PrintWriter(
-                        "Results.txt"); // [1]
-        ) {
-            in.skip(5)
-                    .limit(1)
-                    .map(String::toLowerCase)
-                    .forEachOrdered(outfile::println);
-        } // [2]
-    }
-}
-```
-
-- [1] 你在这里可以看到其他的特性：资源规范头中可以包含多个定义，并且通过分号进行分割（最后一个分号是可选的）。规范头中定义的每个对象都会在 try 语句块运行结束之后调用 close() 方法。
-- [2] try-with-resources 里面的 try 语句块可以不包含 catch 或者 finally 语句而独立存在。在这里，IOException 被 main() 方法抛出，所以这里并不需要在 try 后面跟着一个 catch 语句块。
-
-Java 5 中的 Closeable 已经被修改，修改之后的接口继承了 AutoCloseable 接口。所以所有实现了 Closeable 接口的对象，都支持了  try-with-resources 特性。
-
-### 揭示细节
-
-为了研究 try-with-resources 的基本机制，我们将创建自己的 AutoCloseable 类：
-
-```java
-// exceptions/AutoCloseableDetails.java
-class Reporter implements AutoCloseable {
-    String name = getClass().getSimpleName();
-    Reporter() {
-        System.out.println("Creating " + name);
-    }
-    public void close() {
-        System.out.println("Closing " + name);
-    }
-}
-class First extends Reporter {}
-class Second extends Reporter {}
-public class AutoCloseableDetails {
-    public static void main(String[] args) {
-        try(
-                First f = new First();
-                Second s = new Second()
-        ) {
-        }
-    }
-}
-```
-
-输出为：
-
-```
-Creating First
-Creating Second
-Closing Second
-Closing First
-```
-
-退出 try 块会调用两个对象的 close() 方法，并以与创建顺序相反的顺序关闭它们。顺序很重要，因为在此配置中，Second 对象可能依赖于 First 对象，因此如果 First 在第 Second 关闭时已经关闭。 Second 的 close() 方法可能会尝试访问 First 中不再可用的某些功能。
-
-假设我们在资源规范头中定义了一个不是 AutoCloseable 的对象
-
-```java
-// exceptions/TryAnything.java
-// {WillNotCompile}
-class Anything {}
-public class TryAnything {
-    public static void main(String[] args) {
-        try(
-                Anything a = new Anything()
-        ) {
-        }
-    }
-}
-```
-
-正如我们所希望和期望的那样，Java 不会让我们这样做，并且出现编译时错误。
-
-如果其中一个构造函数抛出异常怎么办？
-
-```java
-// exceptions/ConstructorException.java
-class CE extends Exception {}
-class SecondExcept extends Reporter {
-    SecondExcept() throws CE {
-        super();
-        throw new CE();
-    }
-}
-public class ConstructorException {
-    public static void main(String[] args) {
-        try(
-                First f = new First();
-                SecondExcept s = new SecondExcept();
-                Second s2 = new Second()
-        ) {
-            System.out.println("In body");
-        } catch(CE e) {
-            System.out.println("Caught: " + e);
-        }
-    }
-}
-```
-
-输出为：
-
-```
-Creating First
-Creating SecondExcept
-Closing First
-Caught: CE
-```
-
-现在资源规范头中定义了 3 个对象，中间的对象抛出异常。因此，编译器强制我们使用 catch 子句来捕获构造函数异常。这意味着资源规范头实际上被 try 块包围。
-
-正如预期的那样，First 创建时没有发生意外，SecondExcept 在创建期间抛出异常。请注意，不会为 SecondExcept 调用 close()，因为如果构造函数失败，则无法假设你可以安全地对该对象执行任何操作，包括关闭它。由于 SecondExcept 的异常，Second 对象实例 s2 不会被创建，因此也不会有清除事件发生。
-
-如果没有构造函数抛出异常，但你可能会在 try 的主体中获取它们，则再次强制你实现 catch 子句：
-
-```java
-// exceptions/BodyException.java
-class Third extends Reporter {}
-public class BodyException {
-    public static void main(String[] args) {
-        try(
-                First f = new First();
-                Second s2 = new Second()
-        ) {
-            System.out.println("In body");
-            Third t = new Third();
-            new SecondExcept();
-            System.out.println("End of body");
-        } catch(CE e) {
-            System.out.println("Caught: " + e);
-        }
-    }
-}
-```
-
-输出为：
-
-```java
-Creating First
-Creating Second
-In body
-Creating Third
-Creating SecondExcept
-Closing Second
-Closing First
-Caught: CE
-```
-
-请注意，第 3 个对象永远不会被清除。那是因为它不是在资源规范头中创建的，所以它没有被保护。这很重要，因为 Java 在这里没有以警告或错误的形式提供指导，因此像这样的错误很容易漏掉。实际上，如果依赖某些集成开发环境来自动重写代码，以使用 try-with-resources 特性，那么它们（在撰写本文时）通常只会保护它们遇到的第一个对象，而忽略其余的对象。
-
-最后，让我们看一下抛出异常的 close() 方法：
-
-```java
-// exceptions/CloseExceptions.java
-class CloseException extends Exception {}
-class Reporter2 implements AutoCloseable {
-    String name = getClass().getSimpleName();
-    Reporter2() {
-        System.out.println("Creating " + name);
-    }
-    public void close() throws CloseException {
-        System.out.println("Closing " + name);
-    }
-}
-class Closer extends Reporter2 {
-    @Override
-    public void close() throws CloseException {
-        super.close();
-        throw new CloseException();
-    }
-}
-public class CloseExceptions {
-    public static void main(String[] args) {
-        try(
-                First f = new First();
-                Closer c = new Closer();
-                Second s = new Second()
-        ) {
-            System.out.println("In body");
-        } catch(CloseException e) {
-            System.out.println("Caught: " + e);
-        }
-    }
-}
-```
-
-输出为：
-
-```
-Creating First
-Creating Closer
-Creating Second
-In body
-Closing Second
-Closing Closer
-Closing First
-Caught: CloseException
-```
-
-从技术上讲，我们并没有被迫在这里提供一个 catch 子句；你可以通过 **main() throws CloseException** 的方式来报告异常。但 catch 子句是放置错误处理代码的典型位置。
-
-请注意，因为所有三个对象都已创建，所以它们都以相反的顺序关闭 - 即使 Closer 也是如此。 close() 抛出异常。当你想到它时，这就是你想要发生的事情，但是如果你必须自己编写所有这些逻辑，那么你可能会错过一些错误。想象一下所有代码都在那里，程序员没有考虑清理的所有含义，并且做错了。因此，应始终尽可能使用 try-with-resources。它有助于实现该功能，使得生成的代码更清晰，更易于理解。
-
-<!-- Exception Matching -->
-
-## 异常匹配
-
-抛出异常的时候，异常处理系统会按照代码的书写顺序找出“最近”的处理程序。找到匹配的处理程序之后，它就认为异常将得到处理，然后就不再继续查找。
-
-查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配。派生类的对象也可以匹配其基类的处理程序，就像这样：
-
-```java
-// exceptions/Human.java
-// Catching exception hierarchies
-class Annoyance extends Exception {}
-class Sneeze extends Annoyance {}
-public class Human {
-    public static void main(String[] args) {
-        // Catch the exact type:
-        try {
-            throw new Sneeze();
-        } catch(Sneeze s) {
-            System.out.println("Caught Sneeze");
-        } catch(Annoyance a) {
-            System.out.println("Caught Annoyance");
-        }
-        // Catch the base type:
-        try {
-            throw new Sneeze();
-        } catch(Annoyance a) {
-            System.out.println("Caught Annoyance");
-        }
-    }
-}
-```
-
-输出为：
-
-```java
-Caught Sneeze
-Caught Annoyance
-```
-
-Sneeze 异常会被第一个匹配的 catch 子句捕获，也就是程序里的第一个。然而如果将这个 catch 子句删掉，只留下 Annoyance 的 catch 子句，该程序仍然能运行，因为这次捕获的是 Sneeze 的基类。换句话说，catch（Annoyance a）会捕获 Annoyance 以及所有从它派生的异常。这一点非常有用，因为如果决定在方法里加上更多派生异常的话，只要客户程序员捕获的是基类异常，那么它们的代码就无需更改。
-
-如果把捕获基类的 catch 子句放在最前面，以此想把派生类的异常全给“屏蔽”掉，就像这样：
-
-```java
-try {
-    throw new Sneeze();
-} catch(Annoyance a) {
-    // ...
-} catch(Sneeze s) {
-    // ...
-}
-```
-
-此时，编译器会发现 Sneeze 的 catch 子句永远得不到执行，因此它会向你报告错误。
-
-<!-- Alternative Approaches -->
-
-## 其他可选方式
-
-异常处理系统就像一个活门（trap door），使你能放弃程序的正常执行序列。当“异常情形”
-发生的时候，正常的执行已变得不可能或者不需要了，这时就要用到这个“活门"。异常代表了当前方法不能继续执行的情形。开发异常处理系统的原因是，如果为每个方法所有可能发生的错误都进行处理的话，任务就显得过于繁重了，程序员也不愿意这么做。结果常常是将错误忽格。应该注意到，开发异常处理的初衷是为了方便程序员处理错误。
-
-异常处理的一个重要原则是“只有在你知道如何处理的情况下才捕获异常"。实际上，异常处理的一个重要目标就是把错误处理的代码同错误发生的地点相分离。这使你能在一段代码中专注于要完成的事情，至于如何处理错误，则放在另一段代码中完成。这样一来，主要代码就不会与错误处理逻辑混在一起，也更容易理解和维护。通过允许一个处理程序去处理多个出错点，异常处理还使得错误处理代码的数量趋于减少。
-
-“被检查的异常”使这个问题变得有些复杂，因为它们强制你在可能还没准备好处理错误的时候被迫加上 catch 子句，这就导致了吞食则有害（harmful if swallowed）的问题：
-
-```java
-try {
-    // ... to do something useful
-} catch(ObligatoryException e) {} // Gulp!
-```
-
-程序员们只做最简单的事情（包括我自己，在本书第 1 版中也有这个问题），常常是无意中"吞食”了异常，然而一旦这么做，虽然能通过编译，但除非你记得复查并改正代码，否则异常将会丢失。异常确实发生了，但“吞食”后它却完全消失了。因为编译器强迫你立刻写代码来处理异常，所以这种看起来最简单的方法，却可能是最糟糕的做法。
-
-当我意识到犯了这么大一个错误时，简直吓了一大跳，在本书第 2 版中，我在处理程序里通过打印栈轨迹的方法“修补”了这个问题（本章中的很多例子还是使用了这种方法，看起来还是比较合适的），虽然这样可以跟踪异常的行为，但是仍旧不知道该如何处理异常。这一节，我们来研究一下“被检查的异常”及其并发症，以及采用什么方法来解决这些问题。
-
-这个话题看起来简单，但实际上它不仅复杂，更重要的是还非常多变。总有人会顽固地坚持自己的立场，声称正确答案（也是他们的答案）是显而易见的。我觉得之所以会有这种观点，是因为我们使用的工具已经不是 ANS1 标准出台前的像 C 那样的弱类型语言，而是像 C++ 和 Java 这样的“强静态类型语言”（也就是编译时就做类型检查的语言），这是前者所无法比拟的。当刚开始这种转变的时候（就像我一样），会觉得它带来的好处是那样明显，好像类型检查总能解决所有的问题。在此，我想结合我自己的认识过程，告诉读者我是怎样从对类型检查的绝对迷信变成持怀疑态度的，当然，很多时候它还是非常有用的，但是当它挡住我们的去路并成为障碍的时候，我们就得跨过去。只是这条界限往往并不是很清晰（我最喜欢的一句格言是：所有模型都是错误的，但有些是能用的）。
-
-### 历史
-
-异常处理起源于 PL/1 和 Mesa 之类的系统中，后来又出现在 CLU、Smalltalk、Modula-3、Ada、Eiffel、C++、Python、Java 以及后 Java 语言 Ruby 和 C# 中。Java 的设计和 C++ 很相似，只是 Java 的设计者去掉了一些他们认为 C++设计得不好的东西。
-
-为了能向程序员提供一个他们更愿意使用的错误处理和恢复的框架，异常处理机制很晚才被加入 C++ 标准化过程中，这是由 C++ 的设计者 Bjarne Stroustrup 所倡议。C++ 的异常模型主要借鉴了 CLU 的做法。然而，当时其他语言已经支持异常处理了：包括 Ada、Smalltalk（两者都有异常处理，但是都没有异常说明），以及 Modula-3（它既有异常处理也有异常说明）。
-
-Liskov 和 Snyder 在他们的一篇讨论该主题的独创性论文中指出，用瞬时风格（transient fashion）报告错误的语言（如 C 中）有一个主要缺陷，那就是：
-
-> “....每次调用的时候都必须执行条件测试，以确定会产生何种结果。这使程序难以阅读并且有可能降低运行效率，因此程序员们既不愿意指出，也不愿意处理异常。”
-
-因此，异常处理的初衷是要消除这种限制，但是我们又从 Java 的“被检查的异常”中看到了这种代码。他们继续写道：
-
-> “....要求程序员把异常处理程序的代码文本附接到会引发异常的调用上，这会降低程序的可读性，使得程序的正常思路被异常处理给破坏了。”
-
-C++ 中异常的设计参考了 CLU 方式。Stroustrup 声称其目标是减少恢复错误所需的代码。我想他这话是说给那些通常情况下都不写 C 的错误处理的程序员们听的，因为要把那么多代码放到那么多地方实在不是什么好差事。所以他们写 C 程序的习惯是，忽略所有的错误，然后使用调试器来跟踪错误。这些程序员知道，使用异常就意味着他们要写一些通常不用写的、“多出来的”代码。因此，要把他们拉到“使用错误处理”的正轨上，“多出来的”代码决不能太多。我认为，评价 Java 的“被检查的异常”的时候，这一点是很重要的。
-
-C++ 从 CLU 那里还带来另一种思想：异常说明。这样，就可以用编程的方式在方法签名中声明这个方法将会抛出异常。异常说明有两个目的：一个是“我的代码会产生这种异常，这由你来处理”。另一个是“我的代码忽略了这些异常，这由你来处理”。学习异常处理的机制和语法的时候，我们一直在关注“你来处理”部分，但这里特别值得注意的事实是，我们通常都忽略了异常说明所表达的完整含义。
-
-C++ 的异常说明不属于函数的类型信息。编译时唯一要检查的是异常说明是不是前后一致；比如，如果函数或方法会抛出某些异常，那么它的重载版本或者派生版本也必须抛出同样的异常。与 Java 不同，C++ 不会在编译时进行检查以确定函数或方法是不是真的抛出异常，或者异常说明是不是完整（也就是说，异常说明有没有精确描述所有可能被抛出的异常）。这样的检查只发生在运行期间。如果抛出的异常与异常说明不符，C++ 会调用标准类库的 unexpected() 函数。
-
-值得注意的是，由于使用了模板，C++ 的标准类库实现里根本没有使用异常说明。在 Java 中，对于泛型用于异常说明的方式存在着一些限制。
-
-### 观点
-
-首先，Java 无谓地发明了“被检查的异常”（很明显是受 C++ 异常说明的启发，以及受 C++ 程序员们一般对此无动于衷的事实的影响），但是，这还只是一次尝试，目前为止还没有别的语言采用这种做法。
-
-其次，仅从示意性的例子和小程序来看，“被检查的异常”的好处很明显。但是当程序开始变大的时候，就会带来一些微妙的问题。当然，程序不是一下就变大的，这有个过程。如果把不适用于大项目的语言用于小项目，当这些项目不断膨胀时，突然有一天你会发现，原来可以管理的东西，现在已经变得无法管理了。这就是我所说的过多的类型检查，特别是“被检查的异常"所造成的问题。
-
-看来程序的规模是个重要因素。由于很多讨论都用小程序来做演示，因此这并不足以说明问题。一名 C# 的设计人员发现：
-
-> “仅从小程序来看，会认为异常说明能增加开发人员的效率，并提高代码的质量；但考察大项目的时候，结论就不同了-开发效率下降了，而代码质量只有微不足道的提高，甚至毫无提高”。
-
-谈到未被捕获的异常的时候，CLU 的设计师们认为：
-
-> “我们觉得强迫程序员在不知道该采取什么措施的时候提供处理程序，是不现实的。”
-
-在解释为什么“函数没有异常说明就表示可以抛出任何异常”的时候，Stroustrup 这样认为：
-
-> “但是，这样一来几乎所有的函数都得提供异常说明了，也就都得重新编译，而且还会妨碍它同其他语言的交互。这样会迫使程序员违反异常处理机制的约束，他们会写欺骗程序来掩盖异常。这将给没有注意到这些异常的人造成一种虚假的安全感。”
->
-
-我们已经看到这种破坏异常机制的行为了-就在 Java 的“被检查的异常”里。
-
-Martin Fowler（UML Distilled，Refactoring 和 Analysis Patterns 的作者）给我写了下面这段话：
-
-> “...总体来说，我觉得异常很不错，但是 Java 的”被检查的异常“带来的麻烦比好处要多。”
-
-过去，我曾坚定地认为“被检查的异常”和强静态类型检查对开发健壮的程序是非常必要的。但是，我看到的以及我使用一些动态（类型检查）语言的亲身经历告诉我，这些好处实际上是来自于：
-
-1. 不在于编译器是否会强制程序员去处理错误，而是要有一致的、使用异常来报告错误的模型。
-2. 不在于什么时候进行检查，而是一定要有类型检查。也就是说，必须强制程序使用正确的类型，至于这种强制施加于编译时还是运行时，那倒没关系。
-
-此外，减少编译时施加的约束能显著提高程序员的编程效率。事实上，反射和泛型就是用来补偿静态类型检查所带来的过多限制，在本书很多例子中都会见到这种情形。
-
-我已经听到有人在指责了，他们认为这种言论会令我名誉扫地，会让文明堕落，会导致更高比例的项目失败。他们的信念是应该在编译时指出所有错误，这样才能挽救项目，这种信念可以说是无比坚定的；其实更重要的是要理解编译器的能力限制。在 http://MindView.net/Books/BetterJava 上的补充材料中，我强调了自动构建过程和单元测试的重要性，比起把所有的东西都说成是语法错误，它们的效果可以说是事半功倍。下面这段话是至理名言：
-
-> 好的程序设计语言能帮助程序员写出好程序，但无论哪种语言都避免不了程序员用它写出坏程序。
-
-不管怎么说，要让 Java 把“被检查的异常”从语言中去除，这种可能性看来非常渺茫。对语言来说，这个变化可能太激进了点，况且 Sun 的支持者们也非常强大。Sun 有完全向后兼容的历史和策略，实际上所有 Sun 的软件都能在 Sun 的硬件上运行，无论它们有多么古老。然而，如果发现有些“被检查的异常”挡住了路，尤其是发现你不得不去对付那些不知道该如何处理的异常，还是有些办法的。
-
-### 把异常传递给控制台
-
-对于简单的程序，比如本书中的许多例子，最简单而又不用写多少代码就能保护异常信息的方法，就是把它们从 main() 传递到控制台。例如，为了读取信息而打开一个文件（在第 12 章将详细介绍），必须对 FilelnputStream 进行打开和关闭操作，这就可能会产生异常。对于简单的程序，可以像这样做（本书中很多地方采用了这种方法）：
-
-```java
-// exceptions/MainException.java
-import java.util.*;
-import java.nio.file.*;
-public class MainException {
-    // Pass exceptions to the console:
-    public static void main(String[] args) throws Exception {
-        // Open the file:
-        List<String> lines = Files.readAllLines(
-                Paths.get("MainException.java"));
-        // Use the file ...
-    }
-}
-```
-
-注意，main() 作为一个方法也可以有异常说明，这里异常的类型是 Exception，它也是所有“被检查的异常”的基类。通过把它传递到控制台，就不必在 main() 里写 try-catch 子句了。（不过，实际的文件输人输出操作比这个例子要复杂得多。你将会在 [文件](./Files.md) 和 [附录：I/O 流](./Appendix-IO-Streams.md) 章节中学到更多）
-
-### 把“被检查的异常”转换为“不检查的异常”
-在编写你自己使用的简单程序时，从主方法中抛出异常是很方便的，但这不是通用的方法。
-
-问题的实质是，当在一个普通方法里调用别的方法时，要考虑到“我不知道该这样处理这个异常，但是也不想把它‘吞’了，或若打印一些无用的消息”。异常链提供了一种新的思路来解决这个问题。
-可以直接把“被检查的异常”包装进 RuntimeException 里面：
-```java
-try {
-    // ...
-} catch(IDontKnowWhatToDoWithThisCheckedException e) {
-    throw new RuntimeException(e);
-}
-```
-如果想把“被检查的异常”这种功能“屏蔽”掉的话，这看上去像是一个好办法。不用“吞下”异常，也不必把它放到方法的异常说明里面，而异常链还能保证你不会丢失任何原始异常的信息。
-
-这种技巧给了你一种选择，可以不写 try-catch 子句和/或异常说明，直接忽略异常，让它自己沿着调用栈往上“冒泡”，同时，还可以用 getCause() 捕获并处理特定的异常，就像这样：
-```java
-// "Turning off" Checked exceptions
-import java.io.*;
-class WrapCheckedException {
-    void throwRuntimeException(int type) {
-        try {
-            switch(type) {
-                case 0: throw new FileNotFoundException();
-                case 1: throw new IOException();
-                case 2: throw new
-                        RuntimeException("Where am I?");
-                default: return;
-            }
-        } catch(IOException | RuntimeException e) {
-            // Adapt to unchecked:
-            throw new RuntimeException(e);
-        }
-    }
-}
-class SomeOtherException extends Exception {}
-public class TurnOffChecking {
-    public static void main(String[] args) {
-        WrapCheckedException wce =
-                new WrapCheckedException();
-        // You can call throwRuntimeException() without
-        // a try block, and let RuntimeExceptions
-        // leave the method:
-        wce.throwRuntimeException(3);
-        // Or you can choose to catch exceptions:
-        for(int i = 0; i < 4; i++)
-            try {
-                if(i < 3)
-                    wce.throwRuntimeException(i);
-                else
-                    throw new SomeOtherException();
-            } catch(SomeOtherException e) {
-                System.out.println(
-                        "SomeOtherException: " + e);
-            } catch(RuntimeException re) {
-                try {
-                    throw re.getCause();
-                } catch(FileNotFoundException e) {
-                    System.out.println(
-                            "FileNotFoundException: " + e);
-                } catch(IOException e) {
-                    System.out.println("IOException: " + e);
-                } catch(Throwable e) {
-                    System.out.println("Throwable: " + e);
-                }
-            }
-    }
-}
-```
-
-输出为：
-
-```
-FileNotFoundException: java.io.FileNotFoundException
-IOException: java.io.IOException
-Throwable: java.lang.RuntimeException: Where am I?
-SomeOtherException: SomeOtherException
-```
-
-WrapCheckedException.throwRuntimeException() 的代码可以生成不同类型的异常。这些异常被捕获并包装进了 RuntimeException 对象，所以它们成了这些运行时异常的"cause"了。
-
-在 TurnOfChecking 里，可以不用 try 块就调用 throwRuntimeException()，因为它没有抛出“被检查的异常”。但是，当你准备好去捕获异常的时候，还是可以用 try 块来捕获任何你想捕获的异常的。应该捕获 try 块肯定会抛出的异常，这里就是 SomeOtherException，RuntimeException 要放到最后去捕获。然后把 getCause() 的结果（也就是被包装的那个原始异常）抛出来。这样就把原先的那个异常给提取出来了，然后就可以用它们自己的 catch 子句进行处理。
-
-另一种解决方案是创建自己的 RuntimeException 的子类。在这种方式中，不必捕获它，但是希望得到它的其他代码都可以捕获它。
-
-## 异常指南
-应该在下列情况下使用异常：
-
-1. 尽可能使用 try-with-resource。
-2. 在恰当的级别处理问题。（在知道该如何处理的情况下才捕获异常。）
-3. 解决问题并且重新调用产生异常的方法。
-4. 进行少许修补，然后绕过异常发生的地方继续执行。
-5. 用别的数据进行计算，以代替方法预计会返回的值。
-6. 把当前运行环境下能做的事情尽量做完，然后把相同的异常重抛到更高层。
-7. 把当前运行环境下能做的事情尽量做完，然后把不同的异常抛到更高层。
-8. 终止程序。
-9. 进行简化。（如果你的异常模式使问题变得太复杂，那用起来会非常痛苦也很烦人。）
-10. 让类库和程序更安全。（这既是在为调试做短期投资，也是在为程序的健壮性做长期投资。）
-
-# 总结
-异常是 Java 程序设计不可分割的一部分，如果不了解如何使用它们，那你只能完成很有限的工作。正因为如此，本书专门在此介绍了异常——对于许多类库（例如提到过的 I/O 库），如果不处理异常，你就无法使用它们。
-
-优点之一就是它使得你可以在某处集中精力处理你要解决的问题，而在另一处处理你编写的这段代码中产生的错误。尽管异常通常被认为是一种工具，使得你可以在运行时报告错误并从错误中恢复，但是我一直怀疑到底有多少时候“恢复”真正得以实现了，或者能够得以实现。我认为这种情况少于 10%，并且即便是这 10%，也只是将栈展开到某个已知的稳定状态，而并没有实际执行任何种类的恢复性行为。无论这是否正确，我一直相信“报告”功能是异常的精髓所在. Java 坚定地强调将所有的错误都以异常形式报告的这一事实，正是它远远超过语如 C++ 这类语言的长处之一，因为在 C++ 这类语言中，需要以大量不同的方式来报告错误，或者根本就没有提供错误报告功能。一致的错误报告系统意味着，你再也不必对所写的每一段代码，都质问自己“错误是否正在成为漏网之鱼？”（只要你没有“吞咽”异常，这是关键所在！）。
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\346\227\245\345\277\227\347\272\247\345\210\253\357\274\214\351\207\215\345\244\215\350\256\260\345\275\225\343\200\201\344\270\242\346\227\245\345\277\227\351\227\256\351\242\230.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\346\227\245\345\277\227\347\272\247\345\210\253\357\274\214\351\207\215\345\244\215\350\256\260\345\275\225\343\200\201\344\270\242\346\227\245\345\277\227\351\227\256\351\242\230.md"
deleted file mode 100644
index 5da763f8e9..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\346\227\245\345\277\227\347\272\247\345\210\253\357\274\214\351\207\215\345\244\215\350\256\260\345\275\225\343\200\201\344\270\242\346\227\245\345\277\227\351\227\256\351\242\230.md"
+++ /dev/null
@@ -1,243 +0,0 @@
-# 1 SLF4J
-## 日志行业的现状
-- 框架繁
-不同类库可能使用不同日志框架，兼容难，无法接入统一日志，让运维很头疼！
-- 配置复杂
-由于配置文件一般是 xml 文件，内容繁杂！很多人喜欢从其他项目或网上闭眼copy！
-- 随意度高
-因为不会直接导致代码 bug，测试人员也难发现问题，开发就没仔细考虑日志内容获取的性能开销，随意选用日志级别！
-
-Logback、Log4j、Log4j2、commons-logging及java.util.logging等，都是Java体系的日志框架。
-不同的类库，还可能选择使用不同的日志框架，导致日志统一管理困难。
-
-- SLF4J（Simple Logging Facade For Java）就为解决该问题而生
-![](https://img-blog.csdnimg.cn/20201205201659631.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-- 提供统一的日志门面API
-图中紫色部分，实现中立的日志记录API
-- 桥接功能
-蓝色部分，把各种日志框架API桥接到SLF4J API。这样即使你的程序使用了各种日志API记录日志，最终都可桥接到SLF4J门面API
-- 适配功能
-红色部分，绑定SLF4J API和实际的日志框架（灰色部分）
-
-SLF4J只是日志标准，还是需要实际日志框架。日志框架本身未实现SLF4J API，所以需要有个前置转换。
-Logback本身就按SLF4J API标准实现，所以无需绑定模块做转换。
-
-虽然可用`log4j-over-slf4j`实现Log4j桥接到SLF4J，也可使用`slf4j-log4j12`实现SLF4J适配到Log4j，也把它们画到了一列，但是它不能同时使用它们，否则就会产生死循环。jcl和jul同理。
-
-虽然图中有4个灰色的日志实现框架，但业务开发使用最多的还是Logback和Log4j，都是同一人开发的。Logback可认为是Log4j改进版，更推荐使用，已是社会主流。
-
-Spring Boot的日志框架也是Logback。那为什么我们没有手动引入Logback包，就可直接使用Logback？
-
-spring-boot-starter模块依赖**spring-boot-starter-logging**模块，而
-**spring-boot-starter-logging**自动引入**logback-classic**（包含SLF4J和Logback日志框架）和SLF4J的一些适配器。
-# 2 异步日志就肯定能提高性能?
-如何避免日志记录成为系统性能瓶颈呢？
-这关系到磁盘（比如机械磁盘）IO性能较差、日志量又很大的情况下，如何记录日志。
-
-## 2.1 案例
-定义如下的日志配置，一共有两个Appender：
-- **FILE**是一个FileAppender，用于记录所有的日志
-- **CONSOLE**是一个ConsoleAppender，用于记录带有time标记的日志
-![](https://img-blog.csdnimg.cn/20201205223014732.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-把大量日志输出到文件中，日志文件会非常大，若性能测试结果也混在其中，就很难找到那条日志了。
-所以，这里使用EvaluatorFilter对日志按照标记进行过滤，并将过滤出的日志单独输出到控制台。该案例中给输出测试结果的那条日志上做了time标记。
-
-> **配合使用标记和EvaluatorFilter，可实现日志的按标签过滤**。
-
-- 测试代码：实现记录指定次数的大日志，每条日志包含1MB字节的模拟数据，最后记录一条以time为标记的方法执行耗时日志：![](https://img-blog.csdnimg.cn/20201205223508352.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-执行程序后发现，记录1000次日志和10000次日志的调用耗时，分别是5.1s和39s
-![](https://img-blog.csdnimg.cn/2020120522373754.png)![](https://img-blog.csdnimg.cn/20201205224030888.png)
-对只记录文件日志的代码，这耗时过长了。
-## 2.2 源码解析
-FileAppender继承自OutputStreamAppender
-![](https://img-blog.csdnimg.cn/20201205224309140.png)
-在追加日志时，是直接把日志写入OutputStream中，属**同步记录日志**
-![](https://img-blog.csdnimg.cn/20201206132744194.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-所以日志大量写入才会旷日持久。如何才能实现大量日志写入时，不会过多影响业务逻辑执行耗时而影响吞吐量呢？
-## 2.3 AsyncAppender
-使用Logback的**AsyncAppender**，即可实现异步日志记录。
-
-**AsyncAppender**类似装饰模式，在不改变类原有基本功能情况下，为其增添新功能。这便可把**AsyncAppender**附加在其他**Appender**，将其变为异步。
-
-定义一个异步Appender ASYNCFILE，包装之前的同步文件日志记录的FileAppender， 即可实现异步记录日志到文件
-![](https://img-blog.csdnimg.cn/20201206133807409.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-- 记录1000次日志和10000次日志的调用耗时，分别是537ms和1019ms
-![](https://img-blog.csdnimg.cn/20201206133959275.png)![](https://img-blog.csdnimg.cn/2020120613391870.png)
-异步日志真的如此高性能？并不，因为它并没有记录下所有日志。
-# 3 AsyncAppender异步日志的天坑
-- 记录异步日志撑爆内存
-- 记录异步日志出现日志丢失
-- 记录异步日志出现阻塞。
-
-##  3.1 案例
-模拟个慢日志记录场景：
-首先，自定义一个继承自**ConsoleAppender**的**MySlowAppender**，作为记录到控制台的输出器，写入日志时睡1s。
-![](https://img-blog.csdnimg.cn/20201206134635409.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-- 配置文件中使用**AsyncAppender**，将**MySlowAppender**包装为异步日志记录
-![](https://img-blog.csdnimg.cn/20201206141303471.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-- 测试代码
-![](https://img-blog.csdnimg.cn/20201206135344681.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-- 耗时很短但出现日志丢失：要记录1000条日志，最终控制台只能搜索到215条日志，而且日志行号变问号。
-![](https://img-blog.csdnimg.cn/20201206141514155.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-- 原因分析
-**AsyncAppender**提供了一些配置参数，而当前没用对。
-
-### 源码解析
-- includeCallerData
-默认false：方法行号、方法名等信息不显示
-- queueSize
-控制阻塞队列大小，使用的ArrayBlockingQueue阻塞队列，默认容量256：内存中最多保存256条日志
-- discardingThreshold
-丢弃日志的阈值，为防止队列满后发生阻塞。默认`队列剩余容量 ＜ 队列长度的20%`，就会丢弃TRACE、DEBUG和INFO级日志
-- neverBlock
-控制队列满时，加入的数据是否直接丢弃，不会阻塞等待，默认是false
-	- 队列满时：offer不阻塞，而put会阻塞
-	- neverBlock为true时，使用offer
-```java
-public class AsyncAppender extends AsyncAppenderBase<ILoggingEvent> {
-	// 是否收集调用方数据
-    boolean includeCallerData = false;
-    protected boolean isDiscardable(ILoggingEvent event) {
-        Level level = event.getLevel();
-        // 丢弃 ≤ INFO级日志
-        return level.toInt() <= Level.INFO_INT;
-    }
-    protected void preprocess(ILoggingEvent eventObject) {
-        eventObject.prepareForDeferredProcessing();
-        if (includeCallerData)
-            eventObject.getCallerData();
-    }
-}
-public class AsyncAppenderBase<E> extends UnsynchronizedAppenderBase<E> implements AppenderAttachable<E> {
-
-	// 阻塞队列：实现异步日志的核心
-    BlockingQueue<E> blockingQueue;
-    // 默认队列大小
-    public static final int DEFAULT_QUEUE_SIZE = 256;
-    int queueSize = DEFAULT_QUEUE_SIZE;
-    static final int UNDEFINED = -1;
-    int discardingThreshold = UNDEFINED;
-    // 当队列满时：加入数据时是否直接丢弃，不会阻塞等待
-    boolean neverBlock = false;
-
-    @Override
-    public void start() {
-       	...
-        blockingQueue = new ArrayBlockingQueue<E>(queueSize);
-        if (discardingThreshold == UNDEFINED)
-        //默认丢弃阈值是队列剩余量低于队列长度的20%，参见isQueueBelowDiscardingThreshold方法
-            discardingThreshold = queueSize / 5;
-        ...
-    }
-
-    @Override
-    protected void append(E eventObject) {
-        if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) { //判断是否可以丢数据
-            return;
-        }
-        preprocess(eventObject);
-        put(eventObject);
-    }
-
-    private boolean isQueueBelowDiscardingThreshold() {
-        return (blockingQueue.remainingCapacity() < discardingThreshold);
-    }
-
-    private void put(E eventObject) {
-        if (neverBlock) { //根据neverBlock决定使用不阻塞的offer还是阻塞的put方法
-            blockingQueue.offer(eventObject);
-        } else {
-            putUninterruptibly(eventObject);
-        }
-    }
-    //以阻塞方式添加数据到队列
-    private void putUninterruptibly(E eventObject) {
-        boolean interrupted = false;
-        try {
-            while (true) {
-                try {
-                    blockingQueue.put(eventObject);
-                    break;
-                } catch (InterruptedException e) {
-                    interrupted = true;
-                }
-            }
-        } finally {
-            if (interrupted) {
-                Thread.currentThread().interrupt();
-            }
-        }
-    }
-}  
-```
-
-默认队列大小256，达到80%后开始丢弃<=INFO级日志后，即可理解日志中为什么只有两百多条INFO日志了。
-### queueSize 过大
-可能导致**OOM**
-### queueSize 较小
-默认值256就已经算很小了，且**discardingThreshold**设置为大于0（或为默认值），队列剩余容量少于**discardingThreshold**的配置就会丢弃<=INFO日志。这里的坑点有两个：
-1. 因为**discardingThreshold**，所以设置**queueSize**时容易踩坑。
-比如本案例最大日志并发1000，即便置**queueSize**为1000，同样会导致日志丢失
-2. **discardingThreshold**参数容易有歧义，它`不是百分比，而是日志条数`。对于总容量10000队列，若希望队列剩余容量少于1000时丢弃，需配置为1000
-### neverBlock 默认false
-意味总可能会出现阻塞。
-- 若**discardingThreshold = 0**，那么队列满时再有日志写入就会阻塞
-- 若**discardingThreshold != 0**，也只丢弃≤INFO级日志，出现大量错误日志时，还是会阻塞
-
-queueSize、discardingThreshold和neverBlock三参密不可分，务必按业务需求设置：
-- 若优先绝对性能，设置`neverBlock = true`，永不阻塞
-- 若优先绝不丢数据，设置`discardingThreshold = 0`，即使≤INFO级日志也不会丢。但最好把queueSize设置大一点，毕竟默认的queueSize显然太小，太容易阻塞。
-- 若兼顾，可丢弃不重要日志，把**queueSize**设置大点，再设置合理的**discardingThreshold**
-
-以上日志配置最常见两个误区
-
-再看日志记录本身的误区。
-
-# 4 如何选择日志级别?
-
-> 使用{}占位符，就不用判断log level了吗?
-
-据不知名网友说道：SLF4J的{}占位符语法，到真正记录日志时才会获取实际参数，因此解决了日志数据获取的性能问题。
-**是真的吗？**
-![](https://img-blog.csdnimg.cn/596e199b79c1443a89723becb9809f8b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_12,color_FFFFFF,t_70,g_se,x_16)
-
-- 验证代码：返回结果耗时1s
-![](https://img-blog.csdnimg.cn/2020120619541620.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-若记录DEBUG日志，并设置只记录>=INFO级日志，程序是否也会耗时1s？
-三种方法测试：
-- 拼接字符串方式记录slowString
-- 使用占位符方式记录slowString
-- 先判断日志级别是否启用DEBUG。
-
-![](https://img-blog.csdnimg.cn/20201206200002878.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20201206200446663.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-前俩方式都调用slowString，所以都耗时1s。且方式二就是使用占位符记录slowString，这种方式虽允许传Object，不显式拼接String，但也只是延迟（若日志不记录那就是省去）**日志参数对象.toString()**和**字符串拼接**的耗时。
-
-本案例除非事先判断日志级别，否则必调用slowString。所以使用`{}占位符`不能通过延迟参数值获取，来解决日志数据获取的性能问题。
-
-除事先判断日志级别，还可通过lambda表达式延迟参数内容获取。但SLF4J的API还不支持lambda，因此需使用Log4j2日志API，把**Lombok的@Slf4j注解**替换为**@Log4j2**注解，即可提供lambda表达式参数的方法：
-![](https://img-blog.csdnimg.cn/20201206201751860.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-这样调用debug，签名**Supplier<?>**，参数就会延迟到真正需要记录日志时再获取：
-![](https://img-blog.csdnimg.cn/20201206202233179.png)
-![](https://img-blog.csdnimg.cn/20201206202311160.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20201206202346847.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20201206202419598.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-所以debug4并不会调用slowString方法
-![](https://img-blog.csdnimg.cn/20201206203249411.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-只是换成**Log4j2 API**，真正的日志记录还是走的**Logback**，这就是**SLF4J**适配的好处。
-
-# 总结
-- SLF4J统一了Java日志框架。在使用SLF4J时，要理清楚其桥接API和绑定。若程序启动时出现SLF4J错误提示，可能是配置问题，可使用Maven的`dependency:tree`命令梳理依赖关系。
-- 异步日志解决性能问题，是用空间换时间。但空间毕竟有限，当空间满，要考虑阻塞等待or丢弃日志。若更希望不丢弃重要日志，那么选择阻塞等待；如果更希望程序不要因为日志记录而阻塞，那么就需要丢弃日志。
-- 日志框架提供的参数化记录方式不能完全取代日志级别的判断。若日志量很大，获取日志参数代价也很大，就要判断日志级别，避免不记录日志也要耗时获取日志参数！
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\347\261\273\347\232\204\345\210\235\345\247\213\345\214\226\345\222\214\346\270\205\347\220\206.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\347\261\273\347\232\204\345\210\235\345\247\213\345\214\226\345\222\214\346\270\205\347\220\206.md"
index c4723983a7..2ee8c1753e 100644
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\347\261\273\347\232\204\345\210\235\345\247\213\345\214\226\345\222\214\346\270\205\347\220\206.md"
+++ "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\347\261\273\347\232\204\345\210\235\345\247\213\345\214\226\345\222\214\346\270\205\347\220\206.md"
@@ -84,6 +84,292 @@ Tree t = new Tree(12); // 12-foot 树
 构造器消除了一类重要的问题，使得代码更易读。例如，在上面的代码块中，你看不到对 `initialize()` 方法的显式调用，而从概念上来看，`initialize()` 方法应该与对象的创建分离。在 Java 中，对象的创建与初始化是统一的概念，二者不可分割。
 
 构造器没有返回值，它是一种特殊的方法。但它和返回类型为 `void` 的普通方法不同，普通方法可以返回空值，你还能选择让它返回别的类型；而构造器没有返回值，却同时也没有给你选择的余地（`new` 表达式虽然返回了刚创建的对象的引用，但构造器本身却没有返回任何值）。如果它有返回值，并且你也可以自己选择让它返回什么，那么编译器就还得知道接下来该怎么处理那个返回值（这个返回值没有接收者）。
+# 3 方法重载
+任何编程语言中都具备的一项重要特性就是名称。当你创建一个对象时，就会给此对象分配的内存空间一个名称。一个方法就是一种行为的名称。通过名称引用所各种对象，属性和方法。良好的命名可以让系统易于理解和修改。就好比写散文——目的是与读者沟通。
+
+将人类语言细微的差别映射到编程语言中会产生一个问题。通常，相同的词可以表达多种不同的含义——它们被"重载"了。特别是当含义的差别很小时，这会更加有用。你会说"清洗衬衫"、"清洗车"和"清洗狗"。而如果硬要这么说就会显得很愚蠢："以洗衬衫的方式洗衬衫"、"以洗车的方式洗车"和"以洗狗的方式洗狗"，因为听众根本不需要区分行为的动作。大多数人类语言都具有"冗余"性，所以即使漏掉几个词，你也能明白含义。你不需要对每个概念都使用不同的词汇——可以从上下文推断出含义。
+
+大多数编程语言（尤其是 C 语言）要求为每个方法（在这些语言中经常称为函数）提供一个独一无二的标识符。所以，你不能有一个 `print()` 函数既能打印整型，也能打印浮点型——每个函数名都必须不同。
+
+在 Java (C++) 中，还有一个因素也促使了必须使用方法重载：构造器。因为构造器方法名肯定是与类名相同，所以一个类中只会有一个构造器名。那么你怎么通过不同的方式创建一个对象呢？例如，你想创建一个类，这个类的初始化方式有两种：一种是标准化方式，另一种是从文件中读取信息的方式。你需要两个构造器：无参构造器和有一个 **String** 类型参数的构造器，该参数传入文件名。两个构造器具有相同的名字——与类名相同。因此，方法重载是必要的，它允许方法具有相同的方法名但接收的参数不同。尽管方法重载对于构造器是重要的，但是也可以对任何方法很方便地进行重载。
+
+下例展示了如何重载构造器和方法：
+
+```java
+// housekeeping/Overloading.java
+// Both constructor and ordinary method overloading
+
+class Tree {
+    int height;
+    Tree() {
+        System.out.println("Planting a seedling");
+        height = 0;
+    }
+    Tree(int initialHeight) {
+        height = initialHeight;
+        System.out.println("Creating new Tree that is " + height + " feet tall");
+    }
+    void info() {
+        System.out.println("Tree is " + height + " feet tall");
+    }
+    void info(String s) {
+        System.out.println(s + ": Tree is " + height + " feet tall");
+    }
+}
+public class Overloading {
+    public static void main(String[] args) {
+        for (int i = 0; i < 5; i++) {
+            Tree t = new Tree(i);
+            t.info();
+            t.info("overloaded method");
+        }
+        new Tree(); 
+    }
+}
+```
+
+输出：
+
+```java
+Creating new Tree that is 0 feet tall
+Tree is 0 feet tall
+overloaded method: Tree is 0 feet tall
+Creating new Tree that is 1 feet tall
+Tree is 1 feet tall
+overloaded method: Tree is 1 feet tall
+Creating new Tree that is 2 feet tall
+Tree is 2 feet tall
+overloaded method: Tree is 2 feet tall
+Creating new Tree that is 3 feet tall
+Tree is 3 feet tall
+overloaded method: Tree is 3 feet tall
+Creating new Tree that is 4 feet tall
+Tree is 4 feet tall
+overloaded method: Tree is 4 feet tall
+Planting a seedling
+```
+
+一个 **Tree** 对象既可以是一颗树苗，使用无参构造器创建，也可以是一颗在温室中已长大的树，已经有一定高度，这时候，就需要使用有参构造器创建。
+
+你也许想以多种方式调用 `info()` 方法。比如，如果你想打印额外的消息，就可以使用 `info(String)` 方法。如果你无话可说，就可以使用 `info()` 方法。用两个命名定义完全相同的概念看起来很奇怪，而使用方法重载，你就可以使用一个命名来定义一个概念。
+
+## 3.1 区分重载方法
+
+如果两个方法命名相同，Java是怎么知道你调用的是哪个呢？有一条简单的规则：每个被重载的方法必须有独一无二的参数列表。你稍微思考下，就会很明了了，除了通过参数列表的不同来区分两个相同命名的方法，其他也没什么方式了。你甚至可以根据参数列表中的参数顺序来区分不同的方法，尽管这会造成代码难以维护。例如：
+
+```java
+// housekeeping/OverloadingOrder.java
+// Overloading based on the order of the arguments
+
+public class OverloadingOrder {
+    static void f(String s, int i) {
+        System.out.println("String: " + s + ", int: " + i);
+    }
+
+    static void f(int i, String s) {
+        System.out.println("int: " + i + ", String: " + s);
+    }
+
+    public static void main(String[] args) {
+        f("String first", 1);
+        f(99, "Int first");
+    }
+}
+```
+
+输出：
+
+```java
+String: String first, int: 1
+int: 99, String: Int first
+```
+
+两个 `f()` 方法具有相同的参数，但是参数顺序不同，根据这个就可以区分它们。
+
+## 3.2  重载与基本类型
+
+基本类型可以自动从较小的类型转型为较大的类型。当这与重载结合时，这会令人有点困惑，下面是一个这样的例子：
+
+```java
+// housekeeping/PrimitiveOverloading.java
+// Promotion of primitives and overloading
+
+public class PrimitiveOverloading {
+    void f1(char x) {
+        System.out.print("f1(char)");
+    }
+    void f1(byte x) {
+        System.out.print("f1(byte)");
+    }
+    void f1(short x) {
+        System.out.print("f1(short)");
+    }
+    void f1(int x) {
+        System.out.print("f1(int)");
+    }
+    void f1(long x) {
+        System.out.print("f1(long)");
+    }
+    void f1(float x) {
+        System.out.print("f1(float)");
+    }
+    void f1(double x) {
+        System.out.print("f1(double)");
+    }
+    void f2(byte x) {
+        System.out.print("f2(byte)");
+    }
+    void f2(short x) {
+        System.out.print("f2(short)");
+    }
+    void f2(int x) {
+        System.out.print("f2(int)");
+    }
+    void f2(long x) {
+        System.out.print("f2(long)");
+    }
+    void f2(float x) {
+        System.out.print("f2(float)");
+    }
+    void f2(double x) {
+        System.out.print("f2(double)");
+    }
+    void f3(short x) {
+        System.out.print("f3(short)");
+    }
+    void f3(int x) {
+        System.out.print("f3(int)");
+    }
+    void f3(long x) {
+        System.out.print("f3(long)");
+    }
+    void f3(float x) {
+        System.out.print("f3(float)");
+    }
+    void f3(double x) {
+        System.out.print("f3(double)");
+    }
+    void f4(int x) {
+        System.out.print("f4(int)");
+    }
+    void f4(long x) {
+        System.out.print("f4(long)");
+    }
+    void f4(float x) {
+        System.out.print("f4(float)");
+    }
+    void f4(double x) {
+        System.out.print("f4(double)");
+    }
+    void f5(long x) {
+        System.out.print("f5(long)");
+    }
+    void f5(float x) {
+        System.out.print("f5(float)");
+    }
+    void f5(double x) {
+        System.out.print("f5(double)");
+    }
+    void f6(float x) {
+        System.out.print("f6(float)");
+    }
+    void f6(double x) {
+        System.out.print("f6(double)");
+    }
+    void f7(double x) {
+        System.out.print("f7(double)");
+    }
+    void testConstVal() {
+        System.out.print("5: ");
+        f1(5);f2(5);f3(5);f4(5);f5(5);f6(5);f7(5);
+        System.out.println();
+    }
+    void testChar() {
+        char x = 'x';
+        System.out.print("char: ");
+        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
+        System.out.println();
+    }
+    void testByte() {
+        byte x = 0;
+        System.out.print("byte: ");
+        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
+        System.out.println();
+    }
+    void testShort() {
+        short x = 0;
+        System.out.print("short: ");
+        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
+        System.out.println();
+    }
+    void testInt() {
+        int x = 0;
+        System.out.print("int: ");
+        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
+        System.out.println();
+    }
+    void testLong() {
+        long x = 0;
+        System.out.print("long: ");
+        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
+        System.out.println();
+    }
+    void testFloat() {
+        float x = 0;
+        System.out.print("float: ");
+        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
+        System.out.println();
+    }
+    void testDouble() {
+        double x = 0;
+        System.out.print("double: ");
+        f1(x);f2(x);f3(x);f4(x);f5(x);f6(x);f7(x);
+        System.out.println();
+    }
+
+    public static void main(String[] args) {
+        PrimitiveOverloading p = new PrimitiveOverloading();
+        p.testConstVal();
+        p.testChar();
+        p.testByte();
+        p.testShort();
+        p.testInt();
+        p.testLong();
+        p.testFloat();
+        p.testDouble();
+    }
+}
+```
+
+输出：
+
+```java
+5: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)
+char: f1(char)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)
+byte: f1(byte)f2(byte)f3(short)f4(int)f5(long)f6(float)f7(double)
+short: f1(short)f2(short)f3(short)f4(int)f5(long)f6(float)f7(double)
+int: f1(int)f2(int)f3(int)f4(int)f5(long)f6(float)f7(double)
+long: f1(long)f2(long)f3(long)f4(long)f5(long)f6(float)f7(double)
+float: f1(float)f2(float)f3(float)f4(float)f5(float)f6(float)f7(double)
+double: f1(double)f2(double)f3(double)f4(double)f5(double)f6(double)f7(double)
+```
+
+如果传入的参数类型大于方法期望接收的参数类型，你必须首先做缩窄转换，否则编译器就会报错。
+
+### 返回值的重载
+
+经常会有人困惑，"为什么只能通过类名和参数列表，不能通过方法的返回值区分方法呢?"。例如以下两个方法，它们有相同的命名和参数，但是很容易区分：
+
+```java
+void f(){}
+int f() {return 1;}
+```
+
+有些情况下，编译器很容易就可以从上下文准确推断出该调用哪个方法，如 `int x = f()`。
+
+但是，你可以调用一个方法且忽略返回值。这叫做调用一个函数的副作用，因为你不在乎返回值，只是想利用方法做些事。所以如果你直接调用 `f()`，Java 编译器就不知道你想调用哪个方法，阅读者也不明所以。因为这个原因，所以你不能根据返回值类型区分重载的方法。为了支持新特性，Java 8 在一些具体情形下提高了猜测的准确度，但是通常来说并不起作用。
+
+<!-- No-arg Constructors -->
 
 # 4 无参构造器
 
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\347\272\277\347\250\213\347\212\266\346\200\201.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\347\272\277\347\250\213\347\212\266\346\200\201.md"
index c3304c0ee1..5de6e1f0aa 100644
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\347\272\277\347\250\213\347\212\266\346\200\201.md"
+++ "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\347\272\277\347\250\213\347\212\266\346\200\201.md"
@@ -1,87 +1,72 @@
-![](https://img-blog.csdnimg.cn/20210415134805544.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-> 面试官瞅了瞅我精悍的简历，随口一问，说说Java线程有哪些状态？
-
-这问的也太基础了，这就是开场白吗？
-
-这个简单的呀，面试官让我来告诉你，JDK 的 Thread 源码定义了6个状态: **java.lang.Thread.State**
-- New
-尚未启动的线程的线程状态。
-- Runnable
-可运行线程的线程状态，等待CPU调度。
-- Blocked
-线程阻塞等待监视器锁定的线程状态。处于synchronized同步代码块或方法中被阻塞。
-- Waiting
-等待线程的线程状态。下列不带超时的方式:
-`Object.wait`、`Thread.join`、 `LockSupport.park`
-- Timed Waiting
-具有指定等待时间的等待线程的线程状态。下列带超时的方式:
+# 0 线程状态概述
+## 分类
+6个状态定义: java.lang.Thread.State
+1. New: 尚未启动的线程的线程状态。
+2. Runnable: 可运行线程的线程状态，等待CPU调度。
+3. Blocked: 线程阻塞等待监视器锁定的线程状态。
+处于synchronized同步代码块或方法中被阻塞。
+4. Waiting: 等待线程的线程状态。下 列不带超时的方式:
+Object.wait、Thread.join、 LockSupport.park
+5. Timed Waiting:具有指定等待时间的等待线程的线程状态。下 列带超时的方式:
 Thread.sleep、0bject.wait、 Thread.join、 LockSupport.parkNanos、 LockSupport.parkUntil
-- Terminated
-终止线程的线程状态。线程正常完成执行或出现异常。
+6. Terminated: 终止线程的线程状态。线程正常完成执行或者出现异常。
 
-文字说的还不是太清楚了，让我来你画个图就一目了然了：
-- Thread状态机
+## 流程图
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTNkNTRmZDU3MzAwMzg2OGIucG5n?x-oss-process=image/format,png)
-- 上面那个图太复杂了看不懂？没问题，看个小学生版：
+
 ![](https://img-blog.csdnimg.cn/20190826024812640.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210415135011810.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 1 NEW
-- **`线程还没有开始执行`**
-![](https://img-blog.csdnimg.cn/20210415140615814.png)
-实现Runnable接口和继承Thread可以得到一个线程类，new一个实例出来，线程就进入了 **NEW** 状态。
+![](https://img-blog.csdnimg.cn/20190826031245454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+实现Runnable接口和继承Thread可以得到一个线程类，new一个实例出来，线程就进入了初始状态
+
+**`线程还是没有开始执行`**
+
+有状态了，那肯定是已经创建好线程对象了（如果对象都没有，何来状态这说），
+问题的焦点就在于还没有开始执行，当调用线程的start()方法时，线程不一定会马上执行，因为`Java线程是映射到操作系统的线程执行`，此时可能还需要等操作系统调度，但此时该线程的状态已经为**RUNNABLE**
 
-当调用线程的**start()**方法，线程也不一定会马上执行，因为`Java线程是映射到os的线程执行的`，此时可能还需要等os调度，但此时该线程的状态已经为**RUNNABLE**。
-![](https://img-blog.csdnimg.cn/20210415142005133.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 2 RUNNABLE
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTc0M2ZkYmE4MjZhZTU4NzMucG5n?x-oss-process=image/format,png)
 
 只是说你有资格运行，调度程序没有挑选到你，你就永远是可运行状态。
 
 ## 2.1条件
-- 调用start()
-- 当前线程sleep()结束
-- 其他线程join()结束
-- 等待用户输入完毕
-- 某个线程拿到对象锁
-- 当前线程时间片用完
-- 调用当前线程的yield()
+- 调用start()，进入可运行态
+- 当前线程sleep()结束，其他线程join()结束，等待用户输入完毕，某个线程拿到对象锁，这些线程也将进入可运行状态
+- 当前线程时间片用完，调用当前线程的yield()方法，当前线程进入可运行状态
 - 锁池里的线程拿到对象锁后，进入可运行状态
-- 正在执行的线程
+- 正在执行线程必属于此态
 
-该状态最有争议，注释说它表示线程在JVM层面是执行的，但在os不一定，它举例是CPU，毫无疑问CPU是一个os资源，但这也就意味着在等操作系统其他资源的时候，线程也会是这个状态
+这个状态是最有争议的，注释中说了，它表示线程在JVM层面是执行的，但在操作系统层面不一定，它举例是CPU，毫无疑问CPU是一个操作系统资源，但这也就意味着在等操作系统其他资源的时候，线程也会是这个状态
 
 > 这里就有一个关键点IO阻塞算是等操作系统的资源？
 # 3 BLOCKED
-- 线程由于等待监视器锁，被阻塞。 处于阻塞态的线程在调用`Object.wait`之后正在等待监视器锁 进入 同步的块/方法或 再进入 同步的块/方法
-![](https://img-blog.csdnimg.cn/20210420231639649.png)
-
-被挂起，线程因为某原因放弃cpu 时间片，暂时停止运行。
+![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWFkMGY4NWY2MjlmZmZiY2EucG5n?x-oss-process=image/format,png)
+被挂起,线程因为某种原因放弃了cpu timeslice，暂时停止运行。
 
 ## 3.1条件
-- 当前线程调用`Thread.sleep()`
-- 运行在当前线程里的其它线程调用`join()`，当前线程进入阻塞态
-- 等待用户输入时，当前线程进入阻塞态
+- 当前线程调用Thread.sleep()，进入阻塞态
+- 运行在当前线程里的其它线程调用join()，当前线程进入阻塞态。
+- 等待用户输入的时候，当前线程进入阻塞态。
 
 ## 3.2 分类
 - 等待阻塞
-运行的线程执行o.wait()方法，JVM会把该线程放入等待队列(waitting queue)
+运行的线程执行o.wait()方法，JVM会把该线程放入等待队列(waitting queue)中
 
 - 同步阻塞
-运行的线程在获取对象的同步锁时，若该同步锁被别的线程占用，则JVM会把该线程放入锁池(lock pool)
+运行的线程在获取对象的同步锁时，若该同步锁被别的线程占用，则JVM会把该线程放入锁池(lock pool)中 
 
 - 其他阻塞
-运行的线程执行Thread.sleep(long ms)或t.join()方法，或者发出了I/O请求时，JVM会把该线程置为阻塞状态。
-当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时，线程重新转入可运行(runnable)状态。
+运行的线程执行Thread.sleep(long ms)或t.join()方法，或者发出了I/O请求时，JVM会把该线程置为阻塞状态
+当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时，线程重新转入可运行(runnable)状态
 
 线程在阻塞等待monitor lock(监视器锁)
 一个线程在进入synchronized修饰的临界区的时候,或者在synchronized临界区中调用Object.wait然后被唤醒重新进入synchronized临界区都对应该态。
 
 结合上面RUNNABLE的分析,也就是I/O阻塞不会进入BLOCKED状态,只有synchronized会导致线程进入该状态
 
-关于BLOCKED状态，注释里只提到一种情况就是进入synchronized声明的临界区时会导致，这好理解，synchronized是JVM自己控制的，所以这个阻塞事件它自己能够知道（对比理解上面的os层面）。
+关于BLOCKED状态，注释里只提到一种情况就是进入synchronized声明的临界区时会导致，这个也很好理解，synchronized是JVM自己控制的，所以这个阻塞事件它自己能够知道（对比理解上面的操作系统层面）。
 
-interrupt()是无法唤醒的，只是做个标记。
+interrupt()是无法唤醒的!只是做个标记而已!
 
 # 4 等待
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQ5MmNhYTA5MzBlYzhmODcucG5n?x-oss-process=image/format,png)
@@ -207,9 +192,24 @@ while(true) {
 2.  当一个线程1被另外一个线程2唤醒时，1线程进入锁池状态，去争夺对象锁。
 3.  锁池是在同步的环境下才有的概念，`一个对象对应一个锁池`
 
-
+# **几个方法的比较**
+- Thread.sleep(long millis)
+一定是当前线程调用此方法，当前线程进入阻塞，不释放对象锁，millis后线程自动苏醒进入可运行态。
+作用：给其它线程执行机会的最佳方式。
+- Thread.yield()
+一定是当前线程调用此方法，当前线程放弃获取的cpu时间片，由运行状态变会可运行状态，让OS再次选择线程。
+作用：让相同优先级的线程轮流执行，但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的，因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。
+3.  t.join()/t.join(long millis)，当前线程里调用其它线程1的join方法，当前线程阻塞，但不释放对象锁，直到线程1执行完毕或者millis时间到，当前线程进入可运行状态。
 4.  obj.wait()，当前线程调用对象的wait()方法，当前线程释放对象锁，进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout)timeout时间到自动唤醒。
 5.  obj.notify()唤醒在此对象监视器上等待的单个线程，选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
 
-- 当对象锁被某一线程释放的一瞬间，锁池里面的哪个线程能获得这个锁？随机？队列FIFO？or sth else？
-- 等待队列里许许多多的线程都wait()在一个对象上，此时某一线程调用了对象的notify()方法，那唤醒的到底是哪个线程？随机？队列FIFO？or sth else？java文档就简单的写了句：选择是任意性的。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
\ No newline at end of file
+# **疑问**
+1.  当对象锁被某一线程释放的一瞬间，锁池里面的哪个线程能获得这个锁？随机？队列FIFO？or sth else？
+2.  等待队列里许许多多的线程都wait()在一个对象上，此时某一线程调用了对象的notify()方法，那唤醒的到底是哪个线程？随机？队列FIFO？or sth else？java文档就简单的写了句：选择是任意性的。
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\350\257\255\346\263\225\347\263\226.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\350\257\255\346\263\225\347\263\226.md"
new file mode 100644
index 0000000000..dacb3b9a0b
--- /dev/null
+++ "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\350\257\255\346\263\225\347\263\226.md"
@@ -0,0 +1,27 @@
+#1 泛型与类型擦除
+泛型是JDK 1.5的一项新增特性，它的本质是参数化类型(Parametersized Type) 的应用，也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类，接口和方法的创建中, 分別称为泛型类、泛型接口和泛型方法。
+
+在Java语言处于还没有出现泛型的版本时。只能通过Object 是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。例如，在哈希表的存取中,JDK 1.5之前使用HashMap的get() 方法，返回值就是个0bject。由于Java语言里面所有的类型都维承于java.lang.Object 所以Object转型成任何对象都是有可能的。但是也因为有无限的可能性。就只有程序员和运行期的虚拟机才知道这个Objet到底是个什么类型的对象。在编译期间，编译器无法检查这个Object的强制转型是否成功。如果仅仅依赖程序员去保障这项操作的正确性，许多ClassCastException的风险就会转嫁到程予运行期之中。
+
+Java语言中的泛型则不一样，它只在程序源码中存在，在编译后的字节码文件中，就已经替换为原来的原生类型(Raw Type) ，并且在相应的地方插入了强制转型代码，因此，对于运行期的Java来说Araylist<int>Aralist<String>就是同一个类。所以泛型是Java语言的一颗语法糖Java称为类型擦除，基于这种方法实现的泛型称为伪泛型。
+
+![泛型擦除前的例子](https://upload-images.jianshu.io/upload_images/4685968-2c93db4ccfaf1fc4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+把这段Java代码编译成Class文件，然后再用字节码反编译后，將会发现泛型都不见了，又变回了Java泛型出现之前的写法，泛型类型都变回了原类型.如
+![](https://upload-images.jianshu.io/upload_images/4685968-73b7f4720749ecbb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+通过擦除來实现泛型丧失了一些泛型思想应有的优雅
+![当泛型遇见重载1](https://upload-images.jianshu.io/upload_images/4685968-38a718d9584c5ed6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+不能被编译的，因为参数List<Integer>和List<String>编译之后都被擦除了。变成了一样的原生类型List<E>,擦除动作导致这两种方法的特征签名变得一模一样。初步看来，无法重载的原因已经找到了，但真的就如此吗? 只能说，泛型擦除成相同的原生类型只是无法重载的部分原因
+
+![当泛型遇见置载2](https://upload-images.jianshu.io/upload_images/4685968-f6c1eeaed390cd9a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+由于Java泛型的引入,各种场景(虚拟机解析,反射等> 下的方法调用都可能对原有基础产生影响，如在泛型类中如何获取传入的参数化类型等。因此，JCP组织对虚拟机规范做出了相应的修改，引人了诺如Signature,LocalVariableTypeTable 等新的属性用于解决伴随泛型而来的参数类型的识别问题，Signature 是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型，而是包括了参数化类型的信息。修改后的虚拟机规范裂水所有能识别49.0以上版本的Class
+文件的虚拟机都要能正确地识别Signature参数.
+
+从Signature属性的出现我们还可以得出结论，所谓的擦除，仅仅是对方法的Code属性中的宇节码进行擦除，实际上元数据还是保留了泛型信息,这也是我们能通过反射取得参数化类型的根本依据。
+![自动装箱: 拆箱与遍历循环](https://upload-images.jianshu.io/upload_images/4685968-2866b12ca5564476.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![自动装箱: 拆箱与遍历循环编译后](https://upload-images.jianshu.io/upload_images/4685968-0433b37de73e7230.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+遍历循环则把代码还原成了迭代器的实现，这也是为何遍历循环需要被遍历的类实现Iterable接口的原因。最后再看看变长参数，它在调用的时候变成了一个数组类型的参数,在变长参数出现之前，程序员就是使用数组来完成类似功能的。
+![](https://upload-images.jianshu.io/upload_images/4685968-7fe6b94f0458dfda.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\350\257\255\346\263\225\347\263\226\344\271\213\346\263\233\345\236\213\344\270\216\347\261\273\345\236\213\346\223\246\351\231\244.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\350\257\255\346\263\225\347\263\226\344\271\213\346\263\233\345\236\213\344\270\216\347\261\273\345\236\213\346\223\246\351\231\244.md"
deleted file mode 100644
index d1adafb052..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java\350\257\255\346\263\225\347\263\226\344\271\213\346\263\233\345\236\213\344\270\216\347\261\273\345\236\213\346\223\246\351\231\244.md"
+++ /dev/null
@@ -1,38 +0,0 @@
-# 1 泛型与类型擦除
-泛型，JDK 1.5新特性，本质是参数化类型(Parametersized Type) 的应用，即所操作的数据类型被指定为一个参数。这种参数类型可用在：
-- 类
-- 接口
-- 方法
-
-的创建中, 分别称为：
-- 泛型类
-- 泛型接口
-- 泛型方法
-
-在Java还没有泛型的版本时。只能通过：
-1. Object 是所有类型的父类
-2. 类型强制转换
-
-两个特性协作实现类型泛化。例如，在哈希表的存取中，JDK 1.5之前使用HashMap的get() 方法，返回值就是个Object。由于Java语言里面所有的类型都维承于**java.lang.Object**，所以Object转型成任何对象都有可能。但也因为有无限的可能性，就只有程序员和运行期的虚拟机才知道这个Objet到底是个什么类型的对象。
-编译期间，编译器无法检查该Object的强制转型是否成功。若仅仅依赖程序员去保障正确性，许多ClassCastException的风险就会延迟到程序运行期。
-
-Java语言中的泛型则不一样，它只在程序源码中存在，在编译后的字节码文件中，就已经替换为原来的原生类型(Raw Type) ，并在相应地方插入强制转换代码。
-因此，对运行期的Java来说`Araylist<int>`、`Aralist<String>`是同一个类。所以泛型是Java语言的一颗语法糖Java称为类型擦除，基于这种方法实现的泛型称为伪泛型。
-- 泛型擦除前的例子
-![](https://img-blog.csdnimg.cn/img_convert/d347cb20042fbdffec7af32a5cef72b4.png)
-把这段Java代码编译成Class文件，然后再用字节码反编译后，將会发现泛型都不见了，又变回了Java泛型出现之前的写法，泛型类型都变回了原类型。如：
-![](https://img-blog.csdnimg.cn/img_convert/9f9d591480dae3af68c7017b6a39419d.png)
-通过擦除实现泛型，丧失了一些泛型思想应有的优雅
-- 当泛型遇见重载1
-![](https://img-blog.csdnimg.cn/img_convert/c1980fb6370cdc0e40467bc8ed51fbf3.png)
-不能被编译的，因为参数`List<Integer>`和`List<String>`编译之后都被擦除了。变成了一样的原生类型`List<E>`,擦除动作导致这两种方法的特征签名变得一模一样。初步看来，无法重载的原因已经找到了，但真的就如此吗? 只能说，泛型擦除成相同的原生类型只是无法重载的部分原因
-- 当泛型遇见置载2
-![](https://img-blog.csdnimg.cn/img_convert/7122295ac6c3431e2b732e6668008da6.png)
-由于Java泛型的引入，各种场景（虚拟机解析、反射等）下的方法调用都可能对原有基础产生影响，如在泛型类中如何获取传入的参数化类型等。因此，JCP组织对虚拟机规范做出了相应的修改，引入了诸如**Signature、LocalVariableTypeTable** 等新的属性用于解决伴随泛型而来的参数类型的识别问题，Signature 是其中最重要的一项属性,它的作用就是存储一个方法在字节码层面的特征签名,这个属性中保存的参数类型并不是原生类型，而是包括了参数化类型的信息。修改后的虚拟机规范要求所有能识别49.0以上版本的Class文件的虚拟机都要能正确地识别Signature参数。
-
-从Signature属性的出现我们还可以得出结论，所谓的擦除，仅仅是对方法的Code属性中的字节码进行擦除，实际上元数据还是保留了泛型信息,这也是我们能通过反射取得参数化类型的根本依据。
-- 自动装箱: 拆箱与遍历循环
-![](https://img-blog.csdnimg.cn/img_convert/ac763ea9a774a914879ccd27cbdd0498.png)
-- 自动装箱: 拆箱与遍历循环编译后![](https://img-blog.csdnimg.cn/img_convert/51a73df3f1ed22fef870234c2ae178a6.png)
-遍历循环则把代码还原成了迭代器的实现，这也是为何遍历循环需要被遍历的类实现Iterable接口的原因。最后再看看变长参数，它在调用的时候变成了一个数组类型的参数,在变长参数出现之前，程序员就是使用数组来完成类似功能的。
-![](https://img-blog.csdnimg.cn/img_convert/8909a05f4b0a56f95cca330f36147b52.png)
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\347\232\204NIO\346\226\260\346\226\207\344\273\266IO\345\210\260\345\272\225\346\234\211\345\244\232\345\245\275\347\224\250\357\274\237.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/nio.file\346\226\260IO API.md"
similarity index 100%
rename from "JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/Java8\347\232\204NIO\346\226\260\346\226\207\344\273\266IO\345\210\260\345\272\225\346\234\211\345\244\232\345\245\275\347\224\250\357\274\237.md"
rename to "JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/nio.file\346\226\260IO API.md"
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\344\270\200\346\226\207\346\220\236\346\207\202Java\347\232\204SPI\346\234\272\345\210\266.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\344\270\200\346\226\207\346\220\236\346\207\202Java\347\232\204SPI\346\234\272\345\210\266.md"
deleted file mode 100644
index 1c69b61c1e..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\344\270\200\346\226\207\346\220\236\346\207\202Java\347\232\204SPI\346\234\272\345\210\266.md"
+++ /dev/null
@@ -1,63 +0,0 @@
-# 1 简介
-SPI，Service Provider Interface，一种服务发现机制。
-![](https://img-blog.csdnimg.cn/7921efaa5683447cbb1bc6cf351c4332.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-有了SPI，即可实现服务接口与服务实现的解耦：
-- 服务提供者（如 springboot starter）提供出 SPI 接口。身为服务提供者，在你无法形成绝对规范强制时，适度"放权" 比较明智，适当让客户端去自定义实现
-- 客户端（普通的 springboot 项目）即可通过本地注册的形式，将实现类注册到服务端，轻松实现可插拔
-
-## 缺点
-- 不能按需加载。虽然 ServiceLoader 做了延迟加载，但是只能通过遍历的方式全部获取。如果其中某些实现类很耗时，而且你也不需要加载它，那么就形成了资源浪费
-- 获取某个实现类的方式不够灵活，只能通过迭代器的形式获取
-
-> Dubbo SPI 实现方式对以上两点进行了业务优化。
-
-# 源码
-![](https://img-blog.csdnimg.cn/339efe7e74764bbc91f8ea037c3f69a6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-应用程序通过迭代器接口获取对象实例，这里首先会判断 providers 对象中是否有实例对象：
-- 有实例，那么就返回
-- 没有，执行类的装载步骤，具体类装载实现如下：
-
-LazyIterator#hasNextService 读取 META-INF/services 下的配置文件，获得所有能被实例化的类的名称，并完成 SPI 配置文件的解析
-
-
-LazyIterator#nextService 负责实例化 hasNextService() 读到的实现类，并将实例化后的对象存放到 providers 集合中缓存
-
-# 使用
-如某接口有3个实现类，那系统运行时，该接口到底选择哪个实现类呢？
-这时就需要SPI，**根据指定或默认配置，找到对应实现类，加载进来，然后使用该实现类实例**。
-
-如下系统运行时，加载配置，用实现A2实例化一个对象来提供服务：
-![](https://img-blog.csdnimg.cn/20201220141747102.png)
-再如，你要通过jar包给某个接口提供实现，就在自己jar包的`META-INF/services/`目录下放一个接口同名文件，指定接口的实现是自己这个jar包里的某类即可：
-![](https://img-blog.csdnimg.cn/20201220142131599.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-别人用这个接口，然后用你的jar包，就会在运行时通过你的jar包指定文件找到这个接口该用哪个实现类。这是JDK内置提供的功能。
-
-> 我就不定义在 META-INF/services 下面行不行？就想定义在别的地方可以吗？
-
-No！JDK 已经规定好配置路径，你若随便定义，类加载器可就不知道去哪里加载了
-![](https://img-blog.csdnimg.cn/bba23763598a4d19a80616e85623c7c9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-假设你有个工程P，有个接口A，A在P无实现类，系统运行时怎么给A选实现类呢?
-可以自己搞个jar包，`META-INF/services/`，放上一个文件，文件名即接口名，接口A的实现类=`com.javaedge.service.实现类A2`。
-让P来依赖你的jar包，等系统运行时，P跑起来了，对于接口A，就会扫描依赖的jar包，看看有没有`META-INF/services`文件夹：
-- 有，再看看有无名为接口A的文件：
-	- 有，在里面查找指定的接口A的实现是你的jar包里的哪个类即可
-# 适用场景
-## 插件扩展
-比如你开发了一个开源框架，若你想让别人自己写个插件，安排到你的开源框架里中，扩展功能时。
-
-如JDBC。Java定义了一套JDBC的接口，但并未提供具体实现类，而是在不同云厂商提供的数据库实现包。
-> 但项目运行时，要使用JDBC接口的哪些实现类呢?
-
-一般要根据自己使用的数据库驱动jar包，比如我们最常用的MySQL，其`mysql-jdbc-connector.jar` 里面就有：
-![](https://img-blog.csdnimg.cn/20201220151405844.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-系统运行时碰到你使用JDBC的接口，就会在底层使用你引入的那个jar中提供的实现类。
-## 案例
-如sharding-jdbc 数据加密模块，本身支持 AES 和 MD5 两种加密方式。但若客户端不想用内置的两种加密，偏偏想用 RSA 算法呢？难道每加一种算法，sharding-jdbc 就要发个版本？
-
-sharding-jdbc 可不会这么蠢，首先提供出 EncryptAlgorithm 加密算法接口，并引入 SPI 机制，做到服务接口与服务实现分离的效果。
-客户端想要使用自定义加密算法，只需在客户端项目 `META-INF/services` 的路径下定义接口的全限定名称文件，并在文件内写上加密实现类的全限定名
-![](https://img-blog.csdnimg.cn/fea9f40870554ee8b579af6e34e22171.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/2025dd20872942c8a0560338787e9a63.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-这就显示了SPI的优点：
-- 客户端（自己的项目）提供了服务端（sharding-jdbc）的接口自定义实现，但是与服务端状态分离，只有在客户端提供了自定义接口实现时才会加载，其它并没有关联；客户端的新增或删除实现类不会影响服务端
-- 如果客户端不想要 RSA 算法，又想要使用内置的 AES 算法，那么可以随时删掉实现类，可扩展性强，插件化架构
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\345\255\220\347\261\273\345\217\257\344\273\245\347\273\247\346\211\277\345\210\260\347\210\266\347\261\273\344\270\212\347\232\204\346\263\250\350\247\243\345\220\227\357\274\237.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\345\255\220\347\261\273\345\217\257\344\273\245\347\273\247\346\211\277\345\210\260\347\210\266\347\261\273\344\270\212\347\232\204\346\263\250\350\247\243\345\220\227\357\274\237.md"
deleted file mode 100644
index e48364b7f6..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\345\255\220\347\261\273\345\217\257\344\273\245\347\273\247\346\211\277\345\210\260\347\210\266\347\261\273\344\270\212\347\232\204\346\263\250\350\247\243\345\220\227\357\274\237.md"
+++ /dev/null
@@ -1,68 +0,0 @@
-![](https://img-blog.csdnimg.cn/20210703161507167.png)
-
-> 子类重写父类方法后，可以继承方法上的注解吗？
-
-![](https://img-blog.csdnimg.cn/20210703161525416.png)
-这个不急，让我来分析一下，假设有如下注解：
-![](https://img-blog.csdnimg.cn/20210703172456417.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-
-- 定义被注解的类
-![](https://img-blog.csdnimg.cn/2021070316221838.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-
--  子类直接继承父类
-![](https://img-blog.csdnimg.cn/20210703162259213.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-
--  获取父子类和方法的注解信息，并输出注解的value属性的值
-![](https://img-blog.csdnimg.cn/20210703164459591.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-- 日志输出
-![](https://img-blog.csdnimg.cn/20210703172622900.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-可见**子类及子类的方法，无法自动继承父类和父类方法上的注解**。
-
-不对呀，你得使用`@Inherited`元注解才能实现注解的继承！行，那咱就加上![](https://img-blog.csdnimg.cn/20210703173231671.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 再看一遍控制台信息
-![](https://img-blog.csdnimg.cn/20210703173317381.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-可见使用`@Inherited`只能实现类上的注解继承。
-
-> 那么如何实现方法上注解的继承呢？
-
-最简单暴力地，可通过反射技术，在继承链找到对应方法上的注解。但这样很麻烦，还需要考虑桥接方法。幸好Spring足够强大，提供了**AnnotatedElementUtils**类。
-### 对`@Inherited`的支持
-遵循get语义的方法将遵循Java的`@Inherited`注解的约定，除了在本地声明的批注（包括自定义组成的注解）优于继承的注解之外。相反，遵循find语义的方法将完全忽略`@Inherited`的存在，因为find搜索算法手动遍历类型和方法层次结构，从而隐式支持注解继承，而无需`@Inherited`。
-
-### Find V.S Get Semantics
-此类中的方法使用的搜索算法遵循find或get语义。
-#### Get 语义
-仅限于搜索存在于`AnnotatedElement`上的注解（即在本地声明或继承）或在AnnotatedElement上方的注解层次结构中声明的注释。
-####  Find 语义
-更加详尽，提供了获取语义以及对以下内容的支持：
-- 搜索接口（如果带注释的元素是类）
-- 搜索超类（如果带注释的元素是一个类）
-- 解析桥接方法（如果带注释的元素是方法）
-- 如果带注解的元素是方法，则在接口中搜索方法
-- 如果带注解的元素是方法，则在超类中搜索方法
-
-如下俩方法其实也很相像，有何区别呢？
-##### findAllMergedAnnotations
-Find 对应 `SearchStrategy.TYPE_HIERARCHY`
-![](https://img-blog.csdnimg.cn/20201120223339913.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-**findMergedAnnotation**方法可一次性找出父类和接口、父类方法和接口方法上的注解
-![](https://img-blog.csdnimg.cn/2021070317413821.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-##### getAllMergedAnnotations
-Get对应 `SearchStrategy.INHERITED_ANNOTATIONS`：
-![](https://img-blog.csdnimg.cn/20201120223435146.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-> 想想 Spring 的@Service、@Controller 等注解支持继承吗？
-
-我们通常的controller类，都会使用controller注解，如果可以被继承的话，Spring就不会只让我们使用Controller注解了，会提供另一种方式注入Controller组件，就是继承BaseController类。
-Spring 官方对此也有回应：继承的问题在于那些注解真的应该应用于特定的具体类。
-
-> 参考
-> - https://github.com/spring-projects/spring-framework/issues/8859
-> - https://docs.oracle.com/javase/8/docs/api/java/lang/annotation/Inherited.html
-> https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/annotation/AnnotatedElementUtils.html
-> https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
\ No newline at end of file
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\346\267\261\345\205\245\345\210\206\346\236\220-Java-\347\232\204\346\236\232\344\270\276-enum.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\346\267\261\345\205\245\345\210\206\346\236\220-Java-\347\232\204\346\236\232\344\270\276-enum.md"
index b64692d19d..f8a4e2a73c 100644
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\346\267\261\345\205\245\345\210\206\346\236\220-Java-\347\232\204\346\236\232\344\270\276-enum.md"
+++ "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\346\267\261\345\205\245\345\210\206\346\236\220-Java-\347\232\204\346\236\232\344\270\276-enum.md"
@@ -5,7 +5,7 @@
 创建需要enum关键字，如：
 
 ```java
-public enum Color {  
+public enum Color{  
     RED, GREEN, BLUE, BLACK, PINK, WHITE;  
 }
 ```
@@ -13,10 +13,11 @@ public enum Color {
 enum的语法看似与类不同，但它实际上就是一个类。
 
 
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTIyOGI1MmY1ZmE5M2E5ZWYucG5n?x-oss-process=image/format,png)
+![](https://upload-images.jianshu.io/upload_images/4685968-228b52f5fa93a9ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 把上面的编译成 Gender.class, 然后用 ` javap -c Gender `反编译
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTk4NTJkMDc3YTJkZTJkMzUucG5n?x-oss-process=image/format,png)
+
+![](https://upload-images.jianshu.io/upload_images/4685968-9852d077a2de2d35.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 可得到
 - Gender 是 final 的
@@ -26,6 +27,7 @@ enum的语法看似与类不同，但它实际上就是一个类。
 - static{} 对所有成员进行初始化
 
 结合字节码，还原 Gender 的普通类形式
+
 ```java
 public final class Gender extends java.lang.Enum {
 
@@ -54,29 +56,29 @@ public final class Gender extends java.lang.Enum {
 ```
 创建的枚举类型默认是java.lang.enum<枚举类型名>（抽象类）的子类
 
-每个枚举项的类型都为`public static final`。
+每个枚举项的类型都为public static final 。
 
 上面的那个类是无法编译的，因为编译器限制了我们显式的继承自 java.Lang.Enum 类, 报错 "The type Gender may not subclass Enum explicitly", 虽然 java.Lang.Enum 声明的是
 
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTI0NTA4NjYxMDNjYWU3N2MucG5n?x-oss-process=image/format,png)
+![](https://upload-images.jianshu.io/upload_images/4685968-2450866103cae77c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 这样看来枚举类其实用了多例模式，枚举类的实例是有范围限制的
 它同样像我们的传统常量类，只是它的元素是有限的枚举类本身的实例
-它继承自 java.lang.Enum, 所以可以直接调用 java.lang.Enum 的方法，如 name(), original() 等。
+它继承自 java.lang.Enum, 所以可以直接调用 java.lang.Enum 的方法，如 name(), original() 等
 name 就是常量名称
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTM4MzcxMzc4M2JmMzUyYjkucG5n?x-oss-process=image/format,png)
+![](https://upload-images.jianshu.io/upload_images/4685968-383713783bf352b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 original 与 C 的枚举一样的编号
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTAwYmY1YzYwMjk3NjA0MmUucG5n?x-oss-process=image/format,png)
+![](https://upload-images.jianshu.io/upload_images/4685968-00bf5c602976042e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 因为Java的单继承机制，emum不能再用extends继承其他的类。
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtOTBiZWEyMGY0ODQ5ZmJiZQ?x-oss-process=image/format,png) 
+![](http://upload-images.jianshu.io/upload_images/4685968-90bea20f4849fbbe?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 
 可以在枚举类中自定义构造方法，但必须是  private 或  package protected, 因为枚举本质上是不允许在外面用 new Gender() 方式来构造实例的(Cannot instantiate the type Gender)
 
 结合枚举实现接口以及自定义方法，可以写出下面那样的代码
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWE4ZGFiNDMyYTZjMzVlZDEucG5n?x-oss-process=image/format,png)
+![](https://upload-images.jianshu.io/upload_images/4685968-a8dab432a6c35ed1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 方法可以定义成所有实例公有，也可以让个别元素独有
 
 需要特别注明一下，上面在 Male {} 声明一个 print() 方法后实际产生一个 Gender 的匿名子类，编译后的 Gender$1，反编译它
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTI3ZGZkNmFjMjJhZDdmNmUucG5n?x-oss-process=image/format,png)
+![](https://upload-images.jianshu.io/upload_images/4685968-27dfd6ac22ad7f6e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 所以在 emum Gender 那个枚举中的成员  Male 相当于是
 ```
 public static final Male = new Gender$1("Male", 0); //而不是 new Gender("Male", 0)
@@ -84,7 +86,7 @@ public static final Male = new Gender$1("Male", 0); //而不是 new Gender("Male
 上面` 4: Invokespecial #1` 要调用到下面的` Gender(java.lang.String, int, Gender$1) `方法
 
 若要研究完整的 Male 元素的初始化过程就得 javap -c Gender 看 Gender.java 产生的所有字节码，在此列出片断
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWIxOWNjNTZhZGE4NTFjMzcucG5n?x-oss-process=image/format,png)
+![](https://upload-images.jianshu.io/upload_images/4685968-b19cc56ada851c37.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 在 static{} 中大致看下 Male 的初始过程：加载 Gender$1, 并调用它的 Gender$1(java.lang.String, int) 构造函数生成一个 Gender$1 实例赋给 Male 属性
 
 
@@ -155,34 +157,35 @@ Enum<Color>("WHITE", 5);
 
 **枚举类型的常用方法：**
 
-int compareTo(E o)  比较此枚举与指定对象的顺序。
+int compareTo(E o)  比较此枚举与指定对象的顺序。
 
-Class<E> getDeclaringClass()  返回与此枚举常量的枚举类型相对应的 Class 对象。
+Class<E> getDeclaringClass()  返回与此枚举常量的枚举类型相对应的 Class 对象。
 
-String name()   返回此枚举常量的名称，在其枚举声明中对其进行声明。
+String name()   返回此枚举常量的名称，在其枚举声明中对其进行声明。
 
-int ordinal()  返回枚举常量的序数（它在枚举声明中的位置，其中初始常量序数为零
+int ordinal()  返回枚举常量的序数（它在枚举声明中的位置，其中初始常量序数为零
 
-String toString()    返回枚举常量的名称，它包含在声明中。
+String toString()    返回枚举常量的名称，它包含在声明中。
 
-static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)         返回带指定名称的指定枚举类型的枚举常量。
+static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)         返回带指定名称的指定枚举类型的枚举常量。
 
-# 2 常用用法
-## 2.1 常量
+二、常用用法
+用法一：常量
 
 在JDK1.5 之前，我们定义常量都是： public static fianl.... 。现在好了，有了枚举，可以把相关的常量分组到一个枚举类型里，而且枚举提供了比常量更多的方法。
 
-## 2.2 switch
-JDK1.6之前的switch语句只支持int、char、enum类型，使用枚举，能让我们的代码可读性更强。
+用法二：switch
+
+JDK1.6之前的switch语句只支持int,char,enum类型，使用枚举，能让我们的代码可读性更强。
+
 ```java
+enum Color{  
+    RED, GREEN, BLUE, BLACK, PINK, WHITE;  
+}  
 public class TestEnum {  
-
-    public void changeColor() {  
-    
-        Color color = Color.RED; 
-         
-        System.out.println("原色：" + color);
-         
+    public void changeColor(){  
+        Color color = Color.RED;  
+        System.out.println("原色：" + color);  
         switch(color){  
         case RED:  
             color = Color.GREEN;  
@@ -210,14 +213,13 @@ public class TestEnum {
             break;  
         }  
     }  
-    
     public static void main(String[] args){  
         TestEnum testEnum = new TestEnum();  
         testEnum.changeColor();  
     }  
 }
 ```
-## 2.3 实现接口 
+用法三：实现接口 
 ```java
 public interface Behaviour {  
         void print();  
@@ -251,11 +253,30 @@ public interface Behaviour {
         }  
     }
 ```
-## 枚举集合
-- EnumSet保证集合中的元素不重复
-- EnumMap中的 key是enum类型，而value则可以是任意类型
+用法四：枚举集合的应用
 
-![](https://img-blog.csdnimg.cn/20200627221540659.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+java.util.EnumSet和java.util.EnumMap是两个枚举集合。EnumSet保证集合中的元素不重复;EnumMap中的 key是enum类型，而value则可以是任意类型。关于这个两个集合的使用就不在这里赘述，可以参考JDK文档 
+```java
+public class Test {  
+    public static void main(String[] args) {  
+        // EnumSet的使用  
+        EnumSet<EnumTest> weekSet = EnumSet.allOf(EnumTest.class);  
+        for (EnumTest day : weekSet) {  
+            System.out.println(day);  
+        }  
+   
+        // EnumMap的使用  
+        EnumMap<EnumTest, String> weekMap = new EnumMap(EnumTest.class);  
+        weekMap.put(EnumTest.MON, "星期一");  
+        weekMap.put(EnumTest.TUE, "星期二");  
+        // ... ...  
+        for (Iterator<Entry<EnumTest, String>> iter = weekMap.entrySet().iterator(); iter.hasNext();) {  
+            Entry<EnumTest, String> entry = iter.next();  
+            System.out.println(entry.getKey().name() + ":" + entry.getValue());  
+        }  
+    }  
+}
+```
 **三、综合实例**
 ### **最简单的使用**
 最简单的枚举类
@@ -266,11 +287,11 @@ public enum Weekday {
 ```
 如何使用它呢？
 先来看看它有哪些方法：
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMzVhOWRkMTQ4ZWRjNmUzMy5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-35a9dd148edc6e33.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 这是Weekday可以调用的方法和参数。发现它有两个方法：values()和valueOf()。还有我们刚刚定义的七个变量
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZmEwMGM5OTIwNDI5NzU4ZC5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-fa00c9920429758d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 这些事枚举变量的方法。我们接下来会演示几个比较重要的
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNWQ1MGE2NmViYjhlOTFmYi5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-5d50a66ebb8e91fb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 
 这段代码，我们演示了几个常用的方法和功能：
@@ -294,24 +315,24 @@ public enum Weekday {
 5.  枚举变量的compareTo()方法。
 
     > 该方法用来比较两个枚举变量的”大小”，实际上比较的是两个枚举变量的次序，返回两个次序相减后的结果，如果为负数，就证明变量1”小于”变量2 （变量1.compareTo(变量2)，返回【变量1.ordinal() - 变量2.ordinal()】）
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtOTIzNjkyYjQyYmFhMjM0OS5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-923692b42baa2349.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
     > 这是compareTo的源码，会先判断是不是同一个枚举类的变量，然后再返回差值。
 
 6.  枚举类的name()方法。
 
-    > 它和toString()方法的返回值一样，事实上，这两个方法本来就是一样的： 
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZDE4MWQ2YzQyNjI3NjcyMS5wbmc?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZmU1MTVhYzc5ODNlNmFiMi5wbmc?x-oss-process=image/format,png)
+    > 它和toString()方法的返回值一样，事实上，这两个方法本来就是一样的： 
+![](http://upload-images.jianshu.io/upload_images/4685968-d181d6c426276721.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-fe515ac7983e6ab2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
     > 这两个方法的默认实现是一样的，唯一的区别是，你可以重写toString方法。name变量就是枚举变量的字符串形式。
 
 还有一些其他的方法我就暂时不介绍了，感兴趣的话可以自己去看看文档或者源码，都挺简单的。
 
 > **要点：**
 > 
-> *   使用的是enum关键字而不是class。   
-> *   多个枚举变量直接用逗号隔开。  
-> *   枚举变量最好大写，多个单词之间使用”_”隔开（比如：INT_SUM）。 
-> *   定义完所有的变量后，以分号结束，如果只有枚举变量，而没有自定义变量，分号可以省略（例如上面的代码就忽略了分号）。 
+> *   使用的是enum关键字而不是class。   
+> *   多个枚举变量直接用逗号隔开。  
+> *   枚举变量最好大写，多个单词之间使用”_”隔开（比如：INT_SUM）。 
+> *   定义完所有的变量后，以分号结束，如果只有枚举变量，而没有自定义变量，分号可以省略（例如上面的代码就忽略了分号）。 
 > *   在其他类中使用enum变量的时候，只需要【类名.变量名】就可以了，和使用静态变量一样。
 
 但是这种简单的使用显然不能体现出枚举的强大，我们来学习一下复杂的使用：
@@ -341,7 +362,7 @@ public enum Weekday {
 
 看代码：
 
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZmRmOThhMWVhMTQxYTAxMS5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-fdf98a1ea141a011.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 我们对上面的代码做了一些改变：
 
@@ -353,10 +374,10 @@ public enum Weekday {
 
 > 请注意：这里有三点需要注意：
 > 
-> 1.  一定要把枚举变量的定义放在第一行，并且以分号结尾。 
+> 1.  一定要把枚举变量的定义放在第一行，并且以分号结尾。 
 >     
 >     
-> 2.  构造函数必须私有化。事实上，private是多余的，你完全没有必要写，因为它默认并强制是private，如果你要写，也只能写private，写public是不能通过编译的。 
+> 2.  构造函数必须私有化。事实上，private是多余的，你完全没有必要写，因为它默认并强制是private，如果你要写，也只能写private，写public是不能通过编译的。 
 >     
 >     
 > 3.  自定义变量与默认的ordinal属性并不冲突，ordinal还是按照它的规则给每个枚举变量按顺序赋值。
@@ -365,7 +386,7 @@ public enum Weekday {
 
 当然可以：
 
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtY2VlNzUwYzFjMGQ3OTAxYi5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-cee750c1c0d7901b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 你可以定义任何你想要的变量。学完了这些，大概枚举类你也应该掌握了，但是，还有没有其他用法呢?
 
@@ -378,7 +399,7 @@ public enum Weekday {
 你应该知道，有抽象方法的类必然是抽象类，抽象类就需要子类继承它然后实现它的抽象方法，但是呢，枚举类不能被继承。。你是不是有点乱？
 
 我们先来看代码：
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMzJkMGVlZDYxN2NkZWFjYS5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-32d0eed617cdeaca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 你好像懂了点什么。但是你好像又不太懂。为什么一个变量的后边可以带一个代码块并且实现抽象方法呢？
 
@@ -387,10 +408,10 @@ public enum Weekday {
 ## **枚举类的实现原理**
 
 从最简单的看起：
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMDBjN2QwZjQ5NzhjMmFkOC5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-00c7d0f4978c2ad8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 还是这段熟悉的代码，我们编译一下它，再反编译一下看看它到底是什么样子的：
 
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZjhlNzNmNGVkNjk5OGU4Yi5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-f8e73f4ed6998e8b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 
 你是不是觉得很熟悉？反编译出来的代码和我们用静态变量自己写的类出奇的相似！
@@ -402,21 +423,22 @@ public enum Weekday {
 并且，这个类是final的！所以它不能被继承！
 
 回到我们刚才的那个疑问：
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtODA3Y2ZmZTMzYWRlOWU0Ni5wbmc?x-oss-process=image/format,png)
-为什么会有这么神奇的代码？现在你差不多懂了。因为RED本身就是一个TrafficLamp对象的引用。实际上，在初始化这个枚举类的时候，你可以理解为执行的是`TrafficLamp RED = new TrafficLamp（30）` ，但是因为TrafficLamp里面有抽象方法，还记得匿名内部类么？
+![](http://upload-images.jianshu.io/upload_images/4685968-807cffe33ade9e46.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+为什么会有这么神奇的代码？现在你差不多懂了。因为RED本身就是一个TrafficLamp对象的引用。实际上，在初始化这个枚举类的时候，你可以理解为执行的是`TrafficLamp RED = new TrafficLamp（30）` ，但是因为TrafficLamp里面有抽象方法，还记得匿名内部类么？
 
 我们可以这样来创建一个TrafficLamp引用：
 
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMjY0MTYyYzg5OGNlMDJiMy5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-264162c898ce02b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 
 而在枚举类中，我们只需要像上面那样写【`RED(30){}`】就可以了，因为java会自动的去帮我们完成这一系列操作
 
-## 枚举类用法
+## **枚举类的其他用法**
+![ switch语句](http://upload-images.jianshu.io/upload_images/4685968-8cbb5f4a39c40739.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 虽然枚举类不能继承其他类，但是还是可以实现接口的
-![接口定义](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMjY1YzYzNDNjYWE2NWYyZC5wbmc?x-oss-process=image/format,png)
-![实现接口](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMzE5MjhjYTMwZTU5NTc3OC5wbmc?x-oss-process=image/format,png)
-![使用接口组织枚举](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNzkyNWZjNzkxYjQwNDg5OS5wbmc?x-oss-process=image/format,png)
+![接口定义](http://upload-images.jianshu.io/upload_images/4685968-265c6343caa65f2d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![实现接口](http://upload-images.jianshu.io/upload_images/4685968-31928ca30e595778.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![使用接口组织枚举](http://upload-images.jianshu.io/upload_images/4685968-7925fc791b404899.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 
 ### **使用枚举创建单例模式**
@@ -480,8 +502,16 @@ public class Singleton{
 上面的两种方式就是懒汉式和恶汉式单利的创建，但是无论哪一种，都不如枚举来的方便。而且传统的单例模式的另外一个问题是一旦你实现了serializable接口，他们就不再是单例的了。但是枚举类的父类【Enum类】实现了Serializable接口，也就是说，所有的枚举类都是可以实现序列化的，这也是一个优点。
 
 ## **总结**
-- 可以创建一个enum类，把它看做一个普通的类。除了它不能继承其他类了。(java是单继承，它已经继承了Enum),可以添加其他方法，覆盖它本身的方法 
--  switch()参数可以使用enum 
-- values()方法是编译器插入到enum定义中的static方法，所以，当你将enum实例向上转型为父类Enum是，values()就不可访问了。解决办法：在Class中有一个getEnumConstants()方法，所以即便Enum接口中没有values()方法，我们仍然可以通过Class对象取得所有的enum实例 
-- 无法从enum继承子类，如果需要扩展enum中的元素，在一个接口的内部，创建实现该接口的枚举，以此将元素进行分组。达到将枚举元素进行分组。 
-- enum允许程序员为eunm实例编写方法。所以可以为每个enum实例赋予各自不同的行为。
\ No newline at end of file
+> *   可以创建一个enum类，把它看做一个普通的类。除了它不能继承其他类了。(java是单继承，它已经继承了Enum),可以添加其他方法，覆盖它本身的方法 
+>     
+>     
+> *   switch()参数可以使用enum 
+>     
+>     
+> *   values()方法是编译器插入到enum定义中的static方法，所以，当你将enum实例向上转型为父类Enum是，values()就不可访问了。解决办法：在Class中有一个getEnumConstants()方法，所以即便Enum接口中没有values()方法，我们仍然可以通过Class对象取得所有的enum实例 
+>     
+>     
+> *   无法从enum继承子类，如果需要扩展enum中的元素，在一个接口的内部，创建实现该接口的枚举，以此将元素进行分组。达到将枚举元素进行分组。 
+>     
+>     
+> *   enum允许程序员为eunm实例编写方法。所以可以为每个enum实例赋予各自不同的行为。
diff --git "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\346\267\261\345\205\245\350\247\243\346\236\220Java\347\232\204\346\263\250\350\247\243\346\234\272\345\210\266.md" "b/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\346\267\261\345\205\245\350\247\243\346\236\220Java\347\232\204\346\263\250\350\247\243\346\234\272\345\210\266.md"
deleted file mode 100644
index 0533174275..0000000000
--- "a/JDK/Java8\347\274\226\347\250\213\346\234\200\344\275\263\345\256\236\350\267\265/\346\267\261\345\205\245\350\247\243\346\236\220Java\347\232\204\346\263\250\350\247\243\346\234\272\345\210\266.md"
+++ /dev/null
@@ -1,1810 +0,0 @@
-@[toc](目录)
-
-注解（也被称为元数据）为我们在代码中添加信息提供了一种形式化的方式，使我们可以在稍后的某个时刻更容易的使用这些数据。
-
-注解在一定程度上是把元数据和源代码文件结合在一起的趋势所激发的，而不是保存在外部文档。这同样是对像 C# 语言对于 Java 语言特性压力的一种回应。
-
-注解是 Java 5 所引入的众多语言变化之一。它们提供了 Java 无法表达的但是你需要完整表述程序所需的信息。因此，注解使得我们可以以编译器验证的格式存储程序的额外信息。注解可以生成描述符文件，甚至是新的类定义，并且有助于减轻编写“样板”代码的负担。通过使用注解，你可以将元数据保存在 Java 源代码。并拥有如下优势：
-- 简单易读的代码
-- 编译器类型检查
-- 使用 annotation API 为自己的注解构造处理工具
-- 为Java代码提供元数据
-- 暴露功能
-比如Spring的`@Service`、`@Controller`
-
-通过注解配置框架，属于声明式交互:
-- 简化框架配置
-- 和框架解耦
-
-即使 Java 定义了一些类型的元数据，但是一般来说注解类型的添加和如何使用完全取决于你。
-
-注解的语法十分简单，主要是在现有语法中添加 @ 符号。
-Java 5 引入了前三种定义在 **java.lang** 包中的注解：
-- **@Override**：表示当前的方法定义将覆盖基类的方法。如果你不小心拼写错误，或者方法签名被错误拼写的时候，编译器就会发出错误提示。
-- **@Deprecated**：如果使用该注解的元素被调用，编译器就会发出警告信息。
-- **@SuppressWarnings**：关闭不当的编译器警告信息。
-- **@SafeVarargs**：在 Java 7 中加入用于禁止对具有泛型varargs参数的方法或构造函数的调用方发出警告。
-- **@FunctionalInterface**：Java 8 中加入用于表示类型声明为函数式接口
-
-还有 5 种额外的注解类型用于创造新的注解。
-每当创建涉及重复工作的类或接口时，通常可以使用注解来自动化和简化流程。
-
-注解是真正语言层级的概念，以前构造出来就享有编译器的类型检查保护。注解在源代码级别保存所有信息而不是通过注释文字，这使得代码更加整洁和便于维护。通过使用拓展的 annotation API 或外部的字节码工具类库，你会拥有对源代码及字节码强大的检查与操作能力。
-
-# 1  基本语法
-
-<!-- Writing Annotation Processors -->
-
-在下面的例子中，使用 `@Test` 对 `testExecute()` 进行注解。该注解本身不做任何事情，但是编译器要保证其类路径上有 `@Test` 注解的定义。你将在本章看到，我们通过注解创建了一个工具用于运行这个方法：
-
-```java
-// annotations/Testable.java
-package annotations;
-import onjava.atunit.*;
-public class Testable {
-    public void execute() {
-        System.out.println("Executing..");
-    }
-    @Test
-    void testExecute() { execute(); }
-}
-```
-
-被注解标注的方法和其他的方法没有任何区别。在这个例子中，注解 `@Test` 可以和任何修饰符共同用于方法，诸如 **public**、**static** 或 **void**。从语法的角度上看，注解的使用方式和修饰符的使用方式一致。
-
-## 1.1  定义注解
-
-如下是一个注解的定义。注解的定义看起来很像接口的定义。事实上，它们和其他 Java 接口一样，也会被编译成 class 文件。
-
-```java
-// onjava/atunit/Test.java
-// The @Test tag
-package onjava.atunit;
-import java.lang.annotation.*;
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Test {}
-```
-
-除了 @ 符号之外， `@Test` 的定义看起来更像一个空接口。注解的定义也需要一些元注解（meta-annoation），比如 `@Target` 和 `@Retention`。
-- `@Target` 定义你的注解可以应用在哪里（例如是方法还是字段）
-- `@Retention` 定义了注解在哪里可用
-	- 在源代码中（SOURCE）
-	- class文件（CLASS）
-	- 运行时（RUNTIME）
-
-注解通常会包含一些表示特定值的元素。当分析处理注解的时候，程序或工具可以利用这些值。注解的元素看起来就像接口的方法，但是可以为其指定默认值。
-
-不包含任何元素的注解称为标记注解（marker annotation），例如上例中的 `@Test` 就是标记注解。
-
-下面是一个简单的注解，我们可以用它来追踪项目中的用例。程序员可以使用该注解来标注满足特定用例的一个方法或者一组方法。于是，项目经理可以通过统计已经实现的用例来掌控项目的进展，而开发者在维护项目时可以轻松的找到用例用于更新，或者他们可以调试系统中业务逻辑。
-
-```java
-// annotations/UseCase.java
-import java.lang.annotation.*;
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface UseCase {
-    int id();
-    String description() default "no description";
-}
-```
-
-注意 **id** 和 **description** 与方法定义类似。由于编译器会对 **id** 进行类型检查，因此将跟踪数据库与用例文档和源代码相关联是可靠的方式。**description** 元素拥有一个 **default** 值，如果在注解某个方法时没有给出 **description** 的值。则该注解的处理器会使用此元素的默认值。
-
-在下面的类中，有三个方法被注解为用例：
-
-```java
-// annotations/PasswordUtils.java
-import java.util.*;
-public class PasswordUtils {
-    @UseCase(id = 47, description =
-            "Passwords must contain at least one numeric")
-    public boolean validatePassword(String passwd) {
-        return (passwd.matches("\\w*\\d\\w*"));
-    }
-    @UseCase(id = 48)
-    public String encryptPassword(String passwd) {
-        return new StringBuilder(passwd)
-                .reverse().toString();
-    }
-    @UseCase(id = 49, description =
-            "New passwords can't equal previously used ones")
-    public boolean checkForNewPassword(
-            List<String> prevPasswords, String passwd) {
-        return !prevPasswords.contains(passwd);
-    }
-}
-```
-
-注解的元素在使用时表现为 名-值 对的形式，并且需要放置在 `@UseCase` 声明之后的括号内。在 `encryptPassword()` 方法的注解中，并没有给出 **description** 的值，所以在 **@interface UseCase** 的注解处理器分析处理这个类的时候会使用该元素的默认值。
-
-你应该能够想象到如何使用这套工具来“勾勒”出将要建造的系统，然后在建造的过程中逐渐实现系统的各项功能。
-
-## 1.2 元注解
-Java 有 5 种标准注解及 5 种元注解。
-元注解用于注解其他的注解
-
-| 注解        | 解释                                                         |
-| ----------- | ------------------------------------------------------------ |
-| @Target     | 表注解可用于哪些地方。可能的 **ElementType** 参数包括：<br/>**CONSTRUCTOR**：构造器的声明<br/>**FIELD**：字段声明（包括 enum 实例）<br/>**LOCAL_VARIABLE**：局部变量声明<br/>**METHOD**：方法声明<br/>**PACKAGE**：包声明<br/>**PARAMETER**：参数声明<br/>**TYPE**：类、接口（包括注解）或者 enum 声明 |
-| @Retention  | 表示注解信息保存的时长。可选的 **RetentionPolicy** 参数包括：<br/>**SOURCE**：注解将被编译器丢弃<br/>**CLASS**：注解在 class 文件中可用，但是会被 VM 丢弃。<br/>**RUNTIME**：VM 将在运行期也保留注解，因此可以通过反射机制读取注解的信息。 |
-| @Documented | 将此注解保存在 Javadoc 中                                    |
-| @Inherited  | 允许子类继承父类的注解                                       |
-| @Repeatable | 允许一个注解可以被使用一次或者多次（Java 8）。               |
-
-大多数时候，程序员定义自己的注解，并编写自己的处理器来处理他们。
-
-## 编写注解处理器
-
-如果没有用于读取注解的工具，那么注解不会比注释更有用。使用注解中一个很重要的部分就是，创建与使用注解处理器。Java 拓展了反射机制的 API 用于帮助你创造这类工具。同时他还提供了 javac 编译器钩子在编译时使用注解。
-
-下面是一个非常简单的注解处理器，我们用它来读取被注解的 **PasswordUtils** 类，并且使用反射机制来寻找 **@UseCase** 标记。给定一组 **id** 值，然后列出在 **PasswordUtils** 中找到的用例，以及缺失的用例。
-
-```java
-// annotations/UseCaseTracker.java
-import java.util.*;
-import java.util.stream.*;
-import java.lang.reflect.*;
-public class UseCaseTracker {
-    public static void
-    trackUseCases(List<Integer> useCases, Class<?> cl) {
-        for(Method m : cl.getDeclaredMethods()) {
-            UseCase uc = m.getAnnotation(UseCase.class);
-            if(uc != null) {
-                System.out.println("Found Use Case " +
-                        uc.id() + "\n " + uc.description());
-                useCases.remove(Integer.valueOf(uc.id()));
-            }
-        }
-        useCases.forEach(i ->
-                System.out.println("Missing use case " + i));
-    }
-    public static void main(String[] args) {
-        List<Integer> useCases = IntStream.range(47, 51)
-                .boxed().collect(Collectors.toList());
-        trackUseCases(useCases, PasswordUtils.class);
-    }
-}
-```
-
-输出为：
-
-```java
-Found Use Case 48
-no description
-Found Use Case 47
-Passwords must contain at least one numeric
-Found Use Case 49
-New passwords can't equal previously used ones
-Missing use case 50
-```
-
-这个程序用了两个反射的方法：`getDeclaredMethods()`  和 `getAnnotation()`，它们都属于 **AnnotatedElement** 接口（**Class**，**Method** 与 **Field** 类都实现了该接口）。`getAnnotation()` 方法返回指定类型的注解对象，在本例中就是 “**UseCase**”。如果被注解的方法上没有该类型的注解，返回值就为 **null**。我们通过调用 `id()` 和 `description()` 方法来提取元素值。注意 `encryptPassword()` 方法在注解的时候没有指定 **description** 的值，因此处理器在处理它对应的注解时，通过 `description()` 取得的是默认值 “no description”。
-
-### 注解元素
-
-在 **UseCase.java** 中定义的 **@UseCase** 的标签包含 int 元素 **id** 和 String 元素 **description**。注解元素可用的类型如下所示：
-
-- 所有基本类型（int、float、boolean等）
-- String
-- Class
-- enum
-- Annotation
-- 以上类型的数组
-
-如果你使用了其他类型，编译器就会报错。注意，也不允许使用任何包装类型，但是由于自动装箱的存在，这不算是什么限制。注解也可以作为元素的类型。稍后你会看到，注解嵌套是一个非常有用的技巧。
-
-### 默认值限制
-
-编译器对于元素的默认值有些过于挑剔。首先，元素不能有不确定的值。也就是说，元素要么有默认值，要么就在使用注解时提供元素的值。
-
-这里有另外一个限制：任何非基本类型的元素， 无论是在源代码声明时还是在注解接口中定义默认值时，都不能使用 null 作为其值。这个限制使得处理器很难表现一个元素的存在或者缺失的状态，因为在每个注解的声明中，所有的元素都存在，并且具有相应的值。为了绕开这个约束，可以自定义一些特殊的值，比如空字符串或者负数用于表达某个元素不存在。
-
-```java
-// annotations/SimulatingNull.java
-import java.lang.annotation.*;
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface SimulatingNull {
-    int id() default -1;
-    String description() default "";
-}
-```
-
-这是一个在定义注解的习惯用法。
-
-### 生成外部文件
-
-当有些框架需要一些额外的信息才能与你的源代码协同工作，这种情况下注解就会变得十分有用。像 Enterprise JavaBeans (EJB3 之前)这样的技术，每一个 Bean 都需要需要大量的接口和部署描述文件，而这些就是“样板”文件。Web Service，自定义标签库以及对象/关系映射工具（例如 Toplink 和 Hibernate）通常都需要 XML 描述文件，而这些文件脱离于代码之外。除了定义 Java 类，程序员还必须忍受沉闷，重复的提供某些信息，例如类名和包名等已经在原始类中已经提供的信息。每当你使用外部描述文件时，他就拥有了一个类的两个独立信息源，这经常导致代码的同步问题。同时这也要求了为项目工作的程序员在知道如何编写 Java 程序的同时，也必须知道如何编辑描述文件。
-
-假设你想提供一些基本的对象/关系映射功能，能够自动生成数据库表。你可以使用 XML 描述文件来指明类的名字、每个成员以及数据库映射的相关信息。但是，通过使用注解，你可以把所有信息都保存在 **JavaBean** 源文件中。为此你需要一些用于定义数据库表名称、数据库列以及将 SQL 类型映射到属性的注解。
-
-以下是一个注解的定义，它告诉注解处理器应该创建一个数据库表：
-
-```java
-// annotations/database/DBTable.java
-package annotations.database;
-import java.lang.annotation.*;
-@Target(ElementType.TYPE) // Applies to classes only
-@Retention(RetentionPolicy.RUNTIME)
-public @interface DBTable {
-    String name() default "";
-}
-```
-
-在 `@Target` 注解中指定的每一个 **ElementType** 就是一个约束，它告诉编译器，这个自定义的注解只能用于指定的类型。你可以指定 **enum ElementType** 中的一个值，或者以逗号分割的形式指定多个值。如果想要将注解应用于所有的 **ElementType**，那么可以省去 `@Target` 注解，但是这并不常见。
-
-注意 **@DBTable** 中有一个 `name()` 元素，该注解通过这个元素为处理器创建数据库时提供表的名字。
-
-如下是修饰字段的注解：
-
-```java
-// annotations/database/Constraints.java
-package annotations.database;
-import java.lang.annotation.*;
-@Target(ElementType.FIELD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Constraints {
-    boolean primaryKey() default false;
-    boolean allowNull() default true;
-    boolean unique() default false;
-}
-```
-
-```java
-// annotations/database/SQLString.java
-package annotations.database;
-import java.lang.annotation.*;
-@Target(ElementType.FIELD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface SQLString {
-    int value() default 0;
-    String name() default "";
-    Constraints constraints() default @Constraints;
-}
-```
-
-```java
-// annotations/database/SQLInteger.java
-package annotations.database;
-import java.lang.annotation.*;
-@Target(ElementType.FIELD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface SQLInteger {
-    String name() default "";
-    Constraints constraints() default @Constraints;
-}
-```
-
-**@Constraints** 注解允许处理器提供数据库表的元数据。**@Constraints** 代表了数据库通常提供的约束的一小部分，但是它所要表达的思想已经很清楚了。`primaryKey()`，`allowNull()` 和 `unique()` 元素明显的提供了默认值，从而使得在大多数情况下，该注解的使用者不需要输入太多东西。
-
-另外两个 **@interface** 定义的是 SQL 类型。如果希望这个框架更有价值的话，我们应该为每个 SQL 类型都定义相应的注解。不过为为示例，两个元素足够了。
-
-这些 SQL 类型具有 `name()` 元素和 `constraints()` 元素。后者利用了嵌套注解的功能，将数据库列的类型约束信息嵌入其中。注意 `constraints()` 元素的默认值是 **@Constraints**。由于在 **@Constraints** 注解类型之后，没有在括号中指明 **@Constraints** 元素的值，因此，**constraints()** 的默认值为所有元素都为默认值的 **@Constraints** 注解。如果要使得嵌入的  **@Constraints**  注解中的 `unique()` 元素为 true，并作为 `constraints()` 元素的默认值，你可以像如下定义：
-
-```java
-// annotations/database/Uniqueness.java
-// Sample of nested annotations
-package annotations.database;
-public @interface Uniqueness {
-    Constraints constraints()
-            default @Constraints(unique = true);
-}
-```
-
-下面是一个简单的，使用了如上注解的类：
-
-```java
-// annotations/database/Member.java
-package annotations.database;
-@DBTable(name = "MEMBER")
-public class Member {
-    @SQLString(30) String firstName;
-    @SQLString(50) String lastName;
-    @SQLInteger Integer age;
-    @SQLString(value = 30,
-            constraints = @Constraints(primaryKey = true))
-    String reference;
-    static int memberCount;
-    public String getReference() { return reference; }
-    public String getFirstName() { return firstName; }
-    public String getLastName() { return lastName; }
-    @Override
-    public String toString() { return reference; }
-    public Integer getAge() { return age; }
-}
-```
-
-类注解 **@DBTable** 注解给定了元素值 MEMBER，它将会作为标的名字。类的属性 **firstName** 和 **lastName** 都被注解为 **@SQLString** 类型并且给了默认元素值分别为 30 和 50。这些注解都有两个有趣的地方：首先，他们都使用了嵌入的 **@Constraints** 注解的默认值；其次，它们都是用了快捷方式特性。如果你在注解中定义了名为 **value** 的元素，并且在使用该注解时，**value** 为唯一一个需要赋值的元素，你就不需要使用名—值对的语法，你只需要在括号中给出 **value** 元素的值即可。这可以应用于任何合法类型的元素。这也限制了你必须将元素命名为 **value**，不过在上面的例子中，这样的注解语句也更易于理解：
-
-```java
-@SQLString(30)
-```
-
-处理器将在创建表的时候使用该值设置 SQL 列的大小。
-
-默认值的语法虽然很灵巧，但是它很快就变的复杂起来。以 **reference** 字段的注解为例，上面拥有 **@SQLString** 注解，但是这个字段也将成为表的主键，因此在嵌入的 **@Constraint** 注解中设定 **primaryKey** 元素的值。这时事情就变的复杂了。你不得不为这个嵌入的注解使用很长的键—值对的形式，来指定元素名称和 **@interface** 的名称。同时，由于有特殊命名的 **value** 也不是唯一需要赋值的元素，因此不能再使用快捷方式特性。如你所见，最终结果不算清晰易懂。
-
-### 替代方案
-
-可以使用多种不同的方式来定义自己的注解用于上述任务。例如，你可以使用一个单一的注解类 **@TableColumn**，它拥有一个 **enum** 元素，元素值定义了 **STRING**，**INTEGER**，**FLOAT** 等类型。这消除了每个 SQL 类型都需要定义一个 **@interface** 的负担，不过也使得用额外信息修饰 SQL 类型变的不可能，这些额外的信息例如长度或精度等，都可能是非常有用的。
-
-你也可以使用一个 **String** 类型的元素来描述实际的 SQL 类型，比如 “VARCHAR(30)” 或者 “INTEGER”。这使得你可以修饰 SQL 类型，但是这也将 Java 类型到 SQL 类型的映射绑在了一起，这不是一个好的设计。你并不想在数据库更改之后重新编译你的代码；如果我们只需要告诉注解处理器，我们正在使用的是什么“口味（favor）”的 SQL，然后注解助力器来为我们处理 SQL 类型的细节，那将是一个优雅的设计。
-
-第三种可行的方案是一起使用两个注解，**@Constraints** 和相应的 SQL 类型（例如，**@SQLInteger**）去注解同一个字段。这可能会让代码有些混乱，但是编译器允许你对同一个目标使用多个注解。在 Java 8，在使用多个注解的时候，你可以重复使用同一个注解。
-
-### 注解不支持继承
-你不能使用 **extends** 关键字来继承 **@interfaces**。这真是一个遗憾，如果可以定义 **@TableColumn** 注解，同时嵌套一个 **@SQLType** 类型的注解，将成为一个优雅的设计。按照这种方式，你可以通过继承 **@SQLType** 来创造各种 SQL 类型。例如 **@SQLInteger** 和 **@SQLString**。如果支持继承，就会大大减少打字的工作量并且使得语法更整洁。在 Java 的未来版本中，似乎没有任何关于让注解支持继承的提案，所以在当前情况下，上例中的解决方案可能已经是最佳方案了。
-
-### 实现处理器
-下面是一个注解处理器的例子，他将读取一个类文件，检查上面的数据库注解，并生成用于创建数据库的 SQL 命令：
-```java
-// annotations/database/TableCreator.java
-// Reflection-based annotation processor
-// {java annotations.database.TableCreator
-// annotations.database.Member}
-package annotations.database;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.List;
-
-public class TableCreator {
-    public static void
-    main(String[] args) throws Exception {
-        if (args.length < 1) {
-            System.out.println(
-                    "arguments: annotated classes");
-            System.exit(0);
-        }
-        for (String className : args) {
-            Class<?> cl = Class.forName(className);
-            DBTable dbTable = cl.getAnnotation(DBTable.class);
-            if (dbTable == null) {
-                System.out.println(
-                        "No DBTable annotations in class " +
-                                className);
-                continue;
-            }
-            String tableName = dbTable.name();
-            // If the name is empty, use the Class name:
-            if (tableName.length() < 1)
-                tableName = cl.getName().toUpperCase();
-            List<String> columnDefs = new ArrayList<>();
-            for (Field field : cl.getDeclaredFields()) {
-                String columnName = null;
-                Annotation[] anns =
-                        field.getDeclaredAnnotations();
-                if (anns.length < 1)
-                    continue; // Not a db table column
-                if (anns[0] instanceof SQLInteger) {
-                    SQLInteger sInt = (SQLInteger) anns[0];
-                    // Use field name if name not specified
-                    if (sInt.name().length() < 1)
-                        columnName = field.getName().toUpperCase();
-                    else
-                        columnName = sInt.name();
-                    columnDefs.add(columnName + " INT" +
-                            getConstraints(sInt.constraints()));
-                }
-                if (anns[0] instanceof SQLString) {
-                    SQLString sString = (SQLString) anns[0];
-                    // Use field name if name not specified.
-                    if (sString.name().length() < 1)
-                        columnName = field.getName().toUpperCase();
-                    else
-                        columnName = sString.name();
-                    columnDefs.add(columnName + " VARCHAR(" +
-                            sString.value() + ")" +
-                            getConstraints(sString.constraints()));
-                }
-                StringBuilder createCommand = new StringBuilder(
-                        "CREATE TABLE " + tableName + "(");
-                for (String columnDef : columnDefs)
-                    createCommand.append(
-                            "\n " + columnDef + ",");
-                // Remove trailing comma
-                String tableCreate = createCommand.substring(
-                        0, createCommand.length() - 1) + ");";
-                System.out.println("Table Creation SQL for " +
-                        className + " is:\n" + tableCreate);
-            }
-        }
-    }
-
-    private static String getConstraints(Constraints con) {
-        String constraints = "";
-        if (!con.allowNull())
-            constraints += " NOT NULL";
-        if (con.primaryKey())
-            constraints += " PRIMARY KEY";
-        if (con.unique())
-            constraints += " UNIQUE";
-        return constraints;
-    }
-}
-```
-
-输出为：
-
-```sql
-Table Creation SQL for annotations.database.Member is:
-CREATE TABLE MEMBER(
-    FIRSTNAME VARCHAR(30));
-Table Creation SQL for annotations.database.Member is:
-CREATE TABLE MEMBER(
-    FIRSTNAME VARCHAR(30),
-    LASTNAME VARCHAR(50));
-Table Creation SQL for annotations.database.Member is:
-CREATE TABLE MEMBER(
-    FIRSTNAME VARCHAR(30),
-    LASTNAME VARCHAR(50),
-    AGE INT);
-Table Creation SQL for annotations.database.Member is:
-CREATE TABLE MEMBER(
-    FIRSTNAME VARCHAR(30),
-    LASTNAME VARCHAR(50),
-    AGE INT,
-    REFERENCE VARCHAR(30) PRIMARY KEY);
-```
-
-主方法会循环处理命令行传入的每一个类名。每一个类都是用 ` forName()` 方法进行加载，并使用 `getAnnotation(DBTable.class)` 来检查该类是否带有 **@DBTable** 注解。如果存在，将表名存储起来。然后读取这个类的所有字段，并使用 `getDeclaredAnnotations()` 进行检查。这个方法返回一个包含特定字段上所有注解的数组。然后使用 **instanceof** 操作符判断这些注解是否是 **@SQLInteger** 或者 **@SQLString** 类型。如果是的话，在对应的处理块中将构造出相应的数据库列的字符串片段。注意，由于注解没有继承机制，如果要获取近似多态的行为，使用 `getDeclaredAnnotations()` 似乎是唯一的方式。
-
-嵌套的 **@Constraint** 注解被传递给 `getConstraints()`方法，并用它来构造一个包含 SQL 约束的 String 对象。
-
-需要提醒的是，上面演示的技巧对于真实的对象/映射关系而言，是十分幼稚的。使用 **@DBTable** 的注解来获取表的名称，这使得如果要修改表的名字，则迫使你重新编译 Java 代码。这种效果并不理想。现在已经有了很多可用的框架，用于将对象映射到数据库中，并且越来越多的框架开始使用注解了。
-
-## 使用javac处理注解
-
-通过 **javac**，你可以通过创建编译时（compile-time）注解处理器在 Java 源文件上使用注解，而不是编译之后的 class 文件。但是这里有一个重大限制：你不能通过处理器来改变源代码。唯一影响输出的方式就是创建新的文件。
-
-如果你的注解处理器创建了新的源文件，在新一轮处理中注解会检查源文件本身。工具在检测一轮之后持续循环，直到不再有新的源文件产生。然后它编译所有的源文件。
-
-每一个你编写的注解都需要处理器，但是 **javac** 可以非常容易的将多个注解处理器合并在一起。你可以指定多个需要处理的类，并且你可以添加监听器用于监听注解处理完成后接到通知。
-
-本节中的示例将帮助你开始学习，但如果你必须深入学习，请做好反复学习，大量访问 Google 和StackOverflow 的准备。
-
-### 最简单的处理器
-
-让我们开始定义我们能想到的最简单的处理器，只是为了编译和测试。如下是注解的定义：
-
-```java
-// annotations/simplest/Simple.java
-// A bare-bones annotation
-package annotations.simplest;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import java.lang.annotation.ElementType;
-@Retention(RetentionPolicy.SOURCE)
-@Target({ElementType.TYPE, ElementType.METHOD,
-        ElementType.CONSTRUCTOR,
-        ElementType.ANNOTATION_TYPE,
-        ElementType.PACKAGE, ElementType.FIELD,
-        ElementType.LOCAL_VARIABLE})
-public @interface Simple {
-    String value() default "-default-";
-}
-```
-
-**@Retention** 的参数现在为 **SOURCE**，这意味着注解不会再存留在编译后的代码。这在编译时处理注解是没有必要的，它只是指出，在这里，**javac** 是唯一有机会处理注解的代理。
-
-**@Target** 声明了几乎所有的目标类型（除了 **PACKAGE**） ，同样是为了演示。下面是一个测试示例。
-
-```java
-// annotations/simplest/SimpleTest.java
-// Test the "Simple" annotation
-// {java annotations.simplest.SimpleTest}
-package annotations.simplest;
-@Simple
-public class SimpleTest {
-    @Simple
-    int i;
-    @Simple
-    public SimpleTest() {}
-    @Simple
-    public void foo() {
-        System.out.println("SimpleTest.foo()");
-    }
-    @Simple
-    public void bar(String s, int i, float f) {
-        System.out.println("SimpleTest.bar()");
-    }
-    @Simple
-    public static void main(String[] args) {
-        @Simple
-        SimpleTest st = new SimpleTest();
-        st.foo();
-    }
-}
-```
-
-输出为：
-
-```java
-SimpleTest.foo()
-```
-
-在这里我们使用 **@Simple** 注解了所有 **@Target** 声明允许的地方。
-
-**SimpleTest.java** 只需要 **Simple.java** 就可以编译成功。当我们编译的时候什么都没有发生。
-
-**javac** 允许 **@Simple** 注解（只要它存在）在我们创建处理器并将其 hook 到编译器之前，不做任何事情。
-
-如下是一个十分简单的处理器，其所作的事情就是把注解相关的信息打印出来：
-
-```java
-// annotations/simplest/SimpleProcessor.java
-// A bare-bones annotation processor
-package annotations.simplest;
-import javax.annotation.processing.*;
-import javax.lang.model.SourceVersion;
-import javax.lang.model.element.*;
-import java.util.*;
-@SupportedAnnotationTypes(
-        "annotations.simplest.Simple")
-@SupportedSourceVersion(SourceVersion.RELEASE_8)
-public class SimpleProcessor
-        extends AbstractProcessor {
-    @Override
-    public boolean process(
-            Set<? extends TypeElement> annotations,
-            RoundEnvironment env) {
-        for(TypeElement t : annotations)
-            System.out.println(t);
-        for(Element el :
-                env.getElementsAnnotatedWith(Simple.class))
-            display(el);
-        return false;
-    }
-    private void display(Element el) {
-        System.out.println("==== " + el + " ====");
-        System.out.println(el.getKind() +
-                " : " + el.getModifiers() +
-                " : " + el.getSimpleName() +
-                " : " + el.asType());
-        if(el.getKind().equals(ElementKind.CLASS)) {
-            TypeElement te = (TypeElement)el;
-            System.out.println(te.getQualifiedName());
-            System.out.println(te.getSuperclass());
-            System.out.println(te.getEnclosedElements());
-        }
-        if(el.getKind().equals(ElementKind.METHOD)) {
-            ExecutableElement ex = (ExecutableElement)el;
-            System.out.print(ex.getReturnType() + " ");
-            System.out.print(ex.getSimpleName() + "(");
-            System.out.println(ex.getParameters() + ")");
-        }
-    }
-}
-```
-
-（旧的，失效的）**apt** 版本的处理器需要额外的方法来确定支持哪些注解以及支持的 Java 版本。不过，你现在可以简单的使用 **@SupportedAnnotationTypes** 和 **@SupportedSourceVersion** 注解（这是一个很好的示例关于注解如何简化你的代码）。
-
-你唯一需要实现的方法就是 `process()`，这里是所有行为发生的地方。第一个参数告诉你哪个注解是存在的，第二个参数保留了剩余信息。我们所做的事情只是打印了注解（这里只存在一个），可以看 **TypeElement** 文档中的其他行为。通过使用 `process()` 的第二个操作，我们循环所有被 **@Simple** 注解的元素，并且针对每一个元素调用我们的 `display()` 方法。所有 **Element** 展示了本身的基本信息；例如，`getModifiers()` 告诉你它是否为 **public** 和 **static** 的。
-
-**Element** 只能执行那些编译器解析的所有基本对象共有的操作，而类和方法之类的东西有额外的信息需要提取。所以（如果你阅读了正确的文档，但是我没有在任何文档中找到——我不得不通过 StackOverflow 寻找线索）你检查它是哪种 **ElementKind**，然后将其向下转换为更具体的元素类型，注入针对 CLASS 的 TypeElement 和 针对 METHOD 的ExecutableElement。此时，可以为这些元素调用其他方法。
-
-动态向下转型（在编译期不进行检查）并不像是 Java 的做事方式，这非常不直观这也是为什么我从未想过要这样做事。相反，我花了好几天的时间，试图发现你应该如何访问这些信息，而这些信息至少在某种程度上是用不起作用的恰当方法简单明了的。我还没有遇到任何东西说上面是规范的形式，但在我看来是。
-
-如果只是通过平常的方式来编译 **SimpleTest.java**，你不会得到任何结果。为了得到注解输出，你必须增加一个 **processor** 标志并且连接注解处理器类
-
-```shell
-javac -processor annotations.simplest.SimpleProcessor SimpleTest.java
-```
-
-现在编译器有了输出
-
-```shell
-annotations.simplest.Simple
-==== annotations.simplest.SimpleTest ====
-CLASS : [public] : SimpleTest : annotations.simplest.SimpleTest
-annotations.simplest.SimpleTest
-java.lang.Object
-i,SimpleTest(),foo(),bar(java.lang.String,int,float),main(java.lang.String[])
-==== i ====
-FIELD : [] : i : int
-==== SimpleTest() ====
-CONSTRUCTOR : [public] : <init> : ()void
-==== foo() ====
-METHOD : [public] : foo : ()void
-void foo()
-==== bar(java.lang.String,int,float) ====
-METHOD : [public] : bar : (java.lang.String,int,float)void
-void bar(s,i,f)
-==== main(java.lang.String[]) ====
-METHOD : [public, static] : main : (java.lang.String[])void
-void main(args)
-```
-
-这给了你一些可以发现的东西，包括参数名和类型、返回值等。
-
-### 更复杂的处理器
-
-当你创建用于 javac 注解处理器时，你不能使用 Java 的反射特性，因为你处理的是源代码，而并非是编译后的 class 文件。各种 mirror[^3 ] 解决这个问题的方法是，通过允许你在未编译的源代码中查看方法、字段和类型。
-
-如下是一个用于提取类中方法的注解，所以它可以被抽取成为一个接口：
-
-```java
-// annotations/ifx/ExtractInterface.java
-// javac-based annotation processing
-package annotations.ifx;
-import java.lang.annotation.*;
-@Target(ElementType.TYPE)
-@Retention(RetentionPolicy.SOURCE)
-public @interface ExtractInterface {
-    String interfaceName() default "-!!-";
-}
-```
-
-**RetentionPolicy** 的值为 **SOURCE**，这是为了在提取类中的接口之后不再将注解信息保留在 class 文件中。接下来的测试类提供了一些公用方法，这些方法可以成为接口的一部分：
-
-```java
-// annotations/ifx/Multiplier.java
-// javac-based annotation processing
-// {java annotations.ifx.Multiplier}
-package annotations.ifx;
-@ExtractInterface(interfaceName="IMultiplier")
-public class Multiplier {
-    public boolean flag = false;
-    private int n = 0;
-    public int multiply(int x, int y) {
-        int total = 0;
-        for(int i = 0; i < x; i++)
-            total = add(total, y);
-        return total;
-    }
-    public int fortySeven() { return 47; }
-    private int add(int x, int y) {
-        return x + y;
-    }
-    public double timesTen(double arg) {
-        return arg * 10;
-    }
-    public static void main(String[] args) {
-        Multiplier m = new Multiplier();
-        System.out.println(
-                "11 * 16 = " + m.multiply(11, 16));
-    }
-}
-```
-
-输出为：
-
-```java
-11 * 16 = 176
-```
-
-**Multiplier** 类（只能处理正整数）拥有一个 `multiply()` 方法，这个方法会多次调用私有方法 `add()` 来模拟乘法操作。` add()` 是私有方法，因此不能成为接口的一部分。其他的方法提供了语法多样性。注解被赋予 **IMultiplier** 的 **InterfaceName** 作为要创建的接口的名称。
-
-这里有一个编译时处理器用于提取有趣的方法，并创建一个新的 interface 源代码文件（这个源文件将会在下一轮中被自动编译）：
-
-```java
-// annotations/ifx/IfaceExtractorProcessor.java
-// javac-based annotation processing
-package annotations.ifx;
-import javax.annotation.processing.*;
-import javax.lang.model.SourceVersion;
-import javax.lang.model.element.*;
-import javax.lang.model.util.*;
-import java.util.*;
-import java.util.stream.*;
-import java.io.*;
-@SupportedAnnotationTypes(
-        "annotations.ifx.ExtractInterface")
-@SupportedSourceVersion(SourceVersion.RELEASE_8)
-public class IfaceExtractorProcessor
-        extends AbstractProcessor {
-    private ArrayList<Element>
-            interfaceMethods = new ArrayList<>();
-    Elements elementUtils;
-    private ProcessingEnvironment processingEnv;
-    @Override
-    public void init(
-            ProcessingEnvironment processingEnv) {
-        this.processingEnv = processingEnv;
-        elementUtils = processingEnv.getElementUtils();
-    }
-    @Override
-    public boolean process(
-            Set<? extends TypeElement> annotations,
-            RoundEnvironment env) {
-        for(Element elem:env.getElementsAnnotatedWith(
-                ExtractInterface.class)) {
-            String interfaceName = elem.getAnnotation(
-                    ExtractInterface.class).interfaceName();
-            for(Element enclosed :
-                    elem.getEnclosedElements()) {
-                if(enclosed.getKind()
-                        .equals(ElementKind.METHOD) &&
-                        enclosed.getModifiers()
-                                .contains(Modifier.PUBLIC) &&
-                        !enclosed.getModifiers()
-                                .contains(Modifier.STATIC)) {
-                    interfaceMethods.add(enclosed);
-                }
-            }
-            if(interfaceMethods.size() > 0)
-                writeInterfaceFile(interfaceName);
-        }
-        return false;
-    }
-    private void
-    writeInterfaceFile(String interfaceName) {
-        try(
-                Writer writer = processingEnv.getFiler()
-                        .createSourceFile(interfaceName)
-                        .openWriter()
-        ) {
-            String packageName = elementUtils
-                    .getPackageOf(interfaceMethods
-                            .get(0)).toString();
-            writer.write(
-                    "package " + packageName + ";\n");
-            writer.write("public interface " +
-                    interfaceName + " {\n");
-            for(Element elem : interfaceMethods) {
-                ExecutableElement method =
-                        (ExecutableElement)elem;
-                String signature = " public ";
-                signature += method.getReturnType() + " ";
-                signature += method.getSimpleName();
-                signature += createArgList(
-                        method.getParameters());
-                System.out.println(signature);
-                writer.write(signature + ";\n");
-            }
-            writer.write("}");
-        } catch(Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-    private String createArgList(
-            List<? extends VariableElement> parameters) {
-        String args = parameters.stream()
-                .map(p -> p.asType() + " " + p.getSimpleName())
-                .collect(Collectors.joining(", "));
-        return "(" + args + ")";
-    }
-}
-```
-
-**Elements** 对象实例 **elementUtils** 是一组静态方法的工具；我们用它来寻找 **writeInterfaceFile()** 中含有的包名。
-
-`getEnclosedElements()`方法会通过指定的元素生成所有的“闭包”元素。在这里，这个类闭包了它的所有元素。通过使用 `getKind()` 我们会找到所有的 **public** 和 **static** 方法，并将其添加到 **interfaceMethods** 列表中。接下来 `writeInterfaceFile()` 使用 **interfaceMethods** 列表里面的值生成新的接口定义。注意，在 `writeInterfaceFile()` 使用了向下转型到 **ExecutableElement**，这使得我们可以获取所有的方法信息。**createArgList()** 是一个帮助方法，用于生成参数列表。
-
-**Filer**是 `getFiler()` 生成的，并且是 **PrintWriter** 的一种实例，可以用于创建新文件。我们使用 **Filer** 对象，而不是原生的 **PrintWriter** 原因是，这个对象可以运行 **javac** 追踪你创建的新文件，这使得它可以在新一轮中检查新文件中的注解并编译文件。
-
-如下是一个命令行，可以在编译的时候使用处理器：
-
-```shell
-javac -processor annotations.ifx.IfaceExtractorProcessor Multiplier.java
-```
-
-新生成的 **IMultiplier.java** 的文件，正如你通过查看上面处理器的 `println()` 语句所猜测的那样，如下所示：
-
-```java
-package annotations.ifx;
-public interface IMultiplier {
-    public int multiply(int x, int y);
-    public int fortySeven();
-    public double timesTen(double arg);
-}
-```
-
-这个类同样会被 **javac** 编译（在某一轮中），所以你会在同一个目录中看到 **IMultiplier.class** 文件。
-
-<!-- Annotation-Based Unit Testing -->
-
-## 基于注解的单元测试
-
-单元测试是对类中每个方法提供一个或者多个测试的一种事件，其目的是为了有规律的测试一个类中每个部分是否具备正确的行为。在 Java 中，最著名的单元测试工具就是 **JUnit**。**JUnit** 4 版本已经包含了注解。在注解版本之前的 JUnit 一个最主要的问题是，为了启动和运行 **JUnit** 测试，有大量的“仪式”需要标注。这种负担已经减轻了一些，**但是**注解使得测试更接近“可以工作的最简单的测试系统”。
-
-在注解版本之前的 JUnit，你必须创建一个单独的文件来保存单元测试。通过注解，我们可以将单元测试集成在需要被测试的类中，从而将单元测试的时间和麻烦降到了最低。这种方式有额外的好处，就是使得测试私有方法和公有方法变的一样容易。
-
-这个基于注解的测试框架叫做 **@Unit**。其最基本的测试形式，可能也是你使用的最多的一个注解是 **@Test**，我们使用 **@Test** 来标记测试方法。测试方法不带参数，并返回 **boolean** 结果来说明测试方法成功或者失败。你可以任意命名它的测试方法。同时 **@Unit** 测试方法可以是任意你喜欢的访问修饰方法，包括 **private**。
-
-要使用 **@Unit**，你必须导入 **onjava.atunit** 包，并且使用 **@Unit** 的测试标记为合适的方法和字段打上标签（在接下来的例子中你会学到），然后让你的构建系统对编译后的类运行 **@Unit**，下面是一个简单的例子：
-
-```java
-// annotations/AtUnitExample1.java
-// {java onjava.atunit.AtUnit
-// build/classes/main/annotations/AtUnitExample1.class}
-package annotations;
-import onjava.atunit.*;
-import onjava.*;
-public class AtUnitExample1 {
-    public String methodOne() {
-        return "This is methodOne";
-    }
-    public int methodTwo() {
-        System.out.println("This is methodTwo");
-        return 2;
-    }
-    @Test
-    boolean methodOneTest() {
-        return methodOne().equals("This is methodOne");
-    }
-    @Test
-    boolean m2() { return methodTwo() == 2; }
-    @Test
-    private boolean m3() { return true; }
-    // Shows output for failure:
-    @Test
-    boolean failureTest() { return false; }
-    @Test
-    boolean anotherDisappointment() {
-        return false;
-    }
-}
-```
-
-输出为：
-
-```java
-annotations.AtUnitExample1
-. m3
-. methodOneTest
-. m2 This is methodTwo
-. failureTest (failed)
-. anotherDisappointment (failed)
-(5 tests)
->>> 2 FAILURES <<<
-annotations.AtUnitExample1: failureTest
-annotations.AtUnitExample1: anotherDisappointment
-```
-
-使用 **@Unit** 进行测试的类必须定义在某个包中（即必须包括 **package** 声明）。
-
-**@Test** 注解被置于 `methodOneTest()`、 `m2()`、`m3()`、`failureTest()` 以及 a`notherDisappointment()` 方法之前，它们告诉 **@Unit** 方法作为单元测试来运行。同时 **@Test** 确保这些方法没有任何参数并且返回值为 **boolean** 或者 **void**。当你填写单元测试时，唯一需要做的就是决定测试是成功还是失败，（对于返回值为 **boolean** 的方法）应该返回 **ture** 还是 **false**。
-
-如果你熟悉 **JUnit**，你还将注意到 **@Unit** 输出的信息更多。你会看到现在正在运行的测试的输出更有用，最后它会告诉你导致失败的类和测试。
-
-你并非必须将测试方法嵌入到原来的类中，有时候这种事情根本做不到。要生产一个非嵌入式的测试，最简单的方式就是继承：
-
-```java
-// annotations/AUExternalTest.java
-// Creating non-embedded tests
-// {java onjava.atunit.AtUnit
-// build/classes/main/annotations/AUExternalTest.class}
-package annotations;
-import onjava.atunit.*;
-import onjava.*;
-public class AUExternalTest extends AtUnitExample1 {
-    @Test
-    boolean _MethodOne() {
-        return methodOne().equals("This is methodOne");
-    }
-    @Test
-    boolean _MethodTwo() {
-        return methodTwo() == 2;
-    }
-}
-```
-
-输出为：
-
-```java
-annotations.AUExternalTest
-. tMethodOne
-. tMethodTwo This is methodTwo
-OK (2 tests)
-```
-
-这个示例还表现出灵活命名的价值。在这里，**@Test** 方法被命名为下划线前缀加上要测试的方法名称（我并不认为这是一种理想的命名形式，这只是表现一种可能性罢了）。
-
-你也可以使用组合来创建非嵌入式的测试：
-
-```java
-// annotations/AUComposition.java
-// Creating non-embedded tests
-// {java onjava.atunit.AtUnit
-// build/classes/main/annotations/AUComposition.class}
-package annotations;
-import onjava.atunit.*;
-import onjava.*;
-public class AUComposition {
-    AtUnitExample1 testObject = new AtUnitExample1();
-    @Test
-    boolean tMethodOne() {
-        return testObject.methodOne()
-                .equals("This is methodOne");
-    }
-    @Test
-    boolean tMethodTwo() {
-        return testObject.methodTwo() == 2;
-    }
-}
-```
-
-输出为：
-
-```java
-annotations.AUComposition
-. tMethodTwo This is methodTwo
-. tMethodOne
-OK (2 tests)
-```
-
-因为在每一个测试里面都会创建 **AUComposition** 对象，所以创建新的成员变量 **testObject** 用于以后的每一个测试方法。
-
-因为 **@Unit** 中没有 **JUnit** 中特殊的 **assert** 方法，不过另一种形式的 **@Test** 方法仍然允许返回值为 **void**（如果你还想使用 **true** 或者 **false** 的话，也可以使用 **boolean** 作为方法返回值类型）。为了表示测试成功，可以使用 Java 的 **assert** 语句。Java 断言机制需要你在 java 命令行行加上 **-ea** 标志来开启，但是 **@Unit** 已经自动开启了该功能。要表示测试失败的话，你甚至可以使用异常。**@Unit** 的设计目标之一就是尽可能减少添加额外的语法，而 Java 的 **assert** 和异常对于报告错误而言，即已经足够了。一个失败的 **assert** 或者从方法从抛出的异常都被视为测试失败，但是 **@Unit** 不会在这个失败的测试上卡住，它会继续运行，直到所有测试完毕，下面是一个示例程序：
-
-```java
-// annotations/AtUnitExample2.java
-// Assertions and exceptions can be used in @Tests
-// {java onjava.atunit.AtUnit
-// build/classes/main/annotations/AtUnitExample2.class}
-package annotations;
-import java.io.*;
-import onjava.atunit.*;
-import onjava.*;
-public class AtUnitExample2 {
-    public String methodOne() {
-        return "This is methodOne";
-    }
-    public int methodTwo() {
-        System.out.println("This is methodTwo");
-        return 2;
-    }
-    @Test
-    void assertExample() {
-        assert methodOne().equals("This is methodOne");
-    }
-    @Test
-    void assertFailureExample() {
-        assert 1 == 2: "What a surprise!";
-    }
-    @Test
-    void exceptionExample() throws IOException {
-        try(FileInputStream fis =
-                    new FileInputStream("nofile.txt")) {} // Throws
-    }
-    @Test
-    boolean assertAndReturn() {
-        // Assertion with message:
-        assert methodTwo() == 2: "methodTwo must equal 2";
-        return methodOne().equals("This is methodOne");
-    }
-}
-```
-
-输出为：
-
-```java
-annotations.AtUnitExample2
-. exceptionExample java.io.FileNotFoundException:
-nofile.txt (The system cannot find the file specified)
-(failed)
-. assertExample
-. assertAndReturn This is methodTwo
-. assertFailureExample java.lang.AssertionError: What
-a surprise!
-(failed)
-(4 tests)
->>> 2 FAILURES <<<
-annotations.AtUnitExample2: exceptionExample
-annotations.AtUnitExample2: assertFailureExample
-```
-
-如下是一个使用非嵌入式测试的例子，并且使用了断言，它将会对 **java.util.HashSet** 进行一些简单的测试：
-
-```java
-// annotations/HashSetTest.java
-// {java onjava.atunit.AtUnit
-// build/classes/main/annotations/HashSetTest.class}
-package annotations;
-import java.util.*;
-import onjava.atunit.*;
-import onjava.*;
-public class HashSetTest {
-    HashSet<String> testObject = new HashSet<>();
-    @Test
-    void initialization() {
-        assert testObject.isEmpty();
-    }
-    @Test
-    void _Contains() {
-        testObject.add("one");
-        assert testObject.contains("one");
-    }
-    @Test
-    void _Remove() {
-        testObject.add("one");
-        testObject.remove("one");
-        assert testObject.isEmpty();
-    }
-}
-```
-
-采用继承的方式可能会更简单，也没有一些其他的约束。
-
-对每一个单元测试而言，**@Unit** 都会使用默认的无参构造器，为该测试类所属的类创建出一个新的实例。并在此新创建的对象上运行测试，然后丢弃该对象，以免对其他测试产生副作用。如此创建对象导致我们依赖于类的默认构造器。如果你的类没有默认构造器，或者对象需要复杂的构造过程，那么你可以创建一个 **static** 方法专门负责构造对象，然后使用 **@TestObjectCreate** 注解标记该方法，例子如下：
-
-```java
-// annotations/AtUnitExample3.java
-// {java onjava.atunit.AtUnit
-// build/classes/main/annotations/AtUnitExample3.class}
-package annotations;
-import onjava.atunit.*;
-import onjava.*;
-public class AtUnitExample3 {
-    private int n;
-    public AtUnitExample3(int n) { this.n = n; }
-    public int getN() { return n; }
-    public String methodOne() {
-        return "This is methodOne";
-    }
-    public int methodTwo() {
-        System.out.println("This is methodTwo");
-        return 2;
-    }
-    @TestObjectCreate
-    static AtUnitExample3 create() {
-        return new AtUnitExample3(47);
-    }
-    @Test
-    boolean initialization() { return n == 47; }
-    @Test
-    boolean methodOneTest() {
-        return methodOne().equals("This is methodOne");
-    }
-    @Test
-    boolean m2() { return methodTwo() == 2; }
-}
-```
-
-输出为：
-
-```java
-annotations.AtUnitExample3
-. initialization
-. m2 This is methodTwo
-. methodOneTest
-OK (3 tests)
-```
-
-**@TestObjectCreate** 修饰的方法必须声明为 **static** ，且必须返回一个你正在测试的类型对象，这一切都由 **@Unit** 负责确保成立。
-
-有的时候，你需要向单元测试中增加一些字段。这时候可以使用 **@TestProperty** 注解，由它注解的字段表示只在单元测试中使用（因此，在你将产品发布给客户之前，他们应该被删除）。在下面的例子中，一个 **String** 通过 `String.split()` 方法进行分割，从其中读取一个值，这个值将会被生成测试对象：
-
-```java
-// annotations/AtUnitExample4.java
-// {java onjava.atunit.AtUnit
-// build/classes/main/annotations/AtUnitExample4.class}
-// {VisuallyInspectOutput}
-package annotations;
-import java.util.*;
-import onjava.atunit.*;
-import onjava.*;
-public class AtUnitExample4 {
-    static String theory = "All brontosauruses " +
-            "are thin at one end, much MUCH thicker in the " +
-            "middle, and then thin again at the far end.";
-    private String word;
-    private Random rand = new Random(); // Time-based seed
-    public AtUnitExample4(String word) {
-        this.word = word;
-    }
-    public String getWord() { return word; }
-    public String scrambleWord() {
-        List<Character> chars = Arrays.asList(
-                ConvertTo.boxed(word.toCharArray()));
-        Collections.shuffle(chars, rand);
-        StringBuilder result = new StringBuilder();
-        for(char ch : chars)
-            result.append(ch);
-        return result.toString();
-    }
-    @TestProperty
-    static List<String> input =
-            Arrays.asList(theory.split(" "));
-    @TestProperty
-    static Iterator<String> words = input.iterator();
-    @TestObjectCreate
-    static AtUnitExample4 create() {
-        if(words.hasNext())
-            return new AtUnitExample4(words.next());
-        else
-            return null;
-    }
-    @Test
-    boolean words() {
-        System.out.println("'" + getWord() + "'");
-        return getWord().equals("are");
-    }
-    @Test
-    boolean scramble1() {
-// Use specific seed to get verifiable results:
-        rand = new Random(47);
-        System.out.println("'" + getWord() + "'");
-        String scrambled = scrambleWord();
-        System.out.println(scrambled);
-        return scrambled.equals("lAl");
-    }
-    @Test
-    boolean scramble2() {
-        rand = new Random(74);
-        System.out.println("'" + getWord() + "'");
-        String scrambled = scrambleWord();
-        System.out.println(scrambled);
-        return scrambled.equals("tsaeborornussu");
-    }
-}
-```
-
-输出为：
-
-```java
-annotations.AtUnitExample4
-. words 'All'
-(failed)
-. scramble1 'brontosauruses'
-ntsaueorosurbs
-(failed)
-. scramble2 'are'
-are
-(failed)
-(3 tests)
->>> 3 FAILURES <<<
-annotations.AtUnitExample4: words
-annotations.AtUnitExample4: scramble1
-annotations.AtUnitExample4: scramble2
-```
-
-**@TestProperty** 也可以用来标记那些只在测试中使用的方法，但是它们本身不是测试方法。
-
-如果你的测试对象需要执行某些初始化工作，并且使用完成之后还需要执行清理工作，那么可以选择使用 **static** 的  **@TestObjectCleanup** 方法，当测试对象使用结束之后，该方法会为你执行清理工作。在下面的示例中，**@TestObjectCleanup** 为每一个测试对象都打开了一个文件，因此必须在丢弃测试的时候关闭该文件：
-
-```java
-// annotations/AtUnitExample5.java
-// {java onjava.atunit.AtUnit
-// build/classes/main/annotations/AtUnitExample5.class}
-package annotations;
-import java.io.*;
-import onjava.atunit.*;
-import onjava.*;
-public class AtUnitExample5 {
-    private String text;
-    public AtUnitExample5(String text) {
-        this.text = text;
-    }
-    @Override
-    public String toString() { return text; }
-    @TestProperty
-    static PrintWriter output;
-    @TestProperty
-    static int counter;
-    @TestObjectCreate
-    static AtUnitExample5 create() {
-        String id = Integer.toString(counter++);
-        try {
-            output = new PrintWriter("Test" + id + ".txt");
-        } catch(IOException e) {
-            throw new RuntimeException(e);
-        }
-        return new AtUnitExample5(id);
-    }
-    @TestObjectCleanup
-    static void cleanup(AtUnitExample5 tobj) {
-        System.out.println("Running cleanup");
-        output.close();
-    }
-    @Test
-    boolean test1() {
-        output.print("test1");
-        return true;
-    }
-    @Test
-    boolean test2() {
-        output.print("test2");
-        return true;
-    }
-    @Test
-    boolean test3() {
-        output.print("test3");
-        return true;
-    }
-}
-```
-
-输出为：
-
-```java
-annotations.AtUnitExample5
-. test1
-Running cleanup
-. test3
-Running cleanup
-. test2
-Running cleanup
-OK (3 tests)
-```
-
-在输出中我们可以看到，清理方法会在每个测试方法结束之后自动运行。
-
-### 在 @Unit 中使用泛型
-泛型为 **@Unit** 出了一个难题，因为我们不可能“通用测试”。我们必须针对某个特定类型的参数或者参数集才能进行测试。解决方法十分简单，让测试类继承自泛型类的一个特定版本即可：
-
-下面是一个 **stack** 的简单实现：
-```java
-package annotations;
-import java.util.*;
-public class StackL<T> {
-    private LinkedList<T> list = new LinkedList<>();
-    public void push(T v) { list.addFirst(v); }
-    public T top() { return list.getFirst(); }
-    public T pop() { return list.removeFirst(); }
-}
-```
-
-为了测试 String 版本，我们直接让测试类继承一个 Stack\<String\> ：
-
-```java
-// annotations/StackLStringTst.java
-// Applying @Unit to generics
-// {java onjava.atunit.AtUnit
-// build/classes/main/annotations/StackLStringTst.class}
-package annotations;
-import onjava.atunit.*;
-import onjava.*;
-public class
-StackLStringTst extends StackL<String> {
-    @Test
-    void tPush() {
-        push("one");
-        assert top().equals("one");
-        push("two");
-        assert top().equals("two");
-    }
-    @Test
-    void tPop() {
-        push("one");
-        push("two");
-        assert pop().equals("two");
-        assert pop().equals("one");
-    }
-    @Test
-    void tTop() {
-        push("A");
-        push("B");
-        assert top().equals("B");
-        assert top().equals("B");
-    }
-}
-```
-
-输出为：
-
-```java
-annotations.StackLStringTst
-. tTop
-. tPush
-. tPop
-OK (3 tests)
-```
-
-这种方法存在的唯一缺点是，继承使我们失去了访问被测试的类中 **private** 方法的能力。这对你非常重要，那你要么把 private 方法变为 **protected**，要么添加一个非 **private** 的 **@TestProperty** 方法，由它来调用 **private** 方法（稍后我们会看到，**AtUnitRemover** 会删除产品中的 **@TestProperty** 方法）。
-
-**@Unit** 搜索那些包含合适注解的类文件，然后运行 **@Test** 方法。我的主要目标就是让 **@Unit** 测试系统尽可能的透明，使得人们使用它的时候只需要添加 **@Test** 注解，而不需要特殊的编码和知识（现在版本的 **JUnit** 符合这个实践）。不过，如果说编写测试不会遇到任何困难，也不太可能，因此 **@Unit** 会尽量让这些困难变的微不足道，希望通过这种方式，你们会更乐意编写测试。
-
-### 实现 @Unit
-
-首先我们需要定义所有的注解类型。这些都是简单的标签，并且没有任何字段。@Test 标签在本章开头已经定义过了，这里是其他所需要的注解：
-
-```java
-// onjava/atunit/TestObjectCreate.java
-// The @Unit @TestObjectCreate tag
-package onjava.atunit;
-import java.lang.annotation.*;
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface TestObjectCreate {}
-```
-
-```java
-// onjava/atunit/TestObjectCleanup.java
-// The @Unit @TestObjectCleanup tag
-package onjava.atunit;
-import java.lang.annotation.*;
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface TestObjectCleanup {}
-```
-
-```java
-// onjava/atunit/TestProperty.java
-// The @Unit @TestProperty tag
-package onjava.atunit;
-import java.lang.annotation.*;
-// Both fields and methods can be tagged as properties:
-@Target({ElementType.FIELD, ElementType.METHOD})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface TestProperty {}
-```
-
-所有测试的保留属性都为 **RUNTIME**，这是因为 **@Unit** 必须在编译后的代码中发现这些注解。
-
-要实现系统并运行测试，我们还需要反射机制来提取注解。下面这个程序通过注解中的信息，决定如何构造测试对象，并在测试对象上运行测试。正是由于注解帮助，这个程序才会如此短小而直接：
-
-```java
-// onjava/atunit/AtUnit.java
-// An annotation-based unit-test framework
-// {java onjava.atunit.AtUnit}
-package onjava.atunit;
-import java.lang.reflect.*;
-import java.io.*;
-import java.util.*;
-import java.nio.file.*;
-import java.util.stream.*;
-import onjava.*;
-public class AtUnit implements ProcessFiles.Strategy {
-    static Class<?> testClass;
-    static List<String> failedTests= new ArrayList<>();
-    static long testsRun = 0;
-    static long failures = 0;
-    public static void
-    main(String[] args) throws Exception {
-        ClassLoader.getSystemClassLoader()
-                .setDefaultAssertionStatus(true); // Enable assert
-        new ProcessFiles(new AtUnit(), "class").start(args);
-        if(failures == 0)
-            System.out.println("OK (" + testsRun + " tests)");
-        else {
-            System.out.println("(" + testsRun + " tests)");
-            System.out.println(
-                    "\n>>> " + failures + " FAILURE" +
-                            (failures > 1 ? "S" : "") + " <<<");
-            for(String failed : failedTests)
-                System.out.println(" " + failed);
-        }
-    }
-    @Override
-    public void process(File cFile) {
-        try {
-            String cName = ClassNameFinder.thisClass(
-                    Files.readAllBytes(cFile.toPath()));
-            if(!cName.startsWith("public:"))
-                return;
-            cName = cName.split(":")[1];
-            if(!cName.contains("."))
-                return; // Ignore unpackaged classes
-            testClass = Class.forName(cName);
-        } catch(IOException | ClassNotFoundException e) {
-            throw new RuntimeException(e);
-        }
-        TestMethods testMethods = new TestMethods();
-        Method creator = null;
-        Method cleanup = null;
-        for(Method m : testClass.getDeclaredMethods()) {
-            testMethods.addIfTestMethod(m);
-            if(creator == null)
-                creator = checkForCreatorMethod(m);
-            if(cleanup == null)
-                cleanup = checkForCleanupMethod(m);
-        }
-        if(testMethods.size() > 0) {
-            if(creator == null)
-                try {
-                    if(!Modifier.isPublic(testClass
-                            .getDeclaredConstructor()
-                            .getModifiers())) {
-                        System.out.println("Error: " + testClass +
-                                " no-arg constructor must be public");
-                        System.exit(1);
-                    }
-                } catch(NoSuchMethodException e) {
-// Synthesized no-arg constructor; OK
-                }
-            System.out.println(testClass.getName());
-        }
-        for(Method m : testMethods) {
-            System.out.print(" . " + m.getName() + " ");
-            try {
-                Object testObject = createTestObject(creator);
-                boolean success = false;
-                try {
-                    if(m.getReturnType().equals(boolean.class))
-                        success = (Boolean)m.invoke(testObject);
-                    else {
-                        m.invoke(testObject);
-                        success = true; // If no assert fails
-                    }
-                } catch(InvocationTargetException e) {
-// Actual exception is inside e:
-                    System.out.println(e.getCause());
-                }
-                System.out.println(success ? "" : "(failed)");
-                testsRun++;
-                if(!success) {
-                    failures++;
-                    failedTests.add(testClass.getName() +
-                            ": " + m.getName());
-                }
-                if(cleanup != null)
-                    cleanup.invoke(testObject, testObject);
-            } catch(IllegalAccessException |
-                    IllegalArgumentException |
-                    InvocationTargetException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-    public static
-    class TestMethods extends ArrayList<Method> {
-        void addIfTestMethod(Method m) {
-            if(m.getAnnotation(Test.class) == null)
-                return;
-            if(!(m.getReturnType().equals(boolean.class) ||
-                    m.getReturnType().equals(void.class)))
-                throw new RuntimeException("@Test method" +
-                        " must return boolean or void");
-            m.setAccessible(true); // If it's private, etc.
-            add(m);
-        }
-    }
-    private static
-    Method checkForCreatorMethod(Method m) {
-        if(m.getAnnotation(TestObjectCreate.class) == null)
-            return null;
-        if(!m.getReturnType().equals(testClass))
-            throw new RuntimeException("@TestObjectCreate " +
-                    "must return instance of Class to be tested");
-        if((m.getModifiers() &
-                java.lang.reflect.Modifier.STATIC) < 1)
-            throw new RuntimeException("@TestObjectCreate " +
-                    "must be static.");
-        m.setAccessible(true);
-        return m;
-    }
-    private static
-    Method checkForCleanupMethod(Method m) {
-        if(m.getAnnotation(TestObjectCleanup.class) == null)
-            return null;
-        if(!m.getReturnType().equals(void.class))
-            throw new RuntimeException("@TestObjectCleanup " +
-                    "must return void");
-        if((m.getModifiers() &
-                java.lang.reflect.Modifier.STATIC) < 1)
-            throw new RuntimeException("@TestObjectCleanup " +
-                    "must be static.");
-        if(m.getParameterTypes().length == 0 ||
-                m.getParameterTypes()[0] != testClass)
-            throw new RuntimeException("@TestObjectCleanup " +
-                    "must take an argument of the tested type.");
-        m.setAccessible(true);
-        return m;
-    }
-    private static Object
-    createTestObject(Method creator) {
-        if(creator != null) {
-            try {
-                return creator.invoke(testClass);
-            } catch(IllegalAccessException |
-                    IllegalArgumentException |
-                    InvocationTargetException e) {
-                throw new RuntimeException("Couldn't run " +
-                        "@TestObject (creator) method.");
-            }
-        } else { // Use the no-arg constructor:
-            try {
-                return testClass.newInstance();
-            } catch(InstantiationException |
-                    IllegalAccessException e) {
-                throw new RuntimeException(
-                        "Couldn't create a test object. " +
-                                "Try using a @TestObject method.");
-            }
-        }
-    }
-}
-```
-
-虽然它可能是“过早的重构”（因为它只在书中使用过一次），**AtUnit.java** 使用了 **ProcessFiles** 工具逐步判断命令行中的参数，决定它是一个目录还是文件，并采取相应的行为。这可以应用于不同的解决方法，是因为它包含了一个 可用于自定义的 **Strategy** 接口：
-
-```java
-// onjava/ProcessFiles.java
-package onjava;
-import java.io.*;
-import java.nio.file.*;
-public class ProcessFiles {
-    public interface Strategy {
-        void process(File file);
-    }
-    private Strategy strategy;
-    private String ext;
-    public ProcessFiles(Strategy strategy, String ext) {
-        this.strategy = strategy;
-        this.ext = ext;
-    }
-    public void start(String[] args) {
-        try {
-            if(args.length == 0)
-                processDirectoryTree(new File("."));
-            else
-                for(String arg : args) {
-                    File fileArg = new File(arg);
-                    if(fileArg.isDirectory())
-                        processDirectoryTree(fileArg);
-                    else {
-// Allow user to leave off extension:
-                        if(!arg.endsWith("." + ext))
-                            arg += "." + ext;
-                        strategy.process(
-                                new File(arg).getCanonicalFile());
-                    }
-                }
-        } catch(IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-    public void processDirectoryTree(File root) throws IOException {
-        PathMatcher matcher = FileSystems.getDefault()
-                .getPathMatcher("glob:**/*.{" + ext + "}");
-        Files.walk(root.toPath())
-                .filter(matcher::matches)
-                .forEach(p -> strategy.process(p.toFile()));
-    }
-}
-```
-
-**AtUnit** 类实现了 **ProcessFiles.Strategy**，其包含了一个 `process()` 方法。在这种方式下，**AtUnit** 实例可以作为参数传递给 **ProcessFiles** 构造器。第二个构造器的参数告诉 **ProcessFiles** 如寻找所有包含 “class” 拓展名的文件。
-
-如下是一个简单的使用示例：
-
-```java
-// annotations/DemoProcessFiles.java
-import onjava.ProcessFiles;
-public class DemoProcessFiles {
-    public static void main(String[] args) {
-        new ProcessFiles(file -> System.out.println(file),
-                "java").start(args);
-    }
-}
-```
-
-输出为：
-
-```java
-.\AtUnitExample1.java
-.\AtUnitExample2.java
-.\AtUnitExample3.java
-.\AtUnitExample4.java
-.\AtUnitExample5.java
-.\AUComposition.java
-.\AUExternalTest.java
-.\database\Constraints.java
-.\database\DBTable.java
-.\database\Member.java
-.\database\SQLInteger.java
-.\database\SQLString.java
-.\database\TableCreator.java
-.\database\Uniqueness.java
-.\DemoProcessFiles.java
-.\HashSetTest.java
-.\ifx\ExtractInterface.java
-.\ifx\IfaceExtractorProcessor.java
-.\ifx\Multiplier.java
-.\PasswordUtils.java
-.\simplest\Simple.java
-.\simplest\SimpleProcessor.java
-.\simplest\SimpleTest.java
-.\SimulatingNull.java
-.\StackL.java
-.\StackLStringTst.java
-.\Testable.java
-.\UseCase.java
-.\UseCaseTracker.java
-```
-
-如果没有命令行参数，这个程序会遍历当前的目录树。你还可以提供多个参数，这些参数可以是类文件（带或不带.class扩展名）或目录。
-
-回到我们对 **AtUnit.java** 的讨论，因为 **@Unit** 会自动找到可测试的类和方法，所以不需要“套件”机制。
-
-**AtUnit.java** 中存在的一个我们必须要解决的问题是，当它发现类文件时，类文件名中的限定类名（包括包）不明显。为了发现这个信息，必须解析类文件 - 这不是微不足道的，但也不是不可能的。 找到 .class 文件时，会打开它并读取其二进制数据并将其传递给 `ClassNameFinder.thisClass()`。 在这里，我们正在进入“字节码工程”领域，因为我们实际上正在分析类文件的内容：
-
-```java
-// onjava/atunit/ClassNameFinder.java
-// {java onjava.atunit.ClassNameFinder}
-package onjava.atunit;
-import java.io.*;
-import java.nio.file.*;
-import java.util.*;
-import onjava.*;
-public class ClassNameFinder {
-    public static String thisClass(byte[] classBytes) {
-        Map<Integer,Integer> offsetTable = new HashMap<>();
-        Map<Integer,String> classNameTable = new HashMap<>();
-        try {
-            DataInputStream data = new DataInputStream(
-                    new ByteArrayInputStream(classBytes));
-            int magic = data.readInt(); // 0xcafebabe
-            int minorVersion = data.readShort();
-            int majorVersion = data.readShort();
-            int constantPoolCount = data.readShort();
-            int[] constantPool = new int[constantPoolCount];
-            for(int i = 1; i < constantPoolCount; i++) {
-                int tag = data.read();
-                // int tableSize;
-                switch(tag) {
-                    case 1: // UTF
-                        int length = data.readShort();
-                        char[] bytes = new char[length];
-                        for(int k = 0; k < bytes.length; k++)
-                            bytes[k] = (char)data.read();
-                        String className = new String(bytes);
-                        classNameTable.put(i, className);
-                        break;
-                    case 5: // LONG
-                    case 6: // DOUBLE
-                        data.readLong(); // discard 8 bytes
-                        i++; // Special skip necessary
-                        break;
-                    case 7: // CLASS
-                        int offset = data.readShort();
-                        offsetTable.put(i, offset);
-                        break;
-                    case 8: // STRING
-                        data.readShort(); // discard 2 bytes
-                        break;
-                    case 3: // INTEGER
-                    case 4: // FLOAT
-                    case 9: // FIELD_REF
-                    case 10: // METHOD_REF
-                    case 11: // INTERFACE_METHOD_REF
-                    case 12: // NAME_AND_TYPE
-                    case 18: // Invoke Dynamic
-                        data.readInt(); // discard 4 bytes
-                        break;
-                    case 15: // Method Handle
-                        data.readByte();
-                        data.readShort();
-                        break;
-                    case 16: // Method Type
-                        data.readShort();
-                        break;
-                    default:
-                        throw
-                                new RuntimeException("Bad tag " + tag);
-                }
-            }
-            short accessFlags = data.readShort();
-            String access = (accessFlags & 0x0001) == 0 ?
-                    "nonpublic:" : "public:";
-            int thisClass = data.readShort();
-            int superClass = data.readShort();
-            return access + classNameTable.get(
-                    offsetTable.get(thisClass)).replace('/', '.');
-        } catch(IOException | RuntimeException e) {
-            throw new RuntimeException(e);
-        }
-    }
-    // Demonstration:
-    public static void main(String[] args) throws Exception {
-        PathMatcher matcher = FileSystems.getDefault()
-                .getPathMatcher("glob:**/*.class");
-// Walk the entire tree:
-        Files.walk(Paths.get("."))
-                .filter(matcher::matches)
-                .map(p -> {
-                    try {
-                        return thisClass(Files.readAllBytes(p));
-                    } catch(Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                })
-                .filter(s -> s.startsWith("public:"))
-// .filter(s -> s.indexOf('$') >= 0)
-                .map(s -> s.split(":")[1])
-                .filter(s -> !s.startsWith("enums."))
-                .filter(s -> s.contains("."))
-                .forEach(System.out::println);
-    }
-}
-```
-
-输出为：
-
-```java
-onjava.ArrayShow
-onjava.atunit.AtUnit$TestMethods
-onjava.atunit.AtUnit
-onjava.atunit.ClassNameFinder
-onjava.atunit.Test
-onjava.atunit.TestObjectCleanup
-onjava.atunit.TestObjectCreate
-onjava.atunit.TestProperty
-onjava.BasicSupplier
-onjava.CollectionMethodDifferences
-onjava.ConvertTo
-onjava.Count$Boolean
-onjava.Count$Byte
-onjava.Count$Character
-onjava.Count$Double
-onjava.Count$Float
-onjava.Count$Integer
-onjava.Count$Long
-onjava.Count$Pboolean
-onjava.Count$Pbyte
-onjava.Count$Pchar
-onjava.Count$Pdouble
-onjava.Count$Pfloat
-onjava.Count$Pint
-onjava.Count$Plong
-onjava.Count$Pshort
-onjava.Count$Short
-onjava.Count
-onjava.CountingIntegerList
-onjava.CountMap
-onjava.Countries
-onjava.Enums
-onjava.FillMap
-onjava.HTMLColors
-onjava.MouseClick
-onjava.Nap
-onjava.Null
-onjava.Operations
-onjava.OSExecute
-onjava.OSExecuteException
-onjava.Pair
-onjava.ProcessFiles$Strategy
-onjava.ProcessFiles
-onjava.Rand$Boolean
-onjava.Rand$Byte
-onjava.Rand$Character
-onjava.Rand$Double
-onjava.Rand$Float
-onjava.Rand$Integer
-onjava.Rand$Long
-onjava.Rand$Pboolean
-onjava.Rand$Pbyte
-onjava.Rand$Pchar
-onjava.Rand$Pdouble
-onjava.Rand$Pfloat
-onjava.Rand$Pint
-onjava.Rand$Plong
-onjava.Rand$Pshort
-onjava.Rand$Short
-onjava.Rand$String
-onjava.Rand
-onjava.Range
-onjava.Repeat
-onjava.RmDir
-onjava.Sets
-onjava.Stack
-onjava.Suppliers
-onjava.TimedAbort
-onjava.Timer
-onjava.Tuple
-onjava.Tuple2
-onjava.Tuple3
-onjava.Tuple4
-onjava.Tuple5
-onjava.TypeCounter
-```
-
- 虽然无法在这里介绍其中所有的细节，但是每个类文件都必须遵循一定的格式，而我已经尽力用有意义的字段来表示这些从 **ByteArrayInputStream** 中提取出来的数据片段。通过施加在输入流上的读操作，你能看出每个信息片的大小。例如每一个类的头 32 个 bit 总是一个 “神秘数字” **0xcafebabe**，而接下来的两个 **short** 值是版本信息。常量池包含了程序的常量，所以这是一个可变的值。接下来的 **short** 告诉我们这个常量池有多大，然后我们为其创建一个尺寸合适的数组。常量池中的每一个元素，其长度可能是固定式，也可能是可变的值，因此我们必须检查每一个常量的起始标记，然后才能知道该怎么做，这就是 switch 语句的工作。我们并不打算精确的分析类中所有的数据，仅仅是从文件的起始一步一步的走，直到取得我们所需的信息，因此你会发现，在这个过程中我们丢弃了大量的数据。关于类的信息都保存在 **classNameTable** 和 **offsetTable** 中。在读取常量池之后，就找到了 **this_class** 信息，这是 **offsetTable** 的一个坐标，通过它可以找到进入  **classNameTable** 的坐标，然后就可以得到我们所需的类的名字了。
-
-现在让我们回到 **AtUtil.java** 中，process() 方法中拥有了类的名字，然后检查它是否包含“.”，如果有就表示该类定义于一个包中。没有包的类会被忽略。如果一个类在包中，那么我们就可以使用标准的类加载器通过 `Class.forName()`  将其加载进来。现在我们可以对这个类进行 **@Unit** 注解的分析工作了。
-
-我们只需要关注三件事：首先是 **@Test** 方法，它们被保存在 **TestMehtods** 列表中，然后检查其是否具有 @TestObjectCreate 和 **@TestObjectCleanup****** 方法。从代码中可以看到，我们通过调用相应的方法来查询注解从而找到这些方法。
-
-每找到一个 @Test 方法，就打印出来当前类的名字，于是观察者立刻就可以知道发生了什么。接下来开始执行测试，也就是打印出方法名，然后调用 createTestObject() （如果存在一个加了 @TestObjectCreate 注解的方法），或者调用默认构造器。一旦创建出来测试对象，如果调用其上的测试方法。如果测试的返回值为 boolean，就捕获该结果。如果测试方法没有返回值，那么就没有异常发生，我们就假设测试成功，反之，如果当 assert 失败或者有任何异常抛出的时候，就说明测试失败，这时将异常信息打印出来以显示错误的原因。如果有失败的测试发生，那么还要统计失败的次数，并将失败所属的类和方法加入到 failedTests 中，以便最后报告给用户。
-
-<!-- Summary -->
-
-## 本章小结
-
-注解是 Java 引入的一项非常受欢迎的补充，它提供了一种结构化，并且具有类型检查能力的新途径，从而使得你能够为代码中加入元数据，而且不会导致代码杂乱并难以阅读。使用注解能够帮助我们避免编写累赘的部署描述性文件，以及其他的生成文件。而 Javadoc 中的 @deprecated 被 @Deprecated 注解所替代的事实也说明，与注释性文字相比，注解绝对更适用于描述类相关的信息。
-
-Java 提供了很少的内置注解。这意味着如果你在别处找不到可用的类库，那么就只能自己创建新的注解以及相应的处理器。通过将注解处理器链接到 javac，你可以一步完成编译新生成的文件，简化了构造过程。
-
-API 的提供方和框架将会将注解作为他们工具的一部分。通过 @Unit 系统，我们可以想象，注解会极大的改变我们的 Java 编程体验。
-
-<!-- 分页 -->
-
-<div style="page-break-after: always;"></div>
-
-[^3 ]: The Java designers coyly suggest that a mirror is where you find a reflection.
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/AQS\345\216\237\347\220\206\345\210\206\346\236\220.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/AQS\345\216\237\347\220\206\346\265\205\346\236\220.md"
similarity index 57%
rename from "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/AQS\345\216\237\347\220\206\345\210\206\346\236\220.md"
rename to "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/AQS\345\216\237\347\220\206\346\265\205\346\236\220.md"
index 21ce449031..e1261e2308 100644
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/AQS\345\216\237\347\220\206\345\210\206\346\236\220.md"
+++ "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/AQS\345\216\237\347\220\206\346\265\205\346\236\220.md"
@@ -1,79 +1,80 @@
+# 1 AQS（AbstractQueuedSynchronizer）
+## 1.1 基本模型
+![](https://upload-images.jianshu.io/upload_images/4685968-a7056d4c8afa2bfa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-28eb1053dd96343e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-02e979128dd1a9fa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![CLH 队列( FIFO)](https://upload-images.jianshu.io/upload_images/4685968-fb7f2d5bbff78f38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 1.2 类型
-AQS定义两种资源共享方式：
-### 独占锁
+![](https://upload-images.jianshu.io/upload_images/4685968-4694e4866f31515a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 独占锁
 每次只能有一个线程能持有锁，ReentrantLock就是以独占方式实现的互斥锁。
-
-独占锁是一种悲观保守的加锁策略，它避免了读/读冲突。若某只读线程获取锁，则其它读线程都只能等待，这就限制了不必要的并发性，因为读操作并不会影响数据的一致性。
-### 共享锁
-允许多个线程同时获取锁，并发访问共享资源。
-
-共享锁则是一种乐观锁，放宽加锁策略，允许多个执行读操作的线程同时访问共享资源。 ReadWriteLock，读-写锁，就允许一个资源可以被多个读操作访问，或者被一个 写操作访问，但二者不能同时进行。
-
-AQS的内部类Node定义了两个常量SHARED和EXCLUSIVE，分别标识AQS队列中等待线程的锁获取模式。
-![](https://img-blog.csdnimg.cn/img_convert/5f278a7e90f48d306a954cf6aded59fa.png)
-![](https://img-blog.csdnimg.cn/img_convert/aacccb326883560e0b5001362ff1ee6c.png)
+独占锁是一种悲观保守的加锁策略，它避免了读/读冲突，如果某个只读线程获取锁，则其他读线程都只能等待，这种情况下就限制了不必要的并发性，因为读操作并不会影响数据的一致性
+- 共享锁
+允许多个线程同时获取锁，并发访问共享资源
+共享锁则是一种乐观锁，它放宽了加锁策略，允许多个执行读操作的线程同时访问共享资源。 java的并发包中提供了ReadWriteLock，读-写锁。它允许一个资源可以被多个读操作访问，或者被一个 写操作访问，但两者不能同时进行
+AQS的内部类Node定义了两个常量SHARED和EXCLUSIVE，他们分别标识 AQS队列中等待线程的锁获取模式。
+![](https://upload-images.jianshu.io/upload_images/4685968-3ceb1ec59d70d65b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-f19f67a533d4500a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 1.3  锁的公平与非公平
-指线程请求获取锁的过程中，是否允许插队。
-
-- 在公平锁上，线程将按他们发出请求的顺序来获得锁
-- 非公平锁则允许在线程发出请求后立即尝试获取锁，若可用则可直接获取锁，尝试失败才进行排队等待
-
-比如ReentrantLock，就提供了两种锁获取方式
+锁的公平与非公平，是指线程请求获取锁的过程中，是否允许插队。
+在公平锁上，线程将按他们发出请求的顺序来获得锁；而非公平锁则允许在线程发出请求后立即尝试获取锁，如果可用则可直接获取锁，尝试失败才进行排队等待。
+ReentrantLock提供了两种锁获取方式
 - FairSyn
 - NofairSync
 
+结论：ReentrantLock是以独占锁的加锁策略实现的互斥锁，同时它提供了公平和非公平两种锁获取方式。最初看源码时竟然把这两个概念弄混了
 ## 1.4 架构  
  AQS提供了独占锁和共享锁必须实现的方法
 - 具有独占锁功能的子类
 它必须实现tryAcquire、tryRelease、isHeldExclusively等,ReentrantLock是一种独占锁
 - 共享锁功能的子类
 必须实现tryAcquireShared和tryReleaseShared等方法，带有Shared后缀的方法都是支持共享锁加锁的语义。Semaphore是一种共享锁
-![](https://img-blog.csdnimg.cn/img_convert/55ba6b977b57640f28d7e1bc9dfae7dc.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-4b7cf4a30adb76b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 这里就以`ReentrantLock`排它锁为例开始讲解如何利用AQS
 # 2 ReentrantLock
 ## 2.1 构造方法
-![](https://img-blog.csdnimg.cn/img_convert/2da34fb550bbcc114d91caa9eb1809f8.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-dc5fef0586950cf3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 显然，对象中有一个属性叫`sync`
-![](https://img-blog.csdnimg.cn/img_convert/346181cafa2488e883433dddb20b48da.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-6044db1e72a633b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 有两种不同的实现类
 - `NonfairSync`（默认实现）
 - `FairSync`
 
 它们都是排它锁的内部类，不论用哪一个都能实现排它锁，只是内部可能有点原理上的区别。先以`NonfairSync`为例
-![](https://img-blog.csdnimg.cn/img_convert/85eba502320fd9826fb8fbf0e3061a3e.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-9e0903ecdcf73bf8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 `lock()`先通过CAS尝试将状态从0修改为1
   - 若直接修改成功，前提条件自然是锁的状态为0，则直接将线程的OWNER修改为当前线程，这是一种理想情况，如果并发粒度设置适当也是一种乐观情况
   - 若该动作未成功，则会间接调用`acquire(1)`
-![](https://img-blog.csdnimg.cn/img_convert/d86afb34609f0e42d3cbb2df9b579c20.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-a5d664e36dbcf27e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 `acquire(int)`就定义在`AbstractQueuedSynchronizer`
 ## tryAcquire / tryRelease
 首先看`tryAcquire(arg)`的调用（传入的参数是1），在`NonfairSync`中，会这样来实现
-![](https://img-blog.csdnimg.cn/img_convert/c89725bcf7870eb715c3a55b543a9b75.png)
-![](https://img-blog.csdnimg.cn/img_convert/ddd5bbf4995e9da1896f79f596e524bf.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-7c66ff929e8dbac6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-933f1ae5b6b3a8b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 首先获取这个锁的状态
 - 若状态为0，则尝试设置状态为传入的参数（这里就是1），若设置成功就代表自己获取到了锁，返回`true`
 状态为0设置1的动作在外部就有做过一次，内部再一次做只是提升概率，而且这样的操作相对锁来讲不占开销
 - 若状态非0，则判定当前线程是否为排它锁的Owner，如果是Owner则尝试将状态增加acquires（也就是增加1），如果这个状态值溢出，则抛异常，否则即将状态设置进去后返回true（实现类似于偏向的功能，可重入，但是无需进一步征用）
 - 如果状态不是0，且自身不是owner，则返回false
-![image.png](https://img-blog.csdnimg.cn/img_convert/8b12f2bc8eee2ccdeab36151f3e825a2.png)
+![image.png](https://upload-images.jianshu.io/upload_images/4685968-9bf53d5d8bfadc0b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 回到对`acquire()`的调用判定中是通过`if(!tryAcquire())`作为第1个条件的，如果返回true，则判定就不会成立了，自然后面的acquireQueued动作就不会再执行了，如果发生这样的情况是最理想的。
 
 无论多么乐观，征用是必然存在的，如果征用存在则owner自然不会是自己，`tryAcquire()`会返回false，接着就会再调用方法：`acquireQueued(addWaiter(Node.EXCLUSIVE), arg)`
 这个方法的调用的代码更不好懂，需要从里往外看，这里的Node.EXCLUSIVE是节点的类型
-![](https://img-blog.csdnimg.cn/img_convert/83ce028ec3196994fd58dd0e82e58a4c.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-b7654e6a89dfa0ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 看名称应该清楚是排它类型的意思。接着调用addWaiter()来增加一个排它锁类型的节点，这个addWaiter()的代码是这样写的：
-![](https://img-blog.csdnimg.cn/img_convert/95519b69b2739093402801b4bd73e6d2.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-a1955d0e941422c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 这里创建了一个Node的对象，将当前线程和`Node.EXCLUSIVE`模式传入，也就是说Node节点理论上包含了这两项信息
 代码中的tail是AQS的一个属性
-![](https://img-blog.csdnimg.cn/img_convert/f7a6ad5be62368c272e187d91fc2bd69.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-9a14f387f33cf1fb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 刚开始的时候肯定是为`null`，也就是不会进入第一层if判定的区域，而直接会进入`enq(node)`的代码
-![](https://img-blog.csdnimg.cn/img_convert/b1232f867932d50262785c4789d0e376.png)
-AQS是链表，而且它还应该有一个head引用来指向链表的头节点。AQS在初始化时head = tail = null，在运行时来回移动。
-
-AQS是一个基于状态（state）的链表管理方式：
-```java
+![](https://upload-images.jianshu.io/upload_images/4685968-361894babed357bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+看到了`tail`就应该猜到了`AQS是链表`，而且它还应该有一个head引用来指向链表的头节点
+AQS在初始化的时候head、tail都是null，在运行时来回移动
+AQS是一个基于状态（state）的链表管理方式
+```
 /**
  * 这个插入会检测head tail 的初始化, 必要的话会初始化一个 dummy 节点, 这个和 ConcurrentLinkedQueue 一样的
  */
@@ -86,18 +87,14 @@ AQS是一个基于状态（state）的链表管理方式：
  *      3. 这时再在tail后面添加节点(这一步可能失败, 可能发生竞争被其他的线程抢占)
  *  这里为什么要加入一个 dummy 节点呢?
  *      这里的 Sync Queue 是CLH lock的一个变种, 线程节点 node 能否获取lock的判断通过其前继节点
- *      而且这里在当前节点想获取lock时通常给前继节点 打SIGNAL标识(表示当【前继节点】释放lock，需要通知我来获取lock)
+ *      而且这里在当前节点想获取lock时通常给前继节点 打上 signal 的标识(表示当前继节点释放lock需要通知我来获取lock)
  *      若这里不清楚的同学, 请先看看 CLH lock的资料 (这是理解 AQS 的基础)
  */
 private Node enq(final Node node){
     for(;;){
         Node t = tail;
-        // 1. 队列为空
-        // 初始化一个 dummy 节点 其实和 ConcurrentLinkedQueue 一样
-        if(t == null){ // Must initialize     
-        	// 2. 初始化 head 与 tail
-        	// 这个CAS成功后, head 就有值了
-            if(compareAndSetHead(new Node())){
+        if(t == null){ // Must initialize       // 1. 队列为空 初始化一个 dummy 节点 其实和 ConcurrentLinkedQueue 一样
+            if(compareAndSetHead(new Node())){  // 2. 初始化 head 与 tail (这个CAS成功后, head 就有值了, 详情将 Unsafe 操作)
                 tail = head;
             }
         }else{
@@ -114,22 +111,30 @@ private Node enq(final Node node){
 首先这个是一个死循环，而且本身没有锁，因此可以有多个线程进来，假如某个线程进入方法
 此时head、tail都是null，自然会进入
 `if(t == null)`创建一个Node，这个Node没有像开始那样给予类型和线程，很明显是一个空的Node对象
-![此时传入的node和某一个线程创建的Node对象](https://img-blog.csdnimg.cn/img_convert/7daa504e41d2dfd372b50fddbcb44e7f.png)
+![此时传入的node和某一个线程创建的Node对象](https://upload-images.jianshu.io/upload_images/4685968-d7c4592ccdc0cdef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 刚才我们很理想的认为只有一个线程会出现这种情况，如果有多个线程并发进入这个if判定区域，可能就会同时存在多个这样的数据结构，在各自形成数据结构后，多个线程都会去做`compareAndSetHead(new Node())`的动作，也就是尝试将这个临时节点设置为head
-![](https://img-blog.csdnimg.cn/img_convert/e5dc4e25643df72f4964da00e93b9f01.png)
-显然并发时只有一个线程会成功，因此成功的那个线程会执行`tail = head`，整个AQS的链表就成为![AQS被第一个请求成功的线程初始化后](https://img-blog.csdnimg.cn/img_convert/a04559886cf63928b976020b8e49555e.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-e26e3ab3046baead.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+显然并发时只有一个线程会成功，因此成功的那个线程会执行`tail = head`，整个AQS的链表就成为![AQS被第一个请求成功的线程初始化后](https://upload-images.jianshu.io/upload_images/4685968-c116fb5dfd8cfd5f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 有一个线程会成功修改head和tail的值，其它的线程会继续循环，再次循环会进入else
 在else语句所在的逻辑中
 - 第一步是`node.prev = t`，这个t就是tail的临时值，也就是首先让尝试写入的node节点的prev指针指向原来的结束节点
 - 然后尝试通过CAS替换掉AQS中的tail的内容为当前线程的Node
-![](https://img-blog.csdnimg.cn/img_convert/00fb6be2016e81e5cb3162dc52e6d2a5.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-05e2c8e3d92d064f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 无论有多少个线程并发到这里，依然只会有一个能成功，成功者执行
 `t.next = node`，也就是让原先的tail节点的next引用指向现在的node，现在的node已经成为了最新的结束节点，不成功者则会继续循环
-![插入一个节点步骤前后动作](https://img-blog.csdnimg.cn/img_convert/d8f71898fb7fbd75671e44fafdc4e8b4.png)
+![插入一个节点步骤前后动作](https://upload-images.jianshu.io/upload_images/4685968-7bb84a7833688a43.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 总之节点都是在链表尾部写入的，而且是线程安全的
 
 知道了AQS大致的写入是一种双向链表的插入操作，但插入链表节点对锁有何用途呢，我们还得退回到`addWaiter`最终返回了要写入的node节点， 再回退到`acquire ()`中所在的代码中需要将这个返回的node节点作为`acquireQueued`入口参数，并传入另一个参数（依然是1），看看它里面到底做了些什么
-```java
+```
+/**
+ * Acquires in exclusive uninterruptible mode for thread already in
+ * queue. Used by condition wait methods as well as acquire
+ *
+ * @param node  the node
+ * @param arg the acquire argument
+ * @return {@code} if interrupted while waiting
+ */
 /**
  * 不支持中断的获取锁
  */
@@ -145,92 +150,45 @@ final boolean acquireQueued(final Node node, int arg){
                 failed = false;
                 return interrupted;                // 4. 返回在整个获取的过程中是否被中断过 ; 但这又有什么用呢? 若整个过程中被中断过, 则最后我在 自我中断一下 (selfInterrupt), 因为外面的函数可能需要知道整个过程是否被中断过
             }
-            // 5. 调用 shouldParkAfterFailedAcquire 判断是否需要中断
-            // 这里可能会一开始返回 false,但在此进去后直接返回true
-            // 主要和前继节点的状态是否是SIGNAL有关
-            if(shouldParkAfterFailedAcquire(p, node) &&
-            		// 6. 现在lock还是被其他线程占用 那就睡一会, 返回值判断是否这次线程的唤醒是被中断唤醒
-                    parkAndCheckInterrupt()){
+            if(shouldParkAfterFailedAcquire(p, node) && // 5. 调用 shouldParkAfterFailedAcquire 判断是否需要中断(这里可能会一开始 返回 false, 但在此进去后直接返回 true(主要和前继节点的状态是否是 signal))
+                    parkAndCheckInterrupt()){      // 6. 现在lock还是被其他线程占用 那就睡一会, 返回值判断是否这次线程的唤醒是被中断唤醒
                 interrupted = true;
             }
         }
     }finally {
-    	// 7. 在整个获取中出错	
-        if(failed){                             
+        if(failed){                             // 7. 在整个获取中出错
             cancelAcquire(node);                // 8. 清除 node 节点(清除的过程是先给 node 打上 CANCELLED标志, 然后再删除)
         }
     }
 }
 ```
 这里也是一个死循环，除非进入`if(p == head && tryAcquire(arg))`，而p为`node.predcessor()`得到
-![](https://img-blog.csdnimg.cn/img_convert/dab1a1ccffa05d17a1f81fb1f89350a5.png)
-返回node节点的前一个节点，也就是说只有当前一个节点是head时，进一步尝试通过`tryAcquire(arg)`来征用才有机会成功。
-
-`tryAcquire(arg)`成立的条件为：锁的状态为0，且通过CAS尝试设置状态成功或线程的持有者本身是当前线程才会返回true。
-
-如果这个条件成功后，发生的几个动作包含：
-- 首先调用setHead(Node)
-![](https://img-blog.csdnimg.cn/img_convert/4cf5b7eac5225d4e42aafa53a29dbece.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-5c4353c6ca9b2caf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-212c321db4092bad.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+这个方法返回node节点的前一个节点，也就是说只有当前一个节点是head时，进一步尝试通过`tryAcquire(arg)`来征用才有机会成功
+`tryAcquire(arg)`成立的条件为：锁的状态为0，且通过CAS尝试设置状态成功或线程的持有者本身是当前线程才会返回true，我们现在来详细拆分这部分代码。
+○ 如果这个条件成功后，发生的几个动作包含：
+（1） 首先调用setHead(Node)
+![](https://upload-images.jianshu.io/upload_images/4685968-b22e59333dd2bd1d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 这个操作会将传入的node节点作为AQS的head所指向的节点。线程属性设置为空（因为现在已经获取到锁，不再需要记录下这个节点所对应的线程了）
-![](https://img-blog.csdnimg.cn/img_convert/a4c954d8d68618e639e807b685747b42.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-680ccec19b2e7ce6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 再将这个节点的prev引用赋值为null
-
-- 进一步将前一个节点的next引用赋值为null。
-在进行了这样的修改后，队列的结构就变成了以下这种情况了，这样就可让执行完的节点释放掉内存区域，而不是无限制增长队列，也就真正形成FIFO了：
-![CAS成功获取锁后，队列的变化](https://img-blog.csdnimg.cn/img_convert/8f4952a09cad9e12582731e12cef71e7.png)
-
-如果这个判定条件失败，会首先判定：
-### shouldParkAfterFailedAcquire
-```java
-shouldParkAfterFailedAcquire(p , node)
-```
-```java
-private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
-    int ws = pred.waitStatus;
-    // 判断前一个节点的状态是否为 Node.SIGNAL
-    // 是则返回true,不是则返回false
-    if (ws == Node.SIGNAL)
-        /*
-         * This node has already set status asking a release
-         * to signal it, so it can safely park.
-         * 
-         */
-        return true;
-    if (ws > 0) {
-        /*
-         * Predecessor was cancelled. Skip over predecessors and
-         * indicate retry.
-         */
-        do {
-            node.prev = pred = pred.prev;
-        } while (pred.waitStatus > 0);
-        pred.next = node;
-    } else {
-        /* 
-         * waitStatus 必须为 0 或 PROPAGATE。
-         * 表明我们需要一个信号，但不要park。
-         * 调用者将需要重试,以确保在park前无法获取
-         */
-        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
-    }
-    return false;
-}
-```
-判定节点的状态是否大于0，若大于0则认为被“CANCELLED”掉了（大于0的只可能CANCELLED态），因此会从前一个节点开始逐步循环找到一个没有被“CANCELLED”节点，然后与这个节点的next、prev的引用相互指向；如果前一个节点的状态不是大于0的，则通过CAS尝试将状态修改为“Node.SIGNAL”，自然的如果下一轮循环的时候会返回值应该会返回true。
-
-若该方法返回了true，则执行parkAndCheckInterrupt()，它通过
-```java
-LockSupport.park(this)
-```
-将当前线程挂起到WATING态，它需要等待一个中断、unpark方法来唤醒它，通过这样一种FIFO机制的等待，实现Lock操作。
-### unlock()
-如果获取到了锁不释放，那自然就成了死锁，所以必须要释放，来看看它内部是如何释放的。同样从排它锁（ReentrantLock）中的unlock()方法开始
-![](https://img-blog.csdnimg.cn/img_convert/3a3e17f0bea5a86fe6bd5600a2fe41ae.png)
-![unlock方法间接调用AQS的release(1)来完成](https://img-blog.csdnimg.cn/img_convert/35da5c344ffe2c300138715b0f5f5eca.png)
+（2） 进一步将前一个节点的next引用赋值为null。
+在进行了这样的修改后，队列的结构就变成了以下这种情况了，通过这样的方式，就可以让执行完的节点释放掉内存区域，而不是无限制增长队列，也就真正形成FIFO了：
+![CAS成功获取锁后，队列的变化](https://upload-images.jianshu.io/upload_images/4685968-f4d36150337c045d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+○ 如果这个判定条件失败
+会首先判定：“shouldParkAfterFailedAcquire(p , node)”，这个方法内部会判定前一个节点的状态是否为：“Node.SIGNAL”，若是则返回true，若不是都会返回false，不过会再做一些操作：判定节点的状态是否大于0，若大于0则认为被“CANCELLED”掉了（我们没有说明几个状态的值，不过大于0的只可能被CANCELLED的状态），因此会从前一个节点开始逐步循环找到一个没有被“CANCELLED”节点，然后与这个节点的next、prev的引用相互指向；如果前一个节点的状态不是大于0的，则通过CAS尝试将状态修改为“Node.SIGNAL”，自然的如果下一轮循环的时候会返回值应该会返回true。
+如果这个方法返回了true，则会执行：“parkAndCheckInterrupt()”方法，它是通过LockSupport.park(this)将当前线程挂起到WATING状态，它需要等待一个中断、unpark方法来唤醒它，通过这样一种FIFO的机制的等待，来实现了Lock的操作。
+
+相应的，可以自己看看FairSync实现类的lock方法，其实区别不大，有些细节上的区别可能会决定某些特定场景的需求，你也可以自己按照这样的思路去实现一个自定义的锁。
+
+接下来简单看看unlock()解除锁的方式，如果获取到了锁不释放，那自然就成了死锁，所以必须要释放，来看看它内部是如何释放的。同样从排它锁（ReentrantLock）中的unlock()方法开始
+![](https://upload-images.jianshu.io/upload_images/4685968-f5d927da4d032bb9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![unlock方法间接调用AQS的release(1)来完成](https://upload-images.jianshu.io/upload_images/4685968-186a5d604097f75d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 通过tryRelease(int)方法进行了某种判定，若它成立则会将head传入到unparkSuccessor(Node)方法中并返回true，否则返回false。
 
 首先来看看tryRelease(int)方法
-![](https://img-blog.csdnimg.cn/img_convert/9ca048731c45896138cf62356a8aeb24.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-dc1d26d5062071f3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 就是一个设置锁状态的操作，而且是将状态减掉传入的参数值（参数是1），如果结果状态为0，就将排它锁的Owner设置为null，以使得其它的线程有机会进行执行。
 
 在排它锁中，加锁的时候状态会增加1（当然可以自己修改这个值），在解锁的时候减掉1，同一个锁，在可以重入后，可能会被叠加为2、3、4这些值，只有unlock()的次数与lock()的次数对应才会将Owner线程设置为空，而且也只有这种情况下才会返回true。
@@ -238,39 +196,12 @@ LockSupport.park(this)
 这一点大家写代码要注意了哦，如果是在循环体中lock()或故意使用两次以上的lock(),而最终只有一次unlock()，最终可能无法释放锁。
    
 在方法unparkSuccessor(Node)中，就意味着真正要释放锁了
-```java
-private void unparkSuccessor(Node node) {
-    /*
-     * If status is negative (i.e., possibly needing signal) try
-     * to clear in anticipation of signalling.  It is OK if this
-     * fails or if status is changed by waiting thread.
-     */
-    int ws = node.waitStatus;
-    if (ws < 0)
-        compareAndSetWaitStatus(node, ws, 0);
-
-    /*
-     * Thread to unpark is held in successor, which is normally
-     * just the next node.  But if cancelled or apparently null,
-     * traverse backwards from tail to find the actual
-     * non-cancelled successor.
-     */
-    Node s = node.next;
-    if (s == null || s.waitStatus > 0) {
-        s = null;
-        for (Node t = tail; t != null && t != node; t = t.prev)
-            if (t.waitStatus <= 0)
-                s = t;
-    }
-    if (s != null)
-        LockSupport.unpark(s.thread);
-}
-```
-传入的是head节点（head节点是已执行完的节点，在后面阐述该方法的body时，都叫head节点），内部首先会发生的动作是获取head节点的next节点，
+![](https://upload-images.jianshu.io/upload_images/4685968-10dc69128dc1c76f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+它传入的是head节点（head节点是已经执行完的节点，在后面阐述这个方法的body的时候都叫head节点），内部首先会发生的动作是获取head节点的next节点，
 如果获取到的节点不为空，则直接通过`LockSupport.unpark()`释放对应的被挂起的线程，这样一来将会有一个节点唤醒后继续进入`acquireQueued`的循环进一步尝试 `tryAcquire()`来获取锁，但是也未必能完全获取到哦，因为此时也可能有一些外部的请求正好与之征用，而且还奇迹般的成功了，那这个线程的运气就有点悲剧了，不过通常乐观认为不会每一次都那么悲剧。
 
 再看看共享锁，从前面的排它锁可以看得出来是用一个状态来标志锁的，而共享锁也不例外，但是Java不希望去定义两个状态，所以它与排它锁的第一个区别就是在`锁的状态`，它用int来标志锁的状态，int有4个字节，它用高16位标志读锁（共享锁），低16位标志写锁（排它锁），高16位每次增加1相当于增加65536（通过1 << 16得到），自然的在这种读写锁中，读锁和写锁的个数都不能超过65535个（条件是每次增加1的，如果递增是跳跃的将会更少）。在计算读锁数量的时候将状态左移16位，而计算排它锁会与65535“按位求与”操作，如下图所示。
-![读写锁中的数量计算及限制](https://img-blog.csdnimg.cn/img_convert/b01d5ac76ebafd1d49aa534ecf23ef41.png)
+![读写锁中的数量计算及限制](https://upload-images.jianshu.io/upload_images/4685968-61ce30bfe4b6daca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 写锁的功能与“ReentrantLock”基本一致，区域在于它会在`tryAcquire`时，判定状态的时候会更加复杂一些（因此有些时候它的性能未必好）。
 
@@ -283,39 +214,34 @@ private void unparkSuccessor(Node node) {
 
 在本节中我们除了学习到AQS的内在,这个是Java层面的队列模型，其实我们也可以利用许多队列模型来解决自己的问题，甚至于可以改写模型模型来满足自己的需求
 
-##  2.4  ConditionObject
+##  2.4  ConditionObject
 ReentrantLock是独占锁，而且AQS的ConditionObject只能与ReentrantLock一起使用，它是为了支持条件队列的锁更方便
 ConditionObject的signal和await方法都是基于独占锁的，如果线程非锁的独占线程，则会抛出IllegalMonitorStateException
 例如signalAll源码：
-```java
-public final void signalAll() {
-    if (!isHeldExclusively())
-        throw new IllegalMonitorStateException();
-    Node first = firstWaiter;
-    if (first != null)
-        doSignalAll(first);
-}
 ```
-
-既然Condtion是为了支持Lock的，为什么ConditionObject不作为ReentrantLock的内部类呢？对于实现锁功能的子类，直接扩展它就可以实现对条件队列的支持。但是，对于其它非锁语义的实现类如Semaphore、CountDownLatch等类来说，条件队列是无用的，也会给开发者扩展AQS带来困惑。总之，是各有利弊
-# FAQ
+        public final void signalAll() {
+            if (!isHeldExclusively())
+                throw new IllegalMonitorStateException();
+            Node first = firstWaiter;
+            if (first != null)
+                doSignalAll(first);
+        }
+```
+      我在想，既然Condtion是为了支持Lock的，为什么ConditionObject不作为ReentrantLock的内部类呢？对于实现锁功能的子类，直接扩展它就可以实现对条件队列的支持。但是，对于其它非锁语义的实现类如Semaphore、CountDownLatch等类来说，条件队列是无用的，也会给开发者扩展AQS带来困惑。总之，是各有利弊，大师们的思想，还需要仔细揣摩啊！
+#关于Lock及AQS的一些补充：
 - Lock的操作不仅仅局限于lock()/unlock()，因为这样线程可能进入WAITING状态，这个时候如果没有unpark()就没法唤醒它，可能会一直“睡”下去，可以尝试用tryLock()、tryLock(long , TimeUnit)来做一些尝试加锁或超时来满足某些特定场景的需要。
 例如有些时候发现尝试加锁无法加上，先释放已经成功对其它对象添加的锁，过一小会再来尝试，这样在某些场合下可以避免“死锁”哦。
 - lockInterruptibly() 它允许抛出`InterruptException`，也就是当外部发起了中断操作，程序内部有可能会抛出这种异常，但是并不是绝对会抛出异常的，大家仔细看看代码便清楚了。
 - newCondition()操作，是返回一个Condition的对象，Condition只是一个接口，它要求实现await()、awaitUninterruptibly()、awaitNanos(long)、await(long , TimeUnit)、awaitUntil(Date)、signal()、signalAll()方法，`AbstractQueuedSynchronizer`中有一个内部类叫做`ConditionObject`实现了这个接口，它也是一个类似于队列的实现，具体可以参考源码。大多数情况下可以直接使用，当然觉得自己比较牛逼的话也可以参考源码自己来实现。
-
-AQS的Node中有每个Node自己的状态（waitStatus）分别包含:
-- SIGNAL
-是前面有线程在运行，需要前面线程结束后，调用unpark()才能激活自己，值为：-1
-- CANCELLED
-当AQS发起取消或fullyRelease()时，会是这个状态。值为1，也是几个状态中唯一一个大于0的状态，所以前面判定状态大于0就基本等价于是CANCELLED
-- CONDITION
-线程基于Condition对象发生了等待，进入了相应的队列，自然也需要Condition对象来激活，值为-2
+- 在AQS的Node中有每个Node自己的状态（waitStatus），我们这里归纳一下，分别包含:
+  - SIGNAL 从前面的代码状态转换可以看得出是前面有线程在运行，需要前面线程结束后，调用unpark()方法才能激活自己，值为：-1
+  - CANCELLED 当AQS发起取消或fullyRelease()时，会是这个状态。值为1，也是几个状态中唯一一个大于0的状态，所以前面判定状态大于0就基本等价于是CANCELLED的意思。
+  - CONDITION 线程基于Condition对象发生了等待，进入了相应的队列，自然也需要Condition对象来激活，值为-2。
 
 PROPAGATE 读写锁中，当读锁最开始没有获取到操作权限，得到后会发起一个doReleaseShared()动作，内部也是一个循环，当判定后续的节点状态为0时，尝试通过CAS自旋方式将状态修改为这个状态，表示节点可以运行。
 状态0 初始化状态，也代表正在尝试去获取临界资源的线程所对应的Node的状态。
 
-# 羊群效应
+#羊群效应
 说一下羊群效应，当有多个线程去竞争同一个锁的时候，假设锁被某个线程占用，那么如果有成千上万个线程在等待锁，有一种做法是同时唤醒这成千上万个线程去去竞争锁，这个时候就发生了羊群效应，海量的竞争必然造成资源的剧增和浪费，因此终究只能有一个线程竞争成功，其他线程还是要老老实实的回去等待。
 AQS的FIFO的等待队列给解决在锁竞争方面的羊群效应问题提供了一个思路：保持一个FIFO队列，队列每个节点只关心其前一个节点的状态，线程唤醒也只唤醒队头等待线程。
-这个思路已经被应用到了分布式锁的实践中
\ No newline at end of file
+这个思路已经被应用到了分布式锁的实践中
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\344\277\241\345\217\267\351\207\217\346\250\241\345\236\213Semaphore.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\344\277\241\345\217\267\351\207\217\346\250\241\345\236\213Semaphore.md"
deleted file mode 100644
index 68b507ec2d..0000000000
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\344\277\241\345\217\267\351\207\217\346\250\241\345\236\213Semaphore.md"
+++ /dev/null
@@ -1,75 +0,0 @@
-
-> 面试官一声冷笑：用过semaphore吧，说说信号量模型？
-
-信号量模型可简单概括为：一个计数器，一个等待队列，三个方法。在信号量模型里，计数器和等待队列对外是透明的，所以只能通过信号量模型提供的三个方法来访问它们，这三个方法分别是：init()、down()和up()。你可以结合下图来形象化地理解。
-
-- 信号量模型
-![](https://img-blog.csdnimg.cn/2021042214541150.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-> 详细解释下里面提到的这些方法？
-
-- init()
-设置计数器初始值。
-- down()
-计数器-1；若此时计数器＜0，则当前线程被阻塞，否则当前线程可继续执行
-- up()
-计数器+1；若此时计数器≤0，则唤醒等待队列中的一个线程，并将其从等待队列中移除。有的人可能认为这里的判断条件应该≥0，估计你是理解生产者-消费者模式中的生产者。可这样思考，`>0` 意味着没有阻塞的线程，所以只有 ≤0 时才需要唤醒一个等待的线程。
-
-down()和up()应该成对出现，并且先调用down()获取锁，处理完成后再调用up()释放锁。若信号量init值为1，应该不会出现>0情况，除非故意调先用up()，这也失去了信号量本身的意义了。
-
-这些方法都是原子性的，并且这个原子性是由信号量模型的实现方保证的。JDK里的信号量模型是由java.util.concurrent.Semaphore实现，Semaphore这个类能够保证这三个方法都是原子操作。
-
-> talk is cheap，show me code？
-
-- 代码
-![](https://img-blog.csdnimg.cn/20210422115452600.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-信号量模型中，down()、up()最早被称为P操作和V操作，信号量模型也称PV原语。还有的人会用semWait()和semSignal()表达它们，叫法不同，语义都相同。JUC的acquire()和release()就对应down()和up()。
-
-> 如何使用信号量？
-
-就像红绿信号灯，车必须先检查是否为绿灯，绿灯才能通过。
-比如累加器，count+=1操作是个临界区，只允许一个线程执行，也就是说要保证互斥。
-![](https://img-blog.csdnimg.cn/20210422122329760.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-分析如下：假设线程t1、t2同时访问add()，当同时调用`acquire()`时，由于`acquire()`是一个原子操作，只可能有一个线程（假设t1）把信号量里的计数器减为0，t2则是将计数器减为-1:
-- 对t1，信号量里面的计数器的值是0，≥0，所以t1继续执行
-- 对t2，信号量里面的计数器的值是-1，＜0，所以t2被阻塞
-
-所以此时只有t1会进入临界区执行count+=1。
-
-当t1执行release()，信号量里计数器的值是-1，加1之后的值是0，小于等于0，根据up()操作，此时等待队列中的t2会被唤醒。于是t2在t1执行完临界区代码后，才获得进入临界区执行的机会，这就保证了互斥性。
-
-
-既然有JDK提供了Lock，为啥还要提供一个Semaphore ？实现互斥锁，仅是 Semaphore部分功能，Semaphore还可以允许多个线程访问一个临界区。
-
-最常见的就是各种池化资源：连接池、对象池、线程池等。比如数据库连接池，同一时刻，一定是允许多个线程同时使用连接池的。每个连接在被释放前，是不允许其他线程使用的。
-
-对象池要求一次性创建出N个对象，之后所有的线程重复利用这N个对象，当然对象在被释放前，也是不允许其他线程使用的。所以核心就是限流器的设计，这里限流指不允许多于N个线程同时进入临界区。
-如何快速实现一个这样的限流器呢？那就是信号量。
-如果我们把计数器的值设置成对象池里对象的个数N，就能完美解决对象池的限流问题了。
-代码如下：
-![](https://img-blog.csdnimg.cn/20210422144637459.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-注意这里使用的是 Vector，进入临界区的N个线程不安全。add/remove都是不安全的。比如 ArrayList remove() ：
-```java
-public E remove(int index) {
-	rangeCheck(index);
-	
-	modCount++;
-	
-	// 假设俩线程 t1,t2都执行到这一步，t1 让出cpu,t2执行
-	E oldValue = elementData(index);
-	// 到这步,t1继续执行，这时t1,t2拿到的oldValue是一样的，两个线程能拿到同一个对象，线程不安全！
-	int numMoved = size - index - 1;
-	if (numMoved > 0)
-	System.arraycopy(elementData, index+1, elementData, index,
-	numMoved);
-	elementData[--size] = null; // clear to let GC do its work
-	
-	return oldValue;
-}
-```
-
-
-
-> 好的，请回家等通知吧！
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22713-\346\230\276\345\274\217\351\224\201.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22713\344\271\213\346\230\276\345\274\217\351\224\201-(Explicit-Locks).md"
similarity index 77%
rename from "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22713-\346\230\276\345\274\217\351\224\201.md"
rename to "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22713\344\271\213\346\230\276\345\274\217\351\224\201-(Explicit-Locks).md"
index e7c7c3d7b4..99605a115d 100644
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22713-\346\230\276\345\274\217\351\224\201.md"
+++ "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22713\344\271\213\346\230\276\345\274\217\351\224\201-(Explicit-Locks).md"
@@ -1,39 +1,42 @@
-Java5之前只能用synchronized和volatile，Java5后Doug Lea提供了ReentrantLock，并非为了替代内置锁，而是当内置锁的机制不适用时，作为一种可选择的高级功能。
-内置锁不适用的场景包括：
-1. 无法中断一个正在等待获取锁的线程
-2. 无限的锁等待
-3. 内置锁必须放在代码块里（编程有些局限性）
+Java5之前只能用synchronized和volatile，5后Doug Lea加入了ReentrantLock，并不是替代内置锁，而是当内置锁机制不适用时，作为一种可选择的高级功能
+不适用包括
+- 无法中断一个正在等待获取锁的线程
+- 无限的锁等待
+- 内置锁必须放在代码块里面（编程有些局限性）
 
-所以提供了J.U.C的Lock接口及实现。
+所以提供了J.U.C的Lock
 # 1.  Lock和ReentrantLock
-![Lock接口定义](https://img-blog.csdnimg.cn/img_convert/4e40e7aa7a5f74a2af6ebe93b6016b44.png)
-![使用范例](https://img-blog.csdnimg.cn/img_convert/105239afbb92bcb12782361fca77836c.png)
+![Lock接口定义](http://upload-images.jianshu.io/upload_images/4685968-dc1ca37c9ee37a2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![使用范例](http://upload-images.jianshu.io/upload_images/4685968-f31c64919b44f26e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 之所以叫ReentrantLock，可理解为两部分
 - Re-entrant
 可重入，lock多少次都没关系，只需要unlock即可，或者lock里面嵌套了别的lock都可以
 - Lock
 提供了和synchronized一样的互斥性和内存可见性，与synchronized的monitor内存语义一样
 
-# 2 Synchronized(S) V.S Lock（L） 
+#2. synchronized（S）    VS     lock（L） 
 -  L 是接口，S 是关键字 
 - S异常时，会自动释放线程占有的锁，不会发生死锁
-L异常时，若没有主动通过 unlock（）释放锁，则很有可能造成死锁。所以用 lock 时要在 finally 中释放锁.。
+L异常时，若没有主动通过 unlock（）释放锁，则很有可能造成死锁.所以用 lock 时要在 finally 中释放锁. 
 - L 可以当等待锁的线程响应中断
 使用 S 时，等待的线程将会一直等下去，不能响应中断
 - 通过 L 可以知道是否成功获得锁，S 不可以 
 - L 可以提高多个线程进行读写操作的效率 
 
-# 3 Lock的特性
+#3 Lock的特性
 *   可定时锁等待
 *   可轮询锁等待
 *   可中断锁等待
 *   公平性
 *   实现非块结构的加锁
 *   绑定多个Condition。通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过await(),signal();
-## 3.1 轮询锁和定时锁
-内置锁的死锁问题只能通过重启解决，可定时、可轮询锁提供了另一种选择：
+
+下面依次讲解：
+
+##3.1 轮询锁和定时锁
+内置锁的死锁问题只能通过重启解决，可定时和可轮询锁提供了另一种选择
 通过`tryLock`解决
-```java
+```
 public class DeadlockAvoidance {
     private static Random rnd = new Random();
 
@@ -112,13 +115,13 @@ public class DeadlockAvoidance {
 }
 
 ```
-## 3.2 带有时间限制的锁
-![](https://img-blog.csdnimg.cn/img_convert/2d2c05b282043297ed90ffca8a563d57.png)
-## 3.3 可中断的锁
-![](https://img-blog.csdnimg.cn/img_convert/aaf10869447932a5d5c95e017dbd18e9.png)
-## 3.4关于Condition
+##3.2 带有时间限制的锁
+![](http://upload-images.jianshu.io/upload_images/4685968-2042b71c6e0a7eec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+##3.3 可中断的锁
+![](http://upload-images.jianshu.io/upload_images/4685968-d1bcc5b13fa2deae.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+##3.4关于Condition
 最典型的就是阻塞的有界队列的实现。
-```java
+```
 public class BoundedBuffer {
 
     private static final Logger logger = LoggerFactory.getLogger(BoundedBuffer.class);
@@ -222,41 +225,49 @@ public class BoundedBuffer {
 }
 
 ```
-# 4 性能考虑因素
+#4 性能考虑因素
 Java5的时候J.U.C的ReentrantLock锁竞争性能非常好，到了Java6使用了改进后的算法来管理内置锁，所以现在差不太多了，只好一点点
 
 竞争性能的影响可伸缩性的关键要素：如果有越多的资源被耗费在锁的管理和线程调度上，那么应用程序得到的资源就越少，锁的实现方式越好，将需要越少的系统调用和上下文切换。
 
-# 5 公平性
-ReentrantLock默认创建非公平的锁，非公平指被阻塞挂起的线程(LockSupport.park)都在AQS的CLH队列中排队等待自己被唤醒。他们是按照发出的请求顺序来排队的，但一旦有一个唤醒的就会和新来的线程竞争锁，新来的可能会“插队”。若新来的成功获取锁，那么它将跳过所有等待线程而开始执行，这意味着本该被唤醒的线程失败了，对不起您回到队列的尾部继续等。
+#5 公平性
+ReentrantLock默认创建非公平的锁，非公平是指
+被阻塞挂起的线程(LockSupport.park)都在AQS的CLH队列中排队等待自己被唤醒
+他们是按照发出的请求顺序来排队的
+但一旦有一个唤醒的就会和新来的线程竞争锁，新来的可能会“插队”，如果新来的成功获取锁，那么它将跳过所有等待线程而开始执行，这意味着本该被唤醒的线程失败了，对不起您回到队列的尾部继续等
+这就是非公平性。
 
 一般，非公平锁的性能要好于公平锁。
-因为一个线程被唤醒是需要时间的，挂起线程和唤醒恢复线程都存在开销，这个空隙如果有其他线程处于ready状态，无需上下文切换，那么直接运行就行。
+原因在于一个线程被唤醒是需要时间的，挂起线程和唤醒恢复线程存在开销，这个空隙如果有其他线程处于ready状态，不需要上下文切换，那么直接运行就行，
+A持有锁，B请求，但是B在恢复的过程中,C可以插队"非公平"的获取锁，然后执行再释放，这时候B刚刚好做完上下文切换可以
+ 执行，这个对于B和C来说是一个“双赢”的局面，是提高吞吐量的原因。
 
-A持有锁，B请求，但B在恢复的过程中，C可以插队"非公平"的获取锁，然后执行再释放，这时候B刚刚好做完上下文切换可以执行，这个对于B和C来说是一个“双赢”的局面，是提高吞吐量的原因。
+那么JVM也没有在其内置锁上采用公平性的机制。
 
-JVM也没有在其内置锁上采用公平性的机制。
-# 6 选型
+#6 synchronized和ReentrantLock的选择
 除非使用到3提到的高级特性，或者内置锁无法满足需求时，否则还是老实用内置锁，毕竟是JVM自身提供的，而不是靠类库，因此可能会执行一些优化。
 
 另外内置锁在利用kill -3 dump thread的时候可以发现栈帧上的一些monitor lock的信息，识别死锁，而J.U.C的锁这方面就不太行，当然JAVA6之后提供了管理和调试接口解决了。
 
-# 7 读-写锁
+#7 读-写锁
 ReentrantLock每次只有一个线程能持有锁，但是这种严格的互斥也会抑制并发。会抑制
 *   写/写
 *   写/读
 *   读/读
 
-冲突，但是很多情况下读操作是非常多的，如果放宽加锁的需求，允许多个读操作可以同时访问数据，那么就可以提升性能。
-**但是要保证读取的数据是最新的,不会有其他线程修改数据**。
+冲突，但是很多情况下读操作是非常多的，如果放宽加锁的需求，允许多个读操作可以同时访问数据，那么就可以提升性能
+**但是要保证读取的数据是最新的,不会有其他线程修改数据**
 
-使用ReadWriteLock的场景：
+使用ReadWriteLock的场景是
 - 一个资源可以被多个读操作访问
 - 被一个写操作访问
-- 但二者不能同时进行
 
-如果读线程正在持有锁，这时候另外一个写线程，那么会优先获取写锁：
-```java
+但是二者不能同时进行
+![](http://upload-images.jianshu.io/upload_images/4685968-f67b0246c467a4af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+如果读线程正在持有锁,这时候另外一个写线程,那么会优先获取写锁
+
+应用
+```
 public class ReadWriteMap<K, V> {
     private final Map<K, V> map;
     private final ReadWriteLock lock=new ReentrantReadWriteLock();
@@ -348,4 +359,4 @@ public class ReadWriteMap<K, V> {
         }
     }
 }
-```
\ No newline at end of file
+```
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22714\344\271\213\346\236\204\345\273\272\350\207\252\345\256\232\344\271\211\347\232\204\345\220\214\346\255\245\345\267\245\345\205\267-(Building-Custom-Synchronizers).md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22714\344\271\213\346\236\204\345\273\272\350\207\252\345\256\232\344\271\211\347\232\204\345\220\214\346\255\245\345\267\245\345\205\267-(Building-Custom-Synchronizers).md"
new file mode 100644
index 0000000000..15002909d5
--- /dev/null
+++ "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22714\344\271\213\346\236\204\345\273\272\350\207\252\345\256\232\344\271\211\347\232\204\345\220\214\346\255\245\345\267\245\345\205\267-(Building-Custom-Synchronizers).md"
@@ -0,0 +1,783 @@
+类库中包含了许多存在状态依赖的类，例如FutureTask、Semaphore和BlockingQueue，他们的一些操作都有前提条件，例如非空，或者任务已完成等。
+
+创建状态依赖类的最简单的房就是在JDK提供了的状态依赖类基础上构造。例如第八章的ValueLactch，如果这些不满足，可以使用Java语言或者类库提供的底层机制来构造，包括
+
+*   内置的条件队列
+*   condition
+*   AQS
+
+这一章就介绍这些。
+
+### [](#141-%E7%8A%B6%E6%80%81%E4%BE%9D%E8%B5%96%E6%80%A7%E7%9A%84%E7%AE%A1%E7%90%86-state-dependence)14.1 状态依赖性的管理 State Dependence
+
+在14.2节会介绍使用条件队列来解决阻塞线程运行的问题。下面先介绍通过轮询和休眠的方式（勉强）的解决。
+
+下面是一个标准的模板，
+
+```
+void blockingAction() throws InterruptedException {
+   acquire lock on object state
+   while (precondition does not hold) {
+      release lock
+      wait until precondition might hold
+      optionally fail if interrupted or timeout expires
+      reacquire lock
+   }
+   perform action
+}
+
+```
+
+下面介绍阻塞有界队列的集中实现方式。依赖的前提条件是：
+
+*   不能从空缓存中获取元素
+*   不能将元素放入已满的缓存中
+
+不满足条件时候，依赖状态的操作可以
+
+*   抛出异常
+*   返回一个错误状态（码）
+*   阻塞直到进入正确的状态
+
+下面是基类，线程安全，但是非阻塞。
+
+```
+@ThreadSafe
+public abstract class BaseBoundedBuffer <V> {
+    @GuardedBy("this") private final V[] buf;
+    @GuardedBy("this") private int tail;
+    @GuardedBy("this") private int head;
+    @GuardedBy("this") private int count;
+
+    protected BaseBoundedBuffer(int capacity) {
+        this.buf = (V[]) new Object[capacity];
+    }
+
+    protected synchronized final void doPut(V v) {
+        buf[tail] = v;
+        if (++tail == buf.length)
+            tail = 0;
+        ++count;
+    }
+
+    protected synchronized final V doTake() {
+        V v = buf[head];
+        buf[head] = null;
+        if (++head == buf.length)
+            head = 0;
+        --count;
+        return v;
+    }
+
+    public synchronized final boolean isFull() {
+        return count == buf.length;
+    }
+
+    public synchronized final boolean isEmpty() {
+        return count == 0;
+    }
+}
+
+```
+
+“先检查再运行”的逻辑解决方案如下，调用者必须自己处理前提条件失败的情况。当然也可以返回错误消息。
+
+当然调用者可以不Sleep，而是直接重试，这种方法叫做**忙等待或者自旋等待（busy waiting or spin waiting. ）**，如果换成很长时间都不变，那么这将会消耗大量的CPU时间！！！所以调用者自己休眠，sleep让出CPU。但是这个时间就很尴尬了，sleep长了万一一会前提条件就满足了岂不是白等了从而响应性低，sleep短了浪费CPU时钟周期。另外可以试试yield，但是这也不靠谱。
+
+```
+@ThreadSafe
+        public class GrumpyBoundedBuffer <V> extends BaseBoundedBuffer<V> {
+    public GrumpyBoundedBuffer() {
+        this(100);
+    }
+
+    public GrumpyBoundedBuffer(int size) {
+        super(size);
+    }
+
+    public synchronized void put(V v) throws BufferFullException {
+        if (isFull())
+            throw new BufferFullException();
+        doPut(v);
+    }
+
+    public synchronized V take() throws BufferEmptyException {
+        if (isEmpty())
+            throw new BufferEmptyException();
+        return doTake();
+    }
+}
+
+class ExampleUsage {
+    private GrumpyBoundedBuffer<String> buffer;
+    int SLEEP_GRANULARITY = 50;
+
+    void useBuffer() throws InterruptedException {
+        while (true) {
+            try {
+                String item = buffer.take();
+                // use item
+                break;
+            } catch (BufferEmptyException e) {
+                Thread.sleep(SLEEP_GRANULARITY);
+            }
+        }
+    }
+}
+
+```
+
+下一步改进下，首先让客户端舒服些。
+
+```
+@ThreadSafe
+public class SleepyBoundedBuffer <V> extends BaseBoundedBuffer<V> {
+    int SLEEP_GRANULARITY = 60;
+
+    public SleepyBoundedBuffer() {
+        this(100);
+    }
+
+    public SleepyBoundedBuffer(int size) {
+        super(size);
+    }
+
+    public void put(V v) throws InterruptedException {
+        while (true) {
+            synchronized (this) {
+                if (!isFull()) {
+                    doPut(v);
+                    return;
+                }
+            }
+            Thread.sleep(SLEEP_GRANULARITY);
+        }
+    }
+
+    public V take() throws InterruptedException {
+        while (true) {
+            synchronized (this) {
+                if (!isEmpty())
+                    return doTake();
+            }
+            Thread.sleep(SLEEP_GRANULARITY);
+        }
+    }
+}
+
+```
+
+这种方式测试失败，那么释放锁，让别人做，自己休眠下，然后再检测，不断的重复这个过程，当然可以解决，但是还是需要做权衡，CPU使用率与响应性之间的抉择。
+
+那么我们想如果这种轮询和休眠的dummy方式不用，而是存在某种挂起线程的方案，并且这种方法能够确保党某个条件成真时候立刻唤醒线程，那么将极大的简化实现工作，这就是条件队列的实现。
+
+Condition Queues的名字来源：it gives a group of threads called the **wait set** a way to wait for a specific condition to become true. Unlike typical queues in which the elements are data items, the elements of a condition queue are the threads waiting for the condition.
+
+每个Java对象都可以是一个锁，每个对象同样可以作为一个条件队列，并且Object的wait、notify和notifyAll就是内部条件队列的API。对象的内置锁（intrinsic lock ）和内置条件队列是关联的，**要调用X中的条件队列的任何一个方法，都必须持有对象X上的锁。**
+
+Object.wait自动释放锁，并且请求操作系统挂起当前线程，从而其他线程可以获得这个锁并修改对象状态。当被挂起的线程唤醒时。它将在返回之前重新获取锁。
+
+```
+@ThreadSafe
+public class BoundedBuffer <V> extends BaseBoundedBuffer<V> {
+    // CONDITION PREDICATE: not-full (!isFull())
+    // CONDITION PREDICATE: not-empty (!isEmpty())
+    public BoundedBuffer() {
+        this(100);
+    }
+
+    public BoundedBuffer(int size) {
+        super(size);
+    }
+
+    // BLOCKS-UNTIL: not-full
+    public synchronized void put(V v) throws InterruptedException {
+        while (isFull())
+            wait();
+        doPut(v);
+        notifyAll();
+    }
+
+    // BLOCKS-UNTIL: not-empty
+    public synchronized V take() throws InterruptedException {
+        while (isEmpty())
+            wait();
+        V v = doTake();
+        notifyAll();
+        return v;
+    }
+
+    // BLOCKS-UNTIL: not-full
+    // Alternate form of put() using conditional notification
+    public synchronized void alternatePut(V v) throws InterruptedException {
+        while (isFull())
+            wait();
+        boolean wasEmpty = isEmpty();
+        doPut(v);
+        if (wasEmpty)
+            notifyAll();
+    }
+}
+
+```
+
+注意，如果某个功能无法通过“轮询和休眠"来实现，那么条件队列也无法实现。
+
+### [](#142-using-condition-queues)14.2 Using Condition Queues
+
+#### [](#1421-%E6%9D%A1%E4%BB%B6%E8%B0%93%E8%AF%8Dthe-condition-predicate)14.2.1 条件谓词The Condition Predicate
+
+The Condition Predicate 是使某个操作成为状态依赖操作的前提条件。take方法的条件谓词是”缓存不为空“，take方法在执行之前必须首先测试条件谓词。同样，put方法的条件谓词是”缓存不满“。
+
+在条件等待中存在一种重要的三元关系，包括
+
+*   加锁
+*   wait方法
+*   条件谓词
+
+条件谓词中包含多个状态变量，而状态变量由一个锁来保护，因此在测试条件谓词之前必须先持有这个锁。锁对象和条件队列对象必须是同一个对象。wait释放锁，线程挂起阻塞，等待知道超时，然后被另外一个线程中断或者被一个通知唤醒。唤醒后，wait在返回前还需要重新获取锁，当线程从wait方法中唤醒，它在重新请求锁时不具有任何特殊的优先级，和其他人一起竞争。
+
+#### [](#1422-%E8%BF%87%E6%97%A9%E5%94%A4%E9%86%92)14.2.2 过早唤醒
+
+其他线程中间插足了，获取了锁，并且修改了遍历，这时候线程获取锁需要重新检查条件谓词。
+
+```
+wait block ----------race to get lock ------------------------------------------get lock ----- 
+                    ^
+wait block --------> race to get lock ------get lock------> perform action  ---> release lock
+                    ^
+                    notifyAll
+
+```
+
+当然有的时候，比如一个你根本不知道为什么别人调用了notify或者notifyAll，也许条件谓词压根就没满足，但是线程还是获取了锁，然后test条件谓词，释放所，其他线程都来了这么一趟，发生这就是“谎报军情”啊。
+
+基于以上这两种情况，都必须重新测试条件谓词。
+
+When using condition waits (Object.wait or Condition.await):
+
+*   Always have a condition predicate——some test of object state that must hold before proceeding;
+*   Always test the condition predicate before calling wait, and again after returning from wait;
+*   Always call wait in a loop;
+*   Ensure that the state variables making up the condition predicate are guarded by the lock associated with the condition queue;
+*   Hold the lock associated with the the condition queue when calling wait, notify, or notifyAll
+*   Do not release the lock after checking the condition predicate but before acting on it.
+
+模板就是：
+
+```
+void stateDependentMethod() throws InterruptedException {
+ // condition predicate must be guarded by lock
+ synchronized(lock) {  
+     while (!conditionPredicate())  //一定在循环里面做条件谓词
+         lock.wait();  //确保和synchronized的是一个对象
+     // object is now in desired state  //不要释放锁
+ }
+} 
+
+```
+
+#### [](#1423-%E4%B8%A2%E5%A4%B1%E7%9A%84%E4%BF%A1%E5%8F%B7)14.2.3 丢失的信号
+
+保证notify一定在wait之后
+
+#### [](#1424-%E9%80%9A%E7%9F%A5)14.2.4 通知
+
+下面介绍通知。
+
+调用notify和notifyAll也得持有与条件队列对象相关联的锁。调用notify，JVM Thread Scheduler在这个条件队列上等待的多个线程中选择一个唤醒，而notifyAll则会唤醒所有线程。因此一旦notify了那么就需要尽快的释放锁，否则别人都竞争等着拿锁，都会进行blocked的状态，而不是线程挂起waiting状态，竞争都了不是好事，但是这是你考了性能因素和安全性因素的一个矛盾，具体问题要具体分析。
+
+下面的方法可以进来减少竞争，但是确然程序正确的实现有些难写，所以这个折中还得自己考虑：
+
+```
+public synchronized void alternatePut(V v) throws InterruptedException {
+        while (isFull())
+            wait();
+        boolean wasEmpty = isEmpty();
+        doPut(v);
+        if (wasEmpty)
+            notifyAll();
+    }
+
+```
+
+使用notify容易丢失信号，所以大多数情况下用notifyAll，比如take notify，却通知了另外一个take，没有通知put，那么这就是信号丢失，是一种“被劫持的”信号。
+
+因此只有满足下面两个条件，才能用notify，而不是notifyAll：
+
+*   所有等待线程的类型都相同
+*   单进单出
+
+#### [](#1425-%E7%A4%BA%E4%BE%8B%E9%98%80%E9%97%A8%E7%B1%BBa-gate-class)14.2.5 示例：阀门类A Gate Class
+
+和第5章的那个TestHarness中使用CountDownLatch类似，完全可以使用wait/notifyAll做阀门。
+
+```
+@ThreadSafe
+public class ThreadGate {
+    // CONDITION-PREDICATE: opened-since(n) (isOpen || generation>n)
+    @GuardedBy("this") private boolean isOpen;
+    @GuardedBy("this") private int generation;
+
+    public synchronized void close() {
+        isOpen = false;
+    }
+
+    public synchronized void open() {
+        ++generation;
+        isOpen = true;
+        notifyAll();
+    }
+
+    // BLOCKS-UNTIL: opened-since(generation on entry)
+    public synchronized void await() throws InterruptedException {
+        int arrivalGeneration = generation;
+        while (!isOpen && arrivalGeneration == generation)
+            wait();
+    }
+}
+
+```
+
+### [](#143-explicit-condition-objects)14.3 Explicit Condition Objects
+
+Lock是一个内置锁的替代，而Condition也是一种广义的**内置条件队列**。
+
+Condition的API如下：
+
+```
+public interface Condition {
+ void await() throws InterruptedException;
+ boolean await(long time, TimeUnit unit)throws InterruptedException;
+ long awaitNanos(long nanosTimeout) throws InterruptedException;
+ void awaitUninterruptibly();
+ boolean awaitUntil(Date deadline) throws InterruptedException;
+ void signal();
+ void signalAll();
+}
+
+```
+
+内置条件队列存在一些缺陷，每个内置锁都只能有一个相关联的条件队列，记住是**一个**。所以在BoundedBuffer这种累中，**多个线程可能在同一个条件队列上等待不同的条件谓词**，所以notifyAll经常通知不是同一个类型的需求。如果想编写一个带有多个条件谓词的并发对象，或者想获得除了条件队列可见性之外的更多的控制权，可以使用Lock和Condition，而不是内置锁和条件队列，这更加灵活。
+
+一个Condition和一个lock关联，想象一个条件队列和内置锁关联一样。在Lock上调用newCondition就可以新建无数个条件谓词，这些condition是可中断的、可有时间限制的，公平的或者非公平的队列操作。
+
+The equivalents of wait, notify, and notifyAll for Condition objects are await, signal, and signalAll。
+
+下面的例子就是改造后的BoundedBuffer，
+
+```
+@ThreadSafe
+public class ConditionBoundedBuffer <T> {
+    protected final Lock lock = new ReentrantLock();
+    // CONDITION PREDICATE: notFull (count < items.length)
+    private final Condition notFull = lock.newCondition();
+    // CONDITION PREDICATE: notEmpty (count > 0)
+    private final Condition notEmpty = lock.newCondition();
+    private static final int BUFFER_SIZE = 100;
+    @GuardedBy("lock") private final T[] items = (T[]) new Object[BUFFER_SIZE];
+    @GuardedBy("lock") private int tail, head, count;
+
+    // BLOCKS-UNTIL: notFull
+    public void put(T x) throws InterruptedException {
+        lock.lock();
+        try {
+            while (count == items.length)
+                notFull.await();
+            items[tail] = x;
+            if (++tail == items.length)
+                tail = 0;
+            ++count;
+            notEmpty.signal();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    // BLOCKS-UNTIL: notEmpty
+    public T take() throws InterruptedException {
+        lock.lock();
+        try {
+            while (count == 0)
+                notEmpty.await();
+            T x = items[head];
+            items[head] = null;
+            if (++head == items.length)
+                head = 0;
+            --count;
+            notFull.signal();
+            return x;
+        } finally {
+            lock.unlock();
+        }
+    }
+}
+
+```
+
+注意这里使用了signal而不是signalll，能极大的减少每次缓存操作中发生的上下文切换和锁请求次数。
+
+使用condition和内置锁和条件队列一样，必须保卫在lock里面。
+
+### [](#144-synchronizer%E5%89%96%E6%9E%90)14.4 Synchronizer剖析
+
+看似ReentrantLock和Semaphore功能很类似，每次只允许一定的数量线程通过，到达阀门时
+
+*   可以通过 lock或者acquire
+*   等待，阻塞住了
+*   取消tryLock，tryAcquire
+*   可中断的，限时的
+*   公平等待和非公平等待
+
+下面的程序是使用Lock做一个Mutex也就是持有一个许可的Semaphore。
+
+```
+@ThreadSafe
+public class SemaphoreOnLock {
+    private final Lock lock = new ReentrantLock();
+    // CONDITION PREDICATE: permitsAvailable (permits > 0)
+    private final Condition permitsAvailable = lock.newCondition();
+    @GuardedBy("lock") private int permits;
+
+    SemaphoreOnLock(int initialPermits) {
+        lock.lock();
+        try {
+            permits = initialPermits;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    // BLOCKS-UNTIL: permitsAvailable
+    public void acquire() throws InterruptedException {
+        lock.lock();
+        try {
+            while (permits <= 0)
+                permitsAvailable.await();
+            --permits;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public void release() {
+        lock.lock();
+        try {
+            ++permits;
+            permitsAvailable.signal();
+        } finally {
+            lock.unlock();
+        }
+    }
+}
+
+```
+
+实际上很多J.U.C下面的类都是基于AbstractQueuedSynchronizer (AQS)构建的，例如CountDownLatch, ReentrantReadWriteLock, SynchronousQueue,and FutureTask（java7之后不是了）。AQS解决了实现同步器时设计的大量细节问题，例如等待线程采用FIFO队列操作顺序。AQS不仅能极大极少实现同步器的工作量，并且也不必处理竞争问题，基于AQS构建只可能在一个时刻发生阻塞，从而降低上下文切换的开销，提高吞吐量。在设计AQS时，充分考虑了可伸缩性，可谓大师Doug Lea的经典作品啊！
+
+### [](#145-abstractqueuedsynchronizer-aqs)14.5 AbstractQueuedSynchronizer (AQS)
+
+基于AQS构建的同步器勒种，最进步的操作包括各种形式的获取操作和释放操作。获取操作是一种依赖状态的操作，并且通常会阻塞。
+
+如果一个类想成为状态依赖的类，它必须拥有一些状态，AQS负责管理这些状态，通过getState,setState, compareAndSetState等protected类型方法进行操作。这是设计模式中的模板模式。
+
+使用AQS的模板如下：
+
+获取锁：首先判断当前状态是否允许获取锁，如果是就获取锁，否则就阻塞操作或者获取失败，也就是说如果是独占锁就可能阻塞，如果是共享锁就可能失败。另外如果是阻塞线程，那么线程就需要进入阻塞队列。当状态位允许获取锁时就修改状态，并且如果进了队列就从队列中移除。
+
+释放锁:这个过程就是修改状态位，如果有线程因为状态位阻塞的话就唤醒队列中的一个或者更多线程。
+
+```
+boolean acquire() throws InterruptedException {
+ while (state does not permit acquire) {
+ if (blocking acquisition requested) {
+ enqueue current thread if not already queued
+ block current thread
+ }
+ else
+ return failure
+ }
+ possibly update synchronization state
+ dequeue thread if it was queued
+ return success
+}
+void release() {
+ update synchronization state
+ if (new state may permit a blocked thread to acquire)
+ unblock one or more queued threads
+}
+
+```
+
+要支持上面两个操作就必须有下面的条件：
+
+*   原子性操作同步器的状态位
+*   阻塞和唤醒线程
+*   一个有序的队列
+
+**1 状态位的原子操作**
+
+这里使用一个32位的整数来描述状态位，前面章节的原子操作的理论知识整好派上用场，在这里依然使用CAS操作来解决这个问题。事实上这里还有一个64位版本的同步器（AbstractQueuedLongSynchronizer），这里暂且不谈。
+
+**2 阻塞和唤醒线程**
+
+标准的JAVA API里面是无法挂起（阻塞）一个线程，然后在将来某个时刻再唤醒它的。JDK 1.0的API里面有Thread.suspend和Thread.resume，并且一直延续了下来。但是这些都是过时的API，而且也是不推荐的做法。
+
+HotSpot在Linux中中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。
+
+在JDK 5.0以后利用JNI在LockSupport类中实现了此特性。
+
+> LockSupport.park() LockSupport.park(Object) LockSupport.parkNanos(Object, long) LockSupport.parkNanos(long) LockSupport.parkUntil(Object, long) LockSupport.parkUntil(long) LockSupport.unpark(Thread)
+
+上面的API中park()是在当前线程中调用，导致线程阻塞，带参数的Object是挂起的对象，这样监视的时候就能够知道此线程是因为什么资源而阻塞的。由于park()立即返回，所以通常情况下需要在循环中去检测竞争资源来决定是否进行下一次阻塞。park()返回的原因有三：
+
+*   其他某个线程调用将当前线程作为目标调用 [`unpark`](http://www.blogjava.net/xylz/java/util/concurrent/locks/LockSupport.html#unpark(java.lang.Thread))；
+*   其他某个线程[中断](http://www.blogjava.net/xylz/java/lang/Thread.html#interrupt())当前线程；
+*   该调用不合逻辑地（即毫无理由地）返回。
+
+其实第三条就决定了需要循环检测了，类似于通常写的while(checkCondition()){Thread.sleep(time);}类似的功能。
+
+**3 有序队列**
+
+在AQS中采用CHL列表来解决有序的队列的问题。
+
+AQS采用的CHL模型采用下面的算法完成FIFO的入队列和出队列过程。该队列的操作均通过Lock-Free（CAS）操作.
+
+自己实现的CLH SpinLock如下：
+
+```
+class ClhSpinLock {
+    private final ThreadLocal<Node> prev;
+    private final ThreadLocal<Node> node;
+    private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node());
+
+    public ClhSpinLock() {
+        this.node = new ThreadLocal<Node>() {
+            protected Node initialValue() {
+                return new Node();
+            }
+        };
+
+        this.prev = new ThreadLocal<Node>() {
+            protected Node initialValue() {
+                return null;
+            }
+        };
+    }
+
+    public void lock() {
+        final Node node = this.node.get();
+        node.locked = true;
+        // 一个CAS操作即可将当前线程对应的节点加入到队列中，
+        // 并且同时获得了前继节点的引用，然后就是等待前继释放锁
+        Node pred = this.tail.getAndSet(node);
+        this.prev.set(pred);
+        while (pred.locked) {// 进入自旋
+        }
+    }
+
+    public void unlock() {
+        final Node node = this.node.get();
+        node.locked = false;
+        this.node.set(this.prev.get());
+    }
+
+    private static class Node {
+        private volatile boolean locked;
+    }
+}
+
+```
+
+对于入队列(*enqueue)：*采用CAS操作，每次比较尾结点是否一致，然后插入的到尾结点中。
+
+```
+do {
+        pred = tail;
+}while ( !compareAndSet(pred,tail,node) );
+
+```
+
+对于出队列(*dequeue*):由于每一个节点也缓存了一个状态，决定是否出队列，因此当不满足条件时就需要自旋等待，一旦满足条件就将头结点设置为下一个节点。
+
+AQS里面有三个核心字段：
+
+> private volatile int state;
+> 
+> private transient volatile Node head;
+> 
+> private transient volatile Node tail;
+
+其中state描述的有多少个线程取得了锁，对于互斥锁来说state<=1。head/tail加上CAS操作就构成了一个CHL的FIFO队列。下面是Node节点的属性。
+
+独占操作的API都是不带有shared，而共享的包括semaphore和countdownlatch都是使用带有shared字面的API。
+
+一些有用的参考资料：
+
+**java.util.concurrent.locks.AbstractQueuedSynchronizer - **AQS
+
+[http://gee.cs.oswego.edu/dl/papers/aqs.pdf](http://gee.cs.oswego.edu/dl/papers/aqs.pdf)论文
+
+[http://www.blogjava.net/xylz/archive/2010/07/08/325587.html](http://www.blogjava.net/xylz/archive/2010/07/08/325587.html) 一个比较全面的另外一个人的解读
+
+[http://suo.iteye.com/blog/1329460](http://suo.iteye.com/blog/1329460)
+
+[http://www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer](http://www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer)
+
+[http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-overview.html](http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-overview.html)
+
+[http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-clh-and-spin-lock.html](http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-clh-and-spin-lock.html)
+
+[http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-locksupport-and-thread-interrupt.html](http://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-locksupport-and-thread-interrupt.html)
+
+独占的就用TRyAcquire, TRyRelease, and isHeldExclusively,共享的就用 tryAcquireShared and TRyReleaseShared. 带有try前缀的方法都是模板方法，AQS用于判断是否可以继续，例如如果tryAcquireShared返回一个负值，那么表示获取锁失败，失败的就需要进入CLH队列，并且挂起线程。
+
+举一个例子，一个简单的闭锁。
+
+```
+@ThreadSafe
+public class OneShotLatch {
+    private final Sync sync = new Sync();
+
+    public void signal() {
+        sync.releaseShared(0);
+    }
+
+    public void await() throws InterruptedException {
+        sync.acquireSharedInterruptibly(0);
+    }
+
+    private class Sync extends AbstractQueuedSynchronizer {
+        protected int tryAcquireShared(int ignored) {
+            // Succeed if latch is open (state == 1), else fail
+            return (getState() == 1) ? 1 : -1;
+        }
+
+        protected boolean tryReleaseShared(int ignored) {
+            setState(1); // Latch is now open
+            return true; // Other threads may now be able to acquire
+
+        }
+    }
+}
+
+```
+
+下面是自己实现的一个Mutex。
+
+```
+/**
+ * Lock free的互斥锁，简单实现，不可重入锁
+ */
+public class Mutex implements Lock {
+
+    private static final int FREE = 0;
+    private static final int BUSY = 1;
+
+    private static class LockSync extends AbstractQueuedSynchronizer {
+
+        private static final long serialVersionUID = 4689388770786922019L;
+
+        protected boolean isHeldExclusively() {
+            return getState() == BUSY;
+        }
+
+        public boolean tryAcquire(int acquires) {
+            return compareAndSetState(FREE, BUSY);
+        }
+
+        protected boolean tryRelease(int releases) {
+            if (getState() == FREE) {
+                throw new IllegalMonitorStateException();
+            }
+
+            setState(FREE);
+            return true;
+        }
+
+        Condition newCondition() {
+            return new ConditionObject();
+        }
+
+    }
+
+    private final LockSync sync = new LockSync();
+
+    public void lock() {
+        sync.acquire(0);
+    }
+
+    public boolean tryLock() {
+        return sync.tryAcquire(0);
+    }
+
+    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
+        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
+    }
+
+    public void unlock() {
+        sync.release(0);
+    }
+
+    public Condition newCondition() {
+        return sync.newCondition();
+    }
+
+    public boolean isLocked() {
+        return sync.isHeldExclusively();
+    }
+
+    public boolean hasQueuedThreads() {
+        return sync.hasQueuedThreads();
+    }
+
+    public void lockInterruptibly() throws InterruptedException {
+        sync.acquireInterruptibly(0);
+    }
+
+}
+
+```
+
+### [](#146-juc%E5%90%8C%E6%AD%A5%E5%99%A8%E5%8B%92%E7%A7%8D%E7%9A%84aqs)14.6 J.U.C同步器勒种的AQS
+
+*   ReentrantLock
+
+```
+protected boolean tryAcquire(int ignored) {
+ final Thread current = Thread.currentThread();
+ int c = getState();
+ if (c == 0) {
+ if (compareAndSetState(0, 1)) {
+ owner = current;
+ return true;
+ }
+ } else if (current == owner) {
+ setState(c+1);
+ return true;
+ }
+ return false;
+} 
+
+```
+
+*   Semaphore和CountDownLatch
+
+```
+protected int tryAcquireShared(int acquires) {
+ while (true) {
+ int available = getState();
+ int remaining = available - acquires;
+ if (remaining < 0
+ || compareAndSetState(available, remaining))
+ return remaining;
+ }
+}
+protected boolean tryReleaseShared(int releases) {
+ while (true) {
+ int p = getState();
+ if (compareAndSetState(p, p + releases))
+ return true;
+ }
+} 
+```
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\227(15)-\345\216\237\345\255\220\351\201\215\345\216\206\344\270\216\351\235\236\351\230\273\345\241\236\345\220\214\346\255\245\346\234\272\345\210\266.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22715\344\271\213\345\216\237\345\255\220\351\201\215\345\216\206\344\270\216\351\235\236\351\230\273\345\241\236\345\220\214\346\255\245\346\234\272\345\210\266(Atomic-Variables-and-Non-blocking-Synchron.md"
similarity index 56%
rename from "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\227(15)-\345\216\237\345\255\220\351\201\215\345\216\206\344\270\216\351\235\236\351\230\273\345\241\236\345\220\214\346\255\245\346\234\272\345\210\266.md"
rename to "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22715\344\271\213\345\216\237\345\255\220\351\201\215\345\216\206\344\270\216\351\235\236\351\230\273\345\241\236\345\220\214\346\255\245\346\234\272\345\210\266(Atomic-Variables-and-Non-blocking-Synchron.md"
index 5aece1fb28..270e953ff1 100644
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\227(15)-\345\216\237\345\255\220\351\201\215\345\216\206\344\270\216\351\235\236\351\230\273\345\241\236\345\220\214\346\255\245\346\234\272\345\210\266.md"
+++ "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230\347\263\273\345\210\22715\344\271\213\345\216\237\345\255\220\351\201\215\345\216\206\344\270\216\351\235\236\351\230\273\345\241\236\345\220\214\346\255\245\346\234\272\345\210\266(Atomic-Variables-and-Non-blocking-Synchron.md"
@@ -1,30 +1,33 @@
-非阻塞算法，用底层的原子机器指令代替锁，确保数据在并发访问中的一致性。
-非阻塞算法被广泛应用于OS和JVM中实现线程/进程调度机制和GC及锁，并发数据结构中。
+近年在并发算法领域的大多数研究都侧重于非阻塞算法，这种算法用底层的原子机器指令来代替锁来确保数据在并发访问中的一致性，非阻塞算法被广泛应用于OS和JVM中实现线程/进程调度机制和GC以及锁，并发数据结构中。
+
+与锁的方案相比，非阻塞算法都要复杂的多，他们在可伸缩性和活跃性上（避免死锁）都有巨大优势。
+
+非阻塞算法，顾名思义，多个线程竞争相同的数据时不会发生阻塞，因此他能在粒度更细的层次上进行协调，而且极大的减少调度开销。
 
-与锁相比，非阻塞算法复杂的多，在可伸缩性和活跃性上（避免死锁）有巨大优势。
-非阻塞算法，即多个线程竞争相同的数据时不会发生阻塞，因此能更细粒度的层次上进行协调，而且极大减少调度开销。
 # 1 锁的劣势
 独占，可见性是锁要保证的。
 
-许多JVM都对非竞争的锁获取和释放做了很多优化，性能很不错。
-但若一些线程被挂起然后稍后恢复运行，当线程恢复后还得等待其他线程执行完他们的时间片，才能被调度，所以挂起和恢复线程存在很大开销。
-其实很多锁的粒度很小，很简单，若锁上存在激烈竞争，那么 调度开销/工作开销 比值就会非常高，降低业务吞吐量。
+许多JVM都对非竞争的锁获取和释放做了很多优化，性能很不错了。
+
+但是如果一些线程被挂起然后稍后恢复运行，当线程恢复后还得等待其他线程执行完他们的时间片，才能被调度，所以挂起和恢复线程存在很大的开销，其实很多锁的力度很小的，很简单，如果锁上存在着激烈的竞争，那么多调度开销/工作开销比值就会非常高。
+
+与锁相比volatile是一种更轻量的同步机制，因为使用volatile不会发生上下文切换或者线程调度操作，但是volatile的指明问题就是虽然保证了可见性，但是原子性无法保证，比如i++的字节码就是N行。
 
-而与锁相比，volatile是一种更轻量的同步机制，因为使用volatile不会发生上下文切换或线程调度操作，但volatile的指明问题就是虽然保证了可见性，但是原子性无法保证。
+如果一个线程正在等待锁，它不能做任何事情，如果一个线程在持有锁的情况下呗延迟执行了，例如发生了缺页错误，调度延迟，那么就没法执行。如果被阻塞的线程优先级较高，那么就会出现priority invesion的问题，被永久的阻塞下去。
 
-- 若一个线程正在等待锁，它不能做任何事情
-- 若一个线程在持有锁情况下被延迟执行了，如发生缺页错误，调度延迟，就没法执行
-- 若被阻塞的线程优先级较高，就会出现priority invesion问题，被永久阻塞
 # 2  硬件对并发的支持
-独占锁是悲观锁，对细粒度的操作，更高效的应用是乐观锁，这种方法需要借助**冲突监测机制，来判断更新过程中是否存在来自其他线程的干扰，若存在，则失败重试**。
 
-几乎所有现代CPU都有某种形式的原子读-改-写指令，如compare-and-swap等，JVM就是使用这些指令来实现无锁并发。
+独占锁是悲观所，对于细粒度的操作，更高效的应用是乐观锁，这种方法需要借助**冲突监测机制来判断更新过程中是否存在来自其他线程的干扰，如果存在则失败重试**。
+
+几乎所有的现代CPU都有某种形式的原子读-改-写指令，例如compare-and-swap等，JVM就是使用这些指令来实现无锁并发。
+
 ## 2.1 比较并交换
+
 CAS（Compare and set）乐观的技术。Java实现的一个compare and set如下，这是一个模拟底层的示例：
+
 ```java
 @ThreadSafe
 public class SimulatedCAS {
-
     @GuardedBy("this") private int value;
 
     public synchronized int get() {
@@ -45,8 +48,11 @@ public class SimulatedCAS {
                 == compareAndSwap(expectedValue, newValue));
     }
 }
+
 ```
+
 ## 2.2 非阻塞的计数器
+
 ```java
 public class CasCounter {
     private SimulatedCAS value;
@@ -63,36 +69,62 @@ public class CasCounter {
         return v + 1;
     }
 }
+
 ```
+
 Java中使用AtomicInteger。
 
-竞争激烈一般时，CAS性能远超基于锁的计数器。看起来他的指令更多，但无需上下文切换和线程挂起，JVM内部的代码路径实际很长，所以反而好些。
+首先在竞争激烈一般时候，CAS性能远超过第三章基于锁的计数器。看起来他的指令更多，但是无需上下文切换和线程挂起，JVM内部的代码路径实际很长，所以反而好些。但是激烈程度比较高的时候，它的开销还是比较大，但是你会发生这种激烈程度非常高的情况只是理论，实际生产环境很难遇到。况且JIT很聪明，这种操作往往能非常大的优化。
 
-但激烈程度较高时，开销还是较大，但会发生这种激烈程度非常高的情况只是理论，实际生产环境很难遇到。况且JIT很聪明，这种操作往往能非常大的优化。
+为了确保正常更新，可能得将CAS操作放到for循环里，从语法结构上来看，使用**CAS**比使用锁更加复杂，得考虑失败的情况（锁会挂起线程，直到恢复）；但是基于**CAS**的原子操作，在性能上基本超过了基于锁的计数器，即使只有很小的竞争或者不存在竞争！
 
-为确保正常更新，可能得将CAS操作放到for循环，从语法结构看，使用**CAS**比使用锁更加复杂，得考虑失败情况（锁会挂起线程，直到恢复）。
-但基于**CAS**的原子操作，性能基本超过基于锁的计数器，即使只有很小的竞争或不存在竞争！
+在轻度到中度的争用情况下，非阻塞算法的性能会超越阻塞算法，因为 CAS 的多数时间都在第一次尝试时就成功，而发生争用时的开销也不涉及**线程挂起**和**上下文切换**，只多了几个循环迭代。没有争用的 CAS 要比没有争用的锁便宜得多（这句话肯定是真的，因为没有争用的锁涉及 CAS 加上额外的处理，加锁至少需要一个CAS，在有竞争的情况下，需要操作队列，线程挂起，上下文切换），而争用的 CAS 比争用的锁获取涉及更短的延迟。
 
-在轻度到中度争用情况下，非阻塞算法的性能会超越阻塞算法，因为 CAS 的多数时间都在第一次尝试时就成功，而发生争用时的开销也不涉及**线程挂起**和**上下文切换**，只多了几个循环迭代。
-没有争用的 CAS 要比没有争用的锁轻量得多（因为没有争用的锁涉及 CAS 加上额外的处理，加锁至少需要一个CAS，在有竞争的情况下，需要操作队列，线程挂起，上下文切换），而争用的 CAS 比争用的锁获取涉及更短的延迟。
+CAS的缺点是它使用调用者来处理竞争问题，通过重试、回退、放弃，而锁能自动处理竞争问题，例如阻塞。
 
-CAS的缺点是，它使用调用者来处理竞争问题，通过重试、回退、放弃，而锁能自动处理竞争问题，例如阻塞。
+原子变量可以看做更好的volatile类型变量。
+
+AtomicInteger在JDK8里面做了改动。
+
+```java
+public final int getAndIncrement() {
+    return unsafe.getAndAddInt(this, valueOffset, 1);
+}
+
+```
 
-原子变量可看做更好的volatile类型变量。AtomicInteger在JDK8里面做了改动。
-![](https://img-blog.csdnimg.cn/0f94ab5e4b6045e5aa83d99cbc9c03c4.png)
 JDK7里面的实现如下：
-![](https://img-blog.csdnimg.cn/d2f94066894a4501b6dd5e6d9ad4a8c1.png)
-Unsafe是经过特殊处理的，不能理解成常规的Java代码，1.8在调用getAndAddInt时，若系统底层：
-- 支持fetch-and-add，则执行的就是native方法，使用fetch-and-add
-- 不支持，就按照上面getAndAddInt那样，以Java代码方式执行，使用compare-and-swap
+
+```java
+public final int getAndAdd(int delta) {
+       for(;;) {
+           intcurrent= get();
+           intnext=current+delta;
+           if(compareAndSet(current,next))
+               returncurrent;
+        }
+    }
+
+```
+
+Unsafe是经过特殊处理的，不能理解成常规的Java代码，区别在于：
+
+- 1.8在调用getAndAddInt的时候，如果系统底层支持fetch-and-add，那么它执行的就是native方法，使用的是fetch-and-add
+- 如果不支持，就按照上面的所看到的getAndAddInt方法体那样，以java代码的方式去执行，使用的是compare-and-swap
 
 这也正好跟openjdk8中Unsafe::getAndAddInt上方的注释相吻合：
-以下包含在不支持本机指令的平台上使用的基于 CAS 的 Java 实现
-![](https://img-blog.csdnimg.cn/327bda8392cf4158ab94049e67f9b169.png)
+
+```java
+// The following contain CAS-based Java implementations used on
+// platforms not supporting native instructions
+```
+
 # 3 原子变量类
-J.U.C的AtomicXXX。
+
+J.U.C的AtomicXXX
 
 例如一个AtomictReference的使用如下：
+
 ```java
 public class CasNumberRange {
     @Immutable
@@ -140,11 +172,18 @@ public class CasNumberRange {
         }
     }
 }
+
 ```
+
+
 # 4 非阻塞算法
+
 Lock-free算法，可以实现栈、队列、优先队列或者散列表。
+
 ## 4.1 非阻塞的栈
-Trebier算法，1986年提出。
+
+Trebier算法，1986年提出的。
+
 ```java
  public class ConcurrentStack <E> {
     AtomicReference<Node<E>> top = new AtomicReference<Node<E>>();
@@ -179,9 +218,13 @@ Trebier算法，1986年提出。
         }
     }
 }
+
 ```
+
 ## 4.2 非阻塞的链表
-J.U.C的ConcurrentLinkedQueue也是参考这个由Michael and Scott，1996年实现的算法。
+
+有点复杂哦，实际J.U.C的ConcurrentLinkedQueue也是参考了这个由Michael and Scott，1996年实现的算法。
+
 ```java
 public class LinkedQueue <E> {
 
@@ -222,14 +265,19 @@ public class LinkedQueue <E> {
         }
     }
 }
+
 ```
+
 ## 4.3 原子域更新
-AtomicReferenceFieldUpdater，一个基于反射的工具类，能对指定类的指定的volatile引用字段进行原子更新。(该字段不能是private的) 
+
+AtomicReferenceFieldUpdater,一个基于反射的工具类，它能对指定类的指定的volatile引用字段进行原子更新。(注意这个字段不能是private的) 
 
 通过调用AtomicReferenceFieldUpdater的静态方法newUpdater就能创建它的实例，该方法要接收三个参数： 
+
 *   包含该字段的对象的类 
 *   将被更新的对象的类 
 *   将被更新的字段的名称 
+
 ```java
 AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Dog.class,String.class,"name");  
         Dog dog1=new Dog();  
@@ -238,5 +286,7 @@ AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Dog.c
 
 class Dog  {  
      volatile  String name="dog1";  
+
 }  
-```
\ No newline at end of file
+
+```
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\347\237\245\350\257\206\347\202\271\345\205\250\346\200\273\347\273\223.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\347\237\245\350\257\206\347\202\271\345\205\250\346\200\273\347\273\223.md"
index 3c9eb32787..7f0adf7cdb 100644
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\347\237\245\350\257\206\347\202\271\345\205\250\346\200\273\347\273\223.md"
+++ "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\347\237\245\350\257\206\347\202\271\345\205\250\346\200\273\347\273\223.md"
@@ -1,14 +1,18 @@
-# 1 基本概念
-## 1.1 并发
+![](https://upload-images.jianshu.io/upload_images/4685968-f12a51fabb09287a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![高并发处理的思路及手段](https://upload-images.jianshu.io/upload_images/4685968-881327a0ac18f5a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-f6439f7c997bfcf8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+#1 基本概念
+##1.1 并发
 同时拥有两个或者多个线程，如果程序在单核处理器上运行多个线程将交替地换入或者换出内存,这些线程是同时“存在"的，每个线程都处于执行过程中的某个状态，如果运行在多核处理器上,此时，程序中的每个线程都将分配到一个处理器核上，因此可以同时运行.
-## 1.2 高并发( High Concurrency) 
+##1.2 高并发( High Concurrency) 
 互联网分布式系统架构设计中必须考虑的因素之一，通常是指，通过设计保证系统能够同时并行处理很多请求.
-## 1.3 区别与联系
+##1.3 区别与联系
 - 并发: 多个线程操作相同的资源，保证线程安全，合理使用资源
 - 高并发:服务能同时处理很多请求，提高程序性能
-# 2 CPU
-## 2.1 CPU 多级缓存
-![](https://img-blog.csdnimg.cn/img_convert/03859736ec299060001e2702e49ce90f.png)
+#2 CPU
+##2.1 CPU 多级缓存
+![](https://upload-images.jianshu.io/upload_images/4685968-6fc8b5b6509ca6d7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 为什么需要CPU cache
 CPU的频率太快了，快到主存跟不上
 如此,在处理器时钟周期内，CPU常常需要等待主存，浪费资源。所以cache的出现，是为了缓解CPU和内存之间速度的不匹配问题(结构:cpu-> cache-> memory ).
@@ -17,7 +21,7 @@ CPU的频率太快了，快到主存跟不上
  如果某个数据被访问，那么在不久的将来它很可能被再次访问
   2) 空间局部性
 如果某个数据被访问，那么与它相邻的数据很快也可能被访问
-## 2.2 缓存一致性(MESI)
+##2.2 缓存一致性(MESI)
 用于保证多个 CPU cache 之间缓存共享数据的一致
 - M-modified被修改
 该缓存行只被缓存在该 CPU 的缓存中,并且是被修改过的,与主存中数据是不一致的,需在未来某个时间点写回主存,该时间是允许在其他CPU 读取主存中相应的内存之前,当这里的值被写入主存之后,该缓存行状态变为 E
@@ -27,31 +31,30 @@ CPU的频率太快了，快到主存跟不上
 - S-shared共享
 该缓存行可被多个 CPU 缓存,与主存中数据一致
 - I-invalid无效
-![](https://img-blog.csdnimg.cn/img_convert/6f134b3e29210e5efab39c87b65330f4.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-d2325f1e0e5b7786.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 乱序执行优化
 处理器为提高运算速度而做出违背代码原有顺序的优化
-## 并发的优势与风险
-![](https://img-blog.csdnimg.cn/img_convert/611324b1cf6a4e73e374489d631ba506.png)
-# 3 项目准备
-## 3.1 项目初始化
-![自定义4个基本注解](https://img-blog.csdnimg.cn/img_convert/39ce4a664f6c0aae2ac8254813397adf.png)
-![随手写个测试类](https://img-blog.csdnimg.cn/img_convert/bb06219a24cbb1e50b01982299566c07.png)
-![运行正常](https://img-blog.csdnimg.cn/img_convert/3bdb11cf5def6daa6196b1a57c5d2d17.png)
-##  3.2 并发模拟-Jmeter压测
-![](https://img-blog.csdnimg.cn/img_convert/bc910009c5ef0fdeedd010888f792947.png)
-![](https://img-blog.csdnimg.cn/img_convert/a40c37633be6b1ada9013773224d7998.png)
-![添加"查看结果数"和"图形结果"监听器](https://img-blog.csdnimg.cn/img_convert/e21935009955b121f641ccaba344edab.png)
-![log view 下当前日志信息](https://img-blog.csdnimg.cn/img_convert/0c48834ab0b6c22746d638967d62bc20.png)
-![图形结果](https://img-blog.csdnimg.cn/img_convert/c0603ace9b85444880b66e7493a3736f.png)
+##并发的优势与风险
+![](https://upload-images.jianshu.io/upload_images/4685968-e083e9bf164b7d73.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#3 项目准备
+##3.1 项目初始化
+![自定义4个基本注解](https://upload-images.jianshu.io/upload_images/4685968-9ea512f5c1b3b4ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![随手写个测试类](https://upload-images.jianshu.io/upload_images/4685968-7bc23c076be35936.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![运行正常](https://upload-images.jianshu.io/upload_images/4685968-c7fba914bceb792e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+##3.2 并发模拟-Jmeter压测
+![](https://upload-images.jianshu.io/upload_images/4685968-8c994437f5663dd6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-0a6f7c5e0217aa17.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![添加"查看结果数"和"图形结果"监听器](https://upload-images.jianshu.io/upload_images/4685968-646e66ad4e2c7ffc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![log view 下当前日志信息](https://upload-images.jianshu.io/upload_images/4685968-cc70555c9f3b78b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![图形结果](https://upload-images.jianshu.io/upload_images/4685968-01c21bb9069bcd28.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 3.3 并发模拟-代码
 ### CountDownLatch
-- 可阻塞线程,并保证当满足特定条件时可继续执行
-### Semaphore(信号量)
-可阻塞线程,控制同一时间段内的并发量。
-
-以上二者通常和线程池搭配。
+![可阻塞线程,并保证当满足特定条件时可继续执行](https://upload-images.jianshu.io/upload_images/4685968-0d0481ec5302cadb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+###Semaphore(信号量)
+![可阻塞线程,控制同一时间段内的并发量](https://upload-images.jianshu.io/upload_images/4685968-3133af3b8e419a39.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+以上二者通常和线程池搭配
 
-并发模拟：
+下面开始做并发模拟
 ```java
 package com.mmall.concurrency;
 
@@ -195,7 +198,17 @@ public class AtomicExample2 {
 }
 ```
 
-```java
+```
+package com.mmall.concurrency.example.atomic;
+
+import com.mmall.concurrency.annoations.ThreadSafe;
+import lombok.extern.slf4j.Slf4j;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * @author JavaEdge
+ * @date 18/4/3
+ */
 @Slf4j
 @ThreadSafe
 public class AtomicExample4 {
@@ -217,21 +230,21 @@ public class AtomicExample4 {
     }
 }
 ```
-![](https://img-blog.csdnimg.cn/img_convert/ed21be49eb204a082bdf73e6f454169e.png)
+![输出结果](https://upload-images.jianshu.io/upload_images/4685968-b60dac7453bd0904.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - AtomicReference,AtomicReferenceFieldUpdater
-![](https://img-blog.csdnimg.cn/img_convert/035f5f4e875de5f6acc4c825d60b19fc.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-d726108a34669255.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - AtomicBoolean
-![](https://img-blog.csdnimg.cn/img_convert/b761bb82385e4e8750092328ac1cbf44.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-b5fb1eaf9938c163.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 - AtomicStampReference : CAS的 ABA 问题
 ### 4.2.2 锁
 synchronized:依赖 JVM
 - 修饰代码块:大括号括起来的代码，作用于调用的对象
 - 修饰方法: 整个方法，作用于调用的对象
-![](https://img-blog.csdnimg.cn/img_convert/dc5f64990fe69a62f9c722c790447e62.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-88b9935f21beef0e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 修饰静态方法:整个静态方法，作用于所有对象
-![](https://img-blog.csdnimg.cn/img_convert/8cfd5c38ff9079fa2a9025ae6cc2573c.png)
-```java
+![](https://upload-images.jianshu.io/upload_images/4685968-f0b3530e761400d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+```
 package com.mmall.concurrency.example.count;
 
 import com.mmall.concurrency.annoations.ThreadSafe;
@@ -318,9 +331,9 @@ JMM关于synchronized的规定
 屏障指令，将本地内存中的共享变量值刷新到主内存
 - 对volatile变量读操作时，会在读操作前加入一条load
 屏障指令，从主内存中读取共享变量
-![volatile 写](https://img-blog.csdnimg.cn/img_convert/9b5f34400031011ab65c6f0535fd4bcb.png)
-![volatile 读](https://img-blog.csdnimg.cn/img_convert/d7340f94c160e7f53785d31ee9b4094a.png)
-![计数类之 volatile 版,非线程安全的](https://img-blog.csdnimg.cn/img_convert/c949a42943079f294d9d5162988829e5.png)
+![volatile 写](https://upload-images.jianshu.io/upload_images/4685968-1b52dfe716f14632.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![volatile 读](https://upload-images.jianshu.io/upload_images/4685968-fe9dd1b03c64c7f4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![计数类之 volatile 版,非线程安全的](https://upload-images.jianshu.io/upload_images/4685968-977262be174e2dbc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - volatile使用
 ```java
 volatile boolean inited = false;
@@ -352,19 +365,16 @@ JMM允许编译器和处理器对指令进行重排序，但是重排序过程
 - 对象逸出
 一种错误的发布。当-个对象还没有构造完成时,就使它被其他线程所见
 
-![发布对象](https://img-blog.csdnimg.cn/img_convert/584fdb52d87f3c3250dbabf80b68909b.png)
+![发布对象](https://upload-images.jianshu.io/upload_images/4685968-b368f6fe5b350cbe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 
-![对象逸出](https://img-blog.csdnimg.cn/img_convert/03a669241b1b109b5c3aedbdb6e8e0e6.png)
+![对象逸出](https://upload-images.jianshu.io/upload_images/4685968-88d207fcc6bf1866.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 ## 5.1 安全发布对象
-- 在静态初始化函数中初始化一个对象引用
-- 将对象的引用保存到volatile类型域或者AtomicReference对象中
-- 将对象的引用保存到某个正确构造对象的final类型域中
-- 将对象的引用保存到一个由锁保护的域中
-![非线程安全的懒汉模式](https://img-blog.csdnimg.cn/img_convert/53083bac102f7277c08ff5f57bbe9c34.png)
-![饿汉模式](https://img-blog.csdnimg.cn/img_convert/393a83dc5077c07536f2efba418bd3df.png)
-![线程安全的懒汉模式](https://img-blog.csdnimg.cn/img_convert/77fca2c11aaebe47cc5e1324f30c2c7f.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-7400ab2abe1dbbfb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![非线程安全的懒汉模式](https://upload-images.jianshu.io/upload_images/4685968-ba18bdbe3a3c4ed1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![饿汉模式](https://upload-images.jianshu.io/upload_images/4685968-be2854c290143094.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![线程安全的懒汉模式](https://upload-images.jianshu.io/upload_images/4685968-e632243a5a97281a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ```java
 package com.mmall.concurrency.example.singleton;
 
@@ -420,12 +430,12 @@ public class SingletonExample4 {
     }
 }
 ```
-![](https://img-blog.csdnimg.cn/img_convert/049d7847d6ba8e5add4aa1749d61d503.png)
-![](https://img-blog.csdnimg.cn/img_convert/532650dc170a88ce1476c315d0f999a7.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-823166cdf7936293.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-9002671b71096f6c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 # 7 AQS
 ## 7.1 介绍
-![数据结构](https://img-blog.csdnimg.cn/img_convert/a31bb8a3d2d938be38bac2a2f46f6baa.png)
+![数据结构](https://upload-images.jianshu.io/upload_images/4685968-918dcaea77d556e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 使用Node实现FIFO队列，可以用于构建锁或者其他同步装置的基础框架
 - 利用了一个int类型表示状态
 - 使用方法是继承
@@ -532,11 +542,11 @@ public class CountDownLatchExample2 {
 }
 ```
 ##Semaphore用法
-![](https://img-blog.csdnimg.cn/img_convert/750059f485bfac67c031496937c2c678.png)
-![](https://img-blog.csdnimg.cn/img_convert/455407acb625c57b1de26351afc395d1.png)
-![](https://img-blog.csdnimg.cn/img_convert/32149d53141d77efe6906ead30215c51.png)
-## CycliBarrier
-```java
+![](https://upload-images.jianshu.io/upload_images/4685968-e6cbcd4254c642c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-dbefbf2c76ad5a2a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-41f5f5a5fd135804.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+##CycliBarrier
+```
 package com.mmall.concurrency.example.aqs;
 
 import lombok.extern.slf4j.Slf4j;
@@ -579,8 +589,8 @@ public class CyclicBarrierExample1 {
     }
 }
 ```
-![](https://img-blog.csdnimg.cn/img_convert/61b5f12aed68f4879eaf421921180919.png)
-```java
+![](https://upload-images.jianshu.io/upload_images/4685968-4fb51fa4926fd70e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+```
 package com.mmall.concurrency.example.aqs;
 
 import lombok.extern.slf4j.Slf4j;
@@ -628,8 +638,8 @@ public class CyclicBarrierExample2 {
     }
 }
 ```
-![await 超时导致程序抛异常](https://img-blog.csdnimg.cn/img_convert/fd7b571f566476e03b76b10f2a4eff35.png)
-```java
+![await 超时导致程序抛异常](https://upload-images.jianshu.io/upload_images/4685968-0f899c23531f8ee8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+```
 package com.mmall.concurrency.example.aqs;
 
 import lombok.extern.slf4j.Slf4j;
@@ -677,93 +687,85 @@ public class SemaphoreExample3 {
 
 }
 ```
-# 9 线程池
-## 9.1 newCachedThreadPool
-![](https://img-blog.csdnimg.cn/img_convert/f62ed32ba431e25c4e6a8a3a111a571b.png)
-## 9.2 newFixedThreadPool
-![](https://img-blog.csdnimg.cn/img_convert/bc0c48352c23a3dba6719b375e71f972.png)
-## 9.3 newSingleThreadExecutor
+#9 线程池
+##9.1 newCachedThreadPool
+![](https://upload-images.jianshu.io/upload_images/4685968-1122da7a48223ba1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+##9.2 newFixedThreadPool
+![](https://upload-images.jianshu.io/upload_images/4685968-0ea942bf12e5210f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+##9.3 newSingleThreadExecutor
 看出是顺序执行的
-![](https://img-blog.csdnimg.cn/img_convert/01743428143ee985c8b77ef540b4f7d6.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-989d59429f589403.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 ## 9.4 newScheduledThreadPool
-![](https://img-blog.csdnimg.cn/img_convert/fbd0a4e6d07f448eb40fd7b8f1cd25c6.png)
-![](https://img-blog.csdnimg.cn/img_convert/ff45eb82a16ae5504cc575cb44ed51e8.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-f7536ec7a1cf6ecc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-c90e09d5bfe707e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 # 10 死锁
-### 必要条件
-互斥条件
-请求和保持条件
-不剥夺条件
-环路等待条件
+![](https://upload-images.jianshu.io/upload_images/4685968-461f6a4251ae8ca4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-46d58773e597195f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 # 11 高并发之扩容思路
 ## 11.1 扩容
-垂直扩容(纵向扩展) :提高系统部件能力
-水平扩容(横向扩展) :增加更多系统成员来实现
+![](https://upload-images.jianshu.io/upload_images/4685968-8fabe01a7a9073ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 11.1 扩容 - 数据库
-读操作扩展: memcache、redis、 CDN等缓存
-写操作扩展: Cassandra、Hbase等
+![](https://upload-images.jianshu.io/upload_images/4685968-f1b501aa5f49953d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 # 12 高并发之缓存思路
 ## 12.1 缓存
-![](https://img-blog.csdnimg.cn/img_convert/76f907d1cc784020d995a7cf10dbd94f.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-c5b8fa643df25009.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ### 1 缓存特征
-命中率:命中数/(命中数+没有命中数)
-最大元素(空间)
-清空策略:FIFO，LFU，LRU,过期时间，随机等
-### 2 缓存分类和应用场景
-本地缓存：编程实现(成员变量、局部变量、静态变量)、Guava Cache
+![](https://upload-images.jianshu.io/upload_images/4685968-272a9236ceace316.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 2 缓存命中率影响因素
+![](https://upload-images.jianshu.io/upload_images/4685968-302f50ee02ba8cf5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 3  缓存分类和应用场景
+![](https://upload-images.jianshu.io/upload_images/4685968-52b2f576007d9b70.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
-分布式缓存：Memcache、Redis
+##  12.2 高并发之缓存-特征、场景及组件介绍
 
-## 12.2 高并发之缓存-特征、场景及组件
 ### 1 Guava Cache
-![](https://img-blog.csdnimg.cn/img_convert/d067a93f221947fa484f9ee907f7eb86.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-23d4a01198f33926.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
 ### 2 缓存 - Memchche
-![](https://img-blog.csdnimg.cn/img_convert/9b11cc84e7057677c5a96ddeee3fccac.png)
-![](https://img-blog.csdnimg.cn/img_convert/f020c59264775ab2879c0033b0f99e23.png)
-![](https://img-blog.csdnimg.cn/img_convert/b9ee8bc6ce9fd595c8d6ccf1ff8c3b30.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-ddde5e19b73fd570.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-7659178fc344a005.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-59c0c75277c9f31f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 ### 3 缓存 - Redis
-![](https://img-blog.csdnimg.cn/img_convert/979b05e24d7c4bd42e8a670be0b2cb7c.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-c8267b97a065b4b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 ## 12.3 redis的使用
 - 配置类
-![](https://img-blog.csdnimg.cn/img_convert/0028d7224be1324243cccf0c8f7bfc1b.png)
-![](https://img-blog.csdnimg.cn/img_convert/7a999a064f2d5b68f10307dbc0294af8.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-4134748d1b3181f2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-8634e07d480b8546.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 服务类
-![](https://img-blog.csdnimg.cn/img_convert/832c747fdc3815b4236f15fe49c7e326.png)
-![](https://img-blog.csdnimg.cn/img_convert/021f2e1f7504f684fabc7fa8ff705c82.png)
-![](https://img-blog.csdnimg.cn/img_convert/26917bd4ba7dd415543215aff2e443e7.png)
-![](https://img-blog.csdnimg.cn/img_convert/7c8093cefa4e7345caa275623cb300fe.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-fdb9daccd4778227.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-0c8d8a14d833b9e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-461ff2bf851c25ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-a6efafc286fd0119.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 ## 12.4 高并发场景问题及实战
 
 
 ### 缓存一致性
-![](https://img-blog.csdnimg.cn/img_convert/3394e70367765d18207a4c843e343b27.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-8897cf0ea46fd70e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 # 13 高并发之消息队列思路
 ## 13.1 业务案例
-![](https://img-blog.csdnimg.cn/img_convert/6f36c1d234f9df2a3965e1165e83229d.png)
+![](https://upload-images.jianshu.io/upload_images/4685968-269e8e4d643ab023.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 将发短信封装成一条消息放进消息队列中,若发生短信过多,队列已满,需要控制发送的频率.
 通过将事件封装成消息放入队列,实现了业务解耦,异步设计,确保了短信服务只要正常后,一定会将短信成功发到用户.
 
 ## 13.2 消息队列的特性
-业务无关:只做消息分发
-FIFO :先投递先到达
-容灾:节点的动态增删和消息的持久化
-性能:吞吐量提升,系统内部通信效率提高
-
+![](https://upload-images.jianshu.io/upload_images/4685968-4153efed9fa882d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 - 为何需要消息队列呢
-[生产]和[消费]的速度或稳定性等因素不一致
+![](https://upload-images.jianshu.io/upload_images/4685968-4d0a52ea405e86af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 - 优点
-业务解耦
-最终一致性
-广播
-错峰与流控
+![](https://upload-images.jianshu.io/upload_images/4685968-6ff5a7fbc96173b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5b2f547310cfd06f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
 ## 队列
 ### kafka
-![](https://img-blog.csdnimg.cn/img_convert/3f5b237b1a8054b8f19b5ead0161874d.png)
\ No newline at end of file
+![](https://upload-images.jianshu.io/upload_images/4685968-f387cab7dbc62cb3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+、
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\232\204Condition\346\216\245\345\217\243.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\232\204Condition\346\216\245\345\217\243.md"
deleted file mode 100644
index 8d7c46edac..0000000000
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\232\204Condition\346\216\245\345\217\243.md"
+++ /dev/null
@@ -1,60 +0,0 @@
-Condition就是实现了管程里面的条件变量。
-Java 语言内置的管程里只有一个条件变量，而Lock&Condition实现的管程支持多个条件变量。
-支持多个条件变量，能让代码可读性更好，实现也更容易。例如，你看我这里实现一个阻塞队列，就需要两个条件变量：
-- 队列不空
-空队列自然没有元素能出队
-- 队列不满
-队列已满，当然也不可有元素再入队
-![](https://img-blog.csdnimg.cn/20210421150133653.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-Lock和Condition实现的管程，线程等待和通知需要调用await()、signal()、signalAll()，它们的语义和wait()、notify()、notifyAll()相同。
-- Lock&Condition实现的管程里只能使用await()、signal()、signalAll()
-- synchronized实现的管程才能使用wait()、notify()、notifyAll()
-
-如果在Lock&Condition实现的管程里调用wait()、notify()、notifyAll()，你距离离职就更近一步了。
-
-### Thread.sleep() V.S Condition.await()
-Object.wait()和Condition.await()的原理是基本一致的，不同在于Condition.await()底层是调用LockSupport.park()实现阻塞当前线程。它在阻塞当前线程前，其实还做了：
-1. 把当前线程添加到条件队列
-2. **完全**释放锁，即让state=0，然后才调用`LockSupport.park()`阻塞当前线程
-
-JDK的Lock和Condition不过就是管程的一种实现，一般如何使用呢？
-
-> 什么是同步与异步？
-- 同步
-调用方需要等待结果
-- 异步
-不需要等待结果
-
-> 代码里如何实现异步？
-
-- 调用方创建一个子线程，在子线程中执行方法调用，即异步调用
-- 方法实现时，创建一个新的线程执行主要逻辑，主线程直接return，即异步方法。
-
-异步场景挺多，比如TCP协议本身是异步的，日常的RPC调用，在TCP协议层面，发送完RPC请求后，线程不会等待RPC响应结果。
-
-> 是不是好奇了，明明日常使用的RPC调用都是同步的呀？这到底是同步还是异步？
-
-这肯定有人帮忙实现了异步转同步。比如RPC框架Dubbo，具体它是怎么做到的呢？
-
-对于下面一个简单的RPC调用，默认情况下sayHello()是个同步方法，即执行service.sayHello(“dubbo”)时，线程会停下来等结果。
-
-```java
-DemoService service = 初始化部分省略
-String message = service.sayHello("dubbo");
-System.out.println(message);
-```
-
-- 若此时dump调用线程的调用栈
-![](https://img-blog.csdnimg.cn/20210421154327773.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-发现调用线程阻塞了，线程状态是**TIMED_WAITING**。本来发送请求是异步的，但是调用线程却阻塞了，说明Dubbo帮我们做了异步转同步的事情。通过调用栈看到线程是阻塞在`DefaultFuture.get()`，所以Dubbo异步转同步的功能应该是通过DefaultFuture实现。
-
-DefaultFuture.get()之前发生了什么呢：
-![](https://img-blog.csdnimg.cn/20210421160657953.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-> 我们的期望：
-> - RPC返回结果前，阻塞调用线程，让调用线程等待
-> - RPC返回结果后，唤醒调用线程，让调用线程重新执行
-
-这就是经典的等待-通知机制，即管程的实现方案。
-- 看看Dubbo是怎么实现的。
-![](https://img-blog.csdnimg.cn/20210421170330667.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\232\204Lock\346\216\245\345\217\243.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\232\204Lock\346\216\245\345\217\243.md"
deleted file mode 100644
index 98ce77d78b..0000000000
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\232\204Lock\346\216\245\345\217\243.md"
+++ /dev/null
@@ -1,113 +0,0 @@
-> 并发编程的关键是什么，知道吗？
-
-![](https://img-blog.csdnimg.cn/2021042110181946.png)
-
-我淡淡一笑，还好平时就玩的高并发架构设计，不然真被你唬住了！
-
-- 互斥
-同一时刻，只允许一个线程访问共享资源
-- 同步
-线程之间通信、协作
-
-
-而且这俩问题，管程都能解决。JUC通过**Lock**和**Condition**接口实现管程
-- **Lock** 解决互斥
-- **Condition** 解决同步
-
-只见 P8 不慌不忙，又开始问道：
-
-> synchronized也是管程的实现，既然Java已经在SDK实现了管程，为什么还要提供另一种实现？难道JDK作者们喜欢“重复造轮子”？
-
-它们还是有很大区别的。在JDK 1.5，synchronized性能差于Lock，但1.6后，synchronized被优化，将性能提高，所以1.6后又推荐使用synchronized。但性能问题只要优化一下就行了，没必要“重复造轮子”。
-> 死锁问题讨论，推荐看我另一篇文章：[操作系统之进程管理](https://javaedge.blog.csdn.net/article/details/79864874)
-
-而关键在于，死锁问题的破坏“不可抢占”条件，该条件synchronized无法达到。因为synchronized申请资源时，若申请不到，线程直接阻塞，阻塞态的线程无所作为，也释放不了线程已经占有的资源。但我们希望：
-对于“不可抢占”条件，占用部分资源的线程进一步申请其他资源时，若申请不到，可以主动释放它占有的资源，这样“不可抢占”条件就被破坏掉了。
-
-若重新设计一把互斥锁去解决这个问题，咋搞呢？如下设计都能破坏“不可抢占”条件：   
-![](https://img-blog.csdnimg.cn/20210420231345268.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 能响应中断
-使用synchronized持有 **锁X** 后，若尝试获取 **锁Y** 失败，则线程进入阻塞，一旦死锁，就再无机会唤醒阻塞线程。但若阻塞态的线程能够响应中断信号，即当给阻塞线程发送**中断信号**时，能唤醒它，那它就有机会释放曾经持有的 **锁X**。
-- 支持超时
-若线程在一段时间内，都没有获取到锁，不是进入阻塞态，而是返回一个错误，则该线程也有机会释放曾经持有的锁
-- 非阻塞地获取锁
-如果尝试获取锁失败，并不进入阻塞状态，而是直接返回，那这个线程也有机会释放曾经持有的锁
-
-这些设计都弥补了synchronized的缺点。也就是造出Lock的主要原因，其实就是Lock的如下方法：
-- lockInterruptibly() 支持中断
-![](https://img-blog.csdnimg.cn/2021042110461053.png)
-
-- tryLock(long time, TimeUnit unit) 支持超时
-![](https://img-blog.csdnimg.cn/20210421104944523.png)
-- tryLock() 支持非阻塞获取锁
-![](https://img-blog.csdnimg.cn/20210421105016675.png)
-
-> 那你知道它是如何保证可见性的吗？
-
-Lock经典案例就是try/finally，必须在finally块里释放锁。
-Java多线程的可见性是通过Happens-Before规则保证的，而Happens-Before 并没有提到 Lock 锁。那Lock靠什么保证可见性呢？
-![](https://img-blog.csdnimg.cn/20210421111101974.png)
-![](https://img-blog.csdnimg.cn/20210421111039727.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-肯定的，它是利用了volatile的Happens-Before规则。因为 ReentrantLock 的内部类继承了 AQS，其内部维护了一个volatile 变量state
-![](https://img-blog.csdnimg.cn/2021042113041552.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-![](https://img-blog.csdnimg.cn/20210421130332670.png)
-
-- 获取锁时，会读写state
-- 解锁时，也会读写state
-![](https://img-blog.csdnimg.cn/20210421131811540.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-所以，执行value+=1前，程序先读写一次volatile state，在执行value+=1后，又读写一次volatile state。根据Happens-Before的如下规则判定：
-- 顺序性规则：**线程t1的value+=1** Happens-Before **线程t1的unlock()**
-- volatile变量规则：由于此时 state为1，会先读取state，所以**线程t1的unlock()** Happens-Before **线程t2的lock()**
-- 传递性规则：**线程t的value+=1** Happens-Before **线程t2的lock()**
-
-> 说说什么是可重入锁？
-
-可重入锁，就是线程可以重复获取同一把锁，示例如下：
-![](https://img-blog.csdnimg.cn/2021042113474062.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-> 听说过可重入方法吗？
-orz，这是什么鬼？P8 看我一时靓仔语塞，就懂了，说到：没关系，就随便问问，看看你的知识面。
-
-其实就是多线程可以同时调用该方法，每个线程都能得到正确结果；同时在一个线程内支持线程切换，无论被切换多少次，结果都是正确的。多线程可以同时执行，还支持线程切换。所以，可重入方法是线程安全的。
-
-
-> 那你来简单说说公平锁与非公平锁吧？
-
-比如ReentrantLock有两个构造器，一个是无参构造器，一个是传入fair参数的。
-fair参数代表锁的公平策略，true：需要构造一个公平锁，false：构造一个非公平锁（默认）。
-![](https://img-blog.csdnimg.cn/20210421140524934.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-> 知道锁的入口等待队列吗？
-
-锁都对应一个等待队列，如果一个线程没有获得锁，就会进入等待队列，当有线程释放锁的时候，就需要从等待队列中唤醒一个等待的线程。
-若是公平锁，唤醒策略就是谁等待的时间长，就唤醒谁，这很公平
-若是非公平锁，则不提供这个公平保证，所以可能等待时间短的线程被先唤醒。非公平锁的场景应该是线程释放锁之后，如果来了一个线程获取锁，他不必去排队直接获取到，不会入队。获取不到才入队。
-
-> 说说你对锁的一些最佳实践
-
-锁虽然能解决并发问题，但风险也高的。可能会导致死锁，也会影响性能。Doug Lea推荐的三个用锁的最佳实践：
-- 永远只在更新对象的成员变量时加锁
-- 永远只在访问可变的成员变量时加锁
-- 永远不在调用其他对象的方法时加锁
-因为调用其他对象的方法，实在是太不安全了，也许“其他”方法里面有线程sleep()的调用，也可能会有奇慢无比的I/O操作，这些都会严重影响性能。更可怕的是，“其他”类的方法可能也会加锁，然后双重加锁就可能导致死锁。
-
-还有一些常见的比如说，减少锁的持有时间、减小锁的粒度。本质相同，即只在该加锁的地方加锁。
-
-> 最后，来看个小代码，你看有啥问题吗？
-
-![](https://img-blog.csdnimg.cn/2021042114235193.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-不会出现死锁，因为不存在阻塞情况。但会出现活锁，线程较多的情况会导致部分线程始终无法获取到锁，导致活锁。即可能互相持有各自的锁，发现需要的对方的锁都被对方持有，就会释放当前持有的锁，导致大家都在不停持锁，释放锁，但事情还没做。当然还是会存在转账成功的情景，不过效率低下。
-
-成功转账后应该跳出循环，但加了break，也会有活锁问题，不加也是活锁，因为锁都会释放。所以加个随机重试时间避免活锁。
-
-活锁还是比死锁容易解决的，加个随机等待时间或客户端手动重试即可。
-优化后代码如下：
-![](https://img-blog.csdnimg.cn/20210421143652839.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-还有 notifyAll() 在面对公平锁和非公平锁的时候，效果一样。所有等待队列中的线程全部被唤醒，统统到入口等待队列中排队？这些被唤醒的线程不用根据等待时间排队再放入入口等待队列中了吧？
-都被唤醒。理论上是同时进入入口等待队列，等待时间是相同的。
-
-CPU层面的原子性是单条cpu指令。Java层面的互斥（管程）保证了原子性。
-这两个原子性意义不一样。cpu的原子性是不受线程调度影响，指令要不执行了，要么没执行。而Java层面的原子性是在锁的机制下保证只有一个线程执行，其余等待，此时cpu还是可以进行线程调度，使运行中的那个线程让出cpu时间，当然了该线程还是掌握锁。
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\272\277\347\250\213\346\261\240ThreadPoolExecutor.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\272\277\347\250\213\346\261\240ThreadPoolExecutor.md"
deleted file mode 100644
index 9bba52ac2a..0000000000
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\272\277\347\250\213\346\261\240ThreadPoolExecutor.md"
+++ /dev/null
@@ -1,1267 +0,0 @@
-# 1 为什么要用线程池
-## 1.1 线程the more, the better?
-1、线程在java中是一个对象，更是操作系统的资源，线程创建、销毁都需要时间。
-如果创建时间+销毁时间>执行任务时间就很不合算。
-2、Java对象占用堆内存，操作系统线程占用系统内存，根据JVM规范，一个线程默认最大栈
-大小1M，这个栈空间是需要从系统内存中分配的。线程过多，会消耗很多的内存。
-3、操作系统需要频繁切换线程上下文(大家都想被运行)，影响性能。
-
-线程使应用能够更加充分合理地协调利用CPU、内存、网络、I/O等系统资源.
-线程的创建需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间;
-在线程销毁时需要回收这些系统资源.
-频繁地创建和销毁线程会浪费大量的系统资源,增加并发编程风险.
-
-在服务器负载过大的时候,如何让新的线程等待或者友好地拒绝服务?
-
-这些都是线程自身无法解决的;
-所以需要通过线程池协调多个线程,并实现类似主次线程隔离、定时执行、周期执行等任务.
-
-# 2 线程池的作用
-● 利用线程池管理并复用线程、控制最大并发数等
-
-● 实现任务线程队列缓存策略和拒绝机制
-
-● 实现某些与时间相关的功能
-如定时执行、周期执行等
-
-● 隔离线程环境
-比如，交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要大;
-因此,通过配置独立的线程池,将较慢的交易服务与搜索服务隔离开,避免各服务线程相互影响.
-
-在开发中,合理地使用线程池能够带来3个好处
- - **降低资源消耗** 通过重复利用已创建的线程,降低创建和销毁线程造成的系统资源消耗
- - **提高响应速度** 当任务到达时,任务可以不需要等到线程创建就能立即执行
- - **提高线程的可管理性** 线程是稀缺资源,如果过多地创建,不仅会消耗系统资源，还会降低系统的稳定性，导致使用线程池可以进行统一分配、调优和监控。
-
-# 3 概念
-1、**线程池管理器**
-用于创建并管理线程池，包括创建线程池，销毁线程池，添加新任务;
-2、**工作线程**
-线程池中线程，在没有任务时处于等待状态，可以循环的执行任务;
-3、**任务接口**
-每个任务必须实现的接口，以供工作线程调度任务的执行，它主要规定了任务的入口，任务执行完后的收尾工作，任务的执行状态等;
-4、**任务队列**
-用于存放没有处理的任务。提供缓冲机制。.
-
-- 原理示意图
-![](https://img-blog.csdnimg.cn/20191009015833132.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-# 4 线程池API
-## 4.1 接口定义和实现类
-![](https://img-blog.csdnimg.cn/2019100901595683.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-### 继承关系图
-![线程池相关类图](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyOTA5XzQ2ODU5NjgtZWFhYWY4ZmQ4ODQ5Nzc1Ny5wbmc?x-oss-process=image/format,png)
-可以认为ScheduledThreadPoolExecutor是最丰富的实现类!
-
-## 4.2 方法定义
-### 4.2.1 ExecutorService
-![](https://img-blog.csdnimg.cn/20191009020347726.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-### 4.2.2 ScheduledExecutorService
-#### public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
-![](https://img-blog.csdnimg.cn/20191013013014872.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-####  public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
-![](https://img-blog.csdnimg.cn/20191013013113751.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-#### 以上两种都是创建并执行一个一次性任务, 过了延迟时间就会被执行
-####  public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
-
-![](https://img-blog.csdnimg.cn/20191013013412305.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-创建并执行一个周期性任务
-过了给定的初始延迟时间，会第一次被执行
-执行过程中发生了异常,那么任务就停止
-
-一次任务 执行时长超过了周期时间，下一次任务会等到该次任务执行结束后，立刻执行，这也是它和`scheduleWithFixedDelay`的重要区别
-
-#### public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
-创建并执行一个周期性任务
-过了初始延迟时间，第一次被执行，后续以给定的周期时间执行
-执行过程中发生了异常,那么任务就停止
-
-一次任务执行时长超过了周期时间，下一 次任务 会在该次任务执
-行结束的时间基础上，计算执行延时。
-对于超过周期的长时间处理任务的不同处理方式，这是它和`scheduleAtFixedRate`的重要区别。
-
-### 实例
-- 测试例子
-![](https://img-blog.csdnimg.cn/20191013153615841.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 测试实现
-![](https://img-blog.csdnimg.cn/20191013153730175.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 运行结果
-![](https://img-blog.csdnimg.cn/2019101315391641.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-可以看出超过core的线程都在等待,线程池线程数量为何达不到最大线程数呢?那这个参数还有什么意义, 让我们继续往下阅读吧!
-
-
-### 4.2.2  Executors工具类
-你可以自己实例化线程池，也可以用`Executors`创建线程池的工厂类，常用方法如下:
-
-`ExecutorService` 的抽象类`AbstractExecutorService `提供了`submit`、`invokeAll` 等方法的实现;
-但是核心方法`Executor.execute()`并没有在这里实现.
-因为所有的任务都在该方法执行,不同实现会带来不同的执行策略.
-
-通过`Executors`的静态工厂方法可以创建三个线程池的包装对象
-- ForkJoinPool、
-- ThreadPoolExecutor
-- ScheduledThreadPoolExecutor
-
-● Executors.newWorkStealingPool
-JDK8 引入,创建持有足够线程的线程池支持给定的并行度;
-并通过使用多个队列减少竞争;
-构造方法中把CPU数量设置为默认的并行度.
-返回`ForkJoinPool` ( JDK7引入)对象,它也是`AbstractExecutorService` 的子类
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyOTA2XzQ2ODU5NjgtM2I0YThlOGMxNDA4Zjg5Mi5wbmc?x-oss-process=image/format,png)
-
-
-● Executors.newCachedThreadPool
-创建的是一个无界的缓冲线程池。它的任务队列是一个同步队列。
-任务加入到池中
-- 如果池中有空闲线程，则用空闲线程执行
-- 如无, 则创建新线程执行。
-
-池中的线程空闲超过60秒，将被销毁。线程数随任务的多少变化。
-`适用于执行耗时较小的异步任务`。池的核心线程数=0 ,最大线程数= Integer.MAX_ _VALUE
-`maximumPoolSize` 最大可以至`Integer.MAX_VALUE`,是高度可伸缩的线程池.
-若达到该上限,相信没有服务器能够继续工作,直接OOM.
-`keepAliveTime` 默认为60秒;
-工作线程处于空闲状态,则回收工作线程;
-如果任务数增加,再次创建出新线程处理任务.
-
-● Executors.newScheduledThreadPool
-能定时执行任务的线程池。该池的核心线程数由参数指定，线程数最大至`Integer.MAX_ VALUE`,与上述相同,存在OOM风险.
-`ScheduledExecutorService`接口的实现类,支持**定时及周期性任务执行**;
-相比`Timer`,` ScheduledExecutorService` 更安全,功能更强大.
-与`newCachedThreadPool`的区别是**不回收工作线程**.
-
-● Executors.newSingleThreadExecutor
-创建一个单线程的线程池,相当于单线程串行执行所有任务,保证按任务的提交顺序依次执行.
-只有-个线程来执行无界任务队列的单-线程池。该线程池确保任务按加入的顺序一个一
-个依次执行。当唯一的线程因任务 异常中止时，将创建一个新的线程来继续执行 后续的任务。
-与newFixedThreadPool(1)的区别在于，单线程池的池大小在`newSingleThreadExecutor`方法中硬编码，不能再改变的。
-
-
-● Executors.newFixedThreadPool
-创建一个固定大小任务队列容量无界的线程池
-输入的参数即是固定线程数;
-既是核心线程数也是最大线程数;
-不存在空闲线程,所以`keepAliveTime`等于0.
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODE5XzQ2ODU5NjgtOGNkOTFmM2M2ZWFkYTlkZS5wbmc?x-oss-process=image/format,png)
-其中使用了 LinkedBlockingQueue, 但是没有设置上限!!!,堆积过多任务!!!
-
-下面介绍`LinkedBlockingQueue`的构造方法
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyOTEwXzQ2ODU5NjgtZmNlMjYxZGJlMzBkZWY3MS5wbmc?x-oss-process=image/format,png)
-使用这样的无界队列,如果瞬间请求非常大,会有OOM的风险;
-除`newWorkStealingPool` 外,其他四个创建方式都存在资源耗尽的风险.
-
-不推荐使用其中的任何创建线程池的方法,因为都没有任何限制,存在安全隐患.
-
- `Executors`中默认的线程工厂和拒绝策略过于简单,通常对用户不够友好.
-线程工厂需要做创建前的准备工作,对线程池创建的线程必须明确标识,就像药品的生产批号一样,为线程本身指定有意义的名称和相应的序列号.
-拒绝策略应该考虑到业务场景,返回相应的提示或者友好地跳转.
-以下为简单的ThreadFactory 示例
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzk3XzQ2ODU5NjgtZDIwMjUyODdhODJhZGQ5NS5wbmc?x-oss-process=image/format,png)
-
-上述示例包括线程工厂和任务执行体的定义;
-通过newThread方法快速、统一地创建线程任务,强调线程一定要有特定意义的名称,方便出错时回溯.
-
-- 单线程池：newSingleThreadExecutor()方法创建，五个参数分别是ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())。含义是池中保持一个线程，最多也只有一个线程，也就是说这个线程池是顺序执行任务的，多余的任务就在队列中排队。 
-- 固定线程池：newFixedThreadPool(nThreads)方法创建
-[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZEi0e3y-1570557031347)(https://uploadfiles.nowcoder.com/images/20190625/5088755_1561474494512_5D0DD7BCB7171E9002EAD3AEF42149E6 "图片标题")] 
-
-池中保持nThreads个线程，最多也只有nThreads个线程，多余的任务也在队列中排队。 
-[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SId8FBO1-1570557031347)(https://uploadfiles.nowcoder.com/images/20190625/5088755_1561476084467_4A47A0DB6E60853DEDFCFDF08A5CA249 "图片标题")] 
-
-[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6uzv6UAk-1570557031348)(https://uploadfiles.nowcoder.com/images/20190625/5088755_1561476102425_FB5C81ED3A220004B71069645F112867 "图片标题")] 
-线程数固定且线程不超时
-- 缓存线程池：newCachedThreadPool()创建，五个参数分别是ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue())。
-含义是池中不保持固定数量的线程，随需创建，最多可以创建Integer.MAX_VALUE个线程（说一句，这个数量已经大大超过目前任何操作系统允许的线程数了），空闲的线程最多保持60秒，多余的任务在SynchronousQueue（所有阻塞、并发队列在后续文章中具体介绍）中等待。 
-
-为什么单线程池和固定线程池使用的任务阻塞队列是LinkedBlockingQueue()，而缓存线程池使用的是SynchronousQueue()呢？ 
-因为单线程池和固定线程池中，线程数量是有限的，因此提交的任务需要在LinkedBlockingQueue队列中等待空余的线程；而缓存线程池中，线程数量几乎无限（上限为Integer.MAX_VALUE），因此提交的任务只需要在SynchronousQueue队列中同步移交给空余线程即可。
-
-- 单线程调度线程池：newSingleThreadScheduledExecutor()创建，五个参数分别是 (1, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue())。含义是池中保持1个线程，多余的任务在DelayedWorkQueue中等待。 
-- 固定调度线程池：newScheduledThreadPool(n)创建，五个参数分别是 (n, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue())。含义是池中保持n个线程，多余的任务在DelayedWorkQueue中等待。
-
-有一项技术可以缓解执行时间较长任务造成的影响，即限定任务等待资源的时间，而不要无限的等待
-
-先看第一个例子，测试单线程池、固定线程池和缓存线程池（注意增加和取消注释）：
-
-```
-public class ThreadPoolExam {
-    public static void main(String[] args) {
-        //first test for singleThreadPool
-        ExecutorService pool = Executors.newSingleThreadExecutor();
-        //second test for fixedThreadPool
-//        ExecutorService pool = Executors.newFixedThreadPool(2);
-        //third test for cachedThreadPool
-//        ExecutorService pool = Executors.newCachedThreadPool();
-        for (int i = 0; i < 5; i++) {
-            pool.execute(new TaskInPool(i));
-        }
-        pool.shutdown();
-    }
-}
-
-class TaskInPool implements Runnable {
-    private final int id;
-
-    TaskInPool(int id) {
-        this.id = id;
-    }
-
-    @Override
-    public void run() {
-        try {
-            for (int i = 0; i < 5; i++) {
-                System.out.println("TaskInPool-["+id+"] is running phase-"+i);
-                TimeUnit.SECONDS.sleep(1);
-            }
-            System.out.println("TaskInPool-["+id+"] is over");
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-    }
-}
-```
-
-如图为排查底层公共缓存调用出错时的截图
-![有意义的线程命名](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzQ5XzQ2ODU5NjgtODU1MDI1MzM5MDZjMzNmMi5wbmc?x-oss-process=image/format,png)
-绿色框采用自定义的线程工厂,明显比蓝色框默认的线程工厂创建的线程名称拥有更多的额外信息:如调用来源、线程的业务含义，有助于快速定位到死锁、StackOverflowError 等问题.
-
-# 5 创建线程池
-首先从`ThreadPoolExecutor`构造方法讲起,学习如何自定义`ThreadFactory`和`RejectedExecutionHandler`;
-并编写一个最简单的线程池示例.
-然后,通过分析`ThreadPoolExecutor`的`execute`和`addWorker`两个核心方法;
-学习如何把任务线程加入到线程池中运行.
-
-- ThreadPoolExecutor 的构造方法如下
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzMyXzQ2ODU5NjgtYTVmOTU1Yjc5MmJkNDUzZS5wbmc?x-oss-process=image/format,png)
-
-- 第1个参数: corePoolSize 表示常驻核心线程数
-如果等于0,则任务执行完之后,没有任何请求进入时销毁线程池的线程;
-如果大于0,即使本地任务执行完毕,核心线程也不会被销毁.
-这个值的设置非常关键;
-设置过大会浪费资源;
-设置过小会导致线程频繁地创建或销毁.
-
-- 第2个参数: maximumPoolSize 表示线程池能够容纳同时执行的最大线程数
-从第1处来看,必须>=1.
-如果待执行的线程数大于此值,需要借助第5个参数的帮助,缓存在队列中.
-如果`maximumPoolSize = corePoolSize`,即是固定大小线程池.
-
-- 第3个参数: keepAliveTime 表示线程池中的线程空闲时间
-当空闲时间达到`keepAliveTime`时,线程会被销毁,直到只剩下`corePoolSize`个线程;
-避免浪费内存和句柄资源.
-在默认情况下,当线程池的线程数大于`corePoolSize`时,`keepAliveTime`才起作用.
-但是当`ThreadPoolExecutor`的`allowCoreThreadTimeOut = true`时,核心线程超时后也会被回收.
-
-- 第4个参数: TimeUnit表示时间单位
-keepAliveTime的时间单位通常是TimeUnit.SECONDS.
-
-- 第5个参数: workQueue 表示缓存队列
-当请求的线程数大于`maximumPoolSize`时,线程进入`BlockingQueue`.
-后续示例代码中使用的LinkedBlockingQueue是单向链表,使用锁来控制入队和出队的原子性;
-两个锁分别控制元素的添加和获取,是一个生产消费模型队列.
-
-- 第6个参数: threadFactory 表示线程工厂
-它用来生产一组相同任务的线程;
-线程池的命名是通过给这个factory增加组名前缀来实现的.
-在虚拟机栈分析时,就可以知道线程任务是由哪个线程工厂产生的.
-
-- 第7个参数: handler 表示执行拒绝策略的对象
-当超过第5个参数`workQueue`的任务缓存区上限的时候，即可通过该策略处理请求，属于一种简单的限流保护。
-友好的拒绝策略可以是如下:
-1. 保存到数据库进行削峰填谷，空闲时再提取出来执行
-2. 转向某个提示页面
-3. 打印日志
-
-### 2.1.1 corePoolSize(核心线程数量)
-线程池中应该保持的主要线程的数量.即使线程处于空闲状态，除非设置了`allowCoreThreadTimeOut`这个参数,当提交一个任务到线程池时,若线程数量<corePoolSize,线程池会创建一个新线程放入works(一个HashSet)中执行任务,即使其他空闲的基本线程能够执行新任务也还是会创建新线程
-等到需要执行的任务数大于线程池基本大小时就不再创建,会尝试放入等待队列workQueue
-如果调用线程池的`prestartAllCoreThreads()`,线程池会提前创建并启动所有核心线程
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzUzXzQ2ODU5NjgtODQxMGU3OTM1YWE4YjI4ZS5wbmc?x-oss-process=image/format,png)
-### 2.1.2 maximumPoolSize（线程池最大线程数）
-线程池允许创建的最大线程数
-若队列满,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程放入works中执行任务,CashedThreadPool的关键,固定线程数的线程池无效
-若使用了无界任务队列,这个参数就没什么效果
-#### workQueue
-存储待执行任务的阻塞队列，这些任务必须是`Runnable`的对象（如果是Callable对象，会在submit内部转换为Runnable对象） 
- 
-- runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列.可以选择以下几个阻塞队列.
-  - LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue.静态工厂方法Executors.newFixedThreadPool()使用了这个队列
-  - SynchronousQueue:一个不存储元素的阻塞队列.每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列
- - ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字.使用开源框架guava提供ThreadFactoryBuilder可以快速给线程池里的线程设置有意义的名字,代码如下
-```java
-new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();
-```
-#### RejectedExecutionHandler（拒绝策略）
-当队列和线程池都满了，说明线程池饱和，这时又该如何处理提交的新任务呢？
-
-策略默认`AbortPolicy`：无法处理新任务时抛出异常。
-JDK 1.5的Java线程池框架提供了如下4种策略
-- AbortPolicy：丢弃任务，抛出 RejectedExecutionException
-- CallerRunsPolicy：只用调用者所在线程来运行任务,有反馈机制，使任务提交的速度变慢
-- DiscardOldestPolicy：若未发生shutdown，尝试丢弃队列里最近的一个任务，并执行当前任务, 丢弃任务缓存队列中最老的任务，并且尝试重新提交新的任务
-- DiscardPolicy:不处理，丢弃掉，拒绝执行，不抛异常 
-
-也可根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化存储不能处理的任务
-```java
-/**
- * Invokes the rejected execution handler for the given command.
- * Package-protected for use by ScheduledThreadPoolExecutor.
- */
-final void reject(Runnable command) {
-    // 执行拒绝策略
-    handler.rejectedExecution(command, this);
-}
-```
-`handler` 构造线程池时候就传的参数，`RejectedExecutionHandler `的实例
-`RejectedExecutionHandler` 在 `ThreadPoolExecutor` 中有四个实现类可供我们直接使用，当然，也可实现自己的策略，一般也没必要。
-```java
-    //只要线程池没有被关闭，由提交任务的线程自己来执行这个任务
-    public static class CallerRunsPolicy implements RejectedExecutionHandler {
-
-        public CallerRunsPolicy() { }
-
-        /**
-         * Executes task r in the caller's thread, unless the executor
-         * has been shut down, in which case the task is discarded.
-         *
-         * @param r the runnable task requested to be executed
-         * @param e the executor attempting to execute this task
-         */
-        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
-            if (!e.isShutdown()) {
-                r.run();
-            }
-        }
-    }
-
-    // 不管怎样，直接抛出 RejectedExecutionException 异常
-    // 默认的策略，如果我们构造线程池的时候不传相应的 handler ，则指定使用这个
-    public static class AbortPolicy implements RejectedExecutionHandler {
-       
-        public AbortPolicy() { }
-
-        /**
-         * Always throws RejectedExecutionException.
-         *
-         * @param r the runnable task requested to be executed
-         * @param e the executor attempting to execute this task
-         * @throws RejectedExecutionException always
-         */
-        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
-            throw new RejectedExecutionException("Task " + r.toString() +
-                                                 " rejected from " +
-                                                 e.toString());
-        }
-    }
-
-    // 不做任何处理，直接忽略掉这个任务
-    public static class DiscardPolicy implements RejectedExecutionHandler {
-        /**
-         * Creates a {@code DiscardPolicy}.
-         */
-        public DiscardPolicy() { }
-
-        /**
-         * Does nothing, which has the effect of discarding task r.
-         *
-         * @param r the runnable task requested to be executed
-         * @param e the executor attempting to execute this task
-         */
-        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
-        }
-    }
-
-    // 若线程池未被关闭
-    // 把队列队头的任务(也就是等待了最长时间的)直接扔掉，然后提交这个任务到等待队列中
-    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
-      
-        public DiscardOldestPolicy() { }
-
-        /**
-         * Obtains and ignores the next task that the executor
-         * would otherwise execute, if one is immediately available,
-         * and then retries execution of task r, unless the executor
-         * is shut down, in which case task r is instead discarded.
-         *
-         * @param r the runnable task requested to be executed
-         * @param e the executor attempting to execute this task
-         */
-        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
-            if (!e.isShutdown()) {
-                e.getQueue().poll();
-                e.execute(r);
-            }
-        }
-    }
-```
-- keepAliveTime（线程活动保持时间）
-线程没有任务执行时最多保持多久时间终止
-线程池的工作线程空闲后，保持存活的时间。
-所以，如果任务很多，并且每个任务执行的时间比较短，可以调大时间，提高线程的利用率
-
- - TimeUnit（线程活动保持时间的单位）:指示第三个参数的时间单位；可选的单位有天（DAYS）、小时（HOURS）、分钟（MINUTES）、毫秒（MILLISECONDS）、微秒（MICROSECONDS，千分之一毫秒）和纳秒（NANOSECONDS，千分之一微秒）
-
-
-从代码第2处来看，**队列、线程工厂、拒绝处理服务**都必须有实例对象;
-但在实际编程中,很少有程序员对这三者进行实例化,而通过`Executors`这个线程池静态工厂提供默认实现
-
-# 拒绝策略
-简单实现`RejectedExecutionHandler`;
-实现了接口的`rejectedExecution`方法,打印出当前线程池状态
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODA5XzQ2ODU5NjgtYWM3ZmFjMmM2YjMzMmRhZi5wbmc?x-oss-process=image/format,png)
-
-
-在`ThreadPoolExecutor`中提供了四个公开的内部静态类
-● AbortPolicy  - **默认** 
-丢弃任务并抛出`RejectedExecutionException`
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyOTg2XzQ2ODU5NjgtZGNmODEyNmMxODdkNGJmYS5wbmc?x-oss-process=image/format,png)
-
-● DiscardPolicy - **不推荐**
-丢弃任务,但不拋异常.
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyOTU2XzQ2ODU5NjgtZDUxOTUwMjBlMzQ3ZDhlYi5wbmc?x-oss-process=image/format,png)
-
-● DiscardOldestPolicy
-抛弃队列中等待最久的任务,然后把当前任务加入队列中.
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzODA2XzQ2ODU5NjgtZDNhMzZiOTAyMTExMzhmNC5wbmc?x-oss-process=image/format,png)
-
-● CallerRunsPolicy
-调用任务的run()方法绕过线程池直接执行.
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyOTA4XzQ2ODU5NjgtYzA2YTA0ZDZjZTMwZGU5Ny5wbmc?x-oss-process=image/format,png)
-
-
-根据之前实现的线程工厂和拒绝策略,线程池的相关代码实现如下
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODU0MTYxXzQ2ODU5NjgtOGIxYzU3NjIxM2JlYTNjYS5wbmc?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzNjM2XzQ2ODU5NjgtMTg0ZjQ1NTI0MGYxYjc5Yy5wbmc?x-oss-process=image/format,png)
-当任务被拒绝的时候，拒绝策略会打印出当前线程池的大小已经达到了`maximumPoolSize=2`,且队列已满,完成的任务数提示已经有1个(最后一行).
-
-# 源码讲解
-在`ThreadPoolExecutor`的属性定义中频繁地用位运算来表示线程池状态;
-位运算是改变当前值的一种高效手段.
-
-下面从属性定义开始
-
->Integer 有32位;
-最右边29位表工作线程数;
-最左边3位表示线程池状态,可表示从0至7的8个不同数值
-线程池的状态用高3位表示,其中包括了符号位.
-五种状态的十进制值按从小到大依次排序为
-RUNNING < SHUTDOWN < STOP < TIDYING <TERMINATED
-这样设计的好处是可以通过比较值的大小来确定线程池的状态.
-例如程序中经常会出现isRunning的判断:
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODQyXzQ2ODU5NjgtOGRjN2QwZWFjYjQ1OGYyZS5wbmc?x-oss-process=image/format,png)
-
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzE3XzQ2ODU5NjgtODEzMzc2YTk4MjZlMjRjNi5wbmc?x-oss-process=image/format,png)
-
-- 000-1111111111111111111111111;
-类似于子网掩码,用于与运算;
-得到左边3位,还是右边29位
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzMzA5XzQ2ODU5NjgtNmM3Zjg5NDExNjk3MmEyZi5wbmc?x-oss-process=image/format,png)
-
-用左边3位,实现5种线程池状态;
-在左3位之后加入中画线有助于理解;
-
-- 111 - 0000000000000000000000000000(十进制: -536, 870, 912);
-该状态表 线程池能接受新任务
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzEzXzQ2ODU5NjgtZTgzOGM1NmI5OTU0NGU5My5wbmc?x-oss-process=image/format,png)
-
-
-- 000 - 0000000000000000000000000(十进制: 0);
-此状态不再接受新任务,但可继续执行队列中的任务
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzMDI1XzQ2ODU5NjgtZDM0NDc2NzhiNzdjODUxZi5wbmc?x-oss-process=image/format,png)
-
-- 001 - 00000000000000000000000000(十进制: 536,870， 912);
-此状态全面拒绝,并中断正在处理的任务
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODcyXzQ2ODU5NjgtNmMzOTlhMjhkZTBmMzc1Yi5wbmc?x-oss-process=image/format,png)
-
-- 010 - 00000000000000000000000000.(十进制值: 1, 073, 741, 824);
-该状态表 所有任务已经被终止
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODE0XzQ2ODU5NjgtZTEzNDFjY2FlM2Q3MzQ3My5wbmc?x-oss-process=image/format,png)
-
-- 101 - 000000000000000000000000000(十进制值: 1, 610,612, 736)
-该状态表 已清理完现场
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzEzXzQ2ODU5NjgtMmI4M2VmMmEwZjI4YjU3MC5wbmc?x-oss-process=image/format,png)
-
-
-与运算,比如 001 - 000000000000000000000100011 表 67个工作线程;
-掩码取反: 111 - 00000000000000000000000.,即得到左边3位001;
-表示线程池当前处于**STOP**状态
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyOTU2XzQ2ODU5NjgtMGQ5YjA4ODRhYmU4ZTFjMS5wbmc?x-oss-process=image/format,png)
-
-同理掩码 000 - 11111111111111111111,得到右边29位,即工作线程数
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzY4XzQ2ODU5NjgtOTAwNjNiMjEzNGM4OTc3Mi5wbmc?x-oss-process=image/format,png)
-
-把左3位与右29位或运算,合并成一个值
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyOTU5XzQ2ODU5NjgtODU4ZGY0ZWJjYjkxYTg0ZS5wbmc?x-oss-process=image/format,png)
-
-
-我们都知道`Executor`接口有且只有一个方法`execute()`;
-通过参数传入待执行线程的对象.
-下面分析`ThreadPoolExecutor`关于`execute()`方法的实现
-
-线程池执行任务的方法如下
-```java
-   /**
-     * Executes the given task sometime in the future.  The task
-     * may execute in a new thread or in an existing pooled thread.
-     *
-     * If the task cannot be submitted for execution, either because this
-     * executor has been shutdown or because its capacity has been reached,
-     * the task is handled by the current {@code RejectedExecutionHandler}.
-     *
-     * @param command the task to execute
-     * @throws RejectedExecutionException at discretion of
-     *         {@code RejectedExecutionHandler}, if the task
-     *         cannot be accepted for execution
-     * @throws NullPointerException if {@code command} is null
-     */
-    public void execute(Runnable command) {
-        if (command == null)
-            throw new NullPointerException();
-        /*
-         * Proceed in 3 steps:
-         *
-         * 1. If fewer than corePoolSize threads are running, try to
-         * start a new thread with the given command as its first
-         * task.  The call to addWorker atomically checks runState and
-         * workerCount, and so prevents false alarms that would add
-         * threads when it shouldn't, by returning false.
-         *
-         * 2. If a task can be successfully queued, then we still need
-         * to double-check whether we should have added a thread
-         * (because existing ones died since last checking) or that
-         * the pool shut down since entry into this method. So we
-         * recheck state and if necessary roll back the enqueuing if
-         * stopped, or start a new thread if there are none.
-         *
-         * 3. If we cannot queue task, then we try to add a new
-         * thread.  If it fails, we know we are shut down or saturated
-         * and so reject the task.
-         */
-        // 返回包含线程数及线程池状态的Integer 类型数值
-        int c = ctl.get();
-
-        // 若工作线程数 < 核心线程数,则创建线程并执行当前任务
-        if (workerCountOf(c) < corePoolSize) {
-            if (addWorker(command, true))
-```
-`execute`方法在不同的阶段有三次`addWorker`的尝试动作。
-```
-                return;
-            // 若创建失败,为防止外部已经在线程池中加入新任务,在此重新获取一下
-            c = ctl.get();
-        }
-
-        // 若 工作线程数 >=核心线程数 或线程创建失败,则将当前任务放到工作队列中
-        // 只有线程池处于 RUNNING 态,才执行后半句 : 置入队列
-        if (isRunning(c) && workQueue.offer(command)) {
-            int recheck = ctl.get();
-            
-            // 只有线程池处于 RUNNING 态,才执行后半句 : 置入队列
-            if (! isRunning(recheck) && remove(command))
-                reject(command);
-            // 若之前的线程已被消费完,新建一个线程
-            else if (workerCountOf(recheck) == 0)
-                addWorker(null, false);
-        // 核心线程和队列都已满,尝试创建一个新线程
-        }
-        else if (!addWorker(command, false))
-            // 抛出RejectedExecutionException异常
-            // 若 addWorker 返回是 false,即创建失败,则唤醒拒绝策略.
-            reject(command);
-    }
-```
-发生拒绝的理由有两个
-( 1 )线程池状态为非RUNNING状态
-(2)等待队列已满。
-
-下面继续分析`addWorker`
-
-## addWorker 源码解析
-
-根据当前线程池状态,检查是否可以添加新的任务线程,若可以则创建并启动任务;
-若一切正常则返回true;
-返回false的可能性如下
-1. 线程池没有处于`RUNNING`态
-2. 线程工厂创建新的任务线程失败
-### 参数
-- firstTask
-外部启动线程池时需要构造的第一个线程,它是线程的母体
-- core
-新增工作线程时的判断指标
-     - true
-需要判断当前`RUNNING`态的线程是否少于`corePoolsize`
-    - false
-需要判断当前`RUNNING`态的线程是否少于`maximumPoolsize`
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzMjIwXzQ2ODU5NjgtMjg2MDRmYjVkYTE5MjJlNC5wbmc?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzg5XzQ2ODU5NjgtOTk1ZmFlOTQyOTQwMjFjNy5wbmc?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODMwXzQ2ODU5NjgtM2Y3NzViOWQ1MThmMzc4My5wbmc?x-oss-process=image/format,png)
-
-这段代码晦涩难懂,部分地方甚至违反代码规约,但其中蕴含丰富的编码知识点
-
-- 第1处,配合循环语句出现的label,类似于goto 作用
-label 定义时,必须把标签和冒号的组合语句紧紧相邻定义在循环体之前,否则会编译出错.
-目的是 在实现多重循环时能够快速退出到任何一层;
-出发点似乎非常贴心,但在大型软件项目中,滥用标签行跳转的后果将是灾难性的.
-示例代码中在`retry`下方有两个无限循环;
-在`workerCount`加1成功后,直接退出两层循环.
-
-- 第2处,这样的表达式不利于阅读,应如是
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzg1XzQ2ODU5NjgtMDg2ZTlkNWY5ZGEyYWZkNC5wbmc?x-oss-process=image/format,png)
-
-- 第3处,与第1处的标签呼应,`AtomicInteger`对象的加1操作是原子性的;
-`break retry`表 直接跳出与`retry` 相邻的这个循环体
- 
-- 第4处,此`continue`跳转至标签处,继续执行循环.
-如果条件为false,则说明线程池还处于运行状态,即继续在`for(;)`循环内执行.
-
-- 第5处,`compareAndIncrementWorkerCount `方法执行失败的概率非常低.
-即使失败,再次执行时成功的概率也是极高的,类似于自旋原理.
-这里是先加1,创建失败再减1,这是轻量处理并发创建线程的方式;
-如果先创建线程,成功再加1,当发现超出限制后再销毁线程,那么这样的处理方式明显比前者代价要大.
-
-- 第6处,`Worker `对象是工作线程的核心类实现，部分源码如下
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzMjMyXzQ2ODU5NjgtYzkzNTI3ODJjNTZjM2Q2Ny5wbmc?x-oss-process=image/format,png)
-它实现了`Runnable`接口,并把本对象作为参数输入给`run()`中的`runWorker (this)`;
-所以内部属性线程`thread`在`start`的时候,即会调用`runWorker`.
-
-# 总结
-线程池的相关源码比较精炼，还包括线程池的销毁、任务提取和消费等，与线程状态图一样，线程池也有自己独立的状态转化流程，本节不再展开。
-总结一下，使用线程池要注意如下几点:
-(1)合理设置各类参数，应根据实际业务场景来设置合理的工作线程数。
-(2)线程资源必须通过线程池提供，不允许在应用中自行显式创建线程。
-(3)创建线程或线程池时请指定有意义的线程名称，方便出错时回溯。
-
-线程池不允许使用Executors，而是通过ThreadPoolExecutor的方式创建，这样的处理方式能更加明确线程池的运行规则，规避资源耗尽的风险。
-
-
-
-
-
-进一步查看源码发现,这些方法最终都调用了ThreadPoolExecutor和ScheduledThreadPoolExecutor的构造函数
-而ScheduledThreadPoolExecutor继承自ThreadPoolExecutor
-
-## 0.2 ThreadPoolExecutor 自定义线程池
-[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5A6eRvc8-1570557031390)(https://uploadfiles.nowcoder.com/images/20190625/5088755_1561476436402_10FB15C77258A991B0028080A64FB42D "图片标题")] 
-它们都是某种线程池,可以控制线程创建,释放,并通过某种策略尝试复用线程去执行任务的一个管理框架
-
-,因此最终所有线程池的构造函数都调用了Java5后推出的ThreadPoolExecutor的如下构造函数                        
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODEwXzQ2ODU5NjgtYmY0MTAwOTU5Nzk4NjA1OC5wbmc?x-oss-process=image/format,png)
-
-## Java默认提供的线程池
-Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzMDc1XzQ2ODU5NjgtNGYxOGI1ZTk2ZWIxZDkzMC5wbmc?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyOTg5XzQ2ODU5NjgtYjdlYzU5YTgwMDQ0MmIyNi5wbmc?x-oss-process=image/format,png)
-
-我们只需要将待执行的方法放入 run 方法中，将 Runnable 接口的实现类交给线程池的
-execute 方法，作为他的一个参数，比如： 
-```java
-Executor e=Executors.newSingleThreadExecutor();           
-e.execute(new Runnable(){ //匿名内部类     public  void run(){  
-//需要执行的任务 
-} 
-}); 
-
-```
-# 线程池原理 - 任务execute过程
- - 流程图
-![](https://img-blog.csdnimg.cn/20191014020916959.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 示意图
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzMwXzQ2ODU5NjgtYTA3YjhiMzIzMzMxYzE1ZS5wbmc?x-oss-process=image/format,png)
-
-ThreadPoolExecutor执行execute()分4种情况
- - 若当前运行的线程少于`corePoolSize`,则创建新线程来执行任务(该步需要获取全局锁)
- - 若运行的线程多于或等于`corePoolSize`,且工作队列没满，则将新提交的任务存储在工作队列里。即, 将任务加入`BlockingQueue`
- - 若无法将任务加入`BlockingQueue`,且没达到线程池最大数量, 则创建新的线程来处理任务(该步需要获取全局锁)
- - 若创建新线程将使当前运行的线程超出`maximumPoolSize`,任务将被拒绝,并调用`RejectedExecutionHandler.rejectedExecution()`
-
-采取上述思路,是为了在执行`execute()`时,尽可能避免获取全局锁
-在ThreadPoolExecutor完成预热之后（当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁
-
-## 实例
-![](https://img-blog.csdnimg.cn/20191013231005279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 结果
-![](https://img-blog.csdnimg.cn/20191014015653362.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-# **源码分析**
-```java
-   /**
-	 * 检查是否可以根据当前池状态和给定的边界（核心或最大)
-     * 添加新工作线程。如果是这样,工作线程数量会相应调整，如果可能的话,一个新的工作线程创建并启动
-     * 将firstTask作为其运行的第一项任务。
-     * 如果池已停止此方法返回false
-     * 如果线程工厂在被访问时未能创建线程,也返回false
-     * 如果线程创建失败，或者是由于线程工厂返回null，或者由于异常（通常是在调用Thread.start（）后的OOM）），我们干净地回滚。
-	 *
-	 * @param core if true use corePoolSize as bound, else
-	 * maximumPoolSize. (A boolean indicator is used here rather than a
-	 * value to ensure reads of fresh values after checking other pool
-	 * state).
-	 * @return true if successful
-	 */
-    private boolean addWorker(Runnable firstTask, boolean core) {
-        retry:
-        for (;;) {
-            int c = ctl.get();
-            int rs = runStateOf(c);
-      
-            
-    /**
-     * Check if queue empty only if necessary.
-     * 
-     * 如果线程池已关闭，并满足以下条件之一，那么不创建新的 worker：
-     *      1. 线程池状态大于 SHUTDOWN，也就是 STOP, TIDYING, 或 TERMINATED
-     *      2. firstTask != null
-     *      3. workQueue.isEmpty()
-     * 简单分析下：
-     *      状态控制的问题，当线程池处于 SHUTDOWN ，不允许提交任务，但是已有任务继续执行
-     *      当状态大于 SHUTDOWN ，不允许提交任务，且中断正在执行任务
-     *      多说一句：若线程池处于 SHUTDOWN，但 firstTask 为 null，且 workQueue 非空，是允许创建 worker 的
-     *  
-     */
-            if (rs >= SHUTDOWN &&
-                ! (rs == SHUTDOWN &&
-                   firstTask == null &&
-                   ! workQueue.isEmpty()))
-                return false;
-
-            for (;;) {
-                int wc = workerCountOf(c);
-                if (wc >= CAPACITY ||
-                    wc >= (core ? corePoolSize : maximumPoolSize))
-                    return false;
-                // 如果成功，那么就是所有创建线程前的条件校验都满足了，准备创建线程执行任务
-                // 这里失败的话，说明有其他线程也在尝试往线程池中创建线程
-                if (compareAndIncrementWorkerCount(c))
-                    break retry;
-                // 由于有并发，重新再读取一下 ctl
-                c = ctl.get();  // Re-read ctl
-                // 正常如果是 CAS 失败的话，进到下一个里层的for循环就可以了
-                // 可如果是因为其他线程的操作，导致线程池的状态发生了变更，如有其他线程关闭了这个线程池
-                // 那么需要回到外层的for循环
-                if (runStateOf(c) != rs)
-                    continue retry;
-                // else CAS failed due to workerCount change; retry inner loop
-            }
-        }
-
-     /* *
-        * 到这里，我们认为在当前这个时刻，可以开始创建线程来执行任务
-        */
-         
-        // worker 是否已经启动
-        boolean workerStarted = false;
-        // 是否已将这个 worker 添加到 workers 这个 HashSet 中
-        boolean workerAdded = false;
-        Worker w = null;
-        try {
-           // 把 firstTask 传给 worker 的构造方法
-            w = new Worker(firstTask);
-            // 取 worker 中的线程对象，Worker的构造方法会调用 ThreadFactory 来创建一个新的线程
-            final Thread t = w.thread;
-            if (t != null) {
-               //先加锁
-                final ReentrantLock mainLock = this.mainLock;
-                // 这个是整个类的全局锁，持有这个锁才能让下面的操作“顺理成章”，
-                // 因为关闭一个线程池需要这个锁，至少我持有锁的期间，线程池不会被关闭
-                mainLock.lock();
-                try {
-                    // Recheck while holding lock.
-                    // Back out on ThreadFactory failure or if
-                    // shut down before lock acquired.
-                    int rs = runStateOf(ctl.get());
-
-                    // 小于 SHUTTDOWN 即 RUNNING
-                    // 如果等于 SHUTDOWN，不接受新的任务，但是会继续执行等待队列中的任务
-                    if (rs < SHUTDOWN ||
-                        (rs == SHUTDOWN && firstTask == null)) {
-                        // worker 里面的 thread 不能是已启动的
-                        if (t.isAlive()) // precheck that t is startable
-                            throw new IllegalThreadStateException();
-                        // 加到 workers 这个 HashSet 中
-                        workers.add(w);
-                        int s = workers.size();
-                        if (s > largestPoolSize)
-                            largestPoolSize = s;
-                        workerAdded = true;
-                    }
-                } finally {
-                    mainLock.unlock();
-                }
-               // 若添加成功
-                if (workerAdded) {
-                    // 启动线程
-                    t.start();
-                    workerStarted = true;
-                }
-            }
-        } finally {
-            // 若线程没有启动，做一些清理工作，若前面 workCount 加了 1，将其减掉
-            if (! workerStarted)
-                addWorkerFailed(w);
-        }
-        // 返回线程是否启动成功
-        return workerStarted;
-    }
-```
-看下  `addWorkFailed` 
-![workers 中删除掉相应的 worker,workCount 减 1
-private void addWor](https://upload-images.jianshu.io/upload_images/4685968-77abdc7bff21cca6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
-
-![记录 workers 中的个数的最大值,因为 workers 是不断增加减少的，通过这个值可以知道线程池的大小曾经达到的最大值](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzA4XzQ2ODU5NjgtMDc4NDcyYjY4MmZjYzljZC5wbmc?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUzNjMzXzQ2ODU5NjgtMzNmNTE0NTc3ZTk3ZGMzNS5wbmc?x-oss-process=image/format,png)
-
-
-
-
-`worker` 中的线程 `start` 后，其 `run` 方法会调用 `runWorker `
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyODgwXzQ2ODU5NjgtYTAwOWJjMDJhMjI0ZGNlMi5wbmc?x-oss-process=image/format,png)
-继续往下看 `runWorker`
-```java
-//  worker 线程启动后调用,while 循环(即自旋!)不断从等待队列获取任务并执行
-//  worker 初始化时，可指定 firstTask，那么第一个任务也就可以不需要从队列中获取
-final void runWorker(Worker w) {
-    Thread wt = Thread.currentThread();
-    // 该线程的第一个任务(若有)
-    Runnable task = w.firstTask;
-    w.firstTask = null;
-    // 允许中断
-    w.unlock(); 
- 
-    boolean completedAbruptly = true;
-    try {
-        // 循环调用 getTask 获取任务
-        while (task != null || (task = getTask()) != null) {
-            w.lock();          
-            // 若线程池状态大于等于 STOP，那么意味着该线程也要中断
-              /**
-               * 若线程池STOP，请确保线程 已被中断
-               * 如果没有，请确保线程未被中断
-               * 这需要在第二种情况下进行重新检查，以便在关中断时处理shutdownNow竞争
-               */
-            if ((runStateAtLeast(ctl.get(), STOP) ||
-                 (Thread.interrupted() &&
-                  runStateAtLeast(ctl.get(), STOP))) &&
-                !wt.isInterrupted())
-                wt.interrupt();
-            try {
-                // 这是一个钩子方法，留给需要的子类实现
-                beforeExecute(wt, task);
-                Throwable thrown = null;
-                try {
-                    // 到这里终于可以执行任务了
-                    task.run();
-                } catch (RuntimeException x) {
-                    thrown = x; throw x;
-                } catch (Error x) {
-                    thrown = x; throw x;
-                } catch (Throwable x) {
-                    // 这里不允许抛出 Throwable，所以转换为 Error
-                    thrown = x; throw new Error(x);
-                } finally {
-                    // 也是一个钩子方法，将 task 和异常作为参数，留给需要的子类实现
-                    afterExecute(task, thrown);
-                }
-            } finally {
-                // 置空 task，准备 getTask 下一个任务
-                task = null;
-                // 累加完成的任务数
-                w.completedTasks++;
-                // 释放掉 worker 的独占锁
-                w.unlock();
-            }
-        }
-        completedAbruptly = false;
-    } finally {
-        // 到这里，需要执行线程关闭
-        // 1. 说明 getTask 返回 null，也就是说，这个 worker 的使命结束了，执行关闭
-        // 2. 任务执行过程中发生了异常
-        //    第一种情况，已经在代码处理了将 workCount 减 1，这个在 getTask 方法分析中说
-        //    第二种情况，workCount 没有进行处理，所以需要在 processWorkerExit 中处理
-        processWorkerExit(w, completedAbruptly);
-    }
-}
-```
-看看 `getTask() `
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWRmaWxlcy5ub3djb2Rlci5jb20vZmlsZXMvMjAxOTA2MjUvNTA4ODc1NV8xNTYxNDczODUyNzgwXzQ2ODU5NjgtNWU5NDc3MzE5M2Q5Y2Y0OS5wbmc?x-oss-process=image/format,png)
-```java
-// 此方法有三种可能
-// 1. 阻塞直到获取到任务返回。默认 corePoolSize 之内的线程是不会被回收的，它们会一直等待任务
-// 2. 超时退出。keepAliveTime 起作用的时候，也就是如果这么多时间内都没有任务，那么应该执行关闭
-// 3. 如果发生了以下条件，须返回 null
-//     池中有大于 maximumPoolSize 个 workers 存在(通过调用 setMaximumPoolSize 进行设置)
-//     线程池处于 SHUTDOWN，而且 workQueue 是空的，前面说了，这种不再接受新的任务
-//     线程池处于 STOP，不仅不接受新的线程，连 workQueue 中的线程也不再执行
-private Runnable getTask() {
-    boolean timedOut = false; // Did the last poll() time out?
-
-   for (;;) {
-   // 允许核心线程数内的线程回收，或当前线程数超过了核心线程数，那么有可能发生超时关闭
- 
-            // 这里 break，是为了不往下执行后一个 if (compareAndDecrementWorkerCount(c))
-            // 两个 if 一起看：如果当前线程数 wc > maximumPoolSize，或者超时，都返回 null
-            // 那这里的问题来了，wc > maximumPoolSize 的情况，为什么要返回 null？
-            // 换句话说，返回 null 意味着关闭线程。
-            // 那是因为有可能开发者调用了 setMaximumPoolSize 将线程池的 maximumPoolSize 调小了
-        
-            // 如果此 worker 发生了中断，采取的方案是重试
-            // 解释下为什么会发生中断，这个读者要去看 setMaximumPoolSize 方法，
-            // 如果开发者将 maximumPoolSize 调小了，导致其小于当前的 workers 数量，
-            // 那么意味着超出的部分线程要被关闭。重新进入 for 循环，自然会有部分线程会返回 null
-            int c = ctl.get();
-            int rs = runStateOf(c);
-
-            // Check if queue empty only if necessary.
-            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
-                // CAS 操作，减少工作线程数
-                decrementWorkerCount();
-                return null;
-            }
-
-            int wc = workerCountOf(c);
-
-            // Are workers subject to culling?
-            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
-
-            if ((wc > maximumPoolSize || (timed && timedOut))
-                && (wc > 1 || workQueue.isEmpty())) {
-                if (compareAndDecrementWorkerCount(c))
-                    return null;
-                continue;
-            }
-
-            try {
-                Runnable r = timed ?
-                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
-                    workQueue.take();
-                if (r != null)
-                    return r;
-                timedOut = true;
-            } catch (InterruptedException retry) {
-            // 如果此 worker 发生了中断，采取的方案是重试
-            // 解释下为什么会发生中断，这个读者要去看 setMaximumPoolSize 方法，
-            // 如果开发者将 maximumPoolSize 调小了，导致其小于当前的 workers 数量，
-            // 那么意味着超出的部分线程要被关闭。重新进入 for 循环，自然会有部分线程会返回 null
-                timedOut = false;
-            }
-        }
-}
-```
-到这里，基本上也说完了整个流程，回到 execute(Runnable command) 方法，看看各个分支，我把代码贴过来一下：
-```java
-/**
-     * Executes the given task sometime in the future.  The task
-     * may execute in a new thread or in an existing pooled thread.
-     *
-     * If the task cannot be submitted for execution, either because this
-     * executor has been shutdown or because its capacity has been reached,
-     * the task is handled by the current {@code RejectedExecutionHandler}.
-     *
-     * @param command the task to execute
-     * @throws RejectedExecutionException at discretion of
-     *         {@code RejectedExecutionHandler}, if the task
-     *         cannot be accepted for execution
-     * @throws NullPointerException if {@code command} is null
-     */
-    public void execute(Runnable command) {
-        if (command == null)
-            throw new NullPointerException();
-        /*
-         * Proceed in 3 steps:
-         *
-         * 1. If fewer than corePoolSize threads are running, try to
-         * start a new thread with the given command as its first
-         * task.  The call to addWorker atomically checks runState and
-         * workerCount, and so prevents false alarms that would add
-         * threads when it shouldn't, by returning false.
-         *
-         * 2. If a task can be successfully queued, then we still need
-         * to double-check whether we should have added a thread
-         * (because existing ones died since last checking) or that
-         * the pool shut down since entry into this method. So we
-         * recheck state and if necessary roll back the enqueuing if
-         * stopped, or start a new thread if there are none.
-         *
-         * 3. If we cannot queue task, then we try to add a new
-         * thread.  If it fails, we know we are shut down or saturated
-         * and so reject the task.
-         */
-        //表示 “线程池状态” 和 “线程数” 的整数
-        int c = ctl.get();
-        // 如果当前线程数少于核心线程数，直接添加一个 worker 执行任务，
-        // 创建一个新的线程，并把当前任务 command 作为这个线程的第一个任务(firstTask)
-        if (workerCountOf(c) < corePoolSize) {
-        // 添加任务成功，即结束
-        // 执行的结果，会包装到 FutureTask 
-        // 返回 false 代表线程池不允许提交任务
-            if (addWorker(command, true))
-                return;
-           
-            c = ctl.get();
-        }
-
-        // 到这说明，要么当前线程数大于等于核心线程数，要么刚刚 addWorker 失败
-  
-        // 如果线程池处于 RUNNING ，把这个任务添加到任务队列 workQueue 中
-        if (isRunning(c) && workQueue.offer(command)) {
-            /* 若任务进入 workQueue，我们是否需要开启新的线程
-             * 线程数在 [0, corePoolSize) 是无条件开启新线程的
-             * 若线程数已经大于等于 corePoolSize，则将任务添加到队列中，然后进到这里
-             */
-            int recheck = ctl.get();
-            // 若线程池不处于 RUNNING ，则移除已经入队的这个任务，并且执行拒绝策略
-            if (! isRunning(recheck) && remove(command))
-                reject(command);
-            // 若线程池还是 RUNNING ，且线程数为 0，则开启新的线程
-            // 这块代码的真正意图：担心任务提交到队列中了，但是线程都关闭了
-            else if (workerCountOf(recheck) == 0)
-                addWorker(null, false);
-        }
-        // 若 workQueue 满，到该分支
-        // 以 maximumPoolSize 为界创建新 worker，
-        // 若失败，说明当前线程数已经达到 maximumPoolSize，执行拒绝策略
-        else if (!addWorker(command, false))
-            reject(command);
-    }
-```
-**工作线程**:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行.我们可以从Worker类的run()方法里看到这点
-
-```java
-  public void run() {
-        try {
-            Runnable task = firstTask;
-            firstTask = null;
-            while (task != null || (task = getTask()) != null) {
-                runTask(task);
-                task = null;
-            }
-        } finally {
-            workerDone(this);
-        }
-    }
- boolean workerStarted = false;
-        boolean workerAdded = false;
-        Worker w = null;
-        try {
-            w = new Worker(firstTask);
-
-            final Thread t = w.thread;
-            if (t != null) {
-               //先加锁
-                final ReentrantLock mainLock = this.mainLock;
-                mainLock.lock();
-                try {
-                    // Recheck while holding lock.
-                    // Back out on ThreadFactory failure or if
-                    // shut down before lock acquired.
-                    int rs = runStateOf(ctl.get());
-
-                    if (rs < SHUTDOWN ||
-                        (rs == SHUTDOWN && firstTask == null)) {
-                        if (t.isAlive()) // precheck that t is startable
-                            throw new IllegalThreadStateException();
-                        workers.add(w);
-                        int s = workers.size();
-                        if (s > largestPoolSize)
-                            largestPoolSize = s;
-                        workerAdded = true;
-                    }
-                } finally {
-                    mainLock.unlock();
-                }
-                if (workerAdded) {
-                    t.start();
-                    workerStarted = true;
-                }
-            }
-        } finally {
-            if (! workerStarted)
-                addWorkerFailed(w);
-        }
-        return workerStarted;
-    }
-```
-线程池中的线程执行任务分两种情况
- - 在execute()方法中创建一个线程时,会让这个线程执行当前任务
- - 这个线程执行完上图中 1 的任务后,会反复从BlockingQueue获取任务来执行
-
-# 线程池的使用
-
-##  向线程池提交任务
- 可以使用两个方法向线程池提交任务
-###  execute()
-用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功.通过以下代码可知execute()方法输入的任务是一个Runnable类的实例.
-```java
-    threadsPool.execute(new Runnable() {
-            @Override
-            public void run() {
-                   // TODO Auto-generated method stub
-            }
-        });
-```
-从运行结果可以看出，单线程池中的线程是顺序执行的。固定线程池（参数为2）中，永远最多只有两个线程并发执行。缓存线程池中，所有线程都并发执行。 
-第二个例子，测试单线程调度线程池和固定调度线程池。
-
-```java
-public class ScheduledThreadPoolExam {
-    public static void main(String[] args) {
-        //first test for singleThreadScheduledPool
-        ScheduledExecutorService scheduledPool = Executors.newSingleThreadScheduledExecutor();
-        //second test for scheduledThreadPool
-//        ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);
-        for (int i = 0; i < 5; i++) {
-            scheduledPool.schedule(new TaskInScheduledPool(i), 0, TimeUnit.SECONDS);
-        }
-        scheduledPool.shutdown();
-    }
-}
-
-class TaskInScheduledPool implements Runnable {
-    private final int id;
-
-    TaskInScheduledPool(int id) {
-        this.id = id;
-    }
-
-    @Override
-    public void run() {
-        try {
-            for (int i = 0; i < 5; i++) {
-                System.out.println("TaskInScheduledPool-["+id+"] is running phase-"+i);
-                TimeUnit.SECONDS.sleep(1);
-            }
-            System.out.println("TaskInScheduledPool-["+id+"] is over");
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-    }
-}
-```
-从运行结果可以看出，单线程调度线程池和单线程池类似，而固定调度线程池和固定线程池类似。 
-总结：
-
-- 如果没有特殊要求，使用缓存线程池总是合适的；
-- 如果只能运行一个线程，就使用单线程池。
-- 如果要运行调度任务，则按需使用调度线程池或单线程调度线程池
-- 如果有其他特殊要求，则可以直接使用ThreadPoolExecutor类的构造函数来创建线程池，并自己给定那五个参数。
-
-###  submit()
-用于提交需要返回值的任务.线程池会返回一个future类型对象,通过此对象可以判断任务是否执行成功
-并可通过get()获取返回值,get()会阻塞当前线程直到任务完成,而使用get（long timeout，TimeUnit unit）方法则会阻塞当前线程一段时间后立即返回,这时候可能任务没有执行完.
-
-```java
-    Future<Object> future = executor.submit(harReturnValuetask);
-        try {
-            Object s = future.get();
-        } catch (InterruptedException e) {
-            // 处理中断异常
-        } catch (ExecutionException e) {
-            // 处理无法执行任务异常
-        } finally {
-            // 关闭线程池
-            executor.shutdown();
-        }
-```
-##  关闭线程池
-可通过调用线程池的**shutdown**或**shutdownNow**方法来关闭线程池.
-它们的原理是遍历线程池中的工作线程,然后逐个调用线程的**interrupt**方法来中断线程,所以无法响应中断的任务可能永远无法终止.
-但是它们存在一定的区别
-
- - **shutdownNow**首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
- - **shutdown**只是将线程池的状态设置成SHUTDOWN状态，然后中断所有没有正在执行任务的线程.
-
-只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true.
-当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true.
-至于应该调用哪一种方法,应该由提交到线程池的任务的特性决定,通常调用shutdown方法来关闭线程池,若任务不一定要执行完,则可以调用shutdownNow方法.
-
-##  合理配置
-
-要想合理地配置线程池,就必须首先
-
-### 分析任务特性
-
-可从以下几个角度来分析
- - 任务的性质：CPU密集型任务、IO密集型任务和混合型任务
- - 任务的优先级：高、中和低
- - 任务的执行时间：长、中和短
- - 任务的依赖性：是否依赖其他系统资源，如数据库连接。
-
-### 任务性质
-可用不同规模的线程池分开处理
-
-#### CPU密集型任务(计算型任务)
-应配置尽可能小的线程,配置
- ` N(CPU)+1 `或 `N(CPU) * 2`
-
-#### I/O密集型任务
-相对比计算型任务，需多一些线程，根据具体 I/O 阻塞时长考量
-
-> 如Tomcat中默认最大线程数: 200。
-
-也可考虑根据需要在一个最小数量和最大数量间自动增减线程数。
-
-业务读取较多,线程并不是一直在执行任务,则应配置尽可能多的线程
-`N(CPU)/1 - 阻塞系数(0.8~0.9)`
-
-一般,生产环境下,CPU使用率达到80,说明被充分利用
-
-#### 混合型的任务
-如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量.如果这两个任务执行时间相差太大,则没必要进行分解.
-
-可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数.
-
-优先级不同的任务可以使用PriorityBlockingQueue处理.它可以让优先级高
-的任务先执行.
-
-> 注意　如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行
-
-执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行.
-
-依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU.
-
-**建议使用有界队列**  有界队列能增加系统的稳定性和预警能力，可以根据需要设大一点,比如几千.
-假如系统里后台任务线程池的队列和线程池全满了,不断抛出抛弃任务的异常,通过排查发现是数据库出现了问题,导致执行SQL变得非常缓慢,因为后台任务线程池里的任务全是需要向数据库查询和插入数据的,所以导致线程池里的工作线程全部阻塞,任务积压在线程池里.
-如果我们设置成无界队列,那么线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用,而不只是后台任务出现问题.
-## 2.5　线程池的监控
-如果在系统中大量使用线程池,则有必要对线程池进行监控,方便在出现问题时,可以根据线程池的使用状况快速定位问题.可通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性:
-
- - taskCount：线程池需要执行的任务数量
- - completedTaskCount：线程池在运行过程中已完成的任务数量，小于或等于taskCount。
- - largestPoolSize：线程池里曾经创建过的最大线程数量.通过这个数据可以知道线程池是否曾经满过.如该数值等于线程池的最大大小,则表示线程池曾经满过.
- - getPoolSize：线程池的线程数量.如果线程池不销毁的话,线程池里的线程不会自动销毁，所以这个大小只增不减.
- - getActiveCount：获取活动的线程数.
-
-通过扩展线程池进行监控.可以通过继承线程池来自定义线程池,重写线程池的
-beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控.例如,监控任务的平均执行时间、最大执行时间和最小执行时间等.
-这几个方法在线程池里是空方法.
-```java
-protected void beforeExecute(Thread t, Runnable r) { }
-```
-## 2.6 线程池的状态 
-1.当线程池创建后，初始为 running 状态 
-2.调用 shutdown 方法后，处 shutdown 状态，此时不再接受新的任务，等待已有的任务执行完毕 
-3.调用 shutdownnow 方法后，进入 stop 状态，不再接受新的任务，并且会尝试终止正在执行的任务。 
-4.当处于 shotdown 或 stop 状态，并且所有工作线程已经销毁，任务缓存队列已清空，线程池被设为 terminated 状态。 
-
-# 总结
-## java 线程池有哪些关键属性？
-- corePoolSize 到 maximumPoolSize 之间的线程会被回收，当然 corePoolSize 的线程也可以通过设置而得到回收（allowCoreThreadTimeOut(true)）。
-- workQueue 用于存放任务，添加任务的时候，如果当前线程数超过了 corePoolSize，那么往该队列中插入任务，线程池中的线程会负责到队列中拉取任务。
-- keepAliveTime 用于设置空闲时间，如果线程数超出了 corePoolSize，并且有些线程的空闲时间超过了这个值，会执行关闭这些线程的操作
-- rejectedExecutionHandler 用于处理当线程池不能执行此任务时的情况，默认有抛出 RejectedExecutionException 异常、忽略任务、使用提交任务的线程来执行此任务和将队列中等待最久的任务删除，然后提交此任务这四种策略，默认为抛出异常。
-##线程池中的线程创建时机？
-- 如果当前线程数少于 corePoolSize，那么提交任务的时候创建一个新的线程，并由这个线程执行这个任务；
-- 如果当前线程数已经达到 corePoolSize，那么将提交的任务添加到队列中，等待线程池中的线程去队列中取任务；
-- 如果队列已满，那么创建新的线程来执行任务，需要保证池中的线程数不会超过 maximumPoolSize，如果此时线程数超过了 maximumPoolSize，那么执行拒绝策略。
-
-## 任务执行过程中发生异常怎么处理？
-如果某个任务执行出现异常，那么执行任务的线程会被关闭，而不是继续接收其他任务。然后会启动一个新的线程来代替它。
-
-## 什么时候会执行拒绝策略？
-- workers 的数量达到了 corePoolSize，任务入队成功，以此同时线程池被关闭了，而且关闭线程池并没有将这个任务出队，那么执行拒绝策略。这里说的是非常边界的问题，入队和关闭线程池并发执行，读者仔细看看 execute 方法是怎么进到第一个 reject(command) 里面的。
-- workers 的数量大于等于 corePoolSize，准备入队，可是队列满了，任务入队失败，那么准备开启新的线程，可是线程数已经达到 maximumPoolSize，那么执行拒绝策略。
-
-# 参考
-- 《码出高效》
-
-- 《Java并发编程的艺术》
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\272\277\347\250\213\351\200\232\344\277\241.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\272\277\347\250\213\351\200\232\344\277\241.md"
deleted file mode 100644
index 423ce8a991..0000000000
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\272\277\347\250\213\351\200\232\344\277\241.md"
+++ /dev/null
@@ -1,68 +0,0 @@
-要想实现多个线程之间的协同，如：线程执行先后顺序、获取某个线程执行的结果等。
-涉及到线程之间相互通信，分为如下四类：
-# 1 文件共享
-![](https://img-blog.csdnimg.cn/20191008023446691.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-# 2 网络共享
-socket编程
-
-# 3 共享变量
-![](https://img-blog.csdnimg.cn/20191008023621435.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-# 4 线程协作（JDK API）
-细分为: ~~suspend/resume~~ 、 wait/notify、 park/unpark
-
-JDK中对于需要多线程协作完成某一任务的场景，提供了对应API支持。
-多线程协作的典型场景是:生产者-消费者模型。(线程阻塞、 线程唤醒)
-
-## 示例
-- 线程-1去买包子，没有包子，则不再执行
-- 线程-2生产出包子，通知线程-1继续执行
-![](https://img-blog.csdnimg.cn/2019100802383347.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-## 4.1 suspend、resume（废弃）
-- 调用suspend挂起目标线程
-- resume恢复线程执行
-![](https://img-blog.csdnimg.cn/2021071910523538.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-但该组合很容易写出
-### 死锁
-- 同步代码中使用
-![](https://img-blog.csdnimg.cn/2019100802572960.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210719133903312.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-- 先后顺序：suspend比resume后执行
-![](https://img-blog.csdnimg.cn/2019100803021180.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20191008030515787.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-所以用如下机制替代
-## 4.2 wait/notify
-这些方法只能由**同一对象锁**的持有者线程调用，也就是写在同步块里面，否则抛IllegalMonitorStateException。
-
-**wait** 方法导致当前线程等待，加入该对象的等待集合中，并且**放弃当前持有的对象锁**。
-
-**notify**/**notifyAll** 方法唤醒**一个**/**所有**正在等待这个对象锁的线程。
-
-> 虽然wait会自动解锁，但**对顺序有要求**。若在notify被调用后， 才调用wait，则线程会永远处于**WAITING**态。
-
-### 正常使用
-![](https://img-blog.csdnimg.cn/20191008031659529.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-### 死锁
-![](https://img-blog.csdnimg.cn/20191008032042659.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20191008032138968.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-## 4.3 park/unpark
-LockSupport用来创建锁和其他同步类的基本线程阻塞原语：
-- 线程调用`LockSupport.park`，则等待“许可”
-- 线程调用`LockSupport.unpark`，必须把等待获得许可的线程作为参数进行传递，好让此线程继续运行，为指定线程提供“许可(permit)” 
-
-**不要求park和unpark方法的调用顺序**。
-
-多次调用unpark之后，再调用park，线程会直接运行，**不会叠加**，即连续多次调用park，第一次会拿到“许可”直接运行，后续调用会进入等待。
-
-### 正常![](https://img-blog.csdnimg.cn/20191008033156471.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-### 死锁
-![](https://img-blog.csdnimg.cn/20191008033327469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-# 5 伪唤醒
-**之前代码中用if语句来判断，是否进入等待状态，是错误的**。
-
-官方推荐`应该在循环中检查等待条件`，因为处于等待状态的线程可能会收到**错误警报和伪唤醒**，如果不在循环中检查等待条件，程序就可能在没有满足结束条件的情况下退出。
-
-伪唤醒是指线程并非因为notify、notifyall、 unpark等API调用而唤醒，而是更底层原因导致的。
-![](https://img-blog.csdnimg.cn/20191008034349825.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\345\271\266\345\217\221\347\274\226\347\250\213\344\271\213\345\217\257\351\207\215\345\205\245\350\257\273\345\206\231\351\224\201ReadWriteLock.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\345\271\266\345\217\221\347\274\226\347\250\213\344\271\213\345\217\257\351\207\215\345\205\245\350\257\273\345\206\231\351\224\201ReadWriteLock.md"
deleted file mode 100644
index 939671cfc3..0000000000
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\345\271\266\345\217\221\347\274\226\347\250\213\344\271\213\345\217\257\351\207\215\345\205\245\350\257\273\345\206\231\351\224\201ReadWriteLock.md"
+++ /dev/null
@@ -1,53 +0,0 @@
-![](https://img-blog.csdnimg.cn/2021042313262066.JPG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-
-> 了解读写锁吗？
-
-互联网的并发场景大多是读多写少。所以缓存技术使用普遍。JUC也提供了读写锁-ReadWriteLock。
-
-> 那你说说什么是读写锁？
-
-读写锁一般遵循以下设计原则：
-- 允许多个线程同时读共享变量
-- 只允许一个线程写共享变量
-- 如果一个写线程正在执行写操作，此时禁止读线程读共享变量。
-
-> 知道读写锁与互斥锁的区别吗？
-
-读写锁允许多个线程同时读共享变量，而互斥锁不允许。这也是读多写少时读写锁的优势。
-读写锁的写是互斥的，当一个线程在写共享变量时，其他线程不允许执行写或读。
-
-> 知道如何使用ReadWriteLock实现一个缓存吗？
-
-声明了一个Cache<K, V>类，其中类型参数K代表缓存里key的类型，V代表缓存里value的类型。
-
-> 你是怎么解决缓存数据的初始化问题的？
-
-这得看源数据量大不大了。
-
-若源数据量不大，采用**一次性加载**，方便简单，在应用启动时把源数据全部查询出来并put()。
-
-若源数据量很大，就得**按需加载**，即懒加载。当应用查询缓存，并且数据不在缓存时，才触发加载源数据进缓存。
-代码如下：
-![](https://img-blog.csdnimg.cn/20210422174748811.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-高并发下，多线程会竞争写锁。假设缓存为空，若此时有三个线程t1、t2和t3同时调用get()，并且参数相同。则它们会同时执行到代码5处，但此时只有一个线程能够获得写锁，假设是t1。
-t1获取写锁后，查询DB并更新缓存，最终释放写锁。此时t2、t3会再有一个线程能够获取写锁，假设t2。若这里不去再次验证，此时t2会再查DB。t2释放写锁后，t3还会查DB。而事实上t2、t3完全没必要再查询DB。所以这里的再次验证很重要，能避免高并发竞争场景下重复查DB。
-
-
-这里并没有解决缓存数据与源头数据的一致性问题。解决数据一致性问题的一个最简单的方案就是超时：加载进缓存的数据不是长久有效的，而是有时效的，当缓存的数据超过时效，也就是超时之后，这条数据在缓存中就失效了。而访问缓存中失效的数据，会触发缓存重新从源头把数据加载进缓存。
-
-也可以在源头数据发生变化时，快速反馈给缓存，但这个就要依赖具体的场景了。例如MySQL作为数据源头，可以通过近实时地解析binlog来识别数据是否发生了变化，如果发生了变化就将最新的数据推送给缓存。另外，还有一些方案采取的是数据库和缓存的双写方案。
-
-> 说说读写锁的升级与降级？
-
-按需加载的代码中，是否可在第2步下面增加验证并更新缓存的逻辑呢？
-如下：
-![](https://img-blog.csdnimg.cn/20210422175821172.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-看起来没问题的，先获取读锁，再升级为写锁，这是锁的升级。可惜ReadWriteLock并不支持这种升级。在上面的代码示例中，读锁还没有释放，此时获取写锁，会导致写锁永久等待，最终导致相关线程都被阻塞，永远也没有机会被唤醒。所以读写锁是不支持锁升级的！
-
-但锁的降级是可以的。代码如下：
-![](https://img-blog.csdnimg.cn/20210422180421485.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-只有写锁支持条件变量，读锁是不支持条件变量的，读锁调用newCondition()会抛出UnsupportedOperationException异常。
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213 - \345\217\257\351\207\215\345\205\245\350\257\273\345\206\231\351\224\201 ReentrantReadWriteLock.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213 - \345\217\257\351\207\215\345\205\245\350\257\273\345\206\231\351\224\201 ReentrantReadWriteLock.md"
new file mode 100644
index 0000000000..1e25c9b409
--- /dev/null
+++ "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213 - \345\217\257\351\207\215\345\205\245\350\257\273\345\206\231\351\224\201 ReentrantReadWriteLock.md"	
@@ -0,0 +1,497 @@
+# 1 读写锁维护了一对相关的锁，一个用于只读操作，一个用于写入操作。
+
+只要没有writer，读锁可以由多个reader线程同时保持。
+写锁是独占的。
+
+- 互斥锁一次只允许一个线程访问共享数据，哪怕进行的是只读操作
+- 读写锁允许对共享数据进行更高级别的并发访问
+     - 对于写操作，一次只有一个线程（write线程）可以修改共享数据
+     - 对于读操作，允许任意数量的线程同时进行读取。
+
+与互斥锁相比，使用读写锁能否提升性能则取决于读写操作期间`读取数据相对于修改数据的频率，以及数据的争用,即在同一时间试图对该数据执行读取或写入操作的线程数`。
+
+读写锁适用于`读多写少`的情况。
+
+# 2 可重入读写锁 ReentrantReadWriteLock
+
+## 2.1 属性
+
+`ReentrantReadWriteLock` 也是基于 `AbstractQueuedSynchronizer `实现的，具有下面这些属性
+
+- 获取顺序：此类不会将读/写者优先强加给锁访问的排序
+
+   - 非公平模式（默认）
+连续竞争的非公平锁可能无限期地推迟一个或多个reader或writer线程，但吞吐量通常要高于公平锁
+  - 公平模式
+线程利用一个近似到达顺序的策略来争夺进入。
+当释放当前保持的锁时，可以为等待时间最长的单个writer线程分配写锁，如果有一组等待时间大于所有正在等待的writer线程的reader，将为该组分配读锁。
+试图获得公平写入锁的非重入的线程将会阻塞，除非读取锁和写入锁都自由（这意味着没有等待线程）。
+- 重入：此锁允许reader和writer按照 ReentrantLock 的样式重新获取读/写锁。在写线程保持的所有写锁都已释放后，才允许重入reader使用读锁
+writer可以获取读取锁，但reader不能获取写入锁。
+- 锁降级：重入还允许从写锁降级为读锁，实现方式是：先获取写锁，然后获取读取锁，最后释放写锁。但是，`从读取锁升级到写入锁是不可能的`。
+- 锁获取的中断：读锁和写锁都支持锁获取期间的中断。
+- Condition 支持：写锁提供了一个 Condition 实现，对于写锁来说，该实现的行为与 `ReentrantLock.newCondition()` 提供的 Condition 实现对 ReentrantLock 所做的行为相同。当然，`此 Condition 只能用于写锁`。
+读锁不支持 Condition，readLock().newCondition() 会抛UnsupportedOperationException
+- 监测：此类支持一些确定是读锁还是写锁的方法。这些方法设计用于监视系统状态，而不是同步控制。
+
+# 3 ReentrantLock加锁和释放锁的底层原理 - AQS 
+
+## 3.1 回顾
+
+AQS对象内部以单个 int 类型的原子变量来表示其状态, 代表了加锁的状态。
+初始状态下，这个state的值是0
+
+AQS内部还有一个关键变量，记录当前加锁的是哪个线程
+初始化状态下，这个变量是null![](https://img-blog.csdnimg.cn/20191018234504480.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+接着线程1跑过来调用ReentrantLock的*lock*()方法尝试进行加锁，这个加锁的过程，直接就是用CAS操作将state值从0变为1。
+如果之前没人加过锁，那么state的值肯定是0，此时线程1就可以加锁成功。
+一旦线程1加锁成功了之后，就可以设置当前加锁线程是自己。
+
+- 线程1跑过来加锁的一个过程
+![](https://img-blog.csdnimg.cn/20191018235617253.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+说白了，就是并发包里的一个核心组件，里面有state变量、加锁线程变量等核心的东西，维护了加锁状态。
+
+ReentrantLock 这种东西只是一个外层的API，内核中的锁机制实现其实都是依赖AQS组件
+
+Reentrant打头，意思是一个可重入锁。
+可重入锁就是你可以对一个ReentrantLock对象多次执行lock()加锁和unlock()释放锁，也就是可以对一个锁加多次，叫做可重入加锁。
+看明白了那个state变量之后，就知道了如何进行可重入加锁！
+
+
+其实每次线程1可重入加锁一次，会判断一下当前加锁线程就是自己，那么他自己就可以可重入多次加锁，每次加锁就是把state的值给累加1，别的没啥变化。
+
+接着，如果线程1加锁了之后，线程2跑过来加锁会怎么样呢？
+
+## 3.2 我们来看看锁的互斥是如何实现的
+线程2跑过来一下看到，哎呀！state的值不是0啊？所以CAS操作将state从0变为1的过程会失败，因为state的值当前为1，说明已经有人加锁了！
+
+接着线程2会看一下，是不是自己之前加的锁啊？当然不是了，“加锁线程”这个变量明确记录了是线程1占用了这个锁，所以线程2此时就是加锁失败。
+
+- 一起来感受一下线程2的绝望心路
+![](https://img-blog.csdnimg.cn/20191019000850859.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+接着，线程2会将自己放入AQS中的一个等待队列，因为自己尝试加锁失败了，此时就要将自己放入队列中来等待，等待线程1释放锁之后，自己就可以重新尝试加锁了
+
+所以大家可以看到，AQS是如此的核心！AQS内部还有一个等待队列，专门放那些加锁失败的线程！
+
+- 同样，给大家来一张图，一起感受一下：
+![](https://img-blog.csdnimg.cn/20191019001308340.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+接着，线程1在执行完自己的业务逻辑代码之后，就会释放锁！
+他释放锁的过程非常的简单，就是将AQS内的state变量的值递减1，如果state值为0，则彻底释放锁，会将“加锁线程”变量也设置为null！
+- 附图
+![](https://img-blog.csdnimg.cn/20191019001520803.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+接下来，会从等待队列的队头唤醒线程2重新尝试加锁。
+好！线程2现在就重新尝试加锁，这时还是用CAS操作将state从0变为1，此时就会成功，成功之后代表加锁成功，就会将state设置为1。
+此外，还要把“加锁线程”设置为线程2自己，同时线程2自己就从等待队列中出队了。
+- 最后再来一张图，大家来看看这个过程。
+![](https://img-blog.csdnimg.cn/20191019001626741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+
+## 3.3 个抽象方法
+
+- tryAcquire(int)
+- tryRelease(int)
+- tryAcquireShared(int)
+- tryReleaseShared(int)
+
+
+前两个方法用于独占/排他模式，后两个用于共享模式 ,留给子类实现，自定义同步器的行为以实现特定的功能。
+
+- ReentrantLock，它是可重入的独占锁，内部的 Sync 类实现了 tryAcquire(int)、tryRelease(int) 方法，并用状态的值来表示重入次数，加锁或重入锁时状态加 1，释放锁时状态减 1，状态值等于 0 表示锁空闲。
+
+- CountDownLatch，它是一个关卡，在条件满足前阻塞所有等待线程，条件满足后允许所有线程通过。内部类 Sync 把状态初始化为大于 0 的某个值，当状态大于 0 时所有wait线程阻塞，每调用一次 countDown 方法就把状态值减 1，减为 0 时允许所有线程通过。利用了AQS的共享模式。
+
+现在，要用AQS来实现 ReentrantReadWriteLock。
+
+# 4 AQS只有一个状态，那么如何表示 多个读锁 与 单个写锁
+ReentrantLock 里，状态值表示重入计数
+- 现在如何在AQS里表示每个读锁、写锁的重入次数呢
+- 如何实现读锁、写锁的公平性呢
+
+一个状态是没法既表示读锁，又表示写锁的，显然不够用啊，那就辦成两份用了!
+**状态的高位部分表示读锁，低位表示写锁**
+由于写锁只有一个，所以写锁的重入计数也解决了，这也会导致写锁可重入的次数减小。
+
+由于读锁可以同时有多个，肯定不能再用辦成两份用的方法来处理了
+但我们有 `ThreadLocal`，可以把线程重入读锁的次数作为值存在 `ThreadLocal `
+
+对于公平性的实现，可以通过AQS的等待队列和它的抽象方法来控制
+在状态值的另一半里存储当前持有读锁的线程数。
+- 如果读线程申请读锁，当前写锁重入次数不为 0 时，则等待，否则可以马上分配
+- 如果是写线程申请写锁，当前状态为 0 则可以马上分配，否则等待。
+
+# 5 源码分析
+AQS 的状态是32位（int 类型）的
+## 5.1 一分为二
+读锁用高16位，表示持有读锁的线程数（sharedCount），写锁低16位，表示写锁的重入次数 （exclusiveCount）。状态值为 0 表示锁空闲，sharedCount不为 0 表示分配了读锁，exclusiveCount 不为 0 表示分配了写锁，sharedCount和exclusiveCount 肯定不会同时不为 0。
+![](https://img-blog.csdnimg.cn/20191019003604724.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+## 读锁重入计数
+```
+    /**
+      * A counter for per-thread read hold counts.
+      * Maintained as a ThreadLocal; cached in cachedHoldCounter
+      * 每个线程特定的 read 持有计数。存放在ThreadLocal，缓存在cachedHoldCounter
+      * 不需要是线程安全的。
+      */
+    static final class HoldCounter {
+        int count = 0;
+
+        // 使用id而不是引用是为了避免保留垃圾。注意这是个常量。
+        // Use id, not reference, to avoid garbage retention
+        final long tid = Thread.currentThread().getId();
+    }
+
+    /**
+       * ThreadLocal subclass. Easiest to explicitly define for sake
+       * of deserialization mechanics.
+       * 采用继承是为了重写 initialValue 方法，这样就不用进行这样的处理：
+       * 如果ThreadLocal没有当前线程的计数，则new一个，再放进ThreadLocal里。
+       * 可以直接调用 get
+       */
+    static final class ThreadLocalHoldCounter
+        extends ThreadLocal<HoldCounter> {
+        public HoldCounter initialValue() {
+            return new HoldCounter();
+        }
+    }
+
+    /**
+      * The number of reentrant read locks held by current thread.
+      * Initialized only in constructor and readObject.
+      * Removed whenever a thread's read hold count drops to 0.
+      * 保存当前线程重入读锁的次数的容器。在读锁重入次数为 0 时移除
+      */
+    private transient ThreadLocalHoldCounter readHolds;
+
+       /**
+         * The hold count of the last thread to successfully acquire
+         * readLock. This saves ThreadLocal lookup in the common case
+         * where the next thread to release is the last one to
+         * acquire. This is non-volatile since it is just used
+         * as a heuristic, and would be great for threads to cache.
+         *
+         * <p>Can outlive the Thread for which it is caching the read
+         * hold count, but avoids garbage retention by not retaining a
+         * reference to the Thread.
+         *
+         * <p>Accessed via a benign data race; relies on the memory
+         * model's final field and out-of-thin-air guarantees.
+         */
+    /**
+     * 最近一个成功获取读锁的线程的计数。这省却了ThreadLocal查找，
+     * 通常情况下，下一个要释放的线程是最后一个获取的线程。
+     * 这不是 volatile 的，因为它仅用于试探的，线程进行缓存也是极好的
+     * （因为判断是否是当前线程是通过线程id来比较的）。
+     */
+    private transient HoldCounter cachedHoldCounter;
+
+       /**
+         * firstReader is the first thread to have acquired the read lock.
+         * firstReaderHoldCount is firstReader's hold count.
+         *
+         * <p>More precisely, firstReader is the unique thread that last
+         * changed the shared count from 0 to 1, and has not released the
+         * read lock since then; null if there is no such thread.
+         *
+         * <p>Cannot cause garbage retention unless the thread terminated
+         * without relinquishing its read locks, since tryReleaseShared
+         * sets it to null.
+         *
+         * <p>Accessed via a benign data race; relies on the memory
+         * model's out-of-thin-air guarantees for references.
+         *
+         * <p>This allows tracking of read holds for uncontended read
+         * locks to be very cheap.
+         */
+    /**
+     * firstReader是第一个获取读锁的线程,更加确切地说是最后一个把 共享计数 从 0 改为 1 的(在锁空闲的时候)，而且在那之后还没有释放读锁的独特的线程!如果不存在这样的线程则为null
+     * firstReaderHoldCount 是 firstReader 的重入计数。
+     *
+     * firstReader 不会导致垃圾存留，因此在 tryReleaseShared 里设置为null，
+     * 除非线程异常终止，没有释放读锁
+     *
+     * 这使得在跟踪无竞争的读锁计数时代价非常低
+     *
+     * firstReader及其计数firstReaderHoldCount是不会放入 readHolds 的
+     */
+    private transient Thread firstReader = null;
+    private transient int firstReaderHoldCount;
+
+    Sync() {
+        readHolds = new ThreadLocalHoldCounter();
+        // ensures visibility of readHolds
+        setState(getState()); // 确保 readHolds 的内存可见性，利用 volatile 写的内存语义
+    }
+}
+```
+## 写锁的获取与释放
+通过 tryAcquire 和 tryRelease 实现，源码里有这么一段说明
+![](https://img-blog.csdnimg.cn/20191019003704206.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+tryRelease 和 tryAcquire 可能被 Conditions 调用。
+因此可能出现参数里包含在条件等待和用 tryAcquire 重新获取到锁的期间内已经释放的 读和写 计数
+
+这说明看起来像是在 tryAcquire 里设置状态时要考虑方法参数(acquires)的高位部分，其实是不需要的。由于写锁是独占的，acquires 表示的只能是写锁的计数，如果当前线程成功获取写锁，只需要简单地把当前状态加上 acquires 的值即可，tryRelease 里直接减去其参数值即可。
+```
+protected final boolean tryAcquire(int acquires) {
+   /*
+     * Walkthrough:
+     * 1. If read count nonzero or write count nonzero
+     *    and owner is a different thread, fail.
+     * 2. If count would saturate, fail. (This can only
+     *    happen if count is already nonzero.)
+     * 3. Otherwise, this thread is eligible for lock if
+     *    it is either a reentrant acquire or
+     *    queue policy allows it. If so, update state
+     *    and set owner.
+     */
+    Thread current = Thread.currentThread();
+    int c = getState();
+    int w = exclusiveCount(c);
+    if (c != 0) { // 状态不为0，表示锁被分配出去了
+        // (Note: if c != 0 and w == 0 then shared count != 0)
+        // c != 0 且 w == 0 : 分配了读锁
+        // w != 0 && current != getExclusiveOwnerThread() 表示其他线程获取了写锁。
+        if (w == 0 || current != getExclusiveOwnerThread())
+            return false ;
+        // 写锁重入
+        // 检测是否超过最大重入次数。
+        if (w + exclusiveCount(acquires) > MAX_COUNT)
+            throw new Error("Maximum lock count exceeded");
+
+        // 更新写锁重入次数，写锁在低位，直接加上 acquire 即可。
+        // Reentrant acquire
+        setState(c + acquires);
+        return true ;
+    }
+    // writerShouldBlock 留给子类实现，用于实现公平性策略
+    // 如果允许获取写锁，则用 CAS 更新状态
+    if (writerShouldBlock() ||
+        !compareAndSetState(c, c + acquires))
+        return false ; // 不允许获取锁 或 CAS 失败。
+    // 获取写锁，设置独占线程
+    setExclusiveOwnerThread(current);
+    return true;
+}
+
+protected final boolean tryRelease(int releases) {
+    if (!isHeldExclusively()) // 是否是当前线程持有写锁
+        throw new IllegalMonitorStateException();
+    // 这里不考虑高16位是因为高16位肯定是 0
+    int nextc = getState() - releases;
+    boolean free = exclusiveCount(nextc) == 0;
+    if (free)
+        setExclusiveOwnerThread( null); // 写锁完全释放，设置独占线程为null
+    setState(nextc);
+    return free;
+}
+```
+
+## 读锁获取与释放
+```
+// 参数变为 unused 是因为读锁的重入计数是内部维护的
+protected final int tryAcquireShared(int unused) {
+/*
+             * Walkthrough:
+             * 1. If write lock held by another thread, fail.
+             * 2. Otherwise, this thread is eligible for
+             *    lock wrt state, so ask if it should block
+             *    because of queue policy. If not, try
+             *    to grant by CASing state and updating count.
+             *    Note that step does not check for reentrant
+             *    acquires, which is postponed to full version
+             *    to avoid having to check hold count in
+             *    the more typical non-reentrant case.
+             * 3. If step 2 fails either because thread
+             *    apparently not eligible or CAS fails or count
+             *    saturated, chain to version with full retry loop.
+             */
+    Thread current = Thread.currentThread();
+    int c = getState();
+    // 持有写锁的线程可以获取读锁
+    if (exclusiveCount(c) != 0 && // 已分配了写锁
+        getExclusiveOwnerThread() != current) // 且当前线程不是持有写锁的线程
+        return -1;
+    int r = sharedCount(c); // 获取读锁的计数
+    if (!readerShouldBlock() && // 由子类根据其公平策略决定是否允许获取读锁
+        r < MAX_COUNT &&           // 读锁数量尚未达到最大值
+        // 尝试获取读锁,注意读线程计数的单位是  2^16
+        compareAndSetState(c, c + SHARED_UNIT)) {
+         // 成功获取读锁
+
+     // 注意下面对firstReader的处理：firstReader是不会放到readHolds里的
+     // 这样，在读锁只有一个的情况下，就避免了查找readHolds
+     if (r == 0) { // 是 firstReader，计数不会放入 readHolds
+            firstReader = current;
+            firstReaderHoldCount = 1;
+        } else if (firstReader == current) { // firstReader 重入
+            firstReaderHoldCount++;
+        } else {
+             // 非 firstReader 读锁重入计数更新
+            HoldCounter rh = cachedHoldCounter; // 首先访问缓存
+            if (rh == null || rh.tid != current.getId())
+                cachedHoldCounter = rh = readHolds.get();
+            else if (rh.count == 0)
+                readHolds.set(rh);
+            rh.count++;
+        }
+        return 1;
+    }
+    // 获取读锁失败，放到循环里重试。
+    return fullTryAcquireShared(current);
+}
+/**
+  * Full version of acquire for reads, that handles CAS misses
+  * and reentrant reads not dealt with in tryAcquireShared.
+  */
+final int fullTryAcquireShared(Thread current) {
+    /*
+             * This code is in part redundant with that in
+             * tryAcquireShared but is simpler overall by not
+             * complicating tryAcquireShared with interactions between
+             * retries and lazily reading hold counts.
+             */
+    HoldCounter rh = null;
+    for (;;) {
+        int c = getState();
+        if (exclusiveCount(c) != 0) {
+            if (getExclusiveOwnerThread() != current)
+                // 写锁被分配，非写锁线程获取读锁失败
+                return -1;
+        // else we hold the exclusive lock; blocking here
+        // would cause deadlock.
+        // 否则，当前线程持有写锁，在这里阻塞将导致死锁
+        } else if (readerShouldBlock()) {
+            // Make sure we're not acquiring read lock reentrantly
+            // 写锁空闲  且  公平策略决定 线程应当被阻塞
+            // 如果是已获取读锁的线程重入读锁时，
+            // 即使公平策略指示应当阻塞也不会阻塞。
+            // 否则也会导致死锁
+            if (firstReader == current) {
+                // assert firstReaderHoldCount > 0;
+            } else {
+                if (rh == null) {
+                    rh = cachedHoldCounter;
+                    if (rh == null || rh.tid != current.getId()) {
+                        rh = readHolds.get();
+                        if (rh.count == 0)
+                            readHolds.remove();
+                    }
+                }
+                // 需要阻塞且是非重入(还未获取读锁的)，获取失败。
+                if (rh.count == 0)
+                    return -1;
+            }
+        }
+
+        // 写锁空闲  且  公平策略决定线程可以获取读锁
+        if (sharedCount(c) == MAX_COUNT) 
+            throw new Error( "Maximum lock count exceeded");
+        if (compareAndSetState(c, c + SHARED_UNIT)) {
+            // 申请读锁成功，下面的处理跟tryAcquireShared类似
+            if (sharedCount(c) == 0) {
+                firstReader = current;
+                firstReaderHoldCount = 1;
+            } else if (firstReader == current) {
+                firstReaderHoldCount++;
+            } else {
+           // 设定最后一次获取读锁的缓存
+                if (rh == null)
+                    rh = cachedHoldCounter;
+
+                if (rh == null || rh.tid != current.getId())
+                    rh = readHolds.get();
+                else if (rh.count == 0)
+                    readHolds.set(rh);
+                rh.count++;
+
+                cachedHoldCounter = rh; // 缓存起来用于释放      cache for release
+            }
+            return 1;
+        }
+    }
+}
+
+protected final boolean tryReleaseShared(int unused) {
+    Thread current = Thread.currentThread();
+    // 清理firstReader缓存 或 readHolds里的重入计数
+    if (firstReader == current) {
+        // assert firstReaderHoldCount > 0;
+        if (firstReaderHoldCount == 1)
+            firstReader = null;
+        else
+            firstReaderHoldCount--;
+    } else {
+        HoldCounter rh = cachedHoldCounter;
+        if (rh == null || rh.tid != current.getId())
+            rh = readHolds.get();
+        int count = rh.count;
+        if (count <= 1) {
+            // 完全释放读锁
+            readHolds.remove();
+            if (count <= 0)
+                throw unmatchedUnlockException();
+        }
+        --rh.count; // 主要用于重入退出
+    }
+
+    // 循环在CAS更新状态值，主要是把读锁数量减 1
+    for (;;) {
+        int c = getState();
+        int nextc = c - SHARED_UNIT;
+        if (compareAndSetState(c, nextc))
+            // 释放读锁对其他读线程没有任何影响，
+            // 但可以允许等待的写线程继续，如果读锁、写锁都空闲。
+            return nextc == 0;
+    }
+}
+```
+## 公平性策略
+策略由 Sync 的子类 FairSync 和 NonfairSync 实现
+```
+/**
+ * 这个非公平策略的同步器是写锁优先的，申请写锁时总是不阻塞。
+ */
+static final class NonfairSync extends Sync {
+    private static final long serialVersionUID = -8159625535654395037L;
+    final boolean writerShouldBlock() {
+        return false; // 写线程总是可以突入
+    }
+    final boolean readerShouldBlock() {
+        /* 作为一个启发用于避免写线程饥饿，如果线程临时出现在等待队列的头部则阻塞，
+         * 如果存在这样的，则是写线程。
+         */
+        return apparentlyFirstQueuedIsExclusive();
+    }
+}
+
+/**
+ * 公平的 Sync，它的策略是：如果线程准备获取锁时，
+ * 同步队列里有等待线程，则阻塞获取锁，不管是否是重入
+ * 这也就需要tryAcqire、tryAcquireShared方法进行处理。
+ */
+static final class FairSync extends Sync {
+    private static final long serialVersionUID = -2274990926593161451L;
+    final boolean writerShouldBlock() {
+        return hasQueuedPredecessors();
+    }
+    final boolean readerShouldBlock() {
+        return hasQueuedPredecessors();
+    }
+}
+```
+现在用奇数表示申请读锁的读线程，偶数表示申请写锁的写线程，每个数都表示一个不同的线程，存在下面这样的申请队列，假设开始时锁空闲：
+
+1  3  5  0  7  9  2  4
+读线程1申请读锁时，锁是空闲的，马上分配，读线程3、5申请时，由于已分配读锁，它们也可以马上获取读锁。
+假设此时有线程11申请读锁，由于它不是读锁重入，只能等待。而线程1再次申请读锁是可以的，因为它的重入。
+写线程0申请写锁时，由于分配了读锁，只能等待，当读线程1、3、5都释放读锁后，线程0可以获取写锁。
+线程0释放后，线程7、9获取读锁，它们释放后，线程2获取写锁，此时线程4必须等待线程2释放。
+线程4在线程2释放写锁后获取写锁，它释放写锁后，锁恢复空闲
+
+
+
+# 参考
+https://blog.csdn.net/qq_42046105/article/details/102384342
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213\345\256\236\346\210\230 - Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md"
similarity index 67%
rename from "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md"
rename to "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213\345\256\236\346\210\230 - Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md"
index ae69956c52..7e6a3383a5 100644
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md"
+++ "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213\345\256\236\346\210\230 - Java\345\206\205\345\255\230\346\250\241\345\236\213\344\270\216volatile\345\205\263\351\224\256\345\255\227.md"	
@@ -1,28 +1,190 @@
-#  volatile 的实现维度
-|级别|实现  |
-|--|--|
-| Java 代码	 |volatile int i  |
-| ByteCode 字节码	 | ACC_VOLATILE  |
-| JVM 虚拟机规范	 | JVM 内存屏障 |
-|  HotSpot 实现	 |汇编语言调用 |
-|  CPU 级别	 | MESI 原语支持总线锁  |
+# [相关源码](https://github.com/Wasabi1234/Java-Concurrency-Progamming-Tutorial)
 
+# 0 CPU 性能优化手段 - 缓存
+为了提高程序运行的性能，现代CPU在很多方面对程序进行了优化。:
+例如: CPU高速缓存。
+尽可能地避免处理器访问主内存的时间开销，处理器大多会利用缓
+存(cache)以提高性能。
+
+## 0.1 多级缓存
+- L1 Cache(一级缓存)
+CPU第一层高速缓存，分为数据缓存和指令缓存。一般服务器CPU的L1缓存的容量通常在32- - 4096KB。
+- L2
+由于L1级高速缓存容量的限制，为了再次提高CPU的运算速度，在CPU外部放置一高速存
+储器，即二级缓存。:
+- L3
+现在的都是内置的。而它的实际作用即是，L3缓存的应用可以进一步降低内存延迟，同时
+提升大数据量计算时处理器的性能。具有较大L3缓存的处理器提供更有效的文件系统缓存行为
+及较短消息和处理器队列长度。-般是多核共享一-个L3缓存!
+
+CPU在读取数据时，先在L1中寻找，再从L2寻找，再从L3寻找，然后是内存，再后是外存储器。
+
+## 0.2 缓存同步协议
+多CPU读取同样的数据进行缓存，进行不同运算之后，最终写入主内存以哪个CPU为准?
+在这种高速缓存回写的场景下，有一个缓存一致性协议, 多数CPU厂商对它进行了实现。
+`MESI协议`，它规定每条缓存有个状态位，同时定义了下面四个状态:
+- 修改态(Modified) 
+此cache行已被修改过(脏行)，内容已不同于主存，为此cache专有;
+- 专有态(Exclusive)
+此cache行内容同于主存，但不出现于其它cache中;
+- 共享态(Shared)一此cache行内容同于主存，但也出现于其它cache中;
+- 无效态(Invalid) 
+此cache行内容无效(空行)
+
+多处理器时，单个CPU对缓存中数据进行了改动，需要通知给其他CPU
+这意味着，CPU处理要控制自己的读写操作，还要监听其他CPU发出的通知，从而保证**最终
+一致。**
+
+## 0.3 问题
+缓存中的数据与主内存的数据并不是实时同步的，各CPU (或CPU核心)间缓存的数据也不是
+实时同步。**在同一个时间点，各CPU所看到同一内存地址的数据的值可能是不一致的。**
+
+# 1 Java内存模型(JMM)的意义
+![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTkyYTFmZGY0OGJlMTllMDYucG5n?x-oss-process=image/format,png)
+![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTIzZTVlOWE0OWFkZWI1YTEucG5n?x-oss-process=image/format,png)
+![JMM 与硬件内存架构对应关系](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTVlMTM3NGEwYWJmOWM5MjkucG5n?x-oss-process=image/format,png)
+![JMM抽象结构图](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQ0ZWE4ODQzYTg4YTk0MGQucG5n?x-oss-process=image/format,png)
+内存模型描述程序的可能行为。
+
+Java虚拟机规范中试图定义一种Java内存模型`来屏蔽掉各种硬件和操作系统的内存访问差异`，规定
+- 线程如何,何时能看到其他线程修改过的共享变量的值
+- 在必要时如何同步地访问共享变量
+
+以实现让Java程序在各种平台下都能达到一致性的内存访问效果。
+
+
+**Java编程语言内存模型** 通过检查执行跟踪中的每个读操作，并根据某些规则检查该读操作观察到的写操作是否有效来工作。
+
+只要程序的所有执行产生的结果都可以由内存模型预测。具体的实现者任意实现，包括操作的重新排序和删除不必要的同步。
+
+`内存模型决定了在程序的每个点上可以读取什么值`
+
+## 1.1 Shared Variables 共享变量的描述
+可以在线程之间共享的内存称为`共享内存或堆内存`
+所有实例字段、静态字段和数组元素都存储在堆内存中
+
+如果至少有一个访问是写的，那么对同一个变量的两次访问(读或写)是冲突的。
+
+ [定义](https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.1)
+
+
+
+
+# 2 主内存与工作内存
+- 工作内存缓存
+![](https://img-blog.csdnimg.cn/20191014024209488.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- Java内存模型的主要目标是定义`各个变量的访问规则`
+即在虚拟机中将变量存储到内存和从内存中取出变量值这样的底层细节
+
+此处的`变量`包括了实例域，静态域和构成数组对象的元素，但不包括局部变量与方法参数，因为后者是线程私有的，不存在竞争
+
+为了获得比较好的执行效率，JMM并没有限制执行引擎使用处理器的特定寄存器或缓存来和主内存进行交互，也没有限制即时编译器调整代码执行顺序这类权限。
+
+JMM`规定`
+- 所有的变量都存储在主内存(Main Memory)
+- 每条线程有自己的工作内存(Working Memory)
+保存了该线程使用到的`变量的主内存副本拷贝`(线程所访问对象的引用或者对象中某个在线程访问到的字段,不会是整个对象的拷贝!)
+线程对变量的所有操作(读，赋值等)都必须在工作内存中进行，不能直接读写主内存中的变量
+volatile变量依然有工作内存的拷贝,只是他特殊的操作顺序性规定,看起来如同直接在主内存读写
+不同线程之间无法直接访问对方工作内存中的变量，线程间变量值的传递均要通过主内存
+![线程、主内存、工作内存三者的交互关系](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTEyMjA5YjEyZDU3OGEyZWQucG5n?x-oss-process=image/format,png)
+![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJiM2QzN2MxNTVjZDgyZDgucG5n?x-oss-process=image/format,png)
+
+JVM模型与JMM不是同一层次的内存划分，基本是没有关系的，硬要对应起来，从变量，内存，工作内存的定义来看
+  - 主内存 === Java堆中的对象实例数据部分
+  - 工作内存 === 虚拟机栈中的部分区域
+ 
+从更底层的层次来说
+- 主内存直接对应于物理硬件的内存
+- 为了更好的运行速度，虚拟机（甚至硬件系统的本身的优化措施）可能会让工作内存优先存储于寄存器和高速缓存器中，因为程序运行时主要访问读写的是工作内存
+# 3 内存间同步操作
+## 3.1 线程操作的定义
+###  操作定义
+write要写的变量以及要写的值。
+read要读的变量以及可见的写入值(由此，我们可以确定可见的值)。
+lock要锁定的管程(监视器monitor)。
+unlock要解锁的管程。
+外部操作(socket等等..)
+启动和终止
+
+### 程序顺序
+如果一个程序没有数据竞争，那么程序的所有执行看起来都是顺序一致的
+
+本规范只涉及线程间的操作;
+
+
+
+一个变量如何从主内存拷贝到工作内存,从工作内存同步回主内存的实现细节
+
+JMM 本身已经定义实现了以下8种操作来完成，且都具备`原子性`
+- lock(锁定)
+作用于主内存变量，把一个变量标识为一条线程独占的状态
+- unlock(解锁)
+作用于主内存变量，把一个处于锁定状态的变量释放,释放后的变量才可以被其它线程锁定
+unlock之前必须将变量值同步回主内存
+- read(读取)
+作用于主内存变量，把一个变量的值从主内存传输到工作内存，以便随后的load
+- load(载入)
+作用于工作内存变量，把read从主内存中得到的变量值放入工作内存的变量副本
+- use(使用)
+作用于工作内存变量，把工作内存中一个变量的值传递给执行引擎，每当虚拟机遇到一个需要使用到的变量的值得字节码指令时将会执行这个操作
+- assign(赋值)
+作用于工作内存变量，把一个从执行引擎接收到的值赋给工作内存的变量，每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
+- store(存储)
+作用于工作内存变量，把工作内存中一个变量的值传送到主内存，以便随后的write操作使用
+- write(写入)
+作用于主内存变量，把store操作从工作内存中得到的值放入主内存的变量中
+
+- JMM 同步操作
+![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTlkZDE0ZTMxNmFiNjgxODQucG5n?x-oss-process=image/format,png)
+- 把一个变量从主内存`复制`到工作内存
+就要顺序执行read和load
+
+- 把变量从工作内存`同步`回主内存
+就要顺序地执行store和write操作
+
+JMM只要求上述两个操作必须`按序执行`，而没有保证连续执行
+也就是说read/load之间、store/write之间可以插入其它指令
+如对主内存中的变量a,b访问时，一种可能出现的顺序是read a->readb->loadb->load a
+
+JMM规定执行上述八种基础操作时必须满足如下
+## 3.1 同步规则
+◆ 对于监视器 m 的解锁与所有后续操作对于 m 的加锁 `同步`(之前的操作保持可见)
+◆对 volatile变量v的写入，与所有其他线程后续对v的读同步
+
+◆ `启动` 线程的操作与线程中的第一个操作同步
+◆ 对于每个属性写入默认值(0， false, null)与每个线程对其进行的操作同步
+◆ 线程 T1的最后操作与线程T2发现线程T1已经结束同步。( isAlive ,join可以判断线程是否终结)
+◆ 如果线程 T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步通过抛出*InterruptedException*异常，或者调用*Thread.interrupted*或*Thread.isInterrupted*
+
+- 不允许read/load、store/write操作之一单独出现
+不允许一个变量从主内存读取了但工作内存不接收，或从工作内存发起回写但主内存不接收
+- 不允许一个线程丢弃它的最近的assign
+即变量在工作内存中改变(为工作内存变量赋值)后必须把该变化同步回主内存
+- 新变量只能在主内存“诞生”，不允许在工作内存直接使用一个未被初始化(load或assign)的变量
+换话说就是一个变量在实施use，store之前，必须先执行过assign和load
+- 如果一个变量事先没有被load锁定，则不允许对它执行unlock，也不允许去unlock一个被其它线程锁定的变量
+- 对一个变量执行unloack前，必须把此变量同步回主内存中(执行store，write)
+
+# 4 volatile 关键字
 - 可见性问题
 让一个线程对共享变量的修改，能够及时的被其他线程看到。
 
-根据JMM中规定的happen before和同步原则：
+根据JMM中规定的happen before和同步原则:
 对某个volatile字段的写操作happens- before每个后续对该volatile字段的读操作。
 对volatile变量v的写入，与所有其他线程后续对v的读同步
 
-要满足这些条件，所以volatile关键字就有这些功能：
+要满足这些条件，所以volatile关键字就有这些功能:
 - 禁止缓存;
 [volatile变量的访问控制符会加个**ACC_VOLATILE**](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5)
 - 对volatile变 量相关的指令不做重排序
 
-**`volatile`** 变量可以被看作是一种 **"轻量的 `synchronized`**，可算是JVM提供的最轻量级的同步机制。
+**`volatile`** 变量可以被看作是一种 **"轻量的 `synchronized`**
+可以说是JVM提供的最轻量级的同步机制
 
-当一个变量定义为volatile后，可以保证此变量对所有线程的可见性。
-# 2 原子性(Atomicity)
+当一个变量定义为volatile后
+- 保证此变量对所有线程的可见性
+# 4 原子性(Atomicity)
 一次只允许一个线程持有某锁，一次只有一个线程能使用共享数据
 
 由JMM直接保证的原子性变量操作包括read、load、use、assign、store和write六个，大致可以认为基础数据类型的访问读写是原子性的
@@ -30,9 +192,9 @@
 如果应用场景需要一个更大范围的原子性保证，JMM还提供了lock和unlock操作来满足这种需求，尽管虚拟机未把lock与unlock操作直接开放给用户使用，但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐匿地使用这两个操作，这两个字节码指令反映到Java代码中就是同步块synchronized关键字，因此在synchronized块之间的操作也具备原子性
 
 # 5  可见性(Visibility)
-当一个线程修改了线程共享变量的值，其它线程能够立即得知这个修改。
+当一个线程修改了线程共享变量的值，其它线程能够立即得知这个修改
 
-由于现代可共享内存的多处理器架构可能导致一个线程无法马上看到另一个线程操作产生的结果。所以 Java 内存模型规定了 JVM 的一种最小保证：什么时候写入一个变量对其他线程可见。
+由于现代可共享内存的多处理器架构可能导致一个线程无法马上（甚至永远）看到另一个线程操作产生的结果。所以 Java 内存模型规定了 JVM 的一种最小保证：什么时候写入一个变量对其他线程可见。
 
 在现代可共享内存的多处理器体系结构中每个处理器都有自己的缓存，并周期性的与主内存协调一致。假设线程 A 写入一个变量值 V，随后另一个线程 B 读取变量 V 的值
 在下列情况下，线程 B 读取的值可能不是线程 A 写入的最新值：
@@ -40,12 +202,13 @@
 - 执行线程 A 的处理器把变量 V 缓存到自己的缓存中，但还没有同步刷新到主内存中去。
 - 执行线程 B 的处理器的缓存中有变量 V 的旧值。
 
-JMM通过在变量修改后将新值同步回主内存，在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性，无论是普通变量还是volatile变量都是如此。
+JMM通过在变量修改后将新值同步回主内存，在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方法来实现可见性,无论是普通变量还是volatile变量都是如此
 
-普通变量与volatile变量的区别：
-volatile保证了新值能`立即同步`到主内存，以及每使用前`立即从内存刷新`。因此volatile保证了线程操作时变量的可见性，而普通变量则不能保证。
+普通变量与volatile变量的区别是
+`volatile的特殊规则保证了新值能立即同步到主内存，以及每使用前立即从内存刷新`
+因此volatile保证了线程操作时变量的可见性，而普通变量则不能保证
 
-除了volatile，Java还有两个关键字能**实现可见性**：
+除了volatile，Java还有两个关键字能**实现可见性**
 ### synchronized
 由“对一个变量执行`unlock`前，必须先把此变量同步回主内存中(执行`store`和`write`)”这条规则获得的
 ### final
@@ -320,50 +483,8 @@ A和C之间存在数据依赖关系，同时B和C之间也存在数据依赖关
 这个问题有时候被称为“字分裂(word tearing)”,在单独更新单个字节有难度的处理器上，就需要寻求其它方式了。
 基本不需要考虑这个，了解就好。
 
-```bash
-JAVA代码层级 - volatile
-JVM层级 - JSR
-os - 具体实现
-```
-# 内存屏障
-## JSR的内存屏障(JVM 规范)
-`这只是 JVM 层级的要求,非底层硬件的具体实现！`
-
-- 在 volatile 读写前后都加上屏障
-![](https://img-blog.csdnimg.cn/20200405201806565.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-### LoadLoad屏障
-对于这样的语句Load1; Loadload; Load2,
-在Load2及后续读取操作要读取的数据被访问前，保证Load1要读取的数据被读取完毕
-
-### StoreStore屏障
-对于这样的语句Store1; StoreStore; Store2,
-在Store2及后续写入操作执行前，保证Store1的写入操作对其它处理器可见。
-### LoadStore屏障
-对于这样的语甸oad1; LoadStore; Store2,
-在Store2及后续写入操作被刷出前，保证Load1要读取的数据被读取完毕。
-### Storeload屏障
-对于这样的语句Store1; StoreL oad; Ioad2,
-在Load2及后续所有读取操作执行前，保证Store1的写入对所有处理器可见。
-
-## JVM 层面 volatile的实现细节
-
-![在这里插入图片描述](https://img-blog.csdnimg.cn/20200424205227180.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-## x86 CPU 内存屏障 - 原语级别实现
-之所以JVM不直接使用这些指令,是因为并非所有 cpu 都支持,但是所有 cpu 都支持 lock 指令!
-lock 指令直接锁定总线,肯定直接禁止了重排序,因此 JVM是调用了该指令,简单暴力!
-![](https://img-blog.csdnimg.cn/20200405202125331.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- sfence
-在sfence指令前的写操作当必须在sfence指令后的写操作前完成
-- lfence
-在Ifence指令前的读操作当必须在Ifence指令后的读操作前完成
-- mfence
-在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成。
-
-有序性保障：intel lock 汇编指令
-原子指令，如x86上的lock指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序，甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序
-
-
 
+# 7 内存屏障
 处理器提供了两个内存屏障指令(Memory Barrier)用于解决上述的两个问题:
 ## 7.1 指令分类
 - 写内存屏障(Store Memory Barrier) 
@@ -385,34 +506,38 @@ Java提供了volatile和synchronized保证线程之间操作的有序性
 volatile本身就包含了禁止指令重排序的语义
 synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则来获得的，这个规则决定了持有同一个锁的两个同步块只能串行地进入。
 
-## 7.3 Happens-beofre 先行发生原则（JVM 规范）
-这八种不能指令重排序
-如果JMM中所有的有序性都只靠volatile和synchronized，那么有一些操作将会变得很繁琐，但我们在编写Java并发代码时并没有感到这一点，这是因为Java语言中有一个`先行发生(Happen-Before)`原则。它是判断数据是否存在竞争，线程是否安全的主要依赖。
+## 7.3 Happens-beofre 先行发生原则
+如果JMM中所有的有序性都只靠volatile和synchronized，那么有一些操作将会变得很繁琐，但我们在编写Java并发代码时并没有感到这一点，这是因为Java语言中有一个`先行发生(Happen-Before)`原则
 
-- 先行发生原则
-JMM中定义的两项操作之间的依序关系。
+这个原则非常重要，它是判断数据是否存在竞争，线程是否安全的主要依赖。
 
-`happens- before关系` 主要是强调两个有冲突的动作之间的顺序，以及定义数据争用的发生时机。
+- 先行发生原则是指
+JMM中定义的两项操作之间的依序关系
 
-如果操作A先行发生于操作B，就是在说发生B前，A产生的影响能被B观察到，“影响”包含了修改内存中共享变量的值、发送了消息、调用了方法等。案例如下：
-```java
-// 线程A中执行  
+`happens- before关系` 主要用于强调两个有冲突的动作之间的顺序，以及定义数据争用的发生时机
+
+
+
+如果说操作A先行发生于操作B，就是在说发生B前，A产生的影响能被B观察到，“影响”包含了修改内存中共享变量的值、发送了消息、调用了方法等。意味着什么呢？如下例：
+```
+//线程A中执行  
 i = 1;  
 
-// 线程B中执行  
+//线程B中执行  
 j = i;  
 
-// 线程C中执行  
+//线程C中执行  
 i = 2;
 ```
-下面是JMM下一些”天然的“先行发生关系，无须任何同步器协助就已经存在，可以在编码中直接使用。
-如果两个操作之间的关系不在此列，并且无法从下列规则推导出来的话，它们就没有顺序性保障，虚拟机可以对它们进行随意重排序。
+下面是JMM下一些”天然的“先行发生关系，无须任何同步器协助就已经存在，可以在编码中直接使用
+如果两个操作之间的关系不在此列，并且无法从下列规则推导出来的话，它们就没有顺序性保障，虚拟机可以对它们进行随意地重排序
+
+`具体的虚拟机实现`，有必要确保以下原则的成立
 
-`具体的虚拟机实现`，有必要确保以下原则的成立：
 - 程序次序规则(Pragram Order Rule)
 在一个线程内，按照代码顺序，书写在前面的操作先行发生于书写在后面的操作。准确地说应该是控制流顺序而不是程序代码顺序，因为要考虑分支、循环结构。
 - 对象锁(监视器锁)法则(Monitor Lock Rule )
-某个管程(也叫做对象锁，监视器锁) 上的unlock动作happens-before同一个管程上后续的lock动作 。这里必须强调的是同一个锁，而”后面“是指时间上的先后。
+某个 管程(也叫做对象锁,监视器锁) 上的unlock动作happens-before同一个管程上后续的lock动作 。这里必须强调的是同一个锁，而”后面“是指时间上的先后。
 - volatile变量规则(Volatile Variable Rule)
 对某个volatile字段的写操作happens- before每个后续对该volatile字段的读操作，这里的”后面“同样指时间上的先后顺序。
 - 线程启动规则(Thread Start Rule)
@@ -426,7 +551,7 @@ i = 2;
 - 传递性(Transitivity)
 如果操作A先行发生于操作B，操作B先行发生于操作C，那就可以得出操作A先行发生于操作C的结论
 
-一个操作”时间上的先发生“不代表这个操作会是”先行发生“，那如果一个操作”先行发生“是否就能推导出这个操作必定是”时间上的先发生“呢？也是不成立的，一个典型的例子就是指令重排序。
+一个操作”时间上的先发生“不代表这个操作会是”先行发生“，那如果一个操作”先行发生“是否就能推导出这个操作必定是”时间上的先发生“呢？也是不成立的，一个典型的例子就是指令重排序
 所以时间上的先后顺序与先行发生原则之间基本没有什么关系，所以衡量并发安全问题一切必须以先行发生原则为准。
 
 ## 7.4 作用
@@ -446,7 +571,12 @@ i = 2;
 > **在每个volatile写操作前插入StoreStore屏障，在写操作后插入StoreLoad屏障
 > 在每个volatile读操作前插入LoadLoad屏障，在读操作后插入LoadStore屏障**
 
-由于内存屏障的作用，避免了volatile变量和其它指令重排序、线程之间实现了通信，使得volatile表现出了锁的特性。
+由于内存屏障的作用，避免了volatile变量和其它指令重排序、线程之间实现了通信，使得volatile表现出了锁的特性
+
+
+
 
 # 总结
-看到了现代CPU不断演进，在程序运行优化中做出的努力。不同CPU厂商所付出的人力物力成本，最终体现在不同CPU性能差距上。而Java就随即推出了大量保证线程安全的机
\ No newline at end of file
+看到了现代CPU不断演进，在程序运行优化中做出的努力。
+不同CPU厂商所付出的人力物力成本，最终体现在不同CPU性能差距上。
+而Java就随即推出了大量保证线程安全的机制
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213\345\256\236\346\210\230 - \347\272\277\347\250\213\351\200\232\344\277\241.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213\345\256\236\346\210\230 - \347\272\277\347\250\213\351\200\232\344\277\241.md"
new file mode 100644
index 0000000000..8aa6e4f3ea
--- /dev/null
+++ "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213\345\256\236\346\210\230 - \347\272\277\347\250\213\351\200\232\344\277\241.md"	
@@ -0,0 +1,76 @@
+要想实现多个线程之间的协同，如:线程执行先后顺序、获取某个线程执行的结果等等。
+涉及到线程之间相互通信，分为下面四类:
+
+# 1 文件共享
+![](https://img-blog.csdnimg.cn/20191008023446691.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+# 2 网络共享
+socket编程问题,非本文重点,不再赘述
+
+# 3 共享变量
+![](https://img-blog.csdnimg.cn/20191008023621435.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+# 4 线程协作 - JDK API
+细分为: ~~suspend/resume~~ 、 wait/notify、 park/unpark
+
+JDK中对于需要多线程协作完成某一任务的场景，提供了对应API支持。
+多线程协作的典型场景是:生产者-消费者模型。(线程阻塞、 线程唤醒)
+
+示例:线程1去买包子，没有包子，则不再执行。线程-2生产出包子，通知线程-1继续执行。
+![](https://img-blog.csdnimg.cn/2019100802383347.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+## 4.1 API - 被弃用的suspend和resume
+作用:调用suspend挂起目标线程，通过resume可以恢复线程执行
+![](https://img-blog.csdnimg.cn/20191008025019964.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+被弃用的主要原因是，容易写出
+### 死锁代码
+- 同步代码中使用
+![](https://img-blog.csdnimg.cn/2019100802572960.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191008030038265.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- 先后顺序:suspend比resume后执行
+![](https://img-blog.csdnimg.cn/2019100803021180.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+![](https://img-blog.csdnimg.cn/20191008030515787.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+所以用wait/notify和park/unpark机制对它进行替代
+
+## 4.2 wait/notify
+这些方法只能由**同一对象锁**的持有者线程调用，也就是写在同步块里面，否则会抛IllegalMonitorStateException
+
+**wait** 方法导致当前线程等待，加入该对象的等待集合中，并且**放弃当前持有的对象锁**
+
+**notify**/**notifyAll** 方法唤醒一个 或所有正在等待这个对象锁的线程。
+
+> 虽然wait会自动解锁，但是**对顺序有要求**，如果在notify被调用之后， 才开始wait方法的调用，线程会永远处于**WAITING**状态。
+
+-  正常使用
+![](https://img-blog.csdnimg.cn/20191008031659529.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- 死锁
+![](https://img-blog.csdnimg.cn/20191008032042659.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191008032138968.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- 小结
+![](https://img-blog.csdnimg.cn/20191008031954289.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+## 4.3 park/unpark
+线程调用park则等待“许可”，unpark方法为指定线程提供“许可(permit)” 。
+
+**不要求park和unpark方法的调用顺序**
+
+多次调用unpark之后，再调用park, 线程会直接运行。
+**但不会叠加**，即连续多次调用park方法，第一次会拿到“许可”直接运行，后续调
+用会进入等待。
+
+- 正常![](https://img-blog.csdnimg.cn/20191008033156471.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- 死锁
+![](https://img-blog.csdnimg.cn/20191008033327469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+# 5 伪唤醒
+** 之前代码中用if语句来判断，是否进入等待状态，是错误的! **
+
+官方建议`应该在循环中检查等待条件`，原因是处于等待状态的线程可能会收到**错误警报和伪
+唤醒**，如果不在循环中检查等待条件，程序就会在没有满足结束条件的情况下退出。
+
+伪唤醒是指线程并非因为notify、notifyall、 unpark等 api调用而唤醒，是更底层原因导致的。
+![](https://img-blog.csdnimg.cn/20191008034349825.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+#  6 总结
+ 涉及很多JDK多线程开发工具类及其底层实现的原理
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\232\204\347\272\277\347\250\213\345\260\201\351\227\255\344\270\216ThreadLocal.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213\345\256\236\346\210\230 --- \347\272\277\347\250\213\345\260\201\351\227\255\344\270\216ThreadLocal.md"
similarity index 92%
rename from "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\232\204\347\272\277\347\250\213\345\260\201\351\227\255\344\270\216ThreadLocal.md"
rename to "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213\345\256\236\346\210\230 --- \347\272\277\347\250\213\345\260\201\351\227\255\344\270\216ThreadLocal.md"
index f6f2a8eb6f..9ddb5a3ff8 100644
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\347\232\204\347\272\277\347\250\213\345\260\201\351\227\255\344\270\216ThreadLocal.md"
+++ "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\351\253\230\346\200\247\350\203\275\347\274\226\347\250\213\345\256\236\346\210\230 --- \347\272\277\347\250\213\345\260\201\351\227\255\344\270\216ThreadLocal.md"	
@@ -1,4 +1,5 @@
 # 1 线程封闭
+
 多线程访问共享可变数据时，涉及到线程间数据同步的问题。并不是所有时候，都要用到
 共享数据，所以线程封闭概念就提出来了。
 
@@ -10,6 +11,7 @@
 那么该对象就算非线程安全,也不会出现任何并发安全问题.
 
 ## 1.1 栈封闭
+
 局部变量的固有属性之一就是封闭在线程中。
 它们位于执行线程的栈中，其他线程无法访问这个栈
 
@@ -21,26 +23,32 @@
 竞争条件被彻底消除了，在并发模式下是绝对安全的变量。
 
 - 用法
+- 
 ```java
 ThreadLocal<T> var = new ThreadLocal<T>();
 ```
+
 会自动在每一个线程上创建一个T的副本，副本之间彼此独立，互不影响。
 可以用 ThreadLocal 存储一些参数， 以便在线程中多个方法中使用，用来代替方法传参的做法。
 
 实例
 ![](https://img-blog.csdnimg.cn/20191009013555646.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-`ThreadLocal`内部维护了一个Map,Map的key是每个线程的名称,Map的value就是我们要封闭的对象.
+`ThreadLocal`内部维护了一个Map,Map的key是每个线程的名称,Map的值就是我们要封闭的对象.
 每个线程中的对象都对应着Map中一个值,也就是`ThreadLocal`利用Map实现了对象的线程封闭.
 
+
+
+
 对于CS游戏,开始时,每个人能够领到一把枪,枪把上有三个数字:子弹数、杀敌数、自己的命数，为其设置的初始值分别为1500、0、10.
-设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢?如果每个线程都写死这三个值,万一将初始子弹数统一改成 1000发呢?
-如果共享,那么线程之间的并发修改会导致数据不准确.
-能不能构造这样一个对象,将这个对象设置为共享变量, 统一设置初始值, 但是每个线程对这个值的修改都是互相独立的.这个对象就是ThreadLocal
 
+设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢?
+如果每个线程都写死这三个值,万一将初始子弹数统一改成 1000发呢?
+如果共享,那么线程之间的并发修改会导致数据不准确.
+能不能构造这样一个对象,将这个对象设置为共享变量,统一设置初始值,但是每个线程对这个值的修改都是互相独立的.这个对象就是ThreadLocal
 >注意不能将其翻译为线程本地化或本地线程
->英语恰当的名称应该叫作:CopyValueIntoEveryThread
+英语恰当的名称应该叫作:CopyValueIntoEveryThread
 
-- 示例代码
+示例代码
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTA2MWI2N2QzYjdkZDAyNjcucG5n?x-oss-process=image/format,png)
 
 > 实在难以理解的，可以理解为，JVM维护了一个Map<Thread, T>,每个线程要用这个T的时候，用当前的线程去Map里面取。仅作为一个概念理解
@@ -49,17 +57,16 @@ ThreadLocal<T> var = new ThreadLocal<T>();
 该示例中,无 set 操作,那么初始值又是如何进入每个线程成为独立拷贝的呢?
 首先,虽然`ThreadLocal`在定义时重写了`initialValue()` ,但并非是在`BULLET_ NUMBER_ THREADLOCAL`对象加载静态变量的时候执行;
 而是每个线程在`ThreadLocal.get()`时都会执行到;
-
-- 其源码如下
+其源码如下
 ![ThreadLocal # get()](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTdkY2JjNjZjOTY5YzUzMDcucG5n?x-oss-process=image/format,png)
+
 每个线程都有自己的`ThreadLocalMap`;
 如果`map ==null`,则直接执行`setInitialValue()`;
 如果 map 已创建,就表示 Thread 类的`threadLocals` 属性已初始化完毕;
 如果 `e==null`,依然会执行到`setinitialValue()`
-
-- `setinitialValue() `的源码如下:
+`setinitialValue() `的源码如下:
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTQ4MzcxODdjNTk4NjIwN2EucG5n?x-oss-process=image/format,png)
-这是一个保护方法，CsGameByThreadLocal中初始化ThreadLocal对象时已覆写value = initialValue() 
+这是一个保护方法，CsGameByThreadLocal中初始化ThreadLocal对象时已覆写value = initialValue() ;
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWM0NDI3Mzk3ZDBjMDRkZmMucG5n?x-oss-process=image/format,png)
 ` getMap `的源码就是提取线程对象t的ThreadLocalMap属性: t. threadLocals.
 
@@ -115,18 +122,18 @@ Entry 对象源码如下
 即`ThreadLocal`,它通常用于同一个线程内,跨类、跨方法传递数据;
 如果没有ThreadLocal,那么相互之间的信息传递,势必要靠返回值和参数,这样无形之中,有些类甚至有些框架会互相耦合;
 通过将Thread构造方法的最后一个参数设置为true,可以把当前线程的变量继续往下传递给它创建的子线程
-```java
+```
 public Thread (ThreadGroup group, Runnable target, String name,long stackSize, boolean inheritThreadLocals) [
    this (group, target, name,  stackSize, null, inheritThreadLocals) ;
 }
 ```
 parent为其父线程
-```java
+```
 if (inheritThreadLocals && parent. inheritableThreadLocals != null)
       this. inheritableThreadLocals = ThreadLocal. createInheritedMap (parent. inheritableThreadLocals) ;
 ```
 `createlnheritedMap()`其实就是调用`ThreadLocalMap`的私有构造方法来产生一个实例对象,把父线程中不为`null`的线程变量都拷贝过来
-```java
+```
 private ThreadLocalMap (ThreadLocalMap parentMap) {
     // table就是存储
     Entry[] parentTable = parentMap. table;
@@ -152,10 +159,9 @@ private ThreadLocalMap (ThreadLocalMap parentMap) {
 很多场景下可通过`ThreadLocal`来透传全局上下文的;
 比如用`ThreadLocal`来存储监控系统的某个标记位,暂且命名为traceld.
 某次请求下所有的traceld都是一致的,以获得可以统一解析的日志文件;
-但在实际开发过程中,发现子线程里的traceld为null,跟主线程的traceld并不一致,所以这就需要刚才说到的`InheritableThreadLocal`来解决父子线程之间共享线程变量的问题,使整个连接过程中的traceld一致。
-
+但在实际开发过程中,发现子线程里的traceld为null,跟主线程的traceld并不一致,所以这就需要刚才说到的`InheritableThreadLocal`来解决父子线程之间共享线程变量的问题,使整个连接过程中的traceld一致.
 示例代码如下
-```java
+```
 import org.apache.commons.lang3.StringUtils;
 
 /**
@@ -202,25 +208,29 @@ public class RequestProcessTrace {
 推荐使用` ThreadLocal`,让每个线程单独拥有这个对象.
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWVmMWM2OGY0ODUzYWYwNWQucG5n?x-oss-process=image/format,png)
 
+
 # ThreadLocal的副作用
 为了使线程安全地共享某个变量，JDK给出了`ThreadLocal`.
 但`ThreadLocal`的主要问题是会产生脏数据和内存泄漏;
 这两个问题通常是在线程池的线程中使用ThreadLocal引发的，因为线程池有线程复用和内存常驻两是在线程池的线程中使用ThreadLocal 引发的，因为线程池有线程复用和内存常驻两个特点
 
-## 脏数据
-线程复用会产生脏数据。
-由于线程池会重用 `Thread` 对象，与 `Thread` 绑定的静态属性 `ThreadLocal` 变量也会被重用。
-如果在实现的线程run()方法中不显式调用`remove()`清理与线程相关的`ThreadLocal`信息，那么若下一个线程不调用`set()`，就可能`get()` 到重用的线程信息。包括`ThreadLocal`所关联的线程对象的**value**值。
-
-### 脏读案例
-比如，用户A下单后没有看到订单记录，而用户B却看到了用户A的订单记录。通过排查发现是由于 session 优化引发。
-在原来的请求过程中，用户每次请求Server，都需要通过 sessionId 去缓存里查询用户的session信息，这样无疑增加了一次调用。
-因此工程师决定采用某框架来缓存每个用户对应的`SecurityContext`，它封装了session 相关信息。优化后虽然会为每个用户新建一个 session 相关的上下文，但由于`Threadlocal`没有在线程处理结束时及时`remove()`。在高并发场景下,线程池中的线程可能会读取到上一个线程缓存的用户信息。
-
+## 1 脏数据
+线程复用会产生脏数据;
+由于线程池会重用 `Thread` 对象,与 `Thread` 绑定的静态属性 `ThreadLoca` l变量也会被重用.
+如果在实现的线程run()方法中不显式调用`remove()`清理与线程相关的`ThreadLocal`信息,那么若下一个线程不调用`set()`,就可能`get()` 到重用的线程信息;
+包括`ThreadLocal`所关联的线程对象的**value**值.
+
+脏读问题其实十分常见.
+比如,用户A下单后没有看到订单记录,而用户B却看到了用户A的订单记录.
+通过排查发现是由于 session 优化引发.
+在原来的请求过程中,用户每次请求Server,都需要通过 sessionId 去缓存里查询用户的session信息,这样无疑增加了一次调用.
+因此,工程师决定采用某框架来缓存每个用户对应的`SecurityContext`, 它封装了session 相关信息.
+优化后虽然会为每个用户新建一个 session 相关的上下文,但由于`Threadlocal`没有在线程处理结束时及时`remove()`;
+在高并发场景下,线程池中的线程可能会读取到上一个线程缓存的用户信息.
 - 示例代码
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTNhN2IyNTg1ZGQ1NTljMjkucG5n?x-oss-process=image/format,png)
-- 输出结果![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTI5ZTI0NTA2ZmI3YmJjYWEucG5n?x-oss-process=image/format,png)
-##  2 内存泄漏
+![输出结果](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTI5ZTI0NTA2ZmI3YmJjYWEucG5n?x-oss-process=image/format,png)
+ ##  2 内存泄漏
 在源码注释中提示使用static关键字来修饰`ThreadLocal`.
 在此场景下,寄希望于`ThreadLocal`对象失去引用后,触发弱引用机制来回收`Entry`的`Value`就不现实了.
 在上例中,如果不进行`remove()`,那么当该线程执行完成后,通过`ThreadLocal`对象持有的String对象是不会被释放的.
@@ -228,6 +238,9 @@ public class RequestProcessTrace {
 - **以上两个问题的解决办法很简单
 每次用完ThreadLocal时,及时调用`remove()`清理**
 
+
+
+
 # What is ThreadLocal
 该类提供了线程局部 (thread-local) 变量;
 这些变量不同于它们的普通对应物,因为访问某变量（通过其 get /set 方法）的每个线程都有自己的局部变量,它独立于变量的初始化副本.
@@ -237,7 +250,7 @@ public class RequestProcessTrace {
 一个以`ThreadLocal`对象为键、任意对象为值的存储结构;
 有点像`HashMap`,可以保存"key : value"键值对,但一个`ThreadLocal`只能保存一个键值对,各个线程的数据互不干扰.
 该结构被附带在线程上,也就是说一个线程可以根据一个`ThreadLocal`对象查询到绑定在这个线程上的一个值.
-```java
+```
 ThreadLocal<String> localName = new ThreadLocal();
 localName.set("JavaEdge");
 String name = localName.get();
@@ -301,7 +314,7 @@ String name = localName.get();
 # 内存泄露
 ThreadLocal可能导致内存泄漏，为什么？
 先看看Entry的实现： 
-```java
+```
 static class Entry extends WeakReference<ThreadLocal<?>> {
     /** The value associated with this ThreadLocal. */
     Object value;
@@ -321,14 +334,14 @@ static class Entry extends WeakReference<ThreadLocal<?>> {
 既然发现有内存泄露的隐患，自然有应对策略，在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象，这样对应的value就没有GC Roots可达了，下次GC的时候就可以被回收，当然如果调用remove方法，肯定会删除对应的Entry对象。
 
 如果使用ThreadLocal的set方法之后，没有显示的调用remove方法，就有可能发生内存泄露，所以养成良好的编程习惯十分重要，使用完ThreadLocal之后，记得调用remove方法。
-```java
-ThreadLocal<String> localName = new ThreadLocal();
-try {
-    localName.set("JavaEdge");
-    // 其它业务逻辑
-} finally {
-    localName.remove();
-}
+```
+    ThreadLocal<String> localName = new ThreadLocal();
+    try {
+        localName.set("JavaEdge");
+        // 其它业务逻辑
+    } finally {
+        localName.remove();
+    }
 ```
 
 # 题外小话
@@ -346,7 +359,7 @@ ThreadLocal实例是作为map的key来使用的.
 那么多个线程的get()取得的还是这个共享对象本身，还是有并发访问问题。
 
 # Hibernate中典型的 ThreadLocal 应用
-```java
+```
 private static final ThreadLocal threadSession = new ThreadLocal();  
   
 public static Session getSession() throws InfrastructureException {  
@@ -393,7 +406,7 @@ threadSession 作为这个值的key，要取得这个 session 可以通过thread
 
 可以看到ThreadLocal类中的变量只有这3个int型：
 
-```java
+```
 private final int threadLocalHashCode = nextHashCode();  
 private static AtomicInteger nextHashCode =
         new AtomicInteger();
@@ -406,11 +419,11 @@ private static final int HASH_INCREMENT = 0x61c88647;
 - nextHashCode 表示了即将分配的下一个ThreadLocal实例的threadLocalHashCode 的值
 
 看一下创建一个ThreadLocal实例即new ThreadLocal()时做了哪些操作，构造方法`ThreadLocal()`里什么操作都没有，唯一的操作是这句
-```java
+```
 private final int threadLocalHashCode = nextHashCode();  
 ```
 那么nextHashCode()做了什么呢
-```java
+```
 private static int nextHashCode() {
         return nextHashCode.getAndAdd(HASH_INCREMENT);
     }
@@ -421,12 +434,12 @@ private static int nextHashCode() {
 ThreadLocal类主要是作为工具类来使用，那么set()进去的对象是放在哪儿的呢？
 
 看一下上面的set()方法，两句合并一下成为 
-```java
+```
 ThreadLocalMap map = Thread.currentThread().threadLocals;  
 ```
 这个ThreadLocalMap 类是ThreadLocal中定义的内部类，但是它的实例却用在Thread类中： 
 
-```java
+```
 public class Thread implements Runnable {  
     ......  
   
@@ -438,11 +451,11 @@ public class Thread implements Runnable {
 ```
 再看这句：
 
-```java
+```
 if (map != null)  
     map.set(this, value);  
 ```
 也就是将该ThreadLocal实例作为key，要保持的对象作为值，设置到当前线程的ThreadLocalMap 中，get()方法同样看了代码也就明白了.
 
-> 参考
-> - 《码出高效：Java开发手册》
\ No newline at end of file
+# 参考
+《码出高效：Java开发手册》
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/\345\217\257\351\207\215\345\205\245\350\257\273\345\206\231\351\224\201ReentrantReadWriteLock.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/\345\217\257\351\207\215\345\205\245\350\257\273\345\206\231\351\224\201ReentrantReadWriteLock.md"
deleted file mode 100644
index 15bf8bc6e0..0000000000
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/\345\217\257\351\207\215\345\205\245\350\257\273\345\206\231\351\224\201ReentrantReadWriteLock.md"
+++ /dev/null
@@ -1,87 +0,0 @@
-# 1 读写锁，一个用于只读，一个用于写入
-只要没有writer，读锁可以由多个reader线程同时保持。
-写锁是独占的。
-
-- 互斥锁一次只允许一个线程访问共享数据，哪怕是只读
-- 读写锁允许对共享数据进行更高性能的并发访问
-     - 对于写操作，一次只有一个线程（write线程）可修改共享数据
-     - 对于读操作，允许任意数量的线程同时读取
-
-与互斥锁相比，使用读写锁能否提升性能则取决于读写操作期间`读取数据相对于修改数据的频率，以及数据的争用,即在同一时间试图对该数据执行读取或写入操作的线程数`。
-
-读写锁适用于`读多写少`的场景。
-
-# 2 可重入读写锁 ReentrantReadWriteLock
-可重入锁，又名递归锁。
-## 2.1 属性
-`ReentrantReadWriteLock` 基于 `AbstractQueuedSynchronizer `实现，具有如下属性
-
-### 获取顺序
-此类不会将读/写者优先强加给锁访问的排序。
-
-- 非公平模式（默认）
-连续竞争的非公平锁可能无限期推迟一或多个读或写线程，但吞吐量通常要高于公平锁。 
-  
-- 公平模式
-线程利用一个近似到达顺序的策略来竞争进入。
-当释放当前持有的锁时，可以为等待时间最长的单个writer线程分配写锁，如果有一组等待时间大于所有正在等待的writer线程的reader，将为该组分配读锁。
-试图获得公平写入锁的非重入的线程将会阻塞，除非读取锁和写入锁都自由（这意味着没有等待线程）。
-### 重入
-此锁允许reader和writer按照 ReentrantLock 的样式重新获取读/写锁。在写线程保持的所有写锁都已释放后，才允许重入reader使用读锁
-writer可以获取读取锁，但reader不能获取写入锁。
-### 锁降级
-重入还允许从写锁降级为读锁，实现方式是：先获取写锁，然后获取读取锁，最后释放写锁。但是，`从读取锁升级到写入锁是不可能的`。
-### 锁获取的中断
-读锁和写锁都支持锁获取期间的中断。
-### Condition 支持
-写锁提供了一个 Condition 实现，对于写锁来说，该实现的行为与 `ReentrantLock.newCondition()` 提供的 Condition 实现对 ReentrantLock 所做的行为相同。当然，`此 Condition 只能用于写锁`。
-读锁不支持 Condition，readLock().newCondition() 会抛UnsupportedOperationException
-### 监测
-此类支持一些确定是读锁还是写锁的方法。这些方法设计用于监视系统状态，而不是同步控制。
-
-# 3 AQS 
-- 记录当前加锁的是哪个线程，初始化状态下，这个变量是null![](https://img-blog.csdnimg.cn/20191018234504480.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-接着线程1跑过来调用ReentrantLock#lock()尝试加锁：直接用CAS操作将state值从0变为1。
-如果之前没人加过锁，那么state的值肯定是0，此时线程1就可以加锁成功。
-一旦线程1加锁成功了之后，就可以设置当前加锁线程是自己。
-
-- 线程1跑过来加锁的一个过程
-![](https://img-blog.csdnimg.cn/20191018235617253.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-加锁线程变量
-
-其实每次线程1可重入加锁一次，会判断一下当前加锁线程就是自己，那么他自己就可以可重入多次加锁，每次加锁就是把state的值给累加1，别的没啥变化。
-
-接着，如果线程1加锁了之后，线程2跑过来加锁会怎么样呢？
-
-## 锁的互斥是如何实现的？
-线程2跑过来一下看到 state≠0，所以CAS将state 01=》失败，因为state的值当前为1，已有人加锁了！
-
-接着线程2会看一下，是不是自己之前加的锁啊？当然不是了，“加锁线程”这个变量明确记录了是线程1占用了这个锁，所以线程2此时就是加锁失败。
-![](https://img-blog.csdnimg.cn/20191019000850859.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-接着，线程2会将自己放入AQS中的一个等待队列，因为自己尝试加锁失败了，此时就要将自己放入队列中来等待，等待线程1释放锁之后，自己就可以重新尝试加锁了。
-![](https://img-blog.csdnimg.cn/20191019001308340.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-接着，线程1在执行完自己的业务逻辑代码之后，就会释放锁：将AQS内的state变量的值减1，若state值为0，则彻底释放锁，会将“加锁线程”变量也设置为null。
-![](https://img-blog.csdnimg.cn/20191019001520803.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-接下来，会从等待队列的队头唤醒线程2重新尝试加锁。线程2现在就重新尝试加锁：CAS将state从0变为1，此时就会成功，成功之后代表加锁成功，就会将state设置为1。还要把“加锁线程”设置为线程2自己，同时线程2自己就从等待队列中出队了。
-![](https://img-blog.csdnimg.cn/20191019001626741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-- ReentrantLock，它是可重入的独占锁，内部的 Sync 类实现了 tryAcquire(int)、tryRelease(int) 方法，并用状态的值来表示重入次数，加锁或重入锁时状态加 1，释放锁时状态减 1，状态值等于 0 表示锁空闲。
-
-- CountDownLatch，它是一个关卡，在条件满足前阻塞所有等待线程，条件满足后允许所有线程通过。内部类 Sync 把状态初始化为大于 0 的某个值，当状态大于 0 时所有wait线程阻塞，每调用一次 countDown 方法就把状态值减 1，减为 0 时允许所有线程通过。利用了AQS的**共享模式**。
-
-# 4 AQS只有一个状态，那么如何表示多个读锁与单个写锁
-ReentrantLock 里，状态值表示重入计数
-- 现在如何在AQS里表示每个读锁、写锁的重入次数呢
-- 如何实现读锁、写锁的公平性呢
-
-一个状态是没法既表示读锁，又表示写锁的，那就辦成两份用了!
-**状态的高位部分表示读锁，低位表示写锁**。由于写锁只有一个，所以写锁的重入计数也解决了，这也会导致写锁可重入的次数减小。
-
-由于读锁可以同时有多个，肯定不能再用辦成两份用的方法来处理了。但我们有 `ThreadLocal`，可以把线程重入读锁的次数作为值存在 `ThreadLocal `。
-
-对于公平性的实现，可以通过AQS的等待队列和它的抽象方法来控制。在状态值的另一半里存储当前持有读锁的线程数。
-- 如果读线程申请读锁，当前写锁重入次数不为 0 时，则等待，否则可以马上分配
-- 如果是写线程申请写锁，当前状态为 0 则可以马上分配，否则等待。
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/\347\224\261\344\272\216\344\270\215\347\237\245\351\201\223Java\347\272\277\347\250\213\346\261\240\347\232\204bug,\346\237\220\347\250\213\345\272\217\345\221\230\345\217\225\350\242\253\347\245\255\345\244\251.md" "b/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/\347\224\261\344\272\216\344\270\215\347\237\245\351\201\223Java\347\272\277\347\250\213\346\261\240\347\232\204bug,\346\237\220\347\250\213\345\272\217\345\221\230\345\217\225\350\242\253\347\245\255\345\244\251.md"
deleted file mode 100644
index 5efc1e0999..0000000000
--- "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/\347\224\261\344\272\216\344\270\215\347\237\245\351\201\223Java\347\272\277\347\250\213\346\261\240\347\232\204bug,\346\237\220\347\250\213\345\272\217\345\221\230\345\217\225\350\242\253\347\245\255\345\244\251.md"
+++ /dev/null
@@ -1,145 +0,0 @@
-> 说说你对线程池的理解？
-
-首先明确，池化的意义在于**缓存，创建性能开销较大的对象**，比如线程池、连接池、内存池。预先在池里创建一些对象，使用时直接取，用完就归还复用，使用策略调整池中缓存对象的数量。
-
-Java创建对象，仅是在JVM堆分块内存，但创建一个线程，却需调用os内核API，然后os要为线程分配一系列资源，成本很高，所以线程是一个重量级对象，应避免频繁创建或销毁。
-既然这么麻烦，就要避免呀，所以要使用线程池！
-
-一般池化资源，当你需要资源时，就调用申请线程方法申请资源，用完后调用释放线程方法释放资源。但JDK的线程池根本没有申请线程和释放线程的方法。
-
-那到底该如何理解它的设计思想呢？
-其实线程池的设计，采用的是**生产者-消费者模式**：
-- 线程池的使用方是生产者
-- 线程池本身是消费者
-
-以下简化代码即可显示线程池的基本原理：
-![](https://img-blog.csdnimg.cn/20210516215144285.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-JDK线程池最核心的就是ThreadPoolExecutor，看名字，它强调的是Executor，并非一般的池化资源。
-
-> 为什么都说要手动声明线程池？
-
-虽然JDK的`Executors`工具类提供的方法可快速创建线程池。
-![](https://img-blog.csdnimg.cn/20201107175307275.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-但阿里有话说：
-![](https://img-blog.csdnimg.cn/2020110717563445.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-> 弊端真的这么严重吗，newFixedThreadPool=OOM？
-
-写段测试代码：
-![ ](https://img-blog.csdnimg.cn/20210516231308315.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-执行不久，出现OOM
-```bash
-Exception in thread "http-nio-30666-ClientPoller" 
-	java.lang.OutOfMemoryError: GC overhead limit exceeded
-```
-
-- `newFixedThreadPool`线程池的工作队列直接new了一个LinkedBlockingQueue
-![](https://img-blog.csdnimg.cn/20201107180734266.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-- 但其默认构造器是一个`Integer.MAX_VALUE`长度的队列，所以很快Q满
-![](https://img-blog.csdnimg.cn/20201107180815786.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-虽然使用`newFixedThreadPool`可以固定工作线程数量，但任务队列几乎无界。若任务较多且执行较慢，队列就会快速积压，内存不够，易导致OOM。
-
-> newCachedThreadPool也等于OOM？
-
-```bash
-[11:30:30.487] [http-nio-30666-exec-1] [ERROR] [.a.c.c.C.[.[.[/].[dispatcherServlet]:175 ] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: unable to create new native thread] with root cause
-java.lang.OutOfMemoryError: unable to create new native thread 
-```
-可见OOM是因为无法创建线程，newCachedThreadPool这种线程池的最大线程数是Integer.MAX_VALUE，也可认为无上限，而其工作队列SynchronousQueue是一个没有存储空间的阻塞队列。
-![](https://img-blog.csdnimg.cn/20210516231654879.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-所以只要有请求到来，就必须找到一条工作线程处理，若当前无空闲线程就再创建一个新的。
-由于我们的任务需很长时间才能执行完成，大量任务进来后会创建大量线程。而线程是需要分配一定内存空间作为线程栈的，比如1MB，因此无限创建线程必OOM
-
-所以使用线程池，请不要抱任何侥幸，以为只是处理轻量任务，不会造成队列积压或创建大量线程！
-比如某业务一旦接受到请求，就会调用外部服务，该外部服务接口正常100ms内会响应，现在TPS过百，CachedThreadPool能稳定在占用10个左右线程情况下满足需求。
-可天有不测风云，该外部服务不可用了！而代码里调用该服务设置的超时又特别长， 比如1min，1min可能已经进成千上万请求，产生几千个任务，需几千个线程，没多久就因为无法再创建新线程，OOM！
-
-所以阿里才不建议使用Executors：
-- 要结合实际并发情况，评估线程池核心参数，确保其工作行为符合预期，关键的也就是设置**有界工作队列和数量可控的线程数**
-- 永远要为自定义的线程池设置有意义名称，以便排查问题
-因为当出现线程数量暴增、死锁、CPU负载高、线程执行异常等事故时，往往都需抓取线程栈。有意义的线程名称，就很重要。示例如下：
-![](https://img-blog.csdnimg.cn/20210518180337647.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-> 注意异常处理
-
-通过ThreadPoolExecutor#execute()提交任务时，若任务在执行的过程中出现运行时异常，会导致 **执行任务的线程** 终止。
-但要命的是，有时任务虽然异常了，但你却收不到任何通知，你还在开心摸鱼，以为任务都执行很正常。虽然线程池提供了很多用于异常处理的方法，但最稳妥和简单的方案还是捕获所有异常并具体处理：
-![](https://img-blog.csdnimg.cn/20210516222856874.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-> 线程池的线程管理
-
-还好有谷歌，一般我们直接利用guava的ThreadFactoryBuilder实现线程池线程的自定义命名即可。
-
-> 线程池的拒绝策略
-
-线程池默认的拒绝策略会抛RejectedExecutionException，这是个运行时异常，IDEA不会强制捕获，所以我们也很容易忽略它。
-对于采用何种策略，具体要看任务的重要性：
-- 若是一些不重要任务，可选择直接丢弃
-- 重要任务，可采用降级，比如将任务信息插入DB或MQ，启用一个专门用作补偿的线程池去补偿处理。所谓降级，也就是在服务无法正常提供功能的情况下，采取的补救措施。具体处理方式也看具体场景而不同。
-
-- 当线程数大于核心线程数时，线程等待keepAliveTime后还是无任务需要处理，收缩线程到核心线程数
-了解这个策略，有助于我们根据实际的容量规划需求，为线程池设置合适的初始化参数。也可通过一些手段来改变这些默认工作行为，比如：
-- 声明线程池后立即调用prestartAllCoreThreads，启动所有核心线程
-![](https://img-blog.csdnimg.cn/20210516233403439.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- 传true给allowCoreThreadTimeOut，让线程池在空闲时同样回收核心线程
-![](https://img-blog.csdnimg.cn/20210516233525879.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-> 弹性伸缩的实现
-
-线程池是先用Q存放来不及处理的任务，满后再扩容线程池。当Q设置很大时（那个 工具类），最大线程数这个参数就没啥意义了，因为队列很难满或到满时可能已OOM，更没机会去扩容线程池了。
-是否能让线程池优先开启更多线程，而把Q当成后续方案？比如我们的任务执行很慢，需要10s，若线程池可优先扩容到5个最大线程，那么这些任务最终都可以完成，而不会因为线程池扩容过晚导致慢任务来不及处理。
-
-难题在于：
-- 线程池在工作队列满时，会扩容线程池
-重写队列的offer，人为制造该队列满的条件
-- 改变了队列机制，达到最大线程后势必要触发拒绝策略
-实现一个自定义拒绝策略，这时再把任务真正插入队列
-
-Tomcat就实现了类似的“弹性”线程池。
-
-> **务必确认清楚线程池本身是不是复用的**。
-
-某服务偶尔报警线程数过多，但过一会儿又会降下来，但应用的请求量却变化不大。
-
-可以在线程数较高时抓取线程栈，发现内存中有上千个线程池，这肯定不正常！
-
-但代码里也没看到声明了线程池，最后发现原来是业务代码调用了一个类库：
-![](https://img-blog.csdnimg.cn/20201221131420308.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-该类库竟然每次都创建一个新的线程池！
-![](https://img-blog.csdnimg.cn/20201221131504744.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-newCachedThreadPool会在需要时创建必要数量的线程，业务代码的一次业务操作会向线程池提交多个慢任务，这样执行一次业务操作就会开启多个线程。如果业务操作并发量较大的话，的确有可能一下子开启几千个线程。
-
-> 那为何监控中看到线程数量会下降，而不OOM？
-
-newCachedThreadPool的核心线程数是0，而keepAliveTime是60s，所以60s后所有线程都可回收。
-
-> 那这如何修复呢？
-
-使用static字段存放线程池引用即可
-![](https://img-blog.csdnimg.cn/20201221131921296.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-> 线程池的意义在于复用，就意味着程序应该始终使用一个线程池吗？
-
-不，具体场景具体分析。
-
-比如一个 I/O 型任务，不断向线程池提交任务：向一个文件写入大量数据。线程池的线程基本一直处于忙碌状态，队列也基本满。而且由于是**CallerRunsPolicy**策略，所以当线程满队列满，任务会在提交任务的线程或调用execute方法的线程执行，所以不要认为提交到线程池的任务就一定会被**异步处理**。
-
-毕竟，若使用CallerRunsPolicy，就有可能**异步任务变同步**执行。使用CallerRunsPolicy，当线程池饱和时，计算任务会在执行Web请求的Tomcat线程执行，这时就会进一步影响到其他同步处理的线程，甚至造成整个应用程序崩溃。
-
-> 如何修正？
-
-使用单独的线程池处理这种“I/O型任务”，将线程数设置多一些！
-
-所以千万不要盲目复用别人写的线程池！因为它不一定适合你的任务！
-
-> Java 8的parallel stream
-
-可方便并行处理集合中的元素，共享同一ForkJoinPool，默认并行度是**CPU核数-1**。对于CPU绑定的任务，使用这样的配置较合适，但若集合操作涉及同步I/O操作（比如数据库操作、外部服务调用），建议自定义一个ForkJoinPool（或普通线程池）。
-
-最后声明一点：提交到相同线程池中的任务，一定要是相互独立的，最好不要有依赖关系！
-
-> 参考
-> - 《阿里巴巴Java开发手册》
\ No newline at end of file
diff --git "a/JavaScript/ReactJS/React-js-\345\256\236\346\210\230---\347\273\204\344\273\266-&-Props.md" "b/JavaScript/ReactJS/React-js-\345\256\236\346\210\230---\347\273\204\344\273\266-&-Props.md"
new file mode 100644
index 0000000000..db79a258ca
--- /dev/null
+++ "b/JavaScript/ReactJS/React-js-\345\256\236\346\210\230---\347\273\204\344\273\266-&-Props.md"
@@ -0,0 +1,274 @@
+# 0 联系我
+![](http://upload-images.jianshu.io/upload_images/4685968-6a8b28d2fd95e8b7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "图片标题") 
+1.[Java开发技术交流Q群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+
+2.[完整博客链接](https://blog.csdn.net/qq_33589510)
+
+3.[个人知乎](http://www.zhihu.com/people/shi-shu-sheng-)
+
+4.[gayhub](https://github.com/Wasabi1234)
+
+>组件可以将UI切分成一些独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件.
+
+组件从概念上看就像是函数，它可以接收任意的输入值（称之为“props”），并返回一个需要在页面上展示的React元素.
+
+# 1 函数定义/类定义组件
+定义一个组件最简单的方式是使用JavaScript函数：
+```
+function Welcome(props) {
+  return <h1>Hello, {props.name}</h1>;
+}
+```
+该函数是一个有效的React组件，它接收一个单一的“props”对象并返回了一个React元素.
+我们之所以称这种类型的组件为函数定义组件，是因为从字面上来看，它就是一个JavaScript函数.
+
+你也可以使用 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 来定义一个组件:
+```
+class Welcome extends React.Component {
+  render() {
+    return <h1>Hello, {this.props.name}</h1>;
+  }
+}
+```
+上面两个组件在React中是相同的.
+
+我们将在[下一节]讨论类的一些额外特性。在那之前，我们都将使用较为简洁的函数定义组件。
+
+# 2 组件渲染
+在前面，我们遇到的React元素都只是DOM标签：
+```
+const element = <div />;
+```
+
+然而，React元素也可以是用户自定义的组件
+
+```
+const element = <Welcome name="Sara" />;
+```
+当React遇到的元素是用户自定义的组件，它会将JSX属性作为单个对象传递给该组件，这个对象称之为“props”。
+
+例如,这段代码会在页面上渲染出”Hello,Sara”:
+![](https://upload-images.jianshu.io/upload_images/4685968-43ff9c0eb2a8aea8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+我们来回顾一下在这个例子中发生了什么：
+
+- 我们对<Welcome name="Sara" />元素调用了ReactDOM.render()方法
+- React将{name: 'Sara'}作为props传入并调用Welcome组件
+- Welcome组件将<h1>Hello, Sara</h1>元素作为结果返回。
+- React DOM将DOM更新为<h1>Hello, Sara</h1>。
+
+>警告:
+组件名称必须以大写字母开头。
+例如，<div /> 表示一个DOM标签，但 <Welcome /> 表示一个组件，并且在使用该组件时你必须定义或引入它。
+
+# 3 组合组件
+组件可以在它的输出中引用其它组件，这就可以让我们用同一组件来抽象出任意层次的细节.
+在React应用中，按钮、表单、对话框、整个屏幕的内容等，这些通常都被表示为组件.
+
+例如，我们可以创建一个`App`组件，用来多次渲染`Welcome`组件
+![](https://upload-images.jianshu.io/upload_images/4685968-e9066fb9559577a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+通常，一个新的React应用程序的顶部是一个`App`组件.
+但是，如果要将React集成到现有应用程序中，则可以从下而上使用像`Button`这样的小组件作为开始，并逐渐运用到视图层的顶部.
+
+>  组件的返回值只能有一个根元素。这也是我们要用一个`<div>`来包裹所有`<Welcome />`元素的原因。
+
+## 4 提取组件
+可以将组件切分为更小的组件
+
+例如，来看看这个Comment组件
+```
+function formatDate(date) {
+  return date.toLocaleDateString();
+}
+
+function Comment(props) {
+  return (
+    <div className="Comment">
+      <div className="UserInfo">
+        <img className="Avatar"
+             src={props.author.avatarUrl}
+             alt={props.author.name} />
+        <div className="UserInfo-name">
+          {props.author.name}
+        </div>
+      </div>
+      <div className="Comment-text">
+        {props.text}
+      </div>
+      <div className="Comment-date">
+        {formatDate(props.date)}
+      </div>
+    </div>
+  );
+}
+
+const comment = {
+  date: new Date(),
+  text: 'I hope you enjoy learning React!',
+  author: {
+    name: 'Hello Kitty',
+    avatarUrl: 'http://placekitten.com/g/64/64'
+  }
+};
+ReactDOM.render(
+  <Comment
+    date={comment.date}
+    text={comment.text}
+    author={comment.author} />,
+  document.getElementById('root')
+);
+```
+![](https://upload-images.jianshu.io/upload_images/4685968-f93aaee5d22df7a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+这个组件接收author(对象)、text(字符串)、以及date(Date对象)作为props，用来描述一个社交媒体网站上的评论.
+
+这个组件由于嵌套，变得难以被修改，可复用的部分也难以被复用。所以让我们从这个组件中提取出一些小组件。
+
+首先，我们来提取Avatar组件
+```
+function Avatar(props) {
+  return (
+    <img className="Avatar"
+      src={props.user.avatarUrl}
+      alt={props.user.name}
+    />
+  );
+}
+```
+Avatar作为Comment的内部组件，不需要知道是否被渲染.
+因此我们将author改为一个更通用的名字user。
+
+我们建议从组件自身的角度来命名props，而不是根据使用组件的上下文命名。
+
+现在我们可以对Comment组件做一些小小的调整：
+```
+function Comment(props) {
+  return (
+    <div className="Comment">
+      <div className="UserInfo">
+        <Avatar user={props.author} />
+        <div className="UserInfo-name">
+          {props.author.name}
+        </div>
+      </div>
+      <div className="Comment-text">
+        {props.text}
+      </div>
+      <div className="Comment-date">
+        {formatDate(props.date)}
+      </div>
+    </div>
+  );
+}
+```
+接下来，我们要提取一个UserInfo组件，用来渲染Avatar旁边的用户名：
+```
+function UserInfo(props) {
+  return (
+    <div className="UserInfo">
+      <Avatar user={props.user} />
+      <div className="UserInfo-name">
+        {props.user.name}
+      </div>
+    </div>
+  );
+}
+```
+这可以让我们进一步简化Comment组件：
+```
+function Comment(props) {
+  return (
+    <div className="Comment">
+      <UserInfo user={props.author} />
+      <div className="Comment-text">
+        {props.text}
+      </div>
+      <div className="Comment-date">
+        {formatDate(props.date)}
+      </div>
+    </div>
+  );
+}
+```
+- 最终代码
+```
+function formatDate(date) {
+  return date.toLocaleDateString();
+}
+
+function Avatar(props) {
+  return (
+    <img className="Avatar"
+         src={props.user.avatarUrl}
+         alt={props.user.name} />
+  );
+}
+
+function UserInfo(props) {
+  return (
+    <div className="UserInfo">
+      <Avatar user={props.user} />
+      <div className="UserInfo-name">
+        {props.user.name}
+      </div>
+    </div>
+  );
+}
+
+function Comment(props) {
+  return (
+    <div className="Comment">
+      <UserInfo user={props.author} />
+      <div className="Comment-text">
+        {props.text}
+      </div>
+      <div className="Comment-date">
+        {formatDate(props.date)}
+      </div>
+    </div>
+  );
+}
+
+const comment = {
+  date: new Date(),
+  text: 'I hope you enjoy learning React!',
+  author: {
+    name: 'Hello Kitty',
+    avatarUrl: 'http://placekitten.com/g/64/64'
+  }
+};
+ReactDOM.render(
+  <Comment
+    date={comment.date}
+    text={comment.text}
+    author={comment.author} />,
+  document.getElementById('root')
+);
+```
+提取组件一开始看起来像是一项单调乏味的工作，但是在大型应用中，构建可复用的组件完全是值得的.
+当你的UI中有一部分重复使用了好几次（比如，Button、Panel、Avatar），或者其自身就足够复杂（比如，App、FeedStory、Comment），类似这些都是抽象成一个可复用组件的绝佳选择，这也是一个比较好的做法.
+
+# 5 Props的只读性
+无论是使用[函数或是类](https://react.docschina.org/docs/components-and-props.html#%E5%87%BD%E6%95%B0%E5%AE%9A%E4%B9%89/%E7%B1%BB%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6)来声明一个组件，它决不能修改它自己的props.
+
+来看这个`sum`函数
+```
+function sum(a, b) {
+  return a + b;
+}
+```
+类似于上面的这种函数称为[“纯函数”](https://en.wikipedia.org/wiki/Pure_function)，它没有改变它自己的输入值，当传入的值相同时，总是会返回相同的结果。
+
+与之相对的是非纯函数，它会改变它自身的输入值
+```
+function withdraw(account, amount) {
+  account.total -= amount;
+}
+```
+
+React是非常灵活的，但它也有一个严格的规则：
+
+**所有的React组件必须像纯函数那样使用它们的props。**
+
+当然，应用的界面是随时间动态变化的，我们将在[下一节](https://react.docschina.org/docs/state-and-lifecycle.html)介绍一种称为“state”的新概念，State可以在不违反上述规则的情况下，根据用户操作、网络响应、或者其他状态变化，使组件动态的响应并改变组件的输出。
+
+
diff --git "a/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-JSX-\347\256\200\344\273\213.md" "b/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-JSX-\347\256\200\344\273\213.md"
index e69de29bb2..96aebca497 100644
--- "a/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-JSX-\347\256\200\344\273\213.md"
+++ "b/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-JSX-\347\256\200\344\273\213.md"
@@ -0,0 +1,247 @@
+![](https://upload-images.jianshu.io/upload_images/4685968-53bf2f140e5d98b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+在项目中引入并使用 JSX
+![添加支持](https://upload-images.jianshu.io/upload_images/4685968-885c2e2934da887a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![插件支持](https://upload-images.jianshu.io/upload_images/4685968-3b3c3bb8534b0bef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![引入依赖](https://upload-images.jianshu.io/upload_images/4685968-ac431d8dfbd065e8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+React提供的环境搭建工具演示
+![新建test项目](https://upload-images.jianshu.io/upload_images/4685968-6b349de565dcfe28.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-89c21d67a9f1465d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![yarn start](https://upload-images.jianshu.io/upload_images/4685968-8380585894f4d26e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![可以将配置文件提出来](https://upload-images.jianshu.io/upload_images/4685968-6e25f2ebf830e983.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![yarn eject](https://upload-images.jianshu.io/upload_images/4685968-be7f41ac5a936de5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+配置文件可读性差,因此不考虑使用这种现成方式写项目,跳过吧
+![](https://upload-images.jianshu.io/upload_images/4685968-d89aeda48085e76c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+我们来观察一下声明的这个变量：
+```
+const element = <h1>Hello, world!</h1>;
+```
+这种看起来可能有些奇怪的标签语法既不是字符串也不是 HTML
+它被称为 JSX， 一种 JavaScript 的语法扩展
+推荐在 React 中使用 JSX 来描述用户界面
+JSX 乍看起来可能比较像是模版语言，但事实上它完全是在 JavaScript 内部实现的
+
+JSX 用来声明 React 当中的元素。在下节会详细介绍元素是如何被渲染出来的
+
+先来看看 JSX 的基本使用方法
+# 在 JSX 中使用表达式
+可任意地在 JSX 当中使用 [JavaScript 表达式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Expressions)，在 JSX 当中的表达式要包含在大括号里
+
+例如 `2 + 2`， `user.firstName`， 以及 `formatName(user)` 都是可以使用的
+```
+function formatName(user) {
+  return user.firstName + ' ' + user.lastName;
+}
+
+const user = {
+  firstName: 'Harper',
+  lastName: 'Perez'
+};
+
+const element = (
+  <h1>
+    Hello, {formatName(user)}!
+  </h1>
+);
+
+ReactDOM.render(
+  element,
+  document.getElementById('root')
+);
+```
+- [在 CodePen 上试试](http://codepen.io/gaearon/pen/PGEjdG?editors=0010)
+![](https://upload-images.jianshu.io/upload_images/4685968-11e62e4e052c1251.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+书写 JSX 的时候一般都会带上换行和缩进，这样可以增强代码的可读性
+同样推荐在 JSX 代码的外面扩上一个小括号，这样可以防止 [分号自动插入](http://stackoverflow.com/q/2846283) 的 bug
+
+# JSX 本身其实也是一种表达式
+在编译后，JSX 其实会被转化为普通的 JavaScript 对象
+
+这意味着，你其实可以在 if 或者 for 语句里使用 JSX，将它赋值给变量，当作参数传入，作为返回值都可以
+```
+function getGreeting(user) {
+  if (user) {
+    return <h1>Hello, {formatName(user)}!</h1>;
+  }
+  return <h1>Hello, Stranger.</h1>;
+}
+```
+# JSX 属性
+## 使用引号来定义以字符串为值的属性
+```
+const element = <div tabIndex="0"></div>;
+```
+## 使用大括号来定义以 JavaScript 表达式为值的属性
+```
+const element = <img src={user.avatarUrl}></img>;
+```
+切记你使用了大括号包裹的 JavaScript 表达式时就不要再到外面套引号了
+`JSX 会将引号当中的内容识别为字符串而不是表达式`
+# JSX 嵌套
+若 JSX 标签是闭合式的，需在结尾处用` />`, 就好像 XML/HTML 一样
+![](https://upload-images.jianshu.io/upload_images/4685968-17b57bc0c305a801.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+JSX 标签同样可以相互嵌套
+![](https://upload-images.jianshu.io/upload_images/4685968-c93c07aa27a4a1bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+>**警告:**
+因为 JSX 的特性更接近 JavaScript 而不是 HTML , 所以 React DOM 使用 `camelCase` 小驼峰命名 来定义属性的名称，而不是使用 HTML 的属性名称
+如 `class` 变成了 [`className`](https://developer.mozilla.org/en-US/docs/Web/API/Element/className)
+而 `tabindex` 则对应着 [`tabIndex`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/tabIndex)
+# JSX 防注入攻击
+可放心在 JSX 当中使用用户输入
+![](https://upload-images.jianshu.io/upload_images/4685968-79ba2b05aafa51f4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+React DOM 在渲染之前默认会 [过滤](http://stackoverflow.com/questions/7381974/which-characters-need-to-be-escaped-on-html) 所有传入的值
+它可以确保你的应用不会被注入攻击。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 [XSS(跨站脚本)](https://en.wikipedia.org/wiki/Cross-site_scripting) 攻击
+
+# JSX 代表 Objects
+Babel 转译器会把 JSX 转换成一个名为 `React.createElement()`的方法调用
+
+下面两种代码的作用是完全相同的
+![](https://upload-images.jianshu.io/upload_images/4685968-565f21c6af404a14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5f28df4f5a1994e8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+`React.createElement() `首先会进行一些避免bug的检查，之后会返回一个类似下面例子的对象
+![](https://upload-images.jianshu.io/upload_images/4685968-cc428b56170551aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+这样的对象被称为 “React 元素”。它代表所有你在屏幕上看到的东西。React 通过读取这些对象来构建 DOM 并保持数据内容一致。
+
+我们将在下一个章节当中介绍更多有关 React 元素 是如何渲染成 DOM 的内容。
+
+>Tip:
+如果你是在使用本地编辑器编写 JSX 代码的话，推荐你去装一个支持 JSX 高亮的插件，这样更方便之后的开发学习。
+# JSX 的怪异之处
+JSX 偶尔也比较奇怪。针对在使用JSX 构建组件时可能会遇到的常见问题，本节汇总了一些小技巧、提示和策略来供你应对。
+- 单一根节点
+React 组件只能渲染一个根节点。想要了解这个限制的原因，我们先来看看render函数的一个返回示例：
+
+```
+return(  
+    <h1>Hello World</h1> 
+) 
+```
+
+它会被转换成一条语句：
+
+`return React.createElement("h1",null,"Hello World"); `
+
+但是，下面的代码却不是合法的：
+
+```
+return (  
+<h1>Hello World</h1> 
+<h2>Have a nice day</h2> 
+) 
+```
+
+需要明确的是，这并非JSX 的限制，而是JavaScript 的一个特性：一条返回语句只能返回单个值，而在前面的代码中我们尝试返回两条语句(两次React.createElement 调用)。解决的方法非常简单：就像你在普通JavaScript 中会做的那样，将所有返回值包含到一个根对象中。
+
+```
+return (  
+<div> 
+<h1>Hello World</h1> 
+<h2>Have a nice day</h2> 
+</div> 
+) 
+```
+
+它完全有效，因为它会被转换成：
+
+```
+return React.createElement("div",null,  
+React.createElement("h1",null,"Hello World"),  
+React.createElement("h2",null," Have a nice day"),  
+) 
+```
+
+它返回单个值，并且是通过合法的JavaScript 完成的。
+
+- 条件语句
+如果语句不兼容于JSX，看上去像是JSX 的限制所致，实际上却是因为JSX 只是普通的JavaScript
+回顾一下JSX 是如何被转换为普通JavaScript 
+如下JSX
+```
+return (  
+      <div className="salutation">Hello JSX</div> 
+) 
+```
+会被转换成这样的JavaScript
+```
+return (  
+    React.createElement("div",{className:"salutation"},"Hello JSX");  
+) 
+```
+
+然而，如果尝试在JSX 的中间编写if 语句，例如：
+`<div className={if (condition) { "salutation" }}>Hello JSX</div> `
+
+它就会被转换成一个非法的JavaScript 表达式，如图2-1 所示：
+
+[![image](http://upload-images.jianshu.io/upload_images/4685968-55ce124f80c28fab.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](https://s1.51cto.com/wyfs02/M02/96/4A/wKioL1ke8qCD28MXAAA3kuzHIgA061.jpg) 
+
+- 有什么解决方法？
+尽管并无可能在JSX 中使用“if”语句，但仍有根据条件渲染内容的方法，包括使用三元表达式和将条件赋值给一个变量(空值和未定义的值都会被React 进行处理，JSX在转义时什么都不会输出)。
+
+使用三元表达式
+
+如果你有一个非常简单的表达式，可以使用三元形式：
+
+```
+render() {  
+  return (  
+    <div className={condition ? "salutation" : ""}> 
+      Hello JSX  
+    </div> 
+  )  
+} 
+```
+
+这段代码会被转换成一段合法的JavaScript：
+
+```
+React.createElement("div",{className: condition ?"salutation" : ""},  
+"Hello JSX"); 
+```
+
+三元形式还可用来有条件地渲染整个节点：
+```
+<div> 
+{condition ?  
+<span>Hello JSX</span> 
+: null}  
+</div> 
+```
+
+将条件外置
+
+如果三元表达式还不能应付你的要求，解决方法是不要在JSX 的中间使用条件。简单地将条件语句移动到外部(就像你在第2 章中隐藏和显示ContactItem 细节时所采取的方法)。
+
+下面是原先的代码：
+```
+
+1.  render() {  
+2.  return (  
+3.  <div className={if (condition) { "salutation" }}> 
+4.  Hello JSX  
+5.  </div> 
+6.  )  
+7.  } 
+
+```
+
+将条件移动到JSX 的外部，就像：
+
+```
+render() {  
+let className;  
+if(condition){  
+className = "salutation";  
+}  
+return (  
+<div className={className}>Hello JSX</div> 
+)  
+} 
+```
+React 知道如何处理未定义的值，如果条件为假，它甚至不会在div 标签中创建class特性。
+
diff --git "a/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-\344\272\213\344\273\266\345\244\204\347\220\206.md" "b/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-\344\272\213\344\273\266\345\244\204\347\220\206.md"
index e69de29bb2..632637475c 100644
--- "a/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-\344\272\213\344\273\266\345\244\204\347\220\206.md"
+++ "b/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-\344\272\213\344\273\266\345\244\204\347\220\206.md"
@@ -0,0 +1,37 @@
+# 0 联系我
+![](http://upload-images.jianshu.io/upload_images/4685968-6a8b28d2fd95e8b7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "图片标题") 
+1.[Java开发技术交流Q群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+
+2.[完整博客链接](https://blog.csdn.net/qq_33589510)
+
+3.[个人知乎](http://www.zhihu.com/people/shi-shu-sheng-)
+
+4.[gayhub](https://github.com/Wasabi1234)
+
+React 元素的事件处理和 DOM元素的很相似。但是有一点语法上的不同:
+
+React事件绑定属性的命名采用驼峰式写法，而不是小写。
+如果采用 JSX 的语法你需要传入一个函数作为事件处理函数，而不是一个字符串(DOM元素的写法)
+例如，传统的 HTML：
+![](https://upload-images.jianshu.io/upload_images/4685968-042e501a230172b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+React 中稍稍有点不同
+![](https://upload-images.jianshu.io/upload_images/4685968-561dfa41bf3a69ca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+在 React 中另一个不同是你不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault。例如，传统的 HTML 中阻止链接默认打开一个新页面，你可以这样写：
+![](https://upload-images.jianshu.io/upload_images/4685968-f58eff6983ece4ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+在 React，应该这样来写
+![](https://upload-images.jianshu.io/upload_images/4685968-ae179d1f7aa58e74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+在这里，`e` 是一个合成事件。React 根据 [W3C spec](https://www.w3.org/TR/DOM-Level-3-Events/) 来定义这些合成事件，所以你不需要担心跨浏览器的兼容性问题。查看 [SyntheticEvent](https://react.docschina.org/docs/events.html) 参考指南来了解更多。
+
+使用 React 的时候通常你不需要使用 `addEventListener` 为一个已创建的 DOM 元素添加监听器。你仅仅需要在这个元素初始渲染的时候提供一个监听器。
+
+- 当使用 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 语法来定义一个组件的时候，事件处理器会成为类的一个方法.
+例如，下面的 `Toggle` 组件渲染一个让用户切换开关状态的按钮：
+![](https://upload-images.jianshu.io/upload_images/4685968-f814833acfca66db.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+必须谨慎对待 JSX 回调函数中的 `this`，类的方法默认是不会绑定`this` 的.
+如果你忘记绑定 `this.handleClick` 并把它传入 `onClick`, 当你调用这个函数的时候 `this` 的值会是 `undefined`.
+
+这并不是 React 的特殊行为；它是[函数如何在 JavaScript 中运行](https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/)的一部分。通常情况下，如果你没有在方法后面添加 `()` ，例如 `onClick={this.handleClick}`，你应该为这个方法绑定 `this`。
+
+如果使用 `bind` 让你很烦，这里有两种方式可以解决。如果你正在使用实验性的[属性初始化器语法](https://babeljs.io/docs/plugins/transform-class-properties/)，你可以使用属性初始化器来正确的绑定回调函数：
diff --git "a/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-\347\273\204\344\273\266-&-Props.md" "b/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-\347\273\204\344\273\266-&-Props.md"
new file mode 100644
index 0000000000..3d816264c8
--- /dev/null
+++ "b/JavaScript/ReactJS/React-js-\345\256\236\346\210\230\344\271\213-\347\273\204\344\273\266-&-Props.md"
@@ -0,0 +1,367 @@
+> 组件可以将UI切分成一些独立的、可复用的部件，这样就只需专注于构建每一个单独的部件
+
+组件从概念上看就像是函数，它可以接收任意的输入值（称之为`props`），并返回一个需要在页面上展示的React元素
+
+# 函数定义/类定义组件
+定义一个组件最简单的方式是使用JavaScript函数
+![](https://upload-images.jianshu.io/upload_images/4685968-f04e96ffc70b4bc2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+该函数是一个有效的React组件，它接收一个单一的“props”对象并返回了一个React元素。我们之所以称这种类型的组件为函数定义组件，是因为从字面上来看，它就是一个JavaScript函数。
+
+- 也可使用 [ES6 class](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes) 来定义一个组件
+![](https://upload-images.jianshu.io/upload_images/4685968-8379d630a347cf5f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+上面两个组件在React中是相同的
+
+下节讨论类的一些额外特性。在那之前，都将使用较为简洁的函数定义组件。
+# 组件渲染
+- 在前面，我们遇到的React元素都只是DOM标签
+![](https://upload-images.jianshu.io/upload_images/4685968-0327c8319f6beb38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- React元素也可以是用户自定义的组件
+![](https://upload-images.jianshu.io/upload_images/4685968-57e405160022a195.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+当React遇到的元素是用户自定义的组件，它会将JSX属性作为单个对象传递给该组件，这个对象称之为`props`
+
+例如,这段代码会在页面上渲染出”Hello,sss”
+![](https://upload-images.jianshu.io/upload_images/4685968-50eb4010d7e3227d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+我们来回顾一下在这个例子中发生了什么：
+- 我们对`<Welcome name="sss" />`元素调用了`ReactDOM.render()`
+- React将`{name: 'sss'}`作为`props`传入并调用`Welcome`组件
+- Welcome组件将`<h1>Hello, sss</h1>`元素作为结果返回。
+- React DOM将DOM更新为<h1>Hello, sss</h1>
+
+>警告:
+组件名称必须以大写字母开头。
+例如，<div /> 表示一个DOM标签，但 <Welcome /> 表示一个组件，并且在使用该组件时你必须定义或引入之
+
+# 组合组件
+组件可以在它的输出中引用其它组件，这就可以让我们用同一组件来抽象出任意层次的细节
+在React应用中，按钮、表单、对话框、整个屏幕的内容等，这些通常都被表示为组件
+
+例如，我们可以创建一个App组件，用来多次渲染Welcome组件
+![](https://upload-images.jianshu.io/upload_images/4685968-248a7ffb078869ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+通常，一个新的React应用程序的顶部是一个App组件
+但是，如果要将React集成到现有应用程序中，则可以从下而上使用像Button这样的小组件作为开始，并逐渐运用到视图层的顶部
+
+>警告:
+组件的返回值只能有一个根元素
+这也是要用一个`<div>`来包裹所有`<Welcome />`元素的原因
+# 提取组件
+你可以将组件切分为更小的组件，这没什么好担心的。
+
+例如，来看看这个Comment组件：
+![](https://upload-images.jianshu.io/upload_images/4685968-dd80dabce51ddc38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+这个组件接收author(对象)、text(字符串)、以及date(Date对象)作为props，用来描述一个社交媒体网站上的评论
+
+这个组件由于嵌套，变得难以被修改，可复用的部分也难以被复用
+所以让我们从这个组件中提取出一些小组件
+
+- 首先，我们来提取Avatar组件：
+![](https://upload-images.jianshu.io/upload_images/4685968-93581a61844ad2c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+Avatar作为Comment的内部组件，不需要知道是否被渲染
+因此我们将author改为一个更通用的名字user
+
+建议从`组件自身的角度`来命名props，而`不是根据使用组件的上下文`命名
+
+现在我们可以对Comment组件做一些小小的调整
+![](https://upload-images.jianshu.io/upload_images/4685968-f5404d292712c4bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+接下来，我们要提取一个UserInfo组件，用来渲染Avatar旁边的用户名
+![](https://upload-images.jianshu.io/upload_images/4685968-a478923b52f0f897.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+这可以让我们进一步简化Comment组件
+![](https://upload-images.jianshu.io/upload_images/4685968-47ba258c51f58db2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+提取组件一开始看起来像是一项单调乏味的工作，但是在大型应用中，构建可复用的组件完全是值得的
+当你的UI中有一部分重复使用了好几次（比如，Button、Panel、Avatar），或者其自身就足够复杂（比如，App、FeedStory、Comment），类似这些都是抽象成一个可复用组件的绝佳选择，这也是一个比较好的做法
+
+# Props的只读性
+无论是使用[函数或是类](https://react.docschina.org/docs/components-and-props.html#%E5%87%BD%E6%95%B0%E5%AE%9A%E4%B9%89/%E7%B1%BB%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6)来声明一个组件，它决不能修改它自己的props
+- 来看这个`sum`函数
+![](https://upload-images.jianshu.io/upload_images/4685968-f85a8baa9be752cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+类似于上面的这种函数称为[“纯函数”](https://en.wikipedia.org/wiki/Pure_function)，它没有改变它自己的输入值，当传入的值相同时，总是会返回相同的结果
+
+与之相对的是非纯函数，它会改变它自身的输入值
+![](https://upload-images.jianshu.io/upload_images/4685968-a92b74c26f25fbf2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+React是非常灵活的，但它也有一个严格的规则：
+
+**所有的React组件必须像纯函数那样使用它们的props**
+
+当然，应用的界面是随时间动态变化的，下节介绍一种称为“state”的新概念，State可以在不违反上述规则的情况下，根据用户操作、网络响应、或者其他状态变化，使组件动态的响应并改变组件的输出
+
+
+
+# sublime 插件安装
+用Package Control安装
+按下`Ctrl+Shift+P`调出命令面板 
+输入`install` 调出 `Install Package` 选项并回车，然后在列表中选中要安装的插件
+ 
+![](https://upload-images.jianshu.io/upload_images/4685968-269620ccb50bb392.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- function形式
+![](https://upload-images.jianshu.io/upload_images/4685968-7904f8f781784156.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-79b1365a8053f4c6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+es6形式
+![](https://upload-images.jianshu.io/upload_images/4685968-9d0a3a9a4b8d3a67.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-e0b71c715c7fe75d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- state属性
+    - 用来存储组件自身需要的数据。它是可以改变的，它的每次改变都会引发组件的更新。这也是 ReactJS 中的关键点之一。
+    - 即每次数据的更新都是通过修改 state 属性的值，然后 ReactJS 内部会监听 state 属性的变化，一旦发生变化，就会触发组件的 render 方法来更新 DOM 结构。
+- props属性介绍：
+    - props 是一个对象，是组件用来接收外面传来的参数的。
+    - 组件内部是不允许修改自己的 props 属性，只能通过父组件来修改。上面的 getDefaultProps 方法便是处理 props 的默认值的。
+![](https://upload-images.jianshu.io/upload_images/4685968-4341391ce68b715a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#组件间通信
+父子组件间通信
+这种情况下很简单，就是通过 props 属性传递，在父组件给子组件设置 props，然后子组件就可以通过 props 访问到父组件的数据／方法，这样就搭建起了父子组件间通信的桥梁。
+```
+import React, { Component } from 'react';
+import { render } from 'react-dom';
+
+class GroceryList extends Component {
+  handleClick(i) {
+    console.log('You clicked: ' + this.props.items[i]);
+  }
+
+  render() {
+    return (
+      <div>
+        {this.props.items.map((item, i) => {
+          return (
+            <div onClick={this.handleClick.bind(this, i)} key={i}>{item}</div>
+          );
+        })}
+      </div>
+    );
+  }
+}
+
+render(
+  <GroceryList items={['Apple', 'Banana', 'Cranberry']} />, mountNode
+);
+```
+div 可以看作一个子组件，指定它的 onClick 事件调用父组件的方法。
+父组件访问子组件？用 refs
+![在子组件改变父组件属性](https://upload-images.jianshu.io/upload_images/4685968-a79ae5a9b6f9c771.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+```
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+// 基础组件写法
+function Component(){
+    return <h1>I am sss</h1>
+}
+
+class ES6Component extends React.Component{
+    render(){
+        return <h1>I am sss in es6</h1>
+    }
+}
+
+ReactDOM.render(
+    <div>
+        <Component/>
+        <ES6Component/>
+    </div>,
+    document.getElementById('app')
+);
+
+// status && props
+class Component extends React.Component{
+    constructor(props){
+        super(props);
+    }
+    render(){
+        setTimeout(()=>{
+            this.setState({
+                name: 'sss Test'
+            })
+        },2000)
+        return <h1>I am {this.state.name}</h1>
+    }
+}
+
+ReactDOM.render(
+    <div>
+        <Component name="sss"/>
+    </div>,
+    document.getElementById('app')
+);
+
+
+// 事件处理方式1
+class Component extends React.Component{
+    constructor(props){
+        super(props);
+        this.state={
+            name : 'sss',
+            age : 18
+        }
+        this.handleClick = this.handleClick.bind(this)
+    }
+    handleClick(){
+        this.setState({
+            age : this.state.age + 1
+        });
+    }
+    render(){
+        return (
+        <div>
+            <h1>I am {this.state.name}</h1>
+            <p>I am {this.state.age} years old!</p>
+            <button onClick={this.handleClick}>加一岁</button>
+        </div>
+        );
+    }
+}
+
+
+
+// 事件处理方式2
+class Component extends React.Component{
+    constructor(props){
+        super(props);
+        this.state={
+            name : 'sss',
+            age : 18
+        }
+    }
+    handleClick(){
+        this.setState({
+            age : this.state.age + 1
+        });
+    }
+    onValueChange(e){
+        this.setState({
+            age : e.target.value
+        });
+    }
+    render(){
+        return (
+        <div>
+            <h1>I am {this.state.name}</h1>
+            <p>I am {this.state.age} years old!</p>
+            <button onClick={(e) => {this.handleClick(e)}}>加一岁</button>
+            <input type="text" onChange={(e) => {this.onValueChange(e)}}/>
+        </div>
+        );
+    }
+}
+
+
+
+// 组件的组合方式
+class Component extends React.Component{
+    constructor(props){
+        super(props);
+        this.state={
+            name : 'sss',
+            age : 18
+        }
+    }
+    handleClick(){
+        this.setState({
+            age : this.state.age + 1
+        });
+    }
+    onValueChange(e){
+        this.setState({
+            age : e.target.value
+        });
+    }
+    render(){
+        return (
+        <div>
+            <h1>I am {this.state.name}</h1>
+            <p>I am {this.state.age} years old!</p>
+            <button onClick={(e) => {this.handleClick(e)}}>加一岁</button>
+            <input type="text" onChange={(e) => {this.onValueChange(e)}}/>
+        </div>
+        );
+    }
+}
+
+class Title extends React.Component{
+    constructor(props){
+        super(props);
+    }
+    render(props){
+        return <h1>{this.props.children}</h1>
+    }
+
+}
+class App extends React.Component{
+    render(){
+        return (
+            <div className="">
+                {/* 容器式组件 */}
+                <Title>
+                    <span>App Span</span>
+                    <a href="">link</a>
+                </Title>
+                <hr/>
+                {/* 单纯组件 */}
+                <Component/>
+            </div>
+        );
+    }
+}
+
+
+// 数据传递和状态提升
+class Child1 extends React.Component{
+    constructor(props){
+        super(props);
+    }
+    handleClick(){
+        this.props.changeChild2Color('red');
+    }
+    render(){
+        return (
+        <div>
+            <h1>Child1： {this.props.bgColor}</h1>
+            <button onClick={(e) => {this.handleClick(e)}}>改变child2 颜色</button>
+        </div>
+        );
+    }
+}
+class Child2 extends React.Component{
+    constructor(props){
+        super(props);
+    }
+    render(){
+        return (
+        <div style={{background:this.props.bgColor}}>
+            <h1>Child2背景颜色： {this.props.bgColor}</h1>
+        </div>
+        );
+    }
+}
+
+class Father extends React.Component{
+    constructor(props){
+        super(props);
+        this.state = {
+            child2BgColor: '#999'
+        }
+    }
+    onChild2BgColorChange(color){
+        this.setState({
+            child2BgColor : color
+        })
+    }
+    render(props){
+        return (
+        <div>
+            <Child1 changeChild2Color={(color) => {this.onChild2BgColorChange(color)}}/>
+            <Child2 bgColor={this.state.child2BgColor}/>
+        </div>
+        );
+    }
+
+}
+```
diff --git "a/JavaScript/ReactJS/React-js\345\256\236\346\210\230\344\271\213React-\347\224\237\345\221\275\345\221\250\346\234\237.md" "b/JavaScript/ReactJS/React-js\345\256\236\346\210\230\344\271\213React-\347\224\237\345\221\275\345\221\250\346\234\237.md"
index e69de29bb2..7c5598d144 100644
--- "a/JavaScript/ReactJS/React-js\345\256\236\346\210\230\344\271\213React-\347\224\237\345\221\275\345\221\250\346\234\237.md"
+++ "b/JavaScript/ReactJS/React-js\345\256\236\346\210\230\344\271\213React-\347\224\237\345\221\275\345\221\250\346\234\237.md"
@@ -0,0 +1,147 @@
+ReactJS 的核心思想是组件化，即按功能封装成一个一个的组件，各个组件维护自己的状态和 UI，当状态发生变化时，会自定重新渲染整个组件，多个组件一起协作共同构成了 ReactJS 应用。
+
+为了能够更好的创建和使用组件，我们首先要了解组件的生命周期。
+生命周期
+![](https://upload-images.jianshu.io/upload_images/4685968-9e91a1f72a17aadd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-e238445a7ad94b0a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 1 组件的生命周期
+创建阶段、实例化阶段、更新阶段、销毁阶段。
+下面对各个阶段分别进行介绍。
+## 1.1加载阶段
+![](https://upload-images.jianshu.io/upload_images/4685968-7cd305fda1660409.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+*   该阶段主要发生在创建组件类的时候，即调用 `React.createClass` 时触发
+*   这个阶段只会触发一个 `getDefaultProps` 方法，该方法会返回一个对象并缓存。然后与父组件指定的 `props` 对象合并，最后赋值给 `this.props` 作为该组件的默认属性。
+
+
+
+## 1.2 实例化阶段
+该阶段主要发生在实例化组件类的时候，也就是该组件类被调用的时候触发。
+这个阶段会触发一系列的流程，按执行顺序如下
+（1）`getInitialState`：初始化组件的 state 的值。其返回值会赋值给组件的 this.state 属性。
+（2）`componentWillMount`：根据业务逻辑来对 state 进行相应的操作。
+（3）`render`：根据 state 的值，生成页面需要的虚拟 DOM 结构，并返回该结构。
+（4）`componentDidMount`：对根据虚拟 DOM 结构而生的真实 DOM 进行相应的处理。组件内部可以通过 ReactDOM.findDOMNode(this) 来获取当前组件的节点，然后就可以像 Web 开发中那样操作里面的 DOM 元素了。
+
+
+## 1.3 更新阶段
+![](https://upload-images.jianshu.io/upload_images/4685968-05443c196248ac99.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+这主要发生在用户操作之后或者父组件有更新的时候，此时会根据用户的操作行为进行相应得页面结构的调整。这个阶段也会触发一系列的流程，按执行顺序如下：
+（1）componentWillReceiveProps：当组件接收到新的 props 时，会触发该函数。在改函数中，通常可以调用 this.setState 方法来完成对 state 的修改。
+（2）shouldComponentUpdate：该方法用来拦截新的 props 或 state，然后根据事先设定好的判断逻辑，做出最后要不要更新组件的决定。
+（3）componentWillUpdate：当上面的方法拦截返回 true 的时候，就可以在该方法中做一些更新之前的操作。
+（4）render：根据一系列的 diff 算法，生成需要更新的虚拟 DOM 数据。（注意：在 render 中最好只做数据和模板的组合，不应进行 state 等逻辑的修改，这样组件结构更加清晰）
+（5）componentDidUpdate：该方法在组件的更新已经同步到 DOM 中去后触发，我们常在该方法中做一 DOM 操作。
+![](https://upload-images.jianshu.io/upload_images/4685968-dff8fe4c8fb67e62.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 1.4 销毁阶段
+![](https://upload-images.jianshu.io/upload_images/4685968-15fa8bc699a18747.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+这个阶段只会触发一个叫 componentWillUnmount 的方法。
+当组件需要从 DOM 中移除的时候，我们通常会做一些取消事件绑定、移除虚拟 DOM 中对应的组件数据结构、销毁一些无效的定时器等工作。这些事情都可以在这个方法中处理。
+![](https://upload-images.jianshu.io/upload_images/4685968-d367fba0a0ab92bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+```
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+
+class Component extends React.Component{
+    // 构造函数
+    constructor(props){
+        super(props)
+        this.state = {
+            data: 'Old State'
+        }
+        console.log('初始化数据','constructor');
+    }
+    // 组件将要加载
+    componentWillMount(){
+        console.log('componentWillMount');
+    }
+    // 组件加载完成
+    componentDidMount(){
+        console.log('componentDidMount');
+    }
+    // 将要接收父组件传来的props
+    componentWillReceiveProps(){
+        console.log('componentWillReceiveProps');
+    }
+    // 子组件是不是应该更新
+    shouldComponentUpdate(){
+        console.log('shouldComponentUpdate');
+        return true;
+    }
+    // 组件将要更新
+    componentWillUpdate(){
+        console.log('componentWillUpdate');
+    }
+    // 组件更新完成
+    componentDidUpdate(){
+        console.log('componentDidUpdate');
+    }
+    // 组件即将销毁
+    componentWillUnmount(){
+        console.log('componentWillUnmount');
+    }
+    // 处理点击事件
+    handleClick(){
+        console.log('更新数据：');
+        this.setState({
+            data: 'New State'
+        });
+    }
+    // 渲染
+    render(){
+        console.log('render')
+        return (
+            <div>
+                <div>Props: {this.props.data}</div>
+                <button onClick={()=>{this.handleClick()}}>更新组件</button>
+            </div>
+        );
+    }
+}
+
+class App extends React.Component{
+    // 构造函数
+    constructor(props){
+        super(props)
+        this.state = {
+            data: 'Old Props',
+            hasChild: true
+        }
+        console.log('初始化数据','constructor');
+    }
+    onPropsChange(){
+        console.log('更新props:');
+        this.setState({
+            data: 'New Props'
+        });
+    }
+    onDestoryChild(){
+        console.log('干掉子组件：');
+        this.setState({
+            hasChild: false
+        });
+    }
+    render(){
+        return (
+            <div>
+                {
+                    this.state.hasChild ? <Component data={this.state.data}/> : null
+                }
+                <button onClick={()=>{this.onPropsChange()}}>改变Props</button>
+                <button onClick={()=>{this.onDestoryChild()}}>干掉子组件</button>
+            </div>
+        );
+    }
+}
+
+ReactDOM.render(
+    <App/>,
+    document.getElementById('app')
+);
+```
diff --git "a/JavaScript/VueJS/RESTful\346\236\266\346\236\204.md" "b/JavaScript/VueJS/RESTful\346\236\266\346\236\204.md"
new file mode 100644
index 0000000000..84fa273c99
--- /dev/null
+++ "b/JavaScript/VueJS/RESTful\346\236\266\346\236\204.md"
@@ -0,0 +1,82 @@
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-39f29a1ad477cdb7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-6fdd998f69fb84be.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![zzz](http://upload-images.jianshu.io/upload_images/4685968-daab1cabc5a51e35.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-ea1df6e21fd0aa44.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-146fc2ec7c746710.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-bf61836dd3ce965f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-ab6dfa795b8589b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+0\. REST不是"rest"这个单词，而是几个单词缩写。
+1\. REST描述的是在网络中client和server的一种交互形式；REST本身不实用，实用的是如何设计 RESTful API（REST风格的网络接口）；
+2\. Server提供的RESTful API中，URL中只使用名词来指定资源，原则上不使用动词。“资源”是REST架构或者说整个网络处理的核心。比如：
+[http://api.qc.com/v1/newsfeed](http://api.qc.com/v1/newsfeed): 获取某人的新鲜;
+[http://api.qc.com/v1/friends](http://api.qc.com/v1/friends): 获取某人的好友列表;
+[http://api.qc.com/v1/profile](http://api.qc.com/v1/profile): 获取某人的详细信息;3\. 用HTTP协议里的动词来实现资源的添加，修改，删除等操作。即通过HTTP动词来实现资源的状态扭转：
+GET 用来获取资源，
+POST 用来新建资源（也可以用于更新资源），
+PUT 用来更新资源，
+DELETE 用来删除资源。比如：
+DELETE [http://api.qc.com/v1/](http://api.qc.com/v1/friends)friends: 删除某人的好友 （在http parameter指定好友id）
+POST [http://api.qc.com/v1/](http://api.qc.com/v1/friends)friends: 添加好友
+UPDATE [http://api.qc.com/v1/profile](http://api.qc.com/v1/profile): 更新个人资料
+
+禁止使用： GET [http://api.qc.com/v1/deleteFriend](http://api.qc.com/v1/deleteFriend) 图例：
+4\. Server和Client之间传递某资源的一个表现形式，比如用JSON，XML传输文本，或者用JPG，WebP传输图片等。当然还可以压缩HTTP传输时的数据（on-wire data compression）。
+5\. 用 HTTP Status Code传递Server的状态信息。比如最常用的 200 表示成功，500 表示Server内部错误等。
+
+主要信息就这么点。最后是要解放思想，Web端不再用之前典型的PHP或JSP架构，而是改为前段渲染和附带处理简单的商务逻辑（比如AngularJS或者BackBone的一些样例）。Web端和Server只使用上述定义的API来传递数据和改变数据状态。格式一般是JSON。iOS和Android同理可得。由此可见，Web，iOS，Android和第三方开发者变为平等的角色通过一套API来共同消费Server提供的服务。
+
+#**REST名称**
+REST -- REpresentational State Transfer
+全称 Resource Representational State Transfer:资源在网络中以某种表现形式进行状态转移
+- Resource：资源，即数据（前面说过网络的核心）。比如 newsfeed，friends
+- Representational：某种表现形式，比如用JSON，XML，JPEG
+- State Transfer：状态变化。通过HTTP动词实现
+#**REST的出处**
+Roy Fielding的毕业论文。参与设计HTTP协议，也是Apache Web Server项目（可惜现在已经是 nginx 的天下）的co-founder。PhD的毕业学校是 UC Irvine，Irvine在加州，有着充裕的阳光和美丽的海滩，是著名的富人区。Oculus VR 的总部就坐落于此（虚拟现实眼镜，被FB收购，CTO为Quake和Doom的作者 John Carmack）
+论文地址：[Architectural Styles and the Design of Network-based Software Architectures](http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)
+REST章节：[Fielding Dissertation: CHAPTER 5: Representational State Transfer (REST)](http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm)
+#**RESTful API**
+**为什么要用RESTful结构呢？**
+大家都知道"古代"网页是前端后端融在一起的，比如之前的PHP，JSP等。在之前的桌面时代问题不大，但是近年来移动互联网的发展，各种类型的Client层出不穷，RESTful可以通过一套统一的接口为 Web，iOS和Android提供服务。另外对于广大平台来说，比如Facebook platform，微博开放平台，微信公共平台等，它们不需要有显式的前端，只需要一套提供服务的接口，于是RESTful更是它们最好的选择。
+在RESTful架构下：
+![](http://upload-images.jianshu.io/upload_images/4685968-bbab8b0bbb38d049.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+**Server的API如何设计才满足RESTful要求?**
+URL中只使用名词来指定资源，原则上不使用动词
+best practices：
+1\. URL root:
+[https://example.org/api/v1/](https://example.org/api/v1/)
+[https://api.example.com/v1/](https://api.example.com/v1/)
+2\. API versioning:
+可以放在URL里面，也可以用HTTP的header：
+/api/v1/
+3\. URI使用名词而不是动词，且推荐用复数。
+BAD
+*   /getProducts
+*   /listOrders
+*   /retrieveClientByOrder?orderId=1
+
+GOOD
+*   GET /products : will return the list of all products
+*   POST /products : will add a product to the collection
+*   GET /products/4 : will retrieve product #4
+*   PATCH/PUT /products/4 : will update product #4
+
+4\. 保证 HEAD 和 GET 方法是安全的，不会对资源状态有所改变（污染）。比如严格杜绝如下情况：
+GET /deleteProduct?id=1
+5\. 资源的地址推荐用嵌套结构。比如：
+GET /friends/10375923/profile
+UPDATE /profile/primaryAddress/city6\. 警惕返回结果的大小。如果过大，及时进行分页（pagination）或者加入限制（limit）。HTTP协议支持分页（Pagination）操作，在Header中使用 Link 即可。
+7\. 使用正确的HTTP Status Code表示访问状态：[HTTP/1.1: Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
+8\. 在返回结果用明确易懂的文本（String。注意返回的错误是要给人看的，避免用 1001 这种错误信息），而且适当地加入注释。
+9\. 关于安全：自己的接口就用https，加上一个key做一次hash放在最后即可。考虑到国情，HTTPS在无线网络里不稳定，可以使用Application Level的加密手段把整个HTTP的payload加密。有兴趣的朋友可以用手机连上电脑的共享Wi-Fi，然后用Charles监听微信的网络请求（发照片或者刷朋友圈）。
+如果是平台的API，可以用成熟但是复杂的OAuth2，新浪微博这篇：[授权机制说明](http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E)
+
+#**各端的具体实现**
+如上面的图所示，Server统一提供一套RESTful API，web+ios+android作为同等公民调用API。各端发展到现在，都有一套比较成熟的框架来帮开发者事半功倍。
+
+-- Server --
+推荐： Spring MVC
+教程：
+[Getting Started · Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index f49a4e16e6..0000000000
--- a/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
\ No newline at end of file
diff --git "a/MyBatis/MyBatis-Generator-\347\224\250\346\263\225\350\257\246\350\247\243.md" "b/MyBatis/MyBatis-Generator-\347\224\250\346\263\225\350\257\246\350\247\243.md"
index f20eafd6ff..ef81400865 100644
--- "a/MyBatis/MyBatis-Generator-\347\224\250\346\263\225\350\257\246\350\247\243.md"
+++ "b/MyBatis/MyBatis-Generator-\347\224\250\346\263\225\350\257\246\350\247\243.md"
@@ -1,16 +1,18 @@
-**MBG**全部指代MyBatis Generator：
-- [**MyBatis Generator 1.3.4 扩展，可以设置 Mapper（Dao）后缀**](http://blog.csdn.net/isea533/article/details/52430691)
-- [运行MyBatis Generator](http://mbg.cndocs.ml/running/running.html)
-- 有4种运行MBG的方法，具体请看文档 [运行 MyBatis Generator](http://mbg.cndocs.ml/running/running.html)
+**MBG**全部指代MyBatis Generator
+
+[**MyBatis Generator 1.3.4 扩展，可以设置 Mapper（Dao）后缀**](http://blog.csdn.net/isea533/article/details/52430691)
+
+## [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#-a-href-http-generator-sturgeon-mopaas-com-running-running-html-%E8%BF%90%E8%A1%8Cmybatis-generator-a-)[运行MyBatis Generator](http://mbg.cndocs.ml/running/running.html)
+- 有4种运行MBG的方法，具体请看文档 [运行 MyBatis Generator](http://mbg.cndocs.ml/running/running.html)
 - MBG下载地址:
 [http://repo1.maven.org/maven2/org/mybatis/generator/mybatis-generator-core/](http://repo1.maven.org/maven2/org/mybatis/generator/mybatis-generator-core/)
-# [XML配置详解](http://mbg.cndocs.ml/configreference/xmlconfig.html)
+# [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#-a-href-http-generator-sturgeon-mopaas-com-configreference-xmlconfig-html-xml%E9%85%8D%E7%BD%AE%E8%AF%A6%E8%A7%A3-a-)[XML配置详解](http://mbg.cndocs.ml/configreference/xmlconfig.html)
 
 在MBG中，最主要也最重要的就是XML配置文件，因此本文主要就是XML配置
 
 这里按照配置的顺序对配置逐个讲解，更细的内容可以配合中文文档参照。
 
-## 1. 配置文件头
+##1. 配置文件头
 ```xml
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE generatorConfiguration
@@ -25,20 +27,20 @@
 </generatorConfiguration>  
 
 ```
-# [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-code-lt-generatorconfiguration-gt-code-%E5%AD%90%E5%85%83%E7%B4%A0)3. `<generatorConfiguration>`的子元素
+# [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-code-lt-generatorconfiguration-gt-code-%E5%AD%90%E5%85%83%E7%B4%A0)3. `<generatorConfiguration>`的子元素
 
 从这段开始，就是配置的主要内容，这些配置都是`generatorConfiguration`元素的子元素（有严格顺序）
 
-1.  [`<properties>`](http://mbg.cndocs.ml/configreference/properties.html) (0个或1个)
+1.  [`<properties>`](http://mbg.cndocs.ml/configreference/properties.html) (0个或1个)
 
-2.  [`<classPathEntry>`](http://mbg.cndocs.ml/configreference/classPathEntry.html) (0个或多个)
+2.  [`<classPathEntry>`](http://mbg.cndocs.ml/configreference/classPathEntry.html) (0个或多个)
 
-3.  [`<context>`](http://mbg.cndocs.ml/configreference/context.html) (1个或多个)
+3.  [`<context>`](http://mbg.cndocs.ml/configreference/context.html) (1个或多个)
 
-## [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-1-a-href-http-generator-sturgeon-mopaas-com-configreference-properties-html-code-lt-properties-gt-code-a-%E5%85%83%E7%B4%A0)3.1 [`<properties>`](http://mbg.cndocs.ml/configreference/properties.html) 元素
+## [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-1-a-href-http-generator-sturgeon-mopaas-com-configreference-properties-html-code-lt-properties-gt-code-a-%E5%85%83%E7%B4%A0)3.1 [`<properties>`](http://mbg.cndocs.ml/configreference/properties.html) 元素
 这个元素用来指定外部的属性元素，不是必须的元素。
 
-元素用于指定一个需要在配置中解析使用的外部属性文件，引入属性文件后，可以在配置中使用 `${property}`这种形式的引用，通过这种方式引用属性文件中的属性值。 对于后面需要配置的**jdbc信息**和`targetProject`属性会很有用
+元素用于指定一个需要在配置中解析使用的外部属性文件，引入属性文件后，可以在配置中使用 `${property}`这种形式的引用，通过这种方式引用属性文件中的属性值。 对于后面需要配置的**jdbc信息**和`targetProject`属性会很有用
 
 这个属性可以通过`resource`或者`url`来指定属性文件的位置，这两个属性只能使用其中一个来指定，同时出现会报错
 
@@ -46,7 +48,7 @@
 
 *   `url`：可以指定文件系统上的特定位置，例如`file:///C:/myfolder/generatorConfig.properties`
 
-## [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-2-a-href-http-generator-sturgeon-mopaas-com-configreference-classpathentry-html-code-lt-classpathentry-gt-code-a-%E5%85%83%E7%B4%A0)3.2 [`<classPathEntry>`](http://mbg.cndocs.ml/configreference/classPathEntry.html) 元素
+## [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-2-a-href-http-generator-sturgeon-mopaas-com-configreference-classpathentry-html-code-lt-classpathentry-gt-code-a-%E5%85%83%E7%B4%A0)3.2 [`<classPathEntry>`](http://mbg.cndocs.ml/configreference/classPathEntry.html) 元素
 这个元素可以0或多个，不受限制。
 最常见的用法是通过这个属性指定驱动的路径，例如：
 `<classPathEntry location="E:\mysql\mysql-connector-java-5.1.29.jar"/>`
@@ -56,7 +58,7 @@
 
 **因此，**如果你需要加载其他用途的jar包，**classPathEntry起不到作用**，不能这么写，解决的办法就是将你用的jar包添加到类路径中，在IDE中运行的时候，添加jar包比较容易。当从命令行执行的时候，需要用java -cp xx.jar,xx2.jar xxxMainClass这种方式在-cp后面指定来使用(注意-jar会导致-cp无效)。
 
-## [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-a-href-http-generator-sturgeon-mopaas-com-configreference-context-html-code-lt-context-gt-code-a-%E5%85%83%E7%B4%A0)3.3 [`<context>`](http://mbg.cndocs.ml/configreference/context.html) 元素
+## [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-a-href-http-generator-sturgeon-mopaas-com-configreference-context-html-code-lt-context-gt-code-a-%E5%85%83%E7%B4%A0)3.3 [`<context>`](http://mbg.cndocs.ml/configreference/context.html) 元素
 
 在MBG的配置中，至少需要有一个`<context>`元素。
 
@@ -76,7 +78,7 @@
     *   `MyBatis3`:*这是默认值*
     *   `MyBatis3Simple`
     *   `Ibatis2Java2`
-    *   `Ibatis2Java5` 一般情况下使用默认值即可，有关这些值的具体作用以及区别请查看中文文档的详细内容。
+    *   `Ibatis2Java5` 一般情况下使用默认值即可，有关这些值的具体作用以及区别请查看中文文档的详细内容。
 
 *   `introspectedColumnImpl`:该参数可以指定扩展`org.mybatis.generator.api.IntrospectedColumn`该类的实现类。该属性的作用可以查看[扩展MyBatis Generator](http://mbg.cndocs.ml/reference/extending.html)。
 
@@ -86,15 +88,15 @@
 <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
 使用`MyBatis3Simple`可以避免在后面的`<table>`中逐个进行配置（后面会提到）
 MBG配置中的其他几个元素，基本上都是`<context>`的子元素，这些子元素（有严格的配置顺序）包括：
-*   [`<property>`](http://mbg.cndocs.ml/configreference/property.html) (0个或多个)
-*   [`<plugin>`](http://mbg.cndocs.ml/configreference/plugin.html) (0个或多个)
-*   [`<commentGenerator>`](http://mbg.cndocs.ml/configreference/commentGenerator.html) (0个或1个)
-*   [`<jdbcConnection>`](http://mbg.cndocs.ml/configreference/jdbcConnection.html) (1个)
-*   [`<javaTypeResolver>`](http://mbg.cndocs.ml/configreference/javaTypeResolver.html) (0个或1个)
-*   [`<javaModelGenerator>`](http://mbg.cndocs.ml/configreference/javaModelGenerator.html) (1个)
-*   [`<sqlMapGenerator>`](http://mbg.cndocs.ml/configreference/sqlMapGenerator.html) (0个或1个)
-*   [`<javaClientGenerator>`](http://mbg.cndocs.ml/configreference/javaClientGenerator.html) (0个或1个)
-*   [`<table>`](http://mbg.cndocs.ml/configreference/table.html) (1个或多个)
+*   [`<property>`](http://mbg.cndocs.ml/configreference/property.html) (0个或多个)
+*   [`<plugin>`](http://mbg.cndocs.ml/configreference/plugin.html) (0个或多个)
+*   [`<commentGenerator>`](http://mbg.cndocs.ml/configreference/commentGenerator.html) (0个或1个)
+*   [`<jdbcConnection>`](http://mbg.cndocs.ml/configreference/jdbcConnection.html) (1个)
+*   [`<javaTypeResolver>`](http://mbg.cndocs.ml/configreference/javaTypeResolver.html) (0个或1个)
+*   [`<javaModelGenerator>`](http://mbg.cndocs.ml/configreference/javaModelGenerator.html) (1个)
+*   [`<sqlMapGenerator>`](http://mbg.cndocs.ml/configreference/sqlMapGenerator.html) (0个或1个)
+*   [`<javaClientGenerator>`](http://mbg.cndocs.ml/configreference/javaClientGenerator.html) (0个或1个)
+*   [`<table>`](http://mbg.cndocs.ml/configreference/table.html) (1个或多个)
 
 其中`<property>`属性比较特殊，后面讲解的时候都会和父元素一起进行讲解。在讲解`<property>`属性前，我们先看看**什么是分隔符？**。
 
@@ -123,7 +125,7 @@ select * from `user info`
 
 接下来分节对其他的子元素逐个进行介绍。
 
-##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-1-a-href-http-generator-sturgeon-mopaas-com-configreference-plugin-html-code-lt-plugin-gt-code-a-%E5%85%83%E7%B4%A0)3.3.1 [`<plugin>`](http://mbg.cndocs.ml/configreference/plugin.html) 元素
+##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-1-a-href-http-generator-sturgeon-mopaas-com-configreference-plugin-html-code-lt-plugin-gt-code-a-%E5%85%83%E7%B4%A0)3.3.1 [`<plugin>`](http://mbg.cndocs.ml/configreference/plugin.html) 元素
 
 该元素可以配置0个或者多个，不受限制。
 
@@ -133,7 +135,7 @@ select * from `user info`
 
 有关插件的详细信息可以参考[开发插件](http://mbg.cndocs.ml/reference/pluggingIn.html)和[提供的插件](http://mbg.cndocs.ml/reference/plugins.html)了解更多。
 
-##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-2-a-href-http-generator-sturgeon-mopaas-com-configreference-commentgenerator-html-code-lt-commentgenerator-gt-code-a-%E5%85%83%E7%B4%A0)3.3.2 [`<commentGenerator>`](http://mbg.cndocs.ml/configreference/commentGenerator.html) 元素
+##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-2-a-href-http-generator-sturgeon-mopaas-com-configreference-commentgenerator-html-code-lt-commentgenerator-gt-code-a-%E5%85%83%E7%B4%A0)3.3.2 [`<commentGenerator>`](http://mbg.cndocs.ml/configreference/commentGenerator.html) 元素
 
 该元素最多可以配置1个。
 
@@ -174,7 +176,7 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 <pre style="margin-top: 0px !important; margin-right: 0px; margin-bottom: 0px !important; margin-left: 0px; padding: 6px 10px; font-weight: 400; box-sizing: border-box; background-color: rgb(248, 248, 248); font-family: Menlo, &quot;Liberation Mono&quot;, Consolas, &quot;Courier New&quot;, &quot;andale mono&quot;, &quot;lucida console&quot;, monospace; font-size: 13px; line-height: 19px; color: rgb(0, 0, 0); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; border: 1px solid rgb(204, 204, 204); overflow: auto;"><commentGenerator type="com.github.abel533.mybatis.generator.MyCommentGenerator"/>
 </pre>
 
-##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-3-a-href-http-generator-sturgeon-mopaas-com-configreference-jdbcconnection-html-code-lt-jdbcconnection-gt-code-a-%E5%85%83%E7%B4%A0)3.3.3 [`<jdbcConnection>`](http://mbg.cndocs.ml/configreference/jdbcConnection.html) 元素
+##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-3-a-href-http-generator-sturgeon-mopaas-com-configreference-jdbcconnection-html-code-lt-jdbcconnection-gt-code-a-%E5%85%83%E7%B4%A0)3.3.3 [`<jdbcConnection>`](http://mbg.cndocs.ml/configreference/jdbcConnection.html) 元素
 
 `<jdbcConnection>`用于指定数据库连接信息，该元素必选，并且只能有一个。
 
@@ -201,7 +203,7 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 </jdbcConnection>
 </pre>
 
-##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-4-a-href-http-generator-sturgeon-mopaas-com-configreference-javatyperesolver-html-code-lt-javatyperesolver-gt-code-a-%E5%85%83%E7%B4%A0)3.3.4 [`<javaTypeResolver>`](http://mbg.cndocs.ml/configreference/javaTypeResolver.html) 元素
+##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-4-a-href-http-generator-sturgeon-mopaas-com-configreference-javatyperesolver-html-code-lt-javatyperesolver-gt-code-a-%E5%85%83%E7%B4%A0)3.3.4 [`<javaTypeResolver>`](http://mbg.cndocs.ml/configreference/javaTypeResolver.html) 元素
 
 该元素最多可以配置一个。
 
@@ -227,7 +229,7 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 </javaTypeResolver>
 </pre>
 
-##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-5-a-href-http-generator-sturgeon-mopaas-com-configreference-javamodelgenerator-html-code-lt-javamodelgenerator-gt-code-a-%E5%85%83%E7%B4%A0)3.3.5 [`<javaModelGenerator>`](http://mbg.cndocs.ml/configreference/javaModelGenerator.html) 元素
+##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-5-a-href-http-generator-sturgeon-mopaas-com-configreference-javamodelgenerator-html-code-lt-javamodelgenerator-gt-code-a-%E5%85%83%E7%B4%A0)3.3.5 [`<javaModelGenerator>`](http://mbg.cndocs.ml/configreference/javaModelGenerator.html) 元素
 
 该元素必须配置一个，并且最多一个。
 
@@ -259,7 +261,7 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 </javaModelGenerator>
 </pre>
 
-##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-6-a-href-http-generator-sturgeon-mopaas-com-configreference-sqlmapgenerator-html-code-lt-sqlmapgenerator-gt-code-a-%E5%85%83%E7%B4%A0)3.3.6 [`<sqlMapGenerator>`](http://mbg.cndocs.ml/configreference/sqlMapGenerator.html) 元素
+##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-6-a-href-http-generator-sturgeon-mopaas-com-configreference-sqlmapgenerator-html-code-lt-sqlmapgenerator-gt-code-a-%E5%85%83%E7%B4%A0)3.3.6 [`<sqlMapGenerator>`](http://mbg.cndocs.ml/configreference/sqlMapGenerator.html) 元素
 
 该元素可选，最多配置一个。但是有如下两种必选的特殊情况：
 
@@ -286,7 +288,7 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 </sqlMapGenerator>
 </pre>
 
-##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-7-a-href-http-generator-sturgeon-mopaas-com-configreference-javaclientgenerator-html-code-lt-javaclientgenerator-gt-code-a-%E5%85%83%E7%B4%A0)3.3.7 [`<javaClientGenerator>`](http://mbg.cndocs.ml/configreference/javaClientGenerator.html) 元素
+##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-7-a-href-http-generator-sturgeon-mopaas-com-configreference-javaclientgenerator-html-code-lt-javaclientgenerator-gt-code-a-%E5%85%83%E7%B4%A0)3.3.7 [`<javaClientGenerator>`](http://mbg.cndocs.ml/configreference/javaClientGenerator.html) 元素
 
 该元素可选，最多配置一个。
 
@@ -332,12 +334,11 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 
 配置示例：
 
-```bash
-<javaClientGenerator type="XMLMAPPER" targetPackage="test.dao"
+<pre style="margin-top: 0px !important; margin-right: 0px; margin-bottom: 0px !important; margin-left: 0px; padding: 6px 10px; font-weight: 400; box-sizing: border-box; background-color: rgb(248, 248, 248); font-family: Menlo, &quot;Liberation Mono&quot;, Consolas, &quot;Courier New&quot;, &quot;andale mono&quot;, &quot;lucida console&quot;, monospace; font-size: 13px; line-height: 19px; color: rgb(0, 0, 0); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; border: 1px solid rgb(204, 204, 204); overflow: auto;"><javaClientGenerator type="XMLMAPPER" targetPackage="test.dao"
               targetProject="src\main\java"/>
-```
+</pre>
 
-### 3.3.8 [`<table>`] 元素
+##### [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#3-3-8-a-href-http-generator-sturgeon-mopaas-com-configreference-table-html-code-lt-table-gt-code-a-%E5%85%83%E7%B4%A0)3.3.8 [`<table>`](http://mbg.cndocs.ml/configreference/table.html) 元素
 
 该元素至少要配置一个，可以配置多个。
 
@@ -349,9 +350,6 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 
 例如要生成全部的表，可以按如下配置：
 
-```bash
-<table tableName="%" />
-```
 该元素包含多个可选属性：
 
 *   `schema`:数据库的schema,可以使用[SQL通配符](http://www.w3school.com.cn/sql/sql_wildcards.asp)匹配。如果设置了该值，生成SQL的表名会变成如`schema.tableName`的形式。
@@ -371,7 +369,8 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 *   `constructorBased`:和`<javaModelGenerator>`中的属性含义一样。
 *   `ignoreQualifiersAtRuntime`:生成的SQL中的表名将不会包含`schema`和`catalog`前缀。
 *   `immutable`:和`<javaModelGenerator>`中的属性含义一样。
-*   `modelOnly`:此属性用于配置是否为表只生成实体类。如果设置为`true`就不会有Mapper接口。如果配置了`<sqlMapGenerator>`，并且`modelOnly`为`true`，那么XML映射文件中只有实体对象的映射元素(`<resultMap>`)。如果为`true`还会覆盖属性中的`enableXXX`方法，将不会生成任何CRUD方法。
+*   `modelOnly`
+此属性用于配置是否为表只生成实体类。如果设置为`true`就不会有Mapper接口。如果配置了`<sqlMapGenerator>`，并且`modelOnly`为`true`，那么XML映射文件中只有实体对象的映射元素(`<resultMap>`)。如果为`true`还会覆盖属性中的`enableXXX`方法，将不会生成任何CRUD方法。
 *   `rootClass`:和`<javaModelGenerator>`中的属性含义一样。
 *   `rootInterface`:和`<javaClientGenerator>`中的属性含义一样。
 *   `runtimeCatalog`:运行时的`catalog`，当生成表和运行环境的表的`catalog`不一样的时候可以使用该属性进行配置。
@@ -384,14 +383,14 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 
 除了`<property>`子元素外，`<table>`还包含以下子元素：
 
-*   [`<generatedKey>`](http://mbg.cndocs.ml/configreference/generatedKey.html) (0个或1个)
-*   [`<columnRenamingRule>`](http://mbg.cndocs.ml/configreference/columnRenamingRule.html) (0个或1个)
-*   [`<columnOverride>`](http://mbg.cndocs.ml/configreference/columnOverride.html) (0个或多个)
-*   [`<ignoreColumn>`](http://mbg.cndocs.ml/configreference/ignoreColumn.html) (0个或多个)
+*   [`<generatedKey>`](http://mbg.cndocs.ml/configreference/generatedKey.html) (0个或1个)
+*   [`<columnRenamingRule>`](http://mbg.cndocs.ml/configreference/columnRenamingRule.html) (0个或1个)
+*   [`<columnOverride>`](http://mbg.cndocs.ml/configreference/columnOverride.html) (0个或多个)
+*   [`<ignoreColumn>`](http://mbg.cndocs.ml/configreference/ignoreColumn.html) (0个或多个)
 
 下面对这4个元素进行详细讲解。
 
-1. [`<generatedKey>`](http://mbg.cndocs.ml/configreference/generatedKey.html) 元素
+1. [`<generatedKey>`](http://mbg.cndocs.ml/configreference/generatedKey.html) 元素
 
 这个元素最多可以配置一个。
 这个元素用来指定自动生成主键的属性（identity字段或者sequences序列）。如果指定这个元素，MBG在生成insert的SQL映射文件中插入一个`<selectKey>`元素。 这个元素**非常重要**，这个元素包含下面两个必选属性：
@@ -408,7 +407,7 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
     *   MySql
     *   SqlServer
     *   SYBASE
-    *   JDBC:这会配置MBG使用MyBatis3支持的JDBC标准的生成key来生成代码。 这是一个独立于数据库获取标识列中的值的方法。 重要: 只有当目标运行为MyBatis3时才会产生正确的代码。 如果与iBATIS2一起使用目标运行时会产生运行时错误的代码。
+    *   JDBC:这会配置MBG使用MyBatis3支持的JDBC标准的生成key来生成代码。 这是一个独立于数据库获取标识列中的值的方法。 重要: 只有当目标运行为MyBatis3时才会产生正确的代码。 如果与iBATIS2一起使用目标运行时会产生运行时错误的代码。
 
 这个元素还包含两个可选属性：
 
@@ -424,38 +423,34 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 
 对应的生成的结果：
 
-```xml
-<insert id="insert" parameterType="test.model.UserLoginInfo">
+<pre style="margin-top: 0px !important; margin-right: 0px; margin-bottom: 0px !important; margin-left: 0px; padding: 6px 10px; font-weight: 400; box-sizing: border-box; background-color: rgb(248, 248, 248); font-family: Menlo, &quot;Liberation Mono&quot;, Consolas, &quot;Courier New&quot;, &quot;andale mono&quot;, &quot;lucida console&quot;, monospace; font-size: 13px; line-height: 19px; color: rgb(0, 0, 0); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; border: 1px solid rgb(204, 204, 204); overflow: auto;"><insert id="insert" parameterType="test.model.UserLoginInfo">
     <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
         SELECT LAST_INSERT_ID()
     </selectKey>
     insert into `user login info` (Id, username, logindate, loginip)
     values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{logindate,jdbcType=TIMESTAMP}, #{loginip,jdbcType=VARCHAR})
 </insert>
-```
+</pre>
 
 配置示例二：
 
-```xml
-<table tableName="user login info" domainObjectName="UserLoginInfo">
+<pre style="margin-top: 0px !important; margin-right: 0px; margin-bottom: 0px !important; margin-left: 0px; padding: 6px 10px; font-weight: 400; box-sizing: border-box; background-color: rgb(248, 248, 248); font-family: Menlo, &quot;Liberation Mono&quot;, Consolas, &quot;Courier New&quot;, &quot;andale mono&quot;, &quot;lucida console&quot;, monospace; font-size: 13px; line-height: 19px; color: rgb(0, 0, 0); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; border: 1px solid rgb(204, 204, 204); overflow: auto;"><table tableName="user login info" domainObjectName="UserLoginInfo">
     <generatedKey column="id" sqlStatement="select SEQ_ID.nextval from dual"/>
 </table>
-```
+</pre>
+
 对应的生成结果：
 
-```xml
-<insert id="insert" parameterType="test.model.UserLoginInfo">
+<pre style="margin-top: 0px !important; margin-right: 0px; margin-bottom: 0px !important; margin-left: 0px; padding: 6px 10px; font-weight: 400; box-sizing: border-box; background-color: rgb(248, 248, 248); font-family: Menlo, &quot;Liberation Mono&quot;, Consolas, &quot;Courier New&quot;, &quot;andale mono&quot;, &quot;lucida console&quot;, monospace; font-size: 13px; line-height: 19px; color: rgb(0, 0, 0); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; border: 1px solid rgb(204, 204, 204); overflow: auto;"><insert id="insert" parameterType="test.model.UserLoginInfo">
     <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.Integer">
         select SEQ_ID.nextval from dual
     </selectKey>
     insert into `user login info` (Id, username, logindate, loginip)
     values (#{id,jdbcType=INTEGER}, #{username,jdbcType=VARCHAR}, #{logindate,jdbcType=TIMESTAMP},#{loginip,jdbcType=VARCHAR})
 </insert>
-```
-
-
+</pre>
 
-2. [`<columnRenamingRule>`](http://mbg.cndocs.ml/configreference/columnRenamingRule.html) 元素
+2. [`<columnRenamingRule>`](http://mbg.cndocs.ml/configreference/columnRenamingRule.html) 元素
 
 该元素最多可以配置一个，使用该元素可以在生成列之前，对列进行重命名。这对那些存在同一前缀的字段想在生成属性名时去除前缀的表非常有用。 例如假设一个表包含以下的列：
 
@@ -466,9 +461,8 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 
 生成的所有属性名中如果都包含`CUST`的前缀可能会让人不爽。这些前缀可以通过如下方式定义重命名规则:
 
-```bash
-<columnRenamingRule searchString="^CUST_" replaceString="" />
-```
+<pre style="margin-top: 0px !important; margin-right: 0px; margin-bottom: 0px !important; margin-left: 0px; padding: 6px 10px; font-weight: 400; box-sizing: border-box; background-color: rgb(248, 248, 248); font-family: Menlo, &quot;Liberation Mono&quot;, Consolas, &quot;Courier New&quot;, &quot;andale mono&quot;, &quot;lucida console&quot;, monospace; font-size: 13px; line-height: 19px; color: rgb(0, 0, 0); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; border: 1px solid rgb(204, 204, 204); overflow: auto;"><columnRenamingRule searchString="^CUST_" replaceString="" />
+</pre>
 
 注意，在内部，MBG使用`java.util.regex.Matcher.replaceAll`方法实现这个功能。 请参阅有关该方法的文档和在Java中使用正则表达式的例子。
 
@@ -484,7 +478,7 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 
 关于`<table>`的`<property>`属性`useActualColumnNames`对此的影响可以查看完整文档。
 
-3. [`<columnOverride>`](http://mbg.cndocs.ml/configreference/columnOverride.html) 元素
+3. [`<columnOverride>`](http://mbg.cndocs.ml/configreference/columnOverride.html) 元素
 
 该元素可选，可以配置多个。
 
@@ -496,28 +490,20 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 
 该元素有多个可选属性：
 
-- `property`
-要使用的Java属性的名称。如果没有指定，MBG会根据列名生成。 例如，如果一个表的一列名为`STRT_DTE`，MBG会根据`<table>`的`useActualColumnNames`属性生成`STRT_DTE`或`strtDte`。
-- `javaType`
-完全限定的Java类型。如果需要，这可以覆盖由`JavaTypeResolver`计算出的类型。 对某些DB，这是必要的用来处理**“奇怪的”**数据库类型（例如MySQL的`unsigned bigint`类型需要映射为`java.lang.Object`)
-- `jdbcType`
-该列的JDBC类型(`INTEGER, DECIMAL, NUMERIC, VARCHAR`等)。 如果需要，这可以覆盖由`JavaTypeResolver`计算出的类型。 对某些数据库来说，这是必要的用来处理怪异的JDBC驱动 (例如DB2的`LONGVARCHAR`类型需要为iBATIS 映射为`VARCHAR`)
-- `typeHandler`
-用户定义的需要用来处理这列的类型处理器。它必须是一个继承iBATIS的`TypeHandler`类或`TypeHandlerCallback`接口（该接口很容易继承）的全限定的类名。如果没有指定或者是空白，iBATIS会用默认的类型处理器来处理类型。**重要**:MBG不会校验这个类型处理器是否存在或者可用。 MGB只是简单的将这个值插入到生成的SQL映射的配置文件中。
--  `delimitedColumnName`
-是否应在生成的SQL的列名称上增加**分隔符**。 如果列的名称中包含空格，MGB会自动添加**分隔符**， 所以这个重写只有当列名需要强制为一个合适的名字或者列名是数据库中的保留字时是必要的。
+*   `property`:要使用的Java属性的名称。如果没有指定，MBG会根据列名生成。 例如，如果一个表的一列名为`STRT_DTE`，MBG会根据`<table>`的`useActualColumnNames`属性生成`STRT_DTE`或`strtDte`。
+*   `javaType`:该列属性值为完全限定的Java类型。如果需要，这可以覆盖由`JavaTypeResolver`计算出的类型。 对某些数据库来说，这是必要的用来处理**“奇怪的”**数据库类型（例如MySql的`unsigned bigint`类型需要映射为`java.lang.Object`)。
+*   `jdbcType`:该列的JDBC类型(`INTEGER, DECIMAL, NUMERIC, VARCHAR`等等)。 如果需要，这可以覆盖由`JavaTypeResolver`计算出的类型。 对某些数据库来说，这是必要的用来处理怪异的JDBC驱动 (例如DB2的`LONGVARCHAR`类型需要为iBATIS 映射为`VARCHAR`)。
+*   `typeHandler`:用户定义的需要用来处理这列的类型处理器。它必须是一个继承iBATIS的`TypeHandler`类或`TypeHandlerCallback`接口（该接口很容易继承）的全限定的类名。如果没有指定或者是空白，iBATIS会用默认的类型处理器来处理类型。**重要**:MBG不会校验这个类型处理器是否存在或者可用。 MGB只是简单的将这个值插入到生成的SQL映射的配置文件中。
+*   `delimitedColumnName`:指定是否应在生成的SQL的列名称上增加**分隔符**。 如果列的名称中包含空格，MGB会自动添加**分隔符**， 所以这个重写只有当列名需要强制为一个合适的名字或者列名是数据库中的保留字时是必要的。
 
 配置示例：
 
-```bash
-<table schema="DB2ADMIN" tableName="ALLTYPES" >
+<pre style="margin-top: 0px !important; margin-right: 0px; margin-bottom: 0px !important; margin-left: 0px; padding: 6px 10px; font-weight: 400; box-sizing: border-box; background-color: rgb(248, 248, 248); font-family: Menlo, &quot;Liberation Mono&quot;, Consolas, &quot;Courier New&quot;, &quot;andale mono&quot;, &quot;lucida console&quot;, monospace; font-size: 13px; line-height: 19px; color: rgb(0, 0, 0); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; border: 1px solid rgb(204, 204, 204); overflow: auto;"><table schema="DB2ADMIN" tableName="ALLTYPES" >
     <columnOverride column="LONG_VARCHAR_FIELD" javaType="java.lang.String" jdbcType="VARCHAR" />
 </table>
-```
-
-
+</pre>
 
-4. [`<ignoreColumn>`](http://mbg.cndocs.ml/configreference/ignoreColumn.html) 元素
+4. [`<ignoreColumn>`](http://mbg.cndocs.ml/configreference/ignoreColumn.html) 元素
 
 该元素可选，可以配置多个。
 
@@ -531,8 +517,12 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 
 *   `delimitedColumnName`:匹配列名的时候是否区分大小写。如果为`true`则区分。默认值为`false`，不区分大小写。
 
-# MyBatis Generator最佳实践
-针对MyBatis3
+## [](http://git.oschina.net/free/Mybatis_Utils/blob/master/MybatisGeneator/MybatisGeneator.md#mybatis-generator%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5)MyBatis Generator最佳实践
+
+本节内容针对MyBatis3，使用iBATIS的不一定适用。
+
+以下根据个人经验（对此有意见的可以留言）对一些配置看法列出如下几点：
+
 1.  关于实体类的`modelType`，建议使用`defaultModelType="flat"`，只有一个对象的情况下管理毕竟方便，使用也简单。
 
 2.  关于注释`<commentGenerator>`，不管你是否要重写自己的注释生成器，有一点不能忘记，那就是注释中一定要保留`@mbggenerated`,MBG通过该字符串来判断代码是否为代码生成器生成的代码，有该标记的的代码在重新生成的时候会被删除，不会重复。不会在XML中出现重复元素。
@@ -553,8 +543,7 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
 
 综合以上信息，这里给出一个`Mysql`的简单配置：
 
-```bash
-<?xml version="1.0" encoding="UTF-8"?>
+<pre style="margin-top: 0px !important; margin-right: 0px; margin-bottom: 0px !important; margin-left: 0px; padding: 6px 10px; font-weight: 400; box-sizing: border-box; background-color: rgb(248, 248, 248); font-family: Menlo, &quot;Liberation Mono&quot;, Consolas, &quot;Courier New&quot;, &quot;andale mono&quot;, &quot;lucida console&quot;, monospace; font-size: 13px; line-height: 19px; color: rgb(0, 0, 0); word-break: break-all; word-wrap: break-word; white-space: pre-wrap; border: 1px solid rgb(204, 204, 204); overflow: auto;"><?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE generatorConfiguration
         PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
         "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
@@ -587,9 +576,9 @@ public void addFieldComment(Field field, IntrospectedTable introspectedTable, In
         </table>
     </context>
 </generatorConfiguration>
-```
+</pre>
+
 `<table>`这里用的通配符匹配全部的表，另外所有表都有自动增长的`id`字段。如果不是所有表的配置都一样，可以做针对性的配置。
 
-**改动：** 去掉来不建议使用Example查询的建议，Example在单表操作上优势明显，可以看个人情况使用。
-参考
-- [MyBatis Generator中文文档](http://mbg.cndocs.ml/)
\ No newline at end of file
+**改动：**去掉来不建议使用Example查询的建议，Example在单表操作上优势明显，可以看个人情况使用。
+# [MyBatis Generator中文文档](http://mbg.cndocs.ml/)
diff --git "a/MyBatis/MyBatis\345\256\236\346\210\230(\344\270\200)-\347\274\223\345\255\230\346\234\272\345\210\266\347\232\204\350\256\276\350\256\241.md" "b/MyBatis/MyBatis\345\256\236\346\210\230(\344\270\200)-\347\274\223\345\255\230\346\234\272\345\210\266\347\232\204\350\256\276\350\256\241.md"
new file mode 100644
index 0000000000..22917b0949
--- /dev/null
+++ "b/MyBatis/MyBatis\345\256\236\346\210\230(\344\270\200)-\347\274\223\345\255\230\346\234\272\345\210\266\347\232\204\350\256\276\350\256\241.md"
@@ -0,0 +1,25 @@
+# 1 数据缓存设计结构
+## 1.1  一级缓存
+ Session会话级别的缓存，位于表示一次数据库会话的`SqlSession`对象之中，又被称之为`本地缓存`
+一级缓存是MyBatis内部实现的一个特性，`用户不能配置`，`默认情况下自动支持的缓存`，一般用户没有定制它的权利
+## 1.2 二级缓存
+Application应用级别的缓存，生命周期长，跟**Application**的生命周期一样，即作用范围为整个**Application**应用
+![缓存架构](https://upload-images.jianshu.io/upload_images/4685968-389b17ae58a342b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 2 工作机制
+## 2.1 一级缓存的工作机制
+ 一级缓存是**Session**会话级别的，一般而言，一个**SqlSession**对象会使用一个**Executor**对象来完成会话操作，**Executor**对象会维护一个**Cache**缓存，以提高查询性能
+![](https://upload-images.jianshu.io/upload_images/4685968-3cb4fdced66fa14c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 2.2 二级缓存的工作机制
+如上所言，一个**SqlSession**对象会使用一个**Executor**对象来完成会话操作，**MyBatis**的二级缓存机制的关键就是对这个**Executor**对象做文章
+如果用户配置了`cacheEnabled=true`，那么在为**SqlSession**对象创建**Executor**对象时，会对**Executor**对象加上一个装饰者 `CachingExecutor`，这时**SqlSession**使用`CachingExecutor`对象来完成操作请求
+**CachingExecutor**对于查询请求，会先判断该查询请求在**Application**级别的二级缓存中是否有缓存结果
+- 如果有查询结果，则直接返回缓存结果
+- 如果缓存未命中，再交给真正的**Executor**对象来完成查询操作，之后**CachingExecutor**会将真正**Executor**返回的查询结果放置到缓存中，然后再返回给用户
+
+
+> **MyBatis**的二级缓存设计得比较灵活，可以使用**MyBatis**自己定义的二级缓存实现
+也可以通过实现**org.apache.ibatis.cache.Cache**接口自定义缓存
+也可以使用第三方内存缓存库，如**Memcached**等
+
+![](https://upload-images.jianshu.io/upload_images/4685968-e634f3282ffe0775.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-89178fc0aaa38c16.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
diff --git "a/MyBatis/MyBatis\347\274\223\345\255\230\346\234\272\345\210\266\350\256\276\350\256\241.md" "b/MyBatis/MyBatis\347\274\223\345\255\230\346\234\272\345\210\266\350\256\276\350\256\241.md"
deleted file mode 100644
index 6e698ad42a..0000000000
--- "a/MyBatis/MyBatis\347\274\223\345\255\230\346\234\272\345\210\266\350\256\276\350\256\241.md"
+++ /dev/null
@@ -1,32 +0,0 @@
-# 1 数据缓存设计结构
-## 1.1  一级缓存
-Session会话级别的缓存，位于表示一次数据库会话的`SqlSession`对象之中，即`本地缓存`。
- 
-一级缓存是MyBatis内部实现的一个特性，`用户不能配置`，`默认自动支持`，用户无定制权利。
-
-## 1.2 二级缓存
-Application应用级别的缓存，生命周期长，跟**Application**的生命周期一样，即作用范围为整个**Application**应用。
-
-- 缓存架构
-![](https://img-blog.csdnimg.cn/img_convert/467c160b28d36dbc89025aae196fa514.png)
-# 2 工作机制
-## 2.1 一级缓存的工作机制
- 一级缓存是**Session**级别，一般一个**SqlSession**对象会使用一个**Executor**对象来完成会话操作，**Executor**对象会维护一个**Cache**缓存，以提高查询性能。
-![](https://img-blog.csdnimg.cn/img_convert/b8e294c3fca853664aa4397656f9f640.png)
-## 2.2 二级缓存的工作机制
-一个**SqlSession**对象会使用一个**Executor**对象来完成会话操作，**MyBatis**的二级缓存机制的关键就是对这个**Executor**对象做文章
-
-如果用户配置了`cacheEnabled=true`，那么在为**SqlSession**对象创建**Executor**对象时，会对**Executor**对象加上一个装饰者 `CachingExecutor`，这时**SqlSession**使用`CachingExecutor`对象来完成操作请求
-**CachingExecutor**对于查询请求，会先判断该查询请求在**Application**级别的二级缓存中是否有缓存结果
-- 如果有查询结果，则直接返回缓存结果
-- 如果缓存未命中，再交给真正的**Executor**对象来完成查询操作，之后**CachingExecutor**会将真正**Executor**返回的查询结果放置到缓存中，然后再返回给用户
-
-**MyBatis**的二级缓存设计得比较灵活，可以使用**MyBatis**自己定义的二级缓存实现。也可通过实现**org.apache.ibatis.cache.Cache**接口自定义缓存
-也可以使用第三方内存缓存库，如**Memcached**等。
-![](https://img-blog.csdnimg.cn/img_convert/ce84ae9d34d83b3f148b9d08132765e7.png)
-![](https://img-blog.csdnimg.cn/img_convert/b40150aeace181dcbc50d6ddfa4190f2.png)
-# 使用
-MyBatis: 
-```xml
-<cache type="org.mybatis.caches.ehcache.LoggingEhcache" > <property name="memoryStoreEvictionPolicy" value="LRU"/></cache> <select id="selectArticleListPage" resultMap="resultUserArticleList" useCache="false">
-```
\ No newline at end of file
diff --git "a/Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\346\225\231\347\250\213(8)-SocketChannel.md" "b/Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\346\225\231\347\250\213(8)-SocketChannel.md"
deleted file mode 100644
index e309870d9b..0000000000
--- "a/Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\346\225\231\347\250\213(8)-SocketChannel.md"
+++ /dev/null
@@ -1,75 +0,0 @@
-Java NIO中的SocketChannel是一个连接到TCP  网络套接字的通道。
-
-可通过如下方式创建SocketChannel：
-- 打开一个SocketChannel，并连接到网络上的某台服务器
-- 一个新连接到达ServerSocketChannel时，会创建一个SocketChannel
-
-# 打开 SocketChannel
-下面是SocketChannel的打开方式的简单用法：
-```java
-SocketChannel socketChannel = SocketChannel.open();
-socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
-```
-看看Tomcat#NioEndpoint是如何使用的
-```java
-ServerSocketChannel serverSock = ServerSocketChannel.open();
-socketProperties.setProperties(serverSock.socket());
-InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
-serverSock.socket().bind(addr,getAcceptCount());
-```
-
-# 关闭 SocketChannel
-当用完SocketChannel之后调用`SocketChannel.close()`关闭SocketChannel：
-```java
-socketChannel.close();
-```
-# 从 SocketChannel 读取数据
-要从SocketChannel中读取数据，调用一个read()的方法之一。如下：
-```java
-ByteBuffer buf = ByteBuffer.allocate(48);
-
-int bytesRead = socketChannel.read(buf);
-```
-首先，分配一个Buffer。从SocketChannel读取到的数据将会放到这个Buffer中。
-
-然后，调用SocketChannel.read()。该方法将数据从SocketChannel 读到Buffer中。read()方法返回的int值表示读了多少字节进Buffer里。如果返回的是-1，表示已经读到了流的末尾（连接关闭了）。
-
-# 写入 SocketChannel
-写数据到SocketChannel用的是`SocketChannel.write()`，该方法以一个Buffer作为参数。
-
-如下：
-```java
-String newData = "New String to write to file..." + System.currentTimeMillis();
-ByteBuffer buf = ByteBuffer.allocate(48);
-buf.clear();
-buf.put(newData.getBytes());
-buf.flip();
-while(buf.hasRemaining()) {
-    channel.write(buf);
-}
-```
-
-注意SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以，我们重复调用write()直到Buffer没有要写的字节为止。
-
-# 非阻塞模式
-可以设置 SocketChannel 为非阻塞模式（non-blocking mode）。设置后，就可以在异步模式下调用connect(), read() 和write()。
-
-## connect()
-如果SocketChannel在非阻塞模式下，此时调用connect()，该方法可能在连接建立之前就返回了。为了确定连接是否建立，可以调用finishConnect()的方法。像这样：
-
-```java
-socketChannel.configureBlocking(false);
-socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
-while(! socketChannel.finishConnect() ){
-    //wait, or do something else...
-}
-```
-
-## write()
-非阻塞模式下，write()方法在尚未写出任何内容时可能就返回了。所以需要在循环中调用write()。前面已经有例子了，这里就不赘述了。
-
-## read()
-非阻塞模式下,read()方法在尚未读取到任何数据时可能就返回了。所以需要关注它的int返回值，它会告诉你读取了多少字节。
-
-# 非阻塞模式与Selector
-非阻塞模式与Selectors搭配会工作的更好，通过将一或多个SocketChannel注册到Selector，可以询问选择器哪个通道已经准备好了读取，写入等。
\ No newline at end of file
diff --git "a/Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\347\274\226\347\250\213(6)-Selector\344\272\213\344\273\266\351\200\211\346\213\251\345\231\250.md" "b/Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\347\274\226\347\250\213(6)-Selector\344\272\213\344\273\266\351\200\211\346\213\251\345\231\250.md"
deleted file mode 100644
index d1de3ea3d2..0000000000
--- "a/Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\347\274\226\347\250\213(6)-Selector\344\272\213\344\273\266\351\200\211\346\213\251\345\231\250.md"
+++ /dev/null
@@ -1,270 +0,0 @@
-Selector提供选择执行已经就绪的任务的能力，使得多元 I/O 成为可能，就绪选择和多元执行使得单线程能够有效率地同时管理多个 I/O channel。 
-
-C/C++许多年前就已经有 select()和 poll()这两个POSIX（可移植性操作系统接口）系统调用可供使用。许多os也提供相似的功能，但对Java 程序员来说，就绪选择功能直到 JDK 1.4 才成为可行方案。
-
-# 简介
-获取到`SocketChannel`后，直接包装成一个任务，提交给线程池。
-引入Selector后， 需要将之前创建的一或多个可选择的Channel注册到Selector对象，一个键(`SelectionKey`)将会被返回。
-`SelectionKey` 会记住你关心的Channel，也会追踪对应的Channel是否已就绪。
-![](https://img-blog.csdnimg.cn/20201103004955128.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-每个Channel在注册到Selector时，都有一个感兴趣的操作。
-- **ServerSocketChannel** 只会在选择器上注册一个，其感兴趣的操作只有**ACCEPT**，表示其只关心客户端的连接请求
-- **SocketChannel**，通常会注册多个，因为一个server通常会接受到多个client的请求，就有对应数量的SocketChannel。SocketChannel感兴趣的操作是**CONNECT**、**READ**、**WRITE**，因为其要与server建立连接，也需要进行读、写数据。
-
-# 1 Selector
-## 1.1 API
-### open
-- 打开一个 selector
-新的selector是通过调用系统默认的SelectorProvider对象的openSelector方法而创建的。
-![](https://img-blog.csdnimg.cn/20201103010536593.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-- 注意到默认选择器提供者![](https://img-blog.csdnimg.cn/20201103011031429.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-- Mac下的JDK，所以我们需要下载对应平台下的 JDK 哦！
-![](https://img-blog.csdnimg.cn/20201103011146123.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-`Selector.open()`不是单例模式的，每次调用该静态方法，会返回新的Selector实例。
-# 2 SelectableChannel
-## 简介
-可通过 Selector 被多路复用的channel。
-
-## register
-为了与一个 selector 被使用，这个类的一个实例必须首先经由register方法。 该方法返回一个新SelectionKey表示与所述选择channel的注册对象。
-- 使用给定的Selector注册此channel，并返回Selectionkey
-![](https://img-blog.csdnimg.cn/20210518173303515.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-一旦与一个Selector注册，直到它的channel资源被关闭完毕。 这包括被分配到由选择的channel任何资源解除分配。
-
-当channel被关闭，该channel上的所有相关key会被自动取消，无论是通过调用其close方法或通过中断一个线程阻塞于所述channel的I/O操作。同时，该 channel 注册到的 selector 也会将该 key 设置为被取消键。
-![](https://img-blog.csdnimg.cn/20210528163415876.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210528163502158.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/2021052816381935.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-所以如果后续使用已被取消的 key，会报错：
-![](https://img-blog.csdnimg.cn/20210528162934579.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- 若是Selector本身被关闭，则所有注册到该Selector的channel都将被关闭，并且相关的键也立即被取消。
-
-虽然说一个channel可以被注册到多个Selector，但对每个Selector而言，该 channel只能被注册一次。
-
-无论是否channel与一个或多个选择可能通过调用来确定注册isRegistered方法。
-
-### 阻塞模式
-可选择的信道或者是在阻断模式或非阻塞模式。 在阻塞模式中，每一个I / O操作在所述信道调用将阻塞，直到它完成。 在非阻塞模式的I / O操作不会阻塞，并且可以传送比被要求或所有可能没有字节更少的字节。 可选择信道的阻塞模式可通过调用其来确定isBlocking方法。
-新创建的可选择通道总是处于阻塞模式。 非阻塞模式是在与基于选择复用相结合最有用的。 信道必须被放置到非阻塞模式与一个选择器注册之前，并且可以不被返回到直到它已被注销阻塞模式。
-
-Selector（选择器）是Java NIO中能够检测一到多个NIO通道，并能够知晓通道是否为诸如读写事件做好准备的组件。这样，一个单独的线程可以管理多个channel，从而管理多个网络连接。
-
-# 3 为什么使用Selector?
-Selector允许单线程处理多个Channel。使用Selector，首先得向Selector注册
-Channel，然后调用它的**select()**。该方法会一直阻塞，直到某个注册的Channel有事件就绪。一旦这个方法返回，线程就可以处理这些事件，事件的例子如新连接
-进来，数据接收等。
-
-**单线程处理多Channel的好处**：
-只需更少线程处理channel。事实上就是可以只用**一个线程处理所有Channel**。对于os，线程间上下文切换开销很大且每个线程都要占用系统资源。因此，使用线程越少越好。
-
-但现代os和CPU在多任务方面表现的越来越好，多线程开销变得越来越小。实际上，若CPU是多核的，不使用多任务可能就是在浪费CPU性能。使用Selector能够处理多个**通道**就足够了。
-
-- 单线程使用一个Selector处理3个channel示例图
-![](https://img-blog.csdnimg.cn/2020110400210082.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-> Selector选择器对象是线程安全的，但它们包含的键集合不是。通过keys()和selectKeys()返回的键的集合是Selector对象内部的私有的Set对象集合的直接引用。这些集合可能在任意时间被改变。已注册的键的集合是只读的。
-
-> 如果在多个线程并发地访问一个选择器的键的集合的时候存在任何问题，可以采用同步的方式进行访问，在执行选择操作时，选择器在Selector对象上进行同步，然后是已注册的键的集合，最后是已选择的键的集合。
-> 
-> 在并发量大的时候，使用同一个线程处理连接请求以及消息服务，可能会出现拒绝连接的情况，这是因为当该线程在处理消息服务的时候，可能会无法及时处理连接请求，从而导致超时；一个更好的策略是对所有的可选择通道使用一个选择器，并将对就绪通道的服务委托给其它线程。只需一个线程监控通道的就绪状态并使用一个协调好的的工作线程池来处理接收及发送数据
-
-# 4 Selector的创建
-通过调用Selector.open()方法创建一个Selector，如下：
-![](https://img-blog.csdnimg.cn/20201103211007422.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-# 5 向Selector注册Channel
-为了将Channel和Selector搭配使用，必须将channel注册到selector。
-通过`SelectableChannel.register()`：
-```java
-// 必须是非阻塞模式
-channel.configureBlocking(false);
-SelectionKey key = channel.register(selector, Selectionkey.OP_READ);
-```
-### configureBlocking
-`configureBlocking()`用于设置通道的阻塞模式，该方法会调用`implConfigureBlocking`
-![](https://img-blog.csdnimg.cn/2021052713243197.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-`implConfigureBlocking`会更改阻塞模式为新传入的值，默认为true，传入false，那么该通道将调整为非阻塞。而NIO最大优势就是非阻塞模型，所以一般都需要设置`SocketChannel.configureBlocking(false)`。
-可以通过调用`isBlocking()`判断某个socket通道当前处于何种模式。
-
-与Selector一起使用时，Channel必须处于非阻塞模式，所以不能将FileChannel和Selector一起使用，因为FileChannel不能切换到非阻塞模式。而socketChannel都可以。
-
-注意register()方法的第二个参数。这是一个“感兴趣的事件集合”，意思是在通过Selector监听Channel时，对什么事件感兴趣。可监听四种不同类型事件：
-- Read
-一个有数据可读的通道可以说是“读就绪”。
-- Write
-等待写数据的通道可以说是“写就绪”。
-- Connect
-通道触发了一个事件意思是该事件已经就绪。所以，某个channel成功连接到另一个服务器称为“连接就绪”。
-- Accept
-一个server socket channel准备好接收新进入的连接称为“接收就绪”。
-
-这四种事件用SelectionKey的四个常量来表示：
-![](https://img-blog.csdnimg.cn/20201103211755909.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-若对不止一种事件感兴趣，那么可以用“|”操作符将常量连接：
-```java
-int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
-```
-# 6 SelectionKey
-封装了特定的channel与特定的Selector的注册关系。
-![](https://img-blog.csdnimg.cn/20210528105807285.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-SelectionKey对象被SelectableChannel.register(Selector sel, int ops)返回并提供一个表示这种注册关系的标记。
-![](https://img-blog.csdnimg.cn/20210527144220779.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-**SelectionKey**包含两个比特集（以整数形式编码）：该注册关系所关心的channel操作及channel已就绪的操作。
-![](https://img-blog.csdnimg.cn/20210527144438676.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-> interestOps(int) 
-> 将此key的interest设置为给定值。 可以随时调用此方法。它是否阻塞以及持续多久取决于实现。
-
-- 兴趣set确定下一次调用选择器的选择方法之一时，将测试哪些操作类别是否准备就绪。使用创建key时给定的值来初始化兴趣set；以后可以通过interestOps(int)对其进行更改。
-- 准备集标识键的选择器已检测到键的通道已准备就绪的操作类别。创建密钥时，将就绪集初始化为零；否则，将其初始化为零。它可能稍后会在选择操作期间由选择器更新，但无法直接更新。
-
-向Selector注册Channel时，register()方法会返回一个SelectionKey对象，包含了一些你感兴趣的属性：
-
-- ready集合
-- Channel
-- Selector
-- 附加的对象（可选）
-interest集合
-## interest集合
-interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合，像这样：
-
-```java
-int interestSet = selectionKey.interestOps();
-boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT；
-boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
-boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
-boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;
-```
-
-“位与”interest 集合和给SelectionKey常量，可以确定某事件是否在interest 集合。
-
-## ready集合
-通道已经准备就绪的操作的集合。在一次选择(Selection)之后，你会首先访问这个ready set。可以这样访问ready集合：
-![](https://img-blog.csdnimg.cn/20201103212408880.png#pic_center)
-
-可用像检测interest集合那样检测channel中什么事件或操作已就绪。
-也可使用以下四个方法，它们都会返回一个布尔类型：
-
-```java
-selectionKey.isAcceptable();
-selectionKey.isConnectable();
-selectionKey.isReadable();
-selectionKey.isWritable();
-```
-
-> 推荐使用内部的已取消的键的集合来延迟注销，是一种防止线程在取消键时阻塞，并防止与正在进行的选择操作冲突的优化。
-
-## Channel + Selector
-从SelectionKey访问Channel和Selector很简单。如下：
-![](https://img-blog.csdnimg.cn/20201103212802626.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-- **SelectionKey.channel()** 返回的channel需要转型成你要处理的类型
-![](https://img-blog.csdnimg.cn/20210528151530345.png)
-
-![](https://img-blog.csdnimg.cn/20201103213124598.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-## 附加的对象
-### attach、attachment
-附加给定对象到该key。一个被附加的对象可能稍后就会被attachment获取。 只有一个对象可以在一个时间被附加。调用此方法会使先前的附加对象被丢弃。 当前附加对象可通过附加`null`丢弃。
-![](https://img-blog.csdnimg.cn/20201104002644277.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-![](https://img-blog.csdnimg.cn/20201104002815286.png#pic_center)
-
-可将一个对象或更多信息附到SelectionKey，方便识别channel。
-例如，可以附加与channel一起使用的Buffer，或是包含聚集数据的某个对象。
-```java
-selectionKey.attach(theObject);
-Object attachedObj = selectionKey.attachment();
-```
-
-还可用register()向Selector注册Channel的时候附加对象。如：
-```java
-SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
-```
-
-# 通过Selector选择通道
-一旦向Selector注册了一或多通道，就可调用重载的select()。这些方法返回你所感兴趣的事件（如连接、接受、读或写）已经准备就绪的那些通道。即如果你对“读就绪”通道感兴趣，select()方法会返回读事件已经就绪的那些通道。
-
-## select() API
-### select()
-- 阻塞，直到至少有一个channel在你注册的事件上就绪。
-![](https://img-blog.csdnimg.cn/20201104003219724.png#pic_center)
-
-select()的返回值不是已就绪的channel的总数，而是从上一个select()调用之后，进入就绪态的channel的数量。
-之前的调用中就绪的，并在本次调用中仍就绪的channel不会被计入。那些在前一次调用中已就绪，但已不再处于就绪态的通道也不会计入。这些channel可能仍然在SelectionKey集合中，但不会被计入返回值中，所以返回值可能是0。
-#### 优雅关闭执行select()的线程
-- 使用volatile boolean变量标识线程是否停止
-- 停止线程时，需要调用停止线程的interrupt()方法，因为线程有可能在wait()或sleep()，提高停止线程的及时性
-- 处于阻塞 IO的处理，尽量使用InterruptibleChannel来代替阻塞 IO。对于NIO，若线程处于select()阻塞状态，这时无法及时检测到条件变量变化，就需要人工调用wakeup()，唤醒线程，使得其可以检测到条件变量。
-
-
-### select(long timeout)
-![](https://img-blog.csdnimg.cn/20201104003551973.png#pic_center)
-和select()一样，只是规定了最长会阻塞timeout毫秒(参数)。
-
-### selectNow()
-![](https://img-blog.csdnimg.cn/20201104003621879.png#pic_center)
-不会阻塞，不管什么channel就绪都立刻返回（此方法执行非阻塞的选择操作。若自从上一次选择操作后，没有channel可选择，则此方法直接返回0）。
-
-select()系列方法返回的int值表示**有多少channel已就绪**，即自上次调用select()方法后有多少channel变成就绪状态。
-若调用select()方法，因为有一个channel变成就绪状态，返回了1，若再次调用select()方法，如果另一个通道就绪了，它会再次返回1。
-若对第一个就绪的channel没有做任何操作，现在就有两个已就绪channel。但是在每次select()方法调用之间，只有一个channel就绪了。
-
-### selectedKeys()
-一旦调用select()方法，并且返回值表明有一个或更多个通道就绪了，然后可以通过调用selector的selectedKeys()方法，访问“已选择键集（selected key set）”中的就绪通道：
-```java
-Set selectedKeys = selector.selectedKeys();
-```
-当像Selector注册Channel时，Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的Channel。可通过SelectionKey的selectedKeySet()方法访问这些对象。
-
-可遍历该**selectedKeys**访问就绪的Channel：
-![](https://img-blog.csdnimg.cn/20210528110235184.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-这个循环遍历已选择键集中的每个键，并检测各个键所对应的通道的就绪事件。
-
-注意每次迭代末尾调用`keyIterator.remove()`。Selector不会自己从**selectedKeys**中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时，Selector会再次将其放入**selectedKeys**。
-
-### wakeUp()
-某个线程调用 **select()** 后阻塞了，即使没有channel已就绪，也有办法让其从select()返回。
-只需通过其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()。阻塞在select()方法上的线程会立马返回。
-
-若有其它线程调用了wakeup()，但当前没有线程阻塞在select()，下个调用select()方法的线程会立即“醒来（wake up）”。
-
-作用总结：
-- 解除阻塞在Selector.select()/select(long)上的线程，立即返回
-- 两次成功的select之间多次调用wakeup等价于一次调用
-- 如果当前没有阻塞在select上，则本次wakeup调用将作用于下一次select操作
-
-### close()
-用完Selector后调用其close()会关闭该Selector，且使注册到该Selector上的所有SelectionKey实例无效。但channel本身并不会关闭。
-
-### 示例
-打开一个Selector，将一个channel注册到这个Selector，然后持续监控这个Selector的四种事件（接受，连接，读，写）是否就绪。
-
-```java
-Selector selector = Selector.open();
-channel.configureBlocking(false);
-SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
-while(true) {
-	int readyChannels = selector.select();
-	if(readyChannels == 0) continue;
-		Set selectedKeys = selector.selectedKeys();
-		Iterator keyIterator = selectedKeys.iterator();
-		while(keyIterator.hasNext()) {
-		SelectionKey key = keyIterator.next();
-	if(key.isAcceptable()) {
-		// a connection was accepted by a ServerSocketChannel.
-	} else if (key.isConnectable()) {
-		// a connection was established with a remote server.
-	} else if (key.isReadable()) {
-		// a channel is ready for reading
-	} else if (key.isWritable()) {
-	 	// a channel is ready for writing
-	}
-	keyIterator.remove();
-	}
-}
-```
\ No newline at end of file
diff --git "a/Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\347\274\226\347\250\213(3)-Channel.md" "b/Netty/NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213/Java NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213(\344\270\211)-Channel.md"
similarity index 100%
rename from "Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\347\274\226\347\250\213(3)-Channel.md"
rename to "Netty/NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213/Java NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213(\344\270\211)-Channel.md"
diff --git "a/Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\347\274\226\347\250\213(4)-ByteBuffer.md" "b/Netty/NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213/Java NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213(\345\233\233)-ByteBuf.md"
similarity index 56%
rename from "Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\347\274\226\347\250\213(4)-ByteBuffer.md"
rename to "Netty/NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213/Java NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213(\345\233\233)-ByteBuf.md"
index ba294f0f70..6d167907c9 100644
--- "a/Netty/NIO\347\275\221\347\273\234\347\274\226\347\250\213/NIO\347\275\221\347\273\234\347\274\226\347\250\213(4)-ByteBuffer.md"
+++ "b/Netty/NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213/Java NIO\351\253\230\346\200\247\350\203\275\347\275\221\347\273\234\347\274\226\347\250\213(\345\233\233)-ByteBuf.md"	
@@ -1,64 +1,50 @@
-NIO中的Buffer用于和NIO Channel交互。
-数据是从Channel读入Buffer，从Buffer写入Channel。
+Java NIO中的Buffer用于和NIO Channel交互。数据是从通道读入缓冲区，从缓冲区写入到通道中。
 
-Buffer本质上是块可以写入数据，然后可从中读数据的内存。这块内存被包装成NIO Buffer对象，并提供了一组方法，用来方便的访问该块内存。
+缓冲区本质上是块可以写入数据，然后可从中读数据的内存。这块内存被包装成NIO Buffer对象，并提供了一组方法，用来方便的访问该块内存。
 
-NIO数据传输基于buffer（java.nio.Buffer及相关类）。这些类表示连续的内存范围，以及少量的数据传输操作。尽管从理论上讲，这些是通用数据结构，但实现可以选择用于对齐或分页特性的内存，而这些内存在Java中是无法访问的。
-通常，这将用于允许缓冲区内容占用os用于其本地I/O操作的相同物理内存，从而允许最直接的传输机制，并消除了任何其他复制的需要。在大多数os中，只要特定的内存区域具有正确的属性，就可以在不使用CPU情况下进行传输。为了支持这些目标，有意限制了NIO Buffer的功能。
-
-除布尔值外，其他所有Java基本类型都有缓冲区类，布尔型可以与字节缓冲区共享内存并允许对底层字节进行任意解释。
 # 1 基本用法
-使用Buffer读写数据一般遵循如下步骤：
+使用Buffer读写数据一般遵循以下四个步骤：
 
 1. 写数据到Buffer
 2. 调用`buffer.flip()`
-3. 从Buffer中读数据
+3. 从Buffer中读取数据
 4. 调用clear()或compact()
 
-当向buffer写数据时，buffer会记录写了多少数据。一旦要读取数据，需通过flip()将Buffer从**写模式切到读模式**。
-**读模式**下，可读之前写到buffer的所有数据。
+当向buffer写数据时，buffer会记录写了多少数据。一旦要读取数据，需通过flip()将Buffer从写模式切到读模式。
+在读模式下，可读之前写到buffer的所有数据。
 
-一旦读完数据，就需清空缓冲区，让它可以再次被写入。
-有两种方式能清空缓冲区：
-- clear()方法
-会清空整个缓冲区
-- compact()方法
-只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处，新写入的数据将放到缓冲区未读数据的后面。
+一旦读完数据，就需要清空缓冲区，让它可以再次被写入。有两种方式能清空缓冲区：调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处，新写入的数据将放到缓冲区未读数据的后面。
 
 # 2 使用Buffer的案例
+
 ```java
 RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
 FileChannel inChannel = aFile.getChannel();
 
-// 创建容量为48字节的缓冲区
+//create buffer with capacity of 48 bytes
 ByteBuffer buf = ByteBuffer.allocate(48);
-// 读进buffer 从该Channel中将字节序列读取到给定buffer
-int bytesRead = inChannel.read(buf);
-// 什么时候会读到-1呢？
-// 对于服务器端，当客户端调用了channel.close()关闭连接时，这时服务器端返回的读取数是-1，表示已到末尾
-// 那么此时需要把对应SelectionKey给cancel掉，表示selector不再监听这个channel上的读事件，并关闭channel
+
+int bytesRead = inChannel.read(buf); //read into buffer.
 while (bytesRead != -1) {
-  // make buffer ready for read	
-  buf.flip();
-  while(buf.hasRemaining()) {
-  	  // read 1 byte at a time
-  	  // 在从channel往buffer中读入后，使用byteBuffer.get()获取时，不可重复调用，因为get()会移动position
-  	  // 使得多次调用get()获取的内容是不同的
-      System.out.print((char) buf.get());
+
+  buf.flip();  //make buffer ready for read
+
+  while(buf.hasRemaining()){
+      System.out.print((char) buf.get()); // read 1 byte at a time
   }
-  // make buffer ready for writing	
-  buf.clear();
+
+  buf.clear(); //make buffer ready for writing
   bytesRead = inChannel.read(buf);
 }
 aFile.close();
 ```
 
-#  3 Buffer的capacity、position和limit
+#  3 Buffer的capacity,position和limit
+
 ![](https://img-blog.csdnimg.cn/20201105005432930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 
 ## 3.1 capacity
-作为一个内存块，Buffer有个固定大小，即`capacity`。
-你只能往里写`capacity`个byte、long，char等。一旦Buffer满，需将其清空（通过读或清除数据）才能继续往里写数据。
+作为一个内存块，Buffer有个固定大小，即`capacity`。你只能往里写`capacity`个byte、long，char等。一旦Buffer满，需将其清空（通过读或清除数据）才能继续往里写数据。
 
 ## 3.2 position
 取决于Buffer处在读还是写模式：
@@ -82,7 +68,6 @@ aFile.close();
 # 5 Buffer的分配
 要想获得一个Buffer对象首先要进行分配。 每个Buffer类都有一个allocate方法。
 
-## allocate
 分配48字节capacity的ByteBuffer的例子。
 ```java
 ByteBuffer buf = ByteBuffer.allocate(48);
@@ -91,26 +76,21 @@ ByteBuffer buf = ByteBuffer.allocate(48);
 ```java
 CharBuffer buf = CharBuffer.allocate(1024);
 ```
-## allocateDirect
-使用allocate创建的缓冲区，并不是一下就分配给缓冲区capacity大小的空间，而是根据缓冲区中存储数据的情况来动态分配缓冲区的大小（底层采用堆数据结构管理缓冲区大小），因此，这个capacity可以是一个很大的值，如1024*1024（1M）。
-
-使用allocateDirect可一次性分配capacity大小的连续字节空间。通过allocateDirect方法来创建具有连续空间的ByteBuffer对象虽然可以在一定程度上提高效率，但这种方式并不是平台独立的。而且allocateDirect方法需要较长的时间来分配内存空间，在释放空间时也较慢。因此，慎用allocateDirect。
-
 
 # 6 向Buffer中写数据
 写数据到Buffer有两种方式：
-## read
-从Channel写到Buffer
+1. 从Channel写到Buffer
+
 ```java
-// read into buffer.
-int bytesRead = inChannel.read(buf);
+int bytesRead = inChannel.read(buf); //read into buffer.
 ```
-## put
-通过Buffer的put()方法写到Buffer
+2. 通过Buffer的put()方法写到Buffer里。
 ```java
 buf.put(127);
 ```
-put(int index, byte b)方法不会移动position，但是put(byte b)会移动position。
+
+put方法有很多版本，允许以不同方式把数据写入到Buffer中。例如， 写到一个指定的位置，或者把一个字节数组写入到Buffer。
+
 # 7 从Buffer读数据
 两种方式：
 1. 从Buffer读取数据到Channel。
@@ -127,17 +107,16 @@ get方法有很多版本，允许你以不同的方式从Buffer中读取数据
 
 # 8 核心 API
 ## flip()
-将Buffer从写模式切换到读模式![](https://img-blog.csdnimg.cn/20201107174036807.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-- `limit`置成之前的`position`值
-在写操作完成后需要进行读操作时，需要将limit设置为position标记有写到哪了
-- `position`置0
-而将position 重新移到0，这样就可以读取到所有的写入数据
-
-所以读模式下的角色转变：
-- `position`现在用于标记读的位置（从0开始）
+- 将Buffer从写模式切换到读模式![](https://img-blog.csdnimg.cn/20201107174036807.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+`limit`置成之前的`position`值
+`position`置0
+
+即
+- `position`现在用于标记读的位置
 - `limit`表示之前写进了多少个byte、char等 —— 现在能读取多少个byte、char等
 
 ## rewind()
+
 ![](https://img-blog.csdnimg.cn/20201107174323502.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 `position`置0：可重读Buffer中的所有数据
 `limit`不变，仍表示能从Buffer中读取多少个元素
@@ -193,6 +172,6 @@ compareTo()方法比较两个Buffer的剩余元素(byte、char等)， 如果满
 
 > 剩余元素是从 position到limit之间的元素。
 
-> 参考
-> - http://tutorials.jenkov.com/java-nio/buffers.html
-> - 《Java的事件驱动网络编程》
\ No newline at end of file
+参考
+- http://tutorials.jenkov.com/java-nio/buffers.html
+- 《Java的事件驱动网络编程》
\ No newline at end of file
diff --git "a/Netty/Netty-\346\272\220\347\240\201\346\267\261\345\272\246\350\247\243\346\236\220(\344\271\235)---\347\274\226\347\240\201.md" "b/Netty/Netty-\346\272\220\347\240\201\346\267\261\345\272\246\350\247\243\346\236\220(\344\271\235)---\347\274\226\347\240\201.md"
new file mode 100644
index 0000000000..fe036a6bf6
--- /dev/null
+++ "b/Netty/Netty-\346\272\220\347\240\201\346\267\261\345\272\246\350\247\243\346\236\220(\344\271\235)---\347\274\226\347\240\201.md"
@@ -0,0 +1,358 @@
+# 概述
+## 一个问题
+![](https://upload-images.jianshu.io/upload_images/4685968-84804df4d0671c4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-19eb33dede2bb8e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+编码器实现了` ChannelOutboundHandler`，并将出站数据从 一种格式转换为另一种格式，和我们方才学习的解码器的功能正好相反。Netty 提供了一组类， 用于帮助你编写具有以下功能的编码器:
+- 将消息编码为字节
+- 将消息编码为消息 
+我们将首先从抽象基类 MessageToByteEncoder 开始来对这些类进行考察
+# 1 抽象类 MessageToByteEncoder
+![MessageToByteEncoder API](https://upload-images.jianshu.io/upload_images/4685968-fc2c1f94df8cb05e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+解码器通常需要在` Channel `关闭之后产生最后一个消息(因此也就有了 `decodeLast()`方法)
+这显然不适于编码器的场景——在连接被关闭之后仍然产生一个消息是毫无意义的
+
+## 1.1 ShortToByteEncoder
+其接受一` Short` 型实例作为消息，编码为`Short`的原子类型值，并写入`ByteBuf`，随后转发给`ChannelPipeline`中的下一个 `ChannelOutboundHandler`
+每个传出的 Short 值都将会占用 ByteBuf 中的 2 字节
+ ![ShortToByteEncoder](https://upload-images.jianshu.io/upload_images/4685968-97f463faa149d584.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-8e071d7bafdfc0a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 1.2 Encoder
+![](https://upload-images.jianshu.io/upload_images/4685968-729af7889f8ae41c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+Netty 提供了一些专门化的 `MessageToByteEncoder`，可基于此实现自己的编码器
+`WebSocket08FrameEncoder `类提供了一个很好的实例
+![](https://upload-images.jianshu.io/upload_images/4685968-69f5cff063544148.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 2 抽象类 MessageToMessageEncoder
+你已经看到了如何将入站数据从一种消息格式解码为另一种
+为了完善这幅图，将展示 对于出站数据将如何从一种消息编码为另一种。`MessageToMessageEncoder `类的 `encode() `方法提供了这种能力
+![MessageToMessageEncoderAPI](https://upload-images.jianshu.io/upload_images/4685968-c2eba110d2a993ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+为了演示,使用` IntegerToStringEncoder` 扩展了 `MessageToMessageEncoder`
+- 编码器将每个出站 Integer 的 String 表示添加到了该 List 中
+![](https://upload-images.jianshu.io/upload_images/4685968-4299c17d7c63ef14.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![IntegerToStringEncoder的设计](https://upload-images.jianshu.io/upload_images/4685968-f7ec09a39121010e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+关于有趣的 MessageToMessageEncoder 的专业用法，请查看 `io.netty.handler. codec.protobuf.ProtobufEncoder `类，它处理了由 Google 的 Protocol Buffers 规范所定义 的数据格式。
+# 一个java对象最后是如何转变成字节流，写到socket缓冲区中去的
+![](https://upload-images.jianshu.io/upload_images/4685968-24fca24338bf2433.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+pipeline中的标准链表结构
+java对象编码过程
+write：写队列
+flush：刷新写队列
+writeAndFlush: 写队列并刷新
+## pipeline中的标准链表结构
+![标准的pipeline链式结构](https://upload-images.jianshu.io/upload_images/4685968-e31d12a7a18ae15d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+数据从head节点流入，先拆包，然后解码成业务对象，最后经过业务`Handler`处理，调用`write`，将结果对象写出去
+而写的过程先通过`tail`节点，然后通过`encoder`节点将对象编码成`ByteBuf`，最后将该`ByteBuf`对象传递到`head`节点，调用底层的Unsafe写到JDK底层管道
+## Java对象编码过程
+为什么我们在pipeline中添加了encoder节点，java对象就转换成netty可以处理的ByteBuf，写到管道里？
+
+我们先看下调用write的code
+![](https://upload-images.jianshu.io/upload_images/4685968-d0f990dfe8feec5e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+业务处理器接受到请求之后，做一些业务处理，返回一个`user`
+- 然后，user在pipeline中传递
+![AbstractChannel#](https://upload-images.jianshu.io/upload_images/4685968-73ef914536864393.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![DefaultChannelPipeline#](https://upload-images.jianshu.io/upload_images/4685968-120b0e1792d9fb6c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![AbstractChannelHandlerContext#](https://upload-images.jianshu.io/upload_images/4685968-9eb7d051da8c055e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![AbstractChannelHandlerContext#](https://upload-images.jianshu.io/upload_images/4685968-c19b9919da807791.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 情形一
+![AbstractChannelHandlerContext#](https://upload-images.jianshu.io/upload_images/4685968-b0a3e8ee071ee4ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![AbstractChannelHandlerContext#](https://upload-images.jianshu.io/upload_images/4685968-7d045ca0ab92822f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 情形二
+![AbstractChannelHandlerContext#](https://upload-images.jianshu.io/upload_images/4685968-a9a361e8b6b0c0b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-800871bb8d968ecb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![AbstractChannelHandlerContext#invokeWrite0](https://upload-images.jianshu.io/upload_images/4685968-6bbc148ee05a7145.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![AbstractChannelHandlerContext#invokeFlush0](https://upload-images.jianshu.io/upload_images/4685968-8aa6125ace28d5ce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+handler 如果不覆盖 flush 方法,就会一直向前传递直到 head 节点
+![](https://upload-images.jianshu.io/upload_images/4685968-bddea4d884dbbbd5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+落到 `Encoder`节点，下面是 `Encoder` 的处理流程
+![](https://upload-images.jianshu.io/upload_images/4685968-74ef3d309d447ade.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+按照简单自定义协议，将Java对象 User 写到传入的参数 out中，这个out到底是什么？
+
+需知` User `对象，从`BizHandler`传入到 `MessageToByteEncoder`时，首先传到 `write` 
+![](https://upload-images.jianshu.io/upload_images/4685968-667d8c9562155645.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+### 1. 判断当前Handelr是否能处理写入的消息(匹配对象)
+![](https://upload-images.jianshu.io/upload_images/4685968-716c1fa479aeed87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-4a1d643f04edb25b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-d46fa8bcf1ce03a3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+  - 判断该对象是否是该类型参数匹配器实例可匹配到的类型
+![TypeParameterMatcher#](https://upload-images.jianshu.io/upload_images/4685968-c2ee03c7c6e9b816.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![具体实例](https://upload-images.jianshu.io/upload_images/4685968-9e5abcf80fc0d548.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 2 分配内存
+![](https://upload-images.jianshu.io/upload_images/4685968-4d65f0f5674af21d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5476a7fdb230815b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 3 编码实现
+- 调用`encode`，这里就调回到  `Encoder` 这个`Handler`中  
+![](https://upload-images.jianshu.io/upload_images/4685968-b9df1d4be3dfabe3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 其为抽象方法,因此自定义实现类实现编码方法
+![](https://upload-images.jianshu.io/upload_images/4685968-6f47b5610e0afa2f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-357ce06307b4b0cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 4  释放对象
+- 既然自定义Java对象转换成`ByteBuf`了，那么这个对象就已经无用，释放掉 (当传入的`msg`类型是`ByteBuf`时，就不需要自己手动释放了)
+![](https://upload-images.jianshu.io/upload_images/4685968-933bdca4347cbc93.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ac7d7e987a786b0e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 5 传播数据
+//112 如果buf中写入了数据，就把buf传到下一个节点,直到 header 节点
+![](https://upload-images.jianshu.io/upload_images/4685968-72ef8f66aaced620.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 6 释放内存
+//115  否则，释放buf，将空数据传到下一个节点    
+// 120 如果当前节点不能处理传入的对象，直接扔给下一个节点处理
+// 127 当buf在pipeline中处理完之后，释放
+![](https://upload-images.jianshu.io/upload_images/4685968-10b7d01223813755.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#### Encoder处理传入的Java对象
+- 判断当前`Handler`是否能处理写入的消息
+    - 如果能处理，进入下面的流程
+    - 否则，直接扔给下一个节点处理
+- 将对象强制转换成`Encoder ` 可以处理的 `Response`对象
+- 分配一个`ByteBuf`
+- 调用`encoder`，即进入到 Encoder 的 encode方法，该方法是用户代码，用户将数据写入ByteBuf
+- 既然自定义Java对象转换成ByteBuf了，那么这个对象就已经无用了，释放掉(当传入的msg类型是ByteBuf时，无需自己手动释放)
+- 如果buf中写入了数据，就把buf传到下一个节点，否则，释放buf，将空数据传到下一个节点
+- 最后，当buf在pipeline中处理完之后，释放节点
+
+总结就是，`Encoder`节点分配一个`ByteBuf`，调用`encode`方法，将Java对象根据自定义协议写入到ByteBuf，然后再把ByteBuf传入到下一个节点，在我们的例子中，最终会传入到head节点
+```
+public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+    unsafe.write(msg, promise);
+}
+```
+这里的msg就是前面在Encoder节点中，载有java对象数据的自定义ByteBuf对象
+## write - 写buffer队列
+![](https://upload-images.jianshu.io/upload_images/4685968-307c73b5dfd72d23.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![ChannelOutboundInvoker#](https://upload-images.jianshu.io/upload_images/4685968-96833b7477fefe2c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-1ae909693fc84ebf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![write(Object msg, boolean flush, ChannelPromise promise)](https://upload-images.jianshu.io/upload_images/4685968-c08cff4eba7e1db2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-e4253f83c5e64716.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-8af14874dc519026.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-7d5e1e346cf7cacb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![HeadContext in DefaultChannelPipeline#write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)](https://upload-images.jianshu.io/upload_images/4685968-82841997d30ade96.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![Unsafe in Channel#write(Object msg, ChannelPromise promise)](https://upload-images.jianshu.io/upload_images/4685968-ef289b46a21326ee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+以下过程分三步讲解
+![](https://upload-images.jianshu.io/upload_images/4685968-0685a9bb2338e8d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#### direct ByteBuf
+![](https://upload-images.jianshu.io/upload_images/4685968-0c4f5243677578a7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![AbstractChannel#filterOutboundMessage(Object msg)](https://upload-images.jianshu.io/upload_images/4685968-045f2edbcc3ac462.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 首先，调用` assertEventLoop `确保该方法的调用是在`reactor`线程中
+- 然后，调用 `filterOutboundMessage() `，将待写入的对象过滤，把非`ByteBuf`对象和`FileRegion`过滤，把所有的非直接内存转换成直接内存`DirectBuffer`
+![](https://upload-images.jianshu.io/upload_images/4685968-6624e78825702577.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![AbstractNioChannel#newDirectBuffer](https://upload-images.jianshu.io/upload_images/4685968-e3b5ee7cc58e8fba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#### 插入写队列
+- 接下来，估算出需要写入的ByteBuf的size
+
+![](https://upload-images.jianshu.io/upload_images/4685968-7c49f8cfc8abb137.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 最后，调用 ChannelOutboundBuffer 的addMessage(msg, size, promise) 方法，所以，接下来，我们需要重点看一下这个方法干了什么事情
+![ChannelOutboundBuffer](https://upload-images.jianshu.io/upload_images/4685968-5d5799967e956bf8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+想要理解上面这段代码，须掌握写缓存中的几个消息指针
+![](https://upload-images.jianshu.io/upload_images/4685968-de798f9d24c6ca9d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+ChannelOutboundBuffer 里面的数据结构是一个单链表结构，每个节点是一个 Entry，Entry 里面包含了待写出ByteBuf 以及消息回调 promise下面分别是
+### 三个指针的作用
+- flushedEntry 
+表第一个被写到OS Socket缓冲区中的节点
+![ChannelOutboundBuffer](https://upload-images.jianshu.io/upload_images/4685968-07a5e15190603fb2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- unFlushedEntry 
+表第一个未被写入到OS Socket缓冲区中的节点
+![ChannelOutboundBuffer](https://upload-images.jianshu.io/upload_images/4685968-87709ad85523cc40.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- tailEntry
+表`ChannelOutboundBuffer`缓冲区的最后一个节点
+![ChannelOutboundBuffer](https://upload-images.jianshu.io/upload_images/4685968-8c220a874c9b76b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+###  图解过程
+- 初次调用write 即 `addMessage` 后
+![](https://upload-images.jianshu.io/upload_images/4685968-01ff08665e5fb3d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+`fushedEntry`指向空，`unFushedEntry`和 `tailEntry `都指向新加入节点
+
+- 第二次调用 `addMessage`后
+![](https://upload-images.jianshu.io/upload_images/4685968-c3f5e605fcabd30d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 第n次调用 `addMessage`后
+![](https://upload-images.jianshu.io/upload_images/4685968-f3340e19079c3e27.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+可得,调用n次`addMessage`后
+- `flushedEntry`指针一直指向`null`，表此时尚未有节点需写到Socket缓冲区
+- `unFushedEntry`后有n个节点，表当前还有n个节点尚未写到Socket缓冲区
+
+#### 设置写状态
+![ChannelOutboundBuffer#addMessage](https://upload-images.jianshu.io/upload_images/4685968-6f2250e10cdbbd9c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 统计当前有多少字节需要需要被写出
+![ChannelOutboundBuffer#addMessage(Object msg, int size, ChannelPromise promise)](https://upload-images.jianshu.io/upload_images/4685968-5543bf8e6d4579e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 当前缓冲区中有多少待写字节
+![ChannelOutboundBuffer#](https://upload-images.jianshu.io/upload_images/4685968-0171d03c0fc61952.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![](https://upload-images.jianshu.io/upload_images/4685968-f0219d7b269f9a6e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![ChannelConfig#getWriteBufferHighWaterMark()](https://upload-images.jianshu.io/upload_images/4685968-b61cc201e20ffe40.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-c8732e196722e9d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-17856713165403bf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 所以默认不能超过64k
+![WriteBufferWaterMark](https://upload-images.jianshu.io/upload_images/4685968-5d7ec5b3129fcadb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![](https://upload-images.jianshu.io/upload_images/4685968-d19715897fdf72b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 自旋锁+CAS 操作,通过 pipeline 将事件传播到channelhandler 中监控
+![](https://upload-images.jianshu.io/upload_images/4685968-bccfb48e0b7c36c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+## flush：刷新buffer队列
+### 添加刷新标志并设置写状态
+- 不管调用`channel.flush()`，还是`ctx.flush()`，最终都会落地到`pipeline`中的`head`节点
+![DefaultChannelPipeline#flush](https://upload-images.jianshu.io/upload_images/4685968-ecb565a2254297d0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 之后进入到`AbstractUnsafe`
+![AbstractChannel#flush()](https://upload-images.jianshu.io/upload_images/4685968-6a2b9b242520c3ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- flush方法中，先调用
+![ChannelOutboundBuffer#addFlush](https://upload-images.jianshu.io/upload_images/4685968-c14e8c30f202db83.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![ChannelOutboundBuffer#decrementPendingOutboundBytes(long size, boolean invokeLater, boolean notifyWritability)](https://upload-images.jianshu.io/upload_images/4685968-09b36f5b96a13a2d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-a01a7b4688477996.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![和之前那个实例相同,不再赘述](https://upload-images.jianshu.io/upload_images/4685968-04c753ec32f58d92.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 结合前面的图来看，上述过程即 
+首先拿到 `unflushedEntry` 指针，然后将` flushedEntry `指向`unflushedEntry`所指向的节点，调用完毕后
+![](https://upload-images.jianshu.io/upload_images/4685968-28a4e504c8b9a158.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 遍历 buffer 队列,过滤bytebuf
+- 接下来，调用 `flush0()`
+![](https://upload-images.jianshu.io/upload_images/4685968-37c6cb089cce16d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 发现这里的核心代码就一个 `doWrite`
+![AbstractChannel#](https://upload-images.jianshu.io/upload_images/4685968-669bf7e04ff0e99a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# AbstractNioByteChannel
+- 继续跟
+```
+protected void doWrite(ChannelOutboundBuffer in) throws Exception {
+    int writeSpinCount = -1;
+
+    boolean setOpWrite = false;
+    for (;;) {
+        // 拿到第一个需要flush的节点的数据
+        Object msg = in.current();
+
+        if (msg instanceof ByteBuf) {
+            boolean done = false;
+            long flushedAmount = 0;
+            // 拿到自旋锁迭代次数
+            if (writeSpinCount == -1) {
+                writeSpinCount = config().getWriteSpinCount();
+            }
+            // 自旋，将当前节点写出
+            for (int i = writeSpinCount - 1; i >= 0; i --) {
+                int localFlushedAmount = doWriteBytes(buf);
+                if (localFlushedAmount == 0) {
+                    setOpWrite = true;
+                    break;
+                }
+
+                flushedAmount += localFlushedAmount;
+                if (!buf.isReadable()) {
+                    done = true;
+                    break;
+                }
+            }
+
+            in.progress(flushedAmount);
+
+            // 写完之后，将当前节点删除
+            if (done) {
+                in.remove();
+            } else {
+                break;
+            }
+        } 
+    }
+}
+```
+- 第一步，调用`current()`先拿到第一个需要`flush`的节点的数据
+![ChannelOutboundBuffer#current](https://upload-images.jianshu.io/upload_images/4685968-d3e67e72eef65f8f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 第二步,拿到自旋锁的迭代次数
+![](https://upload-images.jianshu.io/upload_images/4685968-1d6de61347280fb4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+- 第三步 调用 JDK 底层 API 进行自旋写
+自旋的方式将`ByteBuf`写到JDK NIO的`Channel`
+强转为ByteBuf，若发现没有数据可读，直接删除该节点
+![](https://upload-images.jianshu.io/upload_images/4685968-4d37a05bf05ffe07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 拿到自旋锁迭代次数
+
+![image.png](https://upload-images.jianshu.io/upload_images/4685968-d210766988601cf9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 在并发编程中使用自旋锁可以提高内存使用率和写的吞吐量,默认值为16
+![ChannelConfig](https://upload-images.jianshu.io/upload_images/4685968-a8475bef6ecf13e2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 继续看源码
+![](https://upload-images.jianshu.io/upload_images/4685968-ecafbd59548a6fd0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![AbstractNioByteChannel#](https://upload-images.jianshu.io/upload_images/4685968-dce60c0a5bf11941.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+-  `javaChannel()`，表明 JDK NIO Channel 已介入此次事件
+![NioSocketChannel#](https://upload-images.jianshu.io/upload_images/4685968-a4397658d76d4698.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![ByteBuf#readBytes(GatheringByteChannel out, int length)](https://upload-images.jianshu.io/upload_images/4685968-cbb0d3b5f759ee4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 得到向JDK 底层已经写了多少字节
+![PooledDirectByteBuf#](https://upload-images.jianshu.io/upload_images/4685968-3c02f943cdd66670.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-e7b52e9aa0e733c9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 从 Netty 的 bytebuf 写到 JDK 底层的 bytebuffer
+![](https://upload-images.jianshu.io/upload_images/4685968-ecf02ba9cc614c0f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ccdaa9ea3aff5b95.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 第四步,删除该节点
+节点的数据已经写入完毕，接下来就需要删除该节点
+![](https://upload-images.jianshu.io/upload_images/4685968-624394ec70aace1b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+首先拿到当前被`flush`掉的节点(`flushedEntry`所指)
+然后拿到该节点的回调对象 `ChannelPromise`, 调用 `removeEntry()`移除该节点
+![](https://upload-images.jianshu.io/upload_images/4685968-331da9841bbbdc9b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+这里是逻辑移除，只是将flushedEntry指针移到下个节点，调用后
+![](https://upload-images.jianshu.io/upload_images/4685968-a3ee365cf099451f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+随后，释放该节点数据的内存，调用` safeSuccess `回调，用户代码可以在回调里面做一些记录，下面是一段Example
+```
+ctx.write(xx).addListener(new GenericFutureListener<Future<? super Void>>() {
+    @Override
+    public void operationComplete(Future<? super Void> future) throws Exception {
+       // 回调 
+    }
+})
+```
+最后，调用 `recycle`，将当前节点回收
+## writeAndFlush: 写队列并刷新
+`writeAndFlush`在某个`Handler`中被调用之后，最终会落到 `TailContext `节点
+![](https://upload-images.jianshu.io/upload_images/4685968-7de9bca98e530e24.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+```
+
+public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
+    write(msg, true, promise);
+
+    return promise;
+}
+
+```
+![AbstractChannelHandlerContext#](https://upload-images.jianshu.io/upload_images/4685968-2d386c037c13aa70.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![AbstractChannelHandlerContext#](https://upload-images.jianshu.io/upload_images/4685968-3f7d85e213c1523e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+最终，通过一个`boolean`变量，表示是调用` invokeWriteAndFlush`，还是` invokeWrite`，`invokeWrite`便是我们上文中的write过程
+![AbstractChannelHandlerContext#](https://upload-images.jianshu.io/upload_images/4685968-8d312140e4d95ba0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+可以看到，最终调用的底层方法和单独调用` write `和` flush `一样的
+![](https://upload-images.jianshu.io/upload_images/4685968-d6e4563b1ff4f249.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-579fcadc81f33348.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+由此看来，`invokeWriteAndFlush`基本等价于`write`之后再来一次`flush`
+
+# 总结
+- 调用`write`并没有将数据写到Socket缓冲区中，而是写到了一个单向链表的数据结构中，`flush`才是真正的写出
+- `writeAndFlush`等价于先将数据写到netty的缓冲区，再将netty缓冲区中的数据写到Socket缓冲区中，写的过程与并发编程类似，用自旋锁保证写成功
+- netty中的缓冲区中的ByteBuf为DirectByteBuf
+![](https://upload-images.jianshu.io/upload_images/4685968-42527f4855e71919.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 当 BizHandler 通过 writeAndFlush 方法将自定义对象往前传播时,其实可以拆分成两个过程
+- 通过 pipeline逐渐往前传播,传播到其中的一个 encode 节点后,其负责重写 write 方法将自定义的对象转化为 ByteBuf,接着继续调用 write 向前传播
+
+- pipeline中的编码器原理是创建一个`ByteBuf`,将Java对象转换为`ByteBuf`，然后再把`ByteBuf`继续向前传递,若没有再重写了,最终会传播到 head 节点,其中缓冲区列表拿到缓存写到 JDK 底层 ByteBuffer
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(1)-\345\257\274\350\257\273.md" "b/Netty/Netty-\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\344\270\200)-\344\273\213\347\273\215.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(1)-\345\257\274\350\257\273.md"
rename to "Netty/Netty-\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\344\270\200)-\344\273\213\347\273\215.md"
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(3)-\345\220\257\345\212\250\346\234\215\345\212\241\346\265\201\347\250\213.md" "b/Netty/Netty-\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\344\270\211)-\345\220\257\345\212\250\346\234\215\345\212\241\346\265\201\347\250\213\346\272\220\347\240\201.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(3)-\345\220\257\345\212\250\346\234\215\345\212\241\346\265\201\347\250\213.md"
rename to "Netty/Netty-\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\344\270\211)-\345\220\257\345\212\250\346\234\215\345\212\241\346\265\201\347\250\213\346\272\220\347\240\201.md"
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(2)-\345\237\272\346\234\254\347\273\204\344\273\266.md" "b/Netty/Netty-\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\344\272\214)-Netty-\345\237\272\346\234\254\347\273\204\344\273\266.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(2)-\345\237\272\346\234\254\347\273\204\344\273\266.md"
rename to "Netty/Netty-\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\344\272\214)-Netty-\345\237\272\346\234\254\347\273\204\344\273\266.md"
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(4)-NioEventLoop.md" "b/Netty/Netty-\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\233\233)-NioEventLoop.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(4)-NioEventLoop.md"
rename to "Netty/Netty-\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\233\233)-NioEventLoop.md"
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(5)-\346\226\260\350\277\236\346\216\245\346\243\200\346\265\213.md" "b/Netty/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\344\272\224)-\346\226\260\350\277\236\346\216\245\346\243\200\346\265\213.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(5)-\346\226\260\350\277\236\346\216\245\346\243\200\346\265\213.md"
rename to "Netty/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\344\272\224)-\346\226\260\350\277\236\346\216\245\346\243\200\346\265\213.md"
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\215\201)-\346\200\247\350\203\275\344\274\230\345\214\226.md" "b/Netty/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\215\201)-\346\200\247\350\203\275\344\274\230\345\214\226.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\215\201)-\346\200\247\350\203\275\344\274\230\345\214\226.md"
rename to "Netty/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\215\201)-\346\200\247\350\203\275\344\274\230\345\214\226.md"
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\215\201\344\270\200)---\350\256\276\350\256\241\346\250\241\345\274\217\347\232\204\345\272\224\347\224\250.md" "b/Netty/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\215\201\344\270\200)---\350\256\276\350\256\241\346\250\241\345\274\217\347\232\204\345\272\224\347\224\250.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\215\201\344\270\200)---\350\256\276\350\256\241\346\250\241\345\274\217\347\232\204\345\272\224\347\224\250.md"
rename to "Netty/Netty\346\272\220\347\240\201\351\230\205\350\257\273\345\205\245\351\227\250\345\256\236\346\210\230(\345\215\201\344\270\200)---\350\256\276\350\256\241\346\250\241\345\274\217\347\232\204\345\272\224\347\224\250.md"
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\351\235\242\350\257\225\350\247\243\346\236\220(\345\205\253)-\350\247\243\347\240\201\344\270\212.md" "b/Netty/Netty\346\272\220\347\240\201\351\235\242\350\257\225\350\247\243\346\236\220(\345\205\253)-\350\247\243\347\240\201\344\270\212.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\351\235\242\350\257\225\350\247\243\346\236\220(\345\205\253)-\350\247\243\347\240\201\344\270\212.md"
rename to "Netty/Netty\346\272\220\347\240\201\351\235\242\350\257\225\350\247\243\346\236\220(\345\205\253)-\350\247\243\347\240\201\344\270\212.md"
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\351\235\242\350\257\225\350\247\243\346\236\220(\345\205\253)-\350\247\243\347\240\201\344\270\213.md" "b/Netty/Netty\346\272\220\347\240\201\351\235\242\350\257\225\350\247\243\346\236\220(\345\205\253)-\350\247\243\347\240\201\344\270\213.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\351\235\242\350\257\225\350\247\243\346\236\220(\345\205\253)-\350\247\243\347\240\201\344\270\213.md"
rename to "Netty/Netty\346\272\220\347\240\201\351\235\242\350\257\225\350\247\243\346\236\220(\345\205\253)-\350\247\243\347\240\201\344\270\213.md"
diff --git "a/Netty/Netty\347\273\204\344\273\266/ByteBuf.md" "b/Netty/Netty\347\273\204\344\273\266/ByteBuf.md"
deleted file mode 100644
index ffe39ec8c0..0000000000
--- "a/Netty/Netty\347\273\204\344\273\266/ByteBuf.md"
+++ /dev/null
@@ -1,126 +0,0 @@
-网络数据的基本单位永远是 byte(字节)。Java NIO 提供 ByteBuffer 作为字节的容器，但该类过于复杂，有点难用。
-
-ByteBuf是Netty当中的最重要的工具类，它与JDK的ByteBuffer原理基本上相同，也分为堆内与堆外俩种类型，但是ByteBuf做了极大的优化，具有更简单的API，更多的工具方法和优秀的内存池设计。
-# 1 API
-Netty 的数据处理 API 通过两个组件暴露——抽象类ByteBuf 和 接口 ByteBufHolder。
-
-ByteBuf API 的优点：
-- 它可以被用户自定义的缓冲区类型扩展
-- 通过内置的复合缓冲区类型实现了透明的零拷贝；
-- 容量可以按需增长（类似于 JDK 的 StringBuilder）
-- 在读和写这两种模式之间切换不需要调用 ByteBuffer 的 flip()方法
-- 读和写使用了不同的索引
-- 支持方法的链式调用
-- 支持引用计数
-- 支持池化
-
-其他类可用于管理 ByteBuf 实例的分配，以及执行各种针对于数据容器本身和它所持有的数据的操作。
-# 2 Netty 的数据容器
-**所有网络通信最终都是基于底层的字节流传输**，因此高效、方便、易用的数据接口是迷人的，而 Netty 的 ByteBuf 生而为满足这些需求。 
-##  2.1 工作原理
-ByteBuf 维护俩不同索引：一个用于读取，一个用于写入：
-- 从 ByteBuf 读取时，其 readerIndex 将会被递增已经被读取的字节数
-- 当写入 ByteBuf 时，writerIndex 也会被递增
-
-- 一个读索引和写索引都设置为 0 的 16 字节 ByteBuf
-![](https://img-blog.csdnimg.cn/20210529165446513.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/2021052917024444.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-这些索引两两之间有什么关系呢？
-若打算读取字节直到 readerIndex == writerIndex，会发生啥？此时，将会到达“可读取的”数据的末尾。类似试图读取超出数组末尾的数据一样，试图读取超出该点的数据也会抛 **IndexOutOfBoundsException**。 
-![](https://img-blog.csdnimg.cn/20210530142445418.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- read、write 开头的 ByteBuf 方法，会推进对应索引
-- set、get 开头的操作则不会。后面的这些方法将在作为一个参数传入的一个相对索引上执行操作
-
-可指定 ByteBuf 的最大容量。试图移动写索引（即 writerIndex）超过这个值将会触
-发一个异常。（默认限制 Integer.MAX_VALUE。）
-
-## 内存池化
-### 非池化的堆内与堆外的 ByteBuf 示意图
-![](https://img-blog.csdnimg.cn/20210529191647413.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-```java
-ByteBuf heapBuffer = UnpooledByteBufAllocator.DEFAULT.heapBuffer(10);
-ByteBuf directBuffer = UnpooledByteBufAllocator.DEFAULT.directBuffer(10);
-```
-注意要手动将GC 无法控制的非堆内存的空间释放：
-
-
-### 池化的堆内与堆外的 ByteBuf 示意图
-![](https://img-blog.csdnimg.cn/20210529191524947.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-![](https://img-blog.csdnimg.cn/2021052919043874.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# 字节级操作
-## 派生缓冲区
-派生缓冲区为 ByteBuf 提供了以专门的方式来呈现其内容的视图。这类视图通过以下方法创建：
-- Unpooled.unmodifiableBuffer(…)
-- order(ByteOrder)
-- readSlice(int)
-
-这些方法都将返回一个新的 ByteBuf 实例，但都具有自己独立的读、写和标记索引。
-其内部存储和 JDK 的 ByteBuffer 一样，都是共享的。所以派生缓冲区的创建成本很低，但同时也表明若你修改了它的内容，也会同时修改对应源实例！
-### slice、slice(int, int)、retainedSlice、retainedSlice(int, int)
-返回此缓冲区的可读字节的**一部分**。
-此方法与`buf.slice(buf.readerIndex(), buf.readableBytes())`相同。
-该方法不会调用retain()，引用计数不会增加。
-retainedSlice系列方法调用类似`slice().retain()`，但此方法可能返回产生较少垃圾的缓冲区实现。
-![](https://img-blog.csdnimg.cn/20210530141058700.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### duplicate、retainedDuplicate
-![](https://img-blog.csdnimg.cn/20210530155523259.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-返回一个共享该缓冲区**整个区域**的缓冲区。
-此方法不会修改此缓冲区的readerIndex或writerIndex
-![](https://img-blog.csdnimg.cn/2021053016382481.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-读取器和写入器标记将不会重复。
-duplicate不会调用retain()，不会增加引用计数，而retainedDuplicate会。
-
-### readSlice、readRetainedSlice
-![](https://img-blog.csdnimg.cn/20210530165807505.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-返回部分空间，彼此共享底层缓冲区，会增加原缓冲区的readerIndex。
-
-
-> 如果需要一个现有缓冲区的真实副本，请使用 copy()或者 copy(int, int)，因为这个调用所返回的 ByteBuf 拥有独立的数据副本。
-
-# 引用与释放
-ByteBuf 在使用完毕后一定要记得释放，否则会造成内存泄露。
-## 引用计数
-通过在某个对象所持有的资源不再被其他对象引用时释放该对象所持有的资源来优化内存使用和性能的技术。
-Netty 在4.x为 ByteBuf 和 ByteBufHolder 带来了引用计数技术，都实现了：
-### ReferenceCounted接口
-需要显式释放的引用计数对象。
-
-当一个新的ReferenceCounted被实例化时，以1 作为初始值。 
-#### retain()
-增加引用计数，将引用计数加1。只要**引用计数>0**，就能保证对象不会被释放。
-#### release()
-减少引用计数，将引用计数减1。若引用计数减少到0 ，对象将被显式释放，并且访问释放的对象通常会导致访问冲突。
-
-若实现ReferenceCounted的对象是其他实现ReferenceCounted的对象的容器，则当容器的引用计数变为 0 时，所包含的对象也将通过release()被释放。
-
-引用计数对于池化实现（如 PooledByteBufAllocator）很重要，它降低了内存分配的开销。
-
-```java
-Channel channel = ...;
-// 从 Channel 获取 ByteBufAllocator
-ByteBufAllocator allocator = channel.alloc();
-...
-// 从 ByteBufAllocator 分配一个 ByteBuf
-ByteBuf buffer = allocator.directBuffer();
-// 检查引用计数是否为预期的 1
-assert buffer.refCnt() == 1;
-
-
-ByteBuf buffer = ...;
-// 减少该对象的活动引用。当减少到 0 时，该对象被释放，该方法返回 true
-boolean released = buffer.release();
-```
-试图访问一个已经被释放的引用计数的对象，将会抛IllegalReferenceCountException
-![](https://img-blog.csdnimg.cn/20210530151954894.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-![](https://img-blog.csdnimg.cn/20210529221352800.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-一个特定的（ReferenceCounted 的实现）类，可以用它自己的独特方式来定义它的引用计数规则。例如可以设想一个类，其 release()方法的实现总是将引用计数设为
-零，而不用关心它的当前值，从而一次性使所有的活动引用都失效。
-
-## 谁负责释放
-一般由最后访问（引用计数）对象的那一方来负责将它释放。
\ No newline at end of file
diff --git "a/Python/\347\210\254\350\231\253/python\351\253\230\347\272\247\347\210\254\350\231\253\345\256\236\346\210\230\344\271\213Headers\344\277\241\346\201\257\346\240\241\351\252\214-Cookie.md" "b/Python/\347\210\254\350\231\253/python\351\253\230\347\272\247\347\210\254\350\231\253\345\256\236\346\210\230\344\271\213Headers\344\277\241\346\201\257\346\240\241\351\252\214-Cookie.md"
deleted file mode 100644
index fccea8ba37..0000000000
--- "a/Python/\347\210\254\350\231\253/python\351\253\230\347\272\247\347\210\254\350\231\253\345\256\236\346\210\230\344\271\213Headers\344\277\241\346\201\257\346\240\241\351\252\214-Cookie.md"
+++ /dev/null
@@ -1,68 +0,0 @@
-### python高级爬虫实战之Headers信息校验-Cookie
-
-#### 一、什么是cookie
-
-​		上期我们了解了User-Agent，这期我们来看下如何利用Cookie进行用户模拟登录从而进行网站数据的爬取。
-
-首先让我们来了解下什么是Cookie：
-
-​		Cookie指某些网站为了辨别用户身份、从而储存在用户本地终端上的数据。当客户端在第一次请求网站指定的首页或登录页进行登录之后，服务器端会返回一个Cookie值给客户端。如果客户端为浏览器，将自动将返回的cookie存储下来。当再次访问改网页的其他页面时，自动将cookie值在Headers里传递过去，服务器接受值后进行验证，如合法处理请求，否则拒绝请求。
-
-### 二、如何利用cookie
-
-​		举个例子我们要去微博爬取相关数据，首先我们会遇到登录的问题，当然我们可以利用python其他的功能模块进行模拟登录，这里可能会涉及到验证码等一些反爬手段。
-
-![截屏2024-03-04 下午7.53.55](https://s2.loli.net/2024/03/04/j7RxseHBKSlGMD5.png)
-
-换个思路，我们登录好了，通过开发者工具“右击” 检查（或者按F12） 获取到对应的cookie，那我们就可以绕个登录的页面，利用cookie继续用户模拟操作从而直接进行操作了。
-
-![截屏2024-03-04 下午8.02.39](https://s2.loli.net/2024/03/04/qLygJpvH6RYTlzE.png)
-
-利用cookie实现模拟登录的两种方法：
-
-- [ ] 将cookie插入Headers请求头
-
-  ```
-  Headers={"cookie":"复制的cookie值"}
-  ```
-
-  
-
-- [ ] 将cookie直接作为requests方法的参数
-
-```
-cookie={"cookie":"复制的cookie值"}
-requests.get(url,cookie=cookie)
-```
-
-#### 三、利用selenium获取cookie，实现用户模拟登录
-
-实现方法：利用selenium模拟浏览器操作，输入用户名，密码 或扫码进行登录，获取到登录的cookie保存成文件，加载文件解析cookie实现用户模拟登录。
-
-```python
-from selenium import webdriver
-from time import sleep
-import json
-#selenium模拟浏览器获取cookie
-def getCookie:
-	driver = webdriver.Chrome()
-  driver.maximize_window()
-  driver.get('https://weibo.co m/login.php')
-  sleep(20) # 留时间进行扫码
-  Cookies = driver.get_cookies() # 获取list的cookies
-  jsCookies = json.dumps(Cookies) # 转换成字符串保存
-  with open('cookies.txt', 'w') as f:
-    f.write(jsCookies)
-    
-def login:
-	 filename = 'cookies.txt'
-   #创建MozillaCookieJar实例对象
-   cookie = cookiejar.MozillaCookieJar()
-   #从文件中读取cookie内容到变量
-   cookie.load(filename, ignore_discard=True, ignore_expires=True)
-   response = requests.get('https://weibo.co m/login.php',cookie=cookie)
-```
-
-#### 四、拓展思考
-
-​		如果频繁使用一个账号进行登录爬取网站数据有可能导致服务器检查到异常，对当前账号进行封禁，这边我们就需要考虑cookie池的引入了。
\ No newline at end of file
diff --git "a/Python/\347\210\254\350\231\253/python\351\253\230\347\272\247\347\210\254\350\231\253\345\256\236\346\210\230\344\271\213Headers\344\277\241\346\201\257\346\240\241\351\252\214-User-Agent.md" "b/Python/\347\210\254\350\231\253/python\351\253\230\347\272\247\347\210\254\350\231\253\345\256\236\346\210\230\344\271\213Headers\344\277\241\346\201\257\346\240\241\351\252\214-User-Agent.md"
deleted file mode 100644
index 0fa29af29d..0000000000
--- "a/Python/\347\210\254\350\231\253/python\351\253\230\347\272\247\347\210\254\350\231\253\345\256\236\346\210\230\344\271\213Headers\344\277\241\346\201\257\346\240\241\351\252\214-User-Agent.md"
+++ /dev/null
@@ -1,61 +0,0 @@
-### python高级爬虫实战之Headers信息校验-User-Agent
-
-​		User-agent 是当前网站反爬策略中最基础的一种反爬技术，服务器通过接收请求头中的user-agen的值来判断是否为正常用户访问还是爬虫程序。
-
-​        下面举一个简单的例子 爬取我们熟悉的豆瓣网：
-
-```python
-import requests                                                                                                                                   
-url='https://movie.douban.com/'                                                                                                                                                                                                                                                                
-resp=requests.get(url)                                                                                                                            
-print(resp.status_code)                                                                                                                                                                                                                                                                          
-```
-
-运行结果得到status_code：418
-
-说明我们爬虫程序已经被服务器所拦截，无法正常获取相关网页数据。
-
-我们可以通过返回的状态码来了解服务器的相应情况
-
-- 100–199：信息反馈
-- 200–299：成功反馈
-- 300–399：重定向消息
-- 400–499：客户端错误响应
-- 500–599：服务器错误响应
-
-现在我们利用google chrome浏览器来打开豆瓣网，查看下网页。
-
-正常打开网页后，我们在页面任意地方右击“检查” 打开开发者工具。
-
-<img src="https://s2.loli.net/2024/03/01/ime1CM9vWIGAU2D.png" alt="image-20240301205014592" style="zoom:50%;" />
-
-
-
-选择：Network-在Name中随便找一个文件点击后，右边Headers显示内容，鼠标拉到最下面。
-
-![截屏2024-03-01 下午8.53.05](https://s2.loli.net/2024/03/01/XdjyBL5ClIYnT9F.png)
-
-User-Agent: 
-
-Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
-
-我们把这段带到程序中再试下看效果如何。
-
-```python
-import requests
-url='https://movie.douban.com/'
-headers={
-"user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
-}
-resp=requests.get(url,headers=headers)
-print(resp.status_code)
-```
-
-完美，执行后返回状态码200 ，说明已经成功骗过服务器拿到了想要的数据。
-
-​		对于User-agent 我们可以把它当做一个身份证，这个身份证中会包含很多信息，通过这些信息可以识别出访问者。所以当服务器开启了user-agent认证时，就需要像服务器传递相关的信息进行核对。核对成功，服务器才会返回给用户正确的内容，否则就会拒绝服务。
-
-当然，对于Headers的相关信息还有很多，后续我们再一一讲解，下期见。
-
-
-
diff --git "a/Python/\347\210\254\350\231\253/\345\210\251\347\224\250python\345\256\236\347\216\260\345\260\217\350\257\264\350\207\252\347\224\261.md" "b/Python/\347\210\254\350\231\253/\345\210\251\347\224\250python\345\256\236\347\216\260\345\260\217\350\257\264\350\207\252\347\224\261.md"
deleted file mode 100644
index deb2f6262f..0000000000
--- "a/Python/\347\210\254\350\231\253/\345\210\251\347\224\250python\345\256\236\347\216\260\345\260\217\350\257\264\350\207\252\347\224\261.md"
+++ /dev/null
@@ -1,91 +0,0 @@
-### 利用python实现小说自由
-
-#### 一、用到的相关模块
-
-1.reuqests模块
-
-安装reuqest模块，命令行输入：
-
-``` 
-pip install requests
-```
-
-2.xpath解析
-
-​		XPath 即为 XML 路径语言，它是一种用来确定 XML (标准通用标记语言子集)文档中某部分位置的语言。XPath 基于 XML 的树状结构，提供在数据结构树中找寻节点的能力。起初 XPath 的提出的初衷是将其作为一个通用的、介于 XPointer 与 XSL 间的语法模型。但是 XPath 很快的被开发者采用来当作小型查询语言。
-
-​    简单的来说：Xpath（XML Path Language）是一门在 XML 和 HTML 文档中查找信息的语言，可用来在 XML 和 HTML 文档中对元素和属性进行遍历。
-
-​	xml 是 一个HTML/XML的解析器，主要的功能是如何解析和提取 HTML/XML 数据。
-
-安装xml：
-
-``` 
-pip install lxml
-```
-
-
-
-#### 二、实现步骤
-
-1.首先我们打开一个小说的网址：https://www.qu-la.com/booktxt/17437775116/
-
-2.右击“检查” 查看下这个网页的相关代码情况 
-
-<img src="https://s2.loli.net/2024/03/01/y3XolKRDgu6VJbj.png">
-
-我们可以发现所有的内容都被包裹在<ul class="cf”> 的ul中。
-
-右击复制出xpath路径
-
-//*[@id="list"]/div[3]/ul[2]
-
-<img src="https://s2.loli.net/2024/03/01/kzjRUCqwheHxFuY.png"/>
-
-通过xpath 解析出每个章节的标题，和链接。
-
-3.根据对应的链接获取每个章节的文本。同样的方法找到章节文本的具体位置
-
-//*[@id="txt"]
-
-<img src="https://s2.loli.net/2024/03/01/1j5FktPoXpcnUJi.png"/>
-
-3.for 循环获取所有链接的文本，保存为Txt文件。
-
-#### 三、代码展示
-
-```python
-import requests
-from lxml import etree
-def getNovel():
-    headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36'};
-    html = requests.get('https://www.qu-la.com/booktxt/17437775116/',headers=headers).content
-    doc=etree.HTML(html)
-    contents=doc.xpath('//*[ @id="list"]/div[3]/ul[2]') #获取到小说的所有章节
-    for content in contents:
-        links=content.xpath('li/a/@href') #获取每个章节的链接
-        for link in links: #循环处理每个章节
-            url='https://www.qu-la.com'+link #拼接章节url
-            html=requests.get(url).text
-            doc=etree.HTML(html)
-            content = doc.xpath('//*[@id="txt"]/text()') #获取章节的正文
-            title = doc.xpath('//*[@id="chapter-title"]/h1/text()') #获取标题
-            #所有的保存到一个文件里面
-            with open('books/凡人修仙之仙界篇.txt', 'a') as file:
-                file.write(title[0])
-                print('正在下载{}'.format(title[0]))
-                for items in content:
-                    file.write(item)
-
-    print('下载完成')
-getNovel() #调用函数
-```
-
-#### 四、拓展思考
-
-1.写一个搜索界面，用户输入书名自主下载对应的小说。
-
-2.引入多进程异步下载，提高小说的下载速度。
-
-
-
diff --git a/README.md b/README.md
index 8ad293ad89..1df8ed518c 100755
--- a/README.md
+++ b/README.md
@@ -1,29 +1,38 @@
-## 1 Java程序员充电、求职必备的核心知识库
+## 1 JavaEdge - Java程序员充电、求职必备的核心知识库
 
 全方位详细深入阐述从入门到高级Java程序员必备的知识技能。按照现有计划，主要研究如下方面知识点： 
 
-0. Java SE重难点、包含但不限于集合、多线程、泛型、反射、I/O
-1. Java Web重难点，包含但不限于Servlet、Tomcat
-2. Java 后端开发流行框架，包含但不限于Spring、MyBatis
-3. 计算机理论基础，包含但不限于计算机操作系统（Linux）、计算机网络、常见的数据结构与算法（Java实现）
-4. 数据存储组件的基本操作与原理探究，包含但不限于MySQL、Redis、Kafka、Hive、HBase
-5. 分布式、微服务时下流行框架及理论，包含但不限于Spring Cloud Alibaba、Dubbo全家桶
-6. 设计模式思想及应用
-7. 云原生相关，包含但不限于 Docker、k8s
+0. Java SE重难点、包含但不限于集合、多线程、泛型、反射、I/O； 
+1. Java Web重难点，包含但不限于Servlet、JSP、Tomcat、Jetty ；
+2. Java EE开发流行框架，包含但不限于Spring、MyBatis、Hibernate、Vert.X；
+3. 计算机理论基础，包含但不限于计算机操作系统（Linux）、计算机网络、常见的数据结构与算法（Java实现） ；
+4. 数据存储组件的基本操作与原理探究，包含但不限于MySQL、Redis、Kafka、Hive、HBase ；
+5. 分布式、微服务时下流行框架及理论，包含但不限于CAP理论及其相关算法、Zookeeper、Spring Cloud Alibaba、Dubbo；
+6. 编程设计模式思想及其在各种框架中的实际应用（Java实现） 
+7. 云原生时代的宠儿，包含但不限于 Docker、k8s
 
 ## 2 公众号
 
 更多精彩内容将发布在公众号 **JavaEdge**，公众号提供大量求职面试资料，后台回复 "面试" 即可领取。
 
-本号系统整理了Java高级工程师必备技能点，帮你理清纷杂面试知识点，有的放矢。
+本号系统整理了Java 高级工程师必备技能点，帮你理清纷杂面试知识点，有的放矢。我本人也是基于这些知识体系，在各种求职征途中拿到百度、携程、华为、中兴、顺丰、帆软等offer。
+
+<img src="assets/公众号.png">
 
 ## 3 笔者简介
+
 ### [阿里云栖社区博客专家](https://yq.aliyun.com/users/article?spm=a2c4e.8091938.headeruserinfo.3.65993d6eqaQ0O6)
 
+![](https://img-blog.csdnimg.cn/20190712131824494.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
 ### [腾讯云自媒体邀约计划作者](https://cloud.tencent.com/developer/user/1752328)
+
+![](https://img-blog.csdnimg.cn/20190712140323352.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+
 <br>
 
-## 4 目录结构
+## 4 目录结构（不断优化中）
 
 | &nbsp;数据结构与算法&nbsp; | 操作系统 | &nbsp;网络&nbsp; | 面向对象 | &nbsp;&nbsp;数据存储&nbsp;&nbsp; | &nbsp;&nbsp;&nbsp;Java&nbsp;&nbsp;&nbsp; | 架构设计 | &nbsp;&nbsp;&nbsp;框架&nbsp;&nbsp;&nbsp; | 编程规范 | &nbsp;&nbsp;&nbsp;职业规划&nbsp;&nbsp;&nbsp; |
 | :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:|
@@ -83,8 +92,11 @@
 
 ### :memo: 职业规划
 
-### 本人微信
-<img src="assets/个人微信.jpeg" width="180px">
+## QQ 群
+
+为大家提供一个学习交流平台，在这里你可以自由地讨论技术问题。
+
+<img src="assets/QQ 群.JPG" width="180px">
 
 ## Java源码模拟面试解析指南
 
@@ -95,17 +107,4 @@
 ### 绘图工具
 
 - [draw.io](https://www.draw.io/)
-- keynote
-
-再分享我整理汇总的一些 Java 面试相关资料（亲自验证，严谨科学！别再看网上误导人的垃圾面试题！！！），助你拿到更多 offer！
-
-![](https://img-blog.csdnimg.cn/35dcdea77d6d4845a18ef4780309a2a6.png)
-
-[点击获取更多经典必读电子书！](https://mp.weixin.qq.com/s?__biz=MzUzNTY5MzA3MQ==&mid=2247497273&idx=1&sn=b0f1e2e03cd7de3ce5d93cc8793d6d88&chksm=fa832459cdf4ad4fb046c0beb7e87ecea48f338278846679ef65238af45f0a135720e7061002&token=766333302&lang=zh_CN#rd)
-
-2023年最新Java学习路线一条龙：
-
-[![](https://img-blog.csdnimg.cn/0fe00585e984406fbd9c22cedbf4b239.png)](https://www.nowcoder.com/discuss/353159357007339520?sourceSSR=users)
-
-
-再给大家推荐一个学习 前后端软件开发 和准备Java 面试的公众号[【JavaEdge】](https://mp.weixin.qq.com/s?__biz=MzUzNTY5MzA3MQ==&mid=2247498257&idx=1&sn=b09d88691f9bfd715e000b69ef61227e&chksm=fa832871cdf4a1675d4491727399088ca488fa13e0a3cdf2ece3012265e5a3ef273dff540879&token=766333302&lang=zh_CN#rd)（强烈推荐！）
+- keynote
\ No newline at end of file
diff --git a/Spring/Spring Boot/Spring Boot Actuator.md b/Spring/Spring Boot/Spring Boot Actuator.md
deleted file mode 100644
index b594d64889..0000000000
--- a/Spring/Spring Boot/Spring Boot Actuator.md	
+++ /dev/null
@@ -1,77 +0,0 @@
-# 整合
-- 添加依赖
-![](https://img-blog.csdnimg.cn/20190916234856333.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 启动应用
-![](https://img-blog.csdnimg.cn/2019091623500231.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- [打开链接](http://localhost:8080/actuator)
-![](https://img-blog.csdnimg.cn/20190916235411576.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190917001217324.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-## 健康信息
-健康信息可以检查应用的运行状态，它经常被监控软件用来提醒人们生产环境是否存在问题。health端点暴露的默认信息取决于端点是如何被访问的。对于一个非安全，未认证的连接只返回一个简单的'status'信息。对于一个安全或认证过的连接其他详细信息也会展示
-Spring Boot包含很多自动配置的HealthIndicators，你也可以写自己的。
-
-### 自动配置的HealthIndicators
-Spring Boot在合适的时候会自动配置以下HealthIndicators：
-![](https://img-blog.csdnimg.cn/20190917002830278.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 下表显示了内置状态的默认状态映射：
-![](https://img-blog.csdnimg.cn/20190917003004321.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-- 让我们配置一下health节点,并重启应用
-![](https://img-blog.csdnimg.cn/20190917001814832.png)
-- 可看到对于磁盘的监控信息
-![](https://img-blog.csdnimg.cn/20190917001849686.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-## 应用信息
-应用信息会暴露所有InfoContributor beans收集的各种信息，Spring Boot包含很多自动配置的InfoContributors，你也可以编写自己的实现。
-### 自动配置的InfoContributors
-Spring Boot会在合适的时候自动配置以下InfoContributors：
-![](https://img-blog.csdnimg.cn/20190917003252246.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-> 注 使用management.info.defaults.enabled属性可禁用以上所有InfoContributors。
-
-### 自定义应用info信息
-通过设置Spring属性info.*，你可以定义info端点暴露的数据。所有在info关键字下的Environment属性都将被自动暴露，例如，你可以将以下配置添加到application.properties：
-```
-info.app.encoding=UTF-8
-info.app.java.source=1.8
-info.app.java.target=1.8
-```
-注 你可以在构建时扩展info属性，而不是硬编码这些值。假设使用Maven，你可以按以下配置重写示例：
-```
-info.app.encoding=@project.build.sourceEncoding@
-info.app.java.source=@java.version@
-info.app.java.target=@java.version@
-```
-### Git提交信息
-info端点的另一个有用特性是，在项目构建完成后发布git源码仓库的状态信息。如果GitProperties bean可用，Spring Boot将暴露git.branch，git.commit.id和git.commit.time属性。
-
-> 注 如果classpath根目录存在git.properties文件，Spring Boot将自动配置GitProperties bean。查看Generate git information获取更多详细信息。
-
-使用management.info.git.mode属性可展示全部git信息（比如git.properties全部内容）：
-```
-management.info.git.mode=full
-```
-### 构建信息
-如果BuildProperties bean存在，info端点也会发布你的构建信息。
-
-注 如果classpath下存在META-INF/build-info.properties文件，Spring Boot将自动构建BuildProperties bean。Maven和Gradle都能产生该文件
-
-- 配置info
-![](https://img-blog.csdnimg.cn/20190917003634110.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 启动观察输出信息![](https://img-blog.csdnimg.cn/20190917003721284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-- SpringBoot支持很多端点,除了默认显示的几个,还可以激活暴露所有端点
-![](https://img-blog.csdnimg.cn/20190917004116115.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190917004052711.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 如果只想暴露某个端点也是可以的
-![](https://img-blog.csdnimg.cn/2019091700435537.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- 查看JVM最大内存
-![](https://img-blog.csdnimg.cn/20190917004447618.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## 3 Beans
-Bean 端点提供有关应用程序 bean 的信息。
-### 获取 Beans
-- /actuator/beans GET 请求
-![](https://img-blog.csdnimg.cn/9f6178ded60f4b8ba018bc2bdef864de.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_12,color_FFFFFF,t_70,g_se,x_16)
-响应的结构：
-![](https://img-blog.csdnimg.cn/2f2e8b7d5b0643c5a18f25968242c948.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
diff --git "a/Spring/Spring Boot/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\344\275\240\344\273\254\346\234\215\345\212\241\346\234\200\345\244\247\347\232\204\345\271\266\345\217\221\351\207\217\346\230\257\345\244\232\345\260\221\357\274\237.md" "b/Spring/Spring Boot/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\344\275\240\344\273\254\346\234\215\345\212\241\346\234\200\345\244\247\347\232\204\345\271\266\345\217\221\351\207\217\346\230\257\345\244\232\345\260\221\357\274\237.md"
deleted file mode 100644
index dff1968478..0000000000
--- "a/Spring/Spring Boot/\351\235\242\350\257\225\345\256\230\351\227\256\357\274\232\344\275\240\344\273\254\346\234\215\345\212\241\346\234\200\345\244\247\347\232\204\345\271\266\345\217\221\351\207\217\346\230\257\345\244\232\345\260\221\357\274\237.md"	
+++ /dev/null
@@ -1,63 +0,0 @@
-Spring Boot 能支持的最大并发量主要看其对Tomcat的设置。由于现在都使用的是springboot服务，配置文件中也没有配置Tomcat 相关参数，基本都是使用默认的Tomcat的线程配置。
-![](https://img-blog.csdnimg.cn/20210703192756544.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-默认设置中，Tomcat的最大线程数200，最大连接数10000。
-
-# 并发量指的是连接数，还是线程数？
-连接数。
-
-# 200个线程如何处理10000条连接？
- Tomcat有两种处理连接的模式
-## BIO
-一个线程只处理一个Socket连接
-## NIO
-一个线程处理多个Socket连接。由于HTTP请求不会太耗时，而且多个连接一般不会同时来消息，所以一个线程处理多个连接没有太大问题。
-
-# 为什么不多开几个线程？
-多开线程的代价就是，增加上下文切换的时间，浪费CPU时间，另外还有就是线程数增多，每个线程分配到的时间片就变少。
-多开线程≠提高处理效率。
-
-# 为何不增大最大连接数？
-增大最大连接数，支持的并发量确实可以上去。但是在没有改变硬件条件的情况下，这种并发量的提升必定以牺牲响应时间为代价。
-
-# 配置文件为空，这些默认配置哪来的？
-Spring Boot的默认配置信息，都在 spring-boot-autoconfigure-版本号.jar 这个包中。
-
-Tomcat配置：`org.springframework.boot.autoconfigure.web.ServerProperties.java`
-![](https://img-blog.csdnimg.cn/20210703195025204.png)
-
-
-最佳线程数目 = （（线程等待时间+线程CPU时间）/线程CPU时间 ）* CPU数目。
-
-
-最佳线程数目 = （（线程等待时间+线程CPU时间）/线程CPU时间 ）* CPU数目，
-
-# CPU密集型
-操作内存处理的业务，一般线程数设置为：CPU核数 + 1 或者 CPU核数*2。核数为4的话，一般设置 5 或 8 。
-
-# IO密集型
-文件操作，网络操作，数据库操作，一般线程设置为：cpu核数 / (1-0.9)，核数为4的话，一般设置 40 
-
-```bash
-maxThreads="8"  //最大并发数 
-minSpareThreads="100"///初始化时创建的线程数
-maxSpareThreads="500"///一旦创建的线程超过这个值，Tomcat就会关闭不再需要的socket线程。
-acceptCount="700"// 指定当所有可以使用的处理请求的线程数都被使用时，可以放到处理队列中的请求数，超过这个数的请求将不予处理
-
-maxThreads 客户请求最大线程数 
-minSpareThreads Tomcat初始化时创建的 socket 线程数 
-maxSpareThreads Tomcat连接器的最大空闲 socket 线程数 
-enableLookups 若设为true, 则支持域名解析，可把 ip 地址解析为主机名 
-redirectPort 在需要基于安全通道的场合，把客户请求转发到基于SSL 的 redirectPort 端口 
-acceptAccount 监听端口队列最大数，满了之后客户请求会被拒绝（不能小于maxSpareThreads ） 
-connectionTimeout 连接超时 
-minProcessors 服务器创建时的最小处理线程数 
-maxProcessors 服务器同时最大处理线程数 
-URIEncoding URL统一编码
-
-maxThreads：处理的最大并发请求数，默认值200
-minSpareThreads：最小线程数始终保持运行，默认值10
-maxConnections：在给定时间接受和处理的最大连接数，默认值10000
-```
-
-> 参考
-> - http://tomcat.apache.org/tomcat-8.0-doc/config/http.html#HTTP/1.1_and_HTTP/1.0_Support
\ No newline at end of file
diff --git "a/Spring/Spring RestTemplate\344\270\272\344\275\225\345\277\205\351\241\273\346\220\255\351\205\215MultiValueMap\357\274\237.md" "b/Spring/Spring RestTemplate\344\270\272\344\275\225\345\277\205\351\241\273\346\220\255\351\205\215MultiValueMap\357\274\237.md"
deleted file mode 100644
index 0281c13f2c..0000000000
--- "a/Spring/Spring RestTemplate\344\270\272\344\275\225\345\277\205\351\241\273\346\220\255\351\205\215MultiValueMap\357\274\237.md"	
+++ /dev/null
@@ -1,53 +0,0 @@
-微服务之间的大多都是使用 HTTP 通信，这自然少不了使用 HttpClient。
-在不适用 Spring 前，一般使用 Apache HttpClient 和 Ok HttpClient 等，而一旦引入 Spring，就有了更好选择 -  RestTemplate。
-
-接口：
-![](https://img-blog.csdnimg.cn/f7633a690cec471787c55a5b34722db1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-想接受一个 Form 表单请求，读取表单定义的两个参数 para1 和 para2，然后作为响应返回给客户端。
-
-定义完接口后，使用 RestTemplate 来发送一个这样的表单请求，代码示例如下：
-![](https://img-blog.csdnimg.cn/96362640fa3b4354b5b0a32687c302b1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-上述代码定义了一个 Map，包含了 2 个表单参数，然后使用 RestTemplate 的 postForObject 提交这个表单。
-执行代码提示 400 错误，即请求出错：
-![](https://img-blog.csdnimg.cn/a2dfa488fb0f415aacb08441bd35e76d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-就是缺少 para1 表单参数，why？
-# 解析
-RestTemplate 提交的表单，最后提交请求啥样？
-Wireshark 抓包：
-![](https://img-blog.csdnimg.cn/94de06901df043ac9fa9a8de77ed9fd6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-实际上是将定义的表单数据以 JSON 提交过去了，所以我们的接口处理自然取不到任何表单参数。
-why？怎么变成 JSON 请求体提交数据呢？注意 RestTemplate  执行调用栈：
-![](https://img-blog.csdnimg.cn/63895ac7bac84bde8dce1d208938af54.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-最终使用的 Jackson 工具序列化了表单
-![](https://img-blog.csdnimg.cn/8f4582ece1c244baabe31b43463db638.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-用到 JSON 的关键原因在
-### RestTemplate.HttpEntityRequestCallback#doWithRequest
-![](https://img-blog.csdnimg.cn/c779397a10a14cf3a9b5fe3635b22b8b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-根据当前要提交的 Body 内容，遍历当前支持的所有编解码器:
-- 若找到合适编解码器，用之完成 Body 转化
-
-看下 JSON 的编解码器对是否合适的判断
-### AbstractJackson2HttpMessageConverter#canWrite
-![](https://img-blog.csdnimg.cn/cf45893c65b5498eb2d16638ce7873e5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-可见，当使用的 Body 为 HashMap，是可完成 JSON 序列化的。
-所以后续将这个表单序列化为请求 Body了。
-
-但我还是疑问，为何适应表单处理的编解码器不行？
-那就该看编解码器判断是否支持的实现：
-### FormHttpMessageConverter#canWrite![](https://img-blog.csdnimg.cn/ee0f19a3ed6c43269a58ddb3179591f5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-可见只有当我们发送的 Body 是 MultiValueMap 才能使用表单来提交。
-原来使用 RestTemplate 提交表单必须是 MultiValueMap！
-而我们案例定义的就是普通的 HashMap，最终是按请求 Body 的方式发送出去的。
-# 修正
-换成 MultiValueMap 类型存储表单数据即可：
-![](https://img-blog.csdnimg.cn/ace1283803104462bab2261d6f9789d3.png)
-修正后，表单数据最终使用下面的代码进行了编码：
-### FormHttpMessageConverter#write
-![](https://img-blog.csdnimg.cn/312d71a80572443b8bb153dcdbd15e0e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-发送出的数据截图如下：
-![](https://img-blog.csdnimg.cn/244f8819935244d59083e66ca6b16ea4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-这就对了！其实官方文档也说明了：
-![](https://img-blog.csdnimg.cn/7020d620d5b544a29d022877dcfac1a8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-> 参考：
-> - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html
\ No newline at end of file
diff --git "a/Spring/Spring \346\263\250\345\205\245\351\233\206\345\220\210\347\261\273\345\236\213.md" "b/Spring/Spring \346\263\250\345\205\245\351\233\206\345\220\210\347\261\273\345\236\213.md"
deleted file mode 100644
index a03f12b62c..0000000000
--- "a/Spring/Spring \346\263\250\345\205\245\351\233\206\345\220\210\347\261\273\345\236\213.md"	
+++ /dev/null
@@ -1,111 +0,0 @@
-集合类型的自动注入是Spring提供的另外一个强大功能。我们在方便的使用依赖注入的特性时，必须要思考对象从哪里注入、怎么创建、为什么是注入这一个对象的。虽然编写框架的目的是让开发人员无需关心太多底层细节，能专心业务逻辑的开发，但是作为开发人员不能真的无脑去使用框架。
-务必学会注入集合等高级用法，让自己有所提升！
-
-现在有一需求：存在多个用户Bean，找出来存储到一个List。
-# 1 注入方式
-## 1.1 收集方式
-多个用户Bean定义：
-![](https://img-blog.csdnimg.cn/ac65da5cd0bd43818389f5deab4803e6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-有了集合类型的自动注入后，即可收集零散的用户Bean：
-![](https://img-blog.csdnimg.cn/20d0ea3055e348a2860e82ceeca9f36f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-这样即可完成集合类型注入：
-![](https://img-blog.csdnimg.cn/1d3ae50349f743738a2d7473936e2a3a.png)
-
-
-但当持续增加一些user时，可能就不喜欢用上述的注入集合类型了，而是这样：
-## 1.2 直接装配方式
-![](https://img-blog.csdnimg.cn/b3cf4f059ebe48a2b352ea48b274fe9d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/b1cea2e7b6fb4f7b9ac241002cc2e953.png)
-
-分开玩，大家应该不会有啥问题，若两种方式共存了，会咋样？
-运行程序后发现直接装配方式的未生效：
-![](https://img-blog.csdnimg.cn/ce05a1948eaa414082d8f18685dfdad1.png)
-
-这是为啥呢？
-# 2 源码解析
-就得精通这两种注入风格在Spring分别如何实现的。
-## 2.1 收集装配
-###  DefaultListableBeanFactory#resolveMultipleBeans
-```java
-private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,
-      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
-   final Class<?> type = descriptor.getDependencyType();
-   if (descriptor instanceof StreamDependencyDescriptor) {
-      // 装配stream
-      return stream;
-   }
-   else if (type.isArray()) {
-      // 装配数组
-      return result;
-   }
-   else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
-      // 装配集合
-      // 获取集合的元素类型
-      Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
-      if (elementType == null) {
-         return null;
-      }
-      // 根据元素类型查找所有的bean
-      Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
-            new MultiElementDescriptor(descriptor));
-      if (matchingBeans.isEmpty()) {
-         return null;
-      }
-      if (autowiredBeanNames != null) {
-         autowiredBeanNames.addAll(matchingBeans.keySet());
-      }
-      // 转化查到的所有bean放置到集合并返回
-      TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
-      Object result = converter.convertIfNecessary(matchingBeans.values(), type);
-      // ...
-      return result;
-   }
-   else if (Map.class == type) {
-      // 解析map
-      return matchingBeans;
-   }
-   else {
-      return null;
-   }
-}
-```
-#### 1 获取集合类型的elementType
-目标类型定义为List<User> users，所以元素类型为User：
-![](https://img-blog.csdnimg.cn/f8d0c4f475bb431ea81cd62c880a34cf.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-#### 2 根据元素类型找出所有Bean
-有了elementType，即可据其找出所有Bean：
-![](https://img-blog.csdnimg.cn/a2d520efa38749b79dc754c4eccaf158.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-#### 3 将匹配的所有的Bean按目标类型转化
-上一步获取的所有的Bean都以`java.util.LinkedHashMap.LinkedValues`存储，和目标类型大不相同，所以最后按需转化。
-
-本案例中，需转化为List：
-![](https://img-blog.csdnimg.cn/4fa60adda03b4c9e8ca483a4729d32bf.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-## 2.2 直接装配方式
-#### DefaultListableBeanFactory#findAutowireCandidates
-不再赘述。
-
-最后就是根据目标类型直接寻找匹配Bean名称为users的`List<user>`装配给userController#users属性。
-
-当同时满足这两种装配方式时，Spring会如何处理呢？
-## DefaultListableBeanFactory#doResolveDependency
-![](https://img-blog.csdnimg.cn/c644acc0147a4c09adb3c18d3325083b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-显然这两种装配集合的方式不能同存，结合本案例：
-- 当使用收集装配时，能找到任一对应Bean，则返回
-- 若一个都没找到，才采用直接装配
-
-所以后期以List方式直接添加的user Bean都不生效！
-# 3 修正
-务必避免两种方式共存去装配集合！只选用一种方式即可。
-比如只使用直接装配：
-![](https://img-blog.csdnimg.cn/73198359e18e441ab5c2bbc0337eb2ac.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)只使用收集方式：
-![](https://img-blog.csdnimg.cn/16d5c6659cd94bb68a4fad26224cbcf0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-如何做到让用户2优先输出呢？
-控制spring bean加载顺序：
-1. Bean上使用@Order注解，如@Order(2)。数值越小表示优先级越高。默认优先级最低。
-2. @DependsOn 使用它，可使得依赖的Bean如果未被初始化会被优先初始化。
-3. 添加@Order(number)注解，number越小优先级越高，越靠前
-4. 声明user这些Bean时将id=2的user提到id=1之前
\ No newline at end of file
diff --git "a/Spring/SpringFramework/@Autowired-\344\270\216@Resource\344\271\213\344\272\211.md" "b/Spring/SpringFramework/@Autowired-\344\270\216@Resource\344\271\213\344\272\211.md"
new file mode 100644
index 0000000000..aed93a2df1
--- /dev/null
+++ "b/Spring/SpringFramework/@Autowired-\344\270\216@Resource\344\271\213\344\272\211.md"
@@ -0,0 +1,236 @@
+# 1  共同点
+- 都可以用来装配bean.
+- 都可以写在字段上
+- 或写在setter方法上
+
+
+# 2 @Autowired 默认按 类型 装配（该注解是属Spring）
+默认要求依赖对象必须存在，如果要允许`null`值，可以设置它的`required`属性为`false`
+```
+@Autowired(required=false) 
+```
+
+若我们想使用名称装配可以结合`@Qualifier`注解一起食用，如下：
+```
+@Autowired() @Qualifier("baseDao")    
+private BaseDao baseDao;
+```
+
+# 3 @Resource 默认按 名称 装配(JDK1.6开始支持的注解)
+## 名称可以通过name属性进行指定
+若没有指定`name`属性
+- 当注解写在字段上时，默认取字段名，按照名称查找
+- 如果注解写在setter方法上默认取属性名进行装配
+- 当找不到与名称匹配的bean时才按照类型进行装配
+
+> name属性一旦指定，就只会按照名称进行装配。
+
+只不过注解处理器我们使用的是Spring提供的，是一样的，无所谓解耦不解耦的说法，两个在便利程度上是等同的。
+```
+@Resource(name="baseDao")    
+private BaseDao baseDao; 
+
+```
+
+> **byName** 通过参数名自动装配，如果一个bean的name 和另外一个bean的 property 相同，就自动装配
+**byType** 通过参数的数据类型自动自动装配，如果一个bean的数据类型和另外一个bean的property属性的数据类型兼容，就自动装配
+
+-----------------------------------------------------------------------------------------------------------------------------------------
+
+# 4 实例
+我们可以通过 @Autowired / @Resource 在 Bean 类中使用自动注入功能
+但是 Bean 一般还是需要在 XML 文件中通过 <bean> 进行定义 —— 也就是说，在 XML 配置文件中定义 Bean，通过@Autowired 或 @Resource 为 Bean 的成员变量、方法入参或构造函数入参提供自动注入的功能。
+
+- 比如下面的beans.xml
+![](https://upload-images.jianshu.io/upload_images/16782311-33b1fe625f560db1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-bdeeb0703bbb23c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+定义了三个bean对象，但是没有了我们所述的的ref指向的内容,如下
+![](https://upload-images.jianshu.io/upload_images/16782311-418ad4fdc7435ca7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+-------------------------------------------------------------------------------------------------------
+
+# 5 区别
+spring2.5提供了基于注解的配置，可以通过注解的方式来完成注入依赖
+在Java代码中可以使用 `@Resource`或者`@Autowired`注解方式来进行注入
+
+但它们之间是有区别的
+
+## 5.1 首先来看一下：
+- @Resource默认是按照名称来装配注入的，只有当找不到与名称匹配的bean才会按照类型来装配注入
+- @Autowired默认是按照类型装配注入的，如果想按照名称来转配注入，则需要结合`@Qualifier`一起使用
+- @Resource注解是由JDK提供，而@Autowired是由Spring提供
+- @Resource和@Autowired都可以书写标注在字段或者该字段的setter方法之上
+
+## 5.2 使用注解的方式
+- 我们需要修改spring配置文件的头信息
+![](https://upload-images.jianshu.io/upload_images/16782311-7f4ed6eba0ca5725.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+
+- 在基于注解方式配置Spring的配置文件中，你可能会见到
+![](https://upload-images.jianshu.io/upload_images/16782311-3f2f1c60a46edb16.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 他的作用是式地向 Spring 容器注册
+```
+AutowiredAnnotationBeanPostProcessor
+CommonAnnotationBeanPostProcessor
+PersistenceAnnotationBeanPostProcessor
+RequiredAnnotationBeanPostProcessor
+```
+
+注册这4个BeanPostProcessor的作用，就是为了你的系统能够识别相应的注解。
+
+例如：
+
+要使用@Autowired，就必须事先在 Spring 容器中声明 `AutowiredAnnotationBeanPostProcessor Bean`
+
+传统声明方式如下
+```
+<bean class="org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor "/> 
+```
+
+要使用@ Resource 、@ PostConstruct、@ PreDestroy等注解就必须声明`CommonAnnotationBeanPostProcessor`
+```
+<bean class="org.springframework.beans.factory.annotation. CommonAnnotationBeanPostProcessor"/> 
+```
+
+要使用@PersistenceContext注解，就必须声明`PersistenceAnnotationBeanPostProcessor`的
+```
+<bean class="org.springframework.beans.factory.annotation.PersistenceAnnotationBeanPostProcessor"/> 
+```
+
+要使用 @Required的注解，就必须声明`RequiredAnnotationBeanPostProcessor`
+```
+<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/> 
+```
+
+一般来说，这些注解我们还是比较常用，尤其是`Antowired`，在自动注入的时候更是经常使用，所以如果总是需要按照传统的方式一条一条配置显得有些繁琐和没有必要
+于是spring给我们提供`<context:annotation-config/>`的简化配置方式，自动帮你完成声明
+
+不过,都2019了,我们一般都会配置`包扫描路径`选项
+```
+<context:component-scan base-package=”XX.XX”/>
+```
+该配置项其实也包含了自动注入上述processor的功能，可以将 `<context:annotation-config/> `移除了.
+
+**比如：**
+
+```
+<context:component-scan base-package="carPoolingController, carPoolingService, carPoolingDao" />
+
+```
+就把controller包下 service包下 dao包下的注解全部扫描了
+
+
+## 5.3 修改以上配置文件的头信息后，我们就可以在Java代码通过注解方式来注入bean
+看下面代码
+### 5.3.1 @Resource
+
+```
+public class StudentService3 implements IStudentService {
+    //@Resource(name="studentDao")放在此处也是可行的
+    private IStudentDao studentDao;
+
+    private String id;
+
+    public void setId(String id) {
+    this.id = id;
+    }
+}
+```
+
+@Resource(name="studentDao") // 通过此注解完成从spring配置文件中查找名称为studentDao的bean来装配字段studentDao，如果spring配置文件中不存在 studentDao名称的bean则转向按照bean类型进行查找
+```
+public void setStudentDao(IStudentDao studentDao) {
+        this.studentDao = studentDao;
+}
+public void saveStudent() {
+        studentDao.saveStudent();
+        System.out.print(",ID 为："+id);
+}
+```
+
+配置文件添加如下信息
+
+```
+<bean id="studentDao" class="com.sss.dao.impl.StudentDao"></bean>
+<bean id="studentService3" class="com.sss.service.impl.StudentService3"></bean>
+
+```
+
+### 5.3.2 @Autowired
+
+```
+public class StudentService3 implements IStudentService {
+  //@Autowired放在此处也是可行的
+  private IStudentDao studentDao;
+
+  private String id;
+
+  public void setId(String id) {
+       this.id = id;
+   }
+
+```
+通过此注解完成从spring配置文件中 查找满足studentDao类型的bean
+@Qualifier("studentDao")则按照名称经行来查找转配的
+
+```
+ public void setStudentDao(IStudentDao studentDao) {
+       this.studentDao = studentDao;
+  }
+
+  public void saveStudent() {
+       studentDao.saveStudent();
+       System.out.print(",ID 为："+id);
+  }
+}
+
+```
+
+配置文件添加如下信息
+
+```
+<bean id="studentDao" class="com.wch.dao.impl.StudentDao"></bean>
+<bean id="studentService3" class="com.wch.service.impl.StudentService3" />
+
+```
+--------------------------
+
+> 当我们在xml里面为类配置注入对象时，会发现xml文件会越来越臃肿，维护起来很麻烦
+这时候我们可以使用注解这种机制来为类配置注入对象。
+
+**原生java为我们提供了 javax.annotation.Resource这个注解。**
+
+**spring框架提供了org.springframework.beans.factory.annotation.Autowired。**
+
+一般情况下我们使用 javax.annotation.Resource这个注解，因为这样我们就能实现和spring框架的解藕（不过注解处理器还是Spring提供的）。
+
+# @Resource可以作用于字段和函数上
+## 当作用于字段上的时候
+如果我们只是简单的这样写
+
+```
+@Resource
+PersonDao  p;
+
+```
+
+这时候spring注入p的过程是 
+- 查找xml中是否有id为p的元素
+- 如果没有找到，则看是否有name属性（@Resource name=“”），有则查找name
+- 否则查找PersonDao类型的元素
+
+## @Resource可作用于set函数上。
+例如：
+```
+@Resource
+public void setP(PersonDao p) {
+  this.p = p;
+}
+
+```
+@Autowired注解是根据类型进行查找，比如PersonDao p，他会去xml文件里查找类型为PersonDao的元素
diff --git "a/Spring/SpringFramework/@Autowired\343\200\201@Inject\343\200\201@Resource\347\232\204\345\214\272\345\210\253.md" "b/Spring/SpringFramework/@Autowired\343\200\201@Inject\343\200\201@Resource\347\232\204\345\214\272\345\210\253.md"
deleted file mode 100644
index 6303637b13..0000000000
--- "a/Spring/SpringFramework/@Autowired\343\200\201@Inject\343\200\201@Resource\347\232\204\345\214\272\345\210\253.md"
+++ /dev/null
@@ -1,99 +0,0 @@
-# 共同点
-- 装配bean
-- 写在字段上
-- 写在setter方法上
-
-# @Autowired
-该注解属Spring，默认按类型装配。
-可以作用在变量、setter方法、构造器。
-
-默认要求依赖对象必须存在，如果要允许`null`值，可以设置它的`required`属性为`false`，这样当没有找到相应bean的时候，程序不会抛错。
-
-```java
-@Autowired(required=false) 
-```
-
-若想使用名称装配，需要配合`@Qualifier`一起食用，如下：
-```java
-@Autowired() @Qualifier("baseDao")    
-private BaseDao baseDao;
-```
-
-# @Resource
-默认按名称装配(JDK1.6开始支持的注解)。@Resource是JSR250规范的实现，需要导入javax.annotation实现注入。
-不可以用在构造器。
-
-名称可以通过name属性进行指定，若没有指定`name`属性：
-- 当注解写在字段上时，默认取字段名，按照名称查找
-- 如果注解写在setter方法上默认取属性名进行装配
-- 当找不到与名称匹配的bean时才按照类型进行装配
-
-> name属性一旦指定，就只会按照名称进行装配。
-
-只不过注解处理器我们使用的是Spring提供的，是一样的，无所谓解耦不解耦的说法，两个在便利程度上是等同的。
-
-### @Resource作用于字段
-```java
-@Resource(name="base")    
-private BaseDao baseDao; 
-```
-Spring注入p的过程：
-- 查找xml中是否有id为baseDao的元素
-- 如果没有找到，则看是否有name属性（@Resource name=“base”），有则查找name
-- 否则查找BaseDao类型的元素
-
-> **byName** 通过参数名自动装配，如果一个bean的name 和另外一个bean的 property 相同，就自动装配
-**byType** 通过参数的数据类型自动自动装配，如果一个bean的数据类型和另外一个bean的property属性的数据类型兼容，就自动装配
-
-# @Inject
-JSR330 (Dependency Injection for Java)中的规范，需要导入javax.inject.Inject。根据类型进行自动装配的，如果需要按名称进行装配，则需要配合@Named。
-可以作用在变量、setter方法、构造器。
-
-@Autowired、@Inject用法基本一样，不同的是@Autowired有一个request属性。
-
-
-# 使用注解的方式
-在基于注解方式配置Spring的配置文件中，你可能会见到
-![](https://img-blog.csdnimg.cn/img_convert/e1db938f59a5e7cfe80959703c3dfbce.png)
-作用是向 Spring 容器注册
-```bash
-AutowiredAnnotationBeanPostProcessor
-CommonAnnotationBeanPostProcessor
-PersistenceAnnotationBeanPostProcessor
-RequiredAnnotationBeanPostProcessor
-```
-注册这4个BeanPostProcessor的作用，就是为了你的系统能够识别相应的注解。
-
-例如要使用@Autowired，就必须事先在 Spring 容器中声明 `AutowiredAnnotationBeanPostProcessor Bean`，传统声明方式如下
-```xml
-<bean class="org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor "/> 
-```
-要使用@ Resource 、@ PostConstruct、@ PreDestroy等注解就必须声明`CommonAnnotationBeanPostProcessor`
-```xml
-<bean class="org.springframework.beans.factory.annotation. CommonAnnotationBeanPostProcessor"/> 
-```
-要使用@PersistenceContext注解，就必须声明`PersistenceAnnotationBeanPostProcessor`的
-```xml
-<bean class="org.springframework.beans.factory.annotation.PersistenceAnnotationBeanPostProcessor"/> 
-```
-要使用 @Required的注解，就必须声明`RequiredAnnotationBeanPostProcessor`
-```xml
-<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/> 
-```
-如果总是需要按照传统的方式一条一条配置显得有些繁琐和没有必要
-于是spring给我们提供`<context:annotation-config/>`的简化配置方式，自动帮你完成声明。
-
-都2021了，一般都会配置`包扫描路径`选项
-```xml
-<context:component-scan base-package=”XX.XX”/>
-```
-该配置项其实也包含了自动注入上述processor的功能，可以将 `<context:annotation-config/> `移除了.
-
-**比如：**
-
-```xml
-<context:component-scan base-package="carPoolingController, carPoolingService, carPoolingDao" />
-```
-就把controller包下 service包下 dao包下的注解全部扫描了
-
-一般使用 @Resource，因为这样我们就能实现和spring框架的解耦（不过注解处理器还是Spring提供）。
\ No newline at end of file
diff --git "a/Spring/SpringFramework/InitializingBean\344\275\277\347\224\250.md" "b/Spring/SpringFramework/InitializingBean\344\275\277\347\224\250.md"
new file mode 100644
index 0000000000..d2f4ce1464
--- /dev/null
+++ "b/Spring/SpringFramework/InitializingBean\344\275\277\347\224\250.md"
@@ -0,0 +1,78 @@
+InitializingBean接口为bean提供了初始化方法的方式
+
+它只包括afterPropertiesSet方法，凡是继承该接口的类，在初始化bean的时候会自动执行该方法。
+
+```
+
+package org.springframework.beans.factory;
+
+/**
+ * 所有由BeanFactory设置的所有属性都需要响应的bean的接口：例如，执行自定义初始化，或者只是检查所有强制属性是否被设置。
+ *实现InitializingBean的替代方法是指定一个自定义init方法，例如在XML bean定义中。
+ */
+public interface InitializingBean {
+
+	/**
+	 * 在BeanFactory设置了提供的所有bean属性后，由BeanFactory调用。 
+     *这个方法允许bean实例在所有的bean属性被设置时才能执行
+	 */
+	void afterPropertiesSet() throws Exception;
+
+}
+```
+
+在spring初始化bean的时候，如果该bean是实现了InitializingBean接口，并且同时在配置文件中指定了init-method，系统则先调用afterPropertiesSet方法，再调用init-method中指定的方法。
+```
+<bean id="testInitializingBean" class="com.TestInitializingBean" init-method="testInit"></bean>
+```
+这种方式在spring中是怎么实现的呢？
+通过查看spring加载bean的源码类(AbstractAutowireCapableBeanFactory)可以看出玄机
+AbstractAutowireCapableBeanFactory类中的invokeInitMethods方法讲解的非常清楚，源码如下：
+
+```
+protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
+			throws Throwable {
+		//首先看该bean是否实现了实现了InitializingBean接口，若已实现，则直接调用bean的afterPropertiesSet方法
+		boolean isInitializingBean = (bean instanceof InitializingBean);
+		if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
+			if (logger.isDebugEnabled()) {
+				logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
+			}
+			if (System.getSecurityManager() != null) {
+				try {
+					AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
+						@Override
+						public Object run() throws Exception {
+						    //直接调用afterPropertiesSet
+							((InitializingBean) bean).afterPropertiesSet();
+							return null;
+						}
+					}, getAccessControlContext());
+				}
+				catch (PrivilegedActionException pae) {
+					throw pae.getException();
+				}
+			}
+			else {
+			    //直接调用afterPropertiesSet
+				((InitializingBean) bean).afterPropertiesSet();
+			}
+		}
+
+		if (mbd != null) {
+			String initMethodName = mbd.getInitMethodName();
+			//判断是否指定了init-method方法，如果指定了init-method方法，则再调用制定的init-method
+			if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
+					!mbd.isExternallyManagedInitMethod(initMethodName)) {
+				//进一步查看该方法的源码，可以发现init-method方法中指定的方法是通过反射实现的
+				invokeCustomInitMethod(beanName, bean, mbd);
+			}
+		}
+	}
+```
+
+观察源码可得出以下结论
+
+ 1. spring为bean提供了两种初始化bean的方式，实现InitializingBean接口而实现afterPropertiesSet方法，或者在配置文件中同过init-method指定，两种方式可以同时使用
+ 2. 实现InitializingBean接口是直接调用afterPropertiesSet方法，比通过反射调用init-method指定的方法相对来说效率要高。但init-method方式消除了对spring的依赖
+ 3. 若调用afterPropertiesSet方法时产生异常，则也不会再调用init-method指定的方法
diff --git "a/Spring/SpringFramework/Spring AOP\345\210\233\345\273\272Proxy\347\232\204\350\277\207\347\250\213.md" "b/Spring/SpringFramework/Spring AOP\345\210\233\345\273\272Proxy\347\232\204\350\277\207\347\250\213.md"
deleted file mode 100644
index e1984562a7..0000000000
--- "a/Spring/SpringFramework/Spring AOP\345\210\233\345\273\272Proxy\347\232\204\350\277\207\347\250\213.md"	
+++ /dev/null
@@ -1,124 +0,0 @@
-> 全是干货的技术号：
-> 本文已收录在github，欢迎 star/fork：
-> [https://github.com/Wasabi1234/Java-Interview-Tutorial](https://github.com/Wasabi1234/Java-Interview-Tutorial)
-
-Spring在程序运行期，就能帮助我们把切面中的代码织入Bean的方法内，让开发者能无感知地在容器对象方法前后随心添加相应处理逻辑，所以AOP其实就是个代理模式。
-但凡是代理，由于代码不可直接阅读，也是初级程序员们 bug 的重灾区。
-#  1 案例
-某游戏系统，含负责点券充值的类CouponService，它含有一个充值方法deposit()：
-![](https://img-blog.csdnimg.cn/d92cc2a3908e4c0f8468ef42440bb940.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-deposit()会使用微信支付充值。因此在这个方法中，加入pay()。
-
-由于微信支付是第三方接口，需记录接口调用时间。
-引入 **@Around** 增强 ，分别记录在pay()方法执行前后的时间，并计算pay()执行耗时。
-![](https://img-blog.csdnimg.cn/82ec220929644cc7b751b148279a9c92.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-Controller：
-![](https://img-blog.csdnimg.cn/815d2b60a2ad49e888132a0da33fdbad.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-访问接口，会发现这段计算时间的切面并没有执行到，输出日志如下：
-![](https://img-blog.csdnimg.cn/36db9a5c4f584a6098a9f65be4a76646.png)
-切面类明明定义了切面对应方法，但却没执行到。说明在类的内部，通过this调用的方法，不会被AOP增强。
-# 2 源码解析
-- this对应的对象就是一个普通CouponService对象：
-![](https://img-blog.csdnimg.cn/cfb4ccb9412c4f28baba84a0ff75f5b8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-- 而在Controller层中自动装配的CouponService对象：
-![](https://img-blog.csdnimg.cn/24bcddfe649c427cad718d9e0a7fadea.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-是个被Spring增强过的Bean，所以执行deposit()时，会执行记录接口调用时间的增强操作。而this对应的对象只是一个普通的对象，并无任何额外增强。
-
-为什么this引用的对象只是一个普通对象？
-要从Spring AOP增强对象的过程来看。
-## 实现
-AOP的底层是动态代理，创建代理的方式有两种：
-- JDK方式
-只能对实现了接口的类生成代理，不能针对普通类
-- CGLIB方式
-可以针对类实现代理，主要是对指定的类生成一个子类，覆盖其中的方法，来实现代理对象。
-![](https://img-blog.csdnimg.cn/ddb123e788dc47e083433f5ccd24ce3a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-针对非Spring Boot程序，除了添加相关AOP依赖项外，还会使用 **@EnableAspectJAutoProxy** 开启AOP功能。
-这个注解类引入AspectJAutoProxyRegistrar，它通过实现ImportBeanDefinitionRegistrar接口完成AOP相关Bean准备工作。
-
-现在来看下创建代理对象的过程。先来看下调用栈：
-![](https://img-blog.csdnimg.cn/44c0f0955fa74569abd9baa3496a07a8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-- **创建代理对象的时机**
-创建一个Bean时
-
-创建的的关键工作由AnnotationAwareAspectJAutoProxyCreator完成
-## AnnotationAwareAspectJAutoProxyCreator
-一种BeanPostProcessor。所以它的执行是在完成原始Bean构建后的初始化Bean（initializeBean）过程中
-##### AbstractAutoProxyCreator#postProcessAfterInitialization
-```java
-public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
-   if (bean != null) {
-      Object cacheKey = getCacheKey(bean.getClass(), beanName);
-      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
-         return wrapIfNecessary(bean, beanName, cacheKey);
-      }
-   }
-   return bean;
-}
-```
-关键方法wrapIfNecessary：在需要使用AOP时，它会把创建的原始Bean对象wrap成代理对象，作为Bean返回。
-## AbstractAutoProxyCreator#wrapIfNecessary
-```java
-protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
-   // 省略非关键代码
-   Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
-   if (specificInterceptors != DO_NOT_PROXY) {
-      this.advisedBeans.put(cacheKey, Boolean.TRUE);
-      Object proxy = createProxy(
-            bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
-      this.proxyTypes.put(cacheKey, proxy.getClass());
-      return proxy;
-   }
-   // 省略非关键代码 
-}
-```
-## createProxy
-创建代理对象的关键：
-```java
-protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
-      @Nullable Object[] specificInterceptors, TargetSource targetSource) {
-  // ...
-  // 1. 创建一个代理工厂
-  ProxyFactory proxyFactory = new ProxyFactory();
-  if (!proxyFactory.isProxyTargetClass()) {
-   if (shouldProxyTargetClass(beanClass, beanName)) {
-      proxyFactory.setProxyTargetClass(true);
-   }
-   else {
-      evaluateProxyInterfaces(beanClass, proxyFactory);
-   }
-  }
-  Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
-  // 2. 将通知器（advisors）、被代理对象等信息加入到代理工厂
-  proxyFactory.addAdvisors(advisors);
-  proxyFactory.setTargetSource(targetSource);
-  customizeProxyFactory(proxyFactory);
-   // ...
-   // 3. 通过代理工厂获取代理对象
-  return proxyFactory.getProxy(getProxyClassLoader());
-}
-```
-经过这样一个过程，一个代理对象就被创建出来了。我们从Spring中获取到的对象都是这个代理对象，所以具有AOP功能。而之前直接使用this引用到的只是一个普通对象，自然也就没办法实现AOP的功能了。
-# 3 修正
-经过前面分析可知，**只有引用的是被 `动态代理` 所创对象，才能被Spring增强，实现期望的AOP功能**。
-
-那得怎么处理对象，才具备这样的条件？
-### 被@Autowired注解
-通过 **@Autowired**，在类的内部，自己引用自己：
-![](https://img-blog.csdnimg.cn/6c83d422014c47588a10659ebef5e4ae.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/c4ba19274d3e457a8705790e9ef7d94f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_18,color_FFFFFF,t_70,g_se,x_16)
-
-### 直接从AopContext获取当前Proxy
-AopContext，就是通过一个ThreadLocal来将Proxy和线程绑定起来，这样就可以随时拿出当前线程绑定的Proxy。
-
-使用该方案有个前提，需要在 **@EnableAspectJAutoProxy** 加配置项 **exposeProxy = true** ，表示将代理对象放入到ThreadLocal，这才可以直接通过 
-```java
-AopContext.currentProxy()
-```
-获取到，否则报错：
-![](https://img-blog.csdnimg.cn/8af06b17823a4c8dbec6ca6238807d92.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-于是修改代码：
-![](https://img-blog.csdnimg.cn/562eef29a2bb416c85b378a754ef0257.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-勿忘修改**EnableAspectJAutoProxy** 的 **exposeProxy**属性：
-![](https://img-blog.csdnimg.cn/11175d6f95ab462a8d444cca182493d3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
\ No newline at end of file
diff --git "a/Spring/SpringFramework/Spring AOP\345\210\260\345\272\225\346\230\257\344\273\200\344\271\210\357\274\237.md" "b/Spring/SpringFramework/Spring AOP\345\210\260\345\272\225\346\230\257\344\273\200\344\271\210\357\274\237.md"
deleted file mode 100644
index 5625f5ce76..0000000000
--- "a/Spring/SpringFramework/Spring AOP\345\210\260\345\272\225\346\230\257\344\273\200\344\271\210\357\274\237.md"	
+++ /dev/null
@@ -1,78 +0,0 @@
-IoC和AOP生而就是为了解耦和扩展。
-
-# 什么是 IoC?
-
-一种设计思想，将设计好的对象交给Spring容器控制，而非直接在对象内部控制。
-
-> 为啥要让容器来管理对象呢？你这程序员咋就知道甩锅呢？
-![](https://img-blog.csdnimg.cn/20210512150222541.png)
-
-拥有初级趣味的码农，可能只是觉着使用IoC方便，就是个用来解耦的，但这还远非容器的益处。
-利用容器管理所有的框架、业务对象，我们可以做到：
-- 无侵入调整对象的关系
-- 无侵入地随时调整对象的属性
-- 实现对象的替换
-
-这使得框架开发者在后续实现一些扩展就很容易。
-
-# 什么是AOP？
-
-AOP实现了高内聚、低耦合，在切面集中实现横切关注点（缓存、权限、日志等），然后通过切点配置把代码注入到合适的位置。
-
-# 基本概念
-- **连接点（Join point）**
-就是方法执行
-- **切点（Pointcut）**
-Spring AOP默认使用AspectJ查询表达式，通过在连接点运行查询表达式来匹配切点
-- **增强（Advice）**
-也叫作通知，定义了切入切点后增强的方式，包括前、后、环绕等。Spring AOP中，把增强定义为拦截器
-- **切面（Aspect）**
-切面=切点+增强
-
-### Declaring Advice
-有如下三类增强：
-#### @Before
-#### @After
-#### @Around
-**环绕通知**运行 **around** 匹配方法的执行。它有机会在方法运行之前和之后都工作，并确定方法实际运行何时、如何甚至是否执行。若你需要以线程安全的方式（例如启动和停止计时器）在方法执行前后共享状态，则通常会使用Around advice。
-
-使用案例：
-```java
-import org.aspectj.lang.annotation.Aspect;
-import org.aspectj.lang.annotation.Around;
-import org.aspectj.lang.ProceedingJoinPoint;
-
-@Aspect
-public class AroundExample {
-
-    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
-    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
-        // start stopwatch
-        Object retVal = pjp.proceed();
-        // stop stopwatch
-        return retVal;
-    }
-}
-```
-around advice 返回的值就是方法调用者看到的返回值。例如，一个简单的缓存aspect可以返回一个值从缓存（如果它有）或调用`procedd`如果它没有。请注意，可以多次调用`procedd`，或者根本不在around advice的主体内调用，这都是合法的。
-
-推荐始终使用最不强大的advice形式，以满足需求。
-
-使用 **@Around** 注解声明环绕通知时，第一个参数必须是ProceedingJoinPoint类型。
-在通知的方法体中，调用 `proceed()` 会导致基础方法运行。 `proceed()` 也可以在Object[]中传递。数组中的值在进行时用作方法执行的参数。
-#### Advice参数
-Spring 提供全种类的通知，这意味着你在通知的方法签名中声明所需参数，而非和`Object[]`协作。
-如何编写通用的通知，以便了解通知方法当前在通知啥玩意。
-#### Access to the Current JoinPoint
-任何通知方法都可能声明类型`org.aspectj.lang.JoinPoint` 的参数（请注意，围绕建议需要申报类型'继续JoinPoint'的第一参数，该参数是 JoinPoint 的子类。
-JoinPoint 接口提供了许多有用的方法：
-- getArgs()
-返回方法的参数
-- getThis()
-返回代理对象
-- getTarget()
-返回目标对象
-- getSignature()
-Returns a description of the method that is being advised.
-- toString()
-Prints a useful description of the method being advised.
\ No newline at end of file
diff --git "a/Spring/SpringFramework/Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257.md" "b/Spring/SpringFramework/Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257.md"
deleted file mode 100644
index 7afc37ce21..0000000000
--- "a/Spring/SpringFramework/Spring Bean \344\276\235\350\265\226\346\263\250\345\205\245\345\270\270\350\247\201\351\224\231\350\257\257.md"	
+++ /dev/null
@@ -1,103 +0,0 @@
-有时我们会使用@Value自动注入，同时也存在注入到集合、数组等复杂类型的场景。这都是方便写 bug 的场景。
-
-# 1 @Value未注入预期值
-在字段或方法/构造函数参数级别使用，指示带注释元素的默认值表达式。
-通常用于表达式驱动或属性驱动的依赖注入。 还支持处理程序方法参数的动态解析
-例如，在 Spring MVC 中，一个常见的用例是使用#{systemProperties.myProp} systemProperties.myProp #{systemProperties.myProp}样式的 SpEL（Spring 表达式语言）表达式注入值。 
-或可使用${my.app.myProp}样式属性占位符注入值。
-
-**@Value**实际处理由BeanPostProcessor执行，这意味着不能在BeanPostProcessor或BeanFactoryPostProcessor类型中使用 **@Value**。
-## V.S Autowired
-在装配对象成员属性时，常使用@Autowired来装配。但也使用@Value进行装配：
-- 使用@Autowired一般都不会设置属性值
-- @Value必须指定一个字符串值，因其定义做了要求：
-![](https://img-blog.csdnimg.cn/3182d920dc254a90a519f4691a4f2b6c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-一般都会因 **@Value** 常用于String类型的装配，误以为其不能用于非内置对象的装配。
-
-可用如下方式注入一个属性成员：
-![](https://img-blog.csdnimg.cn/5404d551f42f4937bbc915911e007e58.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-使用 **@Value**更多是用来装配String，而且支持多种强大的装配方式
-![](https://img-blog.csdnimg.cn/84ffc52fd3db4f89b14ca60c2beebccb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_16,color_FFFFFF,t_70,g_se,x_16)
-application.properties配置了这样一个属性：
-```java
-user=admin
-password=pass
-```
-
-然后我们在一个Bean中，分别定义两个属性来引用它们：
-![](https://img-blog.csdnimg.cn/12b3b5bc840f4133b41d71ac8af8e2da.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-password返回了配置值，但user却不是配置文件的指定值，而是PC用户名。
-## 答疑
-有一个正确的，说明 **@Value**使用姿势没问题，但user为啥不正确？
-这就得精通Spring到底如何根据 **@Value**查询值。
-### @Value的核心工作流程
-#### DefaultListableBeanFactory#doResolveDependency
-```java
-@Nullable
-public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
-      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
-    // ...
-    Class<?> type = descriptor.getDependencyType();
-      // 寻找@Value
-      Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
-      if (value != null) {
-         if (value instanceof String) {
-            // 解析Value值
-            String strVal = resolveEmbeddedValue((String) value);
-            BeanDefinition bd = (beanName != null && containsBean(beanName) ?
-                  getMergedBeanDefinition(beanName) : null);
-            value = evaluateBeanDefinitionString(strVal, bd);
-         }
-         
-         // 转化Value解析的结果到装配的类型
-         TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
-         try {
-            return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
-         }
-         catch (UnsupportedOperationException ex) {}
-      }
-    // ...
-  }
-```
-**@Value** 的工作大体分为以下三个核心步骤。
-#### 1 寻找@Value
-判断这个属性字段是否标记为@Value：
-##### QualifierAnnotationAutowireCandidateResolver#findValue
-- valueAnnotationType就是 **@Value**
-![](https://img-blog.csdnimg.cn/8588a95243e1400d82181d711b97b1ef.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/46f640ee047c4f3d8d85c085770e64c9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-#### 2 解析@Value的字符串值
-若一个字段标记了 **@Value**，则可拿到对应字符串值，然后根据字符串值解析，最终解析的结果可能是一个字符串or对象，取决于字符串怎么写。
-
-#### 3 将解析结果转化为待装配的对象的类型
-当拿到上一步生成的结果后，我们会发现可能和我们要装配的类型不匹配。
-比如定义的是UUID，而结果是个字符串，此时就会根据目标类型来寻找转化器执行转化：
-![](https://img-blog.csdnimg.cn/18ed8af280474e6887aa5da59ef53c9d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-分析可得问题关键在第二步，执行过程：
-![](https://img-blog.csdnimg.cn/728d4ba5c3154e2eb952d32609d53995.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-这里是在解析嵌入的值，替换掉占位符。使用PropertySourcesPlaceholderConfigurer根据PropertySources替换。
-
-当使用 `${user}` 获取替换值时，最终执行的查找并非只在application.property文件。
-可以发现如下“源”都是替换的依据：
-![](https://img-blog.csdnimg.cn/3b8888a22217457b8e04c0da90afe8e8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-而具体的查找执行，通过
-##### PropertySourcesPropertyResolver#getProperty
-获取执行方式
-![](https://img-blog.csdnimg.cn/ed8efa9caf61487795c573a75b13d67b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-在解析Value字符串有顺序，源都存在CopyOnWriteArrayList，启动时就被按序固定下来了，一个一个“源”顺序查找，在其中一源找到后，就直接返回。
-
-查看systemEnvironment源，发现刚好有个user和自定义的重合，且值不是admin。
-![](https://img-blog.csdnimg.cn/24b383c3c8a84d7a8edf3f98dfd42e78.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-所以这真是冤家路窄了，刚好系统环境变量（systemEnvironment）含同名配置。若没有意识到它们的存在，起了同名字符串作为 **@Value**，就容易引发这类问题。
-## 修正
-避免使用同一个名称，具体修改如下：
-```java
-user.name=admin
-user.password=pass
-```
-其实还是不行。
-在systemProperties这个PropertiesPropertySource源中刚好存在user.name，真是无巧不成书。所以命名时，我们一定要注意不仅要避免和环境变量冲突，也要注意避免和系统变量等其他变量冲突，才能从根本解决该问题。
-
-Spring给我们提供了很多好用的功能，但是这些功能交织到一起后，就有可能让我们误入一些坑，只有了解它的运行方式，我们才能迅速定位问题、解决问题。
\ No newline at end of file
diff --git "a/Spring/SpringFramework/Spring Bean\344\275\234\347\224\250\345\237\237\347\256\241\347\220\206.md" "b/Spring/SpringFramework/Spring Bean\344\275\234\347\224\250\345\237\237\347\256\241\347\220\206.md"
deleted file mode 100644
index e69de29bb2..0000000000
diff --git "a/Spring/SpringFramework/Spring Bean\344\276\235\350\265\226.md" "b/Spring/SpringFramework/Spring Bean\344\276\235\350\265\226.md"
index de934c3a65..6843c86f7e 100644
--- "a/Spring/SpringFramework/Spring Bean\344\276\235\350\265\226.md"	
+++ "b/Spring/SpringFramework/Spring Bean\344\276\235\350\265\226.md"	
@@ -104,4 +104,6 @@ Setter注入主要应仅用于可以在类中分配合理的默认值的可选
 不过需要注意的是，当lazy-init bean是未lazy-init的单例bean的依赖时，`ApplicationContext`在启动阶段还是会创建lazy-init bean，因为它必须要满足单例的依赖关系，lazy-init bean会被注入到其它未lazy-init 的单例bean中。
 
 另外如果需要，可通过`<bean/>`标签内的 `default-lazy-init` 属性控制容器级别的延迟初始化，案例如下：
-![](https://img-blog.csdnimg.cn/20200911005914816.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
+![](https://img-blog.csdnimg.cn/20200911005914816.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/Spring/SpringFramework/Spring Bean\345\237\272\347\241\200.md" "b/Spring/SpringFramework/Spring Bean\345\237\272\347\241\200.md"
index b946fba568..9b5d16377c 100644
--- "a/Spring/SpringFramework/Spring Bean\345\237\272\347\241\200.md"	
+++ "b/Spring/SpringFramework/Spring Bean\345\237\272\347\241\200.md"	
@@ -186,3 +186,5 @@ bean元数据定义中的指定类只是初始类引用，可能结合使用的
 该方法可确定给定名称bean的类型。 更确切地，返回针对相同bean名称的`BeanFactory.getBean`调用将返回的对象的类型。
 且该方法的实现考虑了前面穷举的所有情况，并针对于FactoryBean ，返回FactoryBean所创建的对象类型，和`FactoryBean.getObjectType()`返回一致。 
 ![](https://img-blog.csdnimg.cn/20200828121006639.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
diff --git "a/Spring/SpringFramework/Spring Exception\344\271\213\345\260\217\345\277\203\350\277\207\346\273\244\345\231\250\345\274\202\345\270\270.md" "b/Spring/SpringFramework/Spring Exception\344\271\213\345\260\217\345\277\203\350\277\207\346\273\244\345\231\250\345\274\202\345\270\270.md"
deleted file mode 100644
index fe83f65d7b..0000000000
--- "a/Spring/SpringFramework/Spring Exception\344\271\213\345\260\217\345\277\203\350\277\207\346\273\244\345\231\250\345\274\202\345\270\270.md"	
+++ /dev/null
@@ -1,67 +0,0 @@
-# 错误场景![](https://img-blog.csdnimg.cn/e2023dfa73674c659fe29797527f1a26.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-验证请求的Token合法性的Filter。Token校验失败时，直接抛自定义异常，移交给Spring处理：
-![](https://img-blog.csdnimg.cn/122fe2d581fc4684a2ad6deb491987e7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/016ce84b7da3488fa65148e1e9a27ee7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/87e550844437408f88f254387b0b2cd6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-测试HTTP请求：
-![](https://img-blog.csdnimg.cn/18d36bd55d484550a4ad5bbee58fa49c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-日志输出如下：说明**IllegalRequestExceptionHandler**未生效。
-![](https://img-blog.csdnimg.cn/81a7b9c33b7b48b5bc4353d11f6a4089.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-why？
-这就需要精通Spring异常处理流程了。
-# 解析
-![](https://img-blog.csdnimg.cn/2c00379066d1490891b909c4013d1fdf.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-当所有Filter被执行完毕，Spring才会处理Servlet相关，而**DispatcherServlet**才是整个Servlet处理核心，它是前端控制器设计模式，提供 Spring Web MVC 的集中访问点并负责职责的分派。
-
-在这，Spring处理了请求和处理器的对应关系及**统一异常处理**。
-
-Filter内异常无法被统一处理，就是因为异常处理发生在 ***DispatcherServlet#doDispatch()***
-![](https://img-blog.csdnimg.cn/13642402d76c4f2fbadf4da28d7239c4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-但此时，**过滤器已全部执行完**。
-# Spring异常统一处理
-## ControllerAdvice如何被Spring加载并对外暴露？
-#### WebMvcConfigurationSupport#handlerExceptionResolver()
-实例化并注册一个ExceptionHandlerExceptionResolver 的实例
-![](https://img-blog.csdnimg.cn/32e6572a6cfd4d93bacc992871875f24.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-最终按下图调用栈，Spring 实例化了ExceptionHandlerExceptionResolver类。
-![](https://img-blog.csdnimg.cn/2055644a405542698006d57e0fba1f50.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-ExceptionHandlerExceptionResolver实现了**InitializingBean**
-![](https://img-blog.csdnimg.cn/916c98b8d2dc4144b3ba690d27f19091.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-重写 ***afterPropertiesSet()*** ![](https://img-blog.csdnimg.cn/09a7b4e7ca2b48ef811b35f3c90089f1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-#### initExceptionHandlerAdviceCache
-完成所有 ControllerAdvice 中的ExceptionHandler 初始化：查找所有 **@ControllerAdvice** 注解的 Bean，把它们放入exceptionHandlerAdviceCache。
-这里即指自定义的IllegalRequestExceptionHandler
-![](https://img-blog.csdnimg.cn/c1102552aa0a4d478d2ed537ba72cf61.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/184207f02e31465ba45de946e5a1b73c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-所有被 **@ControllerAdvice** 注解的异常处理器，都会在 **ExceptionHandlerExceptionResolver** 实例化时自动扫描并装载在其exceptionHandlerAdviceCache。
-#### initHandlerExceptionResolvers
-当第一次请求发生时，***DispatcherServlet#initHandlerExceptionResolvers()*** 将获取所有注册到 Spring 的 HandlerExceptionResolver 实例（ExceptionHandlerExceptionResolver正是），存到**handlerExceptionResolvers**
-![](https://img-blog.csdnimg.cn/9560e3a409254a82af3b655a12fe24cb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/be51ad0247c24552837631caf23b2004.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-## ControllerAdvice如何被Spring消费并处理异常？
-### DispatcherServlet
-#### doDispatch()
-![](https://img-blog.csdnimg.cn/90c9d7b1254b49da8f865323eeb5e42a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-执行用户请求时，当查找、执行请求对应的 handler 过程中异常时：
-1. 会把异常值赋给 dispatchException
-2. 再移交 processDispatchResult()
-#### processDispatchResult
-![](https://img-blog.csdnimg.cn/287b3c8dae8e48c4a5ec8dccc7143dad.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-当Exception非空时，继续移交
-#### processHandlerException
-![](https://img-blog.csdnimg.cn/6b18557adc6844adaaac2562955ef9ef.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-从 handlerExceptionResolvers 获取有效的异常解析器以解析异常。
-
-这里的 handlerExceptionResolvers 一定包含声明的IllegalRequestExceptionHandler#IllegalRequestException 的异常处理器的 ExceptionHandlerExceptionResolver 包装类。
-# 修正
-为利用到 Spring MVC 的异常处理机制，改造Filter：
-- 手动捕获异常
-- 将异常通过  HandlerExceptionResolver 进行解析处理
-
-据此，修改 PermissionFilter，注入 HandlerExceptionResolver：
-![](https://img-blog.csdnimg.cn/1b50cf3672214f418f908fd8f736a120.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-然后，在 doFilter 捕获异常并移交 HandlerExceptionResolver：
-![](https://img-blog.csdnimg.cn/b83601b115024554854786652e2a2f92.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-现在再用错误 Token 请求，日志输出如下：
-![](https://img-blog.csdnimg.cn/0f7cb17d0ccb4b3caf84ff6c78227656.png) 响应体：
-![](https://img-blog.csdnimg.cn/e69723db189b4eca9231dc06fe3c7059.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
\ No newline at end of file
diff --git "a/Spring/SpringFramework/SpringHTTP\347\212\266\346\200\201\347\240\201.md" "b/Spring/SpringFramework/Spring--Http\346\272\220\347\240\201.md"
similarity index 99%
rename from "Spring/SpringFramework/SpringHTTP\347\212\266\346\200\201\347\240\201.md"
rename to "Spring/SpringFramework/Spring--Http\346\272\220\347\240\201.md"
index 0a1d6c6471..ac65c21c7e 100644
--- "a/Spring/SpringFramework/SpringHTTP\347\212\266\346\200\201\347\240\201.md"
+++ "b/Spring/SpringFramework/Spring--Http\346\272\220\347\240\201.md"
@@ -1,4 +1,3 @@
-```java
 /*
  * Copyright 2002-2017 the original author or authors.
  *
@@ -540,4 +539,3 @@ public enum HttpStatus {
 	}
 
 }
-```
\ No newline at end of file
diff --git "a/Spring/SpringFramework/Spring\345\237\272\344\272\216\346\263\250\350\247\243\347\232\204\345\256\271\345\231\250\351\205\215\347\275\256.md" "b/Spring/SpringFramework/Spring-5-0\344\270\255\346\226\207\347\211\210-3-9.md"
similarity index 97%
rename from "Spring/SpringFramework/Spring\345\237\272\344\272\216\346\263\250\350\247\243\347\232\204\345\256\271\345\231\250\351\205\215\347\275\256.md"
rename to "Spring/SpringFramework/Spring-5-0\344\270\255\346\226\207\347\211\210-3-9.md"
index 4fed3adf96..7d17d999e3 100644
--- "a/Spring/SpringFramework/Spring\345\237\272\344\272\216\346\263\250\350\247\243\347\232\204\345\256\271\345\231\250\351\205\215\347\275\256.md"
+++ "b/Spring/SpringFramework/Spring-5-0\344\270\255\346\226\207\347\211\210-3-9.md"
@@ -1,3 +1,5 @@
+### 3.9 基于注解的容器配置
+
 > **在配置Spring时注解是否比XML更好？**
 > 
 > 基于注解配置的引入引出了一个问题——这种方式是否比基于XML的配置更好。简短的回答是视情况而定。长一点的回答是每种方法都有它的优点和缺点，通常是由开发者决定哪一种策略更适合他们。由于注解的定义方式，注解在它们的声明中提供了许多上下文，导致配置更简短更简洁。然而，XML擅长连接组件而不必接触源代码或重新编译它们。一些开发者更喜欢接近源代码，而另一些人则认为基于注解的类不再是POJOs，此外，配置变的去中心化，而且更难控制。
@@ -10,7 +12,8 @@
 > 注解注入在XML注入之前进行，因此对于通过两种方法进行组装的属性后者的配置会覆盖前者。
 
 跟以前一样，你可以作为单独的bean定义来注册它们，但也可以通过在一个基于XML的Spring配置（注入包含上下文命名空间）中包含下面的标签来隐式的注册它们：
-```xml
+
+```
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -33,7 +36,7 @@
 
 `@Required`注解应用到bean属性的setter方法上，例子如下：
 
-```java
+```
 public class SimpleMovieLister {
 
     private MovieFinder movieFinder;
@@ -50,13 +53,13 @@ public class SimpleMovieLister {
 
 这个注解仅仅是表明受影响的bean属性必须在配置时通过显式的bean定义或自动组装填充。如果受影响的bean属性没有填充，容器会抛出一个异常，这允许及早明确的失败，避免`NullPointerExceptions`或后面出现类似的情况。仍然建议你在bean类本身加入断言，例如，加入到初始化方法中。这样做可以强制这些需要的引用和值，甚至是你在容器外部使用这个类的时候。
 
-# 2 @Autowired
+#### 3.9.2 @Autowired
 
 > 在下面的例子中JSR 330的`@Inject`注解可以用来代替Spring的`@Autowired`注解。
 
 你可以将`@Autowired`注解应用到构造函数上。
 
-```java
+```
 public class MovieRecommender {
 
     private final CustomerPreferenceDao customerPreferenceDao;
@@ -75,7 +78,7 @@ public class MovieRecommender {
 
 正如预料的那样，你也可以将`@Autowired`注解应用到“传统的”setter方法上：
 
-```java
+```
 public class SimpleMovieLister {
 
     private MovieFinder movieFinder;
@@ -92,7 +95,7 @@ public class SimpleMovieLister {
 
 你也可以应用注解到具有任何名字和/或多个参数的方法上：
 
-```java
+```
 public class MovieRecommender {
 
     private MovieCatalog movieCatalog;
@@ -113,7 +116,7 @@ public class MovieRecommender {
 
 你也可以应用`@Autowired`到字段上，甚至可以与构造函数混合用：
 
-```java
+```
 public class MovieRecommender {
 
     private final CustomerPreferenceDao customerPreferenceDao;
@@ -133,7 +136,7 @@ public class MovieRecommender {
 
 通过给带有数组的字段或方法添加`@Autowired`注解，也可以从`ApplicationContext`中提供一组特定类型的bean：
 
-```java
+```
 public class MovieRecommender {
 
     @Autowired
@@ -146,7 +149,7 @@ public class MovieRecommender {
 
 同样也可以应用到具有同一类型的集合上：
 
-```java
+```
 public class MovieRecommender {
 
     private Set<MovieCatalog> movieCatalogs;
@@ -165,7 +168,7 @@ public class MovieRecommender {
 
 只要期望的key是`String`，那么类型化的Maps就可以自动组装。Map的值将包含所有期望类型的beans，key将包含对应的bean名字：
 
-```java
+```
 public class MovieRecommender {
 
     private Map<String, MovieCatalog> movieCatalogs;
@@ -182,7 +185,7 @@ public class MovieRecommender {
 
 默认情况下，当没有候选beans可获得时，自动组装会失败；默认的行为是将注解的方法，构造函数和字段看作指明了需要的依赖。这个行为也可以通过下面的方式去改变。
 
-```java
+```
 public class SimpleMovieLister {
 
     private MovieFinder movieFinder;
@@ -202,7 +205,8 @@ public class SimpleMovieLister {
 > 建议在`@Required`注解之上使用`@Autowired`的`required`特性。`required`特性表明这个属性自动装配是不需要的，如果这个属性不能被自动装配，它会被忽略。另一方面`@Required`是更强大的，在它强制这个属性被任何容器支持的bean设置。如果没有值注入，会抛出对应的异常。
 
 你也可以对那些已知的具有可解析依赖的接口使用`@Autowired`：`BeanFactory`，`ApplicationContext`，`Environment`, `ResourceLoader`，`ApplicationEventPublisher`和`MessageSource`。这些接口和它们的扩展接口，例如`ConfigurableApplicationContext`或`ResourcePatternResolver`，可以自动解析，不需要特别的设置。
-```java
+
+```
 public class MovieRecommender {
 
     @Autowired
@@ -215,16 +219,16 @@ public class MovieRecommender {
 
 }
 ```
-`@Autowired`、`@Inject`、`@Resource`和`@Value`都是通过Spring的`BeanPostProcessor`实现处理的，这反过来意味着，你不能在自己的`BeanPostProcessor`或`BeanFactoryPostProcessor`中使用这些注解。
-这些类型必须显式通过XML或使用Spring的`@Bean`方法来装配。
 
-# 3 用@Primary微调基于注解的自动装配
+> `@Autowired`，`@Inject`，`@Resource`和`@Value`注解是通过Spring `BeanPostProcessor`实现处理，这反过来意味着你不能在你自己的`BeanPostProcessor`或`BeanFactoryPostProcessor`中应用这些注解（如果有的话）。这些类型必须显式的通过XML或使用Spring的`@Bean`方法来’wired up’。
+
+#### 3.9.3 用@Primary微调基于注解的自动装配
 
 因为根据类型的自动装配可能会导致多个候选目标，所以在选择过程中进行更多的控制经常是有必要的。一种方式通过Spring的`@Primary`注解来完成。当有个多个候选bean要组装到一个单值的依赖时，`@Primary`表明指定的bean应该具有更高的优先级。如果确定一个’primary’ bean位于候选目标中间，它将是那个自动装配的值。
 
 假设我们具有如下配置，将`firstMovieCatalog`定义为主要的`MovieCatalog`。
 
-```java
+```
 @Configuration
 public class MovieConfiguration {
 
@@ -242,7 +246,7 @@ public class MovieConfiguration {
 
 根据这样的配置，下面的`MovieRecommender`将用`firstMovieCatalog`进行自动装配。
 
-```java
+```
 public class MovieRecommender {
 
     @Autowired
@@ -255,7 +259,7 @@ public class MovieRecommender {
 
 对应的bean定义如下：
 
-```xml
+```
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -284,7 +288,7 @@ public class MovieRecommender {
 
 当有多个实例需要确定一个主要的候选对象时，`@Primary`是一种按类型自动装配的有效方式。当需要在选择过程中进行更多的控制时，可以使用Spring的`@Qualifier`注解。为了给每个选择一个特定的bean，你可以将限定符的值与特定的参数联系在一起，减少类型匹配集合。在最简单的情况下，这是一个纯描述性值:
 
-```java
+```
 public class MovieRecommender {
 
     @Autowired
@@ -298,7 +302,7 @@ public class MovieRecommender {
 
 `@Qualifier`注解也可以指定单个构造函数参数或方法参数：
 
-```java
+```
 public class MovieRecommender {
 
     private MovieCatalog movieCatalog;
@@ -319,7 +323,7 @@ public class MovieRecommender {
 
 对应的bean定义如下。限定符值为”main”的bean被组装到有相同值的构造函数参数中。
 
-```xml
+```
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -362,7 +366,7 @@ public class MovieRecommender {
 
 你可以创建自己的定制限定符注解。简单定义一个注解，在你自己的定义中提供`@Qualifier`注解：
 
-```java
+```
 @Target({ElementType.FIELD, ElementType.PARAMETER})
 @Retention(RetentionPolicy.RUNTIME)
 @Qualifier
@@ -374,7 +378,7 @@ public @interface Genre {
 
 然后你可以在自动装配的字段和参数上提供定制的限定符：
 
-```java
+```
 public class MovieRecommender {
 
     @Autowired
@@ -394,7 +398,7 @@ public class MovieRecommender {
 
 接下来，提供候选bean定义的信息。你可以添加`<qualifier/>`标记作为`<bean/>`标记的子元素，然后指定匹配你的定制限定符注解的类型和值。类型用来匹配注解的全限定类名称。或者，如果没有名称冲突的风险，为了方便，你可以使用简写的类名称。下面的例子证实了这些方法。
 
-```xml
+```
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -425,7 +429,7 @@ public class MovieRecommender {
 
 在某些情况下，使用没有值的注解就是足够的。当注解为了通用的目的时，这是非常有用的，可以应用到跨几个不同类型的依赖上。例如，当网络不可用时，你可以提供一个要搜索的离线目录。首先定义一个简单的注解：
 
-```java
+```
 @Target({ElementType.FIELD, ElementType.PARAMETER})
 @Retention(RetentionPolicy.RUNTIME)
 @Qualifier
@@ -436,7 +440,7 @@ public @interface Offline {
 
 然后将注解添加到要自动装配的字段或属性上：
 
-```java
+```
 public class MovieRecommender {
 
     @Autowired
@@ -450,7 +454,7 @@ public class MovieRecommender {
 
 现在bean定义只需要一个限定符类型：
 
-```xml
+```
 <bean class="example.SimpleMovieCatalog">
     <qualifier type="Offline"/>
     <!-- inject any dependencies required by this bean -->
@@ -459,7 +463,7 @@ public class MovieRecommender {
 
 你也可以定义接收命名属性之外的定制限定符注解或代替简单的值属性。如果要注入的字段或参数指定了多个属性值，bean定义必须匹配所有的属性值才会被认为是一个可自动装配的候选目标。作为一个例子，考虑下面的注解定义：
 
-```java
+```
 @Target({ElementType.FIELD, ElementType.PARAMETER})
 @Retention(RetentionPolicy.RUNTIME)
 @Qualifier
@@ -474,7 +478,7 @@ public @interface MovieQualifier {
 
 这种情况下`Format`是枚举类型：
 
-```java
+```
 public enum Format {
     VHS, DVD, BLURAY
 }
@@ -482,7 +486,7 @@ public enum Format {
 
 要自动装配的字段使用定制限定符进行注解，并且包含了两个属性值：`genre`和`format`。
 
-```java
+```
 public class MovieRecommender {
 
     @Autowired
@@ -508,7 +512,7 @@ public class MovieRecommender {
 
 最后，bean定义应该包含匹配的限定符值。这个例子也证实了bean元属性可以用来代替`<qualifier/>`子元素。如果可获得`<qualifier/>`，它和它的属性优先级更高，如果当前没有限定符，自动装配机制会将`<meta/>`内的值作为备用，正如下面的例子中的最后两个bean定义。
 
-```xml
+```
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -551,11 +555,11 @@ public class MovieRecommender {
 </beans>
 ```
 
-# 5 使用泛型作为自动装配限定符
+#### 3.9.5 使用泛型作为自动装配限定符
 
 除了`@Qualifier`注解外，也可以使用Java的泛型类型作为限定符的一种暗示方式。例如，假设你有如下配置：
 
-```java
+```
 @Configuration
 public class MyConfiguration {
 
@@ -574,7 +578,7 @@ public class MyConfiguration {
 
 假设上面的beans实现了一个泛型接口，例如，`Store<String>`和`Store<Integer>`，你可以`@Autowire` `Store`接口，泛型将作为限定符使用：
 
-```java
+```
 @Autowired
 private Store<String> s1; // <String> qualifier, injects the stringStore bean
 
@@ -584,15 +588,18 @@ private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
 
 当自动装配`Lists`，`Maps`和`Arrays`时，也会应用泛型限定符：
 
-```java
+```
 // Inject all Store beans as long as they have an <Integer> generic
 // Store<String> beans will not appear in this list
 @Autowired
 private List<Store<Integer>> s;
 ```
-### 3.9.6 CustomAutowireConfigurer
-`CustomAutowireConfigurer`是一个能使你注册自己的定制限定符注解类型的`BeanFactoryPostProcessor`，即使它们不使用Spring的`@Qualifier`
-```xml
+
+#### 3.9.6 CustomAutowireConfigurer
+
+`CustomAutowireConfigurer`是一个能使你注册自己的定制限定符注解类型的`BeanFactoryPostProcessor`，即使它们不使用Spring的`@Qualifier`注解进行注解。
+
+```
 <bean id="customAutowireConfigurer"
         class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
     <property name="customQualifierTypes">
@@ -613,13 +620,13 @@ private List<Store<Integer>> s;
 
 当多个beans符合条件成为自动装配的候选目标时，”primary” bean的决定如下：如果在候选目标中某个确定的bean中的`primary`特性被设为`true`，它将被选为目标bean。
 
-# 7 @Resource
+#### 3.9.7 @Resource
 
 Spring也支持使用JSR-250 `@Resource`对字段或bean属性setter方法进行注入。这是在Java EE 5和6中的一种通用模式，例如在JSF 1.2管理的beans或JAX-WS 2.0的端点。Spring对它管理的对象也支持这种模式。
 
 `@Resource`采用名字属性，默认情况下Spring将名字值作为要注入的bean的名字。换句话说，它遵循*by-name*语义，下面的例子证实了这一点：
 
-```java
+```
 public class SimpleMovieLister {
 
     private MovieFinder movieFinder;
@@ -634,7 +641,7 @@ public class SimpleMovieLister {
 
 如果没有显式的指定名字，默认名字从字段名或setter方法中取得。在字段情况下，它采用字段名称；在setter方法情况下，它采用bean的属性名。因此下面的例子将名字为`movieFinder`的bean注入到它的setter方法中：
 
-```java
+```
 public class SimpleMovieLister {
 
     private MovieFinder movieFinder;
@@ -670,10 +677,10 @@ public class MovieRecommender {
 }
 ```
 
-# 8 @PostConstruct和@PreDestroy
+#### 3.9.8 @PostConstruct和@PreDestroy
 `CommonAnnotationBeanPostProcessor`不仅识别`@Resource`注解，而且识别JSR-250生命周期注解。在Spring 2.5引入了对这些注解的支持，也提供了在初始化回调函数和销毁回调函数中描述的那些注解的一种可替代方式。假设`CommonAnnotationBeanPostProcessor`在Spring的`ApplicationContext`中注册，执行这些注解的方法在生命周期的同一点被调用，作为对应的Spring生命周期接口方法或显式声明的回调方法。在下面的例子中，缓存会预先放置接近初始化之前，并在销毁之前清除。
 
-```java
+```
 public class CachingMovieLister {
 
     @PostConstruct
diff --git "a/Spring/SpringFramework/Spring-Bean\347\232\204\344\275\234\347\224\250\345\237\237\347\256\241\347\220\206.md" "b/Spring/SpringFramework/Spring-Bean\347\232\204\344\275\234\347\224\250\345\237\237\347\256\241\347\220\206.md"
new file mode 100644
index 0000000000..b71896babf
--- /dev/null
+++ "b/Spring/SpringFramework/Spring-Bean\347\232\204\344\275\234\347\224\250\345\237\237\347\256\241\347\220\206.md"
@@ -0,0 +1,263 @@
+# 前言
+
+在 Spring 中，那些组成应用程序的主体及由 Spring IOC 容器所管理的对象，被称之为 bean。
+简单地讲，bean 就是由 IOC 容器初始化、装配及管理的对象，除此之外，bean 就与应用程序中的其他对象没有什么区别了。
+而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。
+
+**Spring中的bean默认都是单例的，这些单例Bean在多线程程序下如何保证线程安全呢？**
+
+例如对于Web应用来说，Web容器对于每个用户请求都创建一个单独的Sevlet线程来处理请求，引入Spring框架之后，每个Action都是单例的，那么对于Spring托管的单例Service Bean，如何保证其安全呢？ 
+
+- Spring的单例是基于BeanFactory也就是Spring容器的，单例Bean在此容器内只有一个
+- Java的单例是基于 JVM，每个 JVM 内只有一个实例
+
+# 1 bean的作用域
+
+创建一个bean定义，其实质是用该bean定义对应的类来创建真正实例的“配方”。
+把bean定义看成一个配方很有意义，它与class很类似，只根据一张“处方”就可以创建多个实例。
+不仅可以控制注入到对象中的各种依赖和配置值，还可以控制该对象的作用域。
+这样可以灵活选择所建对象的作用域，而不必在Java Class级定义作用域。
+
+- Spring Framework支持五种作用域，分别阐述如下表。
+![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/1188352.jpg)
+
+五种作用域中，**request、session** 和 **global session** 三种作用域仅在基于web的应用中使用（不必关心你所采用的是什么web应用框架），只能用在基于 web 的 Spring ApplicationContext 环境。
+
+# 2 singleton —— 唯一 bean 实例
+
+**当一个 bean 的作用域为 singleton，那么Spring IoC容器中只会存在一个共享的 bean 实例，并且所有对 bean 的请求，只要 id 与该 bean 定义相匹配，则只会返回bean的同一实例。** 
+singleton 是单例类型(对应于单例模式)，就是在创建起容器时就同时自动创建了一个bean的对象，不管你是否使用，但我们可以指定Bean节点的 `lazy-init=”true”` 来延迟初始化bean，这时候，只有在第一次获取bean时才会初始化bean，即第一次请求该bean时才初始化。 
+每次获取到的对象都是同一个对象。注意，singleton 作用域是Spring中的缺省作用域。要在XML中将 bean 定义成 singleton ，可以这样配置：
+
+```xml
+<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
+```
+
+也可以通过 `@Scope` 注解（它可以显示指定bean的作用范围。）的方式
+
+```java
+@Service
+@Scope("singleton")
+public class ServiceImpl{
+
+}
+```
+
+# 3 prototype——每次请求都会创建一个新的 bean 实例
+
+**当一个bean的作用域为 prototype，表示一个 bean 定义对应多个对象实例。**
+
+**prototype 作用域的 bean 会导致在每次对该 bean 请求**（将其注入到另一个 bean 中，或者以程序的方式调用容器的 getBean() 方法）时都会创建一个新的 bean 实例。prototype 是原型类型，它在我们创建容器的时候并没有实例化，而是当我们获取bean的时候才会去创建一个对象，而且我们每次获取到的对象都不是同一个对象。
+
+根据经验，对有状态的 bean 应该使用 prototype 作用域
+而对无状态的 bean 则应该使用 singleton 作用域。  
+在 XML 中将 bean 定义成 prototype ，可以这样配置：
+
+```java
+<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/>  
+ 或者
+<bean id="account" class="com.foo.DefaultAccount" singleton="false"/> 
+```
+通过 `@Scope` 注解的方式实现就不做演示了。
+
+# 4 request——每一次HTTP请求都会产生一个新的bean
+
+该bean仅在当前HTTP request内有效
+
+**request只适用于Web程序，每一次 HTTP 请求都会产生一个新的bean，同时该bean仅在当前HTTP request内有效，当请求结束后，该对象的生命周期即告结束。** 
+在 XML 中将 bean 定义成 request ，可以这样配置：
+
+```java
+<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
+```
+
+# 5 session——每一次HTTP请求都会产生一个新的 bean
+
+该bean仅在当前 HTTP session 内有效
+
+**session只适用于Web程序，session 作用域表示该针对每一次 HTTP 请求都会产生一个新的 bean，同时该 bean 仅在当前 HTTP session 内有效.与request作用域一样，可以根据需要放心的更改所创建实例的内部状态，而别的 HTTP session 中根据 userPreferences 创建的实例，将不会看到这些特定于某个 HTTP session 的状态变化。当HTTP session最终被废弃的时候，在该HTTP session作用域内的bean也会被废弃掉。**
+
+```xml
+<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
+```
+
+# 6 globalSession
+
+global session 作用域类似于标准的 HTTP session 作用域，不过仅仅在基于 portlet 的 web 应用中才有意义。Portlet 规范定义了全局 Session 的概念，它被所有构成某个 portlet web 应用的各种不同的 portle t所共享。在global session 作用域中定义的 bean 被限定于全局portlet Session的生命周期范围内。
+
+```xml
+<bean id="user" class="com.foo.Preferences "scope="globalSession"/>
+```
+
+ Spring框架支持5种作用域，有三种作用域是当开发者使用基于web的`ApplicationContext`的时候才生效的
+
+下面就是Spring内置支持的作用域
+
+| 作用域 | 描述 |
+| --- | --- |
+| 单例(singleton)   | （默认）每一个Spring IoC容器都拥有唯一的一个实例对象 |
+| 原型(prototype) | 一个Bean定义可以创建任意多个实例对象 |
+| 请求（request） | 一个HTTP请求会产生一个Bean对象，也就是说，每一个HTTP请求都有自己的Bean实例。只在基于web的Spring `ApplicationContext`中可用 |
+| 会话（session） | 限定一个Bean的作用域为HTTP`session`的生命周期。同样，只有基于web的Spring `ApplicationContext`才能使用 |
+| 全局会话(global session） | 限定一个Bean的作用域为全局HTTP`Session`的生命周期。通常用于门户网站场景，同样，只有基于web的Spring `ApplicationContext`可用 |
+| 应用(application) | 限定一个Bean的作用域为`ServletContext`的生命周期。同样，只有基于web的Spring `ApplicationContext`可用 |
+
+> 在Spring 3.0中，*线程作用域*是可用的，但不是默认注册的
+
+#1.  singleton
+全局只有一个共享的实例,所有将单例Bean作为依赖的情况下,容器返回将是同一个实例
+
+换言之，当开发者定义一个Bean的作用域为单例时，Spring IoC容器只会根据Bean定义来创建该Bean的唯一实例。这些唯一的实例会缓存到容器中，后续针对单例Bean的请求和引用，都会从这个缓存中拿到这个唯一的实例
+![](http://upload-images.jianshu.io/upload_images/4685968-0c56982970cc16e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 1.1 单例Bean和单例模式
+- 单例模式是将一个对象的作用域硬编码，一个ClassLoader只有唯一的一个实例 
+- 而Spring的单例作用域，是`基于每个容器`，每个Bean只有一个实例
+这意味着，如果开发者根据一个类定义了一个Bean在单个的Spring容器中，那么Spring容器会根据Bean定义创建一个唯一的Bean实例。默认情况下，它们为每个给定的org.springframework.context.ApplicationContext实例存在唯一的一个bean (有点别扭，也就是可以有多个Spring容器，每一个容器内存在唯一bean实例).这意味着如果你有两个或更多上下文，所有这些上下文都由同一Java的类加载器管理(因为在同一个jvm环境中)，则可能会有多个给定bean的实例。唯一需要做到的是必须在每个上下文中定义此bean.
+
+所以你可以看到，bean只是`一个上下文的单例`
+你不应该将Spring的单例概念与设计模式中的的单例混淆
+
+ *单例作用域是Spring的默认作用域*，下面的例子是在基于XML的配置中配置单例模式的Bean。
+
+```
+<bean id="accountService" class="com.sss.DefaultAccountService"/>
+
+<!-- the following is equivalent, though redundant (singleton scope is the default) -->
+<bean id="accountService" class="com.sss.DefaultAccountService" scope="singleton"/>
+
+```
+#2. prototype
+每次请求Bean实例时，返回的都是新实例的Bean对象
+也就是说，每次注入到另外的Bean或者通过调用`getBean()`来获得的Bean都将是全新的实例。 
+这是基于线程安全性的考虑，如果使用有状态的Bean对象用prototype 作用域，而无状态的Bean对象用singleton 作用域。
+
+下面的例子说明了Spring的原型作用域。DAO通常不会配置为原型对象，因为典型的DAO是不会有任何的状态的。
+![](http://upload-images.jianshu.io/upload_images/4685968-0a35694b27f5a359.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+下面的例子展示了XML中如何定义一个原型的Bean：
+```
+<bean id="accountService" class="com.sss.DefaultAccountService" scope="prototype"/>
+```
+与其他的作用域相比，Spring不会完全管理原型Bean的生命周期：
+Spring容器只会初始化配置以及装载这些Bean，传递给Client。
+但是之后就不会再去管原型Bean之后的动作了。
+ 
+也就是说，初始化生命周期回调方法在所有作用域的Bean是都会调用的，但是销毁生命周期回调方法在原型Bean是不会调用的
+
+所以，客户端代码必须注意清理原型Bean以及释放原型Bean所持有的一些资源。
+ 可以通过使用自定义的`bean post-processor`来让Spring释放掉原型Bean所持有的资源。
+
+在某些方面来说，Spring容器的角色就是取代了Java的`new`操作符，所有的生命周期的控制需要由客户端来处理。
+## 2-1 Singleton beans with prototype-bean dependencies
+###在原型bean中放置单例
+如果注入的单例对象真的是一个单例的bean(没有状态)，这个真的没一点问题
+想象一下，对于我们的购物车，我们需要注入产品服务。此服务只会检查添加到购物车的产品是否库存。由于服务没有状态，并且会基于在方法签名中所传递的对象进行验证，因此不存在风险
+
+当使用单例Bean的时候，而该单例Bean的依赖是原型Bean时，需要注意的是**依赖的解析都是在初始化的阶段**
+因此，如果将原型Bean注入到单例的Bean之中，只会请求一次原型Bean，然后注入到单例Bean中。这个依赖的原型Bean仍然属于只有一个实例的。
+
+然而，假设你需要单例Bean对原型的Bean的依赖
+需要每次在运行时都请求一个新的实例，那么你就不能够将一个原型的Bean来注入到一个单例的Bean当中了，因为依赖注入只会进行*一次* 
+当Spring容器在实例化单例Bean的时候，就会解析以及注入它所需的依赖
+如果实在需要每次都请求一个新的实例，可以通过bean工厂手动获取实例,也可以参考[Dependencies](http://blog.didispace.com/books/spring-framework-4-reference/III.%20Core%20Technologies/Dependencies.html)中的**方法注入**部分。
+##使用单例还是原型？
+- Singleton适用于无状态的bean,比如一个service，DAO或者controller
+他们都没有自己的状态(举个简单的例子，一个函数sin(x)，这个函数本身就是无状态的，所以我们现在喜欢的函数式编程也遵循这个理念)。而是根据传输的参数执行一些操作(作为HTTP请求参数)。
+- 另一方面，我们可以通过状态bean管理一些状态。比如购物车bean，假如它是一个单例，那么两个不同消费者购买的产品将被放置在同一个对象上。而如果其中一个消费者想要删除一个产品，另一个消费者就铁定不高兴。这也就是状态类对象应该是原型
+#3.  Request
+Spring容器会在每次用到`loginAction`来处理每个HTTP请求的时候都会创建一个新的`LoginAction`实例。也就是说，`loginAction`Bean的作用域是HTTP`Request`级别的。 开发者可以随意改变实例的状态，因为其他通过`loginAction`请求来创建的实例根本看不到开发者改变的实例状态，所有创建的Bean实例都是根据独立的请求来的。当请求处理完毕，这个Bean也会销毁。
+![](http://upload-images.jianshu.io/upload_images/4685968-49d6648d6c3d9556.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+每个请求初始化具有此作用域的Bean注解。这听起来像是原型作用域的描述，但它们有一些差异。
+- 原型作用域在Spring的上下文中可用。而请求作用域仅适用于Web应用程序
+- 原型bean根据需求进行初始化，而请求bean是在每个请求下构建的
+
+需要说的是，request作用域bean在其作用域内有且仅有一个实例。而你可以拥有一个或多个原型作用域bean实例
+
+在以下代码中，你可以看到请求作用域bean的示例：
+```java
+<bean id="shoppingCartRequest" class="com.sss.scope.ShoppingCartRequest" scope="request">
+    <aop:scoped-proxy/> 
+</bean>
+```
+当使用注解驱动组件或Java Config时，@RequestScope注解可以用于将一个组件分配给request作用域。
+```java
+@RequestScope
+@Component
+public class ShoppingCartRequest {
+	// ...
+}
+// request bean
+ 
+// injection sample
+@Controller
+public class TestController {
+    @Autowired
+    private ShoppingCartRequest shoppingCartRequest;
+     
+    @RequestMapping(value = "/test", method = RequestMethod.GET)
+    public String test(HttpServletRequest request) {
+        LOGGER.debug("shoppingCartRequest is :"+shoppingCartRequest);
+        // ...
+    }
+}
+```
+请注意<bean>定义内存在的<aop: scoped-proxy />标签。这代表着使用代理对象。所以实际上，TestController持有的是代理对象的引用。我们所有的调用该对象都会转发到真正的ShoppingCartRequest对象。
+
+有时我们需要使用DispatcherServlet的另一个servlet来处理请求。在这种情况下，我们必须确保Spring中所有请求都可用(否则可以抛出与下面类似的异常)。为此，我们需要在web.xml中定义一个监听器:
+```java
+<listener>   
+  <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
+</listener>
+```
+
+调用/测试URL后，你应该能在日志中的发现以下信息:
+```java
+shoppingCartRequest is :com.migo.scope.ShoppingCartRequest@2586b11c
+shoppingCartRequest is :com.migo.scope.ShoppingCartRequest@3bd5b945
+```
+如果我们尝试在单例bean中使用request作用域的bean，则会在应用程序上下文加载阶段抛出一个BeanCreationException
+ #4. session
+参考如下的Bean定义：
+
+```
+<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
+
+```
+
+Spring容器会在每次调用到`userPreferences`在一个单独的HTTP会话周期来创建一个新的`UserPreferences`实例。换言之，`userPreferences`Bean的作用域是HTTP`Session`级别的。 在`request-scoped`作用域的Bean上，开发者可以随意的更改实例的状态，同样，其他的HTTP`Session`基本的实例在每个Session都会请求`userPreferences`来创建新的实例，所以开发者更改Bean的状态，对于其他的Bean仍然是不可见的。当HTTP`Session`销毁了，那么根据这个`Session`来创建的Bean也就销毁了。
+
+![](http://upload-images.jianshu.io/upload_images/4685968-e219a491ac3e8710.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+Session作用域的bean与request 作用域的bean没有太大不同。它们也与纯Web应用程序上下文相关联。注解为Session作用域的Bean对于每个用户的会话仅创建一次。他们在会话结束时被破坏销毁掉。
+
+由Session作用域限制的Bean可以被认为是面向Web的单例，因为给定环境(用户会话)仅存在一个实例。但请记住，你无法在Web应用程序上下文中使用它们(说个好理解点的，就是一个函数内部自定义变量所在的作用域，函数执行完就销毁了，没有什么逃逸)。
+
+想知道Session作用域bean在Spring中的操作，我们需要在配置文件中定义一个bean:
+```java
+<bean id="shoppingCartRequest" class="com.migo.scope.ShoppingCartSession" scope="session">
+
+    <aop:scoped-proxy/> 
+
+</bean>
+```
+通过`@Autowired`注解，查找这个bean的方式与request 作用域的bean相同。可以看到以下结果:
+你可以看到，前5个打印输出代表相同的对象。最后一个是不同的。这是什么意思 ?简单来说，这代表 着一个新的用户使用自动注入的Session作用域访问该页面。我们可以通过打开两个浏览器的测试页(/test)来观察它。每个都将初始化一个新的会话Session，因此也就创建新的`ShoppingCartSession bean`实例。
+#5. global session
+> 该部分主要是描述`portlet`的，详情可以Google更多关于`portlet`的相关信息。
+
+参考如下的Bean定义：
+
+```
+<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
+
+```
+
+`global session`作用域比较类似之前提到的标准的HTTP`Session`，这种作用域是只应用于基于门户（portlet-based）的web应用的上下之中的。门户的Spec中定义的`global session`的意义：`global session`被所有构成门户的web应用所共享。定义为`global session`作用域的Bean是作用在全局门户`Session`的声明周期的。
+
+如果在使用标准的基于Servlet的Web应用，而且定义了`global session`作用域的Bean，那么只是会使用标准的HTTP`Session`作用域，不会报错。
+![](http://upload-images.jianshu.io/upload_images/4685968-100da62584121eeb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+关于全局会话作用域(Global session scope)属于4.3x的范畴了，Spring5已经没有了，Spring5文档是去掉了因为4的存在所以还是说两句，它保留给portlet应用程序。 是不是一脸懵逼，so，来解释一下portlet是什么。Portlet是能够生成语义代码(例如：HTML)片段的小型Java Web插件。它们基于portlet容器，可以像servlet一样处理HTTP请求。但是，与servlet不同，每个portlet都有不同的会话。在这种情况下，Spring提供了一个名为`global-session`的作用域。通过它，一个bean可以通过应用程序中的多个portlet共享。
+
+至此，我们解释了请求和面向会话的作用域。第一个的作用是在每个request请求上创建新的bean。第二个在Session会话开始的时候初始化bean。
+
diff --git "a/Spring/SpringFramework/Spring Bean\347\224\237\345\221\275\345\221\250\346\234\237\347\256\241\347\220\206.md" "b/Spring/SpringFramework/Spring-Bean\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md"
similarity index 54%
rename from "Spring/SpringFramework/Spring Bean\347\224\237\345\221\275\345\221\250\346\234\237\347\256\241\347\220\206.md"
rename to "Spring/SpringFramework/Spring-Bean\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md"
index 0f32a53bfe..daf177acef 100644
--- "a/Spring/SpringFramework/Spring Bean\347\224\237\345\221\275\345\221\250\346\234\237\347\256\241\347\220\206.md"	
+++ "b/Spring/SpringFramework/Spring-Bean\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md"
@@ -1,77 +1,34 @@
-# 1 Spring简介
-- 轻量级容器，提供集中式，自动配置与装配应用业务对象功能
-- 提供统一的事务管理抽象，基于插件式的事务管理(声明性事务管理)能够很容易的实现事务层管理,而无需了解底层事务实现
-- 提供统一的数据访问抽象，包括简单和有效率的JDBC框架，极大的改进了效率(大大减少了开发的代码量)并且减少了可能的错误
+#Spring简介
+- 轻量级的容器，提供集中式，自动配置与装配应用业务对象功能
+- 提供了统一的事务管理抽象，基于插件式的事务管理(声明性事务管理)能够很容易的实现事务层管理,而无需了解各种底层事务实现
+- 提供了统一的数据访问抽象，包括简单和有效率的JDBC框架，极大的改进了效率(大大减少了开发的代码量)并且减少了可能的错误
 - Spring的数据访问层集成了Toplink,Hibernate,JDO,and iBATIS SQL Maps等O/R mapping解决方案，其目的是提供统一的DAO支持类实现和事务管理策略
 - Spring提供了一个用标准Java编写的AOP框架(也能集成AspectJ)，提供基于POJOs的声明式的事务管理和其他企业事务
 - 提供可以与IoC容器集成的强大而灵活的MVCWeb框架 
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNGFmYzBlYjAzMTVhNDBhYS5wbmc?x-oss-process=image/format,png)
+![](http://upload-images.jianshu.io/upload_images/4685968-4afc0eb0315a40aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
-# 2 bean对象生命周期管理
-![生命周期](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZGJhOTAyYjE0NzlhN2U4Mi5wbmc?x-oss-process=image/format,png)
+#bean对象生命周期管理
+![生命周期](http://upload-images.jianshu.io/upload_images/4685968-dba902b1479a7e82.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+1.Spring对Bean进行实例化（相当于程序中的new Class())
+2.Spring将值和Bean的引用注入进Bean对应的属性中
+3.如果Bean实现了BeanNameAware接口，Spring将Bean的ID传递给setBeanName()方法（实现BeanNameAware主要是为了通过Bean的引用来获得Bean的ID，一般业务中是很少有用到Bean的ID的）
+4.如果Bean实现了BeanFactoryAware接口，Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入（实现BeanFactoryAware 主要目的是为了获取Spring容器，如Bean通过Spring容器发布事件等）
+5.如果Bean实现了ApplicationContextAwaer接口，Spring容器将调用setApplicationContext(ApplicationContext ctx)方法，把应用上下文作为参数传入(作用与BeanFactory类似都是为了获取Spring容器，不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入，而Spring容器在调用setBeanDactory前需要程序员自己指定（注入）setBeanDactory里的参数BeanFactory )
+6.如果Bean实现了BeanPostProcess接口，Spring将调用它们的postProcessBeforeInitialization（预初始化）方法（作用是在Bean实例创建成功后对进行增强处理，如对Bean进行修改，增加某个功能）
+7.如果Bean实现了InitializingBean接口，Spring将调用它们的afterPropertiesSet方法，作用与在配置文件中对Bean使用init-method声明初始化的作用一样，都是在Bean的全部属性设置成功后执行的初始化方法。
+8.如果Bean实现了BeanPostProcess接口，Spring将调用它们的postProcessAfterInitialization（后初始化）方法（作用与6的一样，只不过6是在Bean初始化前执行的，而这个是在Bean初始化后执行的，时机不同 )
+9.经过以上的工作后，Bean将一直驻留在应用上下文中给应用使用，直到应用上下文被销毁
+10.如果Bean实现了DispostbleBean接口，Spring将调用它的destory方法，作用与在配置文件中对Bean使用destory-method属性的作用一样，都是在Bean实例销毁前执行的方法
 
-## 1 Spring对Bean进行实例化
-相当于new Class()
 
-## 2 Spring将值和Bean的引用注进Bean对应的属性
+# 7 bean的生命周期
 
-## 3 实现BeanNameAware接口
-- Spring将Bean的ID传递给setBeanName()方法
-- 实现BeanNameAware主要为了通过Bean的引用获得Bean的ID，一般业务中是很少有用到Bean的ID
-## 4 实现BeanFactoryAware接口
-- Spring将调用setBeanDactory(BeanFactory bf)，并把BeanFactory容器实例作为参数传入
-- 实现BeanFactoryAware 主要为了获取Spring容器，如Bean通过Spring容器发布事件
-## 5 实现ApplicationContextAwaer接口
-- Spring容器将调用setApplicationContext(ApplicationContext ctx)，把应用上下文作为参数传入。
-与BeanFactory类似都为获取Spring容器，不同是
-Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入
-而Spring容器在调用setBeanDactory前需要程序员自己指定（注入）setBeanDactory里的参数BeanFactory
-
-## 6 BeanPostProcess接口
-Spring将调用它们的postProcessBeforeInitialization（预初始化）方法，在Bean实例创建成功后对进行增强处理，如对Bean进行修改，增加某个功能。
-
-## 7 InitializingBean接口
-InitializingBean接口为bean提供了初始化方法的方式。
-它只包括afterPropertiesSet方法，凡是继承该接口的类，在初始化bean的时候会自动执行该方法。
-
-Spring将调用它们的afterPropertiesSet方法，作用与在配置文件中对Bean使用init-method声明初始化的作用一样，都是在Bean的全部属性设置成功后执行的初始化方法。
-
-- 实现InitializingBean接口是直接调用afterPropertiesSet方法，比通过反射调用init-method指定的方法相对来说效率要高。但init-method方式消除了对spring的依赖
-- 若调用afterPropertiesSet方法时产生异常，则也不会再调用init-method指定的方法
-```java
-package org.springframework.beans.factory;
-
-/**
- * 所有由BeanFactory设置的所有属性都需要响应的bean的接口：例如，执行自定义初始化，或者只是检查所有强制属性是否被设置。
- * 实现InitializingBean的替代方法是指定一个自定义init方法，例如在XML bean定义中。
- */
-public interface InitializingBean {
+Spring Bean是Spring应用中最最重要的部分了。所以来看看Spring容器在初始化一个bean的时候会做那些事情，顺序是怎样的，在容器关闭的时候，又会做哪些事情。
 
-	/**
-	 * 在BeanFactory设置了提供的所有bean属性后，由BeanFactory调用。 
-     *这个方法允许bean实例在所有的bean属性被设置时才能执行
-	 */
-	void afterPropertiesSet() throws Exception;
+> spring版本：4.2.3.RELEASE
+鉴于Spring源码是用gradle构建的，我也决定舍弃我大maven，尝试下洪菊推荐过的gradle。运行beanLifeCycle模块下的junit test即可在控制台看到如下输出，可以清楚了解Spring容器在创建，初始化和销毁Bean的时候依次做了那些事情。
 
-}
 ```
-若class中实现该接口，在Spring Container中的bean生成之后，自动调用函数afterPropertiesSet()。
-
-因其实现了InitializingBean接口，其中只有一个方法，且在Bean加载后就执行。该方法可被用来检查是否所有的属性都已设置好。
-## 8 BeanPostProcess接口
-Spring将调用它们的postProcessAfterInitialization（后初始化）方法，作用与6一样，只不过6是在Bean初始化前执行，而这是在Bean初始化后执行。
-
-
-> 经过以上工作，Bean将一直驻留在应用上下文中给应用使用，直到应用上下文被销毁。
-
-## 9 DispostbleBean接口
-- Spring将调用它的destory方法
-- 作用与在配置文件中对Bean使用destory-method属性的作用一样，都是在Bean实例销毁前执行的方法
-
-
-Spring Bean是Spring应用中最最重要的部分了。所以来看看Spring容器在初始化一个bean的时候会做那些事情，顺序是怎样的，在容器关闭的时候，又会做哪些事情。
-
-```bash
 Spring容器初始化
 =====================================
 调用GiraffeService无参构造函数
@@ -91,51 +48,59 @@ GiraffeService中利用set方法设置属性值
 Spring容器初始化完毕
 =====================================
 从容器中获取Bean
-giraffe Name=JavaEdge
+giraffe Name=李光洙
 =====================================
 调用preDestroy注解标注的方法
 执行DisposableBean接口的destroy方法
 执行配置的destroy-method
 Spring容器关闭
 ```
+
 先来看看，Spring在Bean从创建到销毁的生命周期中可能做得事情。
 
+
 # initialization 和 destroy
-有时需要在Bean属性值set好后、Bean销毁前搞事情，比如检查Bean中某个属性是否被正常设值。
 
+有时我们需要在Bean属性值set好之后和Bean销毁之前做一些事情，比如检查Bean中某个属性是否被正常的设置好值
 Spring提供了多种方法让我们可以在 Bean 的生命周期中执行initialization和pre-destroy方法
 
-## 1 实现InitializingBean/DisposableBean接口
-这两个接口都只包含一个方法：
-- 实现InitializingBean#afterPropertiesSet()，可在Bean属性值设置好后操作
-- 实现DisposableBean#destroy()，可在销毁Bean前操作
+## **1.实现InitializingBean和DisposableBean接口**
+
+这两个接口都只包含一个方法。
+- 实现InitializingBean接口的afterPropertiesSet()方法可以在Bean属性值设置好之后做一些操作
+- 实现DisposableBean接口的destroy()方法可以在销毁Bean之前做一些操作。
+
 ### 案例
+
 ```java
 public class GiraffeService implements InitializingBean,DisposableBean {
-
     @Override
     public void afterPropertiesSet() throws Exception {
         System.out.println("执行InitializingBean接口的afterPropertiesSet方法");
     }
-    
     @Override
     public void destroy() throws Exception {
         System.out.println("执行DisposableBean接口的destroy方法");
     }
 }
 ```
-这种使用比较简单，但不推荐，会将Bean实现和Spring框架耦合。
-## 2 bean配置文件指定init-method、destroy-method
-Spring允许我们创建自己的 init 方法和 destroy 方法。只要在 Bean 的配置文件中指定 `init-method` 和 `destroy-method` 的值就可以在 Bean 初始化时和销毁之前执行一些操作。
+这种方法比较简单，但是不建议使用。因为这样会将Bean的实现和Spring框架耦合在一起。
+
+## **2.在bean的配置文件中指定init-method和destroy-method方法**
+
+Spring允许我们创建自己的 init 方法和 destroy 方法
+
+只要在 Bean 的配置文件中指定 `init-method` 和 `destroy-method` 的值就可以在 Bean 初始化时和销毁之前执行一些操作。
+
 ### 案例
+
 ```java
 public class GiraffeService {
-    // 通过<bean>的destroy-method属性指定的销毁方法
+    //通过<bean>的destroy-method属性指定的销毁方法
     public void destroyMethod() throws Exception {
         System.out.println("执行配置的destroy-method");
     }
-    
-    // 通过<bean>的init-method属性指定的初始化方法
+    //通过<bean>的init-method属性指定的初始化方法
     public void initMethod() throws Exception {
         System.out.println("执行配置的init-method");
     }
@@ -143,25 +108,28 @@ public class GiraffeService {
 ```
 
 配置文件中的配置：
-```xml
+
+```
 <bean name="giraffeService" class="com.giraffe.spring.service.GiraffeService" init-method="initMethod" destroy-method="destroyMethod">
 </bean>
 ```
-自定义的init-method和post-method方法可以抛异常，但不能有参数。
 
-这种方式比较推荐，因为可以自己创建方法，无需将Bean的实现直接依赖于Spring框架。
+需要注意的是自定义的init-method和post-method方法可以抛异常但是不能有参数。
+
+这种方式比较推荐，因为可以自己创建方法，无需将Bean的实现直接依赖于spring的框架。
 
-## @PostConstruct、@PreDestroy
-这两个注解均在`javax.annotation` 包。
-Spring 支持用 `@PostConstruct`和 `@PreDestroy`注解指定 `init` 和 `destroy` 方法。
-为使注解生效，需在配置文件中定义
+## **3.使用@PostConstruct和@PreDestroy注解**
+
+除了xml配置的方式，Spring 也支持用 `@PostConstruct`和 `@PreDestroy`注解来指定 `init` 和 `destroy` 方法。
+这两个注解均在`javax.annotation` 包中。
+为了注解可以生效，需要在配置文件中定义
 - org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
 - 或context:annotation-config
 
 ### 案例
+
 ```java
 public class GiraffeService {
-
     @PostConstruct
     public void initPostConstruct(){
         System.out.println("执行PostConstruct注解标注的方法");
@@ -172,12 +140,16 @@ public class GiraffeService {
     }
 }
 ```
+
 配置文件：
+
 ```xml
+  
 <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" />
+
 ```
-# 实现Aware接口
-在Bean中使用Spring框架的一些对象
+
+# 实现Aware接口 在Bean中使用Spring框架的一些对象
 
 有些时候我们需要在 Bean 的初始化中使用 Spring 框架自身的一些对象来执行一些操作，比如
 - 获取 ServletContext 的一些参数
@@ -189,27 +161,8 @@ public class GiraffeService {
 这些接口均继承于`org.springframework.beans.factory.Aware`标记接口，并提供一个将由 Bean 实现的set方法,Spring通过基于setter的依赖注入方式使相应的对象可以被Bean使用。
 
 介绍一些重要的Aware接口：
-## **ApplicationContextAware**
-获得ApplicationContext对象,可以用来获取所有Bean definition的名字。
-
-任何希望被通知它运行的ApplicationContext对象要实现的接口。
-例如，当一个对象需要访问一组协作 bean 时。通过 bean 引用进行配置比仅为了bean=查找而实现此接口更有意义！
-如果对象需要访问文件资源，即想要调用getResource ，想要发布应用程序事件，或者需要访问 MessageSource，也可以实现此接口。 但是，在这种特定场景中，最好实现更具体的ResourceLoaderAware 、 ApplicationEventPublisherAware或MessageSourceAware接口。
-请注意，文件资源依赖项也可以作为org.springframework.core.io.Resource类型的 bean 属性公开，通过字符串填充，并由 bean 工厂进行自动类型转换。 这消除了为了访问特定文件资源而实现任何回调接口的需要。
-org.springframework.context.support.ApplicationObjectSupport是应用程序对象的一个​​方便的基类，实现了这个接口。
-
-实现该接口的类，通过方法setApplicationContext()获得该对象所运行在的ApplicationContext。一般用于初始化object。
-![](https://img-blog.csdnimg.cn/20190702041941650.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-在填充普通 bean 属性之后但在初始化回调之前调用，例如：
-- org.springframework.beans.factory.InitializingBean.afterPropertiesSet()
-- 或自定义初始化方法
-
-在：
-- ResourceLoaderAware.setResourceLoader 
-- ApplicationEventPublisherAware.setApplicationEventPublisher
-- 和MessageSourceAware之后调用（如果适用）。
-
 
+- **ApplicationContextAware**: 获得ApplicationContext对象,可以用来获取所有Bean definition的名字。
 - **BeanFactoryAware**:获得BeanFactory对象，可以用来检测Bean的作用域。
 - **BeanNameAware**:获得Bean在配置文件中定义的名字。
 - **ResourceLoaderAware**:获得ResourceLoader对象，可以获得classpath中某个文件。
@@ -258,32 +211,16 @@ public class GiraffeService implements   ApplicationContextAware,
     }
 }
 ```
+
 ### BeanPostProcessor
-允许自定义修改新 bean 实例的工厂钩子——如检查标记接口或用代理包装 bean。
-
-- 通过标记接口或类似方式填充bean的后置处理器将实现postProcessBeforeInitialization（java.lang.Object，java.lang.String）
-- 而用代理包装bean的后置处理器通常会实现postProcessAfterInitialization（java.lang.Object，java.lang.String）
-#### Registration
-一个ApplicationContext可在其 Bean 定义中自动检测 BeanPostProcessor Bean，并将这些后置处理器应用于随后创建的任何 Bean。
-普通的BeanFactory允许对后置处理器进行编程注册，将它们应用于通过Bean工厂创建的所有Bean。
-#### Ordering
-在 ApplicationContext 中自动检测的 OrderBeanPostProcessor Bean 将根据 PriorityOrdered 和 Ordered 语义进行排序。
-相比之下，在BeanFactory以编程方式注册的BeanPostProcessor bean将按注册顺序应用
-对于以编程方式注册的后处理器，通过实现 PriorityOrdered 或 Ordered 接口表达的任何排序语义都将被忽略。
-对于 BeanPostProcessor bean，并不考虑 **@Order** 注解。
-![](https://img-blog.csdnimg.cn/05510c239a6e41a683eb806181b90347.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-Aware接口是针对某个 **实现这些接口的Bean** 定制初始化的过程，
-Spring还可针对容器中 **所有Bean** 或 **某些Bean** 定制初始化过程，只需提供一个实现BeanPostProcessor接口的实现类。 
-
-该接口包含如下方法：
-- postProcessBeforeInitialization
-在容器中的Bean初始化之前执行
-- postProcessAfterInitialization
-在容器中的Bean初始化之后执行
-#### 实例
+
+上面的*Aware接口是针对某个实现这些接口的Bean定制初始化的过程，
+Spring同样可以针对容器中的所有Bean，或者某些Bean定制初始化过程，只需提供一个实现BeanPostProcessor接口的类即可。 该接口中包含两个方法，postProcessBeforeInitialization和postProcessAfterInitialization。 postProcessBeforeInitialization方法会在容器中的Bean初始化之前执行， postProcessAfterInitialization方法在容器中的Bean初始化之后执行。
+
+例子如下：
+
 ```java
 public class CustomerBeanPostProcessor implements BeanPostProcessor {
-
     @Override
     public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
         System.out.println("执行BeanPostProcessor的postProcessBeforeInitialization方法,beanName=" + beanName);
@@ -296,34 +233,44 @@ public class CustomerBeanPostProcessor implements BeanPostProcessor {
     }
 }
 ```
+
 要将BeanPostProcessor的Bean像其他Bean一样定义在配置文件中
+
 ```xml  
-<bean class="com.javaedge.spring.service.CustomerBeanPostProcessor"/>
+<bean class="com.giraffe.spring.service.CustomerBeanPostProcessor"/>
 ```
+
 # 总结
-Spring Bean的生命周期
+
+所以。。。结合第一节控制台输出的内容，Spring Bean的生命周期是这样的：
 
 - Bean容器找到配置文件中 Spring Bean 的定义。
-- Bean容器利用反射创建一个Bean的实例。
-- 如果涉及到一些属性值，利用set方法设置一些属性值。
-- 如果Bean实现了BeanNameAware接口，调用setBeanName()方法，传入Bean的名字
-- 如果Bean实现了BeanClassLoaderAware接口，调用setBeanClassLoader()方法，传入ClassLoader对象的实例
-- 如果Bean实现了BeanFactoryAware接口，调用setBeanClassLoader()方法，传入ClassLoader对象的实例
+- Bean容器利用Java Reflection API创建一个Bean的实例。
+- 如果涉及到一些属性值 利用set方法设置一些属性值。
+- 如果Bean实现了BeanNameAware接口，调用setBeanName()方法，传入Bean的名字。
+- 如果Bean实现了BeanClassLoaderAware接口，调用setBeanClassLoader()方法，传入ClassLoader对象的实例。
+- 如果Bean实现了BeanFactoryAware接口，调用setBeanClassLoader()方法，传入ClassLoader对象的实例。
 - 与上面的类似，如果实现了其他Aware接口，就调用相应的方法。
 - 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象，执行postProcessBeforeInitialization()方法
-- 若Bean实现InitializingBean接口，执行afterPropertiesSet()
-- 若Bean定义包含init-method属性，执行指定方法
+- 如果Bean实现了InitializingBean接口，执行afterPropertiesSet()方法。
+- 如果Bean在配置文件中的定义包含init-method属性，执行指定的方法。
 - 如果有和加载这个Bean的Spring容器相关的BeanPostProcessor对象，执行postProcessAfterInitialization()方法
 - 当要销毁Bean的时候，如果Bean实现了DisposableBean接口，执行destroy()方法。
 - 当要销毁Bean的时候，如果Bean在配置文件中的定义包含destroy-method属性，执行指定的方法。
 
-![](https://imgconvert.csdnimg.cn/aHR0cDovL215LWJsb2ctdG8tdXNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS8xOC05LTE3LzQ4Mzc2MjcyLmpwZw?x-oss-process=image/format,png)
+用图表示一下(图来源:http://www.jianshu.com/p/d00539babca5)：
+
+![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/48376272.jpg)
 
 与之比较类似的中文版本:
 
-![](https://imgconvert.csdnimg.cn/aHR0cDovL215LWJsb2ctdG8tdXNlLm9zcy1jbi1iZWlqaW5nLmFsaXl1bmNzLmNvbS8xOC05LTE3LzU0OTY0MDcuanBn?x-oss-process=image/format,png)
-很多时候我们并不会真的去实现上面说描述的那些接口，那么下面我们就除去那些接口，针对bean的单例和非单例来描述下bean的生命周期：
+![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-17/5496407.jpg)
+
+
+**其实很多时候我们并不会真的去实现上面说描述的那些接口，那么下面我们就除去那些接口，针对bean的单例和非单例来描述下bean的生命周期：**
+
 ### 单例管理的对象
+
 当scope=”singleton”，即默认情况下，会在启动容器时（即实例化容器时）时实例化。但我们可以指定Bean节点的lazy-init=”true”来延迟初始化bean，这时候，只有在第一次获取bean时才会初始化bean，即第一次请求该bean时才初始化。如下配置：
 
 ```xml
@@ -363,12 +310,15 @@ public class LifeBean {
     }  
 }
 ```
-life.xml配置如下：
+　life.xml配置如下：
+
 ```xml
 <bean id="life_singleton" class="com.bean.LifeBean" scope="singleton" 
             init-method="init" destroy-method="destory" lazy-init="true"/>
 ```
+
 测试代码：
+
 ```java
 public class LifeTest {
     @Test 
@@ -384,7 +334,7 @@ public class LifeTest {
 
 运行结果：
 
-```bash
+```
 LifeBean()构造函数
 this is init of lifeBean
 com.bean.LifeBean@573f2bb1
@@ -393,13 +343,17 @@ this is destory of lifeBean com.bean.LifeBean@573f2bb1
 ```
 
 ### 非单例管理的对象
+
 当`scope=”prototype”`时，容器也会延迟初始化 bean，Spring 读取xml 文件的时候，并不会立刻创建对象，而是在第一次请求该 bean 时才初始化（如调用getBean方法时）。在第一次请求每一个 prototype 的bean 时，Spring容器都会调用其构造器创建这个对象，然后调用`init-method`属性值中所指定的方法。对象销毁的时候，Spring 容器不会帮我们调用任何方法，因为是非单例，这个类型的对象有很多个，Spring容器一旦把这个对象交给你之后，就不再管理这个对象了。
 
-为了测试prototype bean的生命周期，life.xml配置如下：
+为了测试prototype bean的生命周期life.xml配置如下：
+
 ```xml
 <bean id="life_prototype" class="com.bean.LifeBean" scope="prototype" init-method="init" destroy-method="destory"/>
 ```
+
 测试程序：
+
 ```java
 public class LifeTest {
     @Test 
@@ -417,7 +371,7 @@ public class LifeTest {
 
 运行结果：
 
-```bash
+```
 LifeBean()构造函数
 this is init of lifeBean
 com.bean.LifeBean@573f2bb1
@@ -430,10 +384,10 @@ this is destory of lifeBean com.bean.LifeBean@573f2bb1
 
 可以发现，对于作用域为 prototype 的 bean ，其`destroy`方法并没有被调用。**如果 bean 的 scope 设为prototype时，当容器关闭时，`destroy` 方法不会被调用。对于 prototype 作用域的 bean，有一点非常重要，那就是 Spring不能对一个 prototype bean 的整个生命周期负责：容器在初始化、配置、装饰或者是装配完一个prototype实例后，将它交给客户端，随后就对该prototype实例不闻不问了。** 不管何种作用域，容器都会调用所有对象的初始化生命周期回调方法。但对prototype而言，任何配置好的析构生命周期回调方法都将不会被调用。**清除prototype作用域的对象并释放任何prototype bean所持有的昂贵资源，都是客户端代码的职责**（让Spring容器释放被prototype作用域bean占用资源的一种可行方式是，通过使用bean的后置处理器，该处理器持有要被清除的bean的引用）。谈及prototype作用域的bean时，在某些方面你可以将Spring容器的角色看作是Java new操作的替代者，任何迟于该时间点的生命周期事宜都得交由客户端来处理。
 
-Spring 容器可以管理 singleton 作用域下 bean 的生命周期，在此作用域下，Spring 能够精确地知道bean何时被创建，何时初始化完成，以及何时被销毁。而对于 prototype 作用域的bean，Spring只负责创建，当容器创建了 bean 的实例后，bean 的实例就交给了客户端的代码管理，Spring容器将不再跟踪其生命周期，并且不会管理那些被配置成prototype作用域的bean的生命周期。
+**Spring 容器可以管理 singleton 作用域下 bean 的生命周期，在此作用域下，Spring 能够精确地知道bean何时被创建，何时初始化完成，以及何时被销毁。而对于 prototype 作用域的bean，Spring只负责创建，当容器创建了 bean 的实例后，bean 的实例就交给了客户端的代码管理，Spring容器将不再跟踪其生命周期，并且不会管理那些被配置成prototype作用域的bean的生命周期。**
+
 
+# 参考
 
-> 参考
-> - https://blog.csdn.net/fuzhongmin05/article/details/73389779
-> - https://yemengying.com/2016/07/14/spring-bean-life-cycle/
-> - https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/config/BeanPostProcessor.html
\ No newline at end of file
+- https://blog.csdn.net/fuzhongmin05/article/details/73389779
+- https://yemengying.com/2016/07/14/spring-bean-life-cycle/
diff --git "a/Spring/SpringFramework/Spring-\344\272\213\345\212\241\347\256\241\347\220\206.md" "b/Spring/SpringFramework/Spring-\344\272\213\345\212\241\347\256\241\347\220\206.md"
new file mode 100644
index 0000000000..f24f8e4bd6
--- /dev/null
+++ "b/Spring/SpringFramework/Spring-\344\272\213\345\212\241\347\256\241\347\220\206.md"
@@ -0,0 +1,42 @@
+# 1 目标
+![](https://upload-images.jianshu.io/upload_images/4685968-eed053d251275855.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 2事务回顾
+## 什么是事务
+![](https://upload-images.jianshu.io/upload_images/4685968-83d81f1c7b75ae07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 具体案例
+![](https://upload-images.jianshu.io/upload_images/4685968-1028f8592a327682.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![钱转了,李四却没收到!!!](https://upload-images.jianshu.io/upload_images/4685968-ce870c99003f98c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![需要事务!!!](https://upload-images.jianshu.io/upload_images/4685968-a102d0e9734d4216.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 事务的特性
+![](https://upload-images.jianshu.io/upload_images/4685968-3a6300f87c2a1953.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![原子性](https://upload-images.jianshu.io/upload_images/4685968-6cc65f05555dd413.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![一致性](https://upload-images.jianshu.io/upload_images/4685968-f76d2f7cab3ca00c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-889156dab97b9c97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-226a7bf9ea65ab08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ec18c46d3c08b495.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 3 事务的API
+## Spring 接口介绍
+![](https://upload-images.jianshu.io/upload_images/4685968-96ed77d556799458.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-01dd1056fff16546.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-2172687122971173.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5c61cb9956587e6e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## PlatformTransactionManager
+![](https://upload-images.jianshu.io/upload_images/4685968-e13c0def9040a4c9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## TransactionDefinition
+![](https://upload-images.jianshu.io/upload_images/4685968-d7ca52c82a556b36.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 脏读
+![](https://upload-images.jianshu.io/upload_images/4685968-d722077c8547ab50.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 不可重复读
+![](https://upload-images.jianshu.io/upload_images/4685968-c78a6f53c4ec2014.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 幻读
+![](https://upload-images.jianshu.io/upload_images/4685968-c3a2b2720333ad2b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 事务隔离级别
+![](https://upload-images.jianshu.io/upload_images/4685968-d7e2033751124d7b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-133d4393a443117a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-738039998b94ad7f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## TransactionDefinition事务传播行为
+![](https://upload-images.jianshu.io/upload_images/4685968-937d0ca6d13ec7d9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## TransactionStatus
+
+# 4 环境搭建
+![](https://upload-images.jianshu.io/upload_images/4685968-d705cb27a349cc84.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
diff --git "a/Spring/SpringFramework/Spring-\345\256\236\346\210\230---IoC\345\256\271\345\231\250\347\232\204\344\276\235\350\265\226\346\263\250\345\205\245.md" "b/Spring/SpringFramework/Spring-\345\256\236\346\210\230---IoC\345\256\271\345\231\250\347\232\204\344\276\235\350\265\226\346\263\250\345\205\245.md"
new file mode 100644
index 0000000000..2a196ea8f0
--- /dev/null
+++ "b/Spring/SpringFramework/Spring-\345\256\236\346\210\230---IoC\345\256\271\345\231\250\347\232\204\344\276\235\350\265\226\346\263\250\345\205\245.md"
@@ -0,0 +1,593 @@
+初始化的过程,主要完成的工作是在容器中建立 BeanDefinition 数据映射,并没有看到容器对Bean依赖关系进行注入
+
+假设当前IoC容器已经载入用户定义的Bean信息,依赖注入主要发生在两个阶段
+- 正常情况下,由用户第一次向 IoC 容器索要 bean 时触发
+- 可在 `BeanDefinition` 信息中通过控制 `lazy-init` 属性
+![](https://upload-images.jianshu.io/upload_images/4685968-61440d86719257a3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+来让容器完成对Bean的预实例化;
+即在初始化的过程中就完成某些Bean的依赖注入的过程.
+
+# 1 getBean触发的依赖注入
+`BeanFactory` 是最原始的 ioc 容器，有以下方法 
+1.getBean
+2.判断是否有 Bean，containsBean
+3.判断是否单例 isSingleton
+
+BeanFactory 只对 ioc 容器最基本的行为作了定义,不关心 Bean 是怎样定义和加载的;
+如果我们想要知道一个工厂具体产生对象的过程,则要看这个接口的实现类.
+
+在基本的容器接口 `BeanFactory` 中;
+有一个 `getBean `接口,这个接口的实现就是触发依赖注入发生的地方.
+为进一步了解该依赖注入的过程,我们从 `DefaultListableBeanFactory `的基类` AbstractBeanFactory `入手去看看`getBean`的实现
+
+>这里是对 BeanFactory 接口的实现,比如getBean接口
+
+```
+	public <T> T getBean(String name, Class<T> requiredType, Object... args) throws BeansException {
+        // 这些 getBean 接口最终都是通过调用 doGetBean 实现
+		return doGetBean(name, requiredType, args, false);
+	}
+```
+![](https://upload-images.jianshu.io/upload_images/4685968-9fee95ac391dc178.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+```
+
+    // 实际取得 Bean 的地方,即触发依赖注入
+	@SuppressWarnings("unchecked")
+	protected <T> T doGetBean(final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
+			throws BeansException {
+
+		final String beanName = transformedBeanName(name);
+		Object bean;
+
+        //急切检查单例模式缓存手动注册的单例
+        //先从缓存中取得Bean,处理那些已经被创建过的单例Bean,这种Bean不要重复创建
+		Object sharedInstance = getSingleton(beanName);
+```
+
+顺道看看getSingleton()的实现
+![DefaultSingletonBeanRegistry # getSingleton(String beanName)](https://upload-images.jianshu.io/upload_images/4685968-2374a16031b2878b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![方法描述](https://upload-images.jianshu.io/upload_images/4685968-f34d70f4d10bb010.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+```
+protected Object getSingleton(String beanName, boolean allowEarlyReference) {
+		Object singletonObject = this.singletonObjects.get(beanName);
+		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
+			synchronized (this.singletonObjects) {
+				singletonObject = this.earlySingletonObjects.get(beanName);
+				if (singletonObject == null && allowEarlyReference) {
+					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
+					if (singletonFactory != null) {
+						singletonObject = singletonFactory.getObject();
+						this.earlySingletonObjects.put(beanName, singletonObject);
+						this.singletonFactories.remove(beanName);
+					}
+				}
+			}
+		}
+		return (singletonObject != NULL_OBJECT ? singletonObject : null);
+	}
+```
+回过头继续看工厂实现
+```
+		if (sharedInstance != null && args == null) {
+		    
+            ...
+
+            //以取得 FactoryBean 的相关处理及生产结果
+			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
+		}
+```
+来看看该 getObjectForBeanInstance 方法的实现,和FactoryBean有何关系呢
+![AbstractBeanFactory # getObjectForBeanInstance(Object beanInstance, String name,String beanName, RootBeanDefinition mbd)](https://upload-images.jianshu.io/upload_images/4685968-941dffd0a41e6ab8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+```
+	protected Object getObjectForBeanInstance(Object beanInstance, String name, String beanName, RootBeanDefinition mbd) {
+
+		// Don't let calling code try to dereference the factory if the bean isn't a factory.
+		if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
+			throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
+		}
+
+		// Now we have the bean instance, which may be a normal bean or a FactoryBean.
+		// If it's a FactoryBean, we use it to create a bean instance, unless the
+		// caller actually wants a reference to the factory.
+		if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
+			return beanInstance;
+		}
+
+		Object object = null;
+		if (mbd == null) {
+			object = getCachedObjectForFactoryBean(beanName);
+		}
+		if (object == null) {
+			// Return bean instance from factory.
+			FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
+			// Caches object obtained from FactoryBean if it is a singleton.
+			if (mbd == null && containsBeanDefinition(beanName)) {
+				mbd = getMergedLocalBeanDefinition(beanName);
+			}
+			boolean synthetic = (mbd != null && mbd.isSynthetic());
+			object = getObjectFromFactoryBean(factory, beanName, !synthetic);
+		}
+		return object;
+	}
+```
+继续回过头
+```
+
+		else {
+			// 若早已创建该 bean 实例则会失败进入到此
+			// 假设处在引用循环依赖中
+			if (isPrototypeCurrentlyInCreation(beanName)) {
+				throw new BeanCurrentlyInCreationException(beanName);
+			}
+
+            // 检查 bean 定义是否存在于该工厂
+			BeanFactory parentBeanFactory = getParentBeanFactory();
+			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
+				// 没有找到,即不存在 -> 检查父类
+				String nameToLookup = originalBeanName(name);
+				if (args != null) {
+					// 有更精确的参数 -> 委托给父类
+					return (T) parentBeanFactory.getBean(nameToLookup, args);
+				}
+				else {
+					// 无参数 -> 委托给标准的 getBean 方法
+					return parentBeanFactory.getBean(nameToLookup, requiredType);
+				}
+			}
+
+			if (!typeCheckOnly) {
+				markBeanAsCreated(beanName);
+			}
+
+			try {
+                //根据 Bean 名取得 BeanDefinition  
+				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
+				checkMergedBeanDefinition(mbd, beanName, args);
+
+				
+                //递归获得当前 Bean 依赖的所有Bean,确保它们的初始化
+				String[] dependsOn = mbd.getDependsOn();
+				if (dependsOn != null) {
+					for (String dep : dependsOn) {
+						if (isDependent(beanName, dep)) {
+							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
+									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
+						}
+						registerDependentBean(dep, beanName);
+						getBean(dep);
+					}
+				}
+  
+                // 创建 singleton bean 的地方
+				if (mbd.isSingleton()) {
+					sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
+						@Override
+						public Object getObject() throws BeansException {
+							try {
+                                // 调用 createBean 创建单例 bean实例
+								return createBean(beanName, mbd, args);
+							}
+							catch (BeansException ex) {
+								// Explicitly remove instance from singleton cache: It might have been put there
+								// eagerly by the creation process, to allow for circular reference resolution.
+								// Also remove any beans that received a temporary reference to the bean.
+								destroySingleton(beanName);
+								throw ex;
+							}
+						}
+					});
+					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
+				}
+
+                // 创建 prototype bean 的地方
+				else if (mbd.isPrototype()) {
+					// It's a prototype -> create a ne w instance.
+					Object prototypeInstance = null;
+					try {
+						beforePrototypeCreation(beanName);
+						prototypeInstance = createBean(beanName, mbd, args);
+					}
+					finally {
+						afterPrototypeCreation(beanName);
+					}
+					bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
+				}
+
+				else {
+					String scopeName = mbd.getScope();
+					final Scope scope = this.scopes.get(scopeName);
+					if (scope == null) {
+						throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
+					}
+					try {
+						Object scopedInstance = scope.get(beanName, new ObjectFactory<Object>() {
+							@Override
+							public Object getObject() throws BeansException {
+								beforePrototypeCreation(beanName);
+								try {
+									return createBean(beanName, mbd, args);
+								}
+								finally {
+									afterPrototypeCreation(beanName);
+								}
+							}
+						});
+						bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
+					}
+					catch (IllegalStateException ex) {
+						throw new BeanCreationException(beanName,
+								"Scope '" + scopeName + "' is not active for the current thread; consider " +
+								"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
+								ex);
+					}
+				}
+			}
+			catch (BeansException ex) {
+				cleanupAfterBeanCreationFailure(beanName);
+				throw ex;
+			}
+		}
+
+		// Check if required type matches the type of the actual bean instance.
+        // 这里对创建的Bean进行类型检查,如果没有问题,就返回这个新创建的Bean,这个Bean已经是包含了依赖关系的Bean
+		if (requiredType != null && bean != null && !requiredType.isAssignableFrom(bean.getClass())) {
+			try {
+				return getTypeConverter().convertIfNecessary(bean, requiredType);
+			}
+			catch (TypeMismatchException ex) {
+				if (logger.isDebugEnabled()) {
+					logger.debug("Failed to convert bean '" + name + "' to required type '" +
+							ClassUtils.getQualifiedName(requiredType) + "'", ex);
+				}
+				throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
+			}
+		}
+		return (T) bean;
+	}
+```
+
+依赖注入的发生是在容器中的 `BeanDefinition` 数据已经建立好的前提下进行的.
+
+
+# 2 依赖注入过程
+![依赖注入的过程](http://upload-images.jianshu.io/upload_images/4685968-474abfacb196d345.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)  
+getBean() 是依赖注入的起点;
+之后会调用`AbstractAutowireCapableBeanFactory`中的`createBean`来生产需要的Bean;
+还对Bean初始化进行了处理,比如实现了在BeanDefinition中的init-method属性定义,Bean后置处理器等.
+
+来看看createBean
+```
+	@Override 
+	protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
+	    ...
+		RootBeanDefinition mbdToUse = mbd;
+
+		// Make sure bean class is actually resolved at this point, and
+		// clone the bean definition in case of a dynamically resolved Class
+		// which cannot be stored in the shared merged bean definition.
+        // 判断需要创建的 Bean 是否可被实例化,该类是否可通过类加载器来载入
+		Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
+		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
+			mbdToUse = new RootBeanDefinition(mbd);
+			mbdToUse.setBeanClass(resolvedClass);
+		}
+
+		// Prepare method overrides.
+		try {
+			mbdToUse.prepareMethodOverrides();
+		}
+	    ...
+		try {			
+            //给 BeanPostProcessor一个机会返回的是一个Proxy代理而不是目标 bean 的实例
+			Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
+			if (bean != null) {
+				return bean;
+			}
+		}
+		...
+		try {
+			Object beanInstance = doCreateBean(beanName, mbdToUse, args);
+			...
+			return beanInstance;
+		}
+		...
+	}
+```
+### doCreateBean 源码解析
+接着到 doCreateBean 中去看看Bean是怎样生成的
+```
+    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
+		// Instantiate the bean.
+        //用来持有创建出来的Bean对象
+		BeanWrapper instanceWrapper = null;
+                //如果是单例,则先把缓存中的同名Bean清除
+		if (mbd.isSingleton()) {
+			instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
+		}
+                //这里是创建Bean的地方,由createBeanInstance来完成
+		if (instanceWrapper == null) {
+                       //根据指定bean使用对应的策略创建新的实例,如:工厂方法,构造函数自动注入,简单初始化
+			instanceWrapper =  createBeanInstance(beanName, mbd, args);
+		}
+		final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
+		Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
+
+		// Allow post-processors to modify the merged bean definition.
+		synchronized (mbd.postProcessingLock) {
+			if (!mbd.postProcessed) {
+				applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
+				mbd.postProcessed = true;
+			}
+		}
+
+		// Eagerly cache singletons to be able to resolve circular references
+		// even when triggered by lifecycle interfaces like BeanFactoryAware.
+                //是否需要提前曝光:单例&允许循环依赖&当前bean正在创建中,检测循环依赖
+		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
+				isSingletonCurrentlyInCreation(beanName));
+		if (earlySingletonExposure) {
+			if (logger.isDebugEnabled()) {
+				logger.debug("Eagerly caching bean '" + beanName +
+						"' to allow for resolving potential circular references");
+			}
+                       //为避免后期循环依赖,可以在bean初始化完成前将创建实例的ObjectFactory加入工厂
+			addSingletonFactory(beanName, new ObjectFactory<Object>() {
+				@Override
+				public Object getObject() throws BeansException {
+                                        //对bean再次依赖引用,主要应用SMartInstantialiationAware BeanPostProcessor,
+                                       //其中我们熟知的AOP就是在这里将advice动态织入bean中,若无则直接返回bean,不做任何处理
+					return getEarlyBeanReference(beanName, mbd, bean);
+				}
+			});
+		}
+
+		// Initialize the bean instance.
+                //这里是对Bean的初始化,依赖注入往往在这里发生,这个exposedObject在初始化处理完后悔返回作为依赖注入完成后的Bean
+		Object exposedObject = bean;
+		try {
+                       //对bean进行填充,将各个属性值注入,其中可能存在依赖于其他bean的属性,则会递归初始化依赖bean
+			populateBean(beanName, mbd, instanceWrapper);
+			if (exposedObject != null) {
+                                //调用初始化方法,比如init-method
+				exposedObject = initializeBean(beanName, exposedObject, mbd);
+			}
+		}
+		catch (Throwable ex) {
+			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
+				throw (BeanCreationException) ex;
+			}
+			else {
+				throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
+			}
+		}
+
+		if (earlySingletonExposure) {
+			Object earlySingletonReference = getSingleton(beanName, false);
+                        // earlySingletonReference 只有在检测到有循环依赖的情况下才会非空
+			if (earlySingletonReference != null) {
+				if (exposedObject == bean) {
+                                        //如果exposedObject 没有在初始化方法中被改变,也就是没有被增强
+					exposedObject = earlySingletonReference;
+				}
+				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
+					String[] dependentBeans = getDependentBeans(beanName);
+					Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
+					for (String dependentBean : dependentBeans) {
+                                               //检测依赖
+						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
+							actualDependentBeans.add(dependentBean);
+						}
+					}
+                                        //因为bean创建后其所依赖的bean一定是已经创建的,actualDependentBeans非空则表示当前bean创建后其依赖的bean却没有全部创建完,也就是说存在循环依赖
+					if (!actualDependentBeans.isEmpty()) {
+						throw new BeanCurrentlyInCreationException(beanName,
+								"Bean with name '" + beanName + "' has been injected into other beans [" +
+								StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
+								"] in its raw version as part of a circular reference, but has eventually been " +
+								"wrapped. This means that said other beans do not use the final version of the " +
+								"bean. This is often the result of over-eager type matching - consider using " +
+								"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
+					}
+				}
+			}
+		}
+	       // Register bean as disposable.
+		try {
+                        //根据scope注册bean
+			registerDisposableBeanIfNecessary(beanName, bean, mbd);
+		}
+		catch (BeanDefinitionValidationException ex) {
+			throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
+		}
+
+		return exposedObject;
+	}
+```
+
+## 小结
+依赖注入其实包括两个主要过程
+- 生产 Bean 所包含的Java对象
+- Bean 对象生成之后,把这些 Bean 对象的依赖关系设置好
+
+我们从上可以看到与依赖注入关系特别密切的方法
+ - `createBeanInstance` 
+生成Bean包含的Java对象
+- `populateBean`.
+处理对各种Bean对象的属性进行处理的过程（即依赖关系处理的过程）
+
+###  createBeanInstance 源码解析
+ ![方法描述](https://upload-images.jianshu.io/upload_images/4685968-3fe67a36af125c99.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+```
+	protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
+        // 确认需要创建的Bean实例的类可以实例化
+		Class<?> beanClass = resolveBeanClass(mbd, beanName);
+
+		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
+			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
+					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
+		}
+
+		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
+		if (instanceSupplier != null) {
+			return obtainFromSupplier(instanceSupplier, beanName);
+		}
+
+        //若工厂方法非空,则使用工厂方法策略对Bean进行实例化
+		if (mbd.getFactoryMethodName() != null)  {
+			return instantiateUsingFactoryMethod(beanName, mbd, args);
+		}
+
+		// Shortcut when re-creating the same bean...
+		boolean resolved = false;
+		boolean autowireNecessary = false;
+		if (args == null) {
+			synchronized (mbd.constructorArgumentLock) {
+                //一个类有多个构造函数,每个构造函数都有不同的参数,所以调用前需要先根据参数锁定构造函数或对应的工厂方法
+				if (mbd.resolvedConstructorOrFactoryMethod != null) {
+					resolved = true;
+					autowireNecessary = mbd.constructorArgumentsResolved;
+				}
+			}
+		}
+                //如果已经解析过则使用解析好的构造函数方法不需要再次锁定
+		if (resolved) {
+			if (autowireNecessary) {
+                                //构造函数自动注入
+				return autowireConstructor(beanName, mbd, null, null);
+			}
+			else {
+                               //使用默认构造函数构造
+				return instantiateBean(beanName, mbd);
+			}
+		}
+
+                 // 使用构造函数对Bean进行实例化
+		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
+		if (ctors != null ||
+				mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
+				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args))  {
+			return autowireConstructor(beanName, mbd, ctors, args);
+		}
+
+		// No special handling: simply use no-arg constructor.
+               //使用默认的构造函数对Bean进行实例化
+		return instantiateBean(beanName, mbd);
+	}
+
+        //最常见的实例化过程instantiateBean
+	protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
+                 //使用默认的实例化策略对Bean进行实例化,默认的实例化策略是
+       //CglibSubclassingInstantiationStrategy,也就是使用CGLIB实例化Bean 
+		try {
+			Object beanInstance;
+			final BeanFactory parent = this;
+			if (System.getSecurityManager() != null) {
+				beanInstance = AccessController.doPrivileged(new PrivilegedAction<Object>() {
+					@Override
+					public Object run() {
+						return getInstantiationStrategy().instantiate(mbd, beanName, parent);
+					}
+				}, getAccessControlContext());
+			}
+			else {
+				beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
+			}
+			BeanWrapper bw = new BeanWrapperImpl(beanInstance);
+			initBeanWrapper(bw);
+			return bw;
+		}
+		catch (Throwable ex) {
+			throw new BeanCreationException(
+					mbd.getResourceDescription(), beanName, "Instantiation of bean failed", ex);
+		}
+	}
+```
+这里使用了CGLIB对Bean进行实例化
+
+在Spring AOP中也使用CGLIB对Java的字节码进行增强.
+## 使用CGLIB来生成Bean对象
+需要看一下`SimpleInstantiationStrategy`类;
+它是 Spring 用来生成Bean对象的默认类;
+它提供了两种实例化Bean对象的方法
+- BeanUtils,使用Java原生的反射功能
+- CGLIB
+
+```
+public class SimpleInstantiationStrategy implements InstantiationStrategy {
+    @Override
+	public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
+		// Don't override the class with CGLIB if no overrides.
+		if (bd.getMethodOverrides().isEmpty()) {
+            //这里取得指定的构造器或者生成对象的工厂方法来对Bean进行实例化
+			Constructor<?> constructorToUse;
+			synchronized (bd.constructorArgumentLock) {
+				constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
+				if (constructorToUse == null) {
+					final Class<?> clazz = bd.getBeanClass();
+					if (clazz.isInterface()) {
+						throw new BeanInstantiationException(clazz, "Specified class is an interface");
+					}
+					try {
+						if (System.getSecurityManager() != null) {
+							constructorToUse = AccessController.doPrivileged(new PrivilegedExceptionAction<Constructor<?>>() {
+								@Override
+								public Constructor<?> run() throws Exception {
+									return clazz.getDeclaredConstructor((Class[]) null);
+								}
+							});
+						}
+						else {
+							constructorToUse =	clazz.getDeclaredConstructor((Class[]) null);
+						}
+						bd.resolvedConstructorOrFactoryMethod = constructorToUse;
+					}
+					catch (Throwable ex) {
+						throw new BeanInstantiationException(clazz, "No default constructor found", ex);
+					}
+				}
+			}
+            //通过BeanUtils进行实例化，这个BeanUtils的实例化通过Constructor来实例化Bean,在BeanUtils中可以看到具体的调用ctor.newInstance(args)
+			return BeanUtils.instantiateClass(constructorToUse);
+		}
+		else {		
+            // 使用CGLIB来实例化对象
+			return instantiateWithMethodInjection(bd, beanName, owner);
+		}
+	}
+}
+```
+
+# 4 Bean之间依赖关系的处理
+依赖关系处理的入口是前面提到的`populateBean`方法;
+由其涉及面广,仅简要介绍依赖关系处理的流程:
+在populateBean方法中
+- 首先取得在`BeanDefinition`中设置的`property`值,然后开始依赖注入的过程
+- 首先处理`Autowire`的注入,可以by Name/Type,之后对属性进行注入
+- 接着需要对Bean Reference进行解析
+在对ManageList、ManageSet、ManageMap等进行解析完之后，就已经为依赖注入准备好了条件;
+这是真正把Bean对象设置到它所依赖的另一个Bean属性中去的地方.
+- 依赖注入发生在BeanWrapper的setPropertyValues中
+具体的完成却是在BeanWrapper的子类BeanWrapperImpl中实现;
+它会完成Bean的属性值的注入，其中包括对Array的注入、对List等集合类以及对非集合类的域进行注入
+
+经过一系列的注入，这样就完成了对各种Bean属性的依赖注入过程
+
+在Bean的创建和对象依赖注入的过程中，需要依据`BeanDefinition`中的信息来递归地完成依赖注入
+
+从前面的几个递归过程中可以看到，这些递归都是以`getBean`为入口
+- 一个递归是在上下文中查找需要的Bean和创建Bean的递归调用
+- 另一个递归是在依赖注入时，通过递归调用容器的getBean方法，得到当前Bean的依赖Bean，同时也触发对依赖Bean的创建和注入。
+
+在对Bean的属性进行依赖注入时，解析也是一个递归的过程
+根据依赖关系，层层完成Bean的创建和注入，直到最后完成当前Bean的创建
+有了这个顶层Bean的创建和对它属性依赖注入的完成，意味着和当前Bean相关的整个依赖链的注入也就完成了
+
+在Bean创建和依赖注入完成后,在容器中建立起一系列依靠依赖关系联系起来的Bean，这个Bean已经不再是简单的Java对象了;
+该Bean系列以及Bean之间的依赖关系建立完成之后，通过IoC的相关接口方法，就可以非常方便地供上层应用使用了。
+
+# 5 lazy-init属性和预实例化
+在前面的`refresh`中，可看到调用了`finishBeanFactoryInitialization`来对配置了`lazy-init`的Bean进行处理
+其实在这个方法中,封装了对`lazy-init`属性的处理,实际的处理是在`DefaultListableBeanFactory`这个基本容器的`preInstantiateSingleton`方法中完成的
+该方法对单例Bean完成预实例化，这个预实例化的完成巧妙地委托给了容器来实现
+如果需要预实例化,那么就直接在这里采用`getBean`去触发依赖注入
diff --git "a/Spring/SpringFramework/SpringMVC\346\213\246\346\210\252\345\244\204\347\220\206\345\231\250.md" "b/Spring/SpringFramework/SpringMVC-\347\232\204\345\244\204\347\220\206\346\213\246\346\210\252\345\231\250.md"
similarity index 58%
rename from "Spring/SpringFramework/SpringMVC\346\213\246\346\210\252\345\244\204\347\220\206\345\231\250.md"
rename to "Spring/SpringFramework/SpringMVC-\347\232\204\345\244\204\347\220\206\346\213\246\346\210\252\345\231\250.md"
index 55df7dae0c..dea63a4a11 100644
--- "a/Spring/SpringFramework/SpringMVC\346\213\246\346\210\252\345\244\204\347\220\206\345\231\250.md"
+++ "b/Spring/SpringFramework/SpringMVC-\347\232\204\345\244\204\347\220\206\346\213\246\346\210\252\345\231\250.md"
@@ -1,21 +1,27 @@
-# 1 工作原理流程图
-![](https://img-blog.csdnimg.cn/img_convert/e0096bfcb38005ce8fe0e648a274777b.png)
-# 2 Spring Web MVC 的处理器拦截器
-- HandlerInterceptor
-![](https://img-blog.csdnimg.cn/5940620ea2f342e99890b8c551300f73.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-类似Servlet 开发中的过滤器Filter，用于对处理器进行预处理和后处理。
+#0 目录
+![](http://upload-images.jianshu.io/upload_images/4685968-0e96e02f95af818c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-14563a0520de7e61.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-48c508160847ed78.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-060046ab31685f80.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+ ![](http://upload-images.jianshu.io/upload_images/4685968-174b6d94bc2397c2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 1 Spring MVC拦截器流程图
+![](http://upload-images.jianshu.io/upload_images/4685968-ca4e9021f653c954.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#2 Spring Web MVC 的处理器拦截器
+![](https://upload-images.jianshu.io/upload_images/4685968-1b14ff00f1e4ab85.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
-HandlerInterceptor接口定义了如下方法：
-## preHandle
-该方法将在**请求处理之前进行调用**，只有当该方法返回true时，才会继续调用下一个`Interceptor`的`preHandle()`，如果已是最后一个`Interceptor`就会是调用当前请求的`Controller`
-## postHandle
+类似于Servlet 开发中的过滤器Filter，用于对处理器进行预处理和后处理
+
+HandlerInterceptor接口定义了三个方法
+##2.1  preHandle
+该方法将在**请求处理之前进行调用**,只有当该方法返回true时,才会继续调用下一个`Interceptor`的`preHandle()`,如果已是最后一个`Interceptor`就会是调用当前请求的`Controller`
+##2.2 postHandle
 该方法将在请求处理后,`DispatcherServlet`进行视图返回**渲染之前进行调用**,可以在这个方法中对`Controller`处理之后的`ModelAndView`对象进行操作(比如这里加入公用信息以便页面显示)
-## 2.3 afterCompletion
+##2.3 afterCompletion
 该方法也是需要当前对应的`Interceptor`的`preHandle`方法的返回值为`true`时才会执行,该方法将在整个请求结束之后,也就是在`DispatcherServlet` 渲染了对应的视图之后执行
 **用于资源清理**
 
-# 3 拦截器配置
-## 3.1 针对某种mapping拦截器配置
+#3 拦截器配置
+##3.1 针对某种mapping拦截器配置
 ```xml
  <bean  
    class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">  
@@ -29,7 +35,7 @@ HandlerInterceptor接口定义了如下方法：
 <bean id="handlerInterceptor1"class="springmvc.intercapter.HandlerInterceptor1"/>  
 <bean id="handlerInterceptor2"class="springmvc.intercapter.HandlerInterceptor2"/> 
 ```
-## 3.2 针对所有mapping配置全局拦截器
+##3.2 针对所有mapping配置全局拦截器
 ```xml
 <!--拦截器 -->  
 <mvc:interceptors>  
@@ -44,7 +50,7 @@ HandlerInterceptor接口定义了如下方法：
    </mvc:interceptor>  
 </mvc:interceptors>  
 ```
-# 4 实践
+#4 实践
  用户访问其他页面时,从Seesion中获取到用户，未登录则重定向到登录页面。
 ```java
 Public class LoginInterceptor implements HandlerInterceptor{   
@@ -66,4 +72,4 @@ Public class LoginInterceptor implements HandlerInterceptor{
           
         return false;  
     }  
-```
\ No newline at end of file
+```
diff --git "a/Spring/SpringFramework/Spring\347\232\204@Transaction\346\263\250\350\247\243\345\244\261\346\225\210\345\234\272\346\231\257.md" "b/Spring/SpringFramework/Spring\347\232\204@Transaction\346\263\250\350\247\243\345\244\261\346\225\210\345\234\272\346\231\257.md"
deleted file mode 100644
index 7039c477e7..0000000000
--- "a/Spring/SpringFramework/Spring\347\232\204@Transaction\346\263\250\350\247\243\345\244\261\346\225\210\345\234\272\346\231\257.md"
+++ /dev/null
@@ -1,184 +0,0 @@
-# 1 前言
-很多需要使用事务的场景，都只是在方法上直接添加个`@Transactional`注解
-![](https://img-blog.csdnimg.cn/20201113124942738.png#pic_center)
-**但是，你以为这真的够了吗？**
-
-事务如果未达到完美效果，在开发和测试阶段都难以被发现，因为你难以考虑到太多意外场景。但当业务数据量发展，就可能导致大量数据不一致的问题，就会造成前人栽树后人踩坑，需要大量人力排查解决问题和修复数据。
-
-# 2 如何确认Spring事务生效了？
-使用`@Transactional`一键开启声明式事务， 这就真的事务生效了？过于信任框架总有“意外惊喜”。来看如下案例
-### 领域层
-#### 实体
-![](https://img-blog.csdnimg.cn/20210206225925639.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-![](https://img-blog.csdnimg.cn/20210206230331301.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-#### 领域服务
-- `createUserError1`调用private方法
-![](https://img-blog.csdnimg.cn/20210207213945975.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- createUserPrivate，被`@Transactional`注解。当传入的用户名包含`test`则抛异常，让用户的创建操作失败
-![](https://img-blog.csdnimg.cn/20201116223137819.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-- getUserCount
-![](https://img-blog.csdnimg.cn/20201116223600764.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-### 用户接口层
-调用UserService#`createUserError1`
-![](https://img-blog.csdnimg.cn/20210206233642484.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- 测试结果
-即便用户名不合法，用户也能创建成功。刷新浏览器，多次发现有十几个的非法用户注册。
-
-## @Transactional生效原则
-### public方法
-除非特殊配置（比如使用AspectJ静态织入实现AOP），**@Transactional**必须定义在public方法才生效。
-
-因为Spring的AOP，private方法无法被代理到，自然也无法动态增强事务处理逻辑。
-
-那简单，把**createUserPrivate**方法改为public不就行了。
-但发现事务依旧**未生效**。
-
-### 必须通过代理过的类从外部调用目标方法
-要调用增强过的方法必然是调用代理后的对象。
-尝试修改UserService，注入一个self，然后再通过self实例调用标记有 **@Transactional** 注解的createUserPublic方法。设置断点可以看到，self是由Spring通过CGLIB方式增强过的类：
-![](https://img-blog.csdnimg.cn/20210207164512699.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-CGLIB通过继承实现代理类，private方法在子类不可见，所以无法进行事务增强。而this指针代表调用对象本身，Spring不可能注入this，所以通过this访问方法必然不是代理。
-把this改为self，这时即可验证事务生效：非法的用户注册操作可回滚。
-
-虽然在UserDomainService内部注入自己调用自己的createUserPublic可正确实现事务，但这不符常规。更合理的实现方式是，让Controller直接调用之前定义的UserService的createUserPublic方法。
-![](https://img-blog.csdnimg.cn/20210207165532655.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## this/self/Controller调用UserDomainService
-![](https://img-blog.csdnimg.cn/20201116225531888.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-- this自调用
-无法走到Spring代理类
-- 后两种
-调用的Spring注入的UserService，通过代理调用才有机会对createUserPublic方法进行动态增强。
-
-> 推荐开发时打开Debug日志以了解Spring事务实现的细节。
-比如JPA数据库访问，开启Debug日志：
-`logging.level.org.springframework.orm.jpa=DEBUG`
-
-开启日志后再比较下在UserService中this调用、Controller中通过注入的UserService Bean调用createUserPublic的区别。
-
-很明显，this调用因没走代理，事务没有在`createUserPublic`生效，只在Repository的`save`生效：
-
-```java
-// 在UserService中通过this调用public的createUserPublic
-[23:04:30.748] [http-nio-45678-exec-5] [DEBUG] [o.s.orm.jpa.JpaTransactionManager:370 ] - 
-Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: 
-PROPAGATION_REQUIRED,ISOLATION_DEFAULT
-
-[DEBUG] [o.s.orm.jpa.JpaTransactionManager       :370 ] - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
-//在Controller中通过注入的UserService Bean调用createUserPublic
-[10:10:47.750] [http-nio-45678-exec-6] [DEBUG] [o.s.orm.jpa.JpaTransactionManager       :370 ] - Creating new transaction with name [org.geekbang.time.commonmistakes.transaction.demo1.UserService.createUserPublic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
-```
-这种实现在Controller里处理异常显得繁琐，还不如直接把`createUserWrong2`加`@Transactional`注解，然后在`Controller`中直接调用该方法。
-这既能从外部（Controller中）调用UserService方法，方法又是public的能够被动态代理AOP增强。
-
-## 小结
-务必确认调用被`@Transactional`注解标记的方法被`public`修饰，并且是通过Spring注入的Bean进行调用。
-
-但有时因没有正确处理异常，导致事务即便生效也不一定能回滚。
-
-# 2 事务生效不代表能正确回滚
-AOP实现事务：使用try/catch包裹`@Transactional`注解的方法：
-- 当方法出现异常并满足**一定条件**，在catch里可设置事务回滚
-- 没有异常则直接提交事务
-
-## 一定条件
-只有异常传播出了标记了`@Transactional`注解的方法，事务才能回滚。
-
-Spring的TransactionAspectSupport#invokeWithinTransaction就是在处理事务。可见，只有捕获到异常后才能进行后续事务处理：
-![](https://img-blog.csdnimg.cn/20210207210542836.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-默认情况下，出现RuntimeException（非受检异常）或Error，Spring才会回滚事务。
-
-Spring的DefaultTransactionAttribute：
-- 受检异常一般是业务异常或类似另一种方法的返回值，出现这种异常可能业务还能完成，所以不会主动回滚
-- 而Error或RuntimeException代表非预期结果，应该回滚
-![](https://img-blog.csdnimg.cn/20210207213319889.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-# 事务无法正常回滚的各种惨案 
-## 异常无法传播出方法
-![](https://img-blog.csdnimg.cn/2021020721574291.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## 受检异常
-注册的同时会有一次文件读，若读文件失败，希望用户注册的DB操作回滚。因读文件抛的是受检异常，createUserError2传播出去的也是受检异常
-![](https://img-blog.csdnimg.cn/20210207222108619.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210207222202446.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-以上方法虽然避开了事务不生效的坑，但因异常处理不当，导致异常时依旧不回滚事务。
-
-## 修复回滚失败bug
-### 1 手动设置让当前事务处回滚态
-若希望自己捕获异常并处理，可手动设置让当前事务处回滚态
-![](https://img-blog.csdnimg.cn/20210207223516636.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-查看日志，事务确定回滚。
-> `Transactional code has requested rollback`：手动请求回滚。
-![](https://img-blog.csdnimg.cn/20201117212046958.png#pic_center)![](https://img-blog.csdnimg.cn/20201117212204857.png#pic_center)
-### 2 注解中声明，期望所有Exception都回滚事务
-- 突破**默认不回滚受检异常**的限制
-![](https://img-blog.csdnimg.cn/20201117214047682.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-- 查看日志，提示回滚：
-![](https://img-blog.csdnimg.cn/20201117214205401.png#pic_center)
-该案例有DB操作、IO操作，在IO操作问题时期望DB事务也回滚，以确保逻辑一致性。
-
-## 小结
-由于异常处理不正确，导致虽然事务生效，但出现异常时没回滚。
-Spring默认只对被`@Transactional`注解的方法出现`RuntimeException`和`Error`时回滚，所以若方法捕获了异常，就需要通过手写代码处理事务回滚。
-若希望Spring针对其他异常也可回滚，可相应配置`@Transactional`注解的`rollbackFor`和`noRollbackFor`属性覆盖Spring的默认配置。
-
-> 有些业务可能包含多次DB操作，不一定希望将两次操作作为一个事务，这时就需仔细考虑事务传播的配置。
-
-# 3 事务传播配置是否符合业务逻辑
-## 案例
-用户注册：会插入一个主用户到用户表，还会注册一个关联的子用户。期望将子用户注册的DB操作作为一个独立事务，即使失败也不影响注册主用户的流程。
-
-- UserService：创建主、子用户
-![](https://img-blog.csdnimg.cn/20201117215231616.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-- SubUserService：使子用户注册失败。期望子用户注册作为一个事务单独回滚而不影响注册主用户
-![](https://img-blog.csdnimg.cn/20201117215759910.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-- 启动调用后查看日志：事务回滚了
-![](https://img-blog.csdnimg.cn/20201117221137343.png#pic_center)
-不对呀！因为运行时异常逃出被`@Transactional`注解的`createUserWrong`，Spring当然会回滚事务。若期望主方法不回滚，应捕获子方法所抛的异常。
-### 修正方案
-把`subUserService#createSubUserWithExceptionError`包上catch，这样外层主方法`createUserError2`就不会出现异常
-![](https://img-blog.csdnimg.cn/20201117222417198.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-启动后查看日志注意到：
-- 对`createUserError2`开启异常处理
-- 子方法因出现运行时异常，标记当前事务为回滚
-- 主方法捕获异常并打印`create sub user error`
-- 主方法提交事务
-
-但Controller出现一个`UnexpectedRollbackException`，异常描述提示最终该事务回滚了且为静默回滚：因`createUserError2`本身并无异常，只不过提交后发现子方法已把当前事务设为回滚，无法完成提交。
-
-**明明无异常发生，但事务也不一定可提交**
-因为主方法注册主用户的逻辑和子方法注册子用户的逻辑为同一事务，子逻辑标记了事务需回滚，主逻辑自然也无法提交。
-那么修复方式就明确了，独立子逻辑的事务，即修正SubUserService注册子用户方法，为注解添加`propagation = Propagation.REQUIRES_NEW`设置`REQUIRES_NEW`事务传播策略。即执行到该方法时开启新事务，并挂起当前事务。
-创建一个新事务，若存在则暂停当前事务。类似同名的EJB事务属性。
-注：实际事务暂停不会对所有事务管理器外的开箱。 这特别适于`org.springframework.transaction.jta.JtaTransactionManager` ，这就需要`javax.transaction.TransactionManager`被提供给它（这是服务器特定的标准Java EE）
-![](https://img-blog.csdnimg.cn/20201117225930918.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-- 主方法无变化，依旧需捕获异常，防止异常外泄导致主事务回滚，重命名为`createUserRight`：
-![](https://img-blog.csdnimg.cn/20201117230327604.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-修正后再查看日志
-
-- Creating new transaction with name createUserRight
-对`createUserRight`开启主方法事务
-- createMainUser finish
-创建主用户完成
-- Suspending current transaction, creating new transaction with name createSubUserWithExceptionRight
-主事务挂起，开启新事务，即对`createSubUserWithExceptionRight`创建子用户的逻辑
-- Initiating transaction rollback
-子方法事务回滚
-- Resuming suspended transaction after completion of inner transaction
-子方法事务完成，继续主方法之前挂起的事务
-- create sub user error:invalid status
-主方法捕获到了子方法的异常
-- Committing JPA transaction on EntityManager
-主方法的事务提交了，随后我们在Controller里没看到静默回滚异常
-
-## 小结
-若方法涉及多次DB操作，并希望将它们作为独立事务进行提交或回滚，即需考虑细化配置事务传播方式，即配置`@Transactional`注解的`Propagation`属性。
-
-#  4 总结
-若要针对private方法启用事务，动态代理方式的AOP不可行，需要使用静态织入方式的AOP，也就是在编译期间织入事务增强代码，可以配置Spring框架使用AspectJ来实现AOP。
\ No newline at end of file
diff --git "a/Spring/SpringFramework/Spring\347\232\204IoC\343\200\201AOP.md" "b/Spring/SpringFramework/Spring\347\232\204IoC\343\200\201AOP.md"
deleted file mode 100644
index 27a2795c12..0000000000
--- "a/Spring/SpringFramework/Spring\347\232\204IoC\343\200\201AOP.md"
+++ /dev/null
@@ -1,206 +0,0 @@
-Spring AOP通过CGlib、JDK动态代理实现运行期的动态方法增强，以抽取出业务无关代码，使其不与业务代码耦合，从而降低系统耦合性，提高代码可重用性和开发效率。
-所以AOP广泛应用在日志记录、监控管理、性能统计、异常处理、权限管理、统一认证等方面。
-
-# 单例Bean如何注入Prototype的Bean？
-
-要为单例Bean注入Prototype Bean，不只是修改Scope属性。由于单例Bean在容器启动时就会完成一次性初始化。所以最简单的，把Prototype Bean设置为通过代理注入，即把proxyMode属性设为**TARGET_CLASS**。
-
-比如抽象类LearnService，可认为是有状态的，如果LearnService是单例的话，那必然会OOM
-![](https://img-blog.csdnimg.cn/20210512164658255.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-实际开发时，很多人不假思索把LearnGo和LearnJava类加上 **@Service**，让它们成为Bean，也没有考虑到父类其实有状态：
-```java
-@Service
-@Slf4j
-public class LearnJava extends LearnService {
-
-    @Override
-    public void learn() {
-        super.learn();
-        log.info("java");
-    }
-}
-
-@Service
-@Slf4j
-public class LearnGo extends LearnService {
-
-    @Override
-    public void learn() {
-        super.learn();
-        log.info("go");
-    }
-}
-```
-相信大多数同学都认为 **@Service** 的意义就在于Spring能通过 **@Autowired** 自动注入对象，就比如可以直接使用注入的List获取到LearnJava和LearnGo，而没想过类的生命周期：
-```java
-@Autowired
-List<LearnService> learnServiceList;
-
-@GetMapping("test")
-public void test() {
-    log.info("====================");
-    learnServiceList.forEach(LearnService::learn);
-}
-```
-- 当年开发父类的人将父类设计为有状态的，但不关心子类怎么使用父类的
-- 而开发子类的同学，没多想就直接添加 **@Service**，让类成为Bean，再通过 **@Autowired**注入该服务
-这样设置后，有状态的父类就可能产生内存泄漏或线程安全问题。
-
-## 最佳实践
-
-在给类添加 **@Service**把类型交由容器管理前，首先考虑类是否有状态，再为Bean设置合适Scope。
-比如该案例，就为我们的两个类添加 **@Scope**即可，设为**PROTOTYPE**生命周期：
-![](https://img-blog.csdnimg.cn/20210512170638576.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-但这样还是会内存泄漏，说明修改无用！
-
-观察日志可得，第一次调用和第二次调用时，SayBye对象都是4c0bfe9e，SayHello也是一样问题。从日志第7到10行还可以看到，第二次调用后List的元素个数变为了2，说明父类SayService维护的List在不断增长，不断调用必然出现OOM：
-```java
-[17:12:45.798] [http-nio-30666-exec-1] [INFO ] [c.j.s.beansingletonandorder.LearnService:26  ] - I'm com.javaedge.springpart1.beansingletonandorder.LearnGo@4a5fe6a2 size:1
-[17:12:45.798] [http-nio-30666-exec-1] [INFO ] [c.j.s.beansingletonandorder.LearnGo:20  ] - go
-[17:12:45.839] [http-nio-30666-exec-1] [INFO ] [c.j.s.beansingletonandorder.LearnService:26  ] - I'm com.javaedge.springpart1.beansingletonandorder.LearnJava@6cb46b0 size:1
-[17:12:45.840] [http-nio-30666-exec-1] [INFO ] [c.j.s.beansingletonandorder.LearnJava:17  ] - java
-[17:12:57.380] [http-nio-30666-exec-2] [INFO ] [c.j.s.b.BeanSingletonAndOrderController:25  ] - ====================
-[17:12:57.416] [http-nio-30666-exec-2] [INFO ] [c.j.s.beansingletonandorder.LearnService:26  ] - I'm com.javaedge.springpart1.beansingletonandorder.LearnGo@b859c00 size:2
-[17:12:57.416] [http-nio-30666-exec-2] [INFO ] [c.j.s.beansingletonandorder.LearnGo:20  ] - go
-[17:12:57.452] [http-nio-30666-exec-2] [INFO ] [c.j.s.beansingletonandorder.LearnService:26  ] - I'm com.javaedge.springpart1.beansingletonandorder.LearnJava@5426300 size:2
-[17:12:57.452] [http-nio-30666-exec-2] [INFO ] [c.j.s.beansingletonandorder.LearnJava:17  ] - java
-```
-所以，问题就是：
-### 单例Bean如何注入Prototype Bean？
-Controller标记了 **@RestController**
-**@RestController** = **@Controller** + **@ResponseBody**，又因为 **@Controller**标记了 **@Component**元注解，所以 **@RestController**也是一个Spring Bean。
-
-Bean默认是单例的，所以单例的Controller注入的Service也是一次性创建的，即使Service本身标识了**prototype**的范围，也不会起作用。
-
-修复方案就是让Service以代理方式注入。这样虽然Controller是单例的，但每次都能从代理获取Service。这样一来，prototype范围的配置才能真正生效：
-![](https://img-blog.csdnimg.cn/20210512172431727.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-调试发现，注入的Service都是Spring生成的代理类：
-![](https://img-blog.csdnimg.cn/20210512172721106.png)
-
-如果不希望走代理，还有一种方案，每次直接从ApplicationContext中获取Bean：
-![](https://img-blog.csdnimg.cn/20210512172928970.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-这里Spring注入的LearnService的List，第一个元素是LearnGo，第二个元素是LearnJava。但我们更希望的是先执行Java再执行Go，所以注入一个List Bean时， 还要能控制Bean的顺序。
-
-一般来说，顺序如何都无所谓，但对AOP，顺序可能会引发致命问题。
-# 监控切面顺序导致的Spring事务失效
-通过AOP实现一个整合日志记录、异常处理和方法耗时打点为一体的统一切面。但后来发现，使用了AOP切面后，这个应用的声明式事务处理居然都是无效的。
-
-
-现在分析AOP实现的监控组件和事务失效有什么关系，以及通过AOP实现监控组件是否还有其他坑。
-
-先定义一个自定义注解Metrics，打上该注解的方法可以实现各种监控功能：
-![](https://img-blog.csdnimg.cn/20210512190554206.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-然后，实现一个切面完成Metrics注解提供的功能。这个切面可以实现标记了@RestController注解的Web控制器的自动切入，如果还需要对更多Bean进行切入的话，再自行标记@Metrics注解。
-
-测试MetricsAspect的功能。
-
-Service中实现创建用户的时候做了事务处理，当用户名包含test字样时会抛出异常，导致事务回滚。为Service中的createUser添加@Metrics注解。
-还可以手动为类或方法添加@Metrics注解，实现Controller之外的其他组件的自动监控。
-
-```java
-@Slf4j
-@RestController //自动进行监控
-@RequestMapping("metricstest")
-public class MetricsController {
-    @Autowired
-    private UserService userService;
-    @GetMapping("transaction")
-    public int transaction(@RequestParam("name") String name) {
-        try {
-            userService.createUser(new UserEntity(name));
-        } catch (Exception ex) {
-            log.error("create user failed because {}", ex.getMessage());
-        }
-        return userService.getUserCount(name);
-    }
-}
-
-@Service
-@Slf4j
-public class UserService {
-    @Autowired
-    private UserRepository userRepository;
-    @Transactional
-    @Metrics //启用方法监控
-    public void createUser(UserEntity entity) {
-        userRepository.save(entity);
-        if (entity.getName().contains("test"))
-            throw new RuntimeException("invalid username!");
-    }
-
-    public int getUserCount(String name) {
-        return userRepository.findByName(name).size();
-    }
-}
-
-@Repository
-public interface UserRepository extends JpaRepository<UserEntity, Long> {
-    List<UserEntity> findByName(String name);
-}
-```
-
-使用用户名“test”测试一下注册功能，自行测试可以观察到日志中打出了整个调用的出入参、方法耗时：
-
-但之后性能分析觉得默认的 **@Metrics**配置不太好，优化点：
-- Controller的自动打点，不要自动记录入参和出参日志，避免日志量过大
-- Service中的方法，最好可以自动捕获异常
-
-优化调整：
-- MetricsController手动添加 **@Metrics**，设置logParameters和logReturn为false
-![](https://img-blog.csdnimg.cn/20210513134317508.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-- Service中的createUser方法的@Metrics注解，设置了ignoreException属性为true
-![](https://img-blog.csdnimg.cn/20210513134421950.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-可是实际上线发现日志量并没有减少，而且事务回滚还失效了，从输出看到最后查询到了名为test的用户。
-
-执行Service的createUser方法时，Spring 的 TransactionAspectSupport并没有捕获到异常，所以自然无法回滚事务。因为异常被MetricsAspect吞了。
-
-切面本身是一个Bean，Spring对不同切面增强的执行顺序是由Bean优先级决定的，具体规则是：
-- 入操作（Around（连接点执行前）、Before），切面优先级越高，越先执行
-一个切面的入操作执行完，才轮到下一切面，所有切面入操作执行完，才开始执行连接点（方法）
-- 出操作（Around（连接点执行后）、After、AfterReturning、AfterThrowing）
-切面优先级越低，越先执行。一个切面的出操作执行完，才轮到下一切面，直到返回到调用点。
-- 同一切面的Around比After、Before先执行
-
-对于Bean可以通过 **@Order** 设置优先级：默认情况下Bean的优先级为最低优先级，其值是Integer的最大值。值越大优先级越低。
-![](https://img-blog.csdnimg.cn/20210513142450888.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210513142719486.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-## 通知
-### 执行顺序
-新建一个TestAspectWithOrder10切面，通过 **@Order**注解设置优先级为10，做简单的日志输出，切点是TestController所有方法；
-![](https://img-blog.csdnimg.cn/20210513143248251.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-然后再定义一个类似的TestAspectWithOrder20切面，设置优先级为20：
-![](https://img-blog.csdnimg.cn/20210513143327567.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-调用TestController的方法，观察日志输出：
-```bash
-TestAspectWithOrder10 @Around before
-TestAspectWithOrder10 @Before
-TestAspectWithOrder20 @Around before
-TestAspectWithOrder20 @Before
-TestAspectWithOrder20 @Around after
-TestAspectWithOrder20 @After
-TestAspectWithOrder10 @Around after
-TestAspectWithOrder10 @After
-```
-Spring的事务管理同样基于AOP，默认，优先级最低，会先执行出操作，但自定义切面MetricsAspect默认情况下也是最低优先级。
-这时就会产生问题：若出操作先执行捕获了异常，则Spring事务就会因为无法catch异常而无法回滚。
-
-所以要指定MetricsAspect的优先级，可设置为最高优先级，即最先执行入操作最后执行出操作：
-![](https://img-blog.csdnimg.cn/20210513150407535.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-切入的连接点是方法，注解定义在类上是无法直接从方法上获取到注解的。所以要改为优先从方法获取，若方法上获取不到再从类获取，若还是获取不到则使用默认注解：
-```java
-Metrics metrics = signature.getMethod().getAnnotation(Metrics.class);
-if (metrics == null) {
-    metrics = signature.getMethod().getDeclaringClass().getAnnotation(Metrics.class);
-}
-```
-修正完后，事务就可以正常回滚了，并且Controller的监控日志也不再出现入参、出参。
-
-监控平台如果想生产可用，需修改：
-- 日志打点，改为对接Metrics监控系统
-- 各种监控开关，从注解属性获取改为通过配置中心实时获取
\ No newline at end of file
diff --git "a/Spring/SpringFramework/Spring\347\274\226\347\250\213\345\274\217\344\272\213\345\212\241.md" "b/Spring/SpringFramework/Spring\347\274\226\347\250\213\345\274\217\344\272\213\345\212\241.md"
deleted file mode 100644
index a26d65f407..0000000000
--- "a/Spring/SpringFramework/Spring\347\274\226\347\250\213\345\274\217\344\272\213\345\212\241.md"
+++ /dev/null
@@ -1,218 +0,0 @@
-为了更细粒度的事务划分，Spring提供如下两种方式的编程式事务管理：
-# 1 PlatformTransactionManager
-你也可以使用 org.springframework.transaction.PlatformTransactionManager 来直接管理你的事务。只需通过bean的引用，简单的把你在使用的PlatformTransactionManager 传递给你的bean。 然后，使用TransactionDefinition和TransactionStatus对象， 你可以启动，回滚和提交事务。
-
-```java
-DefaultTransactionDefinition def = new DefaultTransactionDefinition();
-// explicitly setting the transaction name is something that can only be done programmatically
-def.setName("SomeTxName");
-def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
-
-TransactionStatus status = txManager.getTransaction(def);
-try {
-  // 执行业务逻辑
-}
-catch (MyException ex) {
-  txManager.rollback(status);
-  throw ex;
-}
-txManager.commit(status);
-```
-
-# 2 TransactionTemplate
-PlatformTransactionManager中部分代码是可以重用的，所以spring对其进行了优化，采用模板方法模式就其进行封装，主要省去了提交或者回滚事务的代码。
-
-若你选择编程式事务管理，Spring推荐使用 TransactionTemplate。 类似使用JTA的 UserTransaction API （除了异常处理的部分稍微简单点）。
-## 2.1 简介
-TransactionTemplate 采用与Spring中别的模板同样的方法，如 JdbcTemplate 。
-使用回调机制，将应用代码从样板式的资源获取和释放代码中解放。
-
-同一个事务管理的代码调用逻辑中，每次执行 SQL，都是基于同一连接，所有连接都是从一个公共地方TransactionSynchronizationManager获取。
-所以获取连接操作不应该在 ORM 层框架，而是由 Spring 维护。
-### JdbcTemplate源码
-![](https://img-blog.csdnimg.cn/20210704183829725.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-![](https://img-blog.csdnimg.cn/20210704183726428.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-```java
-@Override
-@Nullable
-public <T> T execute(ConnectionCallback<T> action) throws DataAccessException {
-	Assert.notNull(action, "Callback object must not be null");
-
-	Connection con = DataSourceUtils.getConnection(obtainDataSource());
-	try {
-		// Create close-suppressing Connection proxy, also preparing returned Statements.
-		Connection conToUse = createConnectionProxy(con);
-		return action.doInConnection(conToUse);
-	}
-	catch (SQLException ex) {
-		// Release Connection early, to avoid potential connection pool deadlock
-		// in the case when the exception translator hasn't been initialized yet.
-		String sql = getSql(action);
-		DataSourceUtils.releaseConnection(con, getDataSource());
-		con = null;
-		throw translateException("ConnectionCallback", sql, ex);
-	}
-	finally {
-		DataSourceUtils.releaseConnection(con, getDataSource());
-	}
-}
-```
-
-从给定的数据源获取连接。 知道绑定到当前线程的相应连接，例如在使用DataSourceTransactionManager 。 如果事务同步处于活动状态，例如在JTA事务中运行时，则将连接绑定到线程。
-![](https://img-blog.csdnimg.cn/20210704182450795.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-mybatis 中的各种 Mapper，其实底层就是通过使用 sqlSession 执行的。
-而不管是 jdbcTemplate 还是 mybatis，获取连接都是通过 Spring的TransactionSynchronizationManager：
-![](https://img-blog.csdnimg.cn/20210704201230242.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-#### 为何使用ThreadLocal存储资源？
-若一个线程请求事务处理过程还在正常进行，但同时另一个线程并发也请求了同一事务的处理方法而且出现了异常要回滚了，若两个线程共用同一个数据库连接，就会导致回滚时影响原来正常执行的线程！所以连接对象是使用 ThreadLocal 存储的。就是为了避免多线程共用同一个连接，导致回滚时出现数据错乱。
-![](https://img-blog.csdnimg.cn/20210704202442776.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-
-
-> 使用 TransactionTemplate 会增加你的代码与Spring的事务框架和API间的耦合。所以是否使用编程式事务管理由你自己决定。
-
-应用代码必须在一个事务性的上下文中执行，这样就会像这样一样显式的使用TransactionTemplate。作为应用程序员， 会写一个 TransactionCallback 的实现， (通常会用匿名类实现 )这样的实现会包含所以你需要在该事务上下文中执行的代码。
-然后你会把一个你自己实现TransactionCallback的实例传递给TransactionTemplate暴露的execute 方法。
-```java
-public class SimpleService implements Service {
-
-  // single TransactionTemplate shared amongst all methods in this instance
-  private final TransactionTemplate transactionTemplate;
-
-  // use constructor-injection to supply the PlatformTransactionManager
-  public SimpleService(PlatformTransactionManager transactionManager) {
-    Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
-    this.transactionTemplate = new TransactionTemplate(transactionManager);
-  }
-
-  public Object someServiceMethod() {
-    return transactionTemplate.execute(new TransactionCallback() {
-
-      // the code in this method executes in a transactional context
-      public Object doInTransaction(TransactionStatus status) {
-        updateOperation1();
-        return resultOfUpdateOperation2();
-      }
-    });
-  }
-}
-```
-如果不需要返回值，更方便的是创建一个 TransactionCallbackWithoutResult 匿名类
-```java
-transactionTemplate.execute(new TransactionCallbackWithoutResult() {
-  @Override
-  protected void doInTransactionWithoutResult(TransactionStatus status) {
-    updateOperation1();
-    updateOperation2();
-  }
-});
-```
-
-回调方法内的代码可以通过调用 TransactionStatus 对象的 setRollbackOnly() 方法来回滚事务。
-
-```java
-transactionTemplate.execute(new TransactionCallbackWithoutResult() {
-
-  protected void doInTransactionWithoutResult(TransactionStatus status) {
-    try {
-      updateOperation1();
-      updateOperation2();
-    } catch (SomeBusinessExeption ex) {
-      status.setRollbackOnly();
-    }
-  }
-});
-```
-
-## 2.2 指定事务设置
-诸如传播模式、隔离等级、超时等等的事务设置都可以在TransactionTemplate中或者通过配置或者编程式地实现。 
-TransactionTemplate实例默认继承了默认事务设置。 下面有个编程式的为一个特定的TransactionTemplate定制事务设置的例子。
-```java
-public class SimpleService implements Service {
-
-  private final TransactionTemplate transactionTemplate;
-
-  public SimpleService(PlatformTransactionManager transactionManager) {
-    Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
-    this.transactionTemplate = new TransactionTemplate(transactionManager);
-
-    // the transaction settings can be set here explicitly if so desired
-    this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
-    this.transactionTemplate.setTimeout(30); // 30 seconds
-    // and so forth...
-  }
-}
-```
-
-使用Spring XML配置来定制TransactionTemplate的事务属性。 sharedTransactionTemplate 可以被注入到所有需要的服务中去。
-```xml
-<bean id="sharedTransactionTemplate"
-    class="org.springframework.transaction.support.TransactionTemplate">
-  <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
-  <property name="timeout" value="30"/>
-</bean>"
-```
-TransactionTemplate 类的实例是线程安全的，任何状态都不会被保存。 TransactionTemplate 实例的确会维护配置状态，所以当一些类选择共享一个单独的 TransactionTemplate实例时，如果一个类需要使用不同配置的TransactionTemplate(比如，不同的隔离等级)， 那就需要创建和使用两个不同的TransactionTemplate。
-## 2.3 案例
-
-```java
-@Test
-public void test1() throws Exception {
-    //定义一个数据源
-    org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
-    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
-    dataSource.setUrl("jdbc:mysql://localhost:3306/mydb?characterEncoding=UTF-8");
-    dataSource.setUsername("root");
-    dataSource.setPassword("root123");
-    dataSource.setInitialSize(5);
-    //定义一个JdbcTemplate，用来方便执行数据库增删改查
-    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
-    //1.定义事务管理器，给其指定一个数据源（可以把事务管理器想象为一个人，这个人来负责事务的控制操作）
-    PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
-    //2.定义事务属性：TransactionDefinition，TransactionDefinition可以用来配置事务的属性信息，比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
-    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
-    transactionDefinition.setTimeout(10);//如：设置超时时间10s
-    //3.创建TransactionTemplate对象
-    TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager, transactionDefinition);
-    /**
-     * 4.通过TransactionTemplate提供的方法执行业务操作
-     * 主要有2个方法：
-     * （1）.executeWithoutResult(Consumer action)：没有返回值的，需传递一个Consumer对象，在accept方法中做业务操作
-     * （2）. T execute(TransactionCallback action)：有返回值的，需要传递一个TransactionCallback对象，在doInTransaction方法中做业务操作
-     * 调用execute方法或者executeWithoutResult方法执行完毕之后，事务管理器会自动提交事务或者回滚事务。
-     * 那么什么时候事务会回滚，有2种方式：
-     * （1）transactionStatus.setRollbackOnly();将事务状态标注为回滚状态
-     * （2）execute方法或者executeWithoutResult方法内部抛出异常
-     * 什么时候事务会提交？
-     * 方法没有异常 && 未调用过transactionStatus.setRollbackOnly();
-     */
-    transactionTemplate.executeWithoutResult(new Consumer() {
-        @Override
-        public void accept(TransactionStatus transactionStatus) {
-            jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-1");
-            jdbcTemplate.update("insert into t_user (name) values (?)", "transactionTemplate-2");
-
-        }
-    });
-    System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from t_user"));
-}
-
-output:
-after:[{id=1, name=transactionTemplate-1}, {id=2, name=transactionTemplate-2}]
-```
-### executeWithoutResult：无返回值场景
-executeWithoutResult(Consumer action)：没有返回值的，需传递一个Consumer对象，在accept方法中做业务操作
-![](https://img-blog.csdnimg.cn/baaf82eaee1949ce852206771682c3dc.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-```java
-transactionTemplate.executeWithoutResult(new Consumer() {
-    @Override
-    public void accept(TransactionStatus transactionStatus) {
-        //执行业务操作
-    }
-});
-```
-### execute：有返回值场景
-T execute(TransactionCallback action)：有返回值的，需要传递一个TransactionCallback对象，在doInTransaction方法中做业务操作
\ No newline at end of file
diff --git "a/Spring/SpringFramework/\344\270\272\344\273\200\344\271\210private\346\226\271\346\263\225\345\212\240\344\272\206@Transactional\357\274\214\344\272\213\345\212\241\344\271\237\346\262\241\346\234\211\347\224\237\346\225\210\357\274\237.md" "b/Spring/SpringFramework/\344\270\272\344\273\200\344\271\210private\346\226\271\346\263\225\345\212\240\344\272\206@Transactional\357\274\214\344\272\213\345\212\241\344\271\237\346\262\241\346\234\211\347\224\237\346\225\210\357\274\237.md"
deleted file mode 100644
index 72d83a972c..0000000000
--- "a/Spring/SpringFramework/\344\270\272\344\273\200\344\271\210private\346\226\271\346\263\225\345\212\240\344\272\206@Transactional\357\274\214\344\272\213\345\212\241\344\271\237\346\262\241\346\234\211\347\224\237\346\225\210\357\274\237.md"
+++ /dev/null
@@ -1,75 +0,0 @@
-现在产品期望用户创建和保存逻辑分离：把User实例的创建和保存逻辑拆到两个方法分别进行。
-然后，把事务的注解 **@Transactional** 加在保存数据库的方法上。
-![](https://img-blog.csdnimg.cn/2f29be1be44f4029a8ed70152d34de03.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-执行程序，异常正常抛出
-![](https://img-blog.csdnimg.cn/5952728511b443f38eb79cb3aae0f0b9.png)
-事务未回滚
-![](https://img-blog.csdnimg.cn/4c7e43e4f9944f1fac26d6a7ff83342e.png)
-# 源码解析
- debug：
-![](https://img-blog.csdnimg.cn/dcfa7c2e44ca4048b5f889b4ab14a542.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-前一段是 Spring 创建 Bean 的过程。当 Bean 初始化之后，开始尝试代理操作，这是从如下方法开始处理的：
-### AbstractAutoProxyCreator#postProcessAfterInitialization
-```java
-public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
-   if (bean != null) {
-      Object cacheKey = getCacheKey(bean.getClass(), beanName);
-      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
-         return wrapIfNecessary(bean, beanName, cacheKey);
-      }
-   }
-   return bean;
-}
-```
-继续 debug，直到
-### AopUtils#canApply
-针对切面定义里的条件，确定这个方法是否可被应用创建成代理。
-有段 `methodMatcher.matches(method, targetClass)` 判断这个方法是否符合这样的条件：
-```java
-public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
-   // ...
-   for (Class<?> clazz : classes) {
-      Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
-      for (Method method : methods) {
-         if (introductionAwareMethodMatcher != null ?
-               introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
-               methodMatcher.matches(method, targetClass)) {
-            return true;
-         }
-      }
-   }
-   return false;
-}
-```
-![](https://img-blog.csdnimg.cn/f6729281ab6443cca152a37c2b1dbb76.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-从 matches() 调用到
-##### AbstractFallbackTransactionAttributeSource#getTransactionAttribute
-获取注解中的事务属性，根据属性确定事务的策略。
-![](https://img-blog.csdnimg.cn/7996b41bf8054f28be00f67fd5473cdc.png)
-接着调用到 
-#### computeTransactionAttribute
-根据方法和类的类型确定是否返回事务属性：
-![](https://img-blog.csdnimg.cn/ab5481cb131f41b4956021c582b4a78a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-当上图中条件判断结果为 true，则返回 null，表明该方法不会被代理，从而导致事务注解不会生效。
-
-那到底是不是 true 呢？
-##### 条件1：allowPublicMethodsOnly()
-AnnotationTransactionAttributeSource#publicMethodsOnly属性值
-![](https://img-blog.csdnimg.cn/108a1106eb5946ecab4e182bef6de966.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-publicMethodsOnly 是通过 AnnotationTransactionAttributeSource 的构造方法初始化的，默认为 true。
-![](https://img-blog.csdnimg.cn/591bfd6571814ed29b0b610021724da0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-##### 条件2：Modifier.isPublic()
-根据传入的 `method.getModifiers()` 获取方法的修饰符，该修饰符是 java.lang.reflect.Modifier 的静态属性，对应的几类修饰符分别是：
-- PUBLIC: 1
-- PRIVATE: 2
-- PROTECTED: 4
-
-这里做了一个位运算，只有当传入的方法修饰符是 public 类型的时候，才返回 true
-![](https://img-blog.csdnimg.cn/adafc6c4f16d44478625252d9ac12006.png)
-综上两个条件，只有当注解为事务方法为 public 才会被 Spring 处理。
-# 修正
-只需将修饰符从 private 改成 public，其实该问题 IDEA 也会告警，一般都会避免。
-![](https://img-blog.csdnimg.cn/ee7ded3ee775473a8094d28a1d2d0e08.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-调用这个加了事务注解的方法，必须是调用被 Spring AOP 代理过的方法：不能通过类的内部调用或通过 this 调用。
-所以我们的案例的UserService，它Autowired了自身（UserService）的一个实例来完成代理方法的调用。
\ No newline at end of file
diff --git "a/Spring/SpringFramework/\344\270\272\344\273\200\344\271\210\345\212\240\344\272\206@WebFilter\346\263\250\350\247\243\357\274\214Spring\345\215\264\346\262\241\346\234\211\347\273\231\346\210\221\350\207\252\345\212\250\346\263\250\345\205\245\350\257\245\350\277\207\346\273\244\345\231\250?.md" "b/Spring/SpringFramework/\344\270\272\344\273\200\344\271\210\345\212\240\344\272\206@WebFilter\346\263\250\350\247\243\357\274\214Spring\345\215\264\346\262\241\346\234\211\347\273\231\346\210\221\350\207\252\345\212\250\346\263\250\345\205\245\350\257\245\350\277\207\346\273\244\345\231\250?.md"
deleted file mode 100644
index c0f3fcec3b..0000000000
--- "a/Spring/SpringFramework/\344\270\272\344\273\200\344\271\210\345\212\240\344\272\206@WebFilter\346\263\250\350\247\243\357\274\214Spring\345\215\264\346\262\241\346\234\211\347\273\231\346\210\221\350\207\252\345\212\250\346\263\250\345\205\245\350\257\245\350\277\207\346\273\244\345\231\250?.md"
+++ /dev/null
@@ -1,82 +0,0 @@
-在 Spring 编程中，主要配合如下注解构建过滤器：
--  @ServletComponentScan
-- @WebFilter
-
-那这看起来只是用上这俩注解就能继续摸鱼了呀。但上了生产后，还是能遇到花式问题：
-- 工作不起来
-- 顺序不对
-- 执行多次等
-
-大多因为想当然觉得使用简单，没有上心。还是有必要精通过滤器执行的流程和原理。
-# @WebFilter 过滤器无法被自动注入
-为统计接口耗时，实现一个过滤器：
-![](https://img-blog.csdnimg.cn/e1f8f6ce7b4c47cab322388c54309807.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-该过滤器标记了 **@WebFilter**。所以启动程序加上扫描注解 **@ServletComponentScan** 让其生效：
-![](https://img-blog.csdnimg.cn/6aef56e0ecf742aba96c9a3a83353992.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-然后，提供一个 UserController：
-![](https://img-blog.csdnimg.cn/6a4d432b3f9b4232a8abf44e8c35a697.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-发现应用启动失败
-![](https://img-blog.csdnimg.cn/7d5b595b43024955aa17314dd55f7890.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-TimeCostFilter 看起来是个普通 Bean啊，为何不能被自动注入？
-# 源码解析
-本质上，过滤器被 **@WebFilter** 修饰后，TimeCostFilter 只会被包装为 FilterRegistrationBean，而 TimeCostFilter 本身只会作为一个 InnerBean 被实例化，这意味着 TimeCostFilter 实例并不会作为 Bean 注册到 Spring 容器。
-![](https://img-blog.csdnimg.cn/6af1a896b40b48e7997aab35fe393ef1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-所以当我们想自动注入 TimeCostFilter 时，就会失败。知道这个结论后，我们可以带着两个问题去理清一些关键的逻辑：
-### FilterRegistrationBean 是什么？它是如何被定义的
-javax.servlet.annotation.WebFilter
-![](https://img-blog.csdnimg.cn/d50c1424b7ca4aedaeb5c176d108246c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-所以它不属 Spring，而是 Servlet 规范。
-Spring Boot 项目使用它时，Spring Boot 使用了 `org.springframework.boot.web.servlet.FilterRegistrationBean` 包装 @WebFilter 标记的实例。
-实现上来说，即 FilterRegistrationBean#Filter 属性就是 @WebFilter 标记的实例。这点我们可以从之前给出的截图中看出端倪。
-
-定义一个 Filter 类时，我们可能想的是，会自动生成它的实例，然后以 Filter 的名称作为 Bean 名来指向它。
-但调试发现，在 Spring Boot 中，Bean 名字确实是对的，只是 Bean 实例其实是 FilterRegistrationBean。
-
-> 这 FilterRegistrationBean 最早是如何获取的呢？
-
-得追溯到 @WebFilter 注解是如何被处理的。
-### @WebFilter 是如何工作的
-使用 **@WebFilter** 时，Filter 被加载有两个条件：
-- 声明了 **@WebFilter**
-- 在能被 **@ServletComponentScan** 扫到的路径下
-
-直接搜索对 **@WebFilter** 的使用，可发现 **WebFilterHandler** 使用了它：
-![](https://img-blog.csdnimg.cn/f54dc1da4efb42088f36b65cd49dc4d1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-因此，我们选择在 **doHandle()** 打断点
-![](https://img-blog.csdnimg.cn/3f62fe089b9b4b56a89c2ff3fd44ab74.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-debug启动，观察调用栈：
-![](https://img-blog.csdnimg.cn/a3a423af4efb4123bef27cddd97f0997.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-可见对 **@WebFilter** 的处理是在SB启动时，在**ServletComponentRegisteringPostProcessor**被触发，实现对如下注解的的扫描和处理：
--  **@WebFilter**
-- **@WebListener**
-- **@WebServlet** 
-
-WebFilterHandler则负责处理 **@WebFilter** 的使用：
-
-最后，**WebServletHandler** 通过父类 **ServletComponentHandler** 的模版方法模式，处理了所有被 **@WebFilter** 注解的类：
-![](https://img-blog.csdnimg.cn/c87248e1484644efb912bf3e74513419.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)可见最终注册的 FilterRegistrationBean就是自定义的WebFilter。
-
-看第二个问题：
-# 何时实例化TimeCostFilter
-TimeCostFilter 是何时实例化的呢？为什么它没有成为一个普通 Bean?
-可在 TimeCostFilter 构造器中加断点，便于快速定位初始化时机：
-![](https://img-blog.csdnimg.cn/5e0390b6343046a6812e69da17266688.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-结合源码，可发现：
-- Tomcat 启动时（***onstartUp***），才会创建 FilterRegistrationBean
-- FilterRegistrationBean 在被创建时（***createBean***）会创建 **TimeCostFilter** 装配自身，而 **TimeCostFilter** 是通过 ***ResolveInnerBean*** 创建的
-- **TimeCostFilter** 实例最终是一种 **InnerBean**
-![](https://img-blog.csdnimg.cn/4e37161a2335439da027266872c8118f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-所以最终 TimeCostFilter 实例是一种 **InnerBean**，也就无法自动注入了。
-# 修正
-找到根因，就知道如何解决了。
-
-前文解析可知，使用 **@WebFilter** 修饰过滤器时，TimeCostFilter 类型的 Bean 并没有注册至 Spring 容器，真正注册的是 FilterRegistrationBean。
-考虑到还可能存在多个 Filter，可这样修改：
-![](https://img-blog.csdnimg.cn/a233451d6ac14ffe9154042be51aa677.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-- 注入 **FilterRegistrationBean** 类型，而非 **TimeCostFilter** 类型
-- 注入的名称是包含包名的全限定名，不能直接用 `TimeCostFilter`，以便存在多个过滤器时，能精确匹配。
-
-# 总结
-**@WebFilter** 这种方式构建的 Filter 无法直接根据过滤器定义类型自动注入，因为这种Filter本身是以内部Bean呈现，最终是通过**FilterRegistrationBean**呈现给Spring。
-所以可通过自动注入**FilterRegistrationBean**类型完成自动装配。
\ No newline at end of file
diff --git "a/Spring/SpringFramework/\350\207\252\345\256\232\344\271\211Filter\345\220\216,\346\210\221\347\232\204\344\270\232\345\212\241\344\273\243\347\240\201\346\200\216\344\271\210\350\242\253\346\211\247\350\241\214\344\272\206\345\244\232\346\254\241\357\274\237.md" "b/Spring/SpringFramework/\350\207\252\345\256\232\344\271\211Filter\345\220\216,\346\210\221\347\232\204\344\270\232\345\212\241\344\273\243\347\240\201\346\200\216\344\271\210\350\242\253\346\211\247\350\241\214\344\272\206\345\244\232\346\254\241\357\274\237.md"
deleted file mode 100644
index 6de9747607..0000000000
--- "a/Spring/SpringFramework/\350\207\252\345\256\232\344\271\211Filter\345\220\216,\346\210\221\347\232\204\344\270\232\345\212\241\344\273\243\347\240\201\346\200\216\344\271\210\350\242\253\346\211\247\350\241\214\344\272\206\345\244\232\346\254\241\357\274\237.md"
+++ /dev/null
@@ -1,125 +0,0 @@
-实际生产过程中，若要求构建的过滤器针对全局路径有效，且无任何特殊需求（主要针对 Servlet 3.0 的一些异步特性支持），则完全可直接使用 Filter 接口（或继承 Spring 对 Filter 接口的包装类 OncePerRequestFilter），并使用**@Component** 将其包装为 Spring 中的普通 Bean，也可达到预期需求。
-
-不过不管使用哪种方式，可能都遇到问题：业务代码重复执行多次。
-
-这里以 **@Component** + Filter 接口实现方式呈现案例。
-创建一SB应用：
-![](https://img-blog.csdnimg.cn/c456333c13e5482d90a3875ac5c6d8e2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-UserController：
-![](https://img-blog.csdnimg.cn/3f732602dad946daac2ef662858d1174.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-DemoFilter：
-![](https://img-blog.csdnimg.cn/931f474caedf46739b60b272a07154e9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-查看调用接口后的日志：
-![](https://img-blog.csdnimg.cn/c2ee9c2675f84407a078fe7c93abecf6.png)业务代码竟然被执行两次？？？
-
-预期是 Filter 的业务执行不会影响核心业务，所以当抛异常时，还是会调用`chain.doFilter`。
-但有时，我们会忘记及时返回而误闯其它`chain.doFilter`，最终导致自定义过滤器被执行多次。
-
-而检查代码时，往往不能光速看出问题，所以这是类典型错误，虽然原因很简单。
-现在我们来分析为何会执行两次，以精通 Filter 的执行。
-# 源码解析
-首先我们要搞清
-## 责任链模式
-我们看Tomcat的Filter实现ApplicationFilterChain，采用责任链模式，像递归调用，区别在于：
-- 递归调用，同一对象把子任务交给同一方法本身
-- 责任链，一个对象把子任务交给**其它对象**的同名方法
-
-核心在于上下文 FilterChain 在不同对象 Filter 间的传递与状态的改变，通过这种链式串联，即可对同种对象资源实现不同业务场景的处理，实现业务解耦。
-### FilterChain 结构
-![](https://img-blog.csdnimg.cn/69f2e0298d26439c9d2ca42a19adbd83.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-1. 请求来临时，会执行到 ***StandardWrapperValve#invoke()*** ，该方法会创建 ApplicationFilterChain，并通过 ***ApplicationFilterChain#doFilter()***  触发过滤器执行
-2. ***ApplicationFilterChain#doFilter()*** 会执行其私有方法 internalDoFilter
-3. 在 internalDoFilter 方法中获取下一个Filter，并使用 request、response、this（当前ApplicationFilterChain 实例）作为参数来调用 doFilter()：
-public void doFilter(ServletRequest request, ServletResponse response,
-FilterChain chain) throws IOException, ServletException
-4. 在 Filter 类的 doFilter() 中，执行Filter定义的动作并继续传递，获取第三个参数 ApplicationFilterChain，并执行其 doFilter()
-5. 此时会循环执行进入第 2 步、第 3 步、第 4 步，直到第3步中所有的 Filter 类都被执行完毕为止
-6. 所有的Filter过滤器都被执行完毕后，会执行 servlet.service(request, response) 方法，最终调用对应的 Controller 层方法
-
-现在，让我们先看负责请求处理的触发时机：
-### StandardWrapperValve#invoke()
-FilterChain 在何处被创建，又是在何处进行初始化调用，从而激活责任链开始链式调用？
-```java
-public final void invoke(Request request, Response response)
-    throws IOException, ServletException {
-    // ...
-    // 创建filterChain 
-    ApplicationFilterChain filterChain =
-        ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
-// ...
-try {
-    if ((servlet != null) && (filterChain != null)) {
-        // Swallow output if needed
-        if (context.getSwallowOutput()) {
-             // ...
-             // 执行责任链
-             filterChain.doFilter(request.getRequest(),
-                            response.getResponse());
-             // ...
-         }
-// ...
-}
-```
-那FilterChain为何能被链式调用，调用细节如何？查阅： 
-### ApplicationFilterFactory.createFilterChain()
-```java
-public static ApplicationFilterChain createFilterChain(ServletRequest request,
-        Wrapper wrapper, Servlet servlet) {
-    // ...
-    ApplicationFilterChain filterChain = null;
-    if (request instanceof Request) {
-        // ...
-        // 创建FilterChain
-        filterChain = new ApplicationFilterChain();
-        // ...
-    }
-    // ...
-    // Add the relevant path-mapped filters to this filter chain
-    for (int i = 0; i < filterMaps.length; i++) {
-        // ...
-        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
-            context.findFilterConfig(filterMaps[i].getFilterName());
-        if (filterConfig == null) {
-            continue;
-        }
-        // 增加filterConfig到Chain
-        filterChain.addFilter(filterConfig);
-    }
-
-    // ...
-    return filterChain;
-}
-```
-它创建 FilterChain，并将所有 Filter 逐一添加到 FilterChain 中。
-
-继续查看
-### ApplicationFilterChain
-**javax.servlet.FilterChain** 的实现类
-![](https://img-blog.csdnimg.cn/a086671f1117459e820cbb768553fe26.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-用于管理特定请求的一组过滤器的执行。 当所有定义的过滤器都执行完毕后，对 ***doFilter()*** 的下一次调用将执行 ***servlet#service()*** 本身。
-#### 实例变量
-过滤器集
-![](https://img-blog.csdnimg.cn/9dafeaf064244ad980dee5e22c61ce8d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)过滤器链中当前位置
-![](https://img-blog.csdnimg.cn/a82237385d1141e581a28cc736521cdd.png)链中当前的过滤器数
-![](https://img-blog.csdnimg.cn/6532e64173a04b31a8bb842e9aa767e7.png)
-#### addFilter![](https://img-blog.csdnimg.cn/9a120216a76f46dd8053bfa8c6f3a059.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-每个被初始化的 Filter 都会通过 ***filterChain.addFilter()*** ，加入Filters，并同时更新n，使其等于 Filters数组长度。
-
-至此，Spring 完成对 FilterChain 创建准备工作。
-#### doFilter()
-调用此链中的下一个过滤器，传递指定请求、响应。 若此链无更多过滤器，则调用 ***servlet#service()***
-![](https://img-blog.csdnimg.cn/fb6fb766642040749904839912e9b6af.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-被委派到当前类的私有方法
-#### internalDoFilter（过滤器逻辑的核心）
-每被调用一次，pos 变量值自增 1，即从类成员变量 Filters 中取下一个 Filter：
-![](https://img-blog.csdnimg.cn/6d2e361f3d344aeba4c2dd6eb93f7328.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-***filter.doFilter(request, response, this)*** 会调用过滤器实现的 doFilter()，第三个参数为 this，即当前ApplicationFilterChain实例 ，这意味着用户需要在过滤器中显式调用一次 ***javax.servlet.FilterChain#doFilter***，才能完成整个链路。
-
-当`pos < n`，说明已执行完所有过滤器，才调用 ***servlet.service(request, response)*** 执行真正业务。从 ***internalDoFilter()*** 执行到 ***Controller#saveUser()*** 。
-![](https://img-blog.csdnimg.cn/0011ff410ca545e29d0a0cc736212663.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-回到案例，***DemoFilter#doFilter()*** 捕获异常的部分执行了一次，随后在 try 外面又执行一次，因而当抛出异常时，doFilter() 会被执行两次，相应的 ***servlet.service(request, response)*** 方法及对应的 Controller 处理方法也被执行两次。
-# 修正
-只需除去重复的 ***filterChain.doFilter(request, response)*** ：
-![](https://img-blog.csdnimg.cn/1773dfaf02d84824b5bcfe8276876c6f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-使用过滤器时，切忌多次调用 ***FilterChain#doFilter()*** 。
\ No newline at end of file
diff --git "a/Spring/Spring\347\250\213\345\272\217\351\205\215\347\275\256\344\274\230\345\205\210\347\272\247.md" "b/Spring/Spring\347\250\213\345\272\217\351\205\215\347\275\256\344\274\230\345\205\210\347\272\247.md"
deleted file mode 100644
index c5fa1169c4..0000000000
--- "a/Spring/Spring\347\250\213\345\272\217\351\205\215\347\275\256\344\274\230\345\205\210\347\272\247.md"
+++ /dev/null
@@ -1,81 +0,0 @@
-我们一般使用`application.yml`实现Spring Boot应用参数配置。但Spring配置有优先级，实际开发中要避免重复配置项的覆盖，就必须清晰这个优先级。
-
-Spring通过Environment抽象出：
-- Profile
-规定场景。定义诸如dev、test、prod等环境
-- Property
-PropertySources，各种配置源。一个环境中可能有多个配置源，每个配置源有许多配置项。查询配置信息时，按配置源优先级进行查询
-![](https://img-blog.csdnimg.cn/2021051910232476.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-> Property是如何查询配置的？
-
-首先看下配置的优先级：
-
-```java
-env.getPropertySources().stream()
-	.forEach(System.out::println);
-```
-如下共九个配置源：
-![](https://img-blog.csdnimg.cn/3b8888a22217457b8e04c0da90afe8e8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-
-- systemProperties
-系统配置
-- applicationConfig
-配置文件，我们的 yml 文件配置。如 application.yml 和 bootstrap.yml，首先加载bootstrap.yml。
-![](https://img-blog.csdnimg.cn/20210709155626321.png)
-
-
-其中的**OriginAwareSystemEnvironmentPropertySource**就是我们的`application.yml`。
-
-StandardEnvironment，继承自
-# AbstractEnvironment
-- MutablePropertySources#propertySources
-所有的配置源
-- getProperty
-通过PropertySourcesPropertyResolver类进行查询配置
-![](https://img-blog.csdnimg.cn/20210519120036370.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 实例化PropertySourcesPropertyResolver时，传入了当前的MutablePropertySources
-![](https://img-blog.csdnimg.cn/20210519115930680.png)
-
-那就来具体看看该类：
-# MutablePropertySources
-![](https://img-blog.csdnimg.cn/20210519120800958.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-# PropertySourcesPropertyResolver
-构造器传入后面用来遍历的**propertySources**。
-![](https://img-blog.csdnimg.cn/20210519130713181.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-结合**AbstractEnvironment**，这个**propertySources**就是**AbstractEnvironment#MutablePropertySources**。
-
-遍历时，若发现配置源中有对应K的V，则使用该V。
-所以**MutablePropertySources**中的配置源顺序很关键。
-- 真正查询配置的方法
-![](https://img-blog.csdnimg.cn/20210519130402101.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-在查询所有配置源时，NO.1的 **ConfigurationPropertySourcesPropertySource**并非一个实际存在的配置源，而是一个代理。debug 下查看到"user.name"的值是由它提供并返回，且没有再遍历后面的PropertySource看看有无"user.name"
-![](https://img-blog.csdnimg.cn/2021051913313372.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-# ConfigurationPropertySourcesPropertySource
-- getProperty()最终还调用findConfigurationProperty查询对应配置
-![](https://img-blog.csdnimg.cn/20210516153002620.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-- 上图中**getSource**()结果就是**SpringConfigurationPropertySources**
-![](https://img-blog.csdnimg.cn/20210515204232227.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 其中包含的配置源列表
-![](https://img-blog.csdnimg.cn/20210515204514320.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-第一个就是ConfigurationPropertySourcesPropertySource，这遍历不会导致死循环吗？
-
-注意到configurationProperty的实际配置是从系统属性来的。
-![](https://img-blog.csdnimg.cn/20210515205923497.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-# SpringConfigurationPropertySources
-![](https://img-blog.csdnimg.cn/20210515222936881.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-**ConfigurationPropertySourcesPropertySource**是所有配置源的NO.1，其早就知道了**PropertySourcesPropertyResolver**的遍历逻辑。
-
-> 那知道遍历逻辑后，如何暗箱操作可以让自己成为南波湾配置源？
-
-**ConfigurationPropertySourcesPropertySource**实例化时
-![](https://img-blog.csdnimg.cn/20210515223624562.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)**ConfigurationPropertySourcesPropertySource**是在**ConfigurationPropertySources**#attach中被 new 出来的。
-
-获得MutablePropertySources，把自己加入成为第一
-![](https://img-blog.csdnimg.cn/20210516152639338.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-这个attach方法就是在Spring应用程序启动时准备环境的时候调用的。
-![](https://img-blog.csdnimg.cn/20210516153250188.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/TODO/MySQL\351\207\215\345\244\247Bug!\350\207\252\345\242\236\344\270\273\351\224\256\347\253\237\347\204\266\344\270\215\346\230\257\350\277\236\347\273\255\351\200\222\345\242\236.md" "b/TODO/MySQL\351\207\215\345\244\247Bug!\350\207\252\345\242\236\344\270\273\351\224\256\347\253\237\347\204\266\344\270\215\346\230\257\350\277\236\347\273\255\351\200\222\345\242\236.md"
deleted file mode 100644
index d7216fa1f1..0000000000
--- "a/TODO/MySQL\351\207\215\345\244\247Bug!\350\207\252\345\242\236\344\270\273\351\224\256\347\253\237\347\204\266\344\270\215\346\230\257\350\277\236\347\273\255\351\200\222\345\242\236.md"
+++ /dev/null
@@ -1,217 +0,0 @@
-很多低级开发工程师都想当然觉得自增主键是严格连续递增的，但事实真的如此吗？
-
-创建一个测试表，执行
-![](https://img-blog.csdnimg.cn/2021061110044492.png)
-- show create table
-![](https://img-blog.csdnimg.cn/20210611100749288.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-> SHOW CREATE TABLE tbl_name：显示创建指定命名表的 CREATE TABLE 语句。要使用此语句，必须对该表具有一定的权限。此语句也适用于视图。
-> 更改表的存储引擎时，不适用于新存储引擎的表选项会保留在表定义，以便在必要时将具有先前定义选项的表恢复到原始存储引擎。例如，将存储引擎从 InnoDB 更改为 MyISAM 时，将保留 InnoDB 特定的选项，例如 ROW_FORMAT=COMPACT。
-```sql
-mysql> CREATE TABLE t1 (c1 INT PRIMARY KEY) ROW_FORMAT=COMPACT ENGINE=InnoDB;
-mysql> ALTER TABLE t1 ENGINE=MyISAM;
-mysql> SHOW CREATE TABLE t1\G
-*************************** 1. row ***************************
-       Table: t1
-Create Table: CREATE TABLE `t1` (
-  `c1` int(11) NOT NULL,
-  PRIMARY KEY (`c1`)
-) ENGINE=MyISAM DEFAULT CHARSET=latin1 ROW_FORMAT=COMPACT
-```
-
-> 创建禁用严格模式的表时，若不支持指定的行格式，则使用存储引擎的默认行格式。表的实际行格式在 Row_format 列中报告，以响应
-> SHOW TABLE STATUS。 SHOW CREATE TABLE 显示在 CREATE TABLE 语句中指定的行格式。
-
-`AUTO_INCREMENT=2`，表示下一次插入数据时，若需要自动生成自增值，会生成id=2。
-
-这个输出结果容易引起误解：自增值是保存在表结构定义里的。实际上，表的结构定义存在`.frm`文件，但不会保存自增值。
-## 自增值的保存策略
-### MyISAM
-自增值保存在数据文件中。
-### InnoDB
-自增值保存在内存，MySQL 8.0后，才有了“自增值持久化”能力，即才实现了“若重启，表的自增值可以恢复为MySQL重启前的值”，具体情况是：
-≤5.7，自增值保存在内存，无持久化。每次重启后，第一次打开表时，都会去找自增值的最大值max(id)，然后将max(id)+1作为这个表当前的自增值。
-﻿
-﻿若一个表当前数据行里最大的id是10，`AUTO_INCREMENT=11`。这时，我们删除id=10的行，**AUTO_INCREMENT**还是11。但若马上重启实例，重启后，该表的**AUTO_INCREMENT**就会变成10。﻿
-即MySQL重启可能会修改一个表的**AUTO_INCREMENT**值。
-MySQL 8.0将自增值的变更记录在redo log，重启时依靠redo log恢复重启之前的值。
-理解了MySQL对自增值的保存策略以后，我们再看看自增值修改机制。
-
-## 自增值的修改策略
-若字段id被定义为**AUTO_INCREMENT**，在插入一行数据时，自增值的行为如下：
-1. 若插入数据时id字段指定为0、null 或未指定值，则把该表当前**AUTO_INCREMENT**值填到自增字段
-2. 若插入数据时id字段指定了具体值，则使用语句里指定值
-
-根据要插入的值和当前自增值大小关系，假设要插入值X，而当前自增值Y，若：
-- X<Y，则该表的自增值不变
-- X≥Y，把当前自增值修改为新自增值
-
-#### 自增值生成算法
-从
-- **auto_increment_offset**（自增的初始值）开始
-- 以**auto_increment_increment**（步长）持续叠加
-
-直到找到第一个大于X的值，作为新的自增值。
-两个系统参数默认值都是1。
-
-某些场景使用的就不全是默认值。比如，双M架构要求双写时，可能设置成**auto_increment_increment=2**，让一个库的自增id都是奇数，另一个库的自增id都是偶数，避免两个库生成的主键发生冲突。
-
-所以，默认情况下，若准备插入的值≥当前自增值：
-- 新自增值就是“准备插入的值+1”
-- 否则，自增值不变
-
-# 自增值的修改时机
-- 表t里面已有如下记录
-![](https://img-blog.csdnimg.cn/20210611135004750.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)再执行一条插入数据命令
-![](https://img-blog.csdnimg.cn/20210611135103260.png)
-
-该唯一键冲突的语句执行流程：
-1. 执行器调用InnoDB引擎接口写入一行，传入的这一行的值是(0,1,1)
-2. InnoDB发现用户没有指定自增id的值，获取表t当前的自增值2
-3. 将传入的行的值改成(2,1,1)
-4. 将表的自增值改成3
-5. 继续执行插入数据(2,1,1)，由于已存在c=1，所以报Duplicate key error
-6. 语句返回
-
-该表的自增值已经改成3，是在真正执行插入数据之前。而该语句真正执行时，因唯一键冲突，所以id=2这行插入失败，但却没有将自增值改回去。
-
-- 此后再成功插入新数据，拿到自增id就是3了
-![](https://img-blog.csdnimg.cn/20210611135641125.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-如你所见，自增主键不连续了！所以唯一键冲突是导致自增主键id不连续的一大原因。
-
-事务回滚是二大原因。
-![](https://img-blog.csdnimg.cn/20210611140347623.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-为何现唯一键冲突或回滚时，MySQL不把自增值回退？
-
-这么设计是为了**提升性能**。
-假设有俩并行执行的事务，在申请自增值时，为避免两个事务申请到相同自增id，肯定要加锁，然后顺序申请。
-假设事务 B 稍后于 A
-|事务A|事务B  |
-|--|--|
-| 申请到id=2||
-| |  申请到id=3  |
-| 此时表t的自增值4|此时表t的自增值4
-| |  正确提交了  |
-|唯一键冲突||
-
-若允许A把自增id回退，即把t的当前自增值改回2，则：表里已有id=3，而当前自增id值是2。
-接下来，继续执行其它事务就会申请到id=2，然后再申请到id=3：报错“主键冲突”。
-
-要解决该主键冲突，怎么办？
-![](https://img-blog.csdnimg.cn/20210611154731840.png)
-
-1. 每次申请id前，先判断表里是否已存该id。若存在，就跳过该id。但这样操作成本很高。因为申请id本来很快的，现在竟然还要人家再去主键索引树判断id是否存在
-2. 把自增id的锁范围扩大，必须等到一个事务执行完成并提交，下一个事务才能再申请自增id。但这样锁的粒度太大，系统度大大下降！
-![](https://img-blog.csdnimg.cn/20210611155029829.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-低级的工程师想到的这些方案都会导致性能问题。之所以走进如此的怪圈，就因为“允许自增id回退”这个前提的存在。
-所以InnoDB放弃这样的设计，语句即使执行失败了，也不回退自增id！
-所以自增id只保证是递增的，但不保证是连续的！
-
-# 自增锁的养成计划
-所以自增id的锁并非事务锁，而是每次申请完就马上释放，其它事务可以再申请。其实，在MySQL 5.1版本之前，并不是这样的。
-
-MySQL 5.0时，自增锁的范围是语句级别：若一个语句申请了一个表自增锁，该锁会等语句执行结束以后才释放。显然，这样影响并发度
-
-MySQL 5.1.22版本引入了一个新策略，新增参数**innodb_autoinc_lock_mode**，默认值`1`。该参数的值为0时，表示采用5.0的策略，设置为1时：
-- 普通insert语句
-申请后，马上释放；
-- 类似**insert … select** 这样的批量插入语句
-等语句结束后，才释放
-
-设置为2时，所有的申请自增主键的动作都是申请后就释放锁。
-
-为什么默认设置下的**insert … select** 偏偏要使用语句级锁？为什么该参数默认值不是2？
-![](https://img-blog.csdnimg.cn/20210611155800913.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-为了**数据的一致性**。
-
-看个案例：批量插入数据的自增锁
-
-|session1|session2 |
-|--|--|
-| insert into t values (null, 2, 2); <br>insert into t values (null, 3, 3);<br>insert into t values (null, 4, 4);<br>||
-| |  create table t2 like t;  |
-| insert into t values (null, 5, 5);|insert into t2(c,d) select c,d from t;
-
-若session2申请了自增值后，马上释放自增锁，则可能发生：
-- session2先插入了两个记录，(1,1,1)、(2,2,2)
-- 然后，session1来申请自增id得到id=3，插入（3,5,5)
-- session2继续执行，插入两条记录(4,3,3)、 (5,4,4)
-
-这好像也没关系吧，毕竟session 2语义本身就没有要求t2的所有行数据都和session1相同。
-从数据逻辑角度看是对的。但若此时`binlog_format=statement`，binlog会怎么记录呢？
-先看看 MySQL 此时的告警：
-
-```sql
-mysql> insert into t2(c,d) select c,d from t;
-Query OK, 4 rows affected, 1 warning (0.01 sec)
-Records: 4  Duplicates: 0  Warnings: 1
-
-mysql> show warnings;
-+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-| Level | Code | Message                                                                                                                                                                                                                                                                                                                                                                          |
-+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-| Note  | 1592 | Unsafe statement written to the binary log using statement format since BINLOG_FORMAT = STATEMENT. Statements writing to a table with an auto-increment column after selecting from another table are unsafe because the order in which rows are retrieved determines what (if any) rows will be written. This order cannot be predicted and may differ on master and the slave. |
-+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-1 row in set (0.00 sec)
-```
-
-由于两个session同时执行插入数据命令，所以binlog里对表t2的更新日志只有两种情况：要么先记session1，要么先记session2。
-但无论哪种，这个binlog拿去从库执行或用来恢复临时实例，备库和临时实例里面，session2这个语句执行出来，生成的结果里，id都是连续的。 此时该库就发生了数据不一致。
-
-因为原库session2的insert语句，生成的id不连续。这个不连续的id，用statement格式的binlog来串行执行，是执行不出来的。
-要解决该问题，有如下方案：
-1. 让原库的批量插入数据语句，固定生成连续id值
-所以，自增锁直到语句执行结束才释放，就是为了达此目的
-2. 在binlog里把插入数据的操作都如实记录进来，到备库执行时，不依赖自增主键去生成
-其实就是**innodb_autoinc_lock_mode=2**，同时`binlog_format=row`。
-
-所以生产上有**insert … select**这种批量插入场景时，从并发插入的性能考虑，推荐设置：`innodb_autoinc_lock_mode=2 &&  binlog_format=row`，既能提升并发性，又不会出现数据一致性问题。
-
-> 这里的“批量插入数据”，包含如下语句类型：
-> - insert … select
-> - replace … select
-> - load data
-
-在普通insert语句包含多个value值的场景，即使`innodb_autoinc_lock_mode=1`，也不会等语句执行完成才释放锁。因为这类语句在申请自增id时，可以精确计算出需要多少个id，然后一次性申请，申请完成后锁即可释放。
-
-即批量插入数据的语句，之所以需要这么设置，是因为“不知道要预先申请多少个id”。
-既然不知道要申请多少个自增id，那么最简单的就是需要一个时申请一个。但若一个**select … insert**要插入10万行数据，就要申请10w次，速度慢还影响并发插入性能。
-
-因此，对于批量插入数据语句，MySQL提供了批量申请自增id的策略：
- 1. 语句执行过程中，第一次申请自增id，会分配1个
- 2. 1个用完以后，这个语句第二次申请自增id，会分配2个
- 3. 2个用完以后，还是这个语句，第三次申请自增id，会分配4个
-
-依此类推，同一个语句去申请自增id，每次申请到的自增id个数都是上一次的两倍。
-
-看案例：
-![](https://img-blog.csdnimg.cn/20210611171615128.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-```sql
-mysql> create table t2 like t;
-mysql> insert into t2(c,d) select c,d from t;
-Query OK, 4 rows affected, 1 warning (0.00 sec)
-Records: 4  Duplicates: 0  Warnings: 1
-
-mysql> insert into t2 values(null, 5,5);
-Query OK, 1 row affected (0.00 sec)
-
-mysql> select * from t2;
-+----+------+------+
-| id | c    | d    |
-+----+------+------+
-|  1 |    1 |    1 |
-|  2 |    2 |    2 |
-|  3 |    3 |    3 |
-|  4 |    4 |    4 |
-|  8 |    5 |    5 |
-+----+------+------+
-5 rows in set (0.00 sec)
-```
-**insert…select**实际上往t2中插入4行数据。但这四行数据是分三次申请的自增id，第一次申请到id=1，第二次id=2和id=3， 第三次id=4到id=7。
-由于该语句实际只用上了4个id，所以id=5到id=7就被浪费了。之后，再执行
-```sql
-insert into t2 values(null, 5,5)
-```
-实际上插入的数据是（8,5,5)。这是主键自增id不连续的三大原因。
\ No newline at end of file
diff --git "a/TODO/uml/Java\346\200\273\347\273\223\347\237\245\350\257\206\347\202\271.pos" "b/TODO/uml/Java\346\200\273\347\273\223\347\237\245\350\257\206\347\202\271.pos"
deleted file mode 100644
index 9dea4e81b1..0000000000
--- "a/TODO/uml/Java\346\200\273\347\273\223\347\237\245\350\257\206\347\202\271.pos"
+++ /dev/null
@@ -1 +0,0 @@
-{"diagram":{"image":{"x":0,"width":200,"y":0,"pngdata":"iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAsUlEQVR4nO3BAQEAAACCIP+vbkhAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8GXHmAAFMgHIEAAAAAElFTkSuQmCC","height":200},"elements":{"leftChildren":[],"note":"","watermark":"","children":[{"parent":"root","lineStyle":{"randomLineColor":"#4D69FD"},"children":[{"parent":"800ecb4c0776","children":[{"parent":"36d002b102e2","children":[{"parent":"5351911e121f","children":[],"id":"da9b17a0be4d","title":"数据结构: 数组"},{"parent":"5351911e121f","children":[],"id":"00dd5a2b370d","title":"由于数据结构的特点, 查找访问效率高, 增删效率低"},{"parent":"5351911e121f","children":[],"id":"89a058828311","title":"默认数组大小10"},{"parent":"5351911e121f","children":[],"id":"dc80c006aa14","title":"ArrayList存在指定index新增与直接新增,在新增前会有一步校验长度的判断ensureCapacityInternal,如果长度不够需要扩容<br>"},{"parent":"5351911e121f","children":[],"id":"d16b9b53101f","title":"ArrayList线程不安全,线程安全版本的数组容器是Vector<br>"},{"parent":"5351911e121f","children":[],"id":"5eb59595d8fd","title":"与LinkedList遍历效率对比,性能高很多,ArrayList遍历最大的优势在于内存的连续性，CPU的内部缓存结构会缓存连续的内存片段，可以大幅降低读取内存的性能开销<br>"}],"id":"5351911e121f","title":"ArrayList"},{"parent":"36d002b102e2","children":[{"parent":"f817a30f5e08","children":[],"id":"e3e17fe90642","title":"数据结构: 双向链表"},{"parent":"f817a30f5e08","children":[],"id":"3846d194c83e","title":"适合插入删除频繁的情况&nbsp; 内部维护了链表的长度<br>"}],"id":"f817a30f5e08","title":"LinkedList<br>"}],"id":"36d002b102e2","title":"List"},{"parent":"800ecb4c0776","children":[{"parent":"24f4a3146f29","children":[{"parent":"f4b8a675a029","children":[{"parent":"2353a0a1da34","children":[],"id":"8838e06b887a","title":"数据结构: 数组+链表<br>"},{"parent":"2353a0a1da34","children":[],"id":"7a82749e87f8","title":"头插法:&nbsp;新来的值会取代原有的值，原有的值就顺推到链表中去<br>"},{"parent":"2353a0a1da34","children":[],"id":"c1c3800b1ead","title":"Java7在多线程操作HashMap时可能引起死循环，原因是扩容转移后前后链表顺序倒置，在转移过程中修改了原来链表中节点的引用关系, 可能形成环形链表<br>"}],"id":"2353a0a1da34","title":"1.7"},{"parent":"f4b8a675a029","children":[{"parent":"cb2e0e4bf56a","children":[],"id":"a743dde50473","title":"数据结构: 数组+链表+红黑树&nbsp;<br>"},{"parent":"cb2e0e4bf56a","children":[{"parent":"4bb6868f0cc3","children":[],"id":"4753ca9df7aa","title":"根据泊松分布，在负载因子默认为0.75的时候，单个hash槽内元素个数为8的概率小于百万分之一，所以将7作为一个分水岭，等于7的时候不转换，大于等于8的时候才进行转换，小于等于6的时候就化为链表<br>"}],"id":"4bb6868f0cc3","title":"Hashmap中的链表大小超过八个时会自动转化为红黑树，当删除小于六时重新变为链表<br>"},{"parent":"cb2e0e4bf56a","children":[],"id":"1c07c2a415b7","title":"尾插法<br>"},{"parent":"cb2e0e4bf56a","children":[],"id":"94b8ffa2573f","title":"Java8在同样的前提下并不会引起死循环，原因是扩容转移后前后链表顺序不变，保持之前节点的引用关系<br>"}],"id":"cb2e0e4bf56a","title":"1.8"},{"parent":"f4b8a675a029","children":[{"parent":"6d54a11adce4","children":[],"id":"210d702bbf4a","title":"LoadFactory 默认0.75<br>"},{"parent":"6d54a11adce4","children":[],"id":"e4055d669e46","title":"<ul><li>扩容：创建一个新的Entry空数组，长度是原数组的2倍。</li><li>ReHash：遍历原Entry数组，把所有的Entry重新Hash到新数组</li></ul><br>"},{"parent":"6d54a11adce4","children":[{"parent":"af3ec421dc31","children":[],"id":"0c6cfee11d49","title":"因为长度扩大以后，Hash的规则也随之改变<br>"},{"parent":"af3ec421dc31","children":[],"id":"316f5ac25324","title":"Hash的公式---&gt; index = HashCode（Key） &amp; （Length - 1）<br>原来长度（Length）是8你位运算出来的值是2 ，新的长度是16位运算出来的值不同<br>"},{"parent":"af3ec421dc31","children":[],"id":"ff2cf93209dc","title":"HashMap是通过key的HashCode去寻找index的, 如果不进行重写,会出现在一个index中链表的HashCode相等情况,所以要确保相同的对象返回相同的hash值，不同的对象返回不同的hash值,必须要重写equals<br>"}],"id":"af3ec421dc31","title":"为什么要ReHash而不进行复制?&nbsp;<br>"}],"id":"6d54a11adce4","title":"扩容机制"},{"parent":"f4b8a675a029","children":[{"parent":"c6437e8e4f45","children":[],"id":"cb7132ed82f1","title":"HashMap源码中put/get方法都没有加同步锁,&nbsp;无法保证上一秒put的值，下一秒get的时候还是原值，所以线程安全无法保证<br>"},{"parent":"c6437e8e4f45","children":[{"parent":"9d6423986311","children":[],"id":"b40b3d560950","title":"Collections.synchronizedMap(Map)"},{"parent":"9d6423986311","children":[{"parent":"cdcaf61438fc","children":[{"parent":"404e074072c9","children":[{"parent":"b926b8dd14c9","children":[],"id":"6bf370941ab4","title":"安全失败机制: 这种机制会使你此次读到的数据不一定是最新的数据。<br>如果你使用null值，就会使得其无法判断对应的key是不存在还是为空，因为你无法再调用一次contain(key）来对key是否存在进行判断，ConcurrentHashMap同理"}],"id":"b926b8dd14c9","title":"Hashtable 是不允许键或值为 null 的，HashMap 的键值则都可以为 null"},{"parent":"404e074072c9","children":[],"id":"c4669cca3ca2","title":"Hashtable 继承了 Dictionary类，而 HashMap 继承的是 AbstractMap 类"},{"parent":"404e074072c9","children":[],"id":"63a75cf7717b","title":"HashMap 的初始容量为：16，Hashtable 初始容量为：11，两者的负载因子默认都是：0.75"},{"parent":"404e074072c9","children":[],"id":"8e6e339c6351","title":"当现有容量大于总容量 * 负载因子时，HashMap 扩容规则为当前容量翻倍，Hashtable 扩容规则为当前容量翻倍 + 1"},{"parent":"404e074072c9","children":[{"parent":"13aa27708fb1","children":[],"id":"e72d018a877c","title":"快速失败（fail—fast）是java集合中的一种机制， 在用迭代器遍历一个集合对象时，如果遍历过程中对集合对象的内容进行了修改（增加、删除、修改），则会抛出ConcurrentModificationException"}],"id":"13aa27708fb1","title":"HashMap 中的 Iterator 迭代器是 fail-fast 的，而 Hashtable 的 Enumerator 不是 fail-fast 的. 所以，当其他线程改变了HashMap 的结构，如：增加、删除元素，将会抛出ConcurrentModificationException 异常，而 Hashtable 则不会"}],"id":"404e074072c9","title":"与HahsMap的区别"}],"id":"cdcaf61438fc","title":"Hashtable"},{"parent":"9d6423986311","children":[],"id":"3aa13f74a283","title":"ConcurrentHashMap"}],"id":"9d6423986311","title":"确保线程安全的方式"}],"id":"c6437e8e4f45","title":"线程不安全<br>"},{"parent":"f4b8a675a029","children":[{"parent":"83e802bbbf44","children":[],"id":"6689f5a28d8d","title":"创建HashMap时最好赋初始值, 而且最好为2的幂,为了位运算的方便<br>"},{"parent":"83e802bbbf44","children":[{"parent":"33f1a38f62cd","children":[],"id":"245253ad18f8","title":"实现均匀分布, 在使用不是2的幂的数字的时候，Length-1的值是所有二进制位全为1，这种情况下，index的结果等同于HashCode后几位的值,只要输入的HashCode本身分布均匀，Hash算法的结果就是均匀的<br>"}],"id":"33f1a38f62cd","title":"默认初始化大小为16<br>"}],"id":"83e802bbbf44","title":"初始化<br>"},{"parent":"f4b8a675a029","children":[],"id":"1bf62446136a","title":"重写equals必须重写HashCode<br>"}],"id":"f4b8a675a029","title":"HashMap<br>"},{"parent":"24f4a3146f29","children":[{"parent":"099533450f79","children":[{"parent":"6c4d02753b0b","children":[],"id":"1bf9ad750241","title":"这种机制会使你此次读到的数据不一定是最新的数据。如果你使用null值，就会使得其无法判断对应的key是不存在还是为空，因为你无法再调用一次contain(key）来对key是否存在进行判断，HashTable同理<br>"}],"id":"6c4d02753b0b","title":"安全失败机制<br>"},{"parent":"099533450f79","children":[{"parent":"238f89a01639","children":[],"id":"9a49309bdc28","title":"数据结构: 数组+链表 (Segment 数组、HashEntry 组成)<br>"},{"parent":"238f89a01639","children":[{"parent":"940aa3e4031c","children":[],"id":"6d0c1ad1257f","title":"HashEntry跟HashMap差不多的，但是不同点是，他使用volatile去修饰了他的数据Value还有下一个节点next<br>"}],"id":"940aa3e4031c","title":"HashEntry<br>"},{"parent":"238f89a01639","children":[{"parent":"214133bb3335","children":[{"parent":"c94b076d3d2d","children":[],"id":"ccbe178d4298","title":"继承了ReentrantLock<br>"},{"parent":"c94b076d3d2d","children":[],"id":"8e8ef1826155","title":"每当一个线程占用锁访问一个 Segment 时，不会影响到其他的 Segment。<br>如果容量大小是16他的并发度就是16，可以同时允许16个线程操作16个Segment而且还是线程安全的。<br>"}],"id":"c94b076d3d2d","title":"segment分段锁<br>"},{"parent":"214133bb3335","children":[{"parent":"d93302980fe7","children":[],"id":"26bfa3a12ad8","title":"尝试自旋获取锁,如果获取失败肯定就有其他线程存在竞争，则利用 scanAndLockForPut() 自旋获取锁<br>"},{"parent":"d93302980fe7","children":[],"id":"b933436ce63c","title":"如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取，保证能获取成功<br>"}],"id":"d93302980fe7","title":"put<br>"},{"parent":"214133bb3335","children":[{"parent":"e791448ffbae","children":[],"id":"65b8e0356631","title":"由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的，保证了内存可见性，所以每次获取时都是最新值<br>"},{"parent":"e791448ffbae","children":[],"id":"1748a490b610","title":"ConcurrentHashMap 的 get 方法是非常高效的，因为整个过程都不需要加锁<br>"}],"id":"e791448ffbae","title":"get<br>"}],"id":"214133bb3335","title":"并发度高的原因<br>"}],"id":"238f89a01639","title":"1.7<br>"},{"parent":"099533450f79","children":[{"parent":"bb9fcd3cdf24","children":[],"id":"cefe93491aa8","title":"数组+链表+红黑树<br>"},{"parent":"bb9fcd3cdf24","children":[{"parent":"7f1ff4d86665","children":[],"id":"122b03888ad2","title":"抛弃了原有的 Segment 分段锁，而采用了 CAS + synchronized 来保证并发安全性<br>"}],"id":"7f1ff4d86665","title":"区别<br>"},{"parent":"bb9fcd3cdf24","children":[{"parent":"292fc2483f4c","children":[],"id":"1181a0ece609","title":"根据 key 计算出 hashcode<br>"},{"parent":"292fc2483f4c","children":[],"id":"17e33984c1e5","title":"判断是否需要进行初始化<br>"},{"parent":"292fc2483f4c","children":[],"id":"d0bfa1371c32","title":"即为当前 key 定位出的 Node，如果为空表示当前位置可以写入数据，利用 CAS 尝试写入，失败则自旋保证成功<br>"},{"parent":"292fc2483f4c","children":[],"id":"1ddaa88f7963","title":"如果当前位置的 hashcode == MOVED == -1,则需要进行扩容<br>"},{"parent":"292fc2483f4c","children":[],"id":"5735982de5ef","title":"如果都不满足，则利用 synchronized 锁写入数据<br>"},{"parent":"292fc2483f4c","children":[],"id":"4e006a2b0550","title":"如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树<br>"}],"id":"292fc2483f4c","title":"put操作<br>"},{"parent":"bb9fcd3cdf24","children":[{"parent":"92654a95d638","children":[],"id":"02394dd149e1","title":"根据计算出来的 hashcode 寻址，如果就在桶上那么直接返回值<br>"},{"parent":"92654a95d638","children":[],"id":"d7b487f3b69b","title":"如果是红黑树那就按照树的方式获取值<br>"},{"parent":"92654a95d638","children":[],"id":"1c6f9d2d5016","title":"就不满足那就按照链表的方式遍历获取值<br>"}],"id":"92654a95d638","title":"get操作<br>"}],"collapsed":false,"id":"bb9fcd3cdf24","title":"1.8<br>"}],"id":"099533450f79","title":"ConcurrentHashMap<br>"}],"collapsed":false,"id":"24f4a3146f29","title":"Map"},{"parent":"800ecb4c0776","children":[{"parent":"e7046be1aafc","children":[{"parent":"bb49195cb4a7","children":[],"link":{"title":"https://mp.weixin.qq.com/s/0cMrE87iUxLBw_qTBMYMgA","type":"url","value":"https://mp.weixin.qq.com/s/0cMrE87iUxLBw_qTBMYMgA"},"id":"5448b1ead28d","title":"同步容器（如Vector）的所有操作一定是线程安全的吗？"}],"id":"bb49195cb4a7","title":"相关文档"}],"id":"e7046be1aafc","title":"Vector"},{"parent":"800ecb4c0776","children":[{"parent":"6062a460b43c","children":[{"parent":"d695fc5d8846","children":[],"id":"84bbc18befc1","title":"底层实现的就是HashMap，所以是根据HashCode来判断是否是重复元素<br>"},{"parent":"d695fc5d8846","children":[],"id":"27e06a58782e","title":"初始化容量是：16, 因为底层实现的是HashMap。加载因子是0.75<br>"},{"parent":"d695fc5d8846","children":[],"id":"dbcb9b76077a","title":"无序的<br>"},{"parent":"d695fc5d8846","children":[],"id":"2c468ef34635","title":"HashSet不能根据索引去数据，所以不能用普通的for循环来取出数据，应该用增强for循环,查询性能不好<br>"}],"id":"d695fc5d8846","title":"HashSet<br>"},{"parent":"6062a460b43c","children":[{"parent":"3e5fc407007b","children":[],"id":"34347150ba65","title":"底层是实现的TreeMap<br>"},{"parent":"3e5fc407007b","children":[],"id":"3ead4a788049","title":"元素不能够重复，可以有一个null值，并且这个null值一直在第一个位置上<br>"},{"parent":"3e5fc407007b","children":[],"id":"432c37f69b42","title":"默认容量：16，加载因子是0.75<br>"},{"parent":"3e5fc407007b","children":[],"id":"1bf8f3a4b2c3","title":"TreeMap是有序的，这个有序不是存入的和取出的顺序是一样的，而是根据自然规律拍的序<br>"}],"id":"3e5fc407007b","title":"TreeSet<br>"}],"id":"6062a460b43c","title":"Set"}],"collapsed":true,"id":"800ecb4c0776","title":"集合"},{"parent":"root","lineStyle":{"randomLineColor":"#F4325C"},"children":[],"id":"ef690530e935","title":"基础"},{"parent":"root","lineStyle":{"randomLineColor":"#A04AFB"},"children":[{"parent":"d61da867cb10","children":[{"parent":"c56b85ccc7f0","children":[],"id":"cd07e14ad850","title":"虚拟机堆<br>"},{"parent":"c56b85ccc7f0","children":[],"id":"5f3d1d3f67c2","title":"虚拟机栈<br>"},{"parent":"c56b85ccc7f0","children":[],"id":"b44fa08a79e3","title":"方法区<br>"},{"parent":"c56b85ccc7f0","children":[],"id":"38e4ffe62590","title":"本地方法栈<br>"},{"parent":"c56b85ccc7f0","children":[],"id":"6b76f89e7451","title":"程序计数器<br>"}],"id":"c56b85ccc7f0","title":"Java内存区域<br>"},{"parent":"d61da867cb10","children":[{"parent":"08f416fb625f","children":[],"id":"95995454f91e","title":"加载-&gt;验证-&gt;准备-&gt;解析-&gt;初始化-&gt;使用-&gt;卸载<br>"},{"parent":"08f416fb625f","children":[{"parent":"28edebbeb4d6","children":[],"id":"fddd8578d67e","title":"父类加载 不重复加载<br>"}],"id":"28edebbeb4d6","title":"双亲委派原则<br>"},{"parent":"08f416fb625f","children":[{"parent":"ff18f9026678","children":[],"id":"ea8db2744206","title":"第一次，在JDK1.2以前，双亲委派模型在JDK1.2引入，ClassLoder在最初已经存在了，为了兼容已有代码，添加了findClass（）方法，如果父类加载失败会自动调用findClass（）来完成加载<br>"},{"parent":"ff18f9026678","children":[],"id":"5c240d502f24","title":"第二次，由双亲委派模型缺陷导致，由于双亲委派越基础的类由越上层的加载器进行加载，如果有基础类型调回用户代码回无法解决而产生，出现线程上下文类加载器，会出现父类加载器请求子类加载器完成类加载的行为<br>"},{"parent":"ff18f9026678","children":[],"id":"5d62862bf3e0","title":"第三次，代码热替换、模块热部署，典型：OSGi每一个程序模块都有一个自己的类加载器<br>"}],"id":"ff18f9026678","title":"破坏双亲委派模型<br>"}],"id":"08f416fb625f","title":"类得加载机制"},{"parent":"d61da867cb10","children":[{"parent":"52bb18b06c6e","children":[],"id":"f61579e4d22a","title":"新生代/年轻代<br>"},{"parent":"52bb18b06c6e","children":[],"id":"72db835ecada","title":"老年代<br>"},{"parent":"52bb18b06c6e","children":[{"parent":"559cb847a3c3","children":[{"parent":"48946b525dfd","children":[],"id":"b7b6b8d8f03d","title":"字符串存在永久代中，容易出现性能问题和内存溢出<br>"},{"parent":"48946b525dfd","children":[],"id":"2f7df7f20543","title":"类及方法的信息等比较难确定其大小，因此对于永久代的大小指定比较困难，太小容易出现永久代溢出，太大则容易导致老年代溢出<br>"},{"parent":"48946b525dfd","children":[],"id":"5439cd0f53c8","title":"永久代会为 GC 带来不必要的复杂度，并且回收效率偏低<br>"},{"parent":"48946b525dfd","children":[],"id":"9c36c0817672","title":"将 HotSpot 与 JRockit 合二为一<br>"}],"id":"48946b525dfd","title":"为什么要使用元空间取代永久代的实现？<br>"},{"parent":"559cb847a3c3","children":[{"parent":"1d6773397050","children":[],"id":"c33f0e9fe53f","title":"元空间并不在虚拟机中，而是使用本地内存。因此默认情况下，元空间的大小仅受本地内存限制<br>"}],"id":"1d6773397050","title":"元空间与永久代区别<br>"},{"parent":"559cb847a3c3","children":[{"parent":"afb5323c83d0","children":[],"id":"fbf6f9baf099","title":"-XX:MetaspaceSize：初始空间大小，达到该值会触发垃圾收集进行类型卸载，同时GC会对该值进行调整：如果释放了大量的空间，就适当降低该值；如果释放了很少的空间，那么在不超过MaxMetaspaceSize时，适当提高该值<br>"},{"parent":"afb5323c83d0","children":[],"id":"8e0c80084aef","title":"-XX:MaxMetaspaceSize：最大空间，默认是没有限制的<br>"}],"id":"afb5323c83d0","title":"元空间空间大小设置<br>"}],"id":"559cb847a3c3","title":"永久代/元空间<br>"},{"parent":"52bb18b06c6e","children":[{"parent":"34170f8e7332","children":[],"id":"9d5df7c5e2eb","title":"根据存活时间<br>"}],"id":"34170f8e7332","title":"晋升机制<br>"}],"id":"52bb18b06c6e","title":"分代回收<br>"},{"parent":"d61da867cb10","children":[{"parent":"c465e3a953e3","children":[{"parent":"2e9e41cbb0e4","children":[],"id":"6ff93afab3be","title":"绝大多数对象都是朝生熄灭的<br>"}],"id":"2e9e41cbb0e4","title":"弱分代假说<br>"},{"parent":"c465e3a953e3","children":[{"parent":"759979ad09cd","children":[],"id":"a152f857c10b","title":"熬过越多次垃圾收集过程的对象就越难以消亡<br>"}],"id":"759979ad09cd","title":"强分代假说<br>"},{"parent":"c465e3a953e3","children":[{"parent":"4843a46b8196","children":[],"id":"6481cbce8487","title":"跨代引用相对于同代引用来说仅占极少数<br>"}],"id":"4843a46b8196","title":"跨代引用假说<br>"}],"id":"c465e3a953e3","title":"分代收集理论<br>"},{"parent":"d61da867cb10","children":[{"parent":"85ab22cf15b4","children":[],"id":"0c42af972e09","title":"“GC Roots”的根对象作为起始节点集，从这些节点开始，根据引用关系向下搜索，如果某个对象到GC Roots间没有任何引用链相连接，则证明此对象是不可能再被使用的<br>"}],"id":"85ab22cf15b4","title":"可达性分析算法<br>"},{"parent":"d61da867cb10","children":[{"parent":"133b15c6580f","children":[{"parent":"9bd71d4114f3","children":[],"id":"e602b5dbf42a","title":"强引用是使用最普遍的引用。如果一个对象具有强引用，那垃圾回收器绝不会回收它; 当内存空间不足时，Java虚拟机宁愿抛出OutOfMemoryError错误，使程序异常终止，也不会靠随意回收具有强引用的对象来解决内存不足的问题。<br>"}],"id":"9bd71d4114f3","title":"强引用 (StrongReference)<br>"},{"parent":"133b15c6580f","children":[{"parent":"a4d69c685996","children":[],"id":"aaedb29f6e8f","title":"如果一个对象只具有软引用，则内存空间充足时，垃圾回收器就不会回收它；如果内存空间不足了，就会回收这些对象的内存。<br>"}],"id":"a4d69c685996","title":"软引用 (SoftReference)<br>"},{"parent":"133b15c6580f","children":[{"parent":"6cdc46b73fd3","children":[],"id":"7acc452f65ab","title":"在垃圾回收器线程扫描它所管辖的内存区域的过程中，一旦发现了只具有弱引用的对象，不管当前内存空间足够与否，都会回收它的内存。<br>"}],"id":"6cdc46b73fd3","title":"弱引用 (WeakReference)<br>"},{"parent":"133b15c6580f","children":[{"parent":"5f58e345e898","children":[],"id":"d189c9591839","title":"如果一个对象仅持有虚引用，那么它就和没有任何引用一样，在任何时候都可能被垃圾回收器回收<br>"}],"id":"5f58e345e898","title":"虚引用 (PhantomReference)<br>"}],"id":"133b15c6580f","title":"引用<br>"},{"parent":"d61da867cb10","children":[{"parent":"7a6e56139d11","children":[{"parent":"39f66c681caf","children":[{"parent":"5303c5abf34b","children":[],"id":"e983d3f5fd4a","title":"对象存活较多情况<br>"},{"parent":"5303c5abf34b","children":[],"id":"b9065a228c05","title":"老年代<br>"}],"id":"5303c5abf34b","title":"适用场景<br>"},{"parent":"39f66c681caf","children":[{"parent":"733f3bc0b7ab","children":[],"id":"4f9fdc7ae82e","title":"内存空间碎片化<br>"},{"parent":"733f3bc0b7ab","children":[],"id":"2a97115e02e3","title":"由于空间碎片导致的提前GC<br>"},{"parent":"733f3bc0b7ab","children":[{"parent":"706cff5bc691","children":[],"id":"7a5e8f6db9e2","title":"标记需清除或存货对象<br>"},{"parent":"706cff5bc691","children":[],"id":"3da1d4f2af71","title":"清除标记或未标记对象<br>"}],"id":"706cff5bc691","title":"扫描了两次<br>"}],"id":"733f3bc0b7ab","title":"缺点<br>"}],"id":"39f66c681caf","title":"标记请除<br>"},{"parent":"7a6e56139d11","children":[{"parent":"463d39f131e0","children":[{"parent":"cc3d3a846262","children":[],"id":"ad8382025fe4","title":"存活对象少比较高效<br>"},{"parent":"cc3d3a846262","children":[],"id":"fb2a5786103c","title":"扫描了整个空间（标记存活对象并复制移动）<br>"},{"parent":"cc3d3a846262","children":[],"id":"efebc696f64e","title":"年轻代<br>"}],"id":"cc3d3a846262","title":"适用场景<br>"},{"parent":"463d39f131e0","children":[{"parent":"4a07efc633b8","children":[],"id":"ca57a2a2591a","title":"需要空闲空间<br>"},{"parent":"4a07efc633b8","children":[],"id":"74b68744b877","title":"老年代作为担保空间<br>"},{"parent":"4a07efc633b8","children":[],"id":"9a2b53e3948c","title":"复制移动对象<br>"}],"id":"4a07efc633b8","title":"缺点<br>"}],"id":"463d39f131e0","title":"标记复制<br>"},{"parent":"7a6e56139d11","children":[{"parent":"6e91691d76fb","children":[{"parent":"ca79fb0161d2","children":[],"id":"3093a8344bf8","title":"对象存活较多情况<br>"},{"parent":"ca79fb0161d2","children":[],"id":"171500353dee","title":"老年代<br>"}],"id":"ca79fb0161d2","title":"适用场景<br>"},{"parent":"6e91691d76fb","children":[{"parent":"2e00b69db6db","children":[],"id":"9cc7fd616235","title":"移动存活对象并更新对象引用<br>"},{"parent":"2e00b69db6db","children":[],"id":"9f173f5f0a02","title":"Stop The World<br>"}],"id":"2e00b69db6db","title":"缺点<br>"}],"id":"6e91691d76fb","title":"标记整理<br>"},{"parent":"7a6e56139d11","children":[{"parent":"873b4393cb04","children":[],"id":"e55f55cabd04","title":"没办法解决循环引用的问题<br>"}],"id":"873b4393cb04","title":"引用计数<br>"}],"id":"7a6e56139d11","title":"垃圾回收机制<br>"},{"parent":"d61da867cb10","children":[{"parent":"94c48a9cef88","children":[{"parent":"1871a079226a","children":[{"parent":"ec2e2387d0dc","children":[{"parent":"1e82c0b474da","children":[],"id":"4ae444f631e6","title":"Eden<br>"},{"parent":"1e82c0b474da","children":[],"id":"2e6db7cc3c33","title":"Survivor1<br>"},{"parent":"1e82c0b474da","children":[],"id":"c07a1cd16c8f","title":"Survivor2<br>"},{"parent":"1e82c0b474da","children":[{"parent":"150da6938c96","children":[],"id":"79ab826115b2","title":"通过阈值晋升<br>"}],"id":"150da6938c96","title":"Minor GC<br>"}],"id":"1e82c0b474da","title":"年轻代"},{"parent":"ec2e2387d0dc","children":[{"parent":"c2b0ef70d896","children":[],"id":"69438d9521b8","title":"Major GC 等价于 Full GC<br>"}],"id":"c2b0ef70d896","title":"老年代<br>"},{"parent":"ec2e2387d0dc","children":[],"id":"bb363f2d06eb","title":"永久"}],"id":"ec2e2387d0dc","title":"分代情况<br>"},{"parent":"1871a079226a","children":[{"parent":"ef9b2adfad54","children":[],"id":"f8a938786281","title":"对CPU资源敏感<br>"},{"parent":"ef9b2adfad54","children":[],"id":"f34402ad150a","title":"无法处理浮动垃圾<br>"},{"parent":"ef9b2adfad54","children":[],"id":"1b39a82e9c8c","title":"基于标记清除算法 大量空间碎片<br>"}],"id":"ef9b2adfad54","title":"缺点<br>"}],"id":"1871a079226a","title":"CMS<br>"},{"parent":"94c48a9cef88","children":[{"parent":"9b2c5a694136","children":[],"id":"d3247834f765","title":"分区概念 弱化分代<br>"},{"parent":"9b2c5a694136","children":[{"parent":"0ac46d282191","children":[],"id":"1eba7428e805","title":"不会产生碎片空间，分配大对象不会提前Full GC<br>"}],"id":"0ac46d282191","title":"标记整理算法<br>"},{"parent":"9b2c5a694136","children":[{"parent":"20775041ea09","children":[],"id":"df43ec84ba90","title":"使用参数-XX:MaxGCPauseMills，默认为200毫秒，优先处理回收价值收集最大的Region<br>"}],"id":"20775041ea09","title":"允许用户设置收集的停顿时间<br>"},{"parent":"9b2c5a694136","children":[],"id":"0831e78ce2d4","title":"利用CPU多核条件，缩短STW时间<br>"},{"parent":"9b2c5a694136","children":[],"id":"00fc277cb4e8","title":"原始快照算法（SATB）保证收集线程与用户线程互不干扰，避免标记结果出错<br>"},{"parent":"9b2c5a694136","children":[{"parent":"64ddc0929330","children":[{"parent":"6bda8393ce9e","children":[],"id":"2194b30b8023","title":"标记STW从GC Roots开始直接可达的对象，借用Minor GC时同步完成<br>"}],"id":"6bda8393ce9e","title":"初始标记<br>"},{"parent":"64ddc0929330","children":[{"parent":"8954c96bf47e","children":[],"id":"11402483ace1","title":"从GC Roots开始对堆对象进行可达性分析，找出要回收的对象，与用户程序并发执行，重新处理SATB记录下的并发时引用变动对象<br>"}],"id":"8954c96bf47e","title":"并发标记<br>"},{"parent":"64ddc0929330","children":[{"parent":"dcca5f39bc8c","children":[],"id":"2044b2d42f4a","title":"处理并发阶段结束后遗留下来的少量SATB记录<br>"}],"id":"dcca5f39bc8c","title":"最终标记<br>"},{"parent":"64ddc0929330","children":[{"parent":"32a05d0acfd4","children":[],"id":"1c5bed528018","title":"根据用户期待的GC停顿时间制定回收计划<br>"}],"id":"32a05d0acfd4","title":"筛选回收<br>"}],"id":"64ddc0929330","title":"收集步骤<br>"},{"parent":"9b2c5a694136","children":[{"parent":"144086fdf009","children":[{"parent":"70dcc7b7411a","children":[{"parent":"bf3ba1d7ab1a","children":[],"id":"6d91b336c4b7","title":"复制一些存活对象到Old区、Survivor区<br>"}],"id":"bf3ba1d7ab1a","title":"回收所有Eden、Survivor区<br>"}],"id":"70dcc7b7411a","title":"Minor GC/Young GC<br>"},{"parent":"144086fdf009","children":[],"id":"44c8c447963a","title":"Mixed GC<br>"}],"id":"144086fdf009","title":"回收模式<br>"}],"id":"9b2c5a694136","title":"G1<br>"},{"parent":"94c48a9cef88","children":[{"parent":"8e74a22eafd1","children":[],"id":"642ff9bd8dbb","title":"G1分区域 每个区域是有老年代概念的，但是收集器以整个区域为单位收集<br>"},{"parent":"8e74a22eafd1","children":[],"id":"affafbc6a4da","title":"G1回收后马上合并空闲内存，而CMS会在STW的时候合并<br>"}],"id":"8e74a22eafd1","title":"CMS与G1的区别<br>"}],"id":"94c48a9cef88","title":"垃圾回收器<br>"},{"parent":"d61da867cb10","children":[{"parent":"1dfd2da1ccb2","children":[],"id":"09de7462c062","title":"老年代空间不足<br>"},{"parent":"1dfd2da1ccb2","children":[],"id":"7681c3724945","title":"system.gc（）通知JVM进行Full GC<br>"},{"parent":"1dfd2da1ccb2","children":[],"id":"d1038ecedd23","title":"持久代空间不足<br>"}],"id":"1dfd2da1ccb2","title":"Full GC<br>"},{"parent":"d61da867cb10","children":[{"parent":"a7686c6448fb","children":[],"id":"750a3f631db0","title":"在执行垃圾收集算法时，Java应用程序的其他所有线程都被挂起。是Java中一种全局暂停现象，全局停顿，所有Java代码停止，Native代码可以执行，但不能与JVM交互<br>"}],"id":"a7686c6448fb","title":"STW(Stop The World)<br>"},{"parent":"d61da867cb10","children":[{"parent":"a7d301228e2a","children":[],"id":"92f20719ac8a","title":"设置堆的最大最小值 -xms -xmx<br>"},{"parent":"a7d301228e2a","children":[{"parent":"0715cab0f70c","children":[{"parent":"59b27c4fd939","children":[],"id":"e21f787b02c5","title":"防止年轻代堆收缩：老年代同理<br>"}],"id":"59b27c4fd939","title":"-XX:newSize设置绝对大小<br>"}],"id":"0715cab0f70c","title":"调整老年和年轻代的比例<br>"},{"parent":"a7d301228e2a","children":[],"id":"ff8918fe5f06","title":"主要看是否存在更多持久对象和临时对象<br>"},{"parent":"a7d301228e2a","children":[],"id":"24446fac73b9","title":"观察一段时间 看峰值老年代如何 不影响gc就加大年轻代<br>"},{"parent":"a7d301228e2a","children":[],"id":"9204d8b66e5f","title":"配置好的机器可以用 并发收集算法<br>"},{"parent":"a7d301228e2a","children":[],"id":"b58af9729fb9","title":"每个线程默认会开启1M的堆栈 存放栈帧 调用参数 局部变量 太大了&nbsp; 500k够了<br>"},{"parent":"a7d301228e2a","children":[],"id":"c001d0fcef30","title":"原则 就是减少GC STW<br>"}],"id":"a7d301228e2a","title":"性能调优<br>"},{"parent":"d61da867cb10","children":[{"parent":"febcbf010f6e","children":[],"id":"0e938c11383a","title":"jasvism<br>"},{"parent":"febcbf010f6e","children":[],"id":"64f7c1d4e0c1","title":"dump<br>"},{"parent":"febcbf010f6e","children":[],"id":"3ae88d77a8e0","title":"监控配置 自动dump<br>"}],"id":"febcbf010f6e","title":"FullGC 内存泄露排查<br>"},{"parent":"d61da867cb10","children":[{"parent":"163f2b3e6279","children":[{"parent":"341ea9d0a8b1","children":[{"parent":"7b2c9c719a5b","children":[],"id":"52bb357adfd0","title":"开启逃逸分析：-XX:+DoEscapeAnalysis<br>关闭逃逸分析：-XX:-DoEscapeAnalysis<br>显示分析结果：-XX:+PrintEscapeAnalysis<br>"}],"id":"7b2c9c719a5b","title":"Java Hotspot 虚拟机可以分析新创建对象的使用范围，并决定是否在 Java 堆上分配内存的一项技术<br>"}],"id":"341ea9d0a8b1","title":"概念<br>"},{"parent":"163f2b3e6279","children":[{"parent":"2951be8a5ab7","children":[{"parent":"19414f3dfc6b","children":[],"id":"b8919c87025b","title":"即一个对象的作用范围逃出了当前方法或者当前线程<br>"},{"parent":"19414f3dfc6b","children":[{"parent":"f3f5a4d41ca7","children":[],"id":"78e7003b3c6c","title":"对象是一个静态变量<br>"},{"parent":"f3f5a4d41ca7","children":[],"id":"57d7fe2eba87","title":"对象是一个已经发生逃逸的对象<br>"},{"parent":"f3f5a4d41ca7","children":[],"id":"f72de2af0484","title":"对象作为当前方法的返回值<br>"}],"id":"f3f5a4d41ca7","title":"场景<br>"}],"id":"19414f3dfc6b","title":"全局逃逸<br>"},{"parent":"2951be8a5ab7","children":[{"parent":"078cd7effe02","children":[],"id":"c4a9820ede62","title":"即一个对象被作为方法参数传递或者被参数引用，但在调用过程中不会发生全局逃逸<br>"}],"id":"078cd7effe02","title":"参数级逃逸<br>"},{"parent":"2951be8a5ab7","children":[{"parent":"dfb6626fa13e","children":[],"id":"a1f76aa518e6","title":"即方法中的对象没有发生逃逸<br>"}],"id":"dfb6626fa13e","title":"没有逃逸<br>"}],"id":"2951be8a5ab7","title":"逃逸状态<br>"},{"parent":"163f2b3e6279","children":[{"parent":"74538b6f0667","children":[{"parent":"22052ecf3777","children":[],"id":"1bb3c11ad593","title":"开启锁消除：-XX:+EliminateLocks<br>关闭锁消除：-XX:-EliminateLocks<br>"}],"id":"22052ecf3777","title":"锁消除<br>"},{"parent":"74538b6f0667","children":[{"parent":"d19fda364ab3","children":[],"id":"618f15119b55","title":"开启标量替换：-XX:+EliminateAllocations<br>关闭标量替换：-XX:-EliminateAllocations<br>显示标量替换详情：-XX:+PrintEliminateAllocations<br>"}],"id":"d19fda364ab3","title":"标量替换<br>"},{"parent":"74538b6f0667","children":[{"parent":"58e0672d4093","children":[],"id":"852d0a874d5c","title":"当对象没有发生逃逸时，该对象就可以通过标量替换分解成成员标量分配在栈内存中，和方法的生命周期一致，随着栈帧出栈时销毁，减少了 GC 压力，提高了应用程序性能<br>"}],"id":"58e0672d4093","title":"栈上分配<br>"}],"id":"74538b6f0667","title":"逃逸分析优化<br>"},{"parent":"163f2b3e6279","children":[{"parent":"0c9fe2ae4750","children":[],"id":"d4e70d6dd352","title":"在平时开发过程中尽可能的控制变量的作用范围了，变量范围越小越好，让虚拟机尽可能有优化的空间<br>"}],"id":"0c9fe2ae4750","title":"结论<br>"}],"id":"163f2b3e6279","title":"逃逸分析<br>"},{"parent":"d61da867cb10","children":[{"parent":"f01a444aff49","children":[{"parent":"e5b794712d9f","children":[{"parent":"69d4722f6204","children":[],"id":"9058d548b851","title":"当堆内存（Heap Space）没有足够空间存放新创建的对象时，会抛出<br>"},{"parent":"69d4722f6204","children":[{"parent":"82077529709e","children":[],"id":"e5efc4b324bd","title":"请求创建一个超大对象，通常是一个大数组<br>"},{"parent":"82077529709e","children":[],"id":"f6c6d999b2e2","title":"超出预期的访问量/数据量，通常是上游系统请求流量飙升，常见于各类促销/秒杀活动，可以结合业务流量指标排查是否有尖状峰值<br>"},{"parent":"82077529709e","children":[],"id":"810ed21d175e","title":"过度使用终结器（Finalizer），该对象没有立即被 GC<br>"},{"parent":"82077529709e","children":[],"id":"12f8716631c0","title":"内存泄漏（Memory Leak），大量对象引用没有释放，JVM 无法对其自动回收，常见于使用了 File 等资源没有回收<br>"}],"id":"82077529709e","title":"场景<br>"},{"parent":"69d4722f6204","children":[{"parent":"bb0032b74d7a","children":[],"id":"4508b1e1415b","title":"针对大部分情况，通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可<br>"},{"parent":"bb0032b74d7a","children":[],"id":"8a21793fb810","title":"如果是超大对象，可以检查其合理性，比如是否一次性查询了数据库全部结果，而没有做结果数限制<br>"},{"parent":"bb0032b74d7a","children":[],"id":"7970de1c13ce","title":"如果是业务峰值压力，可以考虑添加机器资源，或者做限流降级<br>"},{"parent":"bb0032b74d7a","children":[],"id":"61721dad15fd","title":"如果是内存泄漏，需要找到持有的对象，修改代码设计，比如关闭没有释放的连接<br>"}],"id":"bb0032b74d7a","title":"解决方案<br>"}],"id":"69d4722f6204","title":"Java heap space<br>"},{"parent":"e5b794712d9f","children":[{"parent":"16ea8d379757","children":[],"id":"089b25ad7859","title":"当 Java 进程花费 98% 以上的时间执行 GC，但只恢复了不到 2% 的内存，且该动作连续重复了 5 次，就会抛出<br>"},{"parent":"16ea8d379757","children":[],"id":"4cccd7722816","title":"场景与解决方案与Java heap space类似<br>"}],"id":"16ea8d379757","title":"GC overhead limit exceeded<br>"},{"parent":"e5b794712d9f","children":[{"parent":"27f250898d47","children":[],"id":"b5735a767281","title":"该错误表示永久代（Permanent Generation）已用满，通常是因为加载的 class 数目太多或体积太大<br>"},{"parent":"27f250898d47","children":[{"parent":"7adba9e7ee50","children":[],"id":"f92f654bc6f2","title":"程序启动报错，修改 -XX:MaxPermSize 启动参数，调大永久代空间<br>"},{"parent":"7adba9e7ee50","children":[],"id":"a1ba084f84ae","title":"应用重新部署时报错，很可能是没有应用没有重启，导致加载了多份 class 信息，只需重启 JVM 即可解决<br>"},{"parent":"7adba9e7ee50","children":[],"id":"ac1dd7e1efe5","title":"运行时报错，应用程序可能会动态创建大量 class，而这些 class 的生命周期很短暂，但是 JVM 默认不会卸载 class，可以设置 -XX:+CMSClassUnloadingEnabled 和 -XX:+UseConcMarkSweepGC这两个参数允许 JVM 卸载 class<br>"},{"parent":"7adba9e7ee50","children":[],"id":"9908b8d183cb","title":"如果上述方法无法解决，可以通过 jmap 命令 dump 内存对象 jmap-dump:format=b,file=dump.hprof&lt;process-id&gt; ，然后利用 Eclipse MAT功能逐一分析开销最大的 classloader 和重复 class<br>"}],"id":"7adba9e7ee50","title":"解决方案<br>"}],"id":"27f250898d47","title":"Permgen space<br>"},{"parent":"e5b794712d9f","children":[{"parent":"5d9794eb44e3","children":[],"id":"ff167c68cd26","title":"该错误表示 Metaspace 已被用满，通常是因为加载的 class 数目太多或体积太大<br>"},{"parent":"5d9794eb44e3","children":[],"id":"9e30222f9cdd","title":"场景与解决方案与Permgen space类似，需注意调整元空间大小参数为&nbsp;-XX:MaxMetaspaceSize<br>"}],"id":"5d9794eb44e3","title":"Metaspace（元空间）<br>"},{"parent":"e5b794712d9f","children":[{"parent":"88733d6fd9e1","children":[],"id":"4b3170dc49e7","title":"当 JVM 向底层操作系统请求创建一个新的 Native 线程时，如果没有足够的资源分配就会报此类错误<br>"},{"parent":"88733d6fd9e1","children":[{"parent":"2de99f106fd9","children":[],"id":"d2894deebffe","title":"线程数超过操作系统最大线程数 ulimit 限制<br>"},{"parent":"2de99f106fd9","children":[],"id":"6b4a4957f1f7","title":"线程数超过 kernel.pid_max（只能重启）<br>"},{"parent":"2de99f106fd9","children":[],"id":"697aa13d5593","title":"Native 内存不足<br>"}],"id":"2de99f106fd9","title":"场景<br>"},{"parent":"88733d6fd9e1","children":[{"parent":"d6e225b4cdbd","children":[],"id":"8f7d0718c783","title":"升级配置，为机器提供更多的内存<br>"},{"parent":"d6e225b4cdbd","children":[],"id":"fd893dac8e71","title":"降低 Java Heap Space 大小<br>"},{"parent":"d6e225b4cdbd","children":[],"id":"96d3dc50bfcf","title":"修复应用程序的线程泄漏问题<br>"},{"parent":"d6e225b4cdbd","children":[],"id":"0ab47fa12a24","title":"限制线程池大小<br>"},{"parent":"d6e225b4cdbd","children":[],"id":"28f3069b69cc","title":"使用 -Xss 参数减少线程栈的大小<br>"},{"parent":"d6e225b4cdbd","children":[],"id":"5b123c27cf17","title":"调高 OS 层面的线程最大数：执行 ulimia-a 查看最大线程数限制，使用 ulimit-u xxx 调整最大线程数限制<br>"}],"id":"d6e225b4cdbd","title":"解决方案<br>"}],"id":"88733d6fd9e1","title":"Unable to create new native thread<br>"},{"parent":"e5b794712d9f","children":[{"parent":"a207e8502e59","children":[],"id":"ee75aeab507e","title":"虚拟内存（Virtual Memory）由物理内存（Physical Memory）和交换空间（Swap Space）两部分组成。当运行时程序请求的虚拟内存溢出时就会报 Outof swap space? 错误<br>"},{"parent":"a207e8502e59","children":[{"parent":"e9aff14f85ee","children":[],"id":"f5d926e1a54d","title":"地址空间不足<br>"},{"parent":"e9aff14f85ee","children":[],"id":"0ab3e7bd4ce0","title":"物理内存已耗光<br>"},{"parent":"e9aff14f85ee","children":[],"id":"a719e6915e62","title":"应用程序的本地内存泄漏（native leak），例如不断申请本地内存，却不释放<br>"},{"parent":"e9aff14f85ee","children":[],"id":"95178fc0d9c2","title":"执行 jmap-histo:live&lt;pid&gt; 命令，强制执行 Full GC；如果几次执行后内存明显下降，则基本确认为 Direct ByteBuffer 问题<br>"}],"id":"e9aff14f85ee","title":"场景<br>"},{"parent":"a207e8502e59","children":[{"parent":"69838d20af49","children":[],"id":"f6eb75443db8","title":"升级地址空间为 64 bit<br>"},{"parent":"69838d20af49","children":[],"id":"4c2d62f88cd0","title":"使用 Arthas 检查是否为 Inflater/Deflater 解压缩问题，如果是，则显式调用 end 方法<br>"},{"parent":"69838d20af49","children":[],"id":"8149b8d47a40","title":"Direct ByteBuffer 问题可以通过启动参数 -XX:MaxDirectMemorySize 调低阈值<br>"},{"parent":"69838d20af49","children":[],"id":"67cf9ef9b29d","title":"升级服务器配置/隔离部署，避免争用<br>"}],"id":"69838d20af49","title":"解决方案<br>"}],"id":"a207e8502e59","title":"Out of swap space？<br>"},{"parent":"e5b794712d9f","children":[{"parent":"fc5d0b351a1a","children":[],"id":"dcbc57e34a45","title":"有一种内核作业（Kernel Job）名为 Out of Memory Killer，它会在可用内存极低的情况下“杀死”（kill）某些进程。OOM Killer 会对所有进程进行打分，然后将评分较低的进程“杀死”，Killprocessorsacrifice child 错误不是由 JVM 层面触发的，而是由操作系统层面触发的<br>"},{"parent":"fc5d0b351a1a","children":[{"parent":"959ac5e8a270","children":[],"id":"a97b65d73f88","title":"默认情况下，Linux 内核允许进程申请的内存总量大于系统可用内存，通过这种“错峰复用”的方式可以更有效的利用系统资源。<br>然而，这种方式也会无可避免地带来一定的“超卖”风险。例如某些进程持续占用系统内存，然后导致其他进程没有可用内存。此时，系统将自动激活 OOM Killer，寻找评分低的进程，并将其“杀死”，释放内存资源<br>"}],"id":"959ac5e8a270","title":"场景<br>"},{"parent":"fc5d0b351a1a","children":[{"parent":"a7ada05bbd00","children":[],"id":"2b1d922217cf","title":"升级服务器配置/隔离部署，避免争用<br>"},{"parent":"a7ada05bbd00","children":[],"id":"b9502a1fe60b","title":"OOM Killer 调优<br>"}],"id":"a7ada05bbd00","title":"解决方案<br>"}],"id":"fc5d0b351a1a","title":"Kill process or sacrifice child<br>"},{"parent":"e5b794712d9f","children":[{"parent":"4dfd99c9bc90","children":[],"id":"52471cca5e33","title":"JVM 限制了数组的最大长度，该错误表示程序请求创建的数组超过最大长度限制<br>"},{"parent":"4dfd99c9bc90","children":[{"parent":"7a7846a84d01","children":[],"id":"ef42a376573a","title":"检查代码，确认业务是否需要创建如此大的数组，是否可以拆分为多个块，分批执行<br>"}],"id":"7a7846a84d01","title":"解决方案<br>"}],"id":"4dfd99c9bc90","title":"Requested array size exceeds VM limit<br>"},{"parent":"e5b794712d9f","children":[{"parent":"926260b12712","children":[],"id":"7deb1188a580","title":"Direct ByteBuffer 的默认大小为 64 MB，一旦使用超出限制，就会抛出 Directbuffer memory 错误<br>"},{"parent":"926260b12712","children":[{"parent":"c8bb4e5de982","children":[],"id":"558937529cce","title":"Java 只能通过 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer，因此，可以通过 Arthas 等在线诊断工具拦截该方法进行排查<br>"},{"parent":"c8bb4e5de982","children":[],"id":"04d0566021d0","title":"检查是否直接或间接使用了 NIO，如 netty，jetty 等<br>"},{"parent":"c8bb4e5de982","children":[],"id":"a5ff4af8fc72","title":"通过启动参数 -XX:MaxDirectMemorySize 调整 Direct ByteBuffer 的上限值<br>"},{"parent":"c8bb4e5de982","children":[],"id":"eb97fd5380b3","title":"检查 JVM 参数是否有 -XX:+DisableExplicitGC 选项，如果有就去掉，因为该参数会使 System.gc（）失效<br>"},{"parent":"c8bb4e5de982","children":[],"id":"e19382936700","title":"检查堆外内存使用代码，确认是否存在内存泄漏；或者通过反射调用 sun.misc.Cleaner 的 clean() 方法来主动释放被 Direct ByteBuffer 持有的内存空间<br>"},{"parent":"c8bb4e5de982","children":[],"id":"b338bbfe6fec","title":"内存容量确实不足，升级配置<br>"}],"id":"c8bb4e5de982","title":"解决方案<br>"}],"id":"926260b12712","title":"Direct buffer memory<br>"}],"collapsed":false,"id":"e5b794712d9f","title":"OOM<br>"},{"parent":"f01a444aff49","children":[],"id":"4fca236e0053","title":"内存泄露"},{"parent":"f01a444aff49","children":[],"id":"31b72a753814","title":"线程死锁<br>"},{"parent":"f01a444aff49","children":[],"id":"f5310ade47dc","title":"锁争用<br>"},{"parent":"f01a444aff49","children":[],"id":"be43a23fb08e","title":"Java进程消耗CPU过高<br>"}],"id":"f01a444aff49","title":"JVM调优<br>"},{"parent":"d61da867cb10","children":[{"parent":"5c6067b84b1e","children":[],"id":"03fed9bf17f7","title":"Jconsole"},{"parent":"5c6067b84b1e","children":[],"id":"b0ec90eb542b","title":"Jprofiler"},{"parent":"5c6067b84b1e","children":[],"id":"baa86063ec3e","title":"jvisualvm"},{"parent":"5c6067b84b1e","children":[],"id":"c7c342ec412e","title":"MAT"}],"id":"5c6067b84b1e","title":"JVM性能检测工具"},{"parent":"d61da867cb10","children":[{"parent":"9a58c2256f22","children":[],"id":"541fa6a430b7","title":"help dump"},{"parent":"9a58c2256f22","children":[],"id":"63ed06e60104","title":"生产机 dump"},{"parent":"9a58c2256f22","children":[],"id":"29df5ce1b166","title":"mat"},{"parent":"9a58c2256f22","children":[],"id":"a2815d62f70f","title":"jmap"},{"parent":"9a58c2256f22","children":[],"id":"aa872dbc3855","title":"-helpdump"}],"id":"9a58c2256f22","title":"内存泄露"},{"parent":"d61da867cb10","children":[{"parent":"dbe9489e1531","children":[],"id":"30af4ef10e6d","title":"topc -c"},{"parent":"dbe9489e1531","children":[],"id":"f413c8870538","title":"top -Hp pid"},{"parent":"dbe9489e1531","children":[{"parent":"d7a7bb2d1ce7","children":[],"id":"3d609323d047","title":"进制转换"}],"id":"d7a7bb2d1ce7","title":"jstack"},{"parent":"dbe9489e1531","children":[],"id":"28dc67e9bcc8","title":"cat<br>"}],"id":"dbe9489e1531","title":"CPU100%"}],"collapsed":true,"id":"d61da867cb10","title":"JVM"},{"parent":"root","lineStyle":{"randomLineColor":"#FF8502"},"children":[],"id":"ecbf53fba47d","title":"多线程"},{"parent":"root","lineStyle":{"randomLineColor":"#F5479C"},"children":[{"parent":"d1383b5af44b","children":[{"parent":"6846db773567","children":[{"parent":"9cfcc064de09","children":[],"id":"3cc1cea3b5d2","title":"Spring 中的 Bean 默认都是单例的"}],"id":"9cfcc064de09","title":"单例模式"},{"parent":"6846db773567","children":[{"parent":"70e04d48b002","children":[],"id":"79b9ad909b14","title":"Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象"}],"id":"70e04d48b002","title":"工厂模式"},{"parent":"6846db773567","children":[{"parent":"18539d17e1a2","children":[],"id":"f10b087651f0","title":"Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller"}],"id":"18539d17e1a2","title":"适配器模式<br>"},{"parent":"6846db773567","children":[{"parent":"aa0d772a111e","children":[],"id":"458c4c117473","title":"Spring AOP 功能的实现"}],"id":"aa0d772a111e","title":"代理设计模式<br>"},{"parent":"6846db773567","children":[{"parent":"cf993fcce1fa","children":[],"id":"5a9fd93671a5","title":"Spring 事件驱动模型就是观察者模式很经典的一个应用"}],"id":"cf993fcce1fa","title":"观察者模式<br>"},{"parent":"6846db773567","children":[],"id":"4761f7394f26","title":"... ..."}],"id":"6846db773567","title":"设计模式"},{"parent":"d1383b5af44b","children":[{"parent":"6be9b379c857","children":[{"image":{"w":721,"h":306,"url":"http://cdn.processon.com/60d5841c07912920c8095f17?e=1624609325&amp;token=trhI0BY8QfVrIGn9nENop6JAc6l5nZuxhjQ62UfM:vvIb8jvxokB9aizRUTjAy8Dz4NI="},"parent":"8218ea06bbce","children":[{"parent":"f7aea39c3b24","children":[],"id":"408ac2ef4844","title":"Bean 容器找到配置文件中 Spring Bean 的定义"},{"parent":"f7aea39c3b24","children":[],"id":"34992c358614","title":"Bean 容器利用 Java Reflection API 创建一个Bean的实例"},{"parent":"f7aea39c3b24","children":[],"id":"e7981f427e63","title":"如果涉及到一些属性值 利用 set()方法设置一些属性值"},{"parent":"f7aea39c3b24","children":[],"id":"dbf75a139571","title":"如果 Bean 实现了 BeanNameAware 接口，调用 setBeanName()方法，传入Bean的名字"},{"parent":"f7aea39c3b24","children":[],"id":"787517f23307","title":"如果 Bean 实现了 BeanClassLoaderAware 接口，调用 setBeanClassLoader()方法，传入 ClassLoader对象的实例"},{"parent":"f7aea39c3b24","children":[],"id":"6466197a3ddf","title":"如果Bean实现了 BeanFactoryAware 接口，调用 setBeanClassLoader()方法，传入 ClassLoader 对象的实例"},{"parent":"f7aea39c3b24","children":[],"id":"feac889b1b8d","title":"与上面的类似，如果实现了其他 *.Aware接口，就调用相应的方法"},{"parent":"f7aea39c3b24","children":[],"id":"91b027b10223","title":"如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象，执行postProcessBeforeInitialization() 方法"},{"parent":"f7aea39c3b24","children":[],"id":"b1af63401bf5","title":"如果Bean实现了InitializingBean接口，执行afterPropertiesSet()方法<br>"},{"parent":"f7aea39c3b24","children":[],"id":"37fa23a9c16a","title":"如果 Bean 在配置文件中的定义包含 init-method 属性，执行指定的方法"},{"parent":"f7aea39c3b24","children":[],"id":"b57d01a401e0","title":"如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象，执行postProcessAfterInitialization() 方法"},{"parent":"f7aea39c3b24","children":[],"id":"baf7bd961172","title":"当要销毁 Bean 的时候，如果 Bean 实现了 DisposableBean 接口，执行 destroy() 方法"},{"parent":"f7aea39c3b24","children":[],"id":"da536275f445","title":"当要销毁 Bean 的时候，如果 Bean 在配置文件中的定义包含 destroy-method 属性，执行指定的方法"}],"style":{"text-align":"center"},"id":"f7aea39c3b24","title":"Spring Bean 生命周期"}],"id":"8218ea06bbce","title":"生命周期"},{"parent":"6be9b379c857","children":[{"parent":"780f6b30ba11","children":[{"parent":"69fa69a6315a","children":[],"id":"847478f4b37e","title":"唯一 bean 实例，Spring 中的 bean 默认都是单例的<br>"}],"id":"69fa69a6315a","title":"singleton <br>"},{"parent":"780f6b30ba11","children":[{"parent":"61779d764f15","children":[],"id":"1b7c8dc3e783","title":"每次请求都会创建一个新的 bean 实例<br>"}],"id":"61779d764f15","title":"prototype <br>"},{"parent":"780f6b30ba11","children":[{"parent":"b84f40579383","children":[],"id":"a2c0221f2a17","title":"每一次HTTP请求都会产生一个新的bean，该bean仅在当前HTTP request内有效<br>"}],"id":"b84f40579383","title":"request<br>"},{"parent":"780f6b30ba11","children":[{"parent":"2980654747ac","children":[],"id":"df20b0568d9b","title":"每一次HTTP请求都会产生一个新的 bean，该bean仅在当前 HTTP session 内有效<br>"}],"id":"2980654747ac","title":"session<br>"}],"id":"780f6b30ba11","title":"作用域"},{"parent":"6be9b379c857","children":[{"parent":"94d2392b941d","children":[],"id":"f6facb5c7bdf","title":"单例 bean 存在线程问题，主要是因为当多个线程操作同一个对象的时候，对这个对象的非静态成员变量的写操作会存在线程安全问题<br>"},{"parent":"94d2392b941d","children":[{"parent":"c50f84f9dd07","children":[],"id":"6a052cae7ac3","title":"在Bean对象中尽量避免定义可变的成员变量（不太现实）"},{"parent":"c50f84f9dd07","children":[],"id":"be83d8ee0d9c","title":"在类中定义一个ThreadLocal成员变量，将需要的可变成员变量保存在 ThreadLocal 中（推荐的一种方式）"}],"id":"c50f84f9dd07","title":"解决方案"}],"id":"94d2392b941d","title":"单例Bean线程不安全"}],"id":"6be9b379c857","title":"Bean"},{"parent":"d1383b5af44b","children":[{"parent":"2bd94a15f271","children":[{"parent":"db24e1d06ef6","children":[],"id":"c975e1461b17","title":"循环依赖其实就是循环引用，一个或多个对象实例之间存在直接或间接的依赖关系，这种依赖关系构成了构成一个环形调用"}],"id":"db24e1d06ef6","title":"定义"},{"parent":"2bd94a15f271","children":[{"parent":"6429ee8470d0","children":[{"parent":"b444d530df33","children":[],"id":"fe4447498b81","title":"可以解决"}],"id":"b444d530df33","title":"单例setter注入<br>"},{"parent":"6429ee8470d0","children":[{"parent":"a628a0c088fe","children":[],"id":"7d2affdf5098","title":"不能解决"}],"id":"a628a0c088fe","title":"多例setter注入"},{"parent":"6429ee8470d0","children":[{"parent":"52ccb73aaa1c","children":[],"id":"c5d72e9224fc","title":"不能解决"}],"id":"52ccb73aaa1c","title":"构造器注入"},{"parent":"6429ee8470d0","children":[{"parent":"e0ddcb98872d","children":[],"id":"8bc315c0e4f4","title":"有可能解决"}],"id":"e0ddcb98872d","title":"单例的代理对象注入<br>"},{"parent":"6429ee8470d0","children":[{"parent":"c1883b1d1d04","children":[],"id":"30c4d71d67b7","title":"不能解决"}],"id":"c1883b1d1d04","title":"DependOn循环依赖<br>"}],"id":"6429ee8470d0","title":"主要场景"},{"parent":"2bd94a15f271","children":[{"parent":"34656efe2153","children":[],"id":"07eedf3c5055","title":"一级缓存: 用于保存实例化、注入、初始化完成的bean实例<br>"},{"parent":"34656efe2153","children":[],"id":"5389148434c4","title":"二级缓存: 用于保存实例化完成的bean实例"},{"parent":"34656efe2153","children":[],"id":"de57eefcdc3f","title":"三级缓存: 用于保存bean创建工厂，以便于后面扩展有机会创建代理对象"}],"id":"34656efe2153","title":"三级缓存"},{"parent":"2bd94a15f271","children":[{"image":{"w":720,"h":309,"url":"http://cdn.processon.com/60d58be3e0b34d7f1166296f?e=1624611315&amp;token=trhI0BY8QfVrIGn9nENop6JAc6l5nZuxhjQ62UfM:Szb4rjzK4-RP973_dTYTIc1x1yg="},"parent":"7537dcdb4537","children":[],"style":{"text-align":"center"},"id":"813c5d07800b","title":"Spring解决循环依赖"}],"id":"7537dcdb4537","title":"Spring如何解决循环依赖?"},{"parent":"2bd94a15f271","children":[{"parent":"6809a28c84fb","children":[{"parent":"0b65efef98be","children":[],"id":"edb8657d8633","title":"使用@Lazy注解，延迟加载<br>"},{"parent":"0b65efef98be","children":[],"id":"713bcafda8f8","title":"使用@DependsOn注解，指定加载先后关系<br>"},{"parent":"0b65efef98be","children":[],"id":"ecde0ac2eff5","title":"修改文件名称，改变循环依赖类的加载顺序<br>"}],"id":"0b65efef98be","title":"生成代理对象产生的循环依赖<br>"},{"parent":"6809a28c84fb","children":[{"parent":"19fc35c94a10","children":[],"id":"a898194da78e","title":"找到@DependsOn注解循环依赖的地方，迫使它不循环依赖<br>"}],"id":"19fc35c94a10","title":"使用@DependsOn产生的循环依赖<br>"},{"parent":"6809a28c84fb","children":[{"parent":"67569155e5e1","children":[],"id":"fbefaf05f15b","title":"把bean改成单例"}],"id":"67569155e5e1","title":"多例循环依赖"},{"parent":"6809a28c84fb","children":[{"parent":"d1721015c62e","children":[],"id":"086c2cf4a1f1","title":"使用@Lazy注解解决"}],"id":"d1721015c62e","title":"构造器循环依赖"}],"id":"6809a28c84fb","title":"Spring无法解决的循环依赖怎么解决?"}],"id":"2bd94a15f271","title":"循环依赖"},{"parent":"d1383b5af44b","children":[{"parent":"7525c1be5308","children":[],"id":"2efd2dd7603f","title":"Spring是父容器，SpringMVC是子容器，Spring父容器中注册的Bean对SpringMVC子容器是可见的，反之则不行"}],"id":"7525c1be5308","title":"父子容器"},{"parent":"d1383b5af44b","children":[{"parent":"2d661386d933","children":[],"id":"9dedda0f87c3","title":"采用不同的连接器"},{"parent":"2d661386d933","children":[{"parent":"9478703f6033","children":[],"id":"906c6c2186d9","title":"共享链接"}],"id":"9478703f6033","title":"用AOP 新建立了一个 链接"},{"parent":"2d661386d933","children":[],"id":"c0c888325014","title":"ThreadLocal 当前事务"},{"parent":"2d661386d933","children":[],"id":"cdf3bf19398a","title":"前提是 关闭AutoCommit"}],"id":"2d661386d933","title":"事务实现原理"},{"parent":"d1383b5af44b","children":[{"parent":"3fc3209ef908","children":[{"parent":"00348542df62","children":[{"parent":"7e65680e758d","children":[],"id":"9d4a3af2166d","title":"如果当前存在事务，则加入该事务；如果当前没有事务，则创建一个新的事务"}],"id":"7e65680e758d","title":"PROPAGATION_REQUIRED"},{"parent":"00348542df62","children":[{"parent":"fbcd20add78f","children":[],"id":"bb981fa897b5","title":"如果当前存在事务，则加入该事务；如果当前没有事务，则以非事务的方式继续运行"}],"id":"fbcd20add78f","title":"PROPAGATION_SUPPORTS"},{"parent":"00348542df62","children":[{"parent":"fa9c9e3e91b3","children":[],"id":"f0bd2ab03cb3","title":"如果当前存在事务，则加入该事务；如果当前没有事务，则抛出异常。（mandatory：强制性）"}],"id":"fa9c9e3e91b3","title":"PROPAGATION_MANDATORY"}],"id":"00348542df62","title":"支持当前事务的情况"},{"parent":"3fc3209ef908","children":[{"parent":"be43db2ade73","children":[{"parent":"274fe413f9a9","children":[],"id":"a9835b9de879","title":"创建一个新的事务，如果当前存在事务，则把当前事务挂起"}],"id":"274fe413f9a9","title":"PROPAGATION_REQUIRES_NEW"},{"parent":"be43db2ade73","children":[{"parent":"6de124ee0323","children":[],"id":"73808be05652","title":"以非事务方式运行，如果当前存在事务，则把当前事务挂起"}],"id":"6de124ee0323","title":"PROPAGATION_NOT_SUPPORTED"},{"parent":"be43db2ade73","children":[{"parent":"b7f5bd597d94","children":[],"id":"aade3bc122d2","title":"以非事务方式运行，如果当前存在事务，则抛出异常"}],"id":"b7f5bd597d94","title":"PROPAGATION_NEVER"}],"id":"be43db2ade73","title":"不支持当前事务的情况"},{"parent":"3fc3209ef908","children":[{"parent":"c633fc129e54","children":[{"parent":"7f933d08995d","children":[],"id":"29d2241160ac","title":"如果当前存在事务，则创建一个事务作为当前事务的嵌套事务来运行；如果当前没有事务，则该取值等价于PROPAGATION_REQUIRED"}],"id":"7f933d08995d","title":"PROPAGATION_NESTED"}],"id":"c633fc129e54","title":"其他情况"}],"id":"3fc3209ef908","title":"事务的传播行为"},{"parent":"d1383b5af44b","children":[{"parent":"d09326c239bc","children":[{"parent":"0ddcc4fc0973","children":[],"id":"2b143ba58aa6","title":"实现类"}],"id":"0ddcc4fc0973","title":"静态代理"},{"parent":"d09326c239bc","children":[{"parent":"e88e55b4f4a1","children":[{"parent":"93e7c1779448","children":[{"parent":"2b790def7035","children":[{"parent":"8aadeb882937","children":[],"id":"b8325d0d990f","title":"调用具体方法的时候调用invokeHandler"}],"id":"8aadeb882937","title":"java反射机制生成一个代理接口的匿名类"}],"id":"2b790def7035","title":"实现接口"}],"id":"93e7c1779448","title":"JDK动态代理"},{"parent":"e88e55b4f4a1","children":[{"parent":"ad4aa9aa8708","children":[{"parent":"d62f18134c3b","children":[],"id":"f4f870f8a078","title":"修改字节码生成子类去处理"}],"id":"d62f18134c3b","title":"asm字节码编辑技术动态创建类 基于classLoad装载"}],"id":"ad4aa9aa8708","title":"cglib"}],"id":"e88e55b4f4a1","title":"动态代理"}],"id":"d09326c239bc","title":"AOP"},{"parent":"d1383b5af44b","children":[],"id":"321b75efaafa","title":"IOC"}],"collapsed":true,"id":"d1383b5af44b","title":"Spring<br>"},{"parent":"root","lineStyle":{"randomLineColor":"#4D69FD"},"children":[{"parent":"93b7f849fef3","children":[{"parent":"2d833717c2db","children":[{"parent":"688bd93ca78f","children":[{"parent":"adb794ebf462","children":[],"id":"28b48a3fc7d9","title":"MVCC支持高并发、四个隔离级别（默认为可重复读）、支持事务操作、聚簇索引<br>"}],"id":"adb794ebf462","title":"InnoDB<br>"},{"parent":"688bd93ca78f","children":[{"parent":"9851e7c98465","children":[],"id":"6cda6ad38022","title":"全文索引、压缩、空间函数、崩溃后无法安全恢复<br>"}],"id":"9851e7c98465","title":"MyISAM<br>"}],"id":"688bd93ca78f","title":"常见"},{"parent":"2d833717c2db","children":[{"parent":"259d8b591849","children":[{"parent":"8af8eb9d1749","children":[],"id":"faac28b5b4bf","title":"只支持insert、select操作，适合日志和数据采集<br>"}],"id":"8af8eb9d1749","title":"Archive<br>"},{"parent":"259d8b591849","children":[{"parent":"246578695747","children":[],"id":"6b252c309014","title":"会丢弃所有插入数据，不做保存，记录Blackhole日志，可以用于复制数据库到备份库<br>"}],"id":"246578695747","title":"Blackhole<br>"},{"parent":"259d8b591849","children":[{"parent":"22c9f95864a3","children":[],"id":"c91fe24e6951","title":"可以将CSV文件作为MySQL表处理，不支持索引<br>"}],"id":"22c9f95864a3","title":"CSV<br>"},{"parent":"259d8b591849","children":[{"parent":"4c62ccaebd05","children":[],"id":"1707d3b04d53","title":"访问MySQL服务器的一个代理，创建远程到MySQL服务器的客户端连接，默认禁用<br>"}],"id":"4c62ccaebd05","title":"Federated<br>"},{"parent":"259d8b591849","children":[{"parent":"7c87e4ff16d1","children":[],"id":"66796cc84a6b","title":"数据保存在内存中，不需要磁盘I/O，重启后数据会丢失但是表结构会保留<br>"}],"id":"7c87e4ff16d1","title":"Memory<br>"},{"parent":"259d8b591849","children":[{"parent":"7e8ec900b7d9","children":[],"id":"260f221a82a2","title":"MyISAM变种，可以用于日志或数据仓库，已被放弃<br>"}],"id":"7e8ec900b7d9","title":"Merge<br>"},{"parent":"259d8b591849","children":[{"parent":"deaf720f74d6","children":[],"id":"f24022cd65fd","title":"集群引擎<br>"}],"id":"deaf720f74d6","title":"NDB<br>"}],"id":"259d8b591849","title":"其他（可做了解）"}],"collapsed":false,"id":"2d833717c2db","title":"存储引擎"},{"parent":"93b7f849fef3","children":[{"parent":"a5966d5788f2","children":[{"parent":"7adc8a1c18d2","children":[],"id":"a4370f67b89b","title":"binlog记录了数据库表结构和表数据变更，比如update/delete/insert/truncate/create<br>"},{"parent":"7adc8a1c18d2","children":[],"id":"4aade95fc08c","title":"主要用来复制和恢复数据"}],"id":"7adc8a1c18d2","title":"binlog<br>"},{"parent":"a5966d5788f2","children":[{"parent":"7577f673f85c","children":[],"id":"31aeaae43467","title":"在写入内存后会产生redo log，记录本次在某个页上做了什么修改<br>"},{"parent":"7577f673f85c","children":[],"id":"142d34087f0c","title":"恢复写入内存但数据还没真正写到磁盘的数据，redo log记载的是物理变化，文件的体积很小，恢复速度很快<br>"}],"id":"7577f673f85c","title":"redo log<br>"},{"parent":"a5966d5788f2","children":[{"parent":"4814832d3190","children":[],"id":"c90806ae3f83","title":"undo log是逻辑日志，存储着修改之前的数据，相当于一个前版本<br>"},{"parent":"4814832d3190","children":[],"id":"99805bbdffab","title":"用来回滚和多版本控制"}],"id":"4814832d3190","title":"undo log<br>"},{"parent":"a5966d5788f2","children":[{"parent":"5dfacdc162c1","children":[],"id":"8d3250892fd5","title":"redo log 记录的是数据的物理变化，binlog 记录的是数据的逻辑变化"},{"parent":"5dfacdc162c1","children":[],"id":"42cefe6fd536","title":"redo log的作用是为持久化而生的，仅存储写入内存但还未刷到磁盘的数据；binlog的作用是复制和恢复而生的，保持主从数据库的一致性，如果整个数据库的数据都被删除了，可以通过binlog恢复，而redo log则不能<br>"},{"parent":"5dfacdc162c1","children":[],"id":"5590b5b15841","title":"redo log是MySQL的InnoDB引擎所产生的；binlog无论MySQL用什么引擎，都会有的"},{"parent":"5dfacdc162c1","children":[{"parent":"fa7e2894f9c9","children":[{"parent":"b2f9cb6a36f3","children":[{"parent":"be9575df9c26","children":[],"id":"8b3e3c83afb7","title":"如果写redo log失败了，那我们就认为这次事务有问题，回滚，不再写binlog"},{"parent":"be9575df9c26","children":[],"id":"a1220b7a6966","title":"如果写redo log成功了，写binlog，写binlog写一半了，但失败了怎么办？我们还是会对这次的事务回滚，将无效的binlog给删除（因为binlog会影响从库的数据，所以需要做删除操作）"},{"parent":"be9575df9c26","children":[],"id":"122c9e026c22","title":"如果写redo log和binlog都成功了，那这次算是事务才会真正成功"}],"id":"be9575df9c26","title":"解析"},{"parent":"b2f9cb6a36f3","children":[{"parent":"be4d6b8613d4","children":[{"parent":"b17221822991","children":[],"id":"9383dc884572","title":"如果redo log写失败了，而binlog写成功了。那假设内存的数据还没来得及落磁盘，机器就挂掉了。那主从服务器的数据就不一致了。（从服务器通过binlog得到最新的数据，而主服务器由于redo log没有记载，没法恢复数据）"},{"parent":"b17221822991","children":[],"id":"4f824c63c2b0","title":"如果redo log写成功了，而binlog写失败了。那从服务器就拿不到最新的数据了"}],"id":"b17221822991","title":"MySQL需要保证redo log和binlog的数据是一致的"}],"id":"be4d6b8613d4","title":"结论"},{"parent":"b2f9cb6a36f3","children":[{"parent":"4c6de79ecd53","children":[{"parent":"9f54d23f7748","children":[{"parent":"4387d2b41401","children":[],"id":"a4c2899d7ffa","title":"阶段1：InnoDBredo log 写盘，InnoDB 事务进入 prepare（做好准备） 状态"},{"parent":"4387d2b41401","children":[],"id":"05dea48990c1","title":"阶段2：binlog 写盘，InooDB 事务进入 commit（提交） 状态"},{"parent":"4387d2b41401","children":[],"id":"7e939c251666","title":"每个事务binlog的末尾，会记录一个 XID event，标志着事务是否提交成功，也就是说，恢复过程中，binlog 最后一个 XID event 之后的内容都应该被 purge（清除）"}],"id":"4387d2b41401","title":"过程"}],"id":"9f54d23f7748","title":"MySQL通过两阶段提交来保证redo log和binlog的数据是一致的"}],"id":"4c6de79ecd53","title":"保持一致性的方法"}],"id":"b2f9cb6a36f3","title":"引申问题：在写入某一个log，失败了，那会怎么办？比如先写redo log，再写binlog"}],"id":"fa7e2894f9c9","title":"redo log事务开始的时候，就开始记录每次的变更信息，而binlog是在事务提交的时候才记录"}],"id":"5dfacdc162c1","title":"binlog与redo log的区别"},{"parent":"a5966d5788f2","children":[{"parent":"cea3499ae4ce","children":[{"parent":"e50c2afacca9","children":[],"id":"18fce611a383","title":"默认不开启，需要手动将参数设置为ON"}],"id":"e50c2afacca9","title":"慢查询日志，记录所有执行时间超过long_query_time的所有查询或不使用索引的查询<br>"}],"id":"cea3499ae4ce","title":"slow log<br>"}],"collapsed":false,"id":"a5966d5788f2","title":"log"},{"parent":"93b7f849fef3","children":[{"parent":"5210cc3e2201","children":[{"parent":"9901dd2630fa","children":[{"parent":"f2714fc25cf8","children":[{"parent":"72e55f7388bc","children":[],"id":"efa64c497a6e","title":"B树在提高了IO性能的同时并没有解决元素遍历的效率低下的问题，为了解决这个问题产生了B+树。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而在数据库中基于范围的查询是非常频繁的，但B树不支持这样的操作或者说效率太低"}],"id":"72e55f7388bc","title":"为什么选用B+Tree不选择B-Tree？"},{"parent":"f2714fc25cf8","children":[{"parent":"118574a529fa","children":[{"parent":"e98c2b0bce63","children":[],"id":"70ba3b2ec259","title":"和索引中的所有列进行匹配"}],"id":"e98c2b0bce63","title":"全值匹配"},{"parent":"118574a529fa","children":[{"parent":"f6fe4bbe8879","children":[],"id":"6650dea74984","title":"只使用索引的第一列"}],"id":"f6fe4bbe8879","title":"匹配最左前缀"},{"parent":"118574a529fa","children":[{"parent":"a8e32170dad9","children":[],"id":"0736d8171b6b","title":"只匹配某一列值的开头部分"}],"id":"a8e32170dad9","title":"匹配列前缀"},{"parent":"118574a529fa","children":[{"parent":"c3ecae94375c","children":[],"id":"d6ce88217808","title":"查找范围区间"}],"id":"c3ecae94375c","title":"匹配范围值"},{"parent":"118574a529fa","children":[{"parent":"f7a85f0b29a2","children":[],"id":"f2c263321a5b","title":"第一列全匹配，第二列匹配范围区间"}],"id":"f7a85f0b29a2","title":"精确匹配某一列并范围匹配另外一列"},{"parent":"118574a529fa","children":[{"parent":"00e02f358513","children":[],"id":"7eaacfcc4407","title":"覆盖索引"}],"id":"00e02f358513","title":"只访问索引的查询"}],"id":"118574a529fa","title":"适用范围"}],"id":"f2714fc25cf8","title":"B+Tree 索引"},{"parent":"9901dd2630fa","children":[{"parent":"079407222137","children":[{"parent":"2961e3444d75","children":[],"id":"9d63f5055547","title":"只有精确匹配索引所有列的查询才有效"}],"id":"2961e3444d75","title":"等值查询"}],"id":"079407222137","title":"Hash 索引"},{"parent":"9901dd2630fa","children":[{"parent":"ac2bb051dc41","children":[],"id":"b1fc3ee864fd","title":"MyISAM表支持，可以用作地理数据存储"}],"id":"ac2bb051dc41","title":"R- Tree 索引（空间数据索引）"},{"parent":"9901dd2630fa","children":[{"parent":"98bc7018f2e8","children":[],"id":"74d8f80862a6","title":"MyISAM表支持，查找文本中的关键字"}],"id":"98bc7018f2e8","title":"全文索引"}],"id":"9901dd2630fa","title":"常见索引"},{"parent":"5210cc3e2201","children":[{"parent":"eb2a05c840d5","children":[],"id":"38870688a881","title":"InnoDB通过主键聚集数据，若没有主键则会选择一个唯一非空索引代替，若都不存在则会隐式定义一个主键来作为聚簇索引"}],"id":"eb2a05c840d5","title":"聚簇索引"},{"parent":"5210cc3e2201","children":[{"parent":"c1563189924d","children":[],"id":"13d6a347d2e2","title":"会多进行一次扫描（回表操作）"}],"id":"c1563189924d","title":"非聚簇索引"},{"parent":"5210cc3e2201","children":[{"parent":"1c121f6da170","children":[{"parent":"e0e8ecd756d7","children":[],"id":"f3837dae5ad5","title":"索引不能是表达式的一部分，也不能是函数的参数<br>"}],"id":"e0e8ecd756d7","title":"独立的列"},{"parent":"1c121f6da170","children":[{"parent":"a3bd55e14de1","children":[],"id":"043d3ebd14c6","title":"在需要使用多列作为查询条件时，联合索引比使用多个单列索引性能更好<br>"}],"id":"a3bd55e14de1","title":"多列索引"},{"parent":"1c121f6da170","children":[{"parent":"d28f309facf7","children":[],"id":"cf867ae6885b","title":"将选择行最强的索引列放在最前面<br>"},{"parent":"d28f309facf7","children":[],"id":"977d01e8182f","title":"索引的选择性：不重复的索引值和记录总数的对比<br>"}],"id":"d28f309facf7","title":"索引列的顺序"},{"parent":"1c121f6da170","children":[{"parent":"6ed32658245b","children":[],"id":"a735cff03984","title":"对BLOG、TEXT、VARCHAR类型的列，使用前缀索引，索引开始的部分字符<br>"},{"parent":"6ed32658245b","children":[],"id":"9af199030361","title":"前缀索引的长度选取，需根据索引的选择性来确定<br>"}],"id":"6ed32658245b","title":"前缀索引"},{"parent":"1c121f6da170","children":[{"parent":"861978fd7ef6","children":[],"id":"94eacfaadf23","title":"索引包含所有需要查询的字段值"},{"parent":"861978fd7ef6","children":[{"parent":"5d4e9d8774fd","children":[],"id":"5d9d32175efc","title":"索引远小于数据行的大小，只读取索引能够减少数据访问量<br>"},{"parent":"5d4e9d8774fd","children":[],"id":"858036b36f18","title":"不用回表"}],"id":"5d4e9d8774fd","title":"优点"}],"id":"861978fd7ef6","title":"覆盖索引"}],"id":"1c121f6da170","title":"索引优化"},{"parent":"5210cc3e2201","children":[{"parent":"a88af8d55657","children":[],"id":"d05b0626264c","title":"大大减少了服务器需要扫描的数据行数"},{"parent":"a88af8d55657","children":[],"id":"f685636c3846","title":"帮助服务器避免进行排序和分组，以及避免创建临时表"},{"parent":"a88af8d55657","children":[],"id":"3df58d5d4a3b","title":"将随机 I/O 变为顺序 I/O"}],"id":"a88af8d55657","title":"索引的优点"},{"parent":"5210cc3e2201","children":[{"parent":"1792cb752801","children":[{"parent":"cef88efe00e5","children":[],"id":"c1e59b66fefb","title":"因为如果不是覆盖索引需要回表"}],"id":"cef88efe00e5","title":"对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效"},{"parent":"1792cb752801","children":[],"id":"64956008bace","title":"对于中到大型的表，索引非常有效"},{"parent":"1792cb752801","children":[],"id":"4070060fdb7d","title":"对于特大型的表，建立和维护索引的代价将会随之增长。这种情况下，需要用到一种技术可以直接区分出需要查询的一组数据，而不是一条记录一条记录地匹配，例如可以使用分区技术"}],"id":"1792cb752801","title":"使用条件"}],"collapsed":false,"id":"5210cc3e2201","title":"索引"},{"parent":"93b7f849fef3","children":[{"parent":"1a7dcec2ca47","children":[{"parent":"85c67aaf34a6","children":[{"parent":"35c936f7bdfa","children":[],"id":"a596da0b2488","title":"SIMPLE 简单查询"},{"parent":"35c936f7bdfa","children":[],"id":"f4b9fe60c553","title":"UNION 联合查询"},{"parent":"35c936f7bdfa","children":[],"id":"5f4c581eaea4","title":"SUBQUERY 子查询"}],"id":"35c936f7bdfa","title":"select_type"},{"parent":"85c67aaf34a6","children":[{"parent":"46d559313322","children":[],"id":"c4ee393f6a07","title":"查询的表"}],"id":"46d559313322","title":"table"},{"parent":"85c67aaf34a6","children":[{"parent":"a7b85d2883a5","children":[],"id":"b616d85f45b2","title":"system"},{"parent":"a7b85d2883a5","children":[{"parent":"35007f4ba57f","children":[],"id":"f1988a49fc81","title":"只有一条查询结果&amp;主键/唯一索引"}],"id":"35007f4ba57f","title":"const"},{"parent":"a7b85d2883a5","children":[{"parent":"ad8c20fd30de","children":[],"id":"96881f188e51","title":"链接查询&amp;主键/唯一索引&amp;只有一条查询结果"}],"id":"ad8c20fd30de","title":"eq_ref"},{"parent":"a7b85d2883a5","children":[{"parent":"f42e8c179697","children":[],"id":"f02141ac36a6","title":"非唯一索引"}],"id":"f42e8c179697","title":"ref"},{"parent":"a7b85d2883a5","children":[{"parent":"43897738682a","children":[],"id":"910cd850cb27","title":"使用索引进行范围查询时"}],"id":"43897738682a","title":"range"},{"parent":"a7b85d2883a5","children":[{"parent":"8fef129a816a","children":[],"id":"200bdd342f1e","title":"查询的字段时索引的一部分，覆盖索引"},{"parent":"8fef129a816a","children":[],"id":"462b089e56d4","title":"使用主键排序"}],"id":"8fef129a816a","title":"index"},{"parent":"a7b85d2883a5","children":[{"parent":"1bb5aaa0dd0d","children":[],"id":"764244bd2a17","title":"全表扫描"}],"id":"1bb5aaa0dd0d","title":"all"}],"id":"a7b85d2883a5","title":"type"},{"parent":"85c67aaf34a6","children":[{"parent":"b31b3a54b63b","children":[],"id":"dde0162686c2","title":"可选择的索引"}],"id":"b31b3a54b63b","title":"possible_keys"},{"parent":"85c67aaf34a6","children":[{"parent":"81ad0f7b17de","children":[],"id":"905080a6f852","title":"实际使用的索引"}],"id":"81ad0f7b17de","title":"key"},{"parent":"85c67aaf34a6","children":[{"parent":"e313359e3213","children":[],"id":"9094ee686fb6","title":"扫描的行数"}],"id":"e313359e3213","title":"rows"}],"id":"85c67aaf34a6","title":"使用 Explain 分析 Select 查询语句<br>"},{"parent":"1a7dcec2ca47","children":[{"parent":"766730b3f63f","children":[{"parent":"e4c6eb7dee1d","children":[],"id":"f00841c92b2d","title":"只查询必要的列，对使用*永远持怀疑态度"},{"parent":"e4c6eb7dee1d","children":[],"id":"6396af513501","title":"只返回必要的行，使用Limit限制返回行数"},{"parent":"e4c6eb7dee1d","children":[],"id":"e69aa7cfb00a","title":"缓存重复查询的数据，例如使用redis"}],"id":"e4c6eb7dee1d","title":"减少请求的数据量"},{"parent":"766730b3f63f","children":[{"parent":"64889a19144a","children":[],"id":"e3d6b04e7263","title":"使用索引覆盖查询"}],"id":"64889a19144a","title":"减少数据库扫描的行数"}],"id":"766730b3f63f","title":"优化数据访问"},{"parent":"1a7dcec2ca47","children":[{"parent":"879ad88fe515","children":[],"id":"4fcee3d0c287","title":"有时候一个复杂的查询并没有多个简单查询执行迅速"},{"parent":"879ad88fe515","children":[],"id":"344a37829ef0","title":"对于大查询，可以使用切分查询，每次只返回一小部分"},{"parent":"879ad88fe515","children":[],"id":"fd9207da3a3a","title":"对于关联查询，可根据情况进行分解，在应用程序中进行关联"},{"parent":"879ad88fe515","children":[],"id":"27dc6dbe2891","title":"对于大分页等场景, 可采用延迟关联<br>"}],"id":"879ad88fe515","title":"重构查询方式"}],"collapsed":false,"id":"1a7dcec2ca47","title":"查询性能优化"},{"parent":"93b7f849fef3","children":[{"parent":"f349bf5b5c84","children":[{"parent":"8a0fefaa1a67","children":[],"id":"9efbed658957","title":"原子性<br>"},{"parent":"8a0fefaa1a67","children":[],"id":"1fa9df39ec26","title":"一致性"},{"parent":"8a0fefaa1a67","children":[],"id":"814aae5b441a","title":"隔离性"},{"parent":"8a0fefaa1a67","children":[],"id":"98bb1a37de0d","title":"持久性"}],"collapsed":false,"id":"8a0fefaa1a67","title":"ACID"},{"parent":"f349bf5b5c84","children":[{"parent":"d4c5e6c7b97d","children":[{"parent":"46a0e40a540b","children":[],"id":"97fc28724d63","title":"事务读取未提交的数据"}],"id":"46a0e40a540b","title":"脏读"},{"parent":"d4c5e6c7b97d","children":[{"parent":"0a6b6f451a92","children":[],"id":"80ae6dfb6f8c","title":"一个事务内，多次读同一数据。A事务未结束，B事务访问同一数据，A事务读取两次可能不一致<br>"}],"id":"0a6b6f451a92","title":"不可重复读"},{"parent":"d4c5e6c7b97d","children":[{"parent":"c653b25154d9","children":[],"id":"dd1fc239cca6","title":"一个事务在前后两次查询同一范围的时候，后一次查询看到了前一次查询没有看到的行。事务A 按照一定条件进行数据读取， 期间事务B 插入了相同搜索条件的新数据，事务A再次按照原先条件进行读取时，发现了事务B 新插入的数据<br>"}],"id":"c653b25154d9","title":"幻读"},{"parent":"d4c5e6c7b97d","children":[{"parent":"ca82f84e0c45","children":[],"id":"0bae386e19f5","title":"一个事务的更新操作会被另一个事务的更新操作所覆盖"},{"parent":"ca82f84e0c45","children":[],"id":"b317c67d4337","title":"例如：T1 和 T2 两个事务都对一个数据进行修改，T1 先修改，T2 随后修改，T2 的修改覆盖了 T1 的修改"}],"id":"ca82f84e0c45","title":"丢失更新"}],"collapsed":false,"id":"d4c5e6c7b97d","title":"脏读、不可重复读、幻读、丢失更新<br>"},{"parent":"f349bf5b5c84","children":[{"parent":"54cad68df1e1","children":[{"parent":"187fad689217","children":[],"id":"40001f009aca","title":"事务可以读取未提交数据（脏读）<br>"},{"parent":"187fad689217","children":[],"id":"74d2f0b7e8ff","title":"存在脏读、不可重复读、幻读"}],"id":"187fad689217","title":"读未提交<br>"},{"parent":"54cad68df1e1","children":[{"parent":"831af13c1fe3","children":[],"id":"7e9293ed801e","title":"事务只可以读取已经提交的事务所做的修改<br>"},{"parent":"831af13c1fe3","children":[],"id":"7214bb4bdc0e","title":"存在不可重复读、幻读"}],"id":"831af13c1fe3","title":"读已提交<br>"},{"parent":"54cad68df1e1","children":[{"parent":"ddabe3557266","children":[],"id":"a6098ce57c11","title":"同一个事务多次读取同样记录结果一致<br>"},{"parent":"ddabe3557266","children":[],"id":"82158205dff5","title":"存在幻读"},{"parent":"ddabe3557266","children":[],"id":"2d11fbd7845e","title":"InnoDB默认级别<br>"}],"id":"ddabe3557266","title":"可重复读<br>"},{"parent":"54cad68df1e1","children":[{"parent":"329045116b43","children":[],"id":"f201ca426fb5","title":"读取每一行数据上都加锁<br>"},{"parent":"329045116b43","children":[],"id":"9040c49f6995","title":"存在加锁读"}],"id":"329045116b43","title":"可串行化<br>"}],"collapsed":false,"id":"54cad68df1e1","title":"事务隔离级别"},{"parent":"f349bf5b5c84","children":[{"parent":"0c3fe336f085","children":[{"parent":"e1911d4cb48b","children":[{"parent":"010c32dd6618","children":[],"id":"7abe6c3095a9","title":"允许事务读一行数据"}],"id":"010c32dd6618","title":"共享锁（读锁）"},{"parent":"e1911d4cb48b","children":[{"parent":"e440236253d1","children":[],"id":"27e41f2eeea3","title":"允许事务删除或更新一行数据"}],"id":"e440236253d1","title":"排他锁（写锁）"},{"parent":"e1911d4cb48b","children":[{"parent":"ae79dcf5ed09","children":[],"id":"b09a0093dd92","title":"事务想要获取一张表中某几行的共享锁<br>"}],"id":"ae79dcf5ed09","title":"意向共享锁"},{"parent":"e1911d4cb48b","children":[{"parent":"a843c2d1177e","children":[],"id":"470c8fe9653b","title":"事务想要获取一张表中某几行的排他锁"}],"id":"a843c2d1177e","title":"意向排他锁"}],"id":"e1911d4cb48b","title":"锁类型<br>"},{"parent":"0c3fe336f085","children":[{"parent":"3a31e35e17d1","children":[{"parent":"8458c31623f5","children":[],"id":"2fb5f24d4ca4","title":"锁定整张表，是开销最小的策略"},{"parent":"8458c31623f5","children":[],"id":"fc59fa7dd010","title":"开销小，加锁快；无死锁；锁定粒度大，发生锁冲突的概率最高,并发度最低"}],"id":"8458c31623f5","title":"表锁"},{"parent":"3a31e35e17d1","children":[{"parent":"85be4bb1b97c","children":[],"id":"7a8a1338b899","title":"行级锁只对用户正在访问的行进行锁定"},{"parent":"85be4bb1b97c","children":[],"id":"1fe3439f48cb","title":"开销大，加锁慢；会出现死锁；锁定粒度最小，发生锁冲突的概率最低,并发度也最高"},{"parent":"85be4bb1b97c","children":[{"parent":"767fe9aba9f7","children":[],"id":"e87c3357ae96","title":"当行锁涉及到索引失效的时候，会触发表锁的行为"}],"id":"767fe9aba9f7","title":"升级行为"}],"id":"85be4bb1b97c","title":"行锁"},{"parent":"3a31e35e17d1","children":[],"id":"30d6e73385cf","title":"间隙锁"}],"id":"3a31e35e17d1","title":"锁粒度"}],"id":"0c3fe336f085","title":"锁"},{"parent":"f349bf5b5c84","children":[{"parent":"eed5a9459b5a","children":[{"parent":"ce09c69be50f","children":[{"parent":"eed8dbe525bf","children":[],"id":"8e7afdfa1b51","title":"像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读。它读取的是记录的最新版本，读取时还要保证其他并发事务不能修改当前记录，会对读取的记录进行加锁"}],"id":"eed8dbe525bf","title":"当前读"},{"parent":"ce09c69be50f","children":[{"parent":"64325b6f0da8","children":[],"id":"2025e92bff9a","title":"不加锁的select操作就是快照读。快照读的前提是隔离级别不是串行级别，串行级别下的快照读会退化成当前读。快照读基于MVCC，为了提高并发性能的考虑<br>"}],"id":"64325b6f0da8","title":"快照读"}],"id":"ce09c69be50f","title":"当前读与快照读"},{"parent":"eed5a9459b5a","children":[{"parent":"287cd593da0c","children":[{"parent":"9d63e35bb5f6","children":[],"id":"2c3c0a528cb3","title":"存储的每次对某条聚簇索引记录进行修改的时候的事务id"}],"id":"9d63e35bb5f6","title":"trx_id"},{"parent":"287cd593da0c","children":[{"parent":"01688368c063","children":[],"id":"1a6030a34904","title":"一个指针，它指向这条聚簇索引记录的上一个版本的位置<br>"}],"id":"01688368c063","title":"roll_pointer<br>"}],"id":"287cd593da0c","title":"聚簇索引中的隐藏列"},{"parent":"eed5a9459b5a","children":[{"parent":"629025098291","children":[],"id":"fd5bc24b1ab0","title":"Read View 保存了当前事务开启时所有活跃的事务列表"}],"id":"629025098291","title":"ReadView<br>"},{"parent":"eed5a9459b5a","children":[{"parent":"0a9f4b6b3fd8","children":[],"id":"2fda9cd25a51","title":"获取事务自己的版本号，即 事务ID<br>"},{"parent":"0a9f4b6b3fd8","children":[],"id":"69d637f03ec4","title":"获取 ReadView<br>"},{"parent":"0a9f4b6b3fd8","children":[{"parent":"d234974b39a2","children":[{"parent":"00c306141cc3","children":[],"id":"6793592fd10e","title":"直接读取最新版本ReadView<br>"}],"id":"00c306141cc3","title":"读未提交<br>"},{"parent":"d234974b39a2","children":[{"parent":"cd3375057c84","children":[],"id":"d9b62e5bee77","title":"每次查询的开始都会生成一个独立的ReadView<br>"}],"id":"cd3375057c84","title":"读已提交<br>"},{"parent":"d234974b39a2","children":[{"parent":"b9c1beaa17b7","children":[],"id":"37c1b07a6706","title":"可重复读隔离级别则在第一次读的时候生成一个ReadView，之后的读都复用之前的ReadView<br>"}],"id":"b9c1beaa17b7","title":"可重复读<br>"}],"id":"d234974b39a2","title":"查询得到的数据，然后与 ReadView 中的事务版本号进行比较<br>"},{"parent":"0a9f4b6b3fd8","children":[],"id":"0931e9c7e939","title":"如果不符合 ReadView 规则， 那么就需要 UndoLog 中历史快照<br>"},{"parent":"0a9f4b6b3fd8","children":[],"id":"ed2a683bfd62","title":"最后返回符合规则的数据<br>"}],"id":"0a9f4b6b3fd8","title":"实现原理"}],"id":"eed5a9459b5a","title":"MVCC"}],"collapsed":false,"id":"f349bf5b5c84","title":"事务"},{"parent":"93b7f849fef3","children":[{"parent":"7bb42e3d47e8","children":[{"parent":"f04740c1a26a","children":[{"parent":"6344bab34462","children":[],"id":"e844fe3ee99d","title":"借助第三方工具pt-online-schema-change"}],"id":"6344bab34462","title":"修改为bigint"},{"parent":"f04740c1a26a","children":[],"id":"c9cc52e3755f","title":"一般用不完就分库分表了, 使用全局唯一id"}],"id":"f04740c1a26a","title":"自增id用完了"},{"parent":"7bb42e3d47e8","children":[{"parent":"827ace408eb2","children":[],"id":"0662d7098e60","title":"设置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count参数，减少binlog的写盘次数。这个方法是基于“额外的故意等待”来实现的，因此可能会增加语句的响应时间，但没有丢失数据的风险。<br><br>将sync_binlog 设置为大于1的值（比较常见是100~1000）。这样做的风险是，主机掉电时会丢binlog日志。<br><br>将innodb_flush_log_at_trx_commit设置为2。这样做的风险是，主机掉电的时候会丢数据。"}],"id":"827ace408eb2","title":"IO性能瓶颈"}],"id":"7bb42e3d47e8","title":"常见问题"}],"collapsed":true,"id":"93b7f849fef3","title":"MySQL"},{"parent":"root","lineStyle":{"randomLineColor":"#F5479C"},"children":[{"parent":"68e2d53f78c0","children":[{"parent":"a2ba7f2839e7","children":[],"id":"332a1e1ff9de","title":"秒杀的库存扣减"},{"parent":"a2ba7f2839e7","children":[],"id":"fdc0cda55b5c","title":"APP首页的访问流量高峰"},{"parent":"a2ba7f2839e7","children":[],"id":"235928a55a4d","title":"避免数据库打崩"}],"id":"a2ba7f2839e7","title":"为什么使用Redis"},{"parent":"68e2d53f78c0","children":[{"parent":"1bbcd515493d","children":[{"parent":"d6e64200f130","children":[{"parent":"c1b235ccc3a0","children":[],"id":"af253a7e312d","title":"key-value，类似ArrayList"}],"id":"c1b235ccc3a0","title":"String"},{"parent":"d6e64200f130","children":[],"id":"805ec0ae37ce","title":"Hash"},{"parent":"d6e64200f130","children":[],"id":"18c40debca54","title":"set"},{"parent":"d6e64200f130","children":[],"id":"5aba119e78a1","title":"zset"},{"parent":"d6e64200f130","children":[],"id":"466e3ae93922","title":"List"}],"id":"d6e64200f130","title":"必会项"},{"parent":"1bbcd515493d","children":[{"parent":"e08a90c96b17","children":[{"parent":"aac9ee1c9de2","children":[],"id":"6529bc24a01c","title":"统计网站UV(独立访客，每个用户每天只记录一次)"}],"id":"aac9ee1c9de2","title":"HyperLogLog<br>"},{"parent":"e08a90c96b17","children":[{"parent":"e9b70d2f5e4a","children":[],"id":"09ba2789d093","title":"计算地理位置，类似功能附近的人"}],"id":"e9b70d2f5e4a","title":"Geo"},{"parent":"e08a90c96b17","children":[{"parent":"36770e0d1f7f","children":[],"id":"951025427dd0","title":"消息的多播，发布/订阅，无法持久化"}],"id":"36770e0d1f7f","title":"Pub/Sub"},{"parent":"e08a90c96b17","children":[{"parent":"504b0480286f","children":[],"id":"ff3c30b3b218","title":"用户签到,短视频属性(特效,加锁),用户在线状态,活跃状态<br>"}],"id":"504b0480286f","title":"BitMap"},{"parent":"e08a90c96b17","children":[{"parent":"750183397ab0","children":[{"parent":"d912173e6c29","children":[],"id":"c0d25c9698a3","title":"缓存穿透"},{"parent":"d912173e6c29","children":[],"id":"cea22ee9fb6b","title":"海量数据去重"}],"id":"d912173e6c29","title":"使用场景"},{"parent":"750183397ab0","children":[{"parent":"d1cce1828064","children":[{"parent":"1c2955281250","children":[],"id":"ebe498b35f4e","title":"可以通过建立一个白名单来存储可能会误判的元素"}],"id":"1c2955281250","title":"存在误判"},{"parent":"d1cce1828064","children":[{"parent":"e4d933c94529","children":[],"id":"0f690c41bf85","title":"可以采用Counting Bloom Filter"}],"id":"e4d933c94529","title":"删除困难"}],"id":"d1cce1828064","title":"缺点"},{"parent":"750183397ab0","children":[{"parent":"3ee30b7a6a80","children":[],"id":"c672a0977842","title":"redisson"},{"parent":"3ee30b7a6a80","children":[],"id":"3525627855c4","title":"guava"}],"id":"3ee30b7a6a80","title":"现成的轮子"},{"parent":"750183397ab0","children":[{"parent":"e23b985c75be","children":[{"parent":"015aee9efba2","children":[],"id":"7a36e20faba7","title":"可进行设置, 默认值为0.03"}],"id":"015aee9efba2","title":"预估数据量n以及期望的误判率fpp"},{"parent":"e23b985c75be","children":[{"parent":"2aa25fd1f61c","children":[{"parent":"2b742b98e5af","children":[],"style":{"font-size":19},"id":"6e1979c1417e","title":"<span class=\"equation-text\" contenteditable=\"false\" data-index=\"0\" data-equation=\"m=-\\frac{{nlnfpp}}{(ln2^2)}\"><span></span><span>﻿</span></span>"}],"id":"2b742b98e5af","title":"Bit数组大小"},{"parent":"2aa25fd1f61c","children":[{"parent":"744b38ebf3de","children":[],"style":{"font-size":20},"id":"19690dd504f2","title":"<span class=\"equation-text\" contenteditable=\"false\" data-index=\"0\" data-equation=\"k=\\frac{{m}}{n}ln2\"><span></span><span>﻿</span></span>"}],"id":"744b38ebf3de","title":"哈希函数选择"}],"id":"2aa25fd1f61c","title":"hash函数的选取以及bit数组的大小"}],"id":"e23b985c75be","title":"实现"}],"id":"750183397ab0","title":"BloomFilter (布隆过滤器)"},{"parent":"e08a90c96b17","children":[{"parent":"a3817cda1299","children":[],"id":"74b95271d80f","title":"可持久化的消息队列，支持多播"}],"id":"a3817cda1299","title":"Stream"}],"id":"e08a90c96b17","title":"加分项"}],"id":"1bbcd515493d","title":"数据结构"},{"parent":"68e2d53f78c0","children":[{"parent":"7742814a28a8","children":[{"parent":"a5a4136392f3","children":[{"parent":"dac8a72c8af3","children":[],"id":"2e0bddd4ebac","title":"Redis中大量key同一时间失效, 导致大量请求落库<br>"}],"id":"dac8a72c8af3","title":"概念"},{"parent":"a5a4136392f3","children":[{"parent":"4f3f99b22654","children":[{"parent":"cae81e94657c","children":[],"id":"f458c0739724","title":"加随机过期时间, 避免大量key同时失效"},{"parent":"cae81e94657c","children":[],"id":"daaa9f606c2b","title":"设置热点数据永不过期<br>"},{"parent":"cae81e94657c","children":[],"id":"1d88c6e21c3c","title":"如果Redis是集群部署,&nbsp;将热点数据均匀分布在不同的Redis库中也能避免全部失效的问题"}],"id":"cae81e94657c","title":"预防方案"},{"parent":"4f3f99b22654","children":[{"parent":"c3bea9f91a7e","children":[],"id":"a7313b4d8479","title":"设置本地缓存(ehcache)+限流(hystrix)，尽量避免请求过多打入数据库导致数据库宕机<br>"}],"id":"c3bea9f91a7e","title":"万一发生"},{"parent":"4f3f99b22654","children":[{"parent":"88f98afe7a1c","children":[],"id":"921be9f40b92","title":"redis持久化，重启后自动从磁盘上加载数据，快速恢复缓存数据<br>"}],"id":"88f98afe7a1c","title":"挂掉以后"}],"id":"4f3f99b22654","title":"解决方案"}],"id":"a5a4136392f3","title":"缓存雪崩"},{"parent":"7742814a28a8","children":[{"parent":"16f48041dfe8","children":[{"parent":"18dad091e32b","children":[],"id":"d4a29ef74f33","title":"缓存和数据库中都没有的数据，而用户不断发起请求, 导致数据库压力过大，严重会击垮数据库<br>"}],"id":"18dad091e32b","title":"概念"},{"parent":"16f48041dfe8","children":[{"parent":"c83b9f04cec3","children":[{"parent":"f1ca0b7df56c","children":[],"id":"06abfdcea127","title":"接口层增加校验，比如用户鉴权校验，参数做校验，存在不合法的参数直接做短路操作"},{"parent":"f1ca0b7df56c","children":[{"parent":"0a8f16c78524","children":[],"id":"77c829f772d4","title":"不存在则不请求"}],"id":"0a8f16c78524","title":"布隆过滤器"}],"id":"f1ca0b7df56c","title":"过滤方式"},{"parent":"c83b9f04cec3","children":[{"parent":"e9827ebf2fbc","children":[],"id":"f58a9e45bbfb","title":"将对应Key的Value对写为null、或其他提示信息，缓存有效时间可以设置短点，如30秒"}],"id":"e9827ebf2fbc","title":"临时返回"}],"id":"c83b9f04cec3","title":"解决方案"}],"id":"16f48041dfe8","title":"缓存穿透"},{"parent":"7742814a28a8","children":[{"parent":"89e2121fac66","children":[{"parent":"72782b925676","children":[],"id":"ccdc7563e860","title":"一个非常热点的Key，在不停的扛着大并发，大并发集中对这一个点进行访问，当这个Key在失效的瞬间，持续的大并发就穿破缓存，直接请求数据库<br>"}],"id":"72782b925676","title":"概念"},{"parent":"89e2121fac66","children":[{"parent":"f2b90513c9c0","children":[],"id":"48a839f3015b","title":"互斥锁"},{"parent":"f2b90513c9c0","children":[],"id":"217261d1bab5","title":"设置热点数据永不过期"}],"id":"f2b90513c9c0","title":"解决方案"}],"id":"89e2121fac66","title":"缓存击穿"},{"parent":"7742814a28a8","children":[{"parent":"796744a8869c","children":[{"parent":"9cb88abbc2df","children":[],"id":"d4df0f21db8b","title":"数据库与缓存内数据不一致问题"}],"id":"9cb88abbc2df","title":"定义"},{"parent":"796744a8869c","children":[{"parent":"cd7299545586","children":[{"parent":"2a161277de43","children":[],"id":"f7e64d411a34","title":"保持最终一致性, 当缓存中没有时会从数据库读取"}],"id":"2a161277de43","title":"设置过期时间"},{"parent":"cd7299545586","children":[{"parent":"787b437c7b36","children":[{"parent":"6a186a44f666","children":[],"id":"71f5b508690c","title":"第一步成功(操作数据库)，第二步失败(删除缓存)，会导致数据库里是新数据，而缓存里是旧数据"},{"parent":"6a186a44f666","children":[],"id":"26959b0b8788","title":"如果第一步(操作数据库)就失败了，直接返回错误(Exception)，不会出现数据不一致<br>"},{"parent":"6a186a44f666","children":[{"parent":"2daec7e7e0d5","children":[{"parent":"cc61025444c8","children":[],"id":"27c9e98de34b","title":"不一致,但出现概率很低"}],"id":"cc61025444c8","title":"<ul><li>缓存刚好失效</li><li>线程A查询数据库，得一个旧值</li><li>线程B将新值写入数据库</li><li>线程B删除缓存</li><li>线程A将查到的旧值写入缓存</li></ul><br>"}],"id":"2daec7e7e0d5","title":"并发场景"}],"id":"6a186a44f666","title":"原子性被破坏情景"},{"parent":"787b437c7b36","children":[{"parent":"a9947ca1b3c6","children":[],"id":"221e14e48d66","title":"将需要删除的key发送到消息队列中<br>"},{"parent":"a9947ca1b3c6","children":[],"id":"d340f9c73068","title":"自己消费消息，获得需要删除的key<br>"},{"parent":"a9947ca1b3c6","children":[],"id":"cdd19d4a6cd3","title":"不断重试删除操作，直到成功<br>"}],"id":"a9947ca1b3c6","title":"删除缓存失败的解决思路"},{"parent":"787b437c7b36","children":[{"parent":"7b07db7a011b","children":[],"id":"51870fc09c5a","title":"高并发下表现优异，在原子性被破坏时表现不如意"}],"id":"7b07db7a011b","title":"结论"}],"id":"787b437c7b36","title":"先更新数据库，再删除缓存"},{"parent":"cd7299545586","children":[{"parent":"99a36d602e49","children":[{"parent":"42e69abbccbe","children":[],"id":"4d13edae29bf","title":"第一步成功(删除缓存)，第二步失败(更新数据库)，数据库和缓存的数据还是一致的<br>"},{"parent":"42e69abbccbe","children":[],"id":"362019fddab4","title":"如果第一步(删除缓存)就失败了，我们可以直接返回错误(Exception)，数据库和缓存的数据还是一致的"},{"parent":"42e69abbccbe","children":[{"parent":"2217b4dc2182","children":[{"parent":"a9df8552a646","children":[],"id":"9d945a5b687c","title":"不一致"}],"id":"a9df8552a646","title":"<div style=\"text-align: justify;\"><ul><li><span style=\"font-size: inherit;\">线程A删除了缓存</span></li><li>线程B查询，发现缓存已不存在</li><li>线程B去数据库查询得到旧值</li><li>线程B将旧值写入缓存</li><li>线程A将新值写入数据库</li></ul></div>"}],"id":"2217b4dc2182","title":"并发场景"}],"id":"42e69abbccbe","title":"原子性被破坏情景"},{"parent":"99a36d602e49","children":[{"parent":"bb57d06a2d89","children":[],"id":"4925061129d7","title":"将删除缓存、修改数据库、读取缓存等的操作积压到队列里边，实现串行化<br>"}],"id":"bb57d06a2d89","title":"并发下解决数据库与缓存不一致的思路"},{"parent":"99a36d602e49","children":[{"parent":"db6def4c48a6","children":[],"id":"8def4023ab41","title":"高并发下表现不如意，在原子性被破坏时表现优异"}],"id":"db6def4c48a6","title":"结论"}],"id":"99a36d602e49","title":"先删除缓存，再更新数据库<br>"},{"parent":"cd7299545586","children":[{"parent":"f9b9c73e9e93","children":[{"parent":"f55f8ee1671a","children":[],"id":"626ca445c252","title":"先删除缓存<br>"},{"parent":"f55f8ee1671a","children":[],"id":"315c5d4f0763","title":"再写数据库"},{"parent":"f55f8ee1671a","children":[{"parent":"23aabe812e01","children":[],"id":"fae527875f52","title":"延时时间要大于数据库一次写操作的时间<br>"},{"parent":"23aabe812e01","children":[],"id":"774f81a5a8a0","title":"需要考虑Redis和数据库的主从同步时间<br>"}],"id":"23aabe812e01","title":"休眠一段时间"},{"parent":"f55f8ee1671a","children":[],"id":"10db51291f20","title":"再次删除缓存"}],"id":"f55f8ee1671a","title":"先删除缓存，再更新数据库"}],"id":"f9b9c73e9e93","title":"延时双删<br>"}],"id":"cd7299545586","title":"解决方案"}],"id":"796744a8869c","title":"双写一致性"},{"parent":"7742814a28a8","children":[{"parent":"13012e650d03","children":[{"parent":"66c624a52b7f","children":[{"parent":"3f21b7a753e0","children":[{"parent":"07452410b695","children":[],"id":"f1badcd09c76","title":"可重入锁"},{"parent":"07452410b695","children":[],"id":"e220542f12fa","title":"乐观锁<br>"},{"parent":"07452410b695","children":[],"id":"bd7cd43665fd","title":"公平锁"},{"parent":"07452410b695","children":[],"id":"28b6c9db34d6","title":"读写锁"},{"parent":"07452410b695","children":[],"id":"0f0046ee2fb4","title":"Redlock"},{"parent":"07452410b695","children":[],"id":"e3e5074fe0ee","title":"BloomFilter (布隆过滤器)"}],"id":"07452410b695","title":"功能概括"}],"id":"3f21b7a753e0","title":"Redisson"},{"parent":"66c624a52b7f","children":[{"parent":"c34fec094423","children":[{"parent":"d2c674dedd38","children":[],"id":"2b3737289a9b","title":"setnx&nbsp;key 不存在，才会设置它的值，否则什么也不做<br>"}],"id":"d2c674dedd38","title":"加锁"},{"parent":"c34fec094423","children":[{"parent":"5c1eb865ad35","children":[],"id":"8132cd2aac6c","title":"expire 设置过期时间<br>"}],"id":"5c1eb865ad35","title":"避免死锁"},{"parent":"c34fec094423","children":[{"parent":"6c746237d083","children":[],"id":"4cd136dcf59a","title":"Redis 2.6.12扩展了 set 命令<br>"}],"id":"6c746237d083","title":"保证setnx与expire的原子性<br>"}],"id":"c34fec094423","title":"操作"},{"parent":"66c624a52b7f","children":[{"parent":"3b3a67a4f68f","children":[{"parent":"945ee50567d1","children":[{"parent":"dfad1fb29323","children":[{"parent":"750122fd9d6d","children":[],"id":"2fce14e8226c","title":"加锁时，先设置一个过期时间，然后我们开启一个守护线程，定时去检测这个锁的失效时间，如果锁快要过期了，操作共享资源还未完成，那么就自动对锁进行续期，重新设置过期时间"},{"parent":"750122fd9d6d","children":[],"id":"c000eca69ea1","title":"Redisson的看门狗"}],"id":"750122fd9d6d","title":"评估操作共享资源的时间不准确"}],"id":"dfad1fb29323","title":"客户端 1 操作共享资源耗时太久，导致锁被自动释放，之后被客户端 2 持有"}],"id":"945ee50567d1","title":"锁过期"},{"parent":"3b3a67a4f68f","children":[{"parent":"bf25f0444016","children":[{"parent":"bbb2d7133150","children":[{"parent":"ea7ea3f77745","children":[{"parent":"fc68dca74343","children":[],"id":"6310889577fc","title":"UUID"},{"parent":"fc68dca74343","children":[],"id":"7462c19e34f5","title":"自己的线程 ID"}],"id":"fc68dca74343","title":"添加唯一标识"},{"parent":"ea7ea3f77745","children":[{"parent":"bb6006e00c6e","children":[{"parent":"6b33de287b91","children":[],"id":"37d6c65470fd","title":"Redis 处理每一个请求是单线程执行的，在执行一个 Lua 脚本时，其它请求必须等待"}],"id":"6b33de287b91","title":"Lua 脚本"}],"id":"bb6006e00c6e","title":"原子性"},{"parent":"ea7ea3f77745","children":[{"parent":"bbadb586bfed","children":[],"id":"59c6c1ed4a41","title":"唯一标识加锁"},{"parent":"bbadb586bfed","children":[],"id":"80b475a0dbea","title":"操作共享资源"},{"parent":"bbadb586bfed","children":[],"id":"b2b7aee63272","title":"释放锁：Lua 脚本，先 GET 判断锁是否归属自己，再 DEL 释放锁"}],"id":"bbadb586bfed","title":"严谨的流程"}],"id":"ea7ea3f77745","title":"没有检查这把锁是否归自己持有"}],"id":"bbb2d7133150","title":"客户端 1 操作共享资源过程中GC或因其他原因超时释放,&nbsp; 导致释放了客户端 2 的锁<br>"}],"id":"bf25f0444016","title":"释放别人的锁"},{"parent":"3b3a67a4f68f","children":[{"parent":"b05bbbfca458","children":[{"parent":"1e47c82d5213","children":[],"id":"d41070261ebd","title":"客户端 1 在主库上执行 SET 命令，加锁成功<br>"},{"parent":"1e47c82d5213","children":[],"id":"ff99a0fb95f0","title":"此时，主库异常宕机，SET 命令还未同步到从库上（主从复制是异步的）"},{"parent":"1e47c82d5213","children":[],"id":"b585e9c1979d","title":"从库被哨兵提升为新主库，这个锁在新的主库上，丢失了"}],"id":"1e47c82d5213","title":"场景"},{"parent":"b05bbbfca458","children":[{"parent":"6d09d47be5d2","children":[{"parent":"4cab76cdd425","children":[{"parent":"2a920be28d4e","children":[],"id":"f784806d22dc","title":"不再需要部署从库和哨兵实例，只部署主库<br>"},{"parent":"2a920be28d4e","children":[],"id":"5b380f8d976a","title":"但主库要部署多个，官方推荐至少 5 个实例"}],"id":"2a920be28d4e","title":"使用前提"},{"parent":"4cab76cdd425","children":[{"parent":"50ee74e743dd","children":[],"id":"964b265a6b2d","title":"客户端先获取「当前时间戳T1」"},{"parent":"50ee74e743dd","children":[],"id":"314294fbf295","title":"客户端依次向这 5 个 Redis 实例发起加锁请求（用前面讲到的 SET 命令），且每个请求会设置超时时间（毫秒级，要远小于锁的有效时间），如果某一个实例加锁失败（包括网络超时、锁被其它人持有等各种异常情况），就立即向下一个 Redis 实例申请加锁"},{"parent":"50ee74e743dd","children":[],"id":"ca66b200ce03","title":"如果客户端从 &gt;=3 个（大多数）以上 Redis 实例加锁成功，则再次获取「当前时间戳T2」，如果 T2 - T1 &lt; 锁的过期时间，此时，认为客户端加锁成功，否则认为加锁失败"},{"parent":"50ee74e743dd","children":[],"id":"6f679cab274f","title":"加锁成功，去操作共享资源"},{"parent":"50ee74e743dd","children":[],"id":"e71de8c8d3b3","title":"加锁失败，向「全部节点」发起释放锁请求（ Lua 脚本）"}],"id":"50ee74e743dd","title":"使用流程"},{"parent":"4cab76cdd425","children":[{"parent":"c8b84a886178","children":[],"id":"0dca2da045c0","title":"网络延迟"},{"parent":"c8b84a886178","children":[],"id":"0794df0823fa","title":"进程暂停（GC）"},{"parent":"c8b84a886178","children":[],"id":"5485ba1922fd","title":"时钟漂移"}],"id":"c8b84a886178","title":"请考虑好分布式系统的NPC问题"}],"id":"4cab76cdd425","title":"Redlock"}],"id":"6d09d47be5d2","title":"解决方案"}],"id":"b05bbbfca458","title":"主从发生切换分布锁安全问题<br>"}],"id":"3b3a67a4f68f","title":"问题"}],"id":"66c624a52b7f","title":"分布式锁"}],"id":"13012e650d03","title":"并发竞争"},{"parent":"7742814a28a8","children":[{"parent":"b36a30a83fbc","children":[],"id":"65abd363741b","title":"bigkey命令 找到干掉"},{"parent":"b36a30a83fbc","children":[],"id":"e9ff6a38dec0","title":"Redis 4.0引入了memory usage命令和lazyfree机制"}],"id":"b36a30a83fbc","title":"大Key"},{"parent":"7742814a28a8","children":[{"parent":"c7346bf92b6a","children":[],"id":"e74b5d1c63a1","title":"设置缓存时间不失效"},{"parent":"c7346bf92b6a","children":[],"id":"69508638ffc6","title":"多级缓存"},{"parent":"c7346bf92b6a","children":[],"id":"bdc6ecf6b73c","title":"布隆过滤器"},{"parent":"c7346bf92b6a","children":[],"id":"30abdb86e98d","title":"读写分离"}],"id":"c7346bf92b6a","title":"热点key"},{"parent":"7742814a28a8","children":[{"parent":"29f0b4fd4759","children":[{"parent":"7ce1965bb4b7","children":[{"parent":"5d0a0294f45d","children":[],"id":"3367ed8b7920","title":"没有offset、limit参数，会一次查出全部"},{"parent":"5d0a0294f45d","children":[],"id":"47780bc0614e","title":"keys算法是遍历算法，复杂度为O(n)，因为Redis单线程的特性，会顺序执行指令，其他指令必须等待keys执行结束才可以执行"}],"id":"5d0a0294f45d","title":"缺点"}],"id":"7ce1965bb4b7","title":"keys"},{"parent":"29f0b4fd4759","children":[{"parent":"b7569f19ae71","children":[{"parent":"c9d9d6881a2c","children":[],"id":"b5bc3f2b03ab","title":"同样可以正则表达式匹配，limit可控条数，游标分布进行不会阻塞"}],"id":"c9d9d6881a2c","title":"优点"},{"parent":"b7569f19ae71","children":[{"parent":"477b5ab0ad6c","children":[],"id":"bab36d1d140e","title":"返回结果会重复"},{"parent":"477b5ab0ad6c","children":[],"id":"8f7198524c90","title":"如果遍历过程出现数据修改，不能确定改动后的数据能不能遍历到"}],"id":"477b5ab0ad6c","title":"缺点"}],"id":"b7569f19ae71","title":"scan"}],"id":"29f0b4fd4759","title":"搜索海量key"}],"id":"7742814a28a8","title":"常见问题"},{"parent":"68e2d53f78c0","children":[{"parent":"3fd6a16f4f94","children":[{"parent":"10c0fbbaf18c","children":[],"id":"3ff27aab32ff","title":"zset滑动窗口实现"},{"parent":"10c0fbbaf18c","children":[],"id":"c2637409763a","title":"在量大时会消耗很多存储空间"}],"id":"10c0fbbaf18c","title":"简单限流"},{"parent":"3fd6a16f4f94","children":[{"parent":"f5de183ac704","children":[{"parent":"d5400b7edf80","children":[],"id":"b4fb7a7e554e","title":"漏斗Funnel、漏斗算法实现makeSpace"},{"parent":"d5400b7edf80","children":[],"id":"76106014ff32","title":"在每次灌水前调用makeSpace，给漏斗腾出空间，腾出的空间取决于水流的速度<br>"}],"id":"d5400b7edf80","title":"单体实现"},{"parent":"f5de183ac704","children":[{"parent":"d62e25e3e13c","children":[],"id":"b04c03869678","title":"将漏斗对象按字段存储到hash结构中，灌水时将hash结构字段去出进行逻辑运算后，再将新值重填到hash结构中，完成频度检测"},{"parent":"d62e25e3e13c","children":[{"parent":"6d2dccb3f68e","children":[],"id":"c6c702826f75","title":"加锁处理，如果失败重试会导致性能下降"},{"parent":"6d2dccb3f68e","children":[],"id":"a030aea687cd","title":"Redis4.0提供的Redis-Cell解决此问题"}],"id":"6d2dccb3f68e","title":"无法保证这三个操作的原子性"}],"id":"d62e25e3e13c","title":"分布式实现"}],"id":"f5de183ac704","title":"漏斗限流"}],"id":"3fd6a16f4f94","title":"限流操作"},{"parent":"68e2d53f78c0","children":[{"parent":"ada2330a2425","children":[{"parent":"668b1454d74f","children":[{"parent":"4d48d557b5e0","children":[],"id":"7fbadfbbcfcc","title":"5分钟一次"},{"parent":"4d48d557b5e0","children":[],"id":"d00795ceede8","title":"冷备"},{"parent":"4d48d557b5e0","children":[],"id":"f8401d16c024","title":"恢复的时候比较快"},{"parent":"4d48d557b5e0","children":[],"id":"3bbfd52a1687","title":"快照文件生成时间久，消耗cpu"},{"parent":"4d48d557b5e0","children":[],"id":"ccc6b9dcf9fb","title":"采用COW机制来实现持久化，fork进程处理"}],"id":"4d48d557b5e0","title":"RDB"},{"parent":"668b1454d74f","children":[{"parent":"9e12decdb68e","children":[],"id":"1d0805523949","title":"appendOnly"},{"parent":"9e12decdb68e","children":[],"id":"89f5517d6396","title":"数据齐全"},{"parent":"9e12decdb68e","children":[],"id":"7491a69d002c","title":"只对内存进行修改的指令记录，进行恢复时相当于‘重放’所有执行指令"},{"parent":"9e12decdb68e","children":[{"parent":"91d9756055c8","children":[],"id":"5d34419b1718","title":"定期做AOF瘦身"}],"id":"91d9756055c8","title":"回复慢文件大"}],"id":"9e12decdb68e","title":"AOF"},{"parent":"668b1454d74f","children":[{"parent":"0aa38f4c9668","children":[],"id":"7f7ef8d8748a","title":"同时将RDB与AOF存放在一起，AOF只存储自持久化开始到持久化结束期间的AOF日志"},{"parent":"0aa38f4c9668","children":[],"id":"a52194b87860","title":"在重启时先加载RDB的内容，然后重放AOF日志，提升效率"}],"id":"0aa38f4c9668","title":"混合持久化"}],"id":"668b1454d74f","title":"持久化"},{"parent":"ada2330a2425","children":[{"parent":"58b8b6e3fee6","children":[],"id":"b7cb33dbb795","title":"主从数据采取异步同步"},{"parent":"58b8b6e3fee6","children":[],"id":"f596df0cfe11","title":"保证最终一致性，从节点会努力追赶主节点，保证最终情况下从节点与主节点一致"},{"parent":"58b8b6e3fee6","children":[{"parent":"c9582a62c7b9","children":[{"parent":"09c563a5c389","children":[],"id":"b12a602a099d","title":"Redis将对自己状态产生修改影响的指令记录在本地内存buffer中，然后异步将buffer中的指令同步到从节点"},{"parent":"09c563a5c389","children":[],"id":"6985e3bc5bf5","title":"复制内存buffer是定长的环形数组，如果数组内容满了，就会从头开始覆盖"},{"parent":"09c563a5c389","children":[],"id":"e0065aa5e0d6","title":"在网络不好的情况下，可能导致无法通过指令流来同步"}],"id":"09c563a5c389","title":"增量同步"},{"parent":"c9582a62c7b9","children":[{"parent":"7e28cef0ac53","children":[],"id":"923021194d4e","title":"快照同步十分耗费资源"},{"parent":"7e28cef0ac53","children":[],"id":"b6a46fbc2eea","title":"首先在主节点进行bgsave，将当前内存快照到磁盘文件，然后再将快照传送到从节点"},{"parent":"7e28cef0ac53","children":[],"id":"19da982e6ab9","title":"快照文件接受完毕后，先将当前内存数据清空，然后执行全量加载，加载后通知主节点进行增量同步"},{"parent":"7e28cef0ac53","children":[],"id":"4954c29c2a50","title":"在此期间buffer依旧向前移动，如果快照同步时间过长或复制buffer太小，就会导致快照同步后依旧无法增量同步，导致死循环"},{"parent":"7e28cef0ac53","children":[],"id":"d4a0e45138a3","title":"所以务必要配置合适的复制buffer大小，避免快照复制死循环"}],"id":"7e28cef0ac53","title":"快照同步"}],"id":"c9582a62c7b9","title":"同步方式"}],"id":"58b8b6e3fee6","title":"数据同步机制"},{"parent":"ada2330a2425","children":[{"parent":"14d4ebf6fb81","children":[],"id":"6d287264815f","title":"哨兵负责监控主从节点健康，当主节点挂掉时，自动选择最优节点成为主节点，原主节点恢复后会变为从节点"},{"parent":"14d4ebf6fb81","children":[{"parent":"22cf1ee3be06","children":[],"id":"0ec54367a481","title":"Redis采用异步复制，所以当主节点挂掉，从节点可能没有收到全部的同步消息，产生消息丢失"},{"parent":"22cf1ee3be06","children":[{"parent":"fea18cac6d2a","children":[],"id":"bb55da53f849","title":"表示主节点必须至少有一个从节点在进行正常复制，否则就停止对外写服务"}],"id":"fea18cac6d2a","title":"min-slaves-to-write 1"},{"parent":"22cf1ee3be06","children":[{"parent":"9d3ebc781908","children":[],"id":"85f361fff22e","title":"表示如果10s内没有收到从节点的反馈，就意味着同步不正常"}],"id":"9d3ebc781908","title":"min-slaves-max-lag 10"}],"id":"22cf1ee3be06","title":"消息丢失"}],"id":"14d4ebf6fb81","title":"哨兵"},{"parent":"ada2330a2425","children":[],"id":"6a204b455e04","title":"集群"}],"id":"ada2330a2425","title":"高可用"},{"parent":"68e2d53f78c0","children":[{"parent":"0bbbfddb1bdf","children":[],"id":"1c62574854ce","title":"合并读、写命令，减少网络开销"}],"id":"0bbbfddb1bdf","title":"管道"},{"parent":"68e2d53f78c0","children":[{"parent":"550cc736d1a3","children":[{"parent":"27b517127004","children":[],"id":"697a6405075e","title":"创建一个定时器，当key设置有过期时间，且到达过期时间，由定时器任务立即删除"},{"parent":"27b517127004","children":[],"id":"fbf01411fe0d","title":"可以快速释放掉不必要的内存占用 ， 但是CPU压力很大<br>"}],"id":"27b517127004","title":"定时删除"},{"parent":"550cc736d1a3","children":[{"parent":"5ad26b89a5fb","children":[],"id":"1954cef851e8","title":"在客户端访问key的时候，进行过期检查，如果过期了立即删除"}],"id":"5ad26b89a5fb","title":"惰性策略（惰性删除）"},{"parent":"550cc736d1a3","children":[{"parent":"1494d4d816a6","children":[],"id":"80ed3dbfa838","title":"Redis会将设置了过期时间的key放入一个独立的字典中，定期遍历（默认每秒进行10次扫描）来删除到底的key"},{"parent":"1494d4d816a6","children":[{"parent":"3c1730cc5539","children":[],"id":"418e5db9ebc8","title":"定期遍历不会遍历所有的key，而是采取贪心策略"},{"parent":"3c1730cc5539","children":[],"id":"c03a6231598c","title":"从过期字典中随机选出20个key"},{"parent":"3c1730cc5539","children":[],"id":"d50e70020d97","title":"删除这20个key中已经过期的key"},{"parent":"3c1730cc5539","children":[],"id":"2de36c1ef778","title":"如果过期的key的比例超过1/4，则重复步骤"},{"parent":"3c1730cc5539","children":[],"id":"c646a5a92ec1","title":"为保证不会循环过度，扫描时间上线默认不会超过25ms"}],"id":"3c1730cc5539","title":"贪心策略"}],"id":"1494d4d816a6","title":"定时扫描（定时删除）"},{"parent":"550cc736d1a3","children":[{"parent":"15e0714a86f6","children":[],"id":"0d3ad6052dae","title":"从节点不会进行过期扫描，对过期的处理时被动的，主节点在key到期时，会在AOF里增加一条del指令，同步到从节点，从节点通过指令删除key"}],"id":"15e0714a86f6","title":"从节点过期策略"}],"id":"550cc736d1a3","title":"过期策略"},{"parent":"68e2d53f78c0","children":[{"parent":"1c19ee20a68c","children":[{"parent":"35311e5d526f","children":[],"id":"6ff15aa765c3","title":"采用key/value+链表实现，当字典元素被访问时移动到表头，当空间满的时候踢掉链表尾部的元素"},{"parent":"35311e5d526f","children":[{"parent":"9b7510be7f91","children":[],"id":"dbcf0b911044","title":"Redis的LRU采用一种近似LRU的算法，为每个key增加一个额外字段长度为24bit，为最后一次访问的时间戳"},{"parent":"9b7510be7f91","children":[],"id":"3f37f69602f0","title":"采取懒惰方式处理，当执行写入操作时如果超出最大内存就执行一次LRU淘汰算法，随机采样5（数量可设置）个key，淘汰掉最旧的key，如果淘汰后依旧超出最大内存则继续淘汰"}],"id":"9b7510be7f91","title":"Redis的LRU"}],"id":"35311e5d526f","title":"LRU"}],"id":"1c19ee20a68c","title":"淘汰机制"},{"parent":"68e2d53f78c0","children":[{"parent":"ef4cb7bfd92a","children":[],"id":"77a7955156bf","title":"在调用套接字方法的时候默认是阻塞的，比如，read方法需要读取规定字节后返回，如果没有线程就会卡在那里，直到新数据来或者链接关闭<br>"},{"parent":"ef4cb7bfd92a","children":[],"id":"f329207efe37","title":"write方法一般不会阻塞，除非内核为套接字分配的写缓冲区已经满了"},{"parent":"ef4cb7bfd92a","children":[],"id":"9bf3cb96e12d","title":"非阻塞的IO提供了一个选项Non_Blocking，打开时读写都不会阻塞&nbsp;读多少写多少取决于内核的套接字字节分配"},{"parent":"ef4cb7bfd92a","children":[],"id":"9235ef78e11c","title":"非阻塞IO也有问题线程要读数据读了一点就返回了线程什么时候知道继续读？写一样"},{"parent":"ef4cb7bfd92a","children":[],"id":"eb7e9d548e40","title":"一般都是select解决但是性能低现在都是epoll"}],"id":"ef4cb7bfd92a","title":"多路IO复用"}],"collapsed":true,"id":"68e2d53f78c0","title":"Redis"},{"parent":"root","lineStyle":{"randomLineColor":"#13A3ED"},"children":[{"parent":"27d73b325f5b","children":[{"parent":"c126505c846f","children":[{"parent":"3e6fdbd260dc","children":[{"parent":"e3bbe630c77b","children":[],"id":"a52aafb21566","title":"所有节点拥有数据的最新版本"}],"id":"e3bbe630c77b","title":"数据一致性(consistency)"},{"parent":"3e6fdbd260dc","children":[{"parent":"16f0be57497c","children":[],"id":"13e807d7a20d","title":"数据具备高可用性"}],"id":"16f0be57497c","title":"可用性(availability)"},{"parent":"3e6fdbd260dc","children":[{"parent":"7394b794bc3c","children":[],"id":"6f3f4c1e3e1f","title":"容忍网络出现分区，分区之间网络不可达"}],"id":"7394b794bc3c","title":"分区容错性(partition-tolerance)"}],"id":"3e6fdbd260dc","title":"概念"},{"parent":"c126505c846f","children":[{"parent":"ea8b927aa992","children":[],"id":"ea6785f04b60","title":"在容忍网络分区的条件下，“强一致性”和“极致可用性”无法同时达到"}],"id":"ea8b927aa992","title":"定义"}],"id":"c126505c846f","title":"CAP理论"},{"parent":"27d73b325f5b","children":[{"parent":"d63e14aab5d1","children":[{"parent":"94404e1c447b","children":[],"id":"dd9e2dc4af2a","title":"用来维护各个服务的注册信息 , 各个服务通过注册清单的服务名获取服务具体位置(IP地址会变，服务名一般不会变)<br>"}],"id":"94404e1c447b","title":"服务治理：Eureka"},{"parent":"d63e14aab5d1","children":[{"parent":"885fa0bda765","children":[],"id":"f1bd36f604b7","title":"客户端可以从Eureka Server中得到一份服务清单，在发送请求时通过负载均衡算法，在多个服务器之间选择一个进行访问"}],"id":"885fa0bda765","title":"客户端负载均衡：Ribbon"},{"parent":"d63e14aab5d1","children":[{"parent":"84ec94898586","children":[],"id":"320c4c3f6ad0","title":"在远程服务出现延迟, 宕机情况时提供断路器、线程隔离等功能"}],"id":"84ec94898586","title":"服务容错保护：Hystrix"},{"parent":"d63e14aab5d1","children":[{"parent":"3f725be6c509","children":[],"id":"5d6df34cab52","title":"整合了Ribbon与Hystrix"}],"id":"3f725be6c509","title":"声明式服务调用：Feign"},{"parent":"d63e14aab5d1","children":[{"parent":"b19ae03b802a","children":[],"id":"1cee4018ba68","title":"解决路由规则与服务实例的维护间题,&nbsp;签名校验、 登录校验冗余问题<br>"}],"id":"b19ae03b802a","title":"API网关服务：Zuul"},{"parent":"d63e14aab5d1","children":[{"parent":"261553fe2960","children":[{"parent":"510230e16d5b","children":[],"id":"605eb3a4eb2a","title":"通过接口获取数据、并依据此数据初始化自己的应用"}],"id":"510230e16d5b","title":"Client<br>"},{"parent":"261553fe2960","children":[{"parent":"f97a17502384","children":[],"id":"f0fc3067f1d7","title":"提供配置文件的存储、以接口的形式将配置文件的内容提供出去"}],"id":"f97a17502384","title":"Server"}],"id":"261553fe2960","title":"分布式配置中心：Config"}],"id":"d63e14aab5d1","title":"基础功能"}],"collapsed":true,"id":"27d73b325f5b","title":"Spring Cloud"},{"parent":"root","lineStyle":{"randomLineColor":"#A04AFB"},"children":[],"id":"bc354f9b6903","title":"Zookeeper"},{"parent":"root","lineStyle":{"randomLineColor":"#74C11F"},"children":[],"id":"96167bd3313f","title":"Dubbo"},{"parent":"root","lineStyle":{"randomLineColor":"#F4325C"},"children":[{"parent":"2967bbb0ce6c","children":[{"parent":"12f64511f568","children":[{"parent":"75e771d94bbf","children":[{"parent":"53caf8da86e2","children":[],"id":"696233005cce","title":"NameServer是几乎无状态的, 可以横向扩展, 节点之间相互无通信, 可以通过部署多态机器来标记自己是一个伪集群"},{"parent":"53caf8da86e2","children":[],"id":"20cf4d49dd61","title":"NameServer的压力不会太大, 主要开销在维持心跳和提供Topic-Broker的关系数据"},{"parent":"53caf8da86e2","children":[],"id":"c96708a3cb62","title":"Broker向NameServer发送心跳时, 会携带上当前自己所负责的所有Topic信息(万级别), 若Topic个数很多会导致一次心跳中,就Topic的数据就几十M, 网络情况差的情况下, 网络传输失败, 心跳失败, 导致NameServer误认为Broker心跳失败"}],"id":"53caf8da86e2","title":"主要负责对于源数据的管理，包括了对于Topic和路由信息的管理"}],"id":"75e771d94bbf","title":"NameServer"},{"parent":"12f64511f568","children":[{"parent":"daad2e0ee47a","children":[{"parent":"a3a4b2fa4290","children":[],"id":"a876bb50d0bb","title":"Broker是具体提供业务的服务器, 单个Broker节点与所有NameServer节点保持长连接及心跳, 并会定时将Topic信息注册到NameServer"},{"parent":"a3a4b2fa4290","children":[],"id":"888065bfed04","title":"底层通信和连接基于Netty实现"},{"parent":"a3a4b2fa4290","children":[],"id":"6ff21d8f55f8","title":"Broker负责消息存储, 以Topic为维度支持轻量级队列, 单机可以支撑上万队列规模, 支持消息推拉模型"}],"id":"a3a4b2fa4290","title":"消息中转角色，负责存储消息，转发消息"}],"id":"daad2e0ee47a","title":"Broker"},{"parent":"12f64511f568","children":[{"parent":"5c20008f41b6","children":[{"parent":"cfa99e651913","children":[],"id":"ecf9bb3145fc","title":"同步发送: 发送者向MQ执行发送消息API,同步等待,直到消息服务器返回发送结果。一般用于重要通知消息，例如重要通知邮件、营销短信<br>"},{"parent":"cfa99e651913","children":[],"id":"8f1cefc4cec3","title":"异步发送: 发送这向MQ执行发送消息API,指定消息发送成功后的回调函数,然后调用消息发送API后,立即返回.消息发送者线程不阻塞,直到运行结束,消息发送成功或失败的回调任务在一个新线程执行，一般用于可能链路耗时较长而对响应时间敏感的业务场景，例如用户视频上传后通知启动转码服务"},{"parent":"cfa99e651913","children":[],"id":"93201d04d89e","title":"单向发送: 发送者MQ执行发送消息API时,直接返回,不等待消息服务器的结果,也不注册回调函数,简单的说就是只管发送,不在乎消息是否成功存储在消息服务器上,适用于某些耗时非常短但对可靠性要求并不高的场景，例如日志收集"}],"id":"cfa99e651913","title":"消息生产者，负责产生消息，一般由业务系统负责产生消息"}],"id":"5c20008f41b6","title":"Producer"},{"parent":"12f64511f568","children":[{"parent":"d7f396e0831a","children":[{"parent":"2167651c5963","children":[],"id":"eac97c7046ef","title":"Pull: 拉取型消费,主动从消息服务器拉去信息, 只要批量拉取到消息, 用户应用就会启动消费过程, 所以Pull成为主动消费型"},{"parent":"2167651c5963","children":[],"id":"e1d302c81ee1","title":"Push: 推送型消费者,封装了消息的拉去,消费进度和其他的内部维护工作, 将消息到达时执行回调接口留给用户程序来实现. 所以Push成为被动消费型, 但从实现上看还是从消息服务器中拉取消息, 不同于Pull的时Push首先要注册消费监听器, 当监听器触发后才开始消费消息"}],"id":"2167651c5963","title":"消息消费者，负责消费消息，一般是后台系统负责异步消费"}],"id":"d7f396e0831a","title":"Consumer"}],"id":"12f64511f568","title":"基础组成"},{"parent":"2967bbb0ce6c","children":[{"parent":"604a2b077915","children":[{"parent":"5f5dd32d8097","children":[{"parent":"ee7415aca487","children":[{"parent":"e8d48c3eb98c","children":[],"id":"c36cc866bf9a","title":"配置简单，单个Master宕机或重启维护对应用无影响，在磁盘配置为RAID10时，即使机器宕机不可恢复情况下，由于RAID10磁盘非常可靠，消息也不会丢失（异步刷盘丢失少量消息，同步刷盘一条不丢）。性能最高"}],"id":"e8d48c3eb98c","title":"优点"},{"parent":"ee7415aca487","children":[{"parent":"043f2943a836","children":[],"id":"df0bc145fab6","title":"单台机器宕机期间，这台机器上未被消费的消息在机器恢复之前不可订阅，消息实时性会受到影响"}],"id":"043f2943a836","title":"缺点"}],"id":"ee7415aca487","title":"一个集群无Slave，全是Master"}],"id":"5f5dd32d8097","title":"多Master"},{"parent":"604a2b077915","children":[{"parent":"1257c8b9aac5","children":[{"parent":"ea64577686e3","children":[{"parent":"90097b5c030f","children":[],"id":"e53091b80fab","title":"即使磁盘损坏，消息丢失的非常少，且消息实时性不会受影响，因为Master 宕机后，消费者仍然可以从Slave消费，此过程对应用透明。不需要人工干预。性能同多 Master 模式几乎一样"}],"id":"90097b5c030f","title":"优点"},{"parent":"ea64577686e3","children":[{"parent":"4ef440c8e995","children":[],"id":"27e509d14ece","title":"Master宕机，磁盘损坏情况，会丢失少量消息"}],"id":"4ef440c8e995","title":"缺点"}],"id":"ea64577686e3","title":"每个Master配置一个Slave，有多对Master-Slave，HA采用异步复制方式，主备有短暂消息延迟，毫秒级"}],"id":"1257c8b9aac5","title":"多Master, 多Salve, 异步复制"},{"parent":"604a2b077915","children":[{"parent":"4d53b0661b8f","children":[{"parent":"e4a1b95dba5b","children":[{"parent":"9f79418a5eed","children":[],"id":"a5fc7ac37b70","title":"数据与服务都无单点，Master宕机情况下，消息无延迟，服务可用性与数据可用性都非常高"}],"id":"9f79418a5eed","title":"优点"},{"parent":"e4a1b95dba5b","children":[{"parent":"6dd53df1911c","children":[],"id":"57d816d31bf1","title":"性能比异步复制模式略低，大约低10%左右，发送单个消息的RT会略高。目前主宕机后，备机不能自动切换为主机，后续会支持自动切换功能"}],"id":"6dd53df1911c","title":"缺点"}],"id":"e4a1b95dba5b","title":"每个Master配置一个Slave，有多对Master-Slave，HA采用同步双写方式，主备都写成功，向应用才返回成功"}],"id":"4d53b0661b8f","title":"多Master, 多Salve, 双写一致"}],"id":"604a2b077915","title":"支持集群模式"},{"parent":"2967bbb0ce6c","children":[{"parent":"43da161a94fa","children":[],"id":"1a296e9626d0","title":"只有发送成功后返回CONSUME_SUCCESS, 消费才是完成的"},{"parent":"43da161a94fa","children":[{"parent":"8fc82e05be66","children":[{"parent":"d99a334f4878","children":[],"id":"40fed29109ad","title":"当确认批次消息消费失败 (RECONSUME_LATER) 时, RocketMQ会把这批消息重发回Broker (此处非原Topic 而是这个消费者组的 RETRY Topic)<br>"},{"parent":"d99a334f4878","children":[],"id":"ec26b776e2f4","title":"在延迟的某个时间点（默认是10秒，业务可设置）后，再次投递到这个ConsumerGroup"},{"parent":"d99a334f4878","children":[],"id":"c226bf1b7a9e","title":"如果一直这样重复消费都持续失败到一定次数（默认16次），就会投递到死信队列。人工干预解决"}],"id":"d99a334f4878","title":"顺序消息重试"},{"parent":"8fc82e05be66","children":[],"id":"813946fc3144","title":"无序消息重试"}],"id":"8fc82e05be66","title":"消息重试"},{"parent":"43da161a94fa","children":[{"parent":"a38425631022","children":[],"id":"0964ff7a4b1c","title":"RocketMQ 以 Consumer Group (消费者组)+ Queue (队列) 为单位管理消费进度, 通过 Consumer Offset 标记这个这个消费组在这条Queue上的消费进度"},{"parent":"a38425631022","children":[],"id":"76e7f5c236a6","title":"如果已存在的消费组出现了新消费实例的时候，依靠这个组的消费进度，可以判断第一次是从哪里开始拉取的"},{"parent":"a38425631022","children":[],"id":"65266364b4b8","title":"每次消息成功后，本地的消费进度会被更新，然后由定时器定时同步到broker，以此持久化消费进度"},{"parent":"a38425631022","children":[],"id":"ddcbe167cfd1","title":"但每次记录消费进度时，只会将一批消息中最小的offset值更新为消费进度值"}],"id":"a38425631022","title":"ACK机制"},{"parent":"43da161a94fa","children":[{"parent":"ce692c70d0c6","children":[],"id":"8c1e99f1aebf","title":"由于消费进度只记录了一个下标，就可能出现拉取了100条消息如 100 - 200 的消息，后面99条都消费结束了，只有101消费一直没有结束的情况"},{"parent":"ce692c70d0c6","children":[],"id":"88f2e23d3c86","title":"RocketMQ为了保证消息肯定被消费成功，消费进度只能维持在101，直到101也消费结束，本地消费进度才能标记200消费结束"},{"parent":"ce692c70d0c6","children":[],"id":"df1747541718","title":"在这种情况下，如果RocketMQ机器断电，或者被kill, 此处的消费进度就还是101, 当队列重新分配实例时, 从broker获取的消费进度维持在101, 就会出现重复消费的情况<br>"},{"parent":"ce692c70d0c6","children":[],"id":"3528a1c8bcec","title":"对于这个场景，RocketMQ暂时无能为力，所以业务必须要保证消息消费的幂等性"}],"id":"ce692c70d0c6","title":"重复消费"},{"parent":"43da161a94fa","children":[{"parent":"b6fc9dbb9580","children":[],"id":"939f53dff9c0","title":"RocketMQ支持按照时间回溯消费，时间维度精确到毫秒，可以向前回溯，也可以向后回溯"}],"id":"b6fc9dbb9580","title":"消息回溯"}],"id":"43da161a94fa","title":"消费保证"},{"parent":"2967bbb0ce6c","children":[{"parent":"403b9cd18917","children":[{"parent":"4439dd28bd16","children":[{"parent":"40d9bda89e49","children":[],"id":"07d61c838390","title":"由于 NameServer 节点是无状态的，且各个节点直接的数据是一致的，故存在多个 NameServer 节点的情况下，部分 NameServer 不可用也可以保证 MQ 服务正常运行"}],"id":"40d9bda89e49","title":"NameServer 集群"},{"parent":"4439dd28bd16","children":[{"parent":"5e1a97f2162e","children":[{"parent":"2abd6a7fd911","children":[],"id":"f4b8c1adb5df","title":"由于Slave只负责读，当 Master 不可用，它对应的 Slave 仍能保证消息被正常消费"},{"parent":"2abd6a7fd911","children":[],"id":"8139525d3db2","title":"由于配置多组 Master-Slave 组，其他的 Master-Slave 组也会保证消息的正常发送和消费"}],"id":"2abd6a7fd911","title":"一个 Master 可以配置多个 Slave，同时也支持配置多个 Master-Slave 组"}],"id":"5e1a97f2162e","title":"Broker 主从, 多主从"},{"parent":"4439dd28bd16","children":[{"parent":"f82965d473fb","children":[],"id":"7e71895d91ef","title":"Consumer 的高可用是依赖于 Master-Slave 配置的，由于 Master 能够支持读写消息，Slave 支持读消息，当 Master 不可用或繁忙时， Consumer 会被自动切换到从 Slave 读取(自动切换，无需配置)"}],"id":"f82965d473fb","title":"Consumer 自动切换"},{"parent":"4439dd28bd16","children":[{"parent":"f3c6854c53d6","children":[],"id":"ad3cb7405f89","title":"在创建Topic时, 将Topic的多个 Message Queue 创建在多个 Broker组 上,&nbsp;这样当一个Broker组的Master不可用后，其他组的Master仍然可用，Producer仍然可以发送消息<br>"}],"id":"f3c6854c53d6","title":"Producer 连接多个 Broker"}],"id":"4439dd28bd16","title":"集群"},{"parent":"403b9cd18917","children":[{"parent":"a4cb838b16ea","children":[{"parent":"c2592c074d4c","children":[],"id":"07ef479a1045","title":"在返回写成功状态时，消息已经被写入磁盘中。即消息被写入内存的PAGECACHE 中后，立刻通知刷新线程刷盘，等待刷盘完成，才会唤醒等待的线程并返回成功状态, 超时会返回错误<br>"}],"id":"c2592c074d4c","title":"同步刷盘"},{"parent":"a4cb838b16ea","children":[{"parent":"3d7e8bfce714","children":[],"id":"4fe92f30b6e8","title":"在返回写成功状态时，消息可能只是被写入内存的 PAGECACHE 中。当内存的消息量积累到一定程度时，触发写操作快速写入, 不返回错误"}],"id":"3d7e8bfce714","title":"异步刷盘"}],"id":"a4cb838b16ea","title":"刷盘机制"},{"parent":"403b9cd18917","children":[{"parent":"55e8e026c976","children":[{"parent":"7e5670f14a09","children":[],"id":"8859c3e13bfe","title":"Master 和 Slave 均写成功后才反馈给客户端写成功状态"}],"id":"7e5670f14a09","title":"同步复制"},{"parent":"55e8e026c976","children":[{"parent":"953b201d922e","children":[],"id":"c97878eed758","title":"只要 Master 写成功，就反馈客户端写成功状态"}],"id":"953b201d922e","title":"异步复制"}],"id":"55e8e026c976","title":"消息的主从复制"}],"id":"403b9cd18917","title":"高可用"},{"parent":"2967bbb0ce6c","children":[{"parent":"e56a055b5068","children":[{"parent":"16961909ae98","children":[],"id":"944ff5256f15","title":"RocketMQ提供了MessageQueueSelector队列选择机制"},{"parent":"16961909ae98","children":[],"id":"68d213356c50","title":"顺序发送 顺序消费由 消费者保证"}],"id":"16961909ae98","title":"Hash取模法"}],"id":"e56a055b5068","title":"顺序消费"},{"parent":"2967bbb0ce6c","children":[{"parent":"7ce52753f364","children":[{"parent":"4fb6ee988efc","children":[],"id":"8c2657879e36","title":"使用业务端逻辑保持幂等性"}],"id":"4fb6ee988efc","title":"原则"},{"parent":"7ce52753f364","children":[{"parent":"1bfaf765406c","children":[],"id":"b0239da60ed7","title":"对于同一操作发起的一次请求或者多次请求的结果是一致的"}],"id":"1bfaf765406c","title":"幂等性"},{"parent":"7ce52753f364","children":[{"parent":"56d747f52657","children":[],"id":"e39d27d3a0ab","title":"保证每条消息都有唯一编号(比如唯一流水号)，重复消费时主键冲突不再处理消息"}],"id":"56d747f52657","title":"去重策略"}],"id":"7ce52753f364","title":"消息去重"},{"parent":"2967bbb0ce6c","children":[{"parent":"09e95e6eab93","children":[{"parent":"c8a5405fb7c5","children":[],"id":"3570c5920213","title":"暂不能被Consumer消费的消息, 需要 Producer 对消息的二次确认后，Consumer才能去消费它"}],"id":"c8a5405fb7c5","title":"Half Message (半消息)"},{"parent":"09e95e6eab93","children":[{"parent":"332f741948f5","children":[],"id":"be923878148d","title":"A服务先发送个 Half Message 给 Broker 端，消息中携带 B服务"},{"parent":"332f741948f5","children":[],"id":"55ea8b7092e3","title":"当A服务知道 Half Message 发送成功后, 执行本地事务"},{"parent":"332f741948f5","children":[],"id":"4187239edfc5","title":"如果本地事务成功，那么 Producer 像 Broker 服务器发送 Commit , 这样B服务就可以消费该 Message"},{"parent":"332f741948f5","children":[],"id":"08a1fccc842a","title":"如果本地事务失败，那么 Producer 像 Broker 服务器发送 Rollback , 那么就会直接删除上面这条半消息"},{"parent":"332f741948f5","children":[],"id":"4da11302aa7f","title":"如果因为网络等原因迟迟没有返回失败还是成功，那么会执行RocketMQ的回调接口,来进行事务的回查"}],"id":"332f741948f5","title":"流程"},{"parent":"09e95e6eab93","children":[],"id":"a5095a559452","title":"最终一致性"},{"parent":"09e95e6eab93","children":[],"id":"5c502c270ff1","title":"最大努力通知"}],"id":"09e95e6eab93","title":"事务消息"},{"parent":"2967bbb0ce6c","children":[{"parent":"539927f8e2c1","children":[],"id":"88eabed09a58","title":"Producer 和 NameServer 节点建立一个长连接"},{"parent":"539927f8e2c1","children":[],"id":"fc3d758dc575","title":"定期从 NameServer 获取 Topic 信息&nbsp;<br>"},{"parent":"539927f8e2c1","children":[],"id":"e2e50215339f","title":"并且向 Broker Master 建立链接 发送心跳"},{"parent":"539927f8e2c1","children":[],"id":"2063f1f65200","title":"发送消息给 Broker Master"},{"parent":"539927f8e2c1","children":[],"id":"0d92c5a8e5e8","title":"Consumer 从 Mater 和 Slave 一起订阅消息"}],"id":"539927f8e2c1","title":"一次完整的通信流程"},{"parent":"2967bbb0ce6c","children":[{"parent":"9081c42a0f2a","children":[],"id":"1219a6e079fa","title":"不再被正常消费 "},{"parent":"9081c42a0f2a","children":[],"id":"5e70a2a44651","title":"保存3天"},{"parent":"9081c42a0f2a","children":[],"id":"50aa84db0020","title":"面向消费者组 "},{"parent":"9081c42a0f2a","children":[],"id":"d4a503883c07","title":"控制台 重发  重写消费者 单独消费"}],"id":"9081c42a0f2a","title":"死信队列"},{"parent":"2967bbb0ce6c","children":[{"parent":"cc10915c102e","children":[{"parent":"712c8d74cc5a","children":[],"id":"e8d44e34f882","title":"生产者将消息发送给Rocket MQ的时候，如果出现了网络抖动或者通信异常等问题，消息就有可能会丢失"},{"parent":"712c8d74cc5a","children":[{"parent":"e4ba8bf00b71","children":[],"id":"4d4c85447a76","title":"如果消息还没有完成异步刷盘，RocketMQ中的Broker宕机的话，就会导致消息丢失"},{"parent":"e4ba8bf00b71","children":[],"id":"a16ee1c4706e","title":"如果消息已经被刷入了磁盘中，但是数据没有做任何备份，一旦磁盘损坏，那么消息也会丢失"}],"id":"e4ba8bf00b71","title":"消息需要持久化到磁盘中，这时会有两种情况导致消息丢失"},{"parent":"712c8d74cc5a","children":[],"id":"f521198db453","title":"消费者成功从RocketMQ中获取到了消息，还没有将消息完全消费完的时候，就通知RocketMQ我已经将消息消费了，然后消费者宕机，但是RocketMQ认为消费者已经成功消费了数据，所以数据依旧丢失了"}],"id":"712c8d74cc5a","title":"常见场景"},{"parent":"cc10915c102e","children":[{"parent":"6e0281cde36d","children":[],"id":"d1b9b4c61dab","title":"事务消息"},{"parent":"6e0281cde36d","children":[],"id":"d5c1c1f6fd31","title":"同步刷盘"},{"parent":"6e0281cde36d","children":[],"id":"dd9b94bf2957","title":"主从机构的话，需要Leader将数据同步给Followe"},{"parent":"6e0281cde36d","children":[],"id":"29c1212c50c6","title":"消费时无法异步消费，只能等待消费完成再通知RocketMQ消费完成"}],"id":"6e0281cde36d","title":"确保消息零丢失"},{"parent":"cc10915c102e","children":[],"id":"f6dabcb64d3a","title":"上述方案会使性能和吞吐量大幅下降, 需按场景谨慎使用"}],"id":"cc10915c102e","title":"消息丢失"},{"parent":"2967bbb0ce6c","children":[{"parent":"ce3c57e6ab49","children":[{"parent":"12b15ec01705","children":[{"parent":"c74aa299e8ad","children":[],"id":"24987ee3c4db","title":"在业务允许的情况下,&nbsp;根据一定的丢弃策略来丢弃消息"},{"parent":"c74aa299e8ad","children":[],"id":"036abd6de052","title":"修复Consumer不消费问题，使其恢复正常消费，根据业务需要看是否要暂停"},{"parent":"c74aa299e8ad","children":[],"id":"81368743808e","title":"停止消费 加机器 加Topic,&nbsp;编写临时处理分发程序消费<br>"}],"id":"c74aa299e8ad","title":"丢弃, 扩容"}],"id":"12b15ec01705","title":"解决思想"}],"id":"ce3c57e6ab49","title":"消息堆积"},{"parent":"2967bbb0ce6c","children":[{"parent":"8a5fb1ce1bf4","children":[],"id":"05af140b142a","title":"RocketMQ支持定时消息，但是不支持任意时间精度，支持特定的level，例如定时5s，10s，1m等"}],"id":"8a5fb1ce1bf4","title":"定时消息"},{"parent":"2967bbb0ce6c","children":[{"parent":"4aa9ff9ad75c","children":[{"parent":"954e7ff26b10","children":[],"id":"e4f3f3d220ec","title":"单机吞吐量：十万级"},{"parent":"954e7ff26b10","children":[],"id":"c4af3edfc858","title":"可用性：非常高，分布式架构"},{"parent":"954e7ff26b10","children":[],"id":"6a45885fa4c5","title":"消息可靠性：经过参数优化配置，消息可以做到零丢失"},{"parent":"954e7ff26b10","children":[],"id":"77955f421cf0","title":"功能支持：MQ功能较为完善，还是分布式的，扩展性好"},{"parent":"954e7ff26b10","children":[],"id":"2159848b2077","title":"支持10亿级别的消息堆积，不会因为堆积导致性能下降"},{"parent":"954e7ff26b10","children":[],"id":"8d732d9ac0d4","title":"源码是java，我们可以自己阅读源码，定制自己公司的MQ，可以掌控"},{"parent":"954e7ff26b10","children":[],"id":"079ef0e7e12f","title":"天生为金融互联网领域而生，对于可靠性要求很高的场景，尤其是电商里面的订单扣款，以及业务削峰，在大量交易涌入时，后端可能无法及时处理的情况"},{"parent":"954e7ff26b10","children":[],"id":"24933c880163","title":"RoketMQ在稳定性上可能更值得信赖，这些业务场景在阿里双11已经经历了多次考验，如果你的业务有上述并发场景，建议可以选择RocketMQ"}],"id":"954e7ff26b10","title":"优点"},{"parent":"4aa9ff9ad75c","children":[{"parent":"6e817ad0d7e2","children":[],"id":"5bc9518a9f6f","title":"支持的客户端语言不多，目前是java及c++，其中c++不成熟"},{"parent":"6e817ad0d7e2","children":[],"id":"bee9d1ce399f","title":"社区活跃度不是特别活跃那种"},{"parent":"6e817ad0d7e2","children":[],"id":"9e4046411e3c","title":"没有在 mq 核心中去实现JMS等接口，有些系统要迁移需要修改大量代码"}],"id":"6e817ad0d7e2","title":"缺点"}],"id":"4aa9ff9ad75c","title":"优缺点总结"}],"collapsed":true,"id":"2967bbb0ce6c","title":"RocketMQ"},{"parent":"root","lineStyle":{"randomLineColor":"#7754F6"},"children":[],"id":"6b402feebcdf","title":"分布式锁"},{"parent":"root","lineStyle":{"randomLineColor":"#FFCA01"},"children":[{"parent":"426e047d8f9d","children":[{"parent":"4b90dd9864c9","children":[{"parent":"057add18a139","children":[{"parent":"3097acf2ab15","children":[],"id":"df6d551163bc","title":"假如在第一阶段所有参与者都返回准备成功，那么协调者则向所有参与者发送提交事务命令，等待所有事务都提交成功之后，返回事务执行成功"},{"parent":"3097acf2ab15","children":[],"id":"d16589167a2c","title":"假如在第一阶段有一个参与者返回失败，那么协调者就会向所有参与者发送回滚事务的请求，即分布式事务执行失败"}],"id":"3097acf2ab15","title":"准备阶段: 协调者(事务管理器)给每个参与者发送Prepare消息，参与者要么直接返回失败,<br>要么在本地执行事务，写本地的redo和undo日志，但不做提交"},{"parent":"057add18a139","children":[{"parent":"8c5177c0c885","children":[],"id":"ab3d2da5a1c6","title":"如果第二阶段提交失败, 执行的是回滚事务操作, 那么会不断重试, 直到所有参与者全部回滚, 不然在第一阶段准备成功的参与者会一直阻塞"},{"parent":"8c5177c0c885","children":[],"id":"acc12723768c","title":"如果第二阶段提交失败, 执行的是提交事务, 也会不断重试, 因为有可能一些参与者已经提交成功, 所以只能不断重试, 甚至人工介入处理<br>"}],"id":"8c5177c0c885","title":"提交阶段: 协调者收到参与者的失败消息或者超时，直接给每个参与者发送回滚消息；<br>否则，发送提交消息. 参与者根据协调者的指令执行提交或回滚操作，释放锁资源"}],"id":"057add18a139","title":"流程"},{"parent":"4b90dd9864c9","children":[],"id":"fb30798578aa","title":"同步阻塞协议"},{"parent":"4b90dd9864c9","children":[{"parent":"7a977c8d1831","children":[],"id":"1cce93f1a7a6","title":"同步阻塞导致长久资源锁定, 效率低"},{"parent":"7a977c8d1831","children":[],"id":"ffb01d2f2712","title":"协调者是一个单点, 存在单点故障问题,&nbsp;参与者将一直处于锁定状态"},{"parent":"7a977c8d1831","children":[],"id":"143f109fefcf","title":"脑裂问题, 在提交阶段，如果只有部分参与者接收并执行了提交请求，会导致节点数据不一致<br>"}],"id":"7a977c8d1831","title":"缺点"},{"parent":"4b90dd9864c9","children":[],"id":"75a98189b051","title":"是数据库层面解决方案"}],"id":"4b90dd9864c9","title":"2PC（两段式提交）"},{"parent":"426e047d8f9d","children":[{"parent":"e8856f8bce0a","children":[{"parent":"1eee8b12a46d","children":[],"id":"623a1ea6da3e","title":"CanCommit阶段:&nbsp;协调者向参与者发送commit请求，参与者如果可以提交就返回Yes响应，否则返回No响应<br>"},{"parent":"1eee8b12a46d","children":[{"parent":"1a7358388525","children":[],"id":"21e1c7f27605","title":"假如协调者从所有的参与者获得的反馈都是Yes响应，那么就会执行事务的预执行"},{"parent":"1a7358388525","children":[],"id":"3e2483073c0d","title":"假如有任何一个参与者向协调者发送了No响应，或者等待超时之后，协调者都没有接到参与者的响应，那么就执行事务的中断"}],"id":"1a7358388525","title":"PreCommit阶段:&nbsp;协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作"},{"parent":"1eee8b12a46d","children":[{"parent":"24711faa09db","children":[{"parent":"28223e1f26b4","children":[],"id":"7ccba5096def","title":"发送提交请求 协调接收到参与者发送的ACK响应，那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求"},{"parent":"28223e1f26b4","children":[],"id":"2dd11bddf0c0","title":"事务提交 参与者接收到doCommit请求之后，执行正式的事务提交。并在完成事务提交之后释放所有事务资源"},{"parent":"28223e1f26b4","children":[],"id":"b7a3a303e9f7","title":"响应反馈 事务提交完之后，向协调者发送Ack响应"},{"parent":"28223e1f26b4","children":[],"id":"211c7b4aa22e","title":"完成事务 协调者接收到所有参与者的ack响应之后，完成事务"}],"id":"28223e1f26b4","title":"执行提交"},{"parent":"24711faa09db","children":[{"parent":"8abf9b8b22a0","children":[],"id":"f7664385e997","title":"发送中断请求 协调者向所有参与者发送abort请求"},{"parent":"8abf9b8b22a0","children":[],"id":"8f46d4cf1026","title":"事务回滚 参与者接收到abort请求之后，利用其在阶段二记录的undo信息来执行事务的回滚操作，并在完成回滚之后释放所有的事务资源"},{"parent":"8abf9b8b22a0","children":[],"id":"18777ca3eaf1","title":"反馈结果 参与者完成事务回滚之后，向协调者发送ACK消息"},{"parent":"8abf9b8b22a0","children":[],"id":"d27ccae9a9b6","title":"中断事务 协调者接收到参与者反馈的ACK消息之后，执行事务的中断"}],"id":"8abf9b8b22a0","title":"中断事务:&nbsp;协调者没有接收到参与者发送的ACK响应, 或响应超时<br>"}],"id":"24711faa09db","title":"doCommit阶段:&nbsp;该阶段进行真正的事务提交"}],"id":"1eee8b12a46d","title":"流程"},{"parent":"e8856f8bce0a","children":[{"parent":"bef994d87c61","children":[],"id":"cce362d1b257","title":"降低了阻塞范围，在等待超时后协调者或参与者会中断事务"},{"parent":"bef994d87c61","children":[],"id":"c30827441ec6","title":"避免了协调者单点问题，doCommit阶段中协调者出现问题时，参与者会继续提交事务"}],"id":"bef994d87c61","title":"优点"},{"parent":"e8856f8bce0a","children":[{"parent":"0c7767c1b90d","children":[],"id":"5942b8825222","title":"脑裂问题依然存在，即在参与者收到PreCommit请求后等待最终指令，如果此时协调者无法与参与者正常通信，会导致参与者继续提交事务，造成数据不一致"}],"id":"0c7767c1b90d","title":"缺点"},{"parent":"e8856f8bce0a","children":[],"id":"5bb33349d790","title":"是数据库层面解决方案"}],"id":"e8856f8bce0a","title":"3PC（三段式提交）"},{"parent":"426e047d8f9d","children":[{"parent":"b0b8679322ca","children":[{"parent":"006e94b9f913","children":[],"id":"59b9eb0f94d6","title":"Try阶段:&nbsp;完成所有业务检查,&nbsp;预留必须业务资源"},{"parent":"006e94b9f913","children":[],"id":"c5313a0fb055","title":"Confirm阶段:&nbsp;真正执行业务,&nbsp;不作任何业务检查,&nbsp;只使用Try阶段预留的业务资源,&nbsp;Confirm操作必须保证幂等性<br>"},{"parent":"006e94b9f913","children":[],"id":"471ecf6d09dc","title":"Cancel阶段:&nbsp;释放Try阶段预留的业务资源,&nbsp;Cancel操作必须保证幂等性<br>"}],"id":"006e94b9f913","title":"流程"},{"parent":"b0b8679322ca","children":[{"parent":"23870fd0159e","children":[],"id":"752def670283","title":"因为Try阶段检查并预留了资源，所以confirm阶段一般都可以执行成功"},{"parent":"23870fd0159e","children":[],"id":"a233a7cbf2e3","title":"资源锁定都是在业务代码中完成，不会block住DB，可以做到对db性能无影响"},{"parent":"23870fd0159e","children":[],"id":"7d19b1ea9c94","title":"TCC的实时性较高，所有的DB写操作都集中在confirm中，写操作的结果实时返回（失败时因为定时程序执行时间的关系，略有延迟）"}],"id":"23870fd0159e","title":"优点"},{"parent":"b0b8679322ca","children":[{"parent":"2ab2502c2344","children":[],"id":"c8b2dc57628d","title":"因为事务状态管理，将产生多次DB操作，这将损耗一定的性能，并使得整个TCC事务时间拉长"},{"parent":"2ab2502c2344","children":[],"id":"4d1e258645f4","title":"事务涉及方越多，Try、Confirm、Cancel中的代码就越复杂，可复用性就越底"},{"parent":"2ab2502c2344","children":[],"id":"56449003ad04","title":"涉及方越多，这几个阶段的处理时间越长，失败的可能性也越高"}],"id":"2ab2502c2344","title":"缺点"},{"parent":"b0b8679322ca","children":[],"id":"4c6d4fa72f28","title":"是业务层面解决方案"}],"id":"b0b8679322ca","title":"TCC（Try、Confirm、Cancel）"},{"parent":"426e047d8f9d","children":[],"id":"121687a469fc","title":"XA"},{"parent":"426e047d8f9d","children":[],"id":"48c137315bda","title":"最大努力通知"},{"parent":"426e047d8f9d","children":[],"id":"c6b7184c14c3","title":"本地消息表（ebay研发出的）"},{"parent":"426e047d8f9d","children":[{"parent":"3b31deded74d","children":[],"id":"84f89c2ef8a1","title":"事务消息"}],"id":"3b31deded74d","title":"半消息/最终一致性（RocketMQ）"}],"collapsed":true,"id":"426e047d8f9d","title":"分布式事务"}],"root":true,"theme":"dark_caihong","id":"root","title":"Java","structure":"mind_right"}},"meta":{"exportTime":"2022-02-16 17:35:03","member":"60b8501a63768975c7bcc153","diagramInfo":{"creator":"60b8501a63768975c7bcc153","created":"2021-06-23 17:55:24","modified":"2022-02-16 17:28:13","title":"Java知识点","category":"mind_free"},"id":"60d3050c1e08532a43b7f737","type":"ProcessOn Schema File","version":"1.0"}}
\ No newline at end of file
diff --git a/TODO/uml/ReplicaManager#appendRecordstxt b/TODO/uml/ReplicaManager#appendRecordstxt
deleted file mode 100644
index cf9eb42aa7..0000000000
--- a/TODO/uml/ReplicaManager#appendRecordstxt
+++ /dev/null
@@ -1,20 +0,0 @@
-@startuml
-title: ReplicaManager#appendRecords
-
-actor       ReplicaManager as ReplicaManager
-
-alt requiredAcks值合法
-ReplicaManager -> ReplicaManager : 写入消息集到本地日志
-ReplicaManager -> ReplicaManager : 构建写入结果状态
-alt 等待其他副本完成写入
-ReplicaManager -> ReplicaManager : 创建延时请求对象
-ReplicaManager -> ReplicaManager : 交由 Puratory 管理
-else
-ReplicaManager -> ReplicaManager : 调用回调逻辑
-end
-else requiredAcks值非法
-ReplicaManager -> ReplicaManager : 构造特定异常对象
-ReplicaManager -> ReplicaManager : 封装进回调函数执行
-end
-
-@enduml
\ No newline at end of file
diff --git a/TODO/uml/mysql.xmind b/TODO/uml/mysql.xmind
deleted file mode 100644
index 2c439b7a2e..0000000000
Binary files a/TODO/uml/mysql.xmind and /dev/null differ
diff --git "a/TODO/uml/redis\344\274\230\345\214\226.xmind" "b/TODO/uml/redis\344\274\230\345\214\226.xmind"
deleted file mode 100644
index f4dfaa071a..0000000000
Binary files "a/TODO/uml/redis\344\274\230\345\214\226.xmind" and /dev/null differ
diff --git a/TODO/uml/spring.xmind b/TODO/uml/spring.xmind
deleted file mode 100644
index f9fd71b68e..0000000000
Binary files a/TODO/uml/spring.xmind and /dev/null differ
diff --git "a/Tomcat/HttpServlet\344\270\272\344\273\200\344\271\210\350\246\201\345\256\236\347\216\260serializable\357\274\237.md" "b/Tomcat/HttpServlet\344\270\272\344\273\200\344\271\210\350\246\201\345\256\236\347\216\260serializable\357\274\237.md"
deleted file mode 100644
index 0d6bacd6c7..0000000000
--- "a/Tomcat/HttpServlet\344\270\272\344\273\200\344\271\210\350\246\201\345\256\236\347\216\260serializable\357\274\237.md"
+++ /dev/null
@@ -1,8 +0,0 @@
-HttpServlet为什么要实现serializable？在什么情况下，servlet会被序列化？
-如果未显示定义serialVersionUID，系统会用什么算法给指定一个？
-![](https://img-blog.csdnimg.cn/d745c434cdc8418e9519cb821fbd9317.png)
-![](https://img-blog.csdnimg.cn/71dc3f2b6ef14d1099605a75c4412639.png)
-Serializable是可序列化。
-简单点将，就是实现了这个接口后，实例就可以转化为数据流了。
-
-Servlet 是有状态的，所以需要持久化到本地（钝化），然后当 Tomcat 重启时，重新加载出来。比如Servlet存储了一些用户登录信息，而当时分布式缓存 redis 也还没流行，所以需要支持可序列化。
\ No newline at end of file
diff --git "a/Tomcat/Servlet\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md" "b/Tomcat/Servlet\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md"
deleted file mode 100644
index c3343e881b..0000000000
--- "a/Tomcat/Servlet\347\232\204\347\224\237\345\221\275\345\221\250\346\234\237.md"
+++ /dev/null
@@ -1,67 +0,0 @@
-Servlet从创建直到毁灭的整个过程：
-- Servlet 初始化后调用 init () 方法
-- Servlet 调用 service() 方法来处理客户端的请求
-- Servlet 销毁前调用 destroy() 方法
-- 最后，Servlet 是由 JVM 的垃圾回收器进行GC
-# init() 
-只调用一次。在第一次创建 Servlet 时被调用，在后续每次用户请求时不再调用。因此，它是用于一次性初始化。
-
-Servlet 创建于用户第一次调用对应于该 Servlet 的 URL 时，但是您也可以指定 Servlet 在服务器第一次启动时被加载。
-
-当用户调用一个 Servlet 时，就会创建一个 Servlet 实例，每一个用户请求都会产生一个新的线程，适当的时候移交给 doGet 或 doPost 方法。init() 方法简单地创建或加载一些数据，这些数据将被用于 Servlet 的整个生命周期。
-```java
-public void init() throws ServletException {
-  // 初始化代码...
-}
-```
-## service()
-执行实际任务的主要方法。Servlet 容器（即 Web 服务器）调用 service() 方法来处理来自客户端（浏览器）的请求，并把格式化的响应写回给客户端。
-
-每次服务器接收到一个 Servlet 请求时，服务器会产生一个新的线程并调用服务。service() 方法检查 HTTP 请求类型（GET、POST、PUT、DELETE 等），并在适当的时候调用 doGet、doPost、doPut，doDelete 等方法。
-```java
-public void service(ServletRequest request, 
-                    ServletResponse response) 
-      throws ServletException, IOException{
-}
-```
-service() 方法由容器调用，service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等方法。所以，您不用对 service() 方法做任何动作，您只需要根据来自客户端的请求类型来重写 doGet() 或 doPost() 即可。
-
-doGet() 和 doPost() 方法是每次服务请求中最常用的方法。下面是这两种方法的特征。
-## doGet()
-GET 请求来自于一个 URL 的正常请求，或者来自于一个未指定 METHOD 的 HTML 表单，它由 doGet() 方法处理。
-```java
-public void doGet(HttpServletRequest request,
-                  HttpServletResponse response)
-    throws ServletException, IOException {
-    // Servlet 代码
-}
-```
-## doPost()
-POST 请求来自于一个特别指定了 METHOD 为 POST 的 HTML 表单，它由 doPost() 方法处理。
-```java
-public void doPost(HttpServletRequest request,
-                   HttpServletResponse response)
-    throws ServletException, IOException {
-    // Servlet 代码
-}
-```
-## destroy() 方法
-destroy() 方法只会被调用一次，在 Servlet 生命周期结束时被调用。destroy() 方法可以让您的 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘，并执行其他类似的清理活动。
-
-在调用 destroy() 方法之后，servlet 对象被标记为垃圾回收。destroy 方法定义如下所示：
-```java
-  public void destroy() {
-    // 终止化代码...
-  }
-```
-# 架构
-![](https://img-blog.csdnimg.cn/64b79221818a4830add2e3333b09355c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_13,color_FFFFFF,t_70,g_se,x_16)
-- 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器
-- Servlet 容器在调用 service() 方法之前加载 Servlet
-- 然后 Servlet 容器处理由多个线程产生的多个请求，每个线程执行一个单一的 Servlet 实例的 service() 方法
-
-![](https://img-blog.csdnimg.cn/4a408cce640d4fccae2efb410bab50e1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-Spring 设计成了枚举
-![](https://img-blog.csdnimg.cn/7267c2be0c0d4f03b7b93405ea8779d5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-Tomat 是设计成普通常量，没有范围，可随便突破。
-区别在于，枚举进行了范围限制。
\ No newline at end of file
diff --git "a/Tomcat/Servlet\350\247\204\350\214\203\345\217\212\345\256\271\345\231\250.md" "b/Tomcat/Servlet\350\247\204\350\214\203\345\217\212\345\256\271\345\231\250.md"
deleted file mode 100644
index e98a8c06de..0000000000
--- "a/Tomcat/Servlet\350\247\204\350\214\203\345\217\212\345\256\271\345\231\250.md"
+++ /dev/null
@@ -1,247 +0,0 @@
-# 1 遥远的CGI
-实现Web动态内容的技术，最早使用的是CGI（Common Gateway Interface，通用网关接口）技术，根据用户输入的请求动态地传送HTML数据。
-CGI并不是开发语言，而只是能够利用为它编写的程序来实现Web服务器的一种协议。
-可用来实现电子商务网站、搜索引擎处理和在线登记等功能。当用户在Web页面中提交输入的数据时，Web浏览器就会将用户输入的数据发送到Web服务器上。在服务器上，CGI程序对输入的数据进行格式化，并将这个信息发送给数据库或服务器上运行的其他程序，然后将结果返回给Web服务器。最后，Web服务器将结果发送给Web浏览器，这些结果有时使用新的Web页面显示，有时在当前Web页面中显示。
-
-编写自定义CGI脚本需要相当多的编程技巧，多数CGI脚本是由Perl,Java,C和C++等语言编写的，服务器上通常很少运行用JavaScript编写的服务器脚本，不管使用何种语言，Web页面设计者都需要控制服务器，包括所需要的后台程序（如数据库），这些后台程序提供结果或来自用户的消息。即使拥有基于服务器的网站设计工具，编写CGI程序也要求程序设计者有一定的经验。
-
-由于每一次对于动态内容的请求都需要启动一个新的CGI程序，因而会增加Web服务器的负担，所以CGI的一个很大缺陷是容易影响Web服务器的运行速度。
-## 脚本编程
-由于CGI程序与HTML文档需要分开编写、分开运行，要将两者融合在一起并不容易，因此，CGI程序的维护与编写比较困难。为了解决这一问题，一些厂商推出了脚本语言来增强网页开发功能。
-
-脚本语言是一种文本型编程语言，可嵌入到HTML文档中。脚本语言分客户端和服务器端两种类型，分别在Web浏览器和Web服务器中运行。
-
-客户端脚本语言主要有JavaScript、Jscript（Microsoft公司的JavaSCript版本）和VBscript等。当Web浏览器需要浏览使用客户端脚本语言编写的Web页面时，Web服务器将客户端脚本连同Web页面一起传送到Web浏览器，Web浏览器同时显示HTML的显示效果和客户端脚本的运行效果， 客户端脚本可减轻Web服务器的处理负担，提高Web页面的响应速度。
-
-服务器端脚本语言主要有ASP，JSP，PHP和LiveWire等。当Web浏览器需要浏览使用服务器端脚本语言编写的Web页面时，Web服务器运行Web页面中的服务器端脚本，将由脚本语言的运行结果与Web页面的HTML部分生成的新的Web页面传送到Web浏览器，Web浏览器显示生成的新的Web页面， 服务器端脚本可减少不同Web浏览器的运行差异，提高Web页面的实用性。
-
-这期间，Java 的 Servlet模型也就诞生了。
-# 2 Servlet
-一种基于Java技术的Web组件，用于生成动态内容，由容器管理。类似于其它Java技术组件，Servlet 是平台无关的Java类组成，并且由Java Web服务器加载执行。
-
-通常由Servlet容器提供运行时环境。Servlet 容器，有时候也称作为Servlet引擎，作为Web服务器或应用服务器的一部分 。通过请求和响应对话，提供Web客户端与Servlets 交互的能力。容器管理Servlets实例以及它们的生命周期。
-
-## 主要版本
-![](https://img-blog.csdnimg.cn/9bdc4d3780174fe6a963291dfea900bb.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-## 核心组件
-|核心组件 API|说明  | 起始版本| Spring framework 代表实现 |
-|--|--|--|--|
-|javax. servlet .Servlet  |  动态内容组件|  1.0| DispatcherServlet|
-|javax. servlet .Filter  |  Servlet过滤器| 2.3| CharacterEncodingFilter|
-|javax. servlet .ServletContext  |  Servlet应用上下文| | |
-|javax. servlet .AsyncContext  |  异步上下文|3.0 | 无|
-|javax. servlet .ServletContextistener  |  ServletContext生命周期监听器| 2.3 | ContextLoaderListener   |
-|javax . servlet . ServletRequestlistener  |  ServletRequest生命周期监听器| 2.3 | RequestContextListener |
-|javax. servlet .http.HttpSessionListener  | 生命周期监听器|  2.3|   HttpSessionMutexListener |
-|javax. servlet .Asynclistener  | 异步上下文监听器|  3.0|  StandardServletAsyncWebRequest  |
-|javax. servlet .ServletContainerInitializer  | Servlet容器初始化器|3.0  |  SpringServletContainerInitializer  |
-
-
-浏览器发给服务端的是一个HTTP请求，HTTP服务器收到请求后，需调用服务端程序处理请求。
-
-> HTTP服务器怎么知道要调用哪个处理器方法？
-
-最简单的就是在HTTP服务器代码写一堆if/else：若是A请求就调x类m1方法，若是B请求就调o类的m2方法。
-这种设计的致命点在于HTTP服务器代码跟业务逻辑耦合，若你新增了业务方法，竟然还得改HTTP服务器代码。
-**面向接口编程**算得上是解决耦合问题的银弹，我们可定义一个接口，各业务类都实现该接口，没错，他就是Servlet接口，实现了Servlet接口的业务类也叫作Servlet。
-
-解决了业务逻辑和HTTP服务器的耦合问题，那又有问题了：对特定请求，HTTP服务器又如何知道：
-- 哪个Servlet负责处理请求？
-- 谁负责实例化Servlet？
-
-显然HTTP服务器不适合负责这些，否则又要和业务逻辑耦合。
-
-于是，诞生了Servlet容器。
-# 3 Servlet容器
-用于加载和管理业务类。
-
-HTTP服务器不直接和业务类交互，而是把请求先交给Servlet容器，Servlet容器内部将请求转发到具体Servlet。
-若该Servlet还没创建，就加载并实例化之，然后调用该Servlet的接口方法。
-
-因此Servlet接口其实是Servlet容器跟具体业务类之间的接口：
-![](https://img-blog.csdnimg.cn/20210716140239681.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-左：HTTP服务器直接调用具体业务类，但紧耦合。
-右：HTTP服务器不直接调用业务类，而是把请求移交给容器，容器通过Servlet接口调用业务类。因此Servlet接口和Servlet容器，实现了HTTP服务器与业务类的解耦。
-
-**Servlet接口和Servlet容器**这一整套规范叫作**Servlet规范**。Tomcat按Servlet规范要求实现了Servlet容器，又兼备HTTP服务器功能。
-若实现新业务，只需实现一个Servlet，并把它注册到Tomcat（Servlet容器），剩下的事情就由Tomcat帮忙。
-# 4  Servlet接口
-Servlet接口定义了下面五个方法：
-![](https://img-blog.csdnimg.cn/20210716140638840.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-具体业务类在service方法实现处理逻辑，入参这两个类是对通信协议的封装：
-- ServletRequest
-封装请求信息
-- ServletResponse
-封装响应信息
-
-HTTP协议中的请求和响应就对应
-- HttpServletRequest
-获取所有请求相关的信息，包括请求路径、Cookie、HTTP头、请求参数等。还能创建和获取Session
-- HttpServletResponse
-封装HTTP响应
-
-生命周期相关方法：
-- init
-Servlet容器在加载Servlet类的时候会调用，可能会在init方法里初始化一些资源。比如Spring MVC中的DispatcherServlet，就是在init方法里创建了自己的Spring容器。
-- destroy
-卸载时会调用，可能在destroy方法里释放这些资源
-
-## ServletConfig
-ServletConfig的作用就是封装Servlet的初始化参数。
-可以在`web.xml`给Servlet配置参数，并在程序通过getServletConfig拿到这些参数。
-
-有接口一般就有抽象类，抽象类用来实现接口和封装通用的逻辑，因此Servlet规范提供了GenericServlet抽象类，可以通过扩展它来实现Servlet。虽然Servlet规范并不在乎通信协议是什么，但是大多数的Servlet都是在HTTP环境中处理的，因此Servet规范还提供了HttpServlet来继承GenericServlet，并且加入了HTTP特性。这样我们通过继承HttpServlet类来实现自己的Servlet，只需要重写两个方法：doGet和doPost。
-![](https://img-blog.csdnimg.cn/20210716162226262.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-# 5 Servlet容器
-## 工作流程
-当客户请求某个资源时
-- HTTP服务器用ServletRequest对象封装客户的请求信息
-- 然后调用Servlet容器的service方法
-- Servlet容器拿到请求后，根据请求的URL和Servlet的映射关系，找到相应的Servlet
-- 如果Servlet还没有被加载，就用反射创建该Servlet
-- 调用Servlet的init方法来完成初始化
-- 调用Servlet的service方法来处理请求
-- 把ServletResponse对象返回给HTTP服务器，HTTP服务器会把响应发送给客户端
-
-![](https://img-blog.csdnimg.cn/20210716165416791.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-## Web应用
-Servlet容器会实例化和调用Servlet，那Servlet怎么注册到Servlet容器？
-
-根据Servlet规范，Web应用程序有一定目录结构，放置了
-- Servlet的类文件
-- 配置文件
-- 静态资源
-
-Servlet容器通过读取配置文件，就能找到并加载Servlet。Web应用的大致目录结构：
-```bash
-| -  MyWebApp
-      | -  WEB-INF/web.xml        -- 配置文件，用来配置Servlet等
-      | -  WEB-INF/lib/           -- 存放Web应用所需各种JAR包
-      | -  WEB-INF/classes/       -- 存放你的应用类，比如Servlet类
-      | -  META-INF/              -- 目录存放工程的一些信息
-```
-# 6 ServletContext
-
-> 定义了一系列servlet用于与其servlet容器通信的方法。如获取文件的 MIME 类型、调度请求或写入日志文件。
-> 每个JVM的Web应用程序都有一个上下文。（Web 应用程序是安装在服务器 URL 名称空间（如
-> `/catalog`）的特定子集下并可能通过 。war 文件安装的服务和内容的集合。如果在部署描述符中标
-> 
-> 分布式系统下，则每个机器节点都有一个上下文实例。在这种情况下，上下文不能用作共享全局信息的位置（因为信息不会是真正的全局的）。应该改用数据库等外部资源。ServletContext
-> 对象包含在 ServletConfig 对象中，当服务器初始化时，Web 服务器会提供该对象。
-
-Servlet规范定义了ServletContext接口对应一个Web应用。比如一个 SpringBoot 应用，那就只有一个ServletContext。
-不要和 Spring 的 applicationContext 混为一谈，因为一个应用中，可以有多个Spring 的 applicationContext。
-
-Web应用部署好后，Servlet容器在启动时会加载Web应用，并为每个Web应用创建一个全局的上下文环境ServletContext对象，为后面的Spring容器提供宿主环境。
-
-可将**ServletContext**看做一个全局对象，一个Web应用可能有多个Servlet，这些Servlet可通过全局ServletContext共享数据：
-- Web应用的初始化参数
-- Web应用目录下的文件资源等
-
-**ServletContext** 持有所有Servlet实例，所以也能实现Servlet请求的转发。
-
-Tomcat&Jetty在启动过程中触发容器初始化事件，Spring的ContextLoaderListener会监听到这个事件，它的contextInitialized方法会被调用，在这个方法中，Spring会初始化全局的Spring根容器，这个就是Spring的IoC容器。
-IoC容器初始化完毕后，Spring将其存储到**ServletContext**，便于以后获取。
-![](https://img-blog.csdnimg.cn/20210716220353539.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-ServletContext就是用来共享数据的，比如SpringMVC需要从ServletContext拿到全局的Spring容器，把它设置成自己的父容器。
-
-Tomcat&Jetty在启动过程中还会扫描Servlet，一个Web应用中的Servlet可以有多个，以SpringMVC中的DispatcherServlet为例，这个Servlet实际上是一个标准的前端控制器，用以转发、匹配、处理每个Servlet请求。
-
-Servlet一般会延迟加载，当第一个请求达到时，Tomcat&Jetty发现DispatcherServlet还没有被实例化，就调用DispatcherServlet的init方法，DispatcherServlet在初始化的时候会建立自己的容器，叫做SpringMVC 容器，用来持有Spring MVC相关的Bean。同时，Spring MVC还会通过ServletContext拿到Spring根容器，并将Spring根容器设为SpringMVC容器的父容器，请注意，Spring MVC容器可以访问父容器中的Bean，但是父容器不能访问子容器的Bean， 也就是说Spring根容器不能访问SpringMVC容器里的Bean。说的通俗点就是，在Controller里可以访问Service对象，但是在Service里不可以访问Controller对象。
-# 初始化工作
-Tomcat/Jetty启动，对于每个WebApp，依次进行初始化工作：
-1、对每个WebApp，都有一个WebApp ClassLoader，和一个ServletContext
-2、ServletContext启动时，会扫描web.xml配置文件，找到Filter、Listener和Servlet配置
-
-3、如果Listener中配有spring的ContextLoaderListener
-3.1、ContextLoaderListener就会收到webapp的各种状态信息。
-3.3、在ServletContext初始化时，ContextLoaderListener也就会将Spring IOC容器进行初始化，管理Spring相关的Bean。
-3.4、ContextLoaderListener会将Spring IOC容器存放到ServletContext中
-
-4、如果Servlet中配有SpringMVC的DispatcherServlet
-4.1、DispatcherServlet初始化时（其一次请求到达）。
-4.2、其中，DispatcherServlet会初始化自己的SpringMVC容器，用来管理Spring MVC相关的Bean。
-4.3、SpringMVC容器可以通过ServletContext获取Spring容器，并将Spring容器设置为自己的根容器。而子容器可以访问父容器，从而在Controller里可以访问Service对象，但是在Service里不可以访问Controller对象。
-4.2、初始化完毕后，DispatcherServlet开始处理MVC中的请求映射关系。
-
-Servlet默认单例模式，Spring的Bean默认也是单例模式，则Spring MVC是如何处理并发请求？
-DispatcherServlet中的成员变量都是初始化好后就不会被改变了，所以是线程安全的，那“可见性”怎么保证呢？
-由Web容器比如Tomcat保证，Tomcat在调用Servlet的init方法时，用synchronized。
-
-- 若还没有至少一个已初始化的实例，则加载并初始化该 servlet 的一个实例。 例如，这可用于加载deployment descriptor中标记为在服务器启动时加载的 servlet。
-实现说明：类名以`org.apache.catalina.`开头的 Servlet org.apache.catalina. （所谓的 servlet容器）由加载此类的同一类加载器加载，而非由当前 Web 应用程序的类加载器加载。 这使此类可以访问 Catalina 内部结构，而对于为 Web 应用程序加载的类，这种访问权限是被阻止的
-![](https://img-blog.csdnimg.cn/20210716204054195.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210716210859693.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210716210934704.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-# 扩展机制
-引入了Servlet规范后，无需关心Socket网络通信、HTTP协议或你的业务类是如何被实例化和调用的，因为这些都被Servlet规范标准化了，我们只需关心怎么实现业务逻辑。
-
-有规范看着很方便，但若规范不能满足你的个性需求，就没法用了，因此设计一个规范或者一个中间件，要充分考虑到可扩展性。
-Servlet规范提供了两种扩展机制：Filter和Listener。
-## Filter
-过滤器，该接口允许你对请求和响应做一些统一的定制化处理。Filter是基于过程的，它是过程的一部分，是基于过程行为的。比如：
-- 根据请求频率限制访问
-- 根据地区不同修改响应内容
-
- 过滤器是 Servlet 的重要标准之一:
-- 请求和响应的统一处理
-- 访问日志记录
-- 请求权限审核
- ......
-都发挥重要作用
-
-### 工作原理
-Web应用部署完成后，Servlet容器需要实例化Filter并把Filter链接成一个FilterChain。当请求进来时，获取第一个Filter并调用doFilter方法，doFilter方法负责调用这个FilterChain中的下一个Filter。
-
-## Listener
-监听器，Listener是基于状态的，任何行为改变同一个状态，触发的事件是一致的。当Web应用在Servlet容器中运行时，Servlet容器内部会不断的发生各种事件，如Web应用的启动和停止、用户请求到达等。 Servlet容器提供了一些默认的监听器来监听这些事件，当事件发生时，Servlet容器会负责调用监听器的方法。当然，你可以定义自己的监听器去监听你感兴趣的事件，将监听器配置在web.xml中。比如Spring就实现了自己的监听器，来监听ServletContext的启动事件，目的是当Servlet容器启动时，创建并初始化全局的Spring容器。
-
-# X  FAQ
-**service方法为什么把request和response都当作输入参数，而不是输入参数只有request，response放到返回值里呢？**
-方便责任链模式下层层传递。
-
-在SpringBoot项目中，为什么没有web.xml了？
-SpringBoot是以嵌入式的方式来启动Tomcat。对于SpringBoot来说，Tomcat只是个JAR包。SpringBoot通过Servlet3.0规范中 **@WebServlet** 注解或者API直接向Servlet容器添加Servlet，无需web.xml。
-
-## 分不清的xxx容器
-### Servlet容器
-用于管理Servlet生命周期。
-### SpringMVC容器
-管理SpringMVC Bean生命周期。
-### Spring容器
-用于管理Spring Bean生命周期。包含许多子容器，其中SpringMVC容器就是其中常用的，DispatcherServlet就是SpringMVC容器中的servlet接口，也是SpringMVC容器的核心类。
-
-Spring容器主要用于整个Web应用程序需要共享的一些组件，比如DAO、数据库的ConnectionFactory等，SpringMVC容器主要用于和该Servlet相关的一些组件，比如Controller、ViewResovler等。
-至此就清楚了Spring容器内部的关系。
-
-**Spring和SpringMVC分别有自己的IOC容器或上下文，为何分成俩容器？** 
-隔离管理的Bean，各管各的，职责明确。SpringMVC的容器直接管理跟DispatcherServlet相关的Bean，也就是Controller，ViewResolver等，并且SpringMVC容器是在DispacherServlet的init方法里创建的。而Spring容器管理其他的Bean比如Service和DAO。
-
-并且SpringMVC容器是Spring容器的子容器，所谓的父子关系意味着什么呢，就是你通过子容器去拿某个Bean时，子容器先在自己管理的Bean中去找这个Bean，如果找不到再到父容器中找。但是父容器不能到子容器中去找某个Bean。
-
-其实这个套路跟JVM的类加载器设计有点像，不同的类加载器也为了隔离，不过加载顺序是反的，子加载器总是先委托父加载器去加载某个类，加载不到再自己来加载。
-
-并且通过父子关系，使得SpringMVC容器可以从父亲Spring容器那里拿Bean，因为Spring容器管理的是公共的Bean。
-当然可以用同一个容器来管理，SpringBoot就是这样做的。
-
-Spring和SpringMVC是通过配置文件来明确指定各自管理的Bean。
-
-**Servlet容器跟Spring容器又有什么关系呢？**
-有人说spring容器是servlet容器的子容器，但是这个servlet容器到底是tomcat实现的容器呢，还是jetty实现的容器呢？所以spring容器与servlet容器他们之间并没有直接的血缘关系，可以说spring容器依赖了servlet容器，spring容器的实现遵循了Servlet 规范。
-
-spring容器只是servlet容器上下文(ServletContext)的一个属性，web容器启动时通过ServletContextListener机制构建出来。SpringBoot中只有一个Spring上下文：
-
-```bash
-org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
-```
-
-![](https://img-blog.csdnimg.cn/20210716201457871.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)servlet容器初始化成功后被spring监听，创建spring容器放入servlet容器中，访问到达，初始化dispatcher servlet时创建springmvc容器，通过servletContext拿到spring容器，并将其作为自己的父容器，spring mvc容器会定义controller相关的bean,spring会定义业务逻辑相关的bean。
-
-
-> 参考
-> - https://blog.csdn.net/zhanglf02/article/details/89791797
-> - https://docs.oracle.com/cd/E19146-01/819-2634/abxbh/index.html
-> - https://matthung0807.blogspot.com/2020/09/tomcat-how-servlet-construct.html
-> - https://github.com/heroku/devcenter-embedded-tomcat
-> - https://biang.io/blog/mixed/cgi
\ No newline at end of file
diff --git "a/Tomcat/Spring Boot\345\246\202\344\275\225\345\220\257\345\212\250\345\265\214\345\205\245\345\274\217Tomcat\357\274\237.md" "b/Tomcat/Spring Boot\345\246\202\344\275\225\345\220\257\345\212\250\345\265\214\345\205\245\345\274\217Tomcat\357\274\237.md"
deleted file mode 100644
index 2d5fd6dc79..0000000000
--- "a/Tomcat/Spring Boot\345\246\202\344\275\225\345\220\257\345\212\250\345\265\214\345\205\245\345\274\217Tomcat\357\274\237.md"	
+++ /dev/null
@@ -1,207 +0,0 @@
-Spring Boot在内部启动了一个嵌入式Web容器。
-Tomcat是组件化设计，所以就是启动这些组件。
-
-> Tomcat独立部署模式是通过startup脚本启动，Tomcat中的Bootstrap和Catalina会负责初始化类加载器，并解析server.xml和启动这些组件。
-
-内嵌模式，Bootstrap和Catalina的工作由Spring Boot代劳，Spring Boot调用Tomcat API启动这些组件。
-# Spring Boot中Web容器相关接口
-### WebServer
-为支持各种Web容器，Spring Boot抽象出嵌入式Web容过滤器器，定义WebServer接口：
-![](https://img-blog.csdnimg.cn/b42b8ef2454f4977a53d6a1a36399ebb.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-Web容器比如Tomcat、Jetty去实现该接口
-![](https://img-blog.csdnimg.cn/7de9b2055c9d48a1b998ae5ba9109d1e.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-### ServletWebServerFactory
-创建Web容器，返回的就是上面提到的WebServer。
-```java
-public interface ServletWebServerFactory {
-    WebServer getWebServer(ServletContextInitializer... initializers);
-}
-```
-ServletContextInitializer入参表示ServletContext的初始化器，用于ServletContext中的一些配置：
-```java
-public interface ServletContextInitializer {
-    void onStartup(ServletContext servletContext) throws ServletException;
-}
-```
-
-getWebServer会调用ServletContextInitializer#onStartup，即若想在Servlet容器启动时做一些事情，比如注册自己的Servlet，可以实现一个ServletContextInitializer，在Web容器启动时，Spring Boot会把所有实现ServletContextInitializer接口的类收集起来，统一调其onStartup。
-### WebServerFactoryCustomizerBeanPostProcessor
-一个BeanPostProcessor，为定制化嵌入式Web容器，在postProcessBeforeInitialization过程中去寻找Spring容器中WebServerFactoryCustomizer类型的Bean，并依次调用WebServerFactoryCustomizer接口的customize方法做一些定制化。
-```java
-public interface WebServerFactoryCustomizer<T extends WebServerFactory> {
-    void customize(T factory);
-}
-```
-# 创建、启动嵌入式Web容器
-Spring的ApplicationContext，其抽象实现类AbstractApplicationContext#refresh
-![](https://img-blog.csdnimg.cn/8305733360bd48069e8306f6d492e664.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-用来新建或刷新一个ApplicationContext，在refresh中会调用onRefresh，AbstractApplicationContext的子类可以重写onRefresh实现Context刷新逻辑。
-
-因此重写 **ServletWebServerApplicationContext#onRefresh** 创建嵌入式Web容器：
-![](https://img-blog.csdnimg.cn/54541ff6f69047f5ba05da0f6811e0e4.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-重写onRefresh方法，调用createWebServer创建和启动Tomcat。
-### createWebServer
-```java
-private void createWebServer() {
-    // WebServer是Spring Boot抽象出来的接口，具体实现类就是不同Web容器
-    WebServer webServer = this.webServer;
-    ServletContext servletContext = this.getServletContext();
-    
-    // 若Web容器尚未创建
-    if (webServer == null && servletContext == null) {
-        // 通过Web容器工厂创建
-        ServletWebServerFactory factory = this.getWebServerFactory();
-        // 传入一个"SelfInitializer"
-        this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
-        
-    } else if (servletContext != null) {
-        try {
-            this.getSelfInitializer().onStartup(servletContext);
-        } catch (ServletException var4) {
-          ...
-        }
-    }
-
-    this.initPropertySources();
-}
-```
-
-### getWebServer
-以Tomcat为例，主要调用Tomcat的API去创建各种组件：
-```java
-public WebServer getWebServer(ServletContextInitializer... initializers) {
-    // 1.实例化一个Tomcat【Server组件】
-    Tomcat tomcat = new Tomcat();
-    
-    // 2. 创建一个临时目录
-    File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
-    tomcat.setBaseDir(baseDir.getAbsolutePath());
-    
-    // 3.初始化各种组件
-    Connector connector = new Connector(this.protocol);
-    tomcat.getService().addConnector(connector);
-    this.customizeConnector(connector);
-    tomcat.setConnector(connector);
-    tomcat.getHost().setAutoDeploy(false);
-    this.configureEngine(tomcat.getEngine());
-    
-    // 4. 创建定制版的"Context"组件
-    this.prepareContext(tomcat.getHost(), initializers);
-    return this.getTomcatWebServer(tomcat);
-}
-```
-prepareContext的Context指Tomcat的Context组件，为控制Context组件行为，Spring Boot自定义了TomcatEmbeddedContext类，继承Tomcat的StandardContext：
-![](https://img-blog.csdnimg.cn/29ea42ba2130426f8a4d3d96dbdbf03d.png)
-# 注册Servlet
-- 有@RestController，为什么还要自己去注册Servlet给Tomcat? 
-可能有些场景需要注册你自己写的一个Servlet提供辅助功能，与主程序分开。
-
-- Sprong Boot 不注册Servlet 给Tomcat 直接用 **@Controller** 就能实现Servlet功能是为啥呢？
-因为Sprong Boot默认给我们注册了DispatcherSetvlet。
-
-## Servlet注解
-在Spring Boot启动类上加上 **@ServletComponentScan** 注解后，使用@WebServlet、@WebFilter、@WebListener标记的Servlet、Filter、Listener就可以自动注册到Servlet容器。
-![](https://img-blog.csdnimg.cn/f30ba0ce2d2242ab972a8fb937fcf0eb.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/9366a2ca7df24abdb7d68987ed91e8cd.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-在Web应用的入口类上加上@ServletComponentScan，并且在Servlet类上加上@WebServlet，这样Spring Boot会负责将Servlet注册到内嵌的Tomcat中。
-
-## ServletRegistrationBean
-Spring Boot提供了
-- ServletRegistrationBean
-- FilterRegistrationBean
-- ServletListenerRegistrationBean
-
-分别用来注册Servlet、Filter、Listener。
-假如要注册一个Servlet：
-![](https://img-blog.csdnimg.cn/61a11f64e3b74f47af071b9661b0e425.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-返回一个ServletRegistrationBean，并将它当作Bean注册到Spring，因此你需要把这段代码放到Spring Boot自动扫描的目录中，或者放到**@Configuration**标识的类中。
-Spring会把这种类型的Bean收集起来，根据Bean里的定义向Tomcat注册Servlet。
-
-## 动态注册
-可以创建一个类去实现ServletContextInitializer接口，并把它注册为一个Bean，Spring Boot会负责调用这个接口的onStartup。
-
-> 实现ServletContextInitializer接口的类会被spring管理，而不是被Servlet容器管理。
-
-```java
-@Component
-public class MyServletRegister implements ServletContextInitializer {
-
-    @Override
-    public void onStartup(ServletContext servletContext) {
-    
-        // Servlet 3.0规范新的API
-        ServletRegistration myServlet = servletContext
-                .addServlet("HelloServlet", HelloServlet.class);
-                
-        myServlet.addMapping("/hello");
-        
-        myServlet.setInitParameter("name", "Hello Servlet");
-    }
-
-}
-```
-ServletRegistrationBean也是通过ServletContextInitializer实现的，它实现了ServletContextInitializer接口。
-注意到onStartup方法的参数是我们熟悉的ServletContext，可以通过调用它的addServlet方法来动态注册新的Servlet，这是Servlet 3.0以后才有的功能。
-
-通过 ServletContextInitializer 接口可以向 Web 容器注册 Servlet，实现 ServletContextInitializer 接口的Bean被speing管理，但是在什么时机触发其onStartup()方法的呢？
-通过 Tomcat 中的 ServletContainerInitializer 接口实现者，如TomcatStarter，创建tomcat时设置了该类，在tomcat启动时会触发ServletContainerInitializer实现者的onStartup()方法，在这个方法中触发ServletContextInitializer接口的onStartup()方法，如注册DispatcherServlet。
-
-DispatcherServletRegistrationBean实现了ServletContextInitializer接口，它的作用就是向Tomcat注册DispatcherServlet，那它是在什么时候、如何被使用的呢？
-prepareContext方法调用了另一个私有方法configureContext，这个方法就包括了往Tomcat的Context添加ServletContainerInitializer对象：
-```java
-context.addServletContainerInitializer(starter, NO_CLASSES);
-```
-其中有DispatcherServletRegistrationBean。
-
-## 定制Web容器
-如何在Spring Boot中定制Web容器。在Spring Boot 2.0中可通过如下方式：
-### ConfigurableServletWebServerFactory
-通用的Web容器工厂，定制Web容器通用参数：
-```java
-@Component
-public class MyGeneralCustomizer implements
-  WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
-  
-    public void customize(ConfigurableServletWebServerFactory factory) {
-        factory.setPort(8081);
-        factory.setContextPath("/hello");
-     }
-}
-```
-### TomcatServletWebServerFactory
-通过特定Web容器工厂进一步定制。
-
-给Tomcat增加一个Valve，这个Valve的功能是向请求头里添加traceid，用于分布式追踪。
-```java
-class TraceValve extends ValveBase {
-    @Override
-    public void invoke(Request request, Response response) throws IOException, ServletException {
-
-        request.getCoyoteRequest().getMimeHeaders().
-        addValue("traceid").setString("1234xxxxabcd");
-
-        Valve next = getNext();
-        if (null == next) {
-            return;
-        }
-
-        next.invoke(request, response);
-    }
-
-}
-```
-跟方式一类似，再添加一个定制器：
-```java
-@Component
-public class MyTomcatCustomizer implements
-        WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
-
-    @Override
-    public void customize(TomcatServletWebServerFactory factory) {
-        factory.setPort(8081);
-        factory.setContextPath("/hello");
-        factory.addEngineValves(new TraceValve() );
-
-    }
-}
-```
\ No newline at end of file
diff --git "a/Tomcat/Tomcat\343\200\201Nginx\345\222\214Apache\347\232\204\345\214\272\345\210\253.md" "b/Tomcat/Tomcat\343\200\201Nginx\345\222\214Apache\347\232\204\345\214\272\345\210\253.md"
deleted file mode 100644
index 077d829508..0000000000
--- "a/Tomcat/Tomcat\343\200\201Nginx\345\222\214Apache\347\232\204\345\214\272\345\210\253.md"
+++ /dev/null
@@ -1,35 +0,0 @@
-- 这三者都是web server，那他们各自有什么特点呢？
-- 他们之间的区别是什么呢？
-- nginx 和 tomcat在性能上面有何异同?
-- tomcat用在java后台程序上，java后台程序难道不能用apache和nginx吗？
-
-Apache HTTP Server Project、Nginx都是开源的HTTP服务器软件。
-
-HTTP服务器本质上也是一种应用程序——它通常运行在服务器之上，绑定服务器的IP地址并监听某一个TCP端口来接收并处理HTTP请求，这样客户端（如Firefox，Chrome这样的浏览器）就能通过HTTP协议获取服务器上的网页（HTML格式）、文档（PDF格式）、音频（MP4格式）、视频（MOV格式）等资源。
-
-下图描述的就是这一过程：
-![](https://img-blog.csdnimg.cn/20210628155422614.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-不仅仅是Apache HTTP Server和Nginx，编程语言比如 Java的类库中也实现了简单的HTTP服务器方便开发者使用：
-- HttpServer (Java HTTP Server )
-
-使用这些类库能够非常容易的运行一个HTTP服务器，它们都能够通过绑定IP地址并监听tcp端口来提供HTTP服务。
-
-Apache Tomcat与Apache HTTP Server相比，Tomcat能够动态生成资源并返回到客户端。Apache HTTP Server和Nginx都能够将某一文本文件内容通过HTTP协议返回到客户端，但该文本文件的内容固定——即无论何时、任何人访问它得到的内容都完全相同，这就是`静态资源`。
-
-动态资源则在不同时间、客户端访问得到的内容不同，例如：
-- 包含显示当前时间的页面
-- 显示当前IP地址的页面
-
-Apache HTTP Server和Nginx本身不支持生成动态页面，但它们可以通过其他模块来支持（例如通过Shell、PHP、Python脚本程序来动态生成内容）。若想要使用Java程序动态生成资源内容，使用这一类HTTP服务器很难做到。Java Servlet以及衍生的JSP可以让Java程序也具有处理HTTP请求并且返回内容（由程序动态控制）的能力，Tomcat正是支持运行Servlet/JSP应用程序的容器（Container）：
-![](https://img-blog.csdnimg.cn/2021062816011444.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-Tomcat运行在JVM之上，和HTTP服务器一样，绑定IP地址并监听TCP端口，同时还包含如下职责：
-- 管理Servlet程序的生命周期
-- 将URL映射到指定Servlet进行处理
-- 与Servlet程序合作处理HTTP请求
-根据HTTP请求生成HttpServletRequest对象并传递给Servlet进行处理，将Servlet中的HttpServletResponse对象生成的内容返回给浏览器
-
-虽然Tomcat也可以认为是HTTP服务器，但通常它仍然会和Nginx配合在一起使用：
-- 动静态资源分离
-运用Nginx的反向代理功能分发请求：所有动态资源的请求交给Tomcat，而静态资源的请求（例如图片、视频、CSS、JavaScript文件等）则直接由Nginx返回到浏览器，大大减轻Tomcat压力
-- 负载均衡
-当业务压力增大时，可能一个Tomcat的实例不足以处理，那么这时可以启动多个Tomcat实例进行水平扩展，而Nginx的负载均衡功能可以把请求通过算法分发到各个不同的实例进行处理
\ No newline at end of file
diff --git "a/Tomcat/Tomcat\345\244\232\345\261\202\345\256\271\345\231\250\347\232\204\350\256\276\350\256\241.md" "b/Tomcat/Tomcat\345\244\232\345\261\202\345\256\271\345\231\250\347\232\204\350\256\276\350\256\241.md"
deleted file mode 100644
index e75660de59..0000000000
--- "a/Tomcat/Tomcat\345\244\232\345\261\202\345\256\271\345\231\250\347\232\204\350\256\276\350\256\241.md"
+++ /dev/null
@@ -1,114 +0,0 @@
-Tomcat的容器用来装载Servlet。那Tomcat的Servlet容器是如何设计的呢？
-
-# 容器的层次结构
-Tomcat设计了4种容器：Engine、Host、Context和Wrapper
-![](https://img-blog.csdnimg.cn/2021071923583247.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-Tomcat通过这种分层，使得Servlet容器具有很好的灵活性。
-- Context表示一个Web应用程序
-- Wrapper表示一个Servlet，一个Web应用程序中可能会有多个Servlet
-- Host代表一个虚拟主机，或一个站点，可以给Tomcat配置多个虚拟主机地址，而一个虚拟主机下可以部署多个Web应用程序
-- Engine表示引擎，用来管理多个虚拟站点，一个Service最多只能有一个Engine
-
-观察Tomcat的server.xml配置文件。Tomcat采用了组件化设计，最外层即是Server
-![](https://img-blog.csdnimg.cn/20210719235935386.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-这些容器具有父子关系，形成一个树形结构，Tomcat用组合模式来管理这些容器。
-
-所有容器组件都实现Container接口，因此组合模式可以使得用户对
-- 单容器对象
-最底层的Wrapper
-- 组合容器对象
-上面的Context、Host或者Engine
-的使用具有一致性。
-
-Container接口定义：
-
-```java
-public interface Container extends Lifecycle {
-    public void setName(String name);
-    public Container getParent();
-    public void setParent(Container container);
-    public void addChild(Container child);
-    public void removeChild(Container child);
-    public Container findChild(String name);
-}
-```
-
-# 请求定位Servlet的过程
-搞这么多层次的容器，Tomcat是怎么确定请求是由哪个Wrapper容器里的Servlet来处理的呢？
-Tomcat用Mapper组件完成这个任务。
-
-Mapper就是将用户请求的URL定位到一个Servlet
-## 工作原理
-Mapper组件保存了Web应用的配置信息：容器组件与访问路径的映射关系，比如
-- Host容器里配置的域名
-- Context容器里的Web应用路径
-- Wrapper容器里Servlet映射的路径
-
-这些配置信息就是一个多层次的Map。
-
-当一个请求到来时，Mapper组件通过解析请求URL里的域名和路径，再到自己保存的Map里去查找，就能定位到一个Servlet。
-一个请求URL最后只会定位到一个Wrapper容器，即一个Servlet。
-
-假如有一网购系统，有
-- 面向B端管理人员的后台管理系统
-- 面向C端用户的在线购物系统
-
-这俩系统跑在同一Tomcat，为隔离它们的访问域名，配置两个虚拟域名：
-- manage.shopping.com
-管理人员通过该域名访问Tomcat去管理用户和商品，而用户管理和商品管理是两个单独的Web应用
-- user.shopping.com
-C端用户通过该域名去搜索商品和下订单，搜索功能和订单管理也是两个独立Web应用
-
-这样部署，Tomcat会创建一个Service组件和一个Engine容器组件，在Engine容器下创建两个Host子容器，在每个Host容器下创建两个Context子容器。由于一个Web应用通常有多个Servlet，Tomcat还会在每个Context容器里创建多个Wrapper子容器。每个容器都有对应访问路径
-![](https://img-blog.csdnimg.cn/20210720111550336.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-Tomcat如何将URL定位到一个Servlet呢？
-- 首先，根据协议和端口号选定Service和Engine
-Tomcat的每个连接器都监听不同的端口，比如Tomcat默认的HTTP连接器监听8080端口、默认的AJP连接器监听8009端口。该URL访问8080端口，因此会被HTTP连接器接收，而一个连接器是属于一个Service组件的，这样Service组件就确定了。一个Service组件里除了有多个连接器，还有一个Engine容器，因此Service确定了，Engine也确定了。
-- 根据域名选定Host。
-Mapper组件通过URL中的域名去查找相应的Host容器，比如user.shopping.com，因此Mapper找到Host2容器。
-- 根据URL路径找到Context组件
-Host确定以后，Mapper根据URL的路径来匹配相应的Web应用的路径，比如例子中访问的是/order，因此找到了Context4这个Context容器。
-- 最后，根据URL路径找到Wrapper（Servlet）
-Context确定后，Mapper再根据web.xml中配置的Servlet映射路径来找到具体Wrapper和Servlet。
-
-并非只有Servlet才会去处理请求，查找路径上的父子容器都会对请求做一些处理：
-- 连接器中的Adapter会调用容器的Service方法执行Servlet
-- 最先拿到请求的是Engine容器，Engine容器对请求做一些处理后，会把请求传给自己子容器Host继续处理，依次类推
-- 最后这个请求会传给Wrapper容器，Wrapper会调用最终的Servlet来处理
-
-这个调用过程使用的Pipeline-Valve管道，责任链模式，在一个请求处理的过程中有很多处理者依次对请求进行处理，每个处理者负责做自己相应的处理，处理完之后将再调用下一个处理者继续处理。
-
-Valve表示一个处理点，比如权限认证和记录日志。
-```java
-public interface Valve {
-  public Valve getNext();
-  public void setNext(Valve valve);
-  public void invoke(Request request, Response response)
-}
-```
-由于Valve是一个处理点，因此invoke方法就是来处理请求的。
-Pipeline接口：
-```java
-public interface Pipeline extends Contained {
-  public void addValve(Valve valve);
-  public Valve getBasic();
-  public void setBasic(Valve valve);
-  public Valve getFirst();
-}
-```
-所以Pipeline中维护了Valve链表，Valve可插入到Pipeline。
-Pipeline中没有invoke方法，因为整个调用链的触发是Valve完成自己的处理后，调用getNext.invoke调用下一个Valve。
-
-每个容器都有一个Pipeline对象，只要触发这个Pipeline的第一个Valve，这个容器里Pipeline中的Valve就都会被调用到。但不同容器的Pipeline如何链式触发？
-比如Engine中Pipeline需要调用下层容器Host中的Pipeline。
-Pipeline有个getBasic方法。这个BasicValve处于Valve链尾，负责调用下层容器的Pipeline里的第一个Valve。
-![](https://img-blog.csdnimg.cn/20210720135942222.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-整个调用过程由连接器中的Adapter触发的，它会调用Engine的第一个Valve：
-![](https://img-blog.csdnimg.cn/20210720141234571.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)Wrapper容器的最后一个Valve会创建一个Filter链，并调用doFilter方法，最终会调到Servlet的service方法。
-
-Valve和Filter有什么区别呢？
-- Valve是Tomcat的私有机制，与Tomcat紧耦合。Servlet API是公有标准，所有Web容器包括Jetty都支持Filter
-- Valve工作在Web容器级别，拦截所有应用的请求。Servlet Filter工作在应用级别，只拦截某个Web应用的所有请求。若想做整个Web容器的拦截器，必须使用Valve。
\ No newline at end of file
diff --git "a/Tomcat/Tomcat\345\246\202\344\275\225\346\211\223\347\240\264\345\217\214\344\272\262\345\247\224\346\264\276\346\234\272\345\210\266\345\256\236\347\216\260\351\232\224\347\246\273Web\345\272\224\347\224\250\347\232\204\357\274\237.md" "b/Tomcat/Tomcat\345\246\202\344\275\225\346\211\223\347\240\264\345\217\214\344\272\262\345\247\224\346\264\276\346\234\272\345\210\266\345\256\236\347\216\260\351\232\224\347\246\273Web\345\272\224\347\224\250\347\232\204\357\274\237.md"
deleted file mode 100644
index ccb7deb634..0000000000
--- "a/Tomcat/Tomcat\345\246\202\344\275\225\346\211\223\347\240\264\345\217\214\344\272\262\345\247\224\346\264\276\346\234\272\345\210\266\345\256\236\347\216\260\351\232\224\347\246\273Web\345\272\224\347\224\250\347\232\204\357\274\237.md"
+++ /dev/null
@@ -1,100 +0,0 @@
-Tomcat通过自定义类加载器WebAppClassLoader打破双亲委派，即重写了JVM的类加载器ClassLoader的findClass方法和loadClass方法，以优先加载Web应用目录下的类。
-
-Tomcat负责加载我们的Servlet类、加载Servlet所依赖的JAR包。Tomcat本身也是个Java程序，因此它需要加载自己的类和依赖的JAR包。
-
-若在Tomcat运行两个Web应用程序，它们有功能不同的同名Servlet，Tomcat需同时加载和管理这两个同名的Servlet类，保证它们不会冲突。所以Web应用之间的类需要隔离
-
-若两个Web应用都依赖同一三方jar，比如Spring，则Spring jar被加载到内存后，Tomcat要保证这两个Web应用能共享之，即Spring jar只被加载一次，否则随着三方jar增多，JVM的内存会占用过大。
-所以，和 JVM 一样，需要隔离Tomcat本身的类和Web应用的类。
-# Tomcat类加载器的层次结构
-- Tomcat的类加载器层次结构
-![](https://img-blog.csdnimg.cn/06ef794a266541b89089131a8c51d8f6.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-- 前三个是加载器实例名，不是类名。![](https://img-blog.csdnimg.cn/f59d56811c574170a2969f620d4be934.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-## WebAppClassLoader
-若使用JVM默认的AppClassLoader加载Web应用，AppClassLoader只能加载一个Servlet类，在加载第二个同名Servlet类时，AppClassLoader会返回第一个Servlet类的Class实例。
-因为在AppClassLoader眼里，同名Servlet类只能被加载一次。
-
-于是，Tomcat自定义了一个类加载器**WebAppClassLoader**， 并为每个Web应用创建一个**WebAppClassLoader**实例。
-
-每个Web应用自己的Java类和依赖的JAR包，分别放在`WEB-INF/classes`和`WEB-INF/lib`目录下，都是WebAppClassLoader加载的。
-
-Context容器组件对应一个Web应用，因此，每个Context容器创建和维护一个WebAppClassLoader加载器实例。
-不同加载器实例加载的类被认为是不同的类，即使类名相同。这就相当于在JVM内部创建相互隔离的Java类空间，每个Web应用都有自己的类空间，Web应用之间通过各自的类加载器互相隔离。
-
-## SharedClassLoader
-两个Web应用之间怎么共享库类，并且不能重复加载相同的类？
-
-双亲委派机制的各子加载器都能通过父加载器去加载类，于是考虑把需共享的类放到父加载器的加载路径。
-
-应用程序即是通过该方式共享JRE核心类。
-Tomcat搞了个类加载器SharedClassLoader，作为WebAppClassLoader的父加载器，以加载Web应用之间共享的类。
-
-若WebAppClassLoader未加载到某类，就委托父加载器SharedClassLoader去加载该类，SharedClassLoader会在指定目录下加载共享类，之后返回给WebAppClassLoader，即可解决共享问题。
-
-## CatalinaClassLoader
-如何隔离Tomcat本身的类和Web应用的类？
-
-兄弟关系：两个类加载器是平行的，它们可能拥有同一父加载器，但两个兄弟类加载器加载的类是隔离的。
-
-于是，Tomcat搞了CatalinaClassLoader，专门加载Tomcat自身的类。
-
-问题是，当Tomcat和各Web应用之间需要共享一些类时该怎么办？
-
-## CommonClassLoader
-共享依旧靠父子关系。
-再增加个CommonClassLoader，作为CatalinaClassLoader和SharedClassLoader的父加载器。
-
-CommonClassLoader能加载的类都可被CatalinaClassLoader、SharedClassLoader 使用，而CatalinaClassLoader和SharedClassLoader能加载的类则与对方相互隔离。WebAppClassLoader可以使用SharedClassLoader加载到的类，但各个WebAppClassLoader实例之间相互隔离。
-
-# Spring的加载问题
-JVM默认情况下，若一个类由类加载器A加载，则该类的依赖类也由相同的类加载器加载。
-比如Spring作为一个Bean工厂，它需要创建业务类的实例，并且在创建业务类实例之前需要加载这些类。Spring是通过调用Class.forName来加载业务类的，我们来看一下forName的源码：
-```java
-public static Class<?> forName(String className) {
-    Class<?> caller = Reflection.getCallerClass();
-    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
-}
-```
-会使用调用者，即Spring的加载器去加载业务类。
-
-Web应用之间共享的jar可交给SharedClassLoader加载，以避免重复加载。Spring作为共享的三方jar，本身由SharedClassLoader加载，Spring又要去加载业务类，按照前面那条规则，加载Spring的类加载器也会用来加载业务类，但是业务类在Web应用目录下，不在SharedClassLoader的加载路径下，这该怎么办呢？
-## 线程上下文加载器
-于是有了线程上下文加载器，一种类加载器传递机制。因为该类加载器保存在线程私有数据里，只要是同一个线程，一旦设置了线程上下文加载器，在线程后续执行过程中就能把这个类加载器取出来用。因此Tomcat为每个Web应用创建一个WebAppClassLoader类加载器，并在启动Web应用的线程里设置线程上下文加载器，这样Spring在启动时就将线程上下文加载器取出来，用来加载Bean。Spring取线程上下文加载的代码如下：
-```java
-cl = Thread.currentThread().getContextClassLoader();
-```
-
-在StandardContext的启动方法，会将当前线程的上下文加载器设置为WebAppClassLoader。
-![](https://img-blog.csdnimg.cn/fc59c5e9cfd6473183c67cf7bd91329a.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-启动方法结束时，会恢复线程的上下文加载器：
-```java
-Thread.currentThread().setContextClassLoader(originalClassLoader);
-```
-
-> 这是为什么呢？
-
-线程上下文加载器其实是线程的一个私有数据，跟线程绑定，这个线程完成启动Context组件后，会被回收到线程池，之后被用来做其他事情，为了不影响其他事情，需恢复之前的线程上下文加载器。
-优先加载web应用的类，当加载完了再改回原来的。
-
-线程上下文的加载器就是指定子类加载器来加载具体的某个桥接类，比如JDBC的Driver的加载。
-# 总结
-Tomcat的Context组件为每个Web应用创建一个WebAppClassLoader类加载器，由于不同类加载器实例加载的类是互相隔离的，因此达到了隔离Web应用的目的，同时通过CommonClassLoader等父加载器来共享第三方JAR包。而共享的第三方JAR包怎么加载特定Web应用的类呢？可以通过设置线程上下文加载器来解决。
-
-多个应用共享的Java类文件和JAR包，分别放在Web容器指定的共享目录：
-- CommonClassLoader
-对应 `<Tomcat>/common/*`
-- CatalinaClassLoader
-对应 `<Tomcat >/server/*`
-- SharedClassLoader
-对应 `<Tomcat >/shared/*`
-- WebAppClassloader
-对应 `<Tomcat >/webapps/<app>/WEB-INF/*`
-
-可以在Tomcat conf目录下的Catalina.properties文件里配置各种类加载器的加载路径。
-
-
-当出现ClassNotFound错误时，应该检查你的类加载器是否正确。
-线程上下文加载器不仅仅可以用在Tomcat和Spring类加载的场景里，核心框架类需要加载具体实现类时都可以用到它，比如我们熟悉的JDBC就是通过上下文类加载器来加载不同的数据库驱动的。
\ No newline at end of file
diff --git "a/Tomcat/Tomcat\345\257\271Servlet\350\247\204\350\214\203\347\232\204Filter\345\217\212Listener\345\256\236\347\216\260.md" "b/Tomcat/Tomcat\345\257\271Servlet\350\247\204\350\214\203\347\232\204Filter\345\217\212Listener\345\256\236\347\216\260.md"
deleted file mode 100644
index e44d50f004..0000000000
--- "a/Tomcat/Tomcat\345\257\271Servlet\350\247\204\350\214\203\347\232\204Filter\345\217\212Listener\345\256\236\347\216\260.md"
+++ /dev/null
@@ -1,171 +0,0 @@
-加载Servlet的类不等于创建Servlet实例，Tomcat先加载Servlet的类，然后还得在Java堆创建Servlet实例。
-
-一个Web应用里往往有多个Servlet，而在Tomcat中一个Web应用对应一个Context容器，即一个Context容器需管理多个Servlet实例。
-但Context容器并不直接持有Servlet实例，而是通过子容器Wrapper管理Servlet，可以把Wrapper容器看作Servlet的包装。
-
-> 为何需要Wrapper？Context容器直接维护一个Servlet数组还不满足？
-
-Servlet不仅是个类实例，还有相关配置信息，比如URL映射、初始化参数，因此设计出了一个包装器，把Servlet本身和它相关的数据包起来。
-
-> 管理好Servlet就够了吗？
-
-Listener和Filter也是Servlet规范，Tomcat也要创建它们的实例，在合适时机调用它们的方法。
-# Servlet管理
-Tomcat用Wrapper容器管理Servlet
-```java
-protected volatile Servlet instance = null;
-```
-它拥有一个Servlet实例，Wrapper#loadServlet实例化Servlet：
-```java
-public synchronized Servlet loadServlet() throws ServletException {
-    Servlet servlet;
-  
-    // 1. 创建Servlet实例
-    servlet = (Servlet) instanceManager.newInstance(servletClass);    
-    
-    // 2.调用了Servlet#init【Servlet规范要求】
-    initServlet(servlet);
-    
-    return servlet;
-}
-```
-
-为加快系统启动速度，一般采取资源延迟加载，所以Tomcat默认情况下Tomcat在启动时不会加载你的Servlet，除非把Servlet loadOnStartup参数置true。
-
-虽然Tomcat在启动时不会创建Servlet实例，但会创建Wrapper容器。当有请求访问某Servlet，才会创建该Servlet实例。
-
-Servlet是被谁调用呢？
-Wrapper作为一个容器组件，有自己的Pipeline和BasicValve，其BasicValve为StandardWrapperValve。
-
-当请求到来，Context容器的BasicValve会调用Wrapper容器中Pipeline中的第一个Valve，然后调用到StandardWrapperValve：
-```java
-public final void invoke(Request request, Response response) {
-
-    // 1.实例化Servlet
-    servlet = wrapper.allocate();
-   
-    // 2.给当前请求创建一个Filter链
-    ApplicationFilterChain filterChain =
-        ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
-
-   // 3. 调用这个Filter链，Filter链中的最后一个Filter会调用Servlet
-   filterChain.doFilter(request.getRequest(), response.getResponse());
-
-}
-```
-
-StandardWrapperValve的invoke就三步：
-1. 创建Servlet实例
-2. 给当前请求创建一个Filter链
-3. 调用Filter链
-
-### 为何要给每个请求创建Filter链
-每个请求的请求路径不同，而Filter都有相应路径映射，因此不是所有Filter都需要处理当前请求，要根据请求路径选择特定的一些Filter。
-
-### 为何没调用Servlet#service
-Filter链的最后一个Filter会负责调用Servlet。
-
-# Filter管理
-跟Servlet一样，Filter也可在`web.xml`配置。
-但Filter的作用域是整个Web应用，因此Filter的实例维护在Context容器：Map里存的是filterDef（filter定义），而非filter类实例
-![](https://img-blog.csdnimg.cn/a3042975203c43a1bb10f19d23110602.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-Filter链存活期很短，它跟每个请求对应。一个新请求来了，就动态创建一个Filter链，请求处理完，Filter链就被回收。
-```java
-public final class ApplicationFilterChain implements FilterChain {
-  
-  // Filter链的Filter数组
-  private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
-    
-  // Filter链的当前的调用位置
-  private int pos = 0;
-    
-  // Filter总数
-  private int n = 0;
-
-  // 每个Filter链最终要调用的Servlet
-  private Servlet servlet = null;
-  
-  public void doFilter(ServletRequest req, ServletResponse res) {
-        internalDoFilter(request,response);
-  }
-   
-  private void internalDoFilter(ServletRequest req,
-                                ServletResponse res){
-
-    if (pos < n) {
-        ApplicationFilterConfig filterConfig = filters[pos++];
-        Filter filter = filterConfig.getFilter();
-
-        filter.doFilter(request, response, this);
-        return;
-    }
-
-    servlet.service(request, response);
-   
-}
-```
-internalDoFilter里会做个判断：
-- 若当前Filter位置 ＜ Filter数组长度，即Filter还没调完，就从Filter数组取下一个Filter，调用其doFilter
-- 否则，说明已调用完所有Filter，该调用Servlet#service了。service方法是留给程序员实现业务逻辑的，比如CRUD
-```java
-public void doFilter(ServletRequest request, ServletResponse response,
-        FilterChain chain){
-        
-          ...
-          
-          //调用Filter的方法
-          chain.doFilter(request, response);
-      
-      }
-```
-Filter#doFilter的FilterChain参数，就是Filter链。每个Filter#doFilter里必须调用Filter链的doFilter，而Filter链中保存当前Filter位置，会调用下一个Filter的doFilter方法，这样就能完成链式调用。
-
-### 对应的filter是怎么注册到Servlet的呢？
-filter是注册到Servlet容器中，Tomcat的StandardContext类中维护了一个Filter列表，所谓注册就是把你写的filter类实例加到这个列表。
-
-# Listener管理
-Listener可以监听容器内部发生的事件：
-- 生命状态的变化
-比如Context容器启动和停止、Session的创建和销毁。
-- 属性变化
-比如Context容器某个属性值变了、Session的某个属性值变了以及新的请求来了
-
-## 怎么添加监听器
-在web.xml配置或注解添加，在监听器里实现业务逻辑。
-Tomcat需读取配置文件，拿到监听器的类名，将它们实例化，并适时调用这些监听器方法。
-
-Tomcat是通过Context容器来管理这些监听器的。Context容器将两类事件分开来管理，分别用不同的集合来存放不同类型事件的监听器：
-```java
-//监听属性值变化的监听器
-private List<Object> applicationEventListenersList = new CopyOnWriteArrayList<>();
-
-//监听生命事件的监听器
-private Object applicationLifecycleListenersObjects[] = new Object[0];
-剩下的事情就是触发监听器了，比如在Context容器的启动方法里，就触发了所有的ServletContextListener：
-
-// 1 拿到所有生命周期监听器
-Object instances[] = getApplicationLifecycleListeners();
-
-for (int i = 0; i < instances.length; i++) {
-   // 2 判断Listener的类型是否为ServletContextListener
-   if (!(instances[i] instanceof ServletContextListener))
-      continue;
-
-   // 3 触发Listener方法
-   ServletContextListener lr = (ServletContextListener) instances[i];
-   lr.contextInitialized(event);
-}
-```
-这里的ServletContextListener接口是留给用户的扩展机制，用户可以实现该接口定义自己的监听器，监听Context容器的启停事件。
-ServletContextListener跟Tomcat自己的生命周期事件LifecycleListener是不同的。LifecycleListener定义在生命周期管理组件中，由基类LifecycleBase统一管理。
-
-可定制监听器监听Tomcat内部发生的各种事件：比如Web应用、Session级别或请求级别的。Tomcat中的Context容器统一维护了这些监听器，并负责触发。
-
-Context组件通过自定义类加载器来加载Web应用，并实现了Servlet规范，直接跟Web应用打交道。
-# FAQ
-Context容器分别用了CopyOnWriteArrayList和对象数组来存储两种不同的监听器，为什么要这样设计呢？
-因为：
-- 属性值变化listener能动态配置，所以用CopyOnWriteArray
-写不会那么频繁，读取比较频繁
-- 生命周期事件listener，不能动态改变，无线程安全问题
-生命周期相关的类比如session一个用户分配一个，用完了就会销毁，用对象数组，可以适应增删改操作
\ No newline at end of file
diff --git "a/Tomcat/Tomcat\346\213\222\347\273\235\350\277\236\346\216\245\345\216\237\345\233\240\345\210\206\346\236\220\345\217\212\347\275\221\347\273\234\344\274\230\345\214\226.md" "b/Tomcat/Tomcat\346\213\222\347\273\235\350\277\236\346\216\245\345\216\237\345\233\240\345\210\206\346\236\220\345\217\212\347\275\221\347\273\234\344\274\230\345\214\226.md"
deleted file mode 100644
index ac3c2efd9f..0000000000
--- "a/Tomcat/Tomcat\346\213\222\347\273\235\350\277\236\346\216\245\345\216\237\345\233\240\345\210\206\346\236\220\345\217\212\347\275\221\347\273\234\344\274\230\345\214\226.md"
+++ /dev/null
@@ -1,123 +0,0 @@
-Java Socket网络编程常见的异常有哪些，然后通过一个实验来重现其中的Connection reset异常，并且通过配置Tomcat的参数来解决这个问题。
-
-# 异常场景
-### java.net.SocketTimeoutException
-超时异常，超时分为
-- 连接超时
-在调用Socket.connect方法的时候超时，大多因为网络不稳定
-- 读取超时
-调用Socket.read方法时超时。不一定是因为网络延迟，很可能下游服务的响应时间过长
-
-### java.net.BindException: Address already in use: JVM_Bind
-端口被占用。
-当服务器端调用
-- new ServerSocket(port) 
-- 或Socket.bind函数
-
-若端口已被占用，就会抛该异常。
-
-可以用
-
-```bash
-netstat –an
-```
-![](https://img-blog.csdnimg.cn/982f7e6b965a44ceb3733716134b387e.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-查看端口被谁占用了，换个空闲端口即可。
-
-### java.net.ConnectException: Connection refused: connect
-连接被拒绝。
-当客户端调用
-- new Socket(ip, port) 
-- 或Socket.connect函数
-
-原因是：
-- 未找到指定IP的机器
-- 机器存在，但该机器上没有开启指定监听端口
-#### 解决方案
-从客户端机器ping一下服务端IP：
-- ping不通，看看IP是不是写错了？
-- ping通，需要确认服务端的服务是不是挂了？
-
-### java.net.SocketException: Socket is closed
-连接已关闭。
-
-通信的一方主动关闭了Socket连接（调用了Socket的close方法），接着又对Socket连接进行了读写操作，这时os会报“Socket连接已关闭”。
-### java.net.SocketException: Connection reset/Connect reset by peer: Socket write error
-连接被重置。
-- 通信的一方已将Socket关闭，可能是主动关闭或是因为异常退出，这时如果通信的另一方还在写数据，就会触发这个异常（Connect reset by peer）
-- 若对方还在尝试从TCP连接中读数据，则会抛出Connection reset异常。
-
-为了避免这些异常发生，在编写网络通信程序时要确保：
-- 程序退出前要主动关闭所有的网络连接
-- 检测通信的另一方的关闭连接操作，当发现另一方关闭连接后自己也要关闭该连接。
-
-### java.net.SocketException: Broken pipe
-通信管道已坏。
-
-发生这个异常的场景是，通信的一方在收到“Connect reset by peer: Socket write error”后，如果再继续写数据则会抛出Broken pipe异常，解决方法同上。
-
-### java.net.SocketException: Too many open files
-进程打开文件句柄数超过限制。
-
-当并发用户数比较大时，服务器可能会报这个异常。这是因为每创建一个Socket连接就需要一个文件句柄，此外服务端程序在处理请求时可能也需要打开一些文件。
-
-可以通过lsof -p pid命令查看进程打开了哪些文件，是不是有资源泄露，即进程打开的这些文件本应该被关闭，但由于程序的Bug而没有被关闭。
-
-如果没有资源泄露，可以通过设置增加最大文件句柄数。具体方法是通过ulimit -a来查看系统目前资源限制，通过ulimit -n 10240修改最大文件数。
-
-# Tomcat网络参数
-- maxConnections
-- acceptCount
-
-## TCP连接的建立过程
-客户端向服务端发送SYN包，服务端回复**SYN＋ACK**，同时将这个处于**SYN_RECV**状态的连接保存到半连接队列。
-
-客户端返回ACK包完成三次握手，服务端将ESTABLISHED状态的连接移入accept队列，等待应用程序（Tomcat）调用accept方法将连接取走。
-这里涉及两个队列：
-- 半连接队列：保存SYN_RECV状态的连接
-队列长度由`net.ipv4.tcp_max_syn_backlog`设置
-- accept队列：保存ESTABLISHED状态的连接
-队列长度为`min(net.core.somaxconn，backlog)`。其中backlog是我们创建ServerSocket时指定的参数，最终会传递给listen方法：
-```c
-int listen(int sockfd, int backlog);
-```
-若设置的backlog大于`net.core.somaxconn`，accept队列的长度将被设置为`net.core.somaxconn`，而这个backlog参数就是Tomcat中的acceptCount参数，默认值100，但请注意`net.core.somaxconn`默认值128。
-在高并发情况下当Tomcat来不及处理新连接时，这些连接都被堆积在accept队列，而acceptCount参数可以控制accept队列长度。超过该长度，内核会向客户端发送RST，这样客户端会触发“Connection reset”异常。
-
-**Tomcat#maxConnections** 指Tomcat在任意时刻接收和处理的最大连接数。
-当Tomcat接收的连接数达到maxConnections时，Acceptor线程不会再从accept队列取走连接，这时accept队列中的连接会越积越多。
-
-maxConnections的默认值与连接器类型有关：NIO的默认值是10000，APR默认是8192。
-
-所以Tomcat
-```bash
-最大并发连接数 = maxConnections + acceptCount
-```
-若acceptCount- 
-设置得过大，请求等待时间会比较长；如果acceptCount设置过小，高并发情况下，客户端会立即触发Connection reset异常。
-
-# Tomcat网络调优实战
-接下来我们通过一个直观的例子来加深对上面两个参数的理解。我们先重现流量高峰时accept队列堆积的情况，这样会导致客户端触发“Connection reset”异常，然后通过调整参数解决这个问题。主要步骤有：
-
-1. JMeter 创建一个测试计划、一个线程组、一个请求。
-测试计划：
-![](https://img-blog.csdnimg.cn/d3a1aff7976b460da92a6cdb74928677.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-线程组（线程数这里设置为1000，模拟大流量）：
-![](https://img-blog.csdnimg.cn/d8311ca4a98540d98b61b381315d0df2.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-请求（请求的路径是Tomcat自带的例子程序）：
-![](https://img-blog.csdnimg.cn/915ac9b81cfd4a7c91ed93f9a74f3d76.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-2.启动Tomcat。
-3.开启JMeter测试，在View Results Tree中会看到大量失败的请求，请求的响应里有“Connection reset”异常，也就是前面提到的，当accept队列溢出时，服务端的内核发送了RST给客户端，使得客户端抛出了这个异常。
-![](https://img-blog.csdnimg.cn/eba2bf60725848548186bc6378b0c363.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-4.修改内核参数，在/etc/sysctl.conf中增加一行net.core.somaxconn=2048，然后执行命令sysctl -p。
-5.修改Tomcat参数acceptCount为2048，重启Tomcat
-![](https://img-blog.csdnimg.cn/49394caf8a8941f4ae49b6c16fca7514.png)
-
-6.再次启动JMeter测试，这一次所有的请求会成功，也看不到异常了。我们可以通过下面的命令看到系统中ESTABLISHED的连接数增大了，这是因为我们加大了accept队列的长度。
-![](https://img-blog.csdnimg.cn/2aa712c36fbb4b2d8fb166763369f2ed.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-
-如果一时找不到问题代码，还可以通过网络抓包工具来分析数据包。
\ No newline at end of file
diff --git "a/Tomcat/Tomcat\346\236\266\346\236\204\347\256\200\344\273\213.md" "b/Tomcat/Tomcat\346\236\266\346\236\204\350\247\243\346\236\220\344\271\2131-\346\236\266\346\236\204\347\256\200\344\273\213.md"
similarity index 100%
rename from "Tomcat/Tomcat\346\236\266\346\236\204\347\256\200\344\273\213.md"
rename to "Tomcat/Tomcat\346\236\266\346\236\204\350\247\243\346\236\220\344\271\2131-\346\236\266\346\236\204\347\256\200\344\273\213.md"
diff --git "a/Tomcat/Tomcat\347\232\204Connector-BIO.md" "b/Tomcat/Tomcat\346\236\266\346\236\204\350\247\243\346\236\220\344\271\2132-connector-BIO.md"
similarity index 100%
rename from "Tomcat/Tomcat\347\232\204Connector-BIO.md"
rename to "Tomcat/Tomcat\346\236\266\346\236\204\350\247\243\346\236\220\344\271\2132-connector-BIO.md"
diff --git "a/Tomcat/Tomcat\347\232\204Connector-NIO.md" "b/Tomcat/Tomcat\346\236\266\346\236\204\350\247\243\346\236\220\344\271\2133-Connector-NIO.md"
similarity index 99%
rename from "Tomcat/Tomcat\347\232\204Connector-NIO.md"
rename to "Tomcat/Tomcat\346\236\266\346\236\204\350\247\243\346\236\220\344\271\2133-Connector-NIO.md"
index 60053206aa..ad8d6748d1 100644
--- "a/Tomcat/Tomcat\347\232\204Connector-NIO.md"
+++ "b/Tomcat/Tomcat\346\236\266\346\236\204\350\247\243\346\236\220\344\271\2133-Connector-NIO.md"
@@ -38,7 +38,7 @@ Worker线程拿到Poller传过来的socket后，将socket封装在SocketProcesso
 # NioSelectorPool
 NioEndpoint对象中维护了一个NioSelecPool对象，这个NioSelectorPool中又维护了一个BlockPoller线程，这个线程就是基于辅Selector进行NIO的逻辑。以执行servlet后，得到response，往socket中写数据为例，最终写的过程调用NioBlockingSelector的write方法。 
 
-```java
+```
 public int write(ByteBuffer buf, NioChannel socket, long writeTimeout,MutableInteger lastWrite) throws IOException {  
         SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());  
         if ( key == null ) throw new IOException("Key no longer registered");  
@@ -97,8 +97,8 @@ public int write(ByteBuffer buf, NioChannel socket, long writeTimeout,MutableInt
 ```
 也就是说当socket.write()返回0时，说明网络状态不稳定，这时将socket注册OP_WRITE事件到辅Selector，由BlockPoller线程不断轮询这个辅Selector，直到发现这个socket的写状态恢复了，通过那个倒数计数器，通知Worker线程继续写socket动作。
 
-BlockSelector线程逻辑：
-```java
+看一下BlockSelector线程的逻辑； 
+```
 public void run() {  
             while (run) {  
                 try {  
@@ -140,7 +140,7 @@ public void run() {
 
 使用这个辅Selector主要是减少线程间的切换，同时还可减轻主Selector的负担。以上描述了NIO connector工作的主要逻辑，可以看到在设计上还是比较精巧的。NIO connector还有一块就是Comet，有时间再说吧。需要注意的是，上面从Acceptor开始，有很多对象的封装，NioChannel及其KeyAttachment，PollerEvent和SocketProcessor对象，这些不是每次都重新生成一个新的，都是NioEndpoint分别维护了它们的对象池； 
 
-```java
+```
 ConcurrentLinkedQueue<SocketProcessor> processorCache = new ConcurrentLinkedQueue<SocketProcessor>()  
 ConcurrentLinkedQueue<KeyAttachment> keyCache = new ConcurrentLinkedQueue<KeyAttachment>()  
 ConcurrentLinkedQueue<PollerEvent> eventCache = new ConcurrentLinkedQueue<PollerEvent>()  
diff --git "a/Tomcat/Tomcat\347\232\204\345\220\204\347\272\247\345\256\271\345\231\250\344\273\254\347\232\204\350\201\214\350\264\243.md" "b/Tomcat/Tomcat\347\232\204\345\220\204\347\272\247\345\256\271\345\231\250\344\273\254\347\232\204\350\201\214\350\264\243.md"
deleted file mode 100644
index 958a15c53e..0000000000
--- "a/Tomcat/Tomcat\347\232\204\345\220\204\347\272\247\345\256\271\345\231\250\344\273\254\347\232\204\350\201\214\350\264\243.md"
+++ /dev/null
@@ -1,230 +0,0 @@
-> 通过startup.sh启动Tomcat后会发生什么呢？
-
-![](https://img-blog.csdnimg.cn/20210720163627669.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-1. Tomcat也是Java程序，因此startup.sh脚本会启动一个JVM运行Tomcat的启动类Bootstrap
-2. Bootstrap主要负责初始化Tomcat的类加载器，并创建Catalina
-3. Catalina是个启动类，解析server.xml、创建相应组件，并调用Server#start
-4. Server组件负责管理Service组件，会调用Service#start
-5. Service组件负责管理连接器和顶层容器Engine，因此会调用连接器和Engine的start()
-
-这些启动类或组件不处理具体的请求，它们主要是“管理”，管理下层组件的生命周期，并给下层组件分配任务，即路由请求到应负责的组件。
-# Catalina
-主要负责创建Server，并非直接new个Server实例就完事了，而是：
-- 解析server.xml，将里面配的各种组件创建出来
-- 接着调用Server组件的init、start方法，这样整个Tomcat就启动起来了
-
-Catalina还需要处理各种“异常”，比如当通过“Ctrl + C”关闭Tomcat时，
-
-> Tomcat会如何优雅停止并清理资源呢？
-
-因此Catalina在JVM中注册一个 **关闭钩子**。
-```java
-public void start() {
-    // 1. 如果持有的Server实例为空，就解析server.xml创建出来
-    if (getServer() == null) {
-        load();
-    }
-    // 2. 如果创建失败，报错退出
-    if (getServer() == null) {
-        log.fatal(sm.getString("catalina.noServer"));
-        return;
-    }
-
-    // 3.启动Server
-    try {
-        getServer().start();
-    } catch (LifecycleException e) {
-        return;
-    }
-
-    // 创建并注册关闭钩子
-    if (useShutdownHook) {
-        if (shutdownHook == null) {
-            shutdownHook = new CatalinaShutdownHook();
-        }
-        Runtime.getRuntime().addShutdownHook(shutdownHook);
-    }
-
-    // 监听停止请求
-    if (await) {
-        await();
-        stop();
-    }
-}
-```
-## 关闭钩子
-若需在JVM关闭时做一些清理，比如：
-- 将缓存数据刷盘
-- 清理一些临时文件
-
-就可以向JVM注册一个关闭钩子，其实就是个线程，JVM在停止之前会尝试执行该线程的run()。
-
-Tomcat的**关闭钩子** 就是CatalinaShutdownHook：
-![](https://img-blog.csdnimg.cn/eeb4c65673154a25876ec027c1f488df.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-Tomcat的“关闭钩子”实际上就执行了Server#stop，会释放和清理所有资源。
-# Server组件
-Server组件具体实现类StandardServer。
-
-Server继承了LifecycleBase，它的生命周期被统一管理
-![](https://img-blog.csdnimg.cn/20210720165154608.png)
-它的子组件是Service，因此它还需要管理Service的生命周期，即在启动时调用Service组件的启动方法，在停止时调用它们的停止方法。Server在内部维护了若干Service组件，它是以数组来保存的，那Server是如何添加一个Service到数组中的呢？
-```java
-@Override
-public void addService(Service service) {
-
-    service.setServer(this);
-
-    synchronized (servicesLock) {
-        // 长度+1的数组并没有一开始就分配一个很长的数组
-        // 而是在添加的过程中动态地扩展数组长度，当添加一个新的Service实例时
-        // 会创建一个新数组并把原来数组内容复制到新数组，节省内存
-        Service results[] = new Service[services.length + 1];
-        
-        // 复制老数据
-        System.arraycopy(services, 0, results, 0, services.length);
-        results[services.length] = service;
-        services = results;
-
-        // 启动Service组件
-        if (getState().isAvailable()) {
-            try {
-                service.start();
-            } catch (LifecycleException e) {
-                // Ignore
-            }
-        }
-
-        // 触发监听事件
-        support.firePropertyChange("service", null, service);
-    }
-
-}
-```
-
-Server组件还需要启动一个Socket来监听停止端口，所以才能通过shutdown命令关闭Tomcat。
-上面Catalina的启动方法最后一行代码就是调用Server#await。
-
-在await方法里会创建一个Socket监听8005端口，并在一个死循环里接收Socket上的连接请求，如果有新的连接到来就建立连接，然后从Socket中读取数据；如果读到的数据是停止命令“SHUTDOWN”，就退出循环，进入stop流程。
-
-# Service组件
-Service组件的具体实现类StandardService
-```java
-public class StandardService extends LifecycleBase implements Service {
-    //名字
-    private String name = null;
-    
-    //Server实例
-    private Server server = null;
-
-    //连接器数组
-    protected Connector connectors[] = new Connector[0];
-    private final Object connectorsLock = new Object();
-
-    //对应的Engine容器
-    private Engine engine = null;
-    
-    //映射器及其监听器
-    protected final Mapper mapper = new Mapper();
-    protected final MapperListener mapperListener = new MapperListener(this);
-```
-
-StandardService继承了LifecycleBase抽象类，此外StandardService中还有一些我们熟悉的组件，比如Server、Connector、Engine和Mapper。
-
-Tomcat支持热部署，当Web应用的部署发生变化，Mapper中的映射信息也要跟着变化，MapperListener就是监听器，监听容器的变化，并把信息更新到Mapper。
-
-## Service启动方法
-```java
-protected void startInternal() throws LifecycleException {
-
-    // 1. 触发启动监听器
-    setState(LifecycleState.STARTING);
-
-    // 2. 先启动Engine，Engine会启动它子容器
-    if (engine != null) {
-        synchronized (engine) {
-            engine.start();
-        }
-    }
-    
-    // 3. 再启动Mapper监听器
-    mapperListener.start();
-
-    // 4.最后启动连接器，连接器会启动它子组件，比如Endpoint
-    synchronized (connectorsLock) {
-        for (Connector connector: connectors) {
-            if (connector.getState() != LifecycleState.FAILED) {
-                connector.start();
-            }
-        }
-    }
-}
-```
-Service先后启动Engine、Mapper监听器、连接器。
-内层组件启动好了才能对外提供服务，才能启动外层的连接器组件。而Mapper也依赖容器组件，容器组件启动好了才能监听它们的变化，因此Mapper和MapperListener在容器组件之后启动。
-# Engine组件
-最后我们再来看看顶层的容器组件Engine具体是如何实现的。Engine本质是一个容器，因此它继承了ContainerBase基类，并且实现了Engine接口。
-
-```java
-public class StandardEngine extends ContainerBase implements Engine {
-}
-```
-Engine的子容器是Host，所以它持有了一个Host容器的数组，这些功能都被抽象到了ContainerBase，ContainerBase中有这样一个数据结构：
-```java
-protected final HashMap<String, Container> children = new HashMap<>();
-```
-ContainerBase用HashMap保存了它的子容器，并且ContainerBase还实现了子容器的“增删改查”，甚至连子组件的启动和停止都提供了默认实现，比如ContainerBase会用专门的线程池来启动子容器。
-```java
-for (int i = 0; i < children.length; i++) {
-   results.add(startStopExecutor.submit(new StartChild(children[i])));
-}
-```
-所以Engine在启动Host子容器时就直接重用了这个方法。
-## Engine自己做了什么？
-容器组件最重要的功能是处理请求，而Engine容器对请求的“处理”，其实就是把请求转发给某一个Host子容器来处理，具体是通过Valve来实现的。
-
-每个容器组件都有一个Pipeline，而Pipeline中有一个基础阀（Basic Valve）。
-Engine容器的基础阀定义如下：
-
-```java
-final class StandardEngineValve extends ValveBase {
-
-    public final void invoke(Request request, Response response)
-      throws IOException, ServletException {
-  
-      // 拿到请求中的Host容器
-      Host host = request.getHost();
-      if (host == null) {
-          return;
-      }
-  
-      // 调用Host容器中的Pipeline中的第一个Valve
-      host.getPipeline().getFirst().invoke(request, response);
-  }
-  
-}
-```
-把请求转发到Host容器。
-处理请求的Host容器对象是从请求中拿到的，请求对象中怎么会有Host容器？
-因为请求到达Engine容器前，Mapper组件已对请求进行路由处理，Mapper组件通过请求URL定位了相应的容器，并且把容器对象保存到请求对象。
-
-所以当我们在设计这样的组件时，需考虑：
-- 用合适的数据结构来保存子组件，比如
-Server用数组来保存Service组件，并且采取动态扩容的方式，这是因为数组结构简单，占用内存小
-ContainerBase用HashMap来保存子容器，虽然Map占用内存会多一点，但是可以通过Map来快速的查找子容器
-- 根据子组件依赖关系来决定它们的启动和停止顺序，以及如何优雅的停止，防止异常情况下的资源泄漏。
-
-# 总结
-- Server 组件, 实现类 StandServer
-- 继承了 LifeCycleBase
-- 子组件是 Service, 需要管理其生命周期(调用其 LifeCycle 的方法), 用数组保存多个 Service 组件, 动态扩容数组来添加组件
-- 启动一个 socket Listen停止端口, Catalina 启动时, 调用 Server await 方法, 其创建 socket Listen 8005 端口, 并在死循环中等连接, 检查到 shutdown 命令, 调用 stop 方法
-- Service 组件, 实现类 StandService
-- 包含 Server, Connector, Engine 和 Mapper 组件的成员变量
-- 还包含 MapperListener 成员变量, 以支持热部署, 其Listen容器变化, 并更新 Mapper, 是观察者模式
-- 需注意各组件启动顺序, 根据其依赖关系确定
-- 先启动 Engine, 再启动 Mapper Listener, 最后启动连接器, 而停止顺序相反.
-- Engine 组件, 实现类 StandEngine 继承 ContainerBase
-- ContainerBase 实现了维护子组件的逻辑, 用 HaspMap 保存子组件, 因此各层容器可重用逻辑
-- ContainerBase 用专门线程池启动子容器, 并负责子组件启动/停止, "增删改查"
-- 请求到达 Engine 之前, Mapper 通过 URL 定位了容器, 并存入 Request 中. Engine 从 Request 取出 Host 子容器, 并调用其 pipeline 的第一个 valve
\ No newline at end of file
diff --git "a/Tomcat/Tomcat\347\232\204\347\272\277\347\250\213\346\250\241\345\236\213\350\256\276\350\256\241.md" "b/Tomcat/Tomcat\347\232\204\347\272\277\347\250\213\346\250\241\345\236\213\350\256\276\350\256\241.md"
deleted file mode 100644
index c00bf8161d..0000000000
--- "a/Tomcat/Tomcat\347\232\204\347\272\277\347\250\213\346\250\241\345\236\213\350\256\276\350\256\241.md"
+++ /dev/null
@@ -1,211 +0,0 @@
-# UNIX系统的I/O模型
-同步阻塞I/O、同步非阻塞I/O、I/O多路复用、信号驱动I/O和异步I/O。
-
-## 什么是 I/O
-就是计算机内存与外部设备之间拷贝数据的过程。
-
-## 为什么需要 I/O
-CPU访问内存的速度远远高于外部设备，因此CPU是先把外部设备的数据读到内存里，然后再进行处理。
-当你的程序通过CPU向外部设备发出一个读指令，数据从外部设备拷贝到内存需要一段时间，这时CPU没事干，你的程序是：
-- 主动把CPU让给别人
-- 还是让CPU不停查：数据到了吗？数据到了吗？...
-
-这就是I/O模型要解决的问题。
-# Java I/O模型
-对于一个网络I/O通信过程，比如网络数据读取，会涉及两个对象:
-- 调用这个I/O操作的用户线程
-- 操作系统内核
-
-一个进程的地址空间分为用户空间和内核空间，用户线程不能直接访问内核空间。
-当用户线程发起I/O操作后（Selector发出的select调用就是一个I/O操作），网络数据读取操作会经历两个步骤：
-1. 用户线程等待内核将数据从网卡拷贝到内核空间
-2. 内核将数据从内核空间拷贝到用户空间
-
-有人会好奇，内核数据从内核空间拷贝到用户空间，这样会不会有点浪费？
-毕竟实际上只有一块内存，能否直接把内存地址指向用户空间可以读取？
-Linux中有个叫mmap的系统调用，可以将磁盘文件映射到内存，省去了内核和用户空间的拷贝，但不支持网络通信场景！
-
-
-各种I/O模型的区别就是这两个步骤的方式不一样。
-
-## 同步阻塞I/O
-用户线程发起read调用后就阻塞了，让出CPU。内核等待网卡数据到来，把数据从网卡拷贝到内核空间，接着把数据拷贝到用户空间，再把用户线程叫醒。
-
-![](https://img-blog.csdnimg.cn/82492e5325a8474490b27099e2516073.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## 同步非阻塞I/O
-用户进程主动发起read调用，这是个系统调用，CPU由用户态切换到内核态，执行内核代码。
-内核发现该socket上的数据已到内核空间，将用户线程挂起，然后把数据从内核空间拷贝到用户空间，再唤醒用户线程，read调用返回。
-
-用户线程不断发起read调用，数据没到内核空间时，每次都返回失败，直到数据到了内核空间，这次read调用后，在等待数据从内核空间拷贝到用户空间这段时间里，线程还是阻塞的，等数据到了用户空间再把线程叫醒。
-![](https://img-blog.csdnimg.cn/99ce6ef4b03d4b629d5c69cb04650765.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## I/O多路复用
-用户线程的读取操作分成两步：
-- 线程先发起select调用，问内核：数据准备好了吗？
-- 等内核把数据准备好了，用户线程再发起read调用
-在等待数据从内核空间拷贝到用户空间这段时间里，线程还是阻塞的
-
-为什么叫I/O多路复用？
-因为一次select调用可以向内核查多个数据通道（Channel）的状态。
-![](https://img-blog.csdnimg.cn/1fdf320b193142c0be25d8ed73988961.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
->NIO API可以不用Selector，就是同步非阻塞。使用了Selector就是IO多路复用。
-
-## 异步I/O
-用户线程发起read调用的同时注册一个回调函数，read立即返回，等内核将数据准备好后，再调用指定的回调函数完成处理。在这个过程中，用户线程一直没有阻塞。
-![](https://img-blog.csdnimg.cn/2a9157c42ae04ecfa35075059df25547.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## 信号驱动I/O
-可以把信号驱动I/O理解为“半异步”，非阻塞模式是应用不断发起read调用查询数据到了内核没有，而信号驱动把这个过程异步了，应用发起read调用时注册了一个信号处理函数，其实是个回调函数，数据到了内核后，内核触发这个回调函数，应用在回调函数里再发起一次read调用去读内核的数据。
-所以是半异步。
-# NioEndpoint组件
-Tomcat的NioEndpoint实现了I/O多路复用模型。
-
-## 工作流程
-Java的多路复用器的使用：
-1. 创建一个Selector，在其上注册感兴趣的事件，然后调用select方法，等待感兴趣的事情发生
-2. 感兴趣的事情发生了，比如可读了，就创建一个新的线程从Channel中读数据
-
-NioEndpoint包含LimitLatch、Acceptor、Poller、SocketProcessor和Executor共5个组件。
-![](https://img-blog.csdnimg.cn/df667632760040c4a197f55936a8eec7.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## LimitLatch
-连接控制器，控制最大连接数，NIO模式下默认是8192。
-![](https://img-blog.csdnimg.cn/cbc41d109e944a14ad51650ec491da8f.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-当连接数到达最大时阻塞线程，直到后续组件处理完一个连接后将连接数减1。
-到达最大连接数后，os底层还是会接收客户端连接，但用户层已不再接收。
-核心代码：
-```java
-public class LimitLatch {
-    private class Sync extends AbstractQueuedSynchronizer {
-     
-        @Override
-        protected int tryAcquireShared() {
-            long newCount = count.incrementAndGet();
-            if (newCount > limit) {
-                count.decrementAndGet();
-                return -1;
-            } else {
-                return 1;
-            }
-        }
-
-        @Override
-        protected boolean tryReleaseShared(int arg) {
-            count.decrementAndGet();
-            return true;
-        }
-    }
-
-    private final Sync sync;
-    private final AtomicLong count;
-    private volatile long limit;
-    
-    // 线程调用该方法，获得接收新连接的许可，线程可能被阻塞
-    public void countUpOrAwait() throws InterruptedException {
-      sync.acquireSharedInterruptibly(1);
-    }
-
-    // 调用这个方法来释放一个连接许可，则前面阻塞的线程可能被唤醒
-    public long countDown() {
-      sync.releaseShared(0);
-      long result = getCount();
-      return result;
-   }
-}
-```
-用户线程调用**LimitLatch#countUpOrAwait**拿到锁，若无法获取，则该线程会被阻塞在AQS队列。
-AQS又是怎么知道是阻塞还是不阻塞用户线程的呢？
-由AQS的使用者决定，即内部类Sync决定，因为Sync类重写了**AQS#tryAcquireShared()**：若当前连接数count ＜ limit，线程能获取锁，返回1，否则返回-1。
-
-如何用户线程被阻塞到了AQS的队列，由Sync内部类决定什么时候唤醒，Sync重写AQS#tryReleaseShared()，当一个连接请求处理完了，又可以接收新连接，这样前面阻塞的线程将会被唤醒。
-
-LimitLatch用来限制应用接收连接的数量，Acceptor用来限制系统层面的连接数量，首先是LimitLatch限制，应用层处理不过来了，连接才会堆积在操作系统的Queue，而Queue的大小由acceptCount控制。
-## Acceptor
-Acceptor实现了Runnable接口，因此可以跑在单独线程里，在这个死循环里调用accept接收新连接。一旦有新连接请求到达，accept方法返回一个Channel对象，接着把Channel对象交给Poller去处理。
-
-一个端口号只能对应一个ServerSocketChannel，因此这个ServerSocketChannel是在多个Acceptor线程之间共享的，它是Endpoint的属性，由Endpoint完成初始化和端口绑定。
-可以同时有过个Acceptor调用accept方法，accept是线程安全的。
-
-### 初始化
-```java
-protected void initServerSocket() throws Exception {
-    if (!getUseInheritedChannel()) {
-        serverSock = ServerSocketChannel.open();
-        socketProperties.setProperties(serverSock.socket());
-        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
-
-        serverSock.socket().bind(addr,getAcceptCount());
-    } else {
-        // Retrieve the channel provided by the OS
-        Channel ic = System.inheritedChannel();
-        if (ic instanceof ServerSocketChannel) {
-            serverSock = (ServerSocketChannel) ic;
-        }
-        if (serverSock == null) {
-            throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
-        }
-    }
-    // 阻塞模式
-    serverSock.configureBlocking(true); //mimic APR behavior
-}
-```
-- bind方法的 getAcceptCount() 参数表示os的等待队列长度。当应用层的连接数到达最大值时，os可以继续接收连接，os能继续接收的最大连接数就是这个队列长度，可以通过acceptCount参数配置，默认是100
-![](https://img-blog.csdnimg.cn/0129d890067c4dafb5465f301990581b.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-ServerSocketChannel通过accept()接受新的连接，accept()方法返回获得SocketChannel对象，然后将SocketChannel对象封装在一个PollerEvent对象中，并将PollerEvent对象压入Poller的Queue里。
-这是个典型的“生产者-消费者”模式，Acceptor与Poller线程之间通过Queue通信。
-
-## Poller
-本质是一个Selector，也跑在单独线程里。
-
-Poller在内部维护一个Channel数组，它在一个死循环里不断检测Channel的数据就绪状态，一旦有Channel可读，就生成一个SocketProcessor任务对象扔给Executor去处理。
-
-内核空间的接收连接是对每个连接都产生一个channel，该channel就是Acceptor里accept方法得到的scoketChannel，后面的Poller在用selector#select监听内核是否准备就绪，才知道监听内核哪个channel。
-
-
-维护了一个 Queue:
-![](https://img-blog.csdnimg.cn/8957657d0d404199b57624d13cdeab49.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-SynchronizedQueue的方法比如offer、poll、size和clear都使用synchronized修饰，即同一时刻只有一个Acceptor线程读写Queue。
-同时有多个Poller线程在运行，每个Poller线程都有自己的Queue。
-每个Poller线程可能同时被多个Acceptor线程调用来注册PollerEvent。
-Poller的个数可以通过pollers参数配置。
-### 职责
-- Poller不断的通过内部的Selector对象向内核查询Channel状态，一旦可读就生成任务类SocketProcessor交给Executor处理
-![](https://img-blog.csdnimg.cn/26a153742fe14f55b5919e86e03782cf.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- Poller循环遍历检查自己所管理的SocketChannel是否已超时。若超时就关闭该SocketChannel
-
-## SocketProcessor
-Poller会创建SocketProcessor任务类交给线程池处理，而SocketProcessor实现了Runnable接口，用来定义Executor中线程所执行的任务，主要就是调用Http11Processor组件处理请求：Http11Processor读取Channel的数据来生成ServletRequest对象。
-
-Http11Processor并非直接读取Channel。因为Tomcat支持同步非阻塞I/O、异步I/O模型，在Java API中，对应Channel类不同，比如有AsynchronousSocketChannel和SocketChannel，为了对Http11Processor屏蔽这些差异，Tomcat设计了一个包装类叫作SocketWrapper，Http11Processor只调用SocketWrapper的方法去读写数据。
-## Executor
-线程池，负责运行SocketProcessor任务类，SocketProcessor的run方法会调用Http11Processor来读取和解析请求数据。我们知道，Http11Processor是应用层协议的封装，它会调用容器获得响应，再把响应通过Channel写出。
-
-Tomcat定制的线程池，它负责创建真正干活的工作线程。就是执行SocketProcessor#run，即解析请求并通过容器来处理请求，最终调用Servlet。
-
-# Tomcat的高并发设计
-高并发就是能快速地处理大量请求，需合理设计线程模型让CPU忙起来，尽量不要让线程阻塞，因为一阻塞，CPU就闲了。
-有多少任务，就用相应规模线程数去处理。
-比如NioEndpoint要完成三件事情：接收连接、检测I/O事件和处理请求，关键就是把这三件事情分别定制线程数处理：
-- 专门的线程组去跑Acceptor，并且Acceptor的个数可以配置
-- 专门的线程组去跑Poller，Poller的个数也可以配置
-- 具体任务的执行也由专门的线程池来处理，也可以配置线程池的大小
-
-# 总结
-I/O模型是为了解决内存和外部设备速度差异。
-- 所谓阻塞或非阻塞是指应用程序在发起I/O操作时，是立即返回还是等待
-- 同步和异步，是指应用程序在与内核通信时，数据从内核空间到应用空间的拷贝，是由内核主动发起还是由应用程序来触发。
-
-Tomcat#Endpoint组件的主要工作就是处理I/O，而NioEndpoint利用Java NIO API实现了多路复用I/O模型。
-读写数据的线程自己不会阻塞在I/O等待上，而是把这个工作交给Selector。
-
-当客户端发起一个HTTP请求时，首先由Acceptor#run中的
-```java
-socket = endpoint.serverSocketAccept();
-```
-
-接收连接，然后传递给名称为Poller的线程去侦测I/O事件，Poller线程会一直select，选出内核将数据从网卡拷贝到内核空间的 channel（也就是内核已经准备好数据）然后交给名称为Catalina-exec的线程去处理，这个过程也包括内核将数据从内核空间拷贝到用户空间这么一个过程，所以对于exec线程是阻塞的，此时用户空间（也就是exec线程）就接收到了数据，可以解析然后做业务处理了。
-
-> 参考
-> - https://blog.csdn.net/historyasamirror/article/details/5778378
\ No newline at end of file
diff --git "a/Tomcat/Tomcat\350\277\233\347\250\213\345\215\240\347\224\250CPU\350\277\207\351\253\230\346\200\216\344\271\210\345\212\236\357\274\237.md" "b/Tomcat/Tomcat\350\277\233\347\250\213\345\215\240\347\224\250CPU\350\277\207\351\253\230\346\200\216\344\271\210\345\212\236\357\274\237.md"
deleted file mode 100644
index 70905512f1..0000000000
--- "a/Tomcat/Tomcat\350\277\233\347\250\213\345\215\240\347\224\250CPU\350\277\207\351\253\230\346\200\216\344\271\210\345\212\236\357\274\237.md"
+++ /dev/null
@@ -1,77 +0,0 @@
-CPU经常会成为系统性能的瓶颈，可能：
-- 内存泄露导致频繁GC，进而引起CPU使用率过高
-- 代码Bug创建了大量的线程，导致CPU频繁上下文切换
-
-通常所说的CPU使用率过高，隐含着一个用来比较高与低的基准值，比如
-- JVM在峰值负载下的平均CPU利用率40％
-- CPU使用率飙到80%就可认为不正常
-
-JVM进程包含多个Java线程：
-- 一些在等待工作
-- 另一些则正在执行任务
-
-最重要的是找到哪些线程在消耗CPU，通过线程栈定位到问题代码
-如果没有找到个别线程的CPU使用率特别高，考虑是否线程上下文切换导致了CPU使用率过高。
-
-# 案例
-程序模拟CPU使用率过高 - 在线程池中创建4096个线程
-
-在Linux环境下启动程序：
-java -Xss256k -jar demo-0.0.1-SNAPSHOT.jar
-线程栈大小指定为256KB。对于测试程序来说，操作系统默认值8192KB过大，因为需要创建4096个线程。
-
-使用top命令，我们看到Java进程的CPU使用率达到了961.6%，注意到进程ID是55790。
-![](https://img-blog.csdnimg.cn/ccc4bf66ef604b20a0875593d571c134.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-用更精细化的top命令查看这个Java进程中各线程使用CPU的情况：
-
-```java
-#top -H -p 55790
-```
-![](https://img-blog.csdnimg.cn/513921500f344102b8857b0cf937b6f4.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-可见，有个叫“scheduling-1”的线程占用了较多的CPU，达到了42.5%。因此下一步我们要找出这个线程在做什么事情。
-
-5. 为了找出线程在做什么，用jstack生成线程快照。
-jstack输出较大，一般将其写入文件：
-```java
-jstack 55790 > 55790.log
-```
-打开55790.log，定位到第4步中找到的名为 **scheduling-1** 的线程，其线程栈：
-![](https://img-blog.csdnimg.cn/fa069dc8e34e4c209d72b5b9a6fa8c40.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-看到AbstractExecutorService#submit这个函数调用，说明它是Spring Boot启动的周期性任务线程，向线程池中提交任务，该线程消耗了大量CPU。
-
-# 上下文切换开销？
-经历上述过程，往往已经可以定位到大量消耗CPU的线程及bug代码，比如死循环。但对于该案例：Java进程占用的CPU是961.6%， 而“scheduling-1”线程只占用了42.5%的CPU，那其它CPU被谁占用了？
-
-第4步用top -H -p pid命令看到的线程列表中还有许多名为“pool-1-thread-x”的线程，它们单个的CPU使用率不高，但是似乎数量比较多。你可能已经猜到，这些就是线程池中干活的线程。那剩下的CPU是不是被这些线程消耗了呢？
-
-还需要看jstack的输出结果，主要是看这些线程池中的线程是不是真的在干活，还是在“休息”呢？
-![](https://img-blog.csdnimg.cn/4346e7a5f97943458a0878b66b1a1298.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)发现这些“pool-1-thread-x”线程基本都处WAITING状态。
-![](https://img-blog.csdnimg.cn/d36a43b009fd4ec9beef152b8b980408.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- Blocking指的是一个线程因为等待临界区的锁（Lock或者synchronized关键字）而被阻塞的状态，请你注意的是处于这个状态的线程还没有拿到锁
-- Waiting指的是一个线程拿到了锁，但需等待其他线程执行某些操作。比如调用了Object.wait、Thread.join或LockSupport.park方法时，进入Waiting状态。前提是这个线程已经拿到锁了，并且在进入Waiting状态前，os层面会自动释放锁，当等待条件满足，外部调用了Object.notify或者LockSupport.unpark方法，线程会重新竞争锁，成功获得锁后才能进入到Runnable状态继续执行。
-
-回到我们的“pool-1-thread-x”线程，这些线程都处在“Waiting”状态，从线程栈我们看到，这些线程“等待”在getTask方法调用上，线程尝试从线程池的队列中取任务，但是队列为空，所以通过LockSupport.park调用进到了“Waiting”状态。那“pool-1-thread-x”线程有多少个呢？通过下面这个命令来统计一下，结果是4096，正好跟线程池中的线程数相等。
-```bash
-grep -o 'pool-2-thread' 55790.log | wc -l
-```
-![](https://img-blog.csdnimg.cn/7085afda568d461d8dc52c2d6a92b602.png)
-
-
-剩下CPU到底被谁消耗了？
-应该怀疑CPU的上下文切换开销了，因为我们看到Java进程中的线程数比较多。
-
-下面通过vmstat命令来查看一下操作系统层面的线程上下文切换活动：
-![](https://img-blog.csdnimg.cn/a72a837da75e427a902b1c60c4210614.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-cs那一栏表示线程上下文切换次数，in表示CPU中断次数，我们发现这两个数字非常高，基本证实了我们的猜测，线程上下文切切换消耗了大量CPU。
-那具体是哪个进程导致的呢？
-
-停止Spring Boot程序，再次运行vmstat命令，会看到in和cs都大幅下降，这就证实引起线程上下文切换开销的Java进程正是55790。
-![](https://img-blog.csdnimg.cn/9200ce42e84543d6b2564f7f501d9b47.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# 总结
-遇到CPU过高，首先定位哪个进程导致的，之后可以通过top -H -p pid命令定位到具体的线程。
-其次还要通jstack查看线程的状态，看看线程的个数或者线程的状态，如果线程数过多，可以怀疑是线程上下文切换的开销，我们可以通过vmstat和pidstat这两个工具进行确认。
\ No newline at end of file
diff --git "a/Tomcat/Tomcat\350\277\236\346\216\245\345\231\250\350\256\276\350\256\241\346\236\266\346\236\204.md" "b/Tomcat/Tomcat\350\277\236\346\216\245\345\231\250\350\256\276\350\256\241\346\236\266\346\236\204.md"
deleted file mode 100644
index 9642be5a67..0000000000
--- "a/Tomcat/Tomcat\350\277\236\346\216\245\345\231\250\350\256\276\350\256\241\346\236\266\346\236\204.md"
+++ /dev/null
@@ -1,118 +0,0 @@
-# 1 Tomcat 核心功能
-- 处理Socket连接，负责网络字节流与Request和Response对象的转化
-因此Tomcat设计了连接器（Connector），负责对外交流
-- 加载和管理Servlet，以及具体处理Request请求
-设计了容器（Container），负责内部处理
-
-# 2 Tomcat支持的I/O模型
-- NIO
-非阻塞I/O，采用Java NIO类库实现。
-- NIO.2
-异步I/O，采用JDK 7最新的NIO.2类库实现。
-- APR
-采用Apache可移植运行库实现，是C/C++编写的本地库
-
-![](https://img-blog.csdnimg.cn/d562828da21147818236f6e2d2bbc8d2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-# 3 Tomcat支持的应用层协议
-- HTTP/1.1
-大部分Web应用采用的访问协议。
-- AJP
-用于和Web服务器集成（如Apache）。
-- HTTP/2
-HTTP 2.0大幅度的提升了Web性能。
-# 4 Service
-Tomcat为 **支持多种I/O模型和应用层协议**，一个容器可能对接多个连接器。
-但单独的连接器或容器都无法对外提供服务，需**组装**才能正常协作，而组装后的整体，就称为Service组件。所以，Service并不神奇，只是在连接器和容器外面多包了一层，把它们组装在一起。
-
-Tomcat内可能有多个Service，在Tomcat中配置多个Service，可实现通过不同端口号访问同一台机器上部署的不同应用。
-![](https://img-blog.csdnimg.cn/94069010a40243b3b33fc96ae5f0f3d5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-最顶层是Server（即一个Tomcat实例）。一个Server中有一或多个Service，一个Service中有多个连接器和一个容器。
-
-连接器与容器之间通过标准的ServletRequest/ServletResponse通信。
-# 5 连接器架构
-连接器对Servlet容器屏蔽了 **协议及I/O模型的区别**，处理Socket通信和应用层协议的解析，得到Servlet请求。
-所以无论是HTTP、AJP，最终在容器中获取到的都是标准ServletRequest对象。
-
-## 5.1 功能需求
-- 监听网络端口
-- 接受网络连接请求
-- 读取网络请求字节流
-- 根据具体应用层协议（HTTP/AJP）解析字节流，生成统一的Tomcat Request对象
-- 将Tomcat Request对象转成标准的ServletRequest
-- 调用Servlet容器，得到ServletResponse
-- 将ServletResponse转成Tomcat Response对象
-- 将Tomcat Response转成网络字节流
-- 将响应字节流写回给浏览器。
-
-那它应该有哪些子模块呢？
-优秀的模块化设计应该考虑高内聚、低耦合。连接器需完成如下高内聚功能：
-- 网络通信
-- 应用层协议解析
-- Tomcat Request/Response与ServletRequest/ServletResponse的转化
-
-因此Tomcat设计3个组件实现这3功能：Endpoint、Processor和Adapter。
-
-组件间通过抽象接口交互，以封装变化：将系统中经常变化的部分和稳定的部分隔离，有助于增加复用性，并降低系统耦合度。
-
-不管网络通信I/O模型、应用层协议、浏览器端发送的请求信息如何变化，但整体处理逻辑不变：
-- Endpoint
-提供字节流给Processor
-- Processor
-提供Tomcat Request对象给Adapter
-- Adapter
-提供ServletRequest对象给容器
-
-若要支持新的I/O方案、新的应用层协议，只需要实现相关具体子类，而上层通用处理逻辑不变。
-
-由于I/O模型和应用层协议可自由组合，比如NIO + HTTP或者NIO.2 + AJP。Tomcat将网络通信和应用层协议解析放在一起考虑，设计了ProtocolHandler接口，封装这两种变化点。
-## 5.2 ProtocolHandler
-各种协议和通信模型的组合有相应的具体实现类，如：
-![](https://img-blog.csdnimg.cn/20210717231048532.png)
-![](https://img-blog.csdnimg.cn/20210717231106745.png)
-![](https://img-blog.csdnimg.cn/20210717231144614.png)
-Tomcat设计了一系列抽象基类封装稳定部分，抽象基类AbstractProtocol实现了ProtocolHandler接口。
-每种应用层协议有自己的抽象基类，如AbstractAjpProtocol、AbstractHttp11Protocol，具体协议实现类扩展了协议层抽象基类。
-![](https://img-blog.csdnimg.cn/20210717232129435.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-如此设计，尽量地将稳定的部分放到抽象基类，同时每一种I/O模型和协议的组合都有相应的具体实现类，我们在使用时可以自由选择。
-
-**Endpoint和Processor放在一起抽象成了ProtocolHandler组件**：
-![](https://img-blog.csdnimg.cn/8005f739f3f442b28ed568395af2435e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-连接器用ProtocolHandler处理网络连接、应用层协议，包含如下重要部件
-### 5.2.1 Endpoint
-通信端点，即通信监听的接口，是具体的Socket接收和发送处理器，是对传输层的抽象，因此Endpoint用来实现TCP/IP协议。
-
-Endpoint是一个接口，对应的抽象实现类是AbstractEndpoint，而AbstractEndpoint的具体子类，比如在NioEndpoint和Nio2Endpoint中，有两个重要的子组件：Acceptor和SocketProcessor。
-#### Acceptor
-用于监听Socket连接请求。SocketProcessor用于处理接收到的Socket请求，它实现Runnable接口，在run方法里调用协议处理组件Processor进行处理。
-
-为了提高处理能力，SocketProcessor被提交到线程池来执行。而这个线程池叫作执行器（Executor)。
-###  5.2.2 Processor
-Processor用来实现应用层的HTTP协议，接收来自Endpoint的Socket，读取字节流解析成Tomcat Request和Response对象，并通过Adapter将其提交到容器处理。
-
-Processor是一个接口，定义了请求的处理等方法。它的抽象实现类AbstractProcessor对一些协议共有的属性进行封装，没有对方法进行实现。具体的实现有AjpProcessor、Http11Processor等，这些具体实现类实现了特定协议的解析方法和请求处理方式。
-
-连接器的组件图：
-![](https://img-blog.csdnimg.cn/8180c30045194e29b5c1d1ef500fa9a9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-Endpoint接收到Socket连接后，生成一个SocketProcessor任务提交到线程池处理，SocketProcessor的run方法会调用Processor组件去解析应用层协议，Processor通过解析生成Request对象后，会调用Adapter的Service方法。
-
-一个连接器对应一个监听端口，比如一扇门，一个web应用是一个业务部门，进了这个门后你可以到各个业务部门去办事。
-Tomcat配置的并发数是endpoint里那个线程池。
-###  5.2.3 Adapter
-由于协议不同，客户端发过来的请求信息也不尽相同，Tomcat定义了自己的Request类来“存放”这些请求信息。
-ProtocolHandler接口负责解析请求并生成Tomcat Request类，但这个Request对象不是标准ServletRequest，不能用Tomcat Request作为参数调用容器。
-
-于是Tomcat引入CoyoteAdapter，连接器调用CoyoteAdapter的sevice方法，传入Tomcat Request对象，CoyoteAdapter负责将Tomcat Request转成ServletRequest，再调用容器的service方法。
-
-连接器用ProtocolHandler接口来封装通信协议和I/O模型的差异，ProtocolHandler内部又分为Endpoint和Processor模块，Endpoint负责底层Socket通信，Processor负责应用层协议解析。连接器通过适配器Adapter调用容器。
-
-**为什么要多一层adapter？**
-在processor直接转换为容器的servletrequest和servletresponse是否更好，为何先转化为Tomcat的request和response，再用adapter做一层转换消耗性能？
-若连接器直接创建ServletRequest、ServletResponse，就和Servlet协议耦合，连接器尽量保持独立性，它不一定要跟Servlet容器工作。
-对象转化的性能消耗还是比较少的，Tomcat对HTTP请求体采取了延迟解析策略，即TomcatRequest对象转化成ServletRequest时，请求体的内容都还没读取，直到容器处理这个请求的时候才读取。
-
-Adapter一层使用的是适配器设计模式，好处是当容器版本升级只修改Adaper组件适配到新版本容器就可以了，protocal handler组件代码不需要改动。
-# 6 Tomcat V.S Netty 
-**为何Netty常用做底层通讯模块，而Tomcat作为web容器？**
-可将Netty理解成Tomcat中的连接器，都负责网络通信、利用了NIO。但Netty素以高性能高并发著称，为何Tomcat不直接将连接器替换成Netty？
-- Tomcat的连接器性能已经足够好了，同样是Java NIO编程，底层原理类似
-- Tomcat做为Web容器，需考虑Servlet规范，Servlet规范规定了对HTTP Body的读写是阻塞的，因此即使用到Netty，也不能充分发挥其优势。所以Netty一般用在非HTTP协议/Servlet场景。
\ No newline at end of file
diff --git a/UML/demo.dio b/UML/demo.dio
new file mode 100644
index 0000000000..94ca051714
--- /dev/null
+++ b/UML/demo.dio
@@ -0,0 +1,751 @@
+<mxfile host="65bd71144e" modified="2021-01-22T06:08:14.981Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.50.1 Chrome/83.0.4103.122 Electron/9.2.1 Safari/537.36" etag="5XmaboyXk4hpopKzh_03" version="13.10.0" type="embed" pages="4">
+    <diagram id="6hGFLwfOUW9BJ-s0fimq" name="消息队列">
+        <mxGraphModel dx="908" dy="343" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
+            <root>
+                <mxCell id="0"/>
+                <mxCell id="1" parent="0"/>
+                <mxCell id="5" value="&lt;font style=&quot;font-size: 23px&quot;&gt;Consumer&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="480" y="206.5" width="133" height="98" as="geometry"/>
+                </mxCell>
+                <mxCell id="9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="7" target="5" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <Array as="points">
+                            <mxPoint x="440" y="242"/>
+                            <mxPoint x="440" y="242"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="7" value="&lt;span style=&quot;font-size: 23px&quot;&gt;Broker&lt;/span&gt;" style="rounded=1;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="267" y="206.5" width="133" height="98" as="geometry"/>
+                </mxCell>
+                <mxCell id="13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="10" target="12" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <Array as="points">
+                            <mxPoint x="225" y="238"/>
+                            <mxPoint x="225" y="238"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="10" value="&lt;span style=&quot;font-size: 23px&quot;&gt;Producer&lt;/span&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="54" y="206.5" width="133" height="98" as="geometry"/>
+                </mxCell>
+                <mxCell id="12" value="&lt;span style=&quot;font-size: 23px&quot;&gt;Broker&lt;/span&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="267" y="206.5" width="133" height="98" as="geometry"/>
+                </mxCell>
+                <mxCell id="14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="267" y="271" as="sourcePoint"/>
+                        <mxPoint x="187" y="271" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="225" y="271"/>
+                            <mxPoint x="225" y="271"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="18" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="480" y="272.0000000000001" as="sourcePoint"/>
+                        <mxPoint x="400" y="272.0000000000001" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="438" y="272"/>
+                            <mxPoint x="438" y="272"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="19" value="Send" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="204" y="216" width="38" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="20" value="Ack" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="208" y="275" width="30" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="21" value="Pull" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="423" y="216" width="30" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="22" value="Ack" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="425" y="275" width="30" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="28" value="" style="endArrow=none;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="55" y="161" as="sourcePoint"/>
+                        <mxPoint x="263" y="161" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="29" value="生产阶段" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="28" vertex="1" connectable="0">
+                    <mxGeometry x="-0.0029" y="3" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="30" value="" style="endArrow=none;dashed=1;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;" parent="1" source="10" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="54" y="213" as="sourcePoint"/>
+                        <mxPoint x="54" y="121" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="31" value="" style="endArrow=none;dashed=1;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="266.43" y="304.5" as="sourcePoint"/>
+                        <mxPoint x="266" y="124" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="32" value="" style="endArrow=none;dashed=1;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="399.43000000000006" y="307.5" as="sourcePoint"/>
+                        <mxPoint x="399" y="128" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="33" value="" style="endArrow=none;dashed=1;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="613" y="307.5" as="sourcePoint"/>
+                        <mxPoint x="613" y="130" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="34" value="" style="endArrow=none;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="267" y="161" as="sourcePoint"/>
+                        <mxPoint x="400" y="161" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="35" value="存储阶段" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="34" vertex="1" connectable="0">
+                    <mxGeometry x="-0.0029" y="3" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="36" value="" style="endArrow=none;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="400" y="161" as="sourcePoint"/>
+                        <mxPoint x="611" y="161" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="37" value="消费阶段" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="36" vertex="1" connectable="0">
+                    <mxGeometry x="-0.0029" y="3" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="40" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="38" target="39" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="41" value="put" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=17;" parent="40" vertex="1" connectable="0">
+                    <mxGeometry x="-0.1805" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="38" value="&lt;span style=&quot;font-size: 23px&quot;&gt;OnMessage&lt;br&gt;&lt;/span&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="79" y="547" width="135" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="45" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=17;" parent="1" source="39" target="44" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="47" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=17;" parent="1" source="39" target="46" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="49" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontSize=17;" parent="1" source="39" target="48" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="39" value="&lt;span style=&quot;font-size: 23px&quot;&gt;内存队列&lt;br&gt;&lt;/span&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="294" y="547" width="135" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="48" value="&lt;span style=&quot;font-size: 23px&quot;&gt;业务线程&lt;/span&gt;&lt;span style=&quot;font-size: 23px&quot;&gt;&lt;br&gt;&lt;/span&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="596" y="544" width="135" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="46" value="&lt;span style=&quot;font-size: 23px&quot;&gt;业务线程&lt;/span&gt;&lt;span style=&quot;font-size: 23px&quot;&gt;&lt;br&gt;&lt;/span&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="596" y="619" width="135" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="44" value="&lt;span style=&quot;font-size: 23px&quot;&gt;业务线程&lt;br&gt;&lt;/span&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="598" y="474" width="135" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="52" value="take" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;" parent="1" vertex="1">
+                    <mxGeometry x="507" y="518" width="42" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="53" value="take" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;" parent="1" vertex="1">
+                    <mxGeometry x="505" y="558.5" width="42" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="62" value="take" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;" parent="1" vertex="1">
+                    <mxGeometry x="502" y="599" width="42" height="23" as="geometry"/>
+                </mxCell>
+            </root>
+        </mxGraphModel>
+    </diagram>
+    <diagram id="YcXlstzRR4efNojDI3mO" name="Netty">
+        <mxGraphModel dx="908" dy="343" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="1600" pageHeight="900" background="#F4F1DE" math="0" shadow="0">
+            <root>
+                <mxCell id="EfUFi5Catn_g72CNZ-E8-0"/>
+                <mxCell id="EfUFi5Catn_g72CNZ-E8-1" parent="EfUFi5Catn_g72CNZ-E8-0"/>
+                <mxCell id="lQPPuHaSg_9s36s1ONYh-2" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="lQPPuHaSg_9s36s1ONYh-0" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="434" y="113.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="lQPPuHaSg_9s36s1ONYh-0" value="NioEventLoop" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="295" y="98" width="86" height="31" as="geometry"/>
+                </mxCell>
+                <mxCell id="lQPPuHaSg_9s36s1ONYh-3" value="Thread" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="434" y="90.5" width="95" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-0" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="F8M6wTGFgnVXGZE4p4G4-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="434" y="211" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-1" value="Channel" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="295" y="195.5" width="86" height="31" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-2" value="Socket" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="434" y="188" width="95" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-3" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="F8M6wTGFgnVXGZE4p4G4-4" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="434" y="295" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-4" value="ByteBuf" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="295" y="279.5" width="86" height="31" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-5" value="IO Bytes" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="434" y="272" width="95" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-6" value="" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="103" y="415" width="110" height="217" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-7" value="JVM进程" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="124" y="433" width="58" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-8" value="堆内存" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="124" y="523" width="65" height="101" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-9" value="内核" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="248" y="412" width="107" height="92" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-11" value="磁盘" style="shape=cylinder2;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="395" y="502" width="82" height="105" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-12" value="" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=11;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="433" y="574" width="22" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-13" value="" style="endArrow=classic;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0;entryDx=22;entryDy=18;entryPerimeter=0;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="F8M6wTGFgnVXGZE4p4G4-12" target="F8M6wTGFgnVXGZE4p4G4-14" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="393" y="548.5" as="sourcePoint"/>
+                        <mxPoint x="443" y="498.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-14" value="" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=11;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="307" y="467" width="22" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-15" value="" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=11;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="152" y="467" width="22" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-16" value="" style="endArrow=classic;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0;entryDx=22;entryDy=18;entryPerimeter=0;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="F8M6wTGFgnVXGZE4p4G4-14" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="340" y="543.25" as="sourcePoint"/>
+                        <mxPoint x="174" y="480.75" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-17" value="" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=11;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="147" y="532" width="22" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8M6wTGFgnVXGZE4p4G4-18" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0;entryDx=11;entryDy=0;entryPerimeter=0;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="F8M6wTGFgnVXGZE4p4G4-15" target="F8M6wTGFgnVXGZE4p4G4-17" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="317" y="489.5" as="sourcePoint"/>
+                        <mxPoint x="184" y="490.75" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-0" value="&lt;font color=&quot;#cc0000&quot;&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;HeapByteBuf&lt;/font&gt;" style="whiteSpace=wrap;html=1;fillColor=none;rounded=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="79.5" y="508" width="154" height="163" as="geometry"/>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-2" value="Head" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="258" y="593" width="39" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-3" value="" style="whiteSpace=wrap;html=1;fillColor=none;rounded=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="84" y="395" width="216" height="101" as="geometry"/>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-4" value="&lt;span style=&quot;color: rgb(204 , 0 , 0)&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;DirectByteBuf&lt;/span&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="77" y="397" width="187" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-5" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="X3sYNRrKfa2ecS8vE-fe-6" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="434" y="356" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-6" value="Pipeline" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="295" y="340.5" width="86" height="31" as="geometry"/>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-7" value="Logic Chain" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="434" y="333" width="95" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-8" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="X3sYNRrKfa2ecS8vE-fe-9" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="434" y="42" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-9" value="ChannelHandler" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="287" y="26.5" width="94" height="31" as="geometry"/>
+                </mxCell>
+                <mxCell id="X3sYNRrKfa2ecS8vE-fe-10" value="Logic" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="434" y="19" width="95" height="46" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-17" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-0" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="949" y="98" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-0" value="NioEventLoop循环起点" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="882" y="21" width="134" height="44" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-4" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-1" target="K9JH9R6U8DBPP-BykJN7-3" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-35" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="1022" y="48" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-1" value="state关闭?" style="rhombus;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="909" y="98" width="80" height="54" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-7" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-3" target="K9JH9R6U8DBPP-BykJN7-6" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-3" value="关闭所有Channel" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="804" y="154" width="103" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-11" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-6" target="K9JH9R6U8DBPP-BykJN7-10" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-6" value="执行存储的Tasks/Hooks" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="786.25" y="198" width="138.5" height="28" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-13" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-10" target="K9JH9R6U8DBPP-BykJN7-12" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-10" target="K9JH9R6U8DBPP-BykJN7-38" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-10" value="是否有 task/hooks&lt;br&gt;被运行" style="rhombus;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="815.5" y="253" width="80" height="70" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-19" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-12" target="K9JH9R6U8DBPP-BykJN7-18" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-22" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-12" target="K9JH9R6U8DBPP-BykJN7-21" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-12" value="超过最大优雅&lt;br&gt;关闭时间?" style="rhombus;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="907" y="325" width="97" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-15" value="无" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="907" y="266" width="22" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-16" value="是" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="873.5" y="107" width="22" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-18" value="关闭,退出循环" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="815.5" y="470.5" width="79.75" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-20" value="超过" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="861.25" y="347" width="34" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-27" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-21" target="K9JH9R6U8DBPP-BykJN7-26" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-45" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-21" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="857" y="453" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-21" value="静默期&lt;br&gt;task/hook&lt;br&gt;是否执行过" style="rhombus;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1006" y="415" width="123" height="76" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-23" value="未超" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1016" y="347" width="34" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-24" value="gracefulShutdownTimeout" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="976" y="323" width="149" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-25" value="gracefulShutdownQuiteTimeout" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1070" y="387" width="177" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-32" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-26" target="K9JH9R6U8DBPP-BykJN7-0" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="1330" y="49.5" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="1266" y="503"/>
+                            <mxPoint x="1266" y="43"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-26" value="不能关闭，停 100ms" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1137" y="492" width="120" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-28" value="有" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1142" y="433" width="22" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-33" value="下一轮循环" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1193" y="131" width="70" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-36" value="否" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="988" y="107" width="22" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-37" value="下一轮循环" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1023" y="68" width="70" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-42" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="K9JH9R6U8DBPP-BykJN7-38" target="K9JH9R6U8DBPP-BykJN7-0" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="657.0000000000002" y="337.9999999999999" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="708" y="338"/>
+                            <mxPoint x="708" y="43"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-38" value="不能关闭" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="737" y="325" width="67" height="26" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-40" value="有" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="755" y="275" width="22" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-43" value="下一轮循环" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="636" y="131" width="70" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="K9JH9R6U8DBPP-BykJN7-46" value="无" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="935" y="433" width="22" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-5" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-0" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="1547.5" y="53.49999999999994" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-0" value="需求分析" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1445" y="42" width="55" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-8" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-6" target="MMHLFaAT7ID8AQoSgAGI-7" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-6" value="定义业务数据结构" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1546" y="42" width="103" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-10" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-7" target="MMHLFaAT7ID8AQoSgAGI-9" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-7" value="实现业务逻辑" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1682" y="42" width="103" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-12" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-9" target="MMHLFaAT7ID8AQoSgAGI-11" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-9" value="选择传输协议" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1820" y="42" width="103" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-14" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-11" target="MMHLFaAT7ID8AQoSgAGI-13" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-11" value="定义传输信息结构" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1820" y="98" width="103" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-37" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-13" target="MMHLFaAT7ID8AQoSgAGI-36" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-13" value="选择编解码" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1707" y="98" width="75" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-32" value="数据本身编解码" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1746" y="125" width="94" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-33" value="压缩等编解码" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1760" y="143" width="82" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-34" value="粘包/半包处理编解码" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1773" y="161" width="121" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-39" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-36" target="MMHLFaAT7ID8AQoSgAGI-38" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-36" value="实现所有的编解码" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1563" y="98" width="104" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-44" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-38" target="MMHLFaAT7ID8AQoSgAGI-43" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-38" value="编写应用程序" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1445" y="98" width="78" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-40" value="编写服务器" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1453" y="125" width="70" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-41" value="编写客户端" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1453" y="143" width="70" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-46" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-43" target="MMHLFaAT7ID8AQoSgAGI-45" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-43" value="测试与优化" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1332" y="98" width="78" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-53" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-45" target="MMHLFaAT7ID8AQoSgAGI-52" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-45" value="复查代码" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1332" y="201" width="78" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-47" value="Google最佳实践" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1330" y="230" width="97" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-48" value="Google技术坑点" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1330" y="248" width="97" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-50" value="对比经典项目实现" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1330" y="264" width="106" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-51" value="Code Review" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1330" y="282" width="82" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-58" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-52" target="MMHLFaAT7ID8AQoSgAGI-57" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-52" value="预留后门" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1508" y="200.5" width="78" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-55" value="检查是否可排查" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1503" y="230" width="94" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-56" value="检查是否可度量" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1503" y="248" width="94" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-60" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-57" target="MMHLFaAT7ID8AQoSgAGI-59" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-57" value="上线" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1651" y="201" width="78" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-59" value="反馈" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1809" y="201" width="78" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-64" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="MMHLFaAT7ID8AQoSgAGI-62" target="MMHLFaAT7ID8AQoSgAGI-0" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-62" value="摸鱼结束" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1343.5" y="42" width="55" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-65" value="收集错误数据" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1807" y="230" width="82" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-66" value="收集性能数据" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1809" y="248" width="82" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-68" value="&lt;b&gt;&lt;font color=&quot;#cc0000&quot;&gt;不定时摸鱼&lt;/font&gt;&lt;/b&gt;" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1568" y="139" width="134" height="44" as="geometry"/>
+                </mxCell>
+                <mxCell id="MMHLFaAT7ID8AQoSgAGI-69" value="&lt;b&gt;&lt;font style=&quot;font-size: 18px&quot;&gt;Java 程序员开发迭代流程&lt;/font&gt;&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="1504" y="7.5" width="217" height="20" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-0" value="Netty客户端" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=0;sketch=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2153" y="107" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-3" value="Netty客户端" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=0;sketch=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2153" y="219.5" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-4" value="Netty服务端" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;rounded=0;sketch=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2375" y="156" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-8" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;rounded=0;sketch=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2346" y="94.5" width="10" height="61" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-9" value="&lt;font color=&quot;#000000&quot; style=&quot;background-color: rgb(255 , 255 , 255)&quot;&gt;授权&lt;/font&gt;" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" target="5s1gLw3eerGRBnV0wAGs-8" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2276" y="94.5" as="sourcePoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-10" value="&lt;span style=&quot;background-color: rgb(255 , 255 , 255)&quot;&gt;结果&lt;/span&gt;" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;exitX=0;exitY=0.95;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="5s1gLw3eerGRBnV0wAGs-8" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2276" y="152.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-11" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;rounded=0;sketch=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2346" y="179" width="10" height="61" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-12" value="&lt;font color=&quot;#000000&quot; style=&quot;background-color: rgb(255 , 255 , 255)&quot;&gt;下单&lt;/font&gt;" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" target="5s1gLw3eerGRBnV0wAGs-11" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2276" y="179" as="sourcePoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-13" value="&lt;span style=&quot;background-color: rgb(255 , 255 , 255)&quot;&gt;结果&lt;/span&gt;" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;exitX=0;exitY=0.95;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="5s1gLw3eerGRBnV0wAGs-11" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2276" y="237" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-14" value="" style="html=1;points=[];perimeter=orthogonalPerimeter;rounded=0;sketch=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2346" y="264" width="10" height="61" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-15" value="&lt;font color=&quot;#000000&quot; style=&quot;background-color: rgb(255 , 255 , 255)&quot;&gt;keepalive&lt;/font&gt;" style="html=1;verticalAlign=bottom;endArrow=block;entryX=0;entryY=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" target="5s1gLw3eerGRBnV0wAGs-14" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2276" y="264" as="sourcePoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-16" value="&lt;span style=&quot;background-color: rgb(255 , 255 , 255)&quot;&gt;结果&lt;/span&gt;" style="html=1;verticalAlign=bottom;endArrow=open;dashed=1;endSize=8;exitX=0;exitY=0.95;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" source="5s1gLw3eerGRBnV0wAGs-14" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2276" y="322" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-17" value="Frame" style="rounded=1;whiteSpace=wrap;html=1;sketch=0;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2591" y="114" width="411" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-18" value="Message" style="rounded=1;whiteSpace=wrap;html=1;sketch=0;strokeColor=#E07A5F;fontColor=#393C56;fillColor=#CCCCCC;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2683" y="149" width="319" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-21" value="Message Body" style="rounded=1;whiteSpace=wrap;html=1;sketch=0;strokeColor=#E07A5F;fontColor=#393C56;fillColor=#FFB366;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2841" y="183" width="163" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-23" value="Message Header" style="rounded=1;whiteSpace=wrap;html=1;sketch=0;strokeColor=#E07A5F;fontColor=#393C56;fillColor=#FFE6CC;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2682" y="183" width="159" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-25" value="operation/operation result" style="rounded=1;whiteSpace=wrap;html=1;sketch=0;strokeColor=#E07A5F;fontColor=#393C56;fillColor=#3399FF;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2841" y="214" width="163" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-26" value="length" style="rounded=1;whiteSpace=wrap;html=1;sketch=0;strokeColor=#E07A5F;fontColor=#393C56;fillColor=#33FF99;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2593" y="214" width="88" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-30" value="opCode" style="rounded=1;whiteSpace=wrap;html=1;sketch=0;strokeColor=#E07A5F;fontColor=#393C56;fillColor=#CCFFCC;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2732" y="214" width="56" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-31" value="streamId" style="rounded=1;whiteSpace=wrap;html=1;sketch=0;strokeColor=#E07A5F;fontColor=#393C56;fillColor=#CCFFCC;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2788" y="214" width="53" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="5s1gLw3eerGRBnV0wAGs-32" value="version" style="rounded=1;whiteSpace=wrap;html=1;sketch=0;strokeColor=#E07A5F;fontColor=#393C56;fillColor=#CCFFCC;" parent="EfUFi5Catn_g72CNZ-E8-1" vertex="1">
+                    <mxGeometry x="2681" y="214" width="51" height="22" as="geometry"/>
+                </mxCell>
+            </root>
+        </mxGraphModel>
+    </diagram>
+    <diagram id="dVF25l2cH5eOeoWfryCS" name="Spring">
+        <mxGraphModel dx="1308" dy="343" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="1600" pageHeight="900" background="none" math="0" shadow="1">
+            <root>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-0"/>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-1" parent="iMc_JdqMJ_6yprQ1mp33-0"/>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-4" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="iMc_JdqMJ_6yprQ1mp33-1" source="iMc_JdqMJ_6yprQ1mp33-2" target="iMc_JdqMJ_6yprQ1mp33-3" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-7" value="生成" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="iMc_JdqMJ_6yprQ1mp33-4" vertex="1" connectable="0">
+                    <mxGeometry x="-0.1863" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-2" value="Spring 容器" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="iMc_JdqMJ_6yprQ1mp33-1" vertex="1">
+                    <mxGeometry x="-75" y="261" width="103" height="37" as="geometry"/>
+                </mxCell>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-3" value="完全配置后且可运行的系统" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;align=left;" parent="iMc_JdqMJ_6yprQ1mp33-1" vertex="1">
+                    <mxGeometry x="-96.75" y="349" width="146.5" height="44" as="geometry"/>
+                </mxCell>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-5" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="iMc_JdqMJ_6yprQ1mp33-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="-23.34" y="181" as="sourcePoint"/>
+                        <mxPoint x="-23.34" y="261" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-6" value="业务对象类" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontStyle=1" parent="iMc_JdqMJ_6yprQ1mp33-5" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2484" relative="1" as="geometry">
+                        <mxPoint x="3.34" y="12.66" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="iMc_JdqMJ_6yprQ1mp33-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="-170" y="279" as="sourcePoint"/>
+                        <mxPoint x="-75" y="279.16" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="-155" y="279.66"/>
+                            <mxPoint x="-155" y="279.66"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-11" value="读取配置元数据" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontStyle=1" parent="iMc_JdqMJ_6yprQ1mp33-10" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2817" relative="1" as="geometry">
+                        <mxPoint x="11.209999999999999" y="0.4399999999999995" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="iMc_JdqMJ_6yprQ1mp33-12" value="应用中的类通过配置元数据进行关联" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontStyle=2;fontSize=11;" parent="iMc_JdqMJ_6yprQ1mp33-1" vertex="1">
+                    <mxGeometry x="-241" y="212" width="186" height="16" as="geometry"/>
+                </mxCell>
+            </root>
+        </mxGraphModel>
+    </diagram>
+    <diagram id="Hc47BLP8L_I3HLgWuVmp" name="架构">
+        <mxGraphModel dx="623" dy="237" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" background="#F4F1DE" math="0" shadow="0">
+            <root>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-0"/>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-1" parent="X_ZS9xsi_e8sdOcgI1hP-0"/>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-2" value="前台" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;sketch=1;rounded=1;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="200" y="90" width="310" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-3" value="&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;业务中台" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="200" y="130" width="180" height="90" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-4" value="数据中台" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="400" y="130" width="110" height="90" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-6" value="通用能力中台" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="210" y="140" width="90" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-7" value="核心能力中台" style="rounded=0;whiteSpace=wrap;html=1;sketch=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="210" y="180" width="90" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-9" value="后台" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;sketch=1;rounded=1;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="200" y="230" width="310" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-12" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" edge="1" parent="X_ZS9xsi_e8sdOcgI1hP-1" source="X_ZS9xsi_e8sdOcgI1hP-10" target="X_ZS9xsi_e8sdOcgI1hP-11">
+                    <mxGeometry relative="1" as="geometry">
+                        <Array as="points">
+                            <mxPoint x="695" y="150"/>
+                            <mxPoint x="695" y="150"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-10" value="用户接口层" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;sketch=1;rounded=1;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="590" y="90" width="210" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-14" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" edge="1" parent="X_ZS9xsi_e8sdOcgI1hP-1" source="X_ZS9xsi_e8sdOcgI1hP-11" target="X_ZS9xsi_e8sdOcgI1hP-13">
+                    <mxGeometry relative="1" as="geometry">
+                        <Array as="points">
+                            <mxPoint x="665" y="200"/>
+                            <mxPoint x="665" y="200"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-11" value="应用层" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fontColor=#393C56;sketch=1;rounded=1;fillColor=#FFE6CC;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="590" y="150" width="150" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-13" value="领域层" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fontColor=#393C56;sketch=1;rounded=1;fillColor=#CCFFCC;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="590" y="210" width="200" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-15" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;entryX=0.9;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="X_ZS9xsi_e8sdOcgI1hP-1" target="X_ZS9xsi_e8sdOcgI1hP-13">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="770" y="120" as="sourcePoint"/>
+                        <mxPoint x="770" y="150" as="targetPoint"/>
+                        <Array as="points"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-16" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" edge="1" source="X_ZS9xsi_e8sdOcgI1hP-17" target="X_ZS9xsi_e8sdOcgI1hP-19" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry relative="1" as="geometry">
+                        <Array as="points">
+                            <mxPoint x="932" y="150"/>
+                            <mxPoint x="932" y="150"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-17" value="用户接口层" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;sketch=1;rounded=1;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="857" y="90" width="153" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-18" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" edge="1" source="X_ZS9xsi_e8sdOcgI1hP-19" target="X_ZS9xsi_e8sdOcgI1hP-20" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry relative="1" as="geometry">
+                        <Array as="points">
+                            <mxPoint x="932" y="190"/>
+                            <mxPoint x="932" y="190"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-19" value="应用层" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fontColor=#393C56;sketch=1;rounded=1;fillColor=#FFE6CC;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="857" y="150" width="150" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="X_ZS9xsi_e8sdOcgI1hP-20" value="领域层" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fontColor=#393C56;sketch=1;rounded=1;fillColor=#CCFFCC;" vertex="1" parent="X_ZS9xsi_e8sdOcgI1hP-1">
+                    <mxGeometry x="857" y="210" width="150" height="30" as="geometry"/>
+                </mxCell>
+            </root>
+        </mxGraphModel>
+    </diagram>
+</mxfile>
\ No newline at end of file
diff --git "a/JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/.md" b/UML/test.java
similarity index 100%
rename from "JDK/\345\271\266\345\217\221\347\274\226\347\250\213/Java\345\271\266\345\217\221\347\274\226\347\250\213\345\256\236\346\210\230/.md"
rename to UML/test.java
diff --git "a/UML/\346\265\201\347\250\213.dio" "b/UML/\346\265\201\347\250\213.dio"
new file mode 100644
index 0000000000..c48ef67f4e
--- /dev/null
+++ "b/UML/\346\265\201\347\250\213.dio"
@@ -0,0 +1,3381 @@
+<mxfile host="65bd71144e" modified="2021-01-29T01:47:04.097Z" agent="5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.50.1 Chrome/83.0.4103.122 Electron/9.2.1 Safari/537.36" etag="8jDkupC8YgW4ESv9lQLn" version="13.10.0" type="embed" pages="4">
+    <diagram id="FMUiYtCSQAETOWIfJOuR" name="MySQL">
+        <mxGraphModel dx="1017" dy="2532" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1600" pageHeight="900" background="none" math="1" shadow="1">
+            <root>
+                <mxCell id="0"/>
+                <mxCell id="1" parent="0"/>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-263" value="原订单库" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="507" y="64" width="74" height="77" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-264" value="订单流程库" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="572" y="191" width="74" height="77" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-266" value="基础订单库" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="442" y="191" width="74" height="77" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-273" value="" style="edgeStyle=elbowEdgeStyle;elbow=vertical;endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="543" y="143" as="sourcePoint"/>
+                        <mxPoint x="480" y="193" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="480" y="168"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-275" value="" style="edgeStyle=elbowEdgeStyle;elbow=vertical;endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="I7SlA6YzeZ6JW0DRfLk0-263" target="I7SlA6YzeZ6JW0DRfLk0-264" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="559" y="140" as="sourcePoint"/>
+                        <mxPoint x="509" y="190" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="509" y="165"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-276" value="order" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="421" y="-273" width="74" height="78" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-277" value="" style="shape=singleArrow;whiteSpace=wrap;html=1;fillColor=#e3c800;strokeColor=#B09500;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="501" y="-234" width="166" height="17.25" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-278" value="分库策略：&lt;br&gt;UserId后四位 mod 32" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="505" y="-273" width="125" height="32" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-280" value="order" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="690" y="-265.37" width="74" height="78" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-281" value="UserId后四位 mod 32 = x的数据库" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="608" y="-136" width="192" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-282" value="数据库内再分表：&lt;br&gt;UserId后四位 mod 32 = x的表" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="772" y="-270" width="168" height="32" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-283" value="order" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="700" y="-255.37" width="74" height="78" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-284" value="order" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="710" y="-245.37" width="74" height="78" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-285" value="order" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="720" y="-235.37" width="74" height="78" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-286" value="order" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="730" y="-225.37" width="74" height="78" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-288" value="分片键" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0a30a;strokeColor=#BD7000;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="481" y="-437" width="85" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-289" value="" style="whiteSpace=wrap;html=1;aspect=fixed;" parent="1" vertex="1">
+                    <mxGeometry x="632" y="-511" width="152" height="152" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-290" value="key1:db1&lt;br&gt;key2:db2&lt;br&gt;key3:db3" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;" parent="1" vertex="1">
+                    <mxGeometry x="657.25" y="-471" width="107.5" height="94.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-291" value="数据库映射" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="676" y="-505" width="70" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-296" value="" style="endArrow=classic;html=1;" parent="1" target="I7SlA6YzeZ6JW0DRfLk0-290" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="566" y="-414" as="sourcePoint"/>
+                        <mxPoint x="616" y="-464" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-297" value="db1" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;" parent="1" vertex="1">
+                    <mxGeometry x="839" y="-511" width="57" height="55" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-298" value="db3" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;" parent="1" vertex="1">
+                    <mxGeometry x="845" y="-392" width="57" height="55" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-299" value="db2" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;" parent="1" vertex="1">
+                    <mxGeometry x="842" y="-451.25" width="57" height="55" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-300" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="734" y="-425" as="sourcePoint"/>
+                        <mxPoint x="837" y="-475" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-301" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="736" y="-410" as="sourcePoint"/>
+                        <mxPoint x="839" y="-418" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-302" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="735.9999999999998" y="-394" as="sourcePoint"/>
+                        <mxPoint x="839" y="-368" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-303" value="分片键" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0a30a;strokeColor=#BD7000;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="665.5" y="-834" width="85" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-304" value="[0，10^6]" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#647687;strokeColor=#314354;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="551" y="-712" width="79" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-305" value="[10^6+1，2*10^6]" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#647687;strokeColor=#314354;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="667" y="-714" width="79" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-306" value="[2*10^6+1，3*10^6]" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;fillColor=#647687;strokeColor=#314354;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="774" y="-714" width="79" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-307" value="……" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="892" y="-689" width="34" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-308" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="669" y="-790" as="sourcePoint"/>
+                        <mxPoint x="593" y="-715" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-309" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="692" y="-790" as="sourcePoint"/>
+                        <mxPoint x="699" y="-717" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-310" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="739" y="-791" as="sourcePoint"/>
+                        <mxPoint x="809" y="-718" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="I7SlA6YzeZ6JW0DRfLk0-311" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="750.5" y="-794" as="sourcePoint"/>
+                        <mxPoint x="920" y="-714" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="2" value="ID_card_n1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="306" y="395" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="5" value="ID_card_n2" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="306" y="425" width="75" height="27" as="geometry"/>
+                </mxCell>
+                <mxCell id="6" value="&lt;span&gt;ID_card_n3&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="306" y="452" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="7" value="&lt;span&gt;ID_card_n4&lt;/span&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;strokeColor=#001DBC;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="306" y="482" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="8" value="1" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="459" y="363" width="32" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="9" value="2" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="459" y="388" width="32" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="10" value="..." style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="459" y="413" width="32" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="11" value="A" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="459" y="438" width="32" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="12" value="B" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="459" y="463" width="32" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="13" value="M" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="459" y="513" width="32" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="14" value="..." style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="459" y="488" width="32" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="15" value="" style="html=1;" parent="1" vertex="1">
+                    <mxGeometry x="581" y="304" width="87" height="91" as="geometry"/>
+                </mxCell>
+                <mxCell id="16" value="ID_card_n1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="587" y="327" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="17" value="User1" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="603" y="307" width="42" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="18" value="username1" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="587" y="361" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="19" value="" style="html=1;" parent="1" vertex="1">
+                    <mxGeometry x="580.5" y="430" width="87" height="91" as="geometry"/>
+                </mxCell>
+                <mxCell id="20" value="ID_card_n2" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="586.5" y="453" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="21" value="User2" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="602.5" y="433" width="42" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="22" value="username2" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="586.5" y="487" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="23" value="" style="html=1;" parent="1" vertex="1">
+                    <mxGeometry x="580.5" y="552" width="87" height="91" as="geometry"/>
+                </mxCell>
+                <mxCell id="24" value="ID_card_n3" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="586.5" y="575" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="25" value="User3" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="602.5" y="555" width="42" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="26" value="username3" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="586.5" y="609" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="27" value="" style="html=1;" parent="1" vertex="1">
+                    <mxGeometry x="720" y="304" width="87" height="91" as="geometry"/>
+                </mxCell>
+                <mxCell id="28" value="ID_card_n4" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;strokeColor=#001DBC;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="726" y="327" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="29" value="User4" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="742" y="307" width="42" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="30" value="username4" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="726" y="361" width="75" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="31" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="381" y="410" as="sourcePoint"/>
+                        <mxPoint x="458" y="451" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="32" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="381" y="435" as="sourcePoint"/>
+                        <mxPoint x="458" y="476" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="33" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="381" y="467" as="sourcePoint"/>
+                        <mxPoint x="458" y="526" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="34" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="381" y="496" as="sourcePoint"/>
+                        <mxPoint x="458" y="451" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="35" value="" style="endArrow=classic;html=1;entryX=0.008;entryY=0.449;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" target="15" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="491" y="450" as="sourcePoint"/>
+                        <mxPoint x="578" y="354" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="37" value="" style="endArrow=classic;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="668" y="357" as="sourcePoint"/>
+                        <mxPoint x="719" y="357" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="38" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" target="19" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="491" y="476.0000000000002" as="sourcePoint"/>
+                        <mxPoint x="578" y="380" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="39" value="" style="endArrow=classic;html=1;entryX=-0.01;entryY=0.559;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" target="23" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="491" y="525.6600000000002" as="sourcePoint"/>
+                        <mxPoint x="580.5" y="525.16" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-39" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="1822" y="166" width="451" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-40" value="&lt;font style=&quot;font-size: 28px&quot;&gt;300&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1854" y="166" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-41" value="&lt;font style=&quot;font-size: 28px&quot;&gt;700&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2001" y="166" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-44" value="&lt;font style=&quot;font-size: 28px&quot;&gt;id&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1834" y="120" width="32" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-50" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-46" target="Po-K-Lq0thHuBN2wwm5y-49" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-46" value="&lt;font style=&quot;font-size: 24px&quot;&gt;100&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="1799" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-49" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D1&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1799" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-52" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-47" target="Po-K-Lq0thHuBN2wwm5y-51" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-47" value="&lt;font style=&quot;font-size: 24px&quot;&gt;200&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="1893" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-51" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D2&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a20025;strokeColor=#6F0000;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1893" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-59" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-60" target="Po-K-Lq0thHuBN2wwm5y-61" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-60" value="&lt;font style=&quot;font-size: 24px&quot;&gt;300&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2093" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-61" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D3&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2093" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-62" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-63" target="Po-K-Lq0thHuBN2wwm5y-64" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-63" value="&lt;font style=&quot;font-size: 24px&quot;&gt;400&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2187" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-65" value="&lt;font style=&quot;font-size: 24px&quot;&gt;600&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2281" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-64" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D4&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a20025;strokeColor=#6F0000;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2187" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-72" value="&lt;font style=&quot;font-size: 24px&quot;&gt;D5&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2281" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-70" value="" style="endArrow=classic;html=1;exitX=0.036;exitY=0.973;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-39" target="Po-K-Lq0thHuBN2wwm5y-46" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="1819" y="324" as="sourcePoint"/>
+                        <mxPoint x="1869" y="274" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-71" value="" style="endArrow=classic;html=1;exitX=0.036;exitY=0.973;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" target="Po-K-Lq0thHuBN2wwm5y-60" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="1986.996" y="226.0000000000001" as="sourcePoint"/>
+                        <mxPoint x="1994.76" y="346.62" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-74" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2499" y="166" width="451" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-75" value="&lt;font style=&quot;font-size: 28px&quot;&gt;3&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2531" y="166" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-76" value="&lt;font style=&quot;font-size: 28px&quot;&gt;7&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2678" y="166" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-77" value="&lt;font style=&quot;font-size: 28px&quot;&gt;k&lt;br&gt;&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2515" y="120" width="24" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-78" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-79" target="Po-K-Lq0thHuBN2wwm5y-80" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-79" value="&lt;font style=&quot;font-size: 24px&quot;&gt;1&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2476" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-80" value="&lt;font style=&quot;font-size: 24px&quot;&gt;100&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2476" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-81" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-82" target="Po-K-Lq0thHuBN2wwm5y-83" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-82" value="&lt;font style=&quot;font-size: 24px&quot;&gt;2&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2570" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-83" value="&lt;font style=&quot;font-size: 24px&quot;&gt;200&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a20025;strokeColor=#6F0000;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2570" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-84" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-85" target="Po-K-Lq0thHuBN2wwm5y-86" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-85" value="&lt;font style=&quot;font-size: 24px&quot;&gt;3&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2770" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-86" value="&lt;font style=&quot;font-size: 24px&quot;&gt;300&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2770" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-87" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-88" target="Po-K-Lq0thHuBN2wwm5y-90" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-88" value="&lt;font style=&quot;font-size: 24px&quot;&gt;4&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2864" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-89" value="&lt;font style=&quot;font-size: 24px&quot;&gt;6&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2958" y="345" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-90" value="&lt;font style=&quot;font-size: 24px&quot;&gt;400&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#a20025;strokeColor=#6F0000;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2864" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-91" value="&lt;font style=&quot;font-size: 24px&quot;&gt;600&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2958" y="385" width="94" height="40" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-92" value="" style="endArrow=classic;html=1;exitX=0.036;exitY=0.973;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-74" target="Po-K-Lq0thHuBN2wwm5y-79" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2496" y="324" as="sourcePoint"/>
+                        <mxPoint x="2546" y="274" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-93" value="" style="endArrow=classic;html=1;exitX=0.036;exitY=0.973;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" target="Po-K-Lq0thHuBN2wwm5y-85" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2663.996" y="226" as="sourcePoint"/>
+                        <mxPoint x="2671.76" y="346.62" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-94" value="&lt;font style=&quot;font-size: 31px&quot;&gt;id&lt;/font&gt;" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="1826" y="82" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-95" value="&lt;span style=&quot;font-size: 31px&quot;&gt;k&lt;/span&gt;" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;" parent="1" vertex="1">
+                    <mxGeometry x="2492" y="82" width="120" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-98" value="" style="endArrow=classic;startArrow=classic;html=1;entryX=0;entryY=0;entryDx=0;entryDy=0;exitX=1;exitY=0;exitDx=0;exitDy=0;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-51" target="Po-K-Lq0thHuBN2wwm5y-61" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="1987" y="391" as="sourcePoint"/>
+                        <mxPoint x="2037" y="341" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-99" value="" style="endArrow=classic;startArrow=classic;html=1;entryX=0;entryY=0;entryDx=0;entryDy=0;exitX=1;exitY=0;exitDx=0;exitDy=0;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2664.0000000000005" y="386.57" as="sourcePoint"/>
+                        <mxPoint x="2770" y="386.57" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-100" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1634" y="-872" width="656" height="316" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-101" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1677" y="-766" width="294" height="159" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-102" value="&lt;font style=&quot;font-size: 32px&quot;&gt;Buffer Pool&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1914" y="-858" width="168" height="24" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-103" value="&lt;font style=&quot;font-size: 23px&quot;&gt;add(id2,k2) to page2&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1691.5" y="-697" width="272" height="59" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-113" value="&lt;font style=&quot;font-size: 23px&quot;&gt;add(id1,k1) to page1&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2416" y="-792" width="272" height="59" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-104" value="&lt;font style=&quot;font-size: 28px&quot;&gt;change buffer&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1737" y="-744" width="181" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-105" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;strokeColor=#B09500;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1984" y="-814" width="257" height="136" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-106" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(a,b)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1984" y="-751" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-109" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(a,b)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2170" y="-751" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-107" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(id1,k1)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2055" y="-751" width="117" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-111" value="&lt;font color=&quot;#ff3333&quot; style=&quot;font-size: 23px&quot;&gt;1&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2121" y="-839" width="22" height="21" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-112" value="&lt;font color=&quot;#ff3333&quot;&gt;&lt;span style=&quot;font-size: 23px&quot;&gt;2&lt;/span&gt;&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1885" y="-800" width="22" height="21" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-115" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2381" y="-864" width="342" height="284" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-116" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6d8764;strokeColor=#3A5431;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2045" y="-506" width="385" height="181" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-117" value="&lt;font style=&quot;font-size: 26px&quot;&gt;Page1&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2070" y="-796" width="85" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-118" value="&lt;font style=&quot;font-size: 26px&quot;&gt;new change buffer item&lt;br&gt;add (id2,k2) to Page2&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2400" y="-707" width="299" height="107" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-119" value="&lt;font style=&quot;font-size: 25px&quot;&gt;redo log(ib-log-fileX)&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2434.5" y="-844" width="235" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-120" value="&lt;font color=&quot;#ff3333&quot; style=&quot;font-size: 23px&quot;&gt;3&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2400" y="-826" width="22" height="21" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-121" value="&lt;font color=&quot;#ff3333&quot; style=&quot;font-size: 23px&quot;&gt;4&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2389" y="-733" width="22" height="21" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-122" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2057.5" y="-451.25" width="365.5" height="116" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-123" value="&lt;font style=&quot;font-size: 27px&quot;&gt;data(t.ibd)&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2160" y="-490.25" width="132" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-124" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;strokeColor=#B09500;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2057.5" y="-452" width="142.5" height="111.75" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-129" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;strokeColor=#B09500;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2279" y="-452" width="142.5" height="111.75" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-127" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(a,b)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2057.5" y="-398.25" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-128" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(c,d)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2128.5" y="-398.25" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-131" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(e,f)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2279.25" y="-398.25" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-132" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(g,h)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2350.25" y="-398.25" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-133" value="&lt;font style=&quot;font-size: 26px&quot;&gt;Page1&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2087" y="-440" width="85" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-134" value="&lt;font style=&quot;font-size: 26px&quot;&gt;Page2&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2311" y="-440" width="85" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-138" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1640" y="-487" width="342" height="284" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-140" value="&lt;font style=&quot;font-size: 27px&quot;&gt;system table space(ibdata1)&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1634.5" y="-447" width="348" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-143" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1661" y="-384.37" width="294" height="159" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-144" value="&lt;font style=&quot;font-size: 23px&quot;&gt;add(id2,k2) to page2&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1675.5" y="-315.37" width="272" height="59" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-146" value="" style="shape=flexArrow;endArrow=classic;html=1;entryX=0.158;entryY=-0.06;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-107" target="Po-K-Lq0thHuBN2wwm5y-122" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2128.5" y="-516" as="sourcePoint"/>
+                        <mxPoint x="2178.5" y="-566" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-147" value="" style="shape=flexArrow;endArrow=classic;html=1;entryX=0.539;entryY=0.091;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" target="Po-K-Lq0thHuBN2wwm5y-144" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="1819.899543069599" y="-637.9999999999998" as="sourcePoint"/>
+                        <mxPoint x="1821.9989999999998" y="-418.2099999999998" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-148" value="&lt;font style=&quot;font-size: 27px&quot;&gt;change buffer&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1724" y="-350" width="175" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-149" value="" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1653" y="-1705" width="753" height="316" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-150" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1691" y="-1617" width="294" height="159" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-151" value="&lt;font style=&quot;font-size: 32px&quot;&gt;Buffer Pool&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1739" y="-1671" width="168" height="24" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-152" value="&lt;font style=&quot;font-size: 23px&quot;&gt;add(id2,k2) to Page2&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1705.5" y="-1548" width="272" height="59" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-154" value="&lt;font style=&quot;font-size: 28px&quot;&gt;change buffer&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1751" y="-1595" width="181" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-155" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;strokeColor=#B09500;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2087" y="-1659.5" width="257" height="112" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-156" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(a,b)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2087" y="-1614.5" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-157" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(a,b)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2273" y="-1614.5" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-158" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(id1,k1)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2158" y="-1614.5" width="117" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-162" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#6d8764;strokeColor=#3A5431;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1657.5" y="-1254" width="385" height="181" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-163" value="&lt;font style=&quot;font-size: 26px&quot;&gt;Page1&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2173" y="-1659.5" width="85" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-168" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=none;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1670" y="-1199.25" width="365.5" height="116" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-169" value="&lt;font style=&quot;font-size: 27px&quot;&gt;data(t.ibd)&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1772.5" y="-1238.25" width="132" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-170" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;strokeColor=#B09500;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1670" y="-1200" width="142.5" height="111.75" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-171" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;strokeColor=#B09500;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1891.5" y="-1200" width="142.5" height="111.75" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-172" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(a,b)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1670" y="-1146.25" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-173" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(c,d)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1741" y="-1146.25" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-174" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(e,f)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1891.75" y="-1146.25" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-175" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(g,h)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="1962.75" y="-1146.25" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-176" value="&lt;font style=&quot;font-size: 26px&quot;&gt;Page1&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1699.5" y="-1188" width="85" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-177" value="&lt;font style=&quot;font-size: 26px&quot;&gt;Page2&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="1923.5" y="-1188" width="85" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-185" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e3c800;strokeColor=#B09500;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2087" y="-1520.5" width="257" height="105" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-186" value="&lt;font style=&quot;font-size: 26px&quot;&gt;Page2&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="2174" y="-1513.5" width="85" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-187" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(e,f)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2088" y="-1475.5" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-188" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(ide,k2)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2159" y="-1475.5" width="117" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-189" value="&lt;font style=&quot;font-size: 25px&quot;&gt;(g,h)&lt;/font&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#1ba1e2;strokeColor=#006EAF;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2274" y="-1475.5" width="71" height="58" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-190" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" parent="1" target="Po-K-Lq0thHuBN2wwm5y-187" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="1977.5" y="-1512.5" as="sourcePoint"/>
+                        <mxPoint x="2027.5" y="-1562.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-191" value="" style="endArrow=classic;html=1;entryX=0;entryY=0.25;entryDx=0;entryDy=0;exitX=0.75;exitY=0;exitDx=0;exitDy=0;" parent="1" source="Po-K-Lq0thHuBN2wwm5y-168" target="Po-K-Lq0thHuBN2wwm5y-187" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="1921" y="-1336" as="sourcePoint"/>
+                        <mxPoint x="2031.5" y="-1284.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-192" value="&lt;font style=&quot;font-size: 26px&quot;&gt;read Page1&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0a30a;strokeColor=#BD7000;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2509" y="-1636.5" width="197" height="77" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-193" value="&lt;font style=&quot;font-size: 26px&quot;&gt;read Page2&lt;/font&gt;" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f0a30a;strokeColor=#BD7000;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="2523" y="-1501" width="195" height="77" as="geometry"/>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-195" value="" style="shape=flexArrow;endArrow=classic;html=1;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endWidth=11.666666666666668;endSize=5.333333333333334;" parent="1" target="Po-K-Lq0thHuBN2wwm5y-192" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2329" y="-1579" as="sourcePoint"/>
+                        <mxPoint x="2396" y="-1619" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="Po-K-Lq0thHuBN2wwm5y-196" value="" style="shape=flexArrow;endArrow=classic;html=1;entryX=0;entryY=0.75;entryDx=0;entryDy=0;endWidth=16.666666666666668;endSize=2.45;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2345" y="-1456" as="sourcePoint"/>
+                        <mxPoint x="2521.9999999999995" y="-1455.75" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="42" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="40" target="41" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="40" value="当前值 4" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="555" y="-1531" width="77" height="40.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="44" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="41" target="43" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="41" value="将 4 改成 3" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="440.5" y="-1531" width="77" height="40.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="46" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="43" target="45" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="43" value="将 3 改成 2" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="329" y="-1531" width="77" height="40.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="45" value="将 2 改成 1" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#008a00;strokeColor=#005700;fontColor=#ffffff;" parent="1" vertex="1">
+                    <mxGeometry x="217" y="-1531" width="77" height="40.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="48" value="" style="endArrow=none;dashed=1;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="590" y="-1531" as="sourcePoint"/>
+                        <mxPoint x="590" y="-1592" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="49" value="read-view C" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="556.5" y="-1612.5" width="74" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="50" value="" style="endArrow=none;dashed=1;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="365.5" y="-1531" as="sourcePoint"/>
+                        <mxPoint x="365.5" y="-1592" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="51" value="read-view B" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="333" y="-1612.5" width="73" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="52" value="read-view A" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;" parent="1" vertex="1">
+                    <mxGeometry x="221" y="-1612.5" width="73" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="53" value="" style="endArrow=none;dashed=1;html=1;" parent="1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="253.5" y="-1531" as="sourcePoint"/>
+                        <mxPoint x="253.5" y="-1592" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="54" value="回滚段" style="shape=crossbar;whiteSpace=wrap;html=1;rounded=1;" parent="1" vertex="1">
+                    <mxGeometry x="203" y="-1472.5" width="342" height="20" as="geometry"/>
+                </mxCell>
+            </root>
+        </mxGraphModel>
+    </diagram>
+    <diagram id="Hdih7j13q_l6n9fzhxRn" name="消息队列">
+        &#xa;        &#xa;&#xa;
+        <mxGraphModel dx="908" dy="343" grid="0" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1600" pageHeight="900" math="0" shadow="0">
+            &#xa;            &#xa;&#xa;
+            <root>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-0"/>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-1" parent="HC5wms0ULaFJogc1axKs-0"/>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-8" style="rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-2" target="HC5wms0ULaFJogc1axKs-3" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="266.2244001116071" y="146.36530848911832" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-10" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-2" target="HC5wms0ULaFJogc1axKs-9" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-2" value="订单模块" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="153" y="154" width="71" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-16" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-9" target="HC5wms0ULaFJogc1axKs-15" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-9" value="发送订单创建的消息" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="274" y="199" width="111" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-18" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-15" target="HC5wms0ULaFJogc1axKs-17" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-15" value="MQ" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;strokeColor=#001DBC;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="407" y="199" width="99" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-20" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-17" target="HC5wms0ULaFJogc1axKs-19" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-17" value="购物车模块" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#0050ef;strokeColor=#001DBC;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="539" y="199" width="99" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-22" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-19" target="HC5wms0ULaFJogc1axKs-21" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-19" value="清理购物车" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="671" y="199" width="99" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-21" value="购物车DB" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#647687;strokeColor=#314354;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="807" y="199" width="99" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-14" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-3" target="HC5wms0ULaFJogc1axKs-13" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-3" value="创建订单" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="274" y="106" width="110" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-13" value="订单DB" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#647687;strokeColor=#314354;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="407" y="106" width="96" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-25" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-23" target="HC5wms0ULaFJogc1axKs-24" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-27" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-23" target="HC5wms0ULaFJogc1axKs-26" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-28" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-23" target="HC5wms0ULaFJogc1axKs-26" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-30" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-23" target="HC5wms0ULaFJogc1axKs-26" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-32" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-23" target="HC5wms0ULaFJogc1axKs-31" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-34" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-23" target="HC5wms0ULaFJogc1axKs-33" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-23" value="订单模块" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="66" y="371" width="96" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-36" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-33" target="HC5wms0ULaFJogc1axKs-35" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-33" value="3.执行本地事务，创建订单" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="178" y="262" width="146" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-35" value="订单DB" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#647687;strokeColor=#314354;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="362" y="262" width="96" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-40" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-31" target="HC5wms0ULaFJogc1axKs-37" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="385" y="393" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-31" value="4.提交或事务回滚" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="214" y="416" width="98" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-39" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-26" target="HC5wms0ULaFJogc1axKs-37" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-26" value="2.发送半消息" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="214" y="371" width="98" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-38" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-24" target="HC5wms0ULaFJogc1axKs-37" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-24" value="1.开启事务" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="214" y="322" width="98" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-42" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-37" target="HC5wms0ULaFJogc1axKs-41" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-37" value="MQ" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="390" y="371" width="98" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-44" value="" style="edgeStyle=none;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="HC5wms0ULaFJogc1axKs-1" source="HC5wms0ULaFJogc1axKs-41" target="HC5wms0ULaFJogc1axKs-43" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-41" value="5.投递消息" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#76608a;strokeColor=#432D57;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="527" y="371" width="98" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="HC5wms0ULaFJogc1axKs-43" value="购物车模块" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#60a917;strokeColor=#2D7600;fontColor=#ffffff;" parent="HC5wms0ULaFJogc1axKs-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="658" y="371" width="98" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+            </root>
+            &#xa;            &#xa;&#xa;
+        </mxGraphModel>
+        &#xa;        &#xa;&#xa;
+    </diagram>
+    <diagram id="9dOi3j1dlMAW7eY_ZneJ" name="Redis">
+        <mxGraphModel dx="1089" dy="441" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1600" pageHeight="900" background="#F4F1DE" math="0" shadow="0">
+            <root>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-0"/>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-1" parent="5GfIY6wJ8j-UMNQF0Tqn-0"/>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-2" value="client1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="81" y="175" width="52" height="52" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-3" value="client2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="81" y="251" width="52" height="52" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-4" value="worker 单线程" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="494" y="110" width="743" height="45" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-6" value="CPU核心" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1173" y="110" width="120" height="71" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-9" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="5GfIY6wJ8j-UMNQF0Tqn-7" target="5GfIY6wJ8j-UMNQF0Tqn-8" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-7" value="c2 read IO" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="494" y="169" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-11" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="5GfIY6wJ8j-UMNQF0Tqn-8" target="5GfIY6wJ8j-UMNQF0Tqn-10" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-8" value="计算2" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="609" y="169" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-13" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="5GfIY6wJ8j-UMNQF0Tqn-10" target="5GfIY6wJ8j-UMNQF0Tqn-12" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-10" value="c2 write IO" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="722.5" y="169" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-15" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="5GfIY6wJ8j-UMNQF0Tqn-12" target="5GfIY6wJ8j-UMNQF0Tqn-14" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-12" value="c1 read IO" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="834" y="169" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-17" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="5GfIY6wJ8j-UMNQF0Tqn-14" target="5GfIY6wJ8j-UMNQF0Tqn-16" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-14" value="计算1" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="949" y="169" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-16" value="c1 write IO" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1061" y="169" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-18" value="客户端并发请求" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="60" y="121" width="94" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-19" value="client1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="83" y="419" width="52" height="52" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-20" value="client2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="83" y="495" width="52" height="52" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-21" value="客户端并发请求" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="62" y="373" width="94" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-22" value="worker 单线程（保证 Redis 操作的串行、原子）" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="488" y="396" width="743" height="45" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-23" value="CPU核心" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1167" y="396" width="120" height="71" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-92" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="5GfIY6wJ8j-UMNQF0Tqn-27" target="5GfIY6wJ8j-UMNQF0Tqn-33" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-27" value="计算2" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="638" y="455" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-33" value="计算1" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="747" y="455" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-61" value="IO 线程" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="488" y="568" width="743" height="45" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-62" value="CPU核心" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1167" y="568" width="120" height="71" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-100" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="5GfIY6wJ8j-UMNQF0Tqn-64" target="5GfIY6wJ8j-UMNQF0Tqn-99" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-64" value="c2 read IO" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="549" y="623" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-99" value="c2 write IO" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="747" y="623" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-74" value="IO 线程" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="488" y="721" width="743" height="45" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-75" value="CPU核心" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1167" y="721" width="120" height="71" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-98" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="5GfIY6wJ8j-UMNQF0Tqn-83" target="5GfIY6wJ8j-UMNQF0Tqn-97" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-83" value="c1 read IO" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="549" y="778" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-97" value="c1 write IO" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="836" y="778" width="89" height="43" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-91" value="IO 多线程并行" style="shape=partialRectangle;whiteSpace=wrap;html=1;bottom=1;right=1;left=1;top=0;fillColor=none;routingCenterX=-0.5;direction=south;rounded=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="428" y="605" width="60" height="120" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-101" value="&lt;b&gt;6 个时间片&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="338" y="185" width="68" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-103" value="&lt;b&gt;4 个时间片&lt;/b&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="340" y="473" width="68" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-104" value="无法保证 1、2 计算顺序，但TCP请求能保证一个IO连接里的顺序" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontStyle=3;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="112" y="71" width="354" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-105" value="" style="endArrow=none;dashed=1;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="81" y="330" as="sourcePoint"/>
+                        <mxPoint x="1352" y="330" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-106" value="传统的 redis 工作流程" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="722.5" y="297" width="127" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="5GfIY6wJ8j-UMNQF0Tqn-107" value="IO 多线程的 redis 工作流程" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="712.5" y="339" width="154" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-0" value="&lt;br&gt;&lt;br&gt;&lt;br&gt;..." style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1993.5" y="361" width="87.5" height="191" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-1" value="socket" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=18;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2006.25" y="326" width="62" height="26" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-2" value="s1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2019.75" y="369" width="35" height="35" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-3" value="s2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2019.75" y="414" width="35" height="35" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-4" value="sN" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2019.75" y="509" width="35" height="35" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-7" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;fontSize=18;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="20GCJt_ftviHmJzq0dEa-5" target="20GCJt_ftviHmJzq0dEa-6" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-5" value="I/0多路复用程序" style="whiteSpace=wrap;html=1;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2145" y="435" width="144" height="34" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-6" value="文件事件分派器" style="whiteSpace=wrap;html=1;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2319" y="435" width="144" height="34" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-8" value="" style="endArrow=classic;html=1;fontSize=18;entryX=0;entryY=0;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="20GCJt_ftviHmJzq0dEa-5" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2054.75" y="389" as="sourcePoint"/>
+                        <mxPoint x="2104.75" y="339" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-9" value="" style="endArrow=classic;html=1;fontSize=18;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="20GCJt_ftviHmJzq0dEa-5" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2054.75" y="431" as="sourcePoint"/>
+                        <mxPoint x="2145" y="477" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-10" value="" style="endArrow=classic;html=1;fontSize=18;entryX=0;entryY=0.75;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="20GCJt_ftviHmJzq0dEa-5" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2054.75" y="526" as="sourcePoint"/>
+                        <mxPoint x="2145" y="547" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-11" value="&lt;br&gt;&lt;br&gt;&lt;br&gt;..." style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2574" y="361" width="135" height="191" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-13" value="事件处理器" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=18;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2586.75" y="319" width="100" height="26" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-14" value="命令请求处理器" style="whiteSpace=wrap;html=1;fontSize=16;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2581.5" y="381" width="120" height="41" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-15" value="命令回复处理器" style="whiteSpace=wrap;html=1;fontSize=16;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2581.5" y="426" width="120" height="41" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-16" value="连接应答处理器" style="whiteSpace=wrap;html=1;fontSize=16;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2581.5" y="491" width="120" height="41" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-17" value="" style="endArrow=classic;html=1;fontSize=18;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="20GCJt_ftviHmJzq0dEa-14" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2463" y="435" as="sourcePoint"/>
+                        <mxPoint x="2553.2500000000005" y="481" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-18" value="" style="endArrow=classic;html=1;fontSize=18;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="20GCJt_ftviHmJzq0dEa-15" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2463" y="449" as="sourcePoint"/>
+                        <mxPoint x="2581.5" y="415.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-19" value="" style="endArrow=classic;html=1;fontSize=18;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="20GCJt_ftviHmJzq0dEa-16" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2463" y="467" as="sourcePoint"/>
+                        <mxPoint x="2581.5" y="464.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-20" value="" style="whiteSpace=wrap;html=1;fontSize=16;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2153" y="649" width="221" height="59" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-21" value="队列" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=16;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2242.5" y="616" width="42" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-22" value="s2" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2319" y="660.5" width="35" height="35" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-25" value="文件事件分派器" style="whiteSpace=wrap;html=1;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2526" y="661.5" width="144" height="34" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-26" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.6;dx=40;notch=15;fontSize=16;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2380" y="664" width="132" height="28" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-27" value="传送" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=16;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2396" y="635" width="42" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-28" value="s1" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2438" y="629" width="35" height="35" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-31" value="s3" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2266" y="660.5" width="35" height="35" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-32" value="..." style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=16;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2230" y="664" width="23" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="20GCJt_ftviHmJzq0dEa-33" value="sN" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fontSize=18;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2173" y="661" width="35" height="35" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-1" value="" style="shape=umlLifeline;participant=umlActor;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;verticalAlign=top;spacingTop=36;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2278" y="962" width="20" height="341" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-2" value="Redis Client" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2238" y="931" width="102" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-5" value="" style="shape=umlLifeline;participant=umlActor;perimeter=lifelinePerimeter;whiteSpace=wrap;html=1;container=1;collapsible=0;recursiveResize=0;verticalAlign=top;spacingTop=36;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2686.75" y="962" width="20" height="347" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-6" value="Redis Server" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2642.75" y="931" width="108" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-9" value="" style="endArrow=classic;html=1;fontSize=17;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2289" y="1030" as="sourcePoint"/>
+                        <mxPoint x="2696.2499999999995" y="1030" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-10" value="c向s发送连接请求" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2428" y="1002" width="146" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-11" value="s 将 &lt;font color=&quot;#a9c4eb&quot;&gt;AE_READABLE&lt;/font&gt; 事件映射到 &lt;font color=&quot;#ff3333&quot;&gt;连接应答处理器&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2304.75" y="1034" width="381" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-12" value="" style="endArrow=classic;html=1;fontSize=17;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="1H8fmVod1uAOQdD5P4jq-5" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2288.9999999999995" y="1149" as="sourcePoint"/>
+                        <mxPoint x="2610.5" y="1149" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-13" value="c向s发送命令请求" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2426" y="1121" width="146" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-14" value="s将 &lt;font color=&quot;#a9c4eb&quot;&gt;AE_READABLE&lt;/font&gt; 事件映射到 &lt;font color=&quot;#ff3333&quot;&gt;命令请求处理器&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2310.5" y="1155" width="377" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-15" value="" style="endArrow=classic;html=1;fontSize=17;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2696.2499999999995" y="1280" as="sourcePoint"/>
+                        <mxPoint x="2288.9999999999995" y="1280" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-16" value="s向c发送命令响应" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2433" y="1282" width="146" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="1H8fmVod1uAOQdD5P4jq-18" value="s将 &lt;font color=&quot;#a9c4eb&quot;&gt;AE_WRITABLE&lt;/font&gt; 事件映射到 &lt;font color=&quot;#ff3333&quot;&gt;命令回复处理器&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2318" y="1252" width="371" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-0" value="Redis Server" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3439" y="473" width="85" height="37" as="geometry"/>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-2" value="&lt;span&gt;Redis Client A&lt;/span&gt;" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3279" y="562" width="82" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-3" value="&lt;span&gt;Redis Client B&lt;/span&gt;" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3439" y="562" width="82" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-5" value="&lt;span&gt;Redis Client C&lt;/span&gt;" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3606" y="562" width="82" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-7" value="" style="endArrow=none;dashed=1;html=1;entryX=-0.006;entryY=0.892;entryDx=0;entryDy=0;entryPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="dhmE1e_itSu7pFT04USm-0" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3316" y="562" as="sourcePoint"/>
+                        <mxPoint x="3366" y="512" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-8" value="" style="endArrow=none;dashed=1;html=1;entryX=-0.006;entryY=0.892;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.554;exitY=0.001;exitDx=0;exitDy=0;exitPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="dhmE1e_itSu7pFT04USm-3" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3361" y="566" as="sourcePoint"/>
+                        <mxPoint x="3483.4900000000002" y="510.004" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-9" value="" style="endArrow=none;dashed=1;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=1;exitY=1;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="dhmE1e_itSu7pFT04USm-0" target="dhmE1e_itSu7pFT04USm-5" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3533" y="549" as="sourcePoint"/>
+                        <mxPoint x="3655.4900000000002" y="493.004" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-10" value="等待命令请求" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="dhmE1e_itSu7pFT04USm-9" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2705" y="2" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-11" value="等待命令请求" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="dhmE1e_itSu7pFT04USm-9" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2705" y="2" relative="1" as="geometry">
+                        <mxPoint x="-86.52" y="-0.07000000000000028" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-12" value="等待命令请求" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="dhmE1e_itSu7pFT04USm-9" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2705" y="2" relative="1" as="geometry">
+                        <mxPoint x="-183.51999999999998" y="-0.07000000000000028" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-14" value="返回命令执行结果" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="dhmE1e_itSu7pFT04USm-9" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2705" y="2" relative="1" as="geometry">
+                        <mxPoint x="-211.51999999999998" y="-19.069999999999993" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="dhmE1e_itSu7pFT04USm-13" value="" style="endArrow=classic;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3439" y="506" as="sourcePoint"/>
+                        <mxPoint x="3318" y="562" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-2" value="时间轴" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2297.75" y="1465" width="46" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-4" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;direction=south;rounded=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2476" y="1456" width="8" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-5" value="t1" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2470" y="1435" width="20" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-9" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2374" y="1562" as="targetPoint"/>
+                        <mxPoint x="2374" y="1450.5" as="sourcePoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-10" value="" style="endArrow=none;dashed=1;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2374" y="1475" as="sourcePoint"/>
+                        <mxPoint x="2617" y="1475" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-12" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2617" y="1560" as="targetPoint"/>
+                        <mxPoint x="2617" y="1450.5" as="sourcePoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-13" value="File Event X" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2375" y="1483" width="153" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-16" value="Time Event Y" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2528" y="1509" width="88" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-14" value="start" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2359" y="1430" width="33" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-15" value="end" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2603" y="1430" width="30" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-18" value="时间轴" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2301" y="1608" width="46" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-37" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="F8GMp22I0Av0FmL0bdyk-19" target="F8GMp22I0Av0FmL0bdyk-36" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-19" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;direction=south;rounded=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2462" y="1616" width="8" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-36" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;direction=south;rounded=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2550" y="1616" width="8" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-20" value="10ms" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2402" y="1606" width="39" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-39" value="10ms" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2489" y="1606" width="39" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-21" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2374" y="1722" as="targetPoint"/>
+                        <mxPoint x="2374" y="1610.5" as="sourcePoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-22" value="" style="endArrow=none;dashed=1;html=1;entryX=1.013;entryY=0.744;entryDx=0;entryDy=0;entryPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2374" y="1635" as="sourcePoint"/>
+                        <mxPoint x="2640.048" y="1635.2469999999998" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-23" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2642.75" y="1720.5" as="targetPoint"/>
+                        <mxPoint x="2642.75" y="1611" as="sourcePoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-24" value="FE1" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2375" y="1643" width="37" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-32" value="FE2" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2412" y="1643" width="55" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-26" value="start" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2359" y="1590" width="33" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-27" value="end" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2626.5" y="1590" width="30" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-38" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2383" y="1625.2599999999998" as="sourcePoint"/>
+                        <mxPoint x="2463" y="1625.2599999999998" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-43" value="sC1" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2467" y="1665" width="37" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-44" value="FE3" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2504" y="1665" width="53" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-45" value="sC2" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2557" y="1687" width="35" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-46" value="FE4" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2592" y="1687" width="50" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-70" value="10ms" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2579" y="1606" width="39" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-71" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2558" y="1626.01" as="sourcePoint"/>
+                        <mxPoint x="2638" y="1626.01" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="2615" y="1626.51"/>
+                            <mxPoint x="2615" y="1626.51"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-72" value="时间轴" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2718" y="1644" width="46" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-73" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="F8GMp22I0Av0FmL0bdyk-74" target="F8GMp22I0Av0FmL0bdyk-75" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-74" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;direction=south;rounded=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2879" y="1652" width="8" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-95" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="F8GMp22I0Av0FmL0bdyk-75" target="F8GMp22I0Av0FmL0bdyk-94" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-75" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;direction=south;rounded=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2967" y="1652" width="8" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-97" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="F8GMp22I0Av0FmL0bdyk-94" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="3143" y="1661.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-94" value="" style="line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;direction=south;rounded=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3055" y="1652" width="8" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-76" value="10ms" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2819" y="1642" width="39" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-77" value="10ms" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2906" y="1642" width="39" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-78" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2791" y="1758" as="targetPoint"/>
+                        <mxPoint x="2791" y="1646.5" as="sourcePoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-79" value="" style="endArrow=none;dashed=1;html=1;entryX=1.013;entryY=0.744;entryDx=0;entryDy=0;entryPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="2791" y="1671" as="sourcePoint"/>
+                        <mxPoint x="3057.048" y="1671.2469999999998" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-80" value="" style="edgeStyle=orthogonalEdgeStyle;html=1;verticalAlign=bottom;endArrow=open;endSize=8;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="3146.8" y="1753.5" as="targetPoint"/>
+                        <mxPoint x="3146.8" y="1644" as="sourcePoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-81" value="FE1" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2792" y="1679" width="67.5" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-82" value="FE2" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2859.5" y="1679" width="71" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-83" value="start" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2776" y="1626" width="33" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-84" value="end" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3132" y="1626" width="30" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-85" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="2800" y="1661.2599999999998" as="sourcePoint"/>
+                        <mxPoint x="2880" y="1661.2599999999998" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-86" value="sC1" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2930.5" y="1701" width="29" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-87" value="FE3" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2959.5" y="1701" width="44" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-88" value="sC2" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3075.5" y="1723" width="35" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-89" value="FE4" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3003.5" y="1701" width="36" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-98" value="FE5" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3039.5" y="1701" width="36" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-90" value="10ms" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2996" y="1642" width="39" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="F8GMp22I0Av0FmL0bdyk-100" value="10ms" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3084.75" y="1642" width="39" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-7" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1679" y="1848.75" width="78.25" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-8" value="1. bgsave" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1680.5" y="1822" width="62" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-9" value="&lt;span style=&quot;color: rgb(255 , 255 , 255)&quot;&gt;Redis Server&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1779.5" y="1832.5" width="75.75" height="51" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-11" value="&lt;span style=&quot;color: rgb(255 , 255 , 255)&quot;&gt;Redis Server&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1779.5" y="1963.5" width="75.75" height="51" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-14" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;direction=south;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1790.88" y="1892" width="18.5" height="64.01" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-15" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;direction=north;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1824.5" y="1894" width="18.5" height="64.01" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-16" value="2. fork" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1739.5" y="1917" width="43" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-17" value="4.&amp;nbsp; bgsave successfully" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1847.5" y="1917" width="133" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-18" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1871.5" y="1979.75" width="78.25" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-19" value="3.&amp;nbsp; dump" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1882.13" y="1958.0100000000002" width="57" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-21" value="&lt;span&gt;RDB二进制文件&lt;/span&gt;" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=14;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1962.5" y="1968.5" width="88" height="41" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-22" value="&lt;span style=&quot;background-color: rgb(42 , 42 , 42)&quot;&gt;&lt;font color=&quot;#a9c4eb&quot;&gt;Redis Client&lt;/font&gt;&lt;/span&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1630.5" y="1816" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-23" value="覆盖旧文件" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2038.5" y="1945.5" width="70" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-37" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1674.5" y="2104.75" width="78.25" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-38" value="1. save" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1683" y="2078" width="49" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-39" value="&lt;span style=&quot;color: rgb(255 , 255 , 255)&quot;&gt;Redis Server&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1775" y="2088.5" width="75.75" height="51" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-45" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1871.5100000000002" y="2108" width="78.25" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-46" value="2.&amp;nbsp; dump" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1881" y="2078" width="57" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-47" value="&lt;span&gt;RDB二进制文件&lt;/span&gt;" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=14;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1966.5" y="2096" width="88" height="41" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-48" value="&lt;span style=&quot;background-color: rgb(42 , 42 , 42)&quot;&gt;&lt;font color=&quot;#a9c4eb&quot;&gt;Redis Client&lt;/font&gt;&lt;/span&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1626" y="2072" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-49" value="覆盖旧文件" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2054.5" y="2074" width="70" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="xMiHy4sk-qOpb-TtcmuA-50" value="阻塞" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1795.25" y="2060" width="34" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-2" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;fontSize=17;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="nXU8iusiy2Wrt838lKxV-0" target="nXU8iusiy2Wrt838lKxV-1" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-5" value="&lt;font style=&quot;font-size: 11px&quot;&gt;1&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=17;rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="nXU8iusiy2Wrt838lKxV-2" vertex="1" connectable="0">
+                    <mxGeometry x="-0.4355" y="1" relative="1" as="geometry">
+                        <mxPoint x="8.49" y="-7" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-0" value="bgrewriteaof" style="whiteSpace=wrap;html=1;fontSize=17;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="536" y="935" width="107" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-4" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;fontSize=17;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="nXU8iusiy2Wrt838lKxV-1" target="nXU8iusiy2Wrt838lKxV-3" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-6" value="&lt;font style=&quot;font-size: 11px&quot;&gt;2&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=17;rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="nXU8iusiy2Wrt838lKxV-4" vertex="1" connectable="0">
+                    <mxGeometry x="-0.1813" y="1" relative="1" as="geometry">
+                        <mxPoint x="11.5" y="-7.909999999999999" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-22" value="&lt;font style=&quot;font-size: 11px&quot;&gt;5-1 信号通知父进程&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontSize=17;rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="nXU8iusiy2Wrt838lKxV-4" vertex="1" connectable="0">
+                    <mxGeometry x="-0.1813" y="1" relative="1" as="geometry">
+                        <mxPoint x="150.48999999999998" y="-23.91" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-1" value="父进程" style="whiteSpace=wrap;html=1;fontSize=17;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="554.5" y="982" width="70" height="26" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-18" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;fontSize=13;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="nXU8iusiy2Wrt838lKxV-3" target="nXU8iusiy2Wrt838lKxV-17" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-3" value="fork" style="whiteSpace=wrap;html=1;fontSize=17;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="569.5" y="1040" width="40" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-40" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;fontSize=13;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="nXU8iusiy2Wrt838lKxV-17" target="nXU8iusiy2Wrt838lKxV-36" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-17" value="子进程" style="whiteSpace=wrap;html=1;fontSize=17;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="743" y="1040" width="60.5" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-7" value="aof_buf" style="whiteSpace=wrap;html=1;fontSize=17;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="503.5" y="1114" width="66" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-41" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;fontSize=13;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="nXU8iusiy2Wrt838lKxV-8" target="nXU8iusiy2Wrt838lKxV-36" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-8" value="aof_rewrite_buf" style="whiteSpace=wrap;html=1;fontSize=17;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="575" y="1114" width="121" height="25" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-10" value="" style="endArrow=classic;html=1;fontSize=17;entryX=0.5;entryY=0;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="nXU8iusiy2Wrt838lKxV-7" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="590" y="1065" as="sourcePoint"/>
+                        <mxPoint x="540" y="1115" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-11" value="" style="endArrow=classic;html=1;fontSize=17;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="587" y="1065" as="sourcePoint"/>
+                        <mxPoint x="635" y="1113" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-12" value="&lt;font style=&quot;font-size: 11px&quot;&gt;3-2&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="615" y="1069" width="26" height="26" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-13" value="&lt;font style=&quot;font-size: 11px&quot;&gt;3-1&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="538" y="1069" width="26" height="26" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-16" value="旧 AOF 文件" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;fontSize=13;size=17;align=left;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="494" y="1179" width="93.5" height="37" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-19" value="" style="endArrow=classic;html=1;fontSize=13;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="nXU8iusiy2Wrt838lKxV-17" target="nXU8iusiy2Wrt838lKxV-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="660" y="1032" as="sourcePoint"/>
+                        <mxPoint x="710" y="982" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-29" value="" style="endArrow=classic;html=1;fontSize=17;exitX=0.5;exitY=1;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="nXU8iusiy2Wrt838lKxV-7" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="583" y="1129" as="sourcePoint"/>
+                        <mxPoint x="537" y="1178" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-36" value="旧 AOF 文件" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;fontSize=13;size=17;align=left;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="726.5" y="1107.5" width="93.5" height="38" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-42" value="&lt;font style=&quot;font-size: 11px&quot;&gt;4&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="773" y="1069" width="16" height="26" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-45" value="" style="endArrow=classic;html=1;fontSize=13;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="nXU8iusiy2Wrt838lKxV-36" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="637.5" y="1153" as="sourcePoint"/>
+                        <mxPoint x="587.5" y="1203" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-46" value="&lt;font style=&quot;font-size: 11px&quot;&gt;5-3&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="648" y="1153.5" width="26" height="26" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-47" value="&lt;font style=&quot;font-size: 11px&quot;&gt;5-2&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=17;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="698" y="1100" width="26" height="26" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-48" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2253" y="1836.75" width="78.25" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-49" value="bgrewriteaof" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2253" y="1814.5" width="76" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-50" value="&lt;span style=&quot;color: rgb(255 , 255 , 255)&quot;&gt;Redis Master&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2349.5" y="1832.5" width="75.75" height="51" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-51" value="Redis 子进程" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2349.5" y="1963.5" width="75.75" height="51" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-52" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;direction=south;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2378.12" y="1889" width="18.5" height="64.01" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-54" value="fork" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2343.75" y="1915" width="30" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-56" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2441.5" y="1979.75" width="78.25" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-57" value="AOF重写" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2451.13" y="1958.0100000000002" width="59" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-58" value="&lt;span&gt;AOF文件&lt;/span&gt;" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=14;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2532.5" y="1968.5" width="66.5" height="41" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-59" value="&lt;span style=&quot;background-color: rgb(42 , 42 , 42)&quot;&gt;&lt;font color=&quot;#a9c4eb&quot;&gt;Redis Client&lt;/font&gt;&lt;/span&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2200.5" y="1816" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-61" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;direction=west;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2255" y="1857.5" width="78.25" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-62" value="OK" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2278" y="1880" width="27" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-63" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1702" y="2254.75" width="93.75" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-64" value="&lt;font style=&quot;font-size: 10px&quot;&gt;写命令刷新的缓冲区&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1702" y="2228" width="100" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-65" value="&lt;span style=&quot;color: rgb(255 , 255 , 255)&quot;&gt;缓冲&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1811.5" y="2238.5" width="75.75" height="51" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-66" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1908.01" y="2258" width="118.49" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-67" value="always" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1951.2500000000002" y="2228" width="47" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-68" value="&lt;span&gt;AOF文件&lt;/span&gt;" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=14;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2057" y="2246" width="88" height="41" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-69" value="&lt;span style=&quot;background-color: rgb(42 , 42 , 42)&quot;&gt;&lt;font color=&quot;#a9c4eb&quot;&gt;Redis Client&lt;/font&gt;&lt;/span&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1645.5" y="2216.5" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="nXU8iusiy2Wrt838lKxV-72" value="&lt;font style=&quot;font-size: 11px&quot;&gt;每条命令 fsync 到硬盘&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=13;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1915.75" y="2276.5" width="118" height="21" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-1" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1698.5" y="2402.25" width="93.75" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-2" value="&lt;font style=&quot;font-size: 10px&quot;&gt;写命令刷新的缓冲区&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1698.5" y="2375.5" width="100" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-3" value="&lt;span style=&quot;color: rgb(255 , 255 , 255)&quot;&gt;缓冲&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1808" y="2386" width="75.75" height="51" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-4" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1904.51" y="2405.5" width="118.49" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-5" value="everysec" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1928.0000000000002" y="2379" width="58" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-6" value="&lt;span&gt;AOF文件&lt;/span&gt;" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=14;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2053.5" y="2393.5" width="88" height="41" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-7" value="&lt;span style=&quot;background-color: rgb(42 , 42 , 42)&quot;&gt;&lt;font color=&quot;#a9c4eb&quot;&gt;Redis Client&lt;/font&gt;&lt;/span&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1642" y="2364" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-8" value="&lt;font style=&quot;font-size: 11px&quot;&gt;每秒把缓冲区 fsync 到硬盘&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=13;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1901.25" y="2424" width="141" height="21" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-9" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1693.5" y="2524.25" width="93.75" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-10" value="&lt;font style=&quot;font-size: 10px&quot;&gt;写命令刷新的缓冲区&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1693.5" y="2497.5" width="100" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-11" value="&lt;span style=&quot;color: rgb(255 , 255 , 255)&quot;&gt;缓冲&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1803" y="2508" width="75.75" height="51" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-12" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1899.51" y="2527.5" width="118.49" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-13" value="no" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1941.0000000000002" y="2501" width="23" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-14" value="&lt;span&gt;AOF文件&lt;/span&gt;" style="shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;darkOpacity=0.05;size=14;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="2048.5" y="2515.5" width="88" height="41" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-15" value="&lt;span style=&quot;background-color: rgb(42 , 42 , 42)&quot;&gt;&lt;font color=&quot;#a9c4eb&quot;&gt;Redis Client&lt;/font&gt;&lt;/span&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1637" y="2486" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="ah_elfWAzptGPmzf7ie0-16" value="&lt;font style=&quot;font-size: 11px&quot;&gt;操作系统控制 fsync&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontSize=13;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="1906.75" y="2546" width="104" height="21" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-3" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="jLEcGjrSGywTrWkUS_0Z-0" target="jLEcGjrSGywTrWkUS_0Z-2" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-0" value="Redis &lt;br&gt;从节点" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="304" y="1927.01" width="60" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-2" value="Redis&lt;br&gt;主节点" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="164" y="1927.01" width="60" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-4" value="复制" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="245" y="1942" width="34" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-6" value="从Redis" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="312" y="2019" width="60" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-10" value="从Redis" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="312" y="2111" width="60" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-13" value="从Redis" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="312" y="2203" width="60" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-7" value="主Redis" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="104" y="2111" width="60" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-9" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="jLEcGjrSGywTrWkUS_0Z-6" target="jLEcGjrSGywTrWkUS_0Z-7" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="301" y="2068" as="sourcePoint"/>
+                        <mxPoint x="251" y="2118" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-12" value="" style="endArrow=classic;html=1;entryX=0.98;entryY=0.624;entryDx=0;entryDy=0;exitX=-0.02;exitY=0.638;exitDx=0;exitDy=0;entryPerimeter=0;exitPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="jLEcGjrSGywTrWkUS_0Z-10" target="jLEcGjrSGywTrWkUS_0Z-7" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="322" y="2069" as="sourcePoint"/>
+                        <mxPoint x="174" y="2161" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-15" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="jLEcGjrSGywTrWkUS_0Z-13" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="312" y="2082" as="sourcePoint"/>
+                        <mxPoint x="164" y="2174" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-16" value="复制" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="222" y="2074" width="34" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-17" value="复制" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="224" y="2139.5" width="34" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-18" value="复制" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="222" y="2216.5" width="34" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-19" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="674" y="1848.99" width="93.75" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-20" value="&lt;font style=&quot;font-size: 10px&quot;&gt;slaveof&lt;/font&gt;" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="697.88" y="1826.99" width="41" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-22" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="866.5" y="1868.49" width="118.49" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-23" value="复制" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="897.0000000000002" y="1844.99" width="34" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-25" value="&lt;span style=&quot;background-color: rgb(42 , 42 , 42)&quot;&gt;&lt;font color=&quot;#a9c4eb&quot;&gt;Redis Client&lt;/font&gt;&lt;/span&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="615" y="1826.99" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-27" value="OK（异步）" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="683.37" y="1889.49" width="75" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-28" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.arrow;dy=0.63;dx=32.75;notch=0;direction=west;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="674" y="1870.99" width="93.75" height="18.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-30" value="从Redis&lt;br&gt;:6380" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="793" y="1831.99" width="60" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-31" value="主Redis&lt;br&gt;:6379" style="shape=cylinder;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="996" y="1835.99" width="60" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-32" value="&lt;font color=&quot;#a9c4eb&quot;&gt;redis-6380&amp;gt; slaveof 127.0.0.1 6379&lt;br&gt;OK&lt;/font&gt;" style="text;html=1;align=left;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="712.5" y="1928" width="198" height="32" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-34" value="" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="613" y="2018" width="111" height="207" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-36" value="" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="885" y="2018" width="111" height="207" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-35" value="master" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="645" y="2046" width="47" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-38" value="slave" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="921.5" y="2050" width="38" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-39" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.25;entryDx=0;entryDy=0;exitX=0;exitY=0.25;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="jLEcGjrSGywTrWkUS_0Z-36" target="jLEcGjrSGywTrWkUS_0Z-34" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="883" y="2070" as="sourcePoint"/>
+                        <mxPoint x="848" y="2022" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="792" y="2070"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-40" value="1. Connection lost" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="750.5" y="2046" width="106" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-43" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="jLEcGjrSGywTrWkUS_0Z-41" target="jLEcGjrSGywTrWkUS_0Z-42" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-44" value="send buffer" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="jLEcGjrSGywTrWkUS_0Z-43" vertex="1" connectable="0">
+                    <mxGeometry x="-0.0932" y="-1" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-41" value="write" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="631" y="2089" width="75" height="19" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-42" value="repl_back_buffer" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="615" y="2154" width="107" height="23" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-45" value="2" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="660.5" y="2064" width="16" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-46" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.25;entryDx=0;entryDy=0;exitX=0;exitY=0.25;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="884" y="2121.21" as="sourcePoint"/>
+                        <mxPoint x="723" y="2121.21" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="791" y="2121.46"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-48" value="3. Connection to master" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="735.5" y="2099" width="137" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-49" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.25;entryDx=0;entryDy=0;exitX=0;exitY=0.25;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="884.5" y="2150.71" as="sourcePoint"/>
+                        <mxPoint x="723.5" y="2150.71" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="791.5" y="2150.96"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-50" value="4. psync (offset) (runId)" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="737.5" y="2130" width="134" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-51" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.25;entryDx=0;entryDy=0;exitX=0;exitY=0.25;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="724" y="2182" as="sourcePoint"/>
+                        <mxPoint x="885" y="2182" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="792" y="2182.25"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-52" value="5. continue" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="769.5" y="2161" width="68" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-53" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.25;entryDx=0;entryDy=0;exitX=0;exitY=0.25;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="724" y="2210" as="sourcePoint"/>
+                        <mxPoint x="885" y="2210" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="792" y="2210.25"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="jLEcGjrSGywTrWkUS_0Z-54" value="6. send partial data" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;rounded=1;sketch=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="745.75" y="2191" width="112" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="y3e3DMnlS1YvEJu_HjQv-0" value="" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3859" y="1870" width="362" height="264" as="geometry"/>
+                </mxCell>
+                <mxCell id="y3e3DMnlS1YvEJu_HjQv-1" value="Sentinel" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3978" y="1882" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="y3e3DMnlS1YvEJu_HjQv-2" value="Sentinel" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3877" y="2043" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="y3e3DMnlS1YvEJu_HjQv-3" value="Sentinel" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4093" y="2043" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="y3e3DMnlS1YvEJu_HjQv-5" value="" style="endArrow=classic;startArrow=classic;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="y3e3DMnlS1YvEJu_HjQv-2" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3990" y="1992.01" as="sourcePoint"/>
+                        <mxPoint x="4040" y="1942.01" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="y3e3DMnlS1YvEJu_HjQv-6" value="" style="endArrow=classic;startArrow=classic;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="y3e3DMnlS1YvEJu_HjQv-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4153" y="2042.99" as="sourcePoint"/>
+                        <mxPoint x="4256" y="1942.0000000000002" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="y3e3DMnlS1YvEJu_HjQv-12" value="" style="endArrow=classic;startArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="y3e3DMnlS1YvEJu_HjQv-2" target="y3e3DMnlS1YvEJu_HjQv-3" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3997" y="2078" as="sourcePoint"/>
+                        <mxPoint x="4047" y="2028" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-1" value="&lt;font style=&quot;font-size: 19px&quot;&gt;master&lt;/font&gt;" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3997" y="2250" width="80" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-4" value="&lt;span style=&quot;font-size: 19px&quot;&gt;slave-2&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4149" y="2417.5" width="80" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-2" value="&lt;span style=&quot;font-size: 19px&quot;&gt;slave-1&lt;/span&gt;" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3848" y="2414" width="80" height="80" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-6" value="" style="endArrow=classic;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="y3e3DMnlS1YvEJu_HjQv-0" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4045" y="2216.5" as="sourcePoint"/>
+                        <mxPoint x="4038" y="2250" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-7" value="&lt;font style=&quot;font-size: 21px&quot;&gt;监控&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="bHKKQAjlWljOpdYD7-YH-6" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2628" y="1" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-13" value="&lt;font style=&quot;font-size: 21px&quot;&gt;监控&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="bHKKQAjlWljOpdYD7-YH-6" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2628" y="1" relative="1" as="geometry">
+                        <mxPoint x="98" y="66" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-14" value="&lt;font style=&quot;font-size: 21px&quot;&gt;监控&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="bHKKQAjlWljOpdYD7-YH-6" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2628" y="1" relative="1" as="geometry">
+                        <mxPoint x="-102" y="63" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-15" value="&lt;font style=&quot;font-size: 21px&quot;&gt;复制&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="bHKKQAjlWljOpdYD7-YH-6" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2628" y="1" relative="1" as="geometry">
+                        <mxPoint x="-67" y="192" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-16" value="&lt;font style=&quot;font-size: 21px&quot;&gt;复制&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="bHKKQAjlWljOpdYD7-YH-6" vertex="1" connectable="0">
+                    <mxGeometry x="-0.2628" y="1" relative="1" as="geometry">
+                        <mxPoint x="62" y="191" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-8" value="" style="endArrow=classic;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;entryX=1;entryY=0;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="bHKKQAjlWljOpdYD7-YH-1" target="bHKKQAjlWljOpdYD7-YH-2" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3958" y="2405.5" as="sourcePoint"/>
+                        <mxPoint x="4008" y="2355.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-9" value="" style="endArrow=classic;html=1;exitX=0;exitY=1;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="bHKKQAjlWljOpdYD7-YH-4" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4070.4357287525377" y="2313.314271247462" as="sourcePoint"/>
+                        <mxPoint x="3978.004271247389" y="2420.7457287526104" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-10" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="bHKKQAjlWljOpdYD7-YH-2" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4039" y="2134" as="sourcePoint"/>
+                        <mxPoint x="3962" y="2210" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-11" value="" style="endArrow=classic;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="y3e3DMnlS1YvEJu_HjQv-0" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4341" y="2139.5" as="sourcePoint"/>
+                        <mxPoint x="4190" y="2419.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-18" value="&lt;b&gt;&lt;font color=&quot;#a9c4eb&quot; style=&quot;background-color: rgb(0 , 0 , 0)&quot;&gt;Client-1&lt;/font&gt;&lt;/b&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3716" y="2508" width="47" height="102" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-26" value="&lt;b&gt;&lt;font color=&quot;#a9c4eb&quot; style=&quot;background-color: rgb(0 , 0 , 0)&quot;&gt;Client-2&lt;/font&gt;&lt;/b&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4059" y="2527.5" width="47" height="102" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-28" value="&lt;b&gt;&lt;font color=&quot;#a9c4eb&quot; style=&quot;background-color: rgb(0 , 0 , 0)&quot;&gt;Client-3&lt;/font&gt;&lt;/b&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4271" y="2527.5" width="47" height="102" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-30" value="" style="endArrow=classic;html=1;entryX=0.022;entryY=0.748;entryDx=0;entryDy=0;entryPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="bHKKQAjlWljOpdYD7-YH-2" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3763" y="2519" as="sourcePoint"/>
+                        <mxPoint x="3813" y="2469" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-31" value="" style="endArrow=classic;html=1;entryX=0.022;entryY=0.748;entryDx=0;entryDy=0;entryPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="bHKKQAjlWljOpdYD7-YH-26" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4062.24" y="2519" as="sourcePoint"/>
+                        <mxPoint x="4149" y="2473.84" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-32" value="" style="endArrow=classic;html=1;entryX=0.022;entryY=0.748;entryDx=0;entryDy=0;entryPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="bHKKQAjlWljOpdYD7-YH-28" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4188" y="2537.674887218045" as="sourcePoint"/>
+                        <mxPoint x="4231" y="2470" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-33" value="" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3887" y="2760" width="362" height="264" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-34" value="Sentinel：26379" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4006" y="2773" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-35" value="Sentinel：26380" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3905" y="2933" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-36" value="Sentinel：26381" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4121" y="2933" width="120" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-37" value="" style="endArrow=classic;startArrow=classic;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="bHKKQAjlWljOpdYD7-YH-35" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4018" y="2882.01" as="sourcePoint"/>
+                        <mxPoint x="4068" y="2832.01" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-38" value="" style="endArrow=classic;startArrow=classic;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="bHKKQAjlWljOpdYD7-YH-34" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4181" y="2932.99" as="sourcePoint"/>
+                        <mxPoint x="4284" y="2832" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-39" value="" style="endArrow=classic;startArrow=classic;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="bHKKQAjlWljOpdYD7-YH-35" target="bHKKQAjlWljOpdYD7-YH-36" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4025" y="2968" as="sourcePoint"/>
+                        <mxPoint x="4075" y="2918" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-40" value="&lt;font color=&quot;#a9c4eb&quot; style=&quot;background-color: rgb(0 , 0 , 0) ; font-size: 16px&quot;&gt;client&lt;/font&gt;" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3700" y="3120" width="48.5" height="92" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-44" value="" style="endArrow=classic;html=1;entryX=0.095;entryY=1.011;entryDx=0;entryDy=0;entryPerimeter=0;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" target="bHKKQAjlWljOpdYD7-YH-33" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3725" y="3120" as="sourcePoint"/>
+                        <mxPoint x="3775" y="3070" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-45" value="&lt;font style=&quot;font-size: 17px&quot;&gt;sentinel failover&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="bHKKQAjlWljOpdYD7-YH-44" vertex="1" connectable="0">
+                    <mxGeometry x="-0.1995" y="3" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-46" value="" style="endArrow=none;dashed=1;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="3891" y="3176" as="sourcePoint"/>
+                        <mxPoint x="4257" y="3176" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-49" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="bHKKQAjlWljOpdYD7-YH-47" target="bHKKQAjlWljOpdYD7-YH-48" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-50" value="&lt;font style=&quot;font-size: 18px&quot;&gt;复制&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="bHKKQAjlWljOpdYD7-YH-49" vertex="1" connectable="0">
+                    <mxGeometry x="0.0511" y="2" relative="1" as="geometry">
+                        <mxPoint x="-1.5" y="-2" as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-47" value="&lt;font style=&quot;font-size: 17px&quot;&gt;6380&lt;/font&gt;" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4176" y="3095" width="65" height="65" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-48" value="&lt;font style=&quot;font-size: 17px&quot;&gt;6379&lt;/font&gt;" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3935" y="3095" width="65" height="65" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-55" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="bHKKQAjlWljOpdYD7-YH-57" target="bHKKQAjlWljOpdYD7-YH-58" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-56" value="&lt;font style=&quot;font-size: 18px&quot;&gt;复制&lt;/font&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];rounded=1;sketch=1;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="bHKKQAjlWljOpdYD7-YH-55" vertex="1" connectable="0">
+                    <mxGeometry x="0.0511" y="2" relative="1" as="geometry">
+                        <mxPoint as="offset"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-57" value="&lt;font style=&quot;font-size: 17px&quot;&gt;6379&lt;/font&gt;" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4179" y="3195" width="65" height="65" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-58" value="&lt;font style=&quot;font-size: 17px&quot;&gt;6380&lt;/font&gt;" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3938" y="3195" width="65" height="65" as="geometry"/>
+                </mxCell>
+                <mxCell id="bHKKQAjlWljOpdYD7-YH-60" value="" style="shape=flexArrow;endArrow=classic;html=1;curved=1;sketch=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4089" y="3150" as="sourcePoint"/>
+                        <mxPoint x="4089" y="3210" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-4" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="qSeehcxtNLp253MRLS-R-0" target="qSeehcxtNLp253MRLS-R-3" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-0" value="Redis" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4000" y="908" width="55" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-6" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="qSeehcxtNLp253MRLS-R-3" target="qSeehcxtNLp253MRLS-R-5" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-20" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="qSeehcxtNLp253MRLS-R-3" target="qSeehcxtNLp253MRLS-R-19" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-3" value="获取锁，setnxex(&quot;lock&quot;,111)" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3935" y="952" width="187.12" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-29" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="qSeehcxtNLp253MRLS-R-5" target="qSeehcxtNLp253MRLS-R-11" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="3952.5" y="1118" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-5" value="获取到锁" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3916" y="1016" width="73" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-8" value="ok" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3973" y="977" width="30" height="20" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-41" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="qSeehcxtNLp253MRLS-R-11" target="qSeehcxtNLp253MRLS-R-40" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-11" value="执行业务" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3913.5" y="1057.12" width="78" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-15" value="结束" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3917.5" y="1151.5" width="70" height="26.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-31" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.75;entryDx=0;entryDy=0;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="qSeehcxtNLp253MRLS-R-19" target="qSeehcxtNLp253MRLS-R-3" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="4190" y="950" as="targetPoint"/>
+                        <Array as="points">
+                            <mxPoint x="4170" y="1023"/>
+                            <mxPoint x="4170" y="969"/>
+                            <mxPoint x="4140" y="969"/>
+                        </Array>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-19" value="未获取到锁" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4063" y="1012" width="92" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-22" value="null" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4074" y="977" width="30" height="20" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-27" value="等待100ms重试" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4150" y="952" width="94" height="18" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-42" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="qSeehcxtNLp253MRLS-R-40" target="qSeehcxtNLp253MRLS-R-15" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="qSeehcxtNLp253MRLS-R-40" value="删除锁" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3909.12" y="1104" width="87.88" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-0" value="Actor" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="3908" y="472" width="30" height="60" as="geometry"/>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-2" value="" style="endArrow=open;endFill=1;endSize=12;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="160" relative="1" as="geometry">
+                        <mxPoint x="3938" y="509" as="sourcePoint"/>
+                        <mxPoint x="4098" y="400" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-3" value="服务a&lt;br&gt;a.javaedge.com" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;rounded=0;sketch=0;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4093" y="349.5" width="110" height="76.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-4" value="服务b&lt;br&gt;b.javaedge.com" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;rounded=0;sketch=0;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4098" y="469" width="110" height="76.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-5" value="服务c&lt;br&gt;c.javaedge.com" style="ellipse;shape=cloud;whiteSpace=wrap;html=1;rounded=0;sketch=0;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4104" y="589.5" width="110" height="76.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-7" value="" style="endArrow=open;endFill=1;endSize=12;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="160" relative="1" as="geometry">
+                        <mxPoint x="3938" y="509.17" as="sourcePoint"/>
+                        <mxPoint x="4098" y="509.17" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-8" value="" style="endArrow=open;endFill=1;endSize=12;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="160" relative="1" as="geometry">
+                        <mxPoint x="3940" y="510" as="sourcePoint"/>
+                        <mxPoint x="4100" y="620" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-9" value="&lt;div&gt;&lt;span&gt;&amp;nbsp;cookie 存储域：.javaedge.com&lt;/span&gt;&lt;/div&gt;" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;rounded=0;sketch=0;align=center;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4390" y="467" width="110" height="83" as="geometry"/>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-10" value="" style="endArrow=open;endFill=1;endSize=12;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="160" relative="1" as="geometry">
+                        <mxPoint x="4221" y="506.6600000000001" as="sourcePoint"/>
+                        <mxPoint x="4381" y="506.6600000000001" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-11" value="" style="endArrow=open;endFill=1;endSize=12;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="160" relative="1" as="geometry">
+                        <mxPoint x="4208" y="396" as="sourcePoint"/>
+                        <mxPoint x="4380" y="500" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="OkcMy6MPD9_dVGg-3okY-13" value="" style="endArrow=open;endFill=1;endSize=12;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="160" relative="1" as="geometry">
+                        <mxPoint x="4221" y="625.5" as="sourcePoint"/>
+                        <mxPoint x="4381" y="516.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="0ay7eOm1cSlqAJpsfEuu-1" value="此处每个系统对一级域，写入 Cookie&lt;br&gt;然后用户信息存储Redis" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4355" y="425" width="210" height="30" as="geometry"/>
+                </mxCell>
+                <mxCell id="0ay7eOm1cSlqAJpsfEuu-2" value="" style="endArrow=classic;startArrow=classic;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" edge="1">
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        <mxPoint x="4500" y="513" as="sourcePoint"/>
+                        <mxPoint x="4570" y="513" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="0ay7eOm1cSlqAJpsfEuu-3" value="&lt;div&gt;&lt;span&gt;Cookie 对应的 Session、UserInfo&lt;/span&gt;&lt;/div&gt;" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;rounded=0;sketch=0;align=center;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4580" y="467" width="110" height="83" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-0" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="RGYdWhREaQg_77uxxW5g-1" target="RGYdWhREaQg_77uxxW5g-4" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-1" value="Redis" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4488.88" y="887.5" width="55" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-4" value="判断是否存在 EXISTS lock" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4423.88" y="931.5" width="187.12" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-21" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="RGYdWhREaQg_77uxxW5g-6" target="RGYdWhREaQg_77uxxW5g-17" edge="1">
+                    <mxGeometry relative="1" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-6" value="判断是否为自己的锁&lt;br&gt;" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4363" y="995.5" width="118.12" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-7" value="存在" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4448.88" y="953.5" width="40" height="20" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-12" value="获取锁并记录重入次数为1" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4543.88" y="993.75" width="170" height="25.5" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-13" value="不存在" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4561" y="953.5" width="50" height="20" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-17" value="重入次数+1" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4271" y="1064.5" width="118.12" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-22" value="存在" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4371" y="1020.5" width="40" height="20" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-23" value="获取锁失败" style="whiteSpace=wrap;html=1;rounded=1;sketch=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4443.88" y="1064.5" width="118.12" height="22" as="geometry"/>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-24" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;entryX=0.54;entryY=0.018;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="RGYdWhREaQg_77uxxW5g-6" target="RGYdWhREaQg_77uxxW5g-23" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="4432.047619047619" y="1027.5" as="sourcePoint"/>
+                        <mxPoint x="4374.857142857142" y="1074.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-25" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="RGYdWhREaQg_77uxxW5g-4" target="RGYdWhREaQg_77uxxW5g-6" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="4511.997619047619" y="945.5" as="sourcePoint"/>
+                        <mxPoint x="4420" y="992.5" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-26" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=0;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" source="RGYdWhREaQg_77uxxW5g-4" target="RGYdWhREaQg_77uxxW5g-12" edge="1">
+                    <mxGeometry relative="1" as="geometry">
+                        <mxPoint x="4599.44" y="944.64" as="sourcePoint"/>
+                        <mxPoint x="4686.93472" y="988.362" as="targetPoint"/>
+                    </mxGeometry>
+                </mxCell>
+                <mxCell id="RGYdWhREaQg_77uxxW5g-27" value="不存在" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;" parent="5GfIY6wJ8j-UMNQF0Tqn-1" vertex="1">
+                    <mxGeometry x="4431.12" y="1020.5" width="50" height="20" as="geometry"/>
+                </mxCell>
+            </root>
+        </mxGraphModel>
+    </diagram>
+    <diagram id="GTNoa96RQ6XVBhrCSo9i" name="Kafka">
+        &#xa;        &#xa;&#xa;
+        <mxGraphModel dx="908" dy="343" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="1600" pageHeight="900" background="#F4F1DE" math="0" shadow="0">
+            &#xa;            &#xa;&#xa;
+            <root>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-0"/>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-1" parent="DF5hBRuXm3O3wHpEomaR-0"/>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-4" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-2" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="252" y="171" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-25" value="是" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#393C56;rounded=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-4" vertex="1" connectable="0">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="-0.091" y="-2" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint as="offset"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-8" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-2" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="132" y="268" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-32" value="否" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#393C56;rounded=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-8" vertex="1" connectable="0">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="-0.1593" y="-1" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint as="offset"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-2" value="日志段为空" style="rhombus;whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="92" y="131" width="80" height="80" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-6" value="&lt;span&gt;更新用于日志段切分的时间戳&lt;/span&gt;" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="253" y="156" width="163" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-11" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-9" target="DF5hBRuXm3O3wHpEomaR-10" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-9" value="确保消息偏移值合法" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="72" y="268" width="120" height="37" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-13" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-10" target="DF5hBRuXm3O3wHpEomaR-12" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-10" value="写入消息" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="72" y="356" width="120" height="32" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-15" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-12" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="273" y="460.5" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-12" value="更新当前最大时间戳和所属消息偏移" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="31" y="441" width="202" height="39" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-18" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-16" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="348.5" y="377" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-21" value="是" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#393C56;rounded=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-18" vertex="1" connectable="0">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="0.0119" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint as="offset"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-24" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-16" target="DF5hBRuXm3O3wHpEomaR-22" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-16" value="&lt;span&gt;是否需要新增索引项&lt;/span&gt;" style="rhombus;whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="276" y="420.5" width="145" height="80" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-23" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-20" target="DF5hBRuXm3O3wHpEomaR-22" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-20" value="新增索引项" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="298" y="341" width="101" height="36" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-22" value="更新已写入字节数" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="478" y="442.5" width="120" height="36" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-31" value="" style="endArrow=classic;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-6" target="DF5hBRuXm3O3wHpEomaR-9" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="274" y="258" as="sourcePoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="324" y="208" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-35" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-33" target="DF5hBRuXm3O3wHpEomaR-34" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-33" value="查找索引确定读取物理文件位置" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="690" y="212" width="178" height="38" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-37" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-34" target="DF5hBRuXm3O3wHpEomaR-36" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-34" value="计算要读取的总字节数" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="690" y="288" width="178" height="38" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-36" value="读取消息" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="690" y="364" width="178" height="38" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-40" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-38" target="DF5hBRuXm3O3wHpEomaR-39" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-38" value="清空索引文件" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="981" y="224" width="97" height="39" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-42" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-39" target="DF5hBRuXm3O3wHpEomaR-41" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-44" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-39" target="DF5hBRuXm3O3wHpEomaR-43" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-39" value="遍历日志段中所有消息集合" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="952" y="296" width="155" height="39" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-43" value="" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1150" y="183" width="155" height="265" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-41" value="执行消息日志索引文件截断" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="952" y="371" width="155" height="39" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-47" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-45" target="DF5hBRuXm3O3wHpEomaR-46" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-45" value="校验消息集合" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1167.5" y="186" width="120" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-49" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-46" target="DF5hBRuXm3O3wHpEomaR-48" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-46" value="保存最大时间戳和所属消息偏移" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1167.5" y="236" width="120" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-51" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-48" target="DF5hBRuXm3O3wHpEomaR-50" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-48" value="更新索引项" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1167.5" y="284" width="120" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-53" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="DF5hBRuXm3O3wHpEomaR-50" target="DF5hBRuXm3O3wHpEomaR-52" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-50" value="更新总消息字节数" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1167.5" y="335" width="120" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="DF5hBRuXm3O3wHpEomaR-52" value="更新事务Pro状态和Leader Epoch缓存" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1167.5" y="384" width="120" height="54.5" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-6" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-7" target="Ygp73Fw7BJIjUW_6YSSX-9" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-22" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-5" target="Ygp73Fw7BJIjUW_6YSSX-7" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-7" value="创建发送者线程" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="376" y="1213" width="120" height="37" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-8" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-9" target="Ygp73Fw7BJIjUW_6YSSX-11" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-9" value="根据 brokerId 和 Partition 选择 SyncPool" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="319.5" y="1281" width="233" height="32" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-24" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-11" target="Ygp73Fw7BJIjUW_6YSSX-23" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-11" value="构造 ProducerRequest 请求" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="346" y="1346.5" width="180" height="33" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-5" value="创建Producer" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="388.5" y="1147" width="95" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-23" value="按照用户执行的序列化函数序列化消息" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="327" y="1415" width="218" height="33.5" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-44" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-25" target="Ygp73Fw7BJIjUW_6YSSX-32" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-25" value="发送 ProducerRequest 请求" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="648" y="1131" width="170" height="31.75" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-27" value="" style="shape=flexArrow;endArrow=classic;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="545" y="1431.41" as="sourcePoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="590" y="1431.8200000000002" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-28" value="" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="591" y="1143" width="11" height="289" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-29" value="" style="shape=flexArrow;endArrow=classic;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="602" y="1147" as="sourcePoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="647" y="1147.41" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-35" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-32" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="732" y="1308" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-36" value="有" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#393C56;rounded=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="Ygp73Fw7BJIjUW_6YSSX-35" vertex="1" connectable="0">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="-0.1381" y="1" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint as="offset"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-38" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-32" target="Ygp73Fw7BJIjUW_6YSSX-37" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-39" value="无" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#393C56;rounded=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="Ygp73Fw7BJIjUW_6YSSX-38" vertex="1" connectable="0">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="0.0372" y="-1" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint as="offset"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-32" value="是否出错？" style="rhombus;whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="654.5" y="1196" width="157" height="80" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-43" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-34" target="Ygp73Fw7BJIjUW_6YSSX-42" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-45" value="超过" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#393C56;rounded=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="Ygp73Fw7BJIjUW_6YSSX-43" vertex="1" connectable="0">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="-0.2784" y="1" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint as="offset"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-46" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-34" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="604" y="1346" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-47" value="没有" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#393C56;rounded=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="Ygp73Fw7BJIjUW_6YSSX-46" vertex="1" connectable="0">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="-0.2126" y="1" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint as="offset"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-34" value="出错是否超过最大次数？" style="rhombus;whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="653.5" y="1306" width="159" height="80" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-41" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-37" target="Ygp73Fw7BJIjUW_6YSSX-40" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-37" value="接受 ProducerResponse 响应" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="853" y="1220" width="165" height="32" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-40" value="按照 client 配置发送 ack 信息" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="847.75" y="1289" width="175.5" height="37" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-42" value="发送出错" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="673" y="1419.5" width="120" height="29" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-48" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-50" target="Ygp73Fw7BJIjUW_6YSSX-52" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-49" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-55" target="Ygp73Fw7BJIjUW_6YSSX-50" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-50" value="创建发送者线程" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="368.75" y="1858" width="97.5" height="31" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-81" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-52" target="Ygp73Fw7BJIjUW_6YSSX-80" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-52" value="消息放入队列" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="368" y="1933" width="99" height="32" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-55" value="创建Producer" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="370" y="1803" width="95" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-56" value="根据 brokerId 和 Partition 选择 SyncPool" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="307.75" y="2117" width="218" height="33.5" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-84" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-58" target="Ygp73Fw7BJIjUW_6YSSX-83" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-58" value="按照用户执行的序列化函数序列化消息" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="629.5" y="1835" width="170" height="31.75" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-59" value="" style="shape=flexArrow;endArrow=classic;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="526.5" y="2135.41" as="sourcePoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="571.5" y="2135.82" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-60" value="" style="whiteSpace=wrap;html=1;fillColor=#F2CC8F;strokeColor=#E07A5F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="572.5" y="1847" width="11" height="289" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-61" value="" style="shape=flexArrow;endArrow=classic;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry width="50" height="50" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="583.5" y="1851" as="sourcePoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="628.5" y="1851.41" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-92" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-73" target="Ygp73Fw7BJIjUW_6YSSX-75" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-73" value="接受 ProducerResponse 响应" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="632" y="2044" width="165" height="32" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-75" value="发送成功" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="654.5" y="2110" width="120" height="29" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-82" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-80" target="Ygp73Fw7BJIjUW_6YSSX-56" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-93" value="是" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#393C56;rounded=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="Ygp73Fw7BJIjUW_6YSSX-82" vertex="1" connectable="0">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="-0.7232" y="2" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint as="offset"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-94" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-80" target="Ygp73Fw7BJIjUW_6YSSX-52" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="518" y="1947" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <Array as="points">
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="494" y="1949"/>
+                            &#xa;                            &#xa;&#xa;
+                        </Array>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-96" value="否" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];fontColor=#393C56;rounded=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="Ygp73Fw7BJIjUW_6YSSX-94" vertex="1" connectable="0">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="-0.0205" y="-2" relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint as="offset"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-80" value="消息数达到？时间达到？" style="rhombus;whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="338.25" y="2010" width="157" height="80" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-87" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-83" target="Ygp73Fw7BJIjUW_6YSSX-86" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-83" value="&lt;span&gt;构造 ProducerRequest 请求&lt;/span&gt;" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="637.75" y="1905" width="153.5" height="36.37" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-91" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Ygp73Fw7BJIjUW_6YSSX-86" target="Ygp73Fw7BJIjUW_6YSSX-73" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Ygp73Fw7BJIjUW_6YSSX-86" value="&lt;span&gt;发送 ProducerRequest 请求&lt;/span&gt;" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="637.75" y="1972.55" width="153.5" height="37.45" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-4" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-0" target="Z0DgyB2yZk5KkLgFr_Ff-3" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-0" value="接收入站连接&lt;br&gt;建立请求" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1500" y="120" width="90" height="36" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-6" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-3" target="Z0DgyB2yZk5KkLgFr_Ff-5" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-3" value="等待 Acceptor 线程启动成功" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1500" y="175" width="90" height="35" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-8" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-5" target="Z0DgyB2yZk5KkLgFr_Ff-7" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-5" value="线程处于&lt;br&gt;运行状态？" style="rhombus;whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1482.5" y="250" width="127.5" height="71" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-11" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-7" target="Z0DgyB2yZk5KkLgFr_Ff-10" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-7" value="遍历准备就绪的连接建立请求事件" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1487.5" y="354" width="115" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-9" value="是" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1530" y="321" width="30" height="20" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-13" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-10" target="Z0DgyB2yZk5KkLgFr_Ff-12" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-10" value="确定处理该事件的 Processor线程" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1487.5" y="402" width="115" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-15" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-12" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="1545" y="230" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <Array as="points">
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1630" y="477"/>
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1630" y="230"/>
+                            &#xa;                            &#xa;&#xa;
+                        </Array>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-12" value="Processor线程与请求方建立TCP连接" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1487.5" y="460" width="115" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-19" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" target="Z0DgyB2yZk5KkLgFr_Ff-16" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="1430" y="290" as="sourcePoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <Array as="points">
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1430" y="280"/>
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1430" y="537"/>
+                            &#xa;                            &#xa;&#xa;
+                        </Array>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-23" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-16" target="Z0DgyB2yZk5KkLgFr_Ff-22" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-16" value="清理资源及关闭线程" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1487.5" y="510" width="115" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-20" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" target="Z0DgyB2yZk5KkLgFr_Ff-5" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="1430" y="286" as="sourcePoint"/>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-22" value="结束" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1507.5" y="570" width="75" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-25" value="否" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1440" y="275.5" width="30" height="20" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-46" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-28" target="Z0DgyB2yZk5KkLgFr_Ff-32" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-28" value="标记Processor线程启动" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1800" y="156" width="90" height="36" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-31" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-32" target="Z0DgyB2yZk5KkLgFr_Ff-34" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-32" value="Processor线程&lt;br&gt;运行中？" style="rhombus;whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1782.5" y="221" width="127.5" height="71" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-33" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-34" target="Z0DgyB2yZk5KkLgFr_Ff-37" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-34" value="建立Acceptor&lt;br&gt;分发的入站连接" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1787.5" y="325" width="115" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-35" value="是" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1830" y="292" width="30" height="20" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-36" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-37" target="Z0DgyB2yZk5KkLgFr_Ff-39" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-37" value="处理新创建的待处理&lt;br&gt;Response 对象" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1787.5" y="373" width="115" height="34" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-38" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="1902.5" y="261" as="sourcePoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="1850" y="670" as="targetPoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <Array as="points">
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1940" y="261"/>
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1940" y="670"/>
+                            &#xa;                            &#xa;&#xa;
+                        </Array>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-48" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-39" target="Z0DgyB2yZk5KkLgFr_Ff-47" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-39" value="执行实际I/O操作" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1787.5" y="424" width="115" height="24" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-43" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;exitX=0;exitY=0.5;exitDx=0;exitDy=0;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-53" target="Z0DgyB2yZk5KkLgFr_Ff-32" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry">
+                        &#xa;                        &#xa;&#xa;
+                        <mxPoint x="1756" y="650" as="sourcePoint"/>
+                        &#xa;                        &#xa;&#xa;
+                        <Array as="points">
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1760" y="650"/>
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1760" y="290"/>
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1756" y="290"/>
+                            &#xa;                            &#xa;&#xa;
+                            <mxPoint x="1756" y="257"/>
+                            &#xa;                            &#xa;&#xa;
+                        </Array>
+                        &#xa;                        &#xa;&#xa;
+                    </mxGeometry>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-50" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-47" target="Z0DgyB2yZk5KkLgFr_Ff-49" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-47" value="将接收到的请求封装后放入请求队列" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1787.5" y="470" width="115" height="30" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-52" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-49" target="Z0DgyB2yZk5KkLgFr_Ff-51" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-49" value="为已成功发送Response对象&lt;br&gt;调用回调逻辑" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1798.75" y="520" width="92.5" height="50" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-54" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-51" target="Z0DgyB2yZk5KkLgFr_Ff-53" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-51" value="处理断开连接后" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1800" y="600" width="92.5" height="20" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-56" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-53" target="Z0DgyB2yZk5KkLgFr_Ff-55" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-53" value="关闭过多连接" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1800" y="640" width="92.5" height="20" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-58" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;curved=1;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="Z0DgyB2yZk5KkLgFr_Ff-55" target="Z0DgyB2yZk5KkLgFr_Ff-57" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-55" value="关闭底层资源" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1800" y="680" width="92.5" height="20" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-57" value="关闭线程" style="whiteSpace=wrap;html=1;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1800" y="720" width="92.5" height="20" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="Z0DgyB2yZk5KkLgFr_Ff-66" value="否" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="1930" y="241" width="30" height="20" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-5" value="" style="edgeStyle=orthogonalEdgeStyle;orthogonalLoop=1;jettySize=auto;html=1;curved=1;strokeColor=#E07A5F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="g7dH6luOK3vaMA45hDJf-0" target="g7dH6luOK3vaMA45hDJf-4" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-6" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontColor=#393C56;strokeColor=#E07A5F;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="g7dH6luOK3vaMA45hDJf-0" target="g7dH6luOK3vaMA45hDJf-4" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-0" value="为所有监听器执行以下操作" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="2160" y="174" width="80" height="36" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-9" value="" style="edgeStyle=orthogonalEdgeStyle;curved=1;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;fontColor=#393C56;strokeColor=#E07A5F;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="g7dH6luOK3vaMA45hDJf-4" target="g7dH6luOK3vaMA45hDJf-8" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-4" value="初始化最大连接数计数器" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="2160" y="230" width="80" height="36" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-13" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;labelBackgroundColor=#F4F1DE;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" source="g7dH6luOK3vaMA45hDJf-8" target="g7dH6luOK3vaMA45hDJf-12" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-8" value="创建Acceptor 线程" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="2145" y="287" width="110" height="23" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-15" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="DF5hBRuXm3O3wHpEomaR-1" source="g7dH6luOK3vaMA45hDJf-12" target="g7dH6luOK3vaMA45hDJf-14" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-12" value="创建 Processor 线程池" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;shadow=0;labelBackgroundColor=none;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="2137.5" y="341" width="125" height="19" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-17" value="" style="edgeStyle=orthogonalEdgeStyle;curved=0;rounded=1;sketch=1;orthogonalLoop=1;jettySize=auto;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;" parent="DF5hBRuXm3O3wHpEomaR-1" source="g7dH6luOK3vaMA45hDJf-14" target="g7dH6luOK3vaMA45hDJf-16" edge="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry relative="1" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-14" value="加入监听器/Acceptor 线程对到Acceptor线程池" style="whiteSpace=wrap;html=1;strokeColor=#E07A5F;fillColor=#F2CC8F;fontColor=#393C56;rounded=1;shadow=0;labelBackgroundColor=none;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="2133.75" y="384" width="132.5" height="35" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+                <mxCell id="g7dH6luOK3vaMA45hDJf-16" value="结束" style="shape=parallelogram;perimeter=parallelogramPerimeter;whiteSpace=wrap;html=1;fixedSize=1;rounded=1;shadow=0;fontColor=#393C56;strokeColor=#E07A5F;fillColor=#F2CC8F;sketch=1;" parent="DF5hBRuXm3O3wHpEomaR-1" vertex="1">
+                    &#xa;                    &#xa;&#xa;
+                    <mxGeometry x="2160" y="441" width="80" height="27.5" as="geometry"/>
+                    &#xa;                    &#xa;&#xa;
+                </mxCell>
+                &#xa;                &#xa;&#xa;
+            </root>
+            &#xa;            &#xa;&#xa;
+        </mxGraphModel>
+        &#xa;        &#xa;&#xa;
+    </diagram>
+</mxfile>
\ No newline at end of file
diff --git "a/assets/\344\270\252\344\272\272\345\276\256\344\277\241.jpeg" "b/assets/\344\270\252\344\272\272\345\276\256\344\277\241.jpeg"
deleted file mode 100644
index 35b73f0e1e..0000000000
Binary files "a/assets/\344\270\252\344\272\272\345\276\256\344\277\241.jpeg" and /dev/null differ
diff --git "a/assets/\345\276\256\344\277\241\347\276\244.jpeg" "b/assets/\345\276\256\344\277\241\347\276\244.jpeg"
deleted file mode 100644
index abeb9aef11..0000000000
Binary files "a/assets/\345\276\256\344\277\241\347\276\244.jpeg" and /dev/null differ
diff --git a/out/TODO/uml/ReplicaManager#appendRecordstxt/ReplicaManager#appendRecordstxt.png b/out/TODO/uml/ReplicaManager#appendRecordstxt/ReplicaManager#appendRecordstxt.png
deleted file mode 100644
index f0f9ca8bf5..0000000000
Binary files a/out/TODO/uml/ReplicaManager#appendRecordstxt/ReplicaManager#appendRecordstxt.png and /dev/null differ
diff --git a/out/TODO/uml/ReplicaManager#fetchMessages/ReplicaManager#fetchMessages.png b/out/TODO/uml/ReplicaManager#fetchMessages/ReplicaManager#fetchMessages.png
deleted file mode 100644
index 0e78971422..0000000000
Binary files a/out/TODO/uml/ReplicaManager#fetchMessages/ReplicaManager#fetchMessages.png and /dev/null differ
diff --git a/out/TODO/uml/appendRecords/appendRecords.png b/out/TODO/uml/appendRecords/appendRecords.png
deleted file mode 100644
index 4834cb85f7..0000000000
Binary files a/out/TODO/uml/appendRecords/appendRecords.png and /dev/null differ
diff --git a/out/TODO/uml/processFetchRequest/processFetchRequest.png b/out/TODO/uml/processFetchRequest/processFetchRequest.png
deleted file mode 100644
index 546e4be796..0000000000
Binary files a/out/TODO/uml/processFetchRequest/processFetchRequest.png and /dev/null differ
diff --git "a/\344\272\221\345\216\237\347\224\237/DevOps/DevOps(1)-\345\237\272\346\234\254\347\220\206\350\256\272.md" "b/\344\272\221\345\216\237\347\224\237/DevOps/DevOps(1)-\345\237\272\346\234\254\347\220\206\350\256\272.md"
deleted file mode 100644
index 727addad1a..0000000000
--- "a/\344\272\221\345\216\237\347\224\237/DevOps/DevOps(1)-\345\237\272\346\234\254\347\220\206\350\256\272.md"
+++ /dev/null
@@ -1,104 +0,0 @@
-# 1 辨析敏捷/持续集成/持续交付/DevOps
-![](https://img-blog.csdnimg.cn/2020091822592454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-# 2 持续集成
-
-## 2.1 为何会有持续集成？
-敏捷开发解决了单体应用的开发和每日构建的问题。
-
-而单体应用拆分成微服务，就需要有一套方案来组装这些微服务，使其成为可协作运行的微服务架构。该方案就是持续集成。
-
-- 持续集成强调开发人员提交新代码后，立刻进行构建、（单元）测试。根据测试结果，可确定新代码和原有代码是否正确集成在一起。
-![](https://img-blog.csdnimg.cn/20200919010053171.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-
-## 2.2 持续集成的定义
-持续交付的鼻祖Martin Fowler提出：持续集成(Continous Integration)是一种软件开发实践，帮助团队成员频繁集成他们的工作，通常每个项目每天至少集成一次，从而每天有可测试的版本。
-每次集成使用自动化构建(含测试)来实现打包和测试，快速验证问题。许多团队发现持续集成显著地降低了集成遇到的错误，使团队能够更加迅速地开发软件。
-
-## 2.3 为何需要持续集成![](https://img-blog.csdnimg.cn/20200919001633283.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-就如同Git是为了把代码集成在一起管理，持续构建就是把功能集成在一起，保证编译不出错。
-类似的还有自动化测试保证一个模块的功能集成在一起能够正确工作。
-联调测试环境则能将不同模块之间集成在一起，在一个类生产的环境中进行测试。
-
-## 2.4 持续集成流水线的设计
-![](https://img-blog.csdnimg.cn/20200919005133257.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-# 3 持续部署
-
-## 3.1 传统部署的缺陷
-- 传统部署部署方式严重依赖手工部署，人力成本高
-- 生产环境依赖手工配置进行变更，非常繁琐
-- 熬夜加班进行上线部署，效率很低
-
-## 3.2 持续部署的定义
-首先明确概念，软件部署：将软件按期望状态部署到目标机器的期望路径。
-
-持续部署：自动化的将一或多个软件尽可能快的、稳定的、可重复的联合部署到目标机器，以便软件功能的验证和实际运行。
-可能是在云环境中自动部署、app升级(如手机上的应用程序)、更新网站或只更新可用版本列表。
-- 持续部署是在持续交付基础上，将部署到生产环境这一过程自动化。
-
-![](https://img-blog.csdnimg.cn/20200919010019776.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-## 3.3 持续部署的基本要素
-- 自动化部署 - ansible
-- 应用与配置分离，一次构建，多处运行 - Spring Cloud Config
-- 提供应用健康监测的接口 - Spring Cloud Actuator
-
-## 3.4 常见自动化部署方法
-![](https://img-blog.csdnimg.cn/20200919011237162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-其中的vault专门用于存储一些加密信息，比如用户密码
-
-## 3.5 测试部署效果
-
-### 蓝绿部署（Blue-Green Deployment）
-一种应用发布模式，可将用户流量从先前版本的应用或微服务逐渐转移到几乎相同的新版本中（两者均保持在生产环境中运行）。
-
-该种部署软件的方法中，维护两个相同的主机环境
-- 蓝色
-旧版本的生产环境
-- 绿色
-新版本的预发布环境
-
-一旦生产流量从蓝色完全转移到绿色，蓝色就可在回滚或退出生产的情况下保持待机，也可更新成为下次更新的模板。
-
-自动化部署面临的挑战之一是转换本身，将软件从测试的最后阶段转移到实际生产中。通常，您需要快速执行此操作，以最大程度减少停机时间。蓝绿部署方法通过确保拥有两个尽可能相同的生产环境来做到这一点。
-在任何时候，其中一个（例如蓝色）都处于活动状态。准备新版本的软件时，在绿色环境中进行最后的测试阶段。一旦软件在绿色环境中运行，就可以切换路由器，以便所有传入请求都进入绿色环境-蓝色的请求现在处于空闲状态。
-
-蓝绿部署还提供了快速回滚的方法-如果出现任何问题，将路由切换回蓝色环境。
-在绿色环境处于活动状态时，仍然存在处理丢失的事务的问题，你可能能够以在绿色环境处于活动状态时将蓝色环境作为备份的方式向这两个环境提供交易。或者，您可以在切换前将应用程序置于只读模式，以只读模式运行一段时间，然后将其切换为读写模式。这可能足以清除许多未解决的问题。
-**两种环境必须不同，但要尽可能相同**。在某些情况下，它们可以是不同的硬件，也可以是在相同（或不同）硬件上运行的不同虚拟机。它们也可以是一个单独的操作环境，分为两个区域，两个区域具有单独的IP地址。
-将绿色环境投入使用并对它的稳定性感到满意之后，就可以将蓝色环境用作过渡环境，以进行下一个部署的最后测试步骤。准备好发布下一个版本时，你从绿色切换为蓝色的方式与之前从蓝色切换为绿色的方式相同。这样，绿色和蓝色环境便会定期在实时上一个版本（用于回滚）和下一个新版本之间进行循环。
-这种方法的一个优点是，它与获得热备份工作所需的基本机制相同。因此，这使您可以在每个版本上测试灾难恢复过程。 （我希望你发布的时间比灾难多得多。）基本思想是要在两个易于切换的环境之间进行切换，有很多方法可以更改细节。一个项目通过跳动Web服务器而不是在路由器上工作来进行切换。另一种变化是使用相同的数据库，从而为Web和域层设置了蓝绿色的开关。使用这种技术，数据库通常可能是一个挑战，尤其是当您需要更改架构以支持软件的新版本时。技巧是将架构更改的部署与应用程序升级分开。因此，首先应用数据库重构来更改架构以支持应用程序的新旧版本，进行部署，检查一切是否正常，以便您有一个回滚点，然后部署该应用程序的新版本。 （并且在升级失败后，删除对旧版本的数据库支持。）
-
-该技术已经存在了很长时间了，但是Martin Fowler并不认为它应该经常使用。 Daniel Terhorst-North和Jez Humble的一些模糊组合提出了这个名称。
-
-这种持续部署模式原本存在不足之处。并非所有环境都具有相同的正常运行时间要求或正确执行 CI/CD 流程（如蓝绿部署）所需的资源。但是，随着企业加大对数字化转型的支持，许多应用开始支持这种。
-#### 模型图
-在这些实例的前面是调度系统，它们充当产品或应用程序的客户“网关”。通过将调度系统指向蓝色或绿色实例,可以将客户流量引流到期望的部署环境。通过这种方式，切换指向哪个部署实例(蓝色或绿色)对用户来说是快速简单而透明的。
-![](https://img-blog.csdnimg.cn/20200919014805478.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-### 金丝雀部署（灰度发布）
-一部分客户流量被重新引流到新的版本部署中。例如，新版本的搜索服务可与当前服务的生产版本一起部署。
-然后，可将10%的搜索查询引流到新版本，以在生产环境中对其进行测试。
-
-如果服务那些流量的新版本没问题，那么可能会有更多流量会被逐渐引流过去。如果仍然没有问题出现，那么随时间推移，可对新版本增量部署，直到100%的流量都调度到新版本。
-#### 模型图
-![](https://img-blog.csdnimg.cn/2020091902172417.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-
-### 暗发布（DarkLaunching）
-也叫功能开关，指软件特性在正式发布之前，先将其第一个版本部署到生产环境。通过应用“开关”技术，使用户在无感的情况下应用新特性的功能，软件提供商通过收集用户的实际操作记录来获得针对这个新特性的反馈数据。
-当然，发布新特性，使用户无感还是比较难做到的。新特性所针对软件的改变通常不体现在用户经常使用的界面按钮的调整，更多的是后台交易逻辑或算法层面的调整。
-
-对于可能需要轻松关掉的新功能(若发现有问题)，开发人员可添加功能开关(feature toggles)。这是代码中的if-then软件功能开关，仅在设置数据值时才激活新代码。
-- 此数据值可以是全可访问的位置，部署的应用程序将检查该位置是否应执行新代码。如果设置了数据值，则执行代码；如果没有，则不执行。
-- 这为开发人员提供了一个远程“终止开关”，以便在部署到生产环境后发现问题时关闭新功能。
-![](https://img-blog.csdnimg.cn/2020091904225939.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-#### 案例
-某互联网公司重新开发了一个在线新闻推荐算法，希望能够为其用户推荐更多和更好的新闻内容。但是，由于此算法相对于以前算法的复杂度较高，提供算法的公司需要搜集该算法的执行效果。基于这种需求，我们就可以应用暗部署的方法。我们可以为这个算法配置一个开关，并将其部署到生产环境中。当针对这个算法的开关打开时，用户的访问流浪就会触发这个新算法的执行。通常用户并不知道其此次访问所调用的算法的新旧。如果这个算法在大规模用户并发情况下的性能不好，我们就可以马上关闭这个算法所对应的开关，让用户使用原来的算法。
-
-参考
-- https://www.mindtheproduct.com/what-the-hell-are-ci-cd-and-devops-a-cheatsheet-for-the-rest-of-us/
-- https://tech.youzan.com/gray-deloyments-and-blue-green-deployments-practices-in-youzan/
-- http://stormluke.me/deploy-not-equal-release-part-one/
-- https://martinfowler.com/bliki/BlueGreenDeployment.html
\ No newline at end of file
diff --git "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md" "b/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md"
index a83cc67093..46173b34af 100644
--- "a/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md"	
+++ "b/\344\272\221\345\216\237\347\224\237/Docker/Docker\345\256\271\345\231\250\345\256\236\346\210\230(\345\205\253)-\350\260\210\350\260\210 Kubernetes \347\232\204\346\234\254\350\264\250.md"	
@@ -31,13 +31,14 @@ Borg系统，一直以来都被誉为Google公司内部最强大的“秘密武
 相比于Spanner、BigTable等相对上层的项目，Borg要承担的责任，是承载Google整个基础设施的核心依赖。
 在Google已经公开发表的基础设施体系论文中，Borg项目当仁不让地位居整个基础设施技术栈的最底层。
 ![](https://img-blog.csdnimg.cn/2019101601190585.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-> 源于Google Omega论文的第一作者的博士毕业论文，描绘了当时Google已公开发表的整个基础设施栈。可发现MapReduce、BigTable等知名项目。Borg和其继任者Omega位于整个技术栈的最底层。
+> 这幅图，来自于Google Omega论文的第一作者的博士毕业论文。它描绘了当时Google已经公开发表的整个基础设施栈。在这个图里，你既可以找到MapReduce、BigTable等知名项目，也能看到Borg和它的继任者Omega位于整个技术栈的最底层。
 
-所以Borg可算是Google最不可能开源的一个项目。得益于Docker项目和容器技术发展，它却终于以Kubernetes身份开源。
+正是由于这样的定位，Borg可以说是Google最不可能开源的一个项目。
+得益于Docker项目和容器技术的风靡，它却终于得以以另一种方式与开源社区见面，就是Kubernetes项目。
 
 相比于“小打小闹”的Docker公司、“旧瓶装新酒”的Mesos社区，**Kubernetes项目从一开始就比较幸运地站上了一个他人难以企及的高度：**
 在它的成长阶段，这个项目每一个核心特性的提出，几乎都脱胎于Borg/Omega系统的设计与经验。
-这些特性在社区落地过程中，又在整个社区的合力之下得到了极大的改进，修复了大量Borg体系的历史缺陷。
+更重要的是，这些特性在开源社区落地的过程中，又在整个社区的合力之下得到了极大的改进，修复了很多当年遗留在Borg体系中的缺陷和问题。
 
 尽管在发布之初被批“曲高和寡”，但在逐渐觉察到Docker技术栈的“稚嫩”和Mesos社区的“老迈”，社区很快就明白了：Kubernetes项目在Borg体系的指导下，体现出了一种独有的先进与完备性，这些才是一个基础设施领域开源项目的核心价值。
 
@@ -283,5 +284,6 @@ $ kubectl create -f nginx-deployment.yaml
 Kubernetes为用户提供的不仅限于一个工具。它真正的价值，还是在于提供了一套基于容器构建分布式系统的基础依赖
 
 # 参考
+
 - 深入剖析Kubernetes
 - [Large-scale cluster management at Google with Borg](https://storage.googleapis.com/pub-tools-public-publication-data/pdf/43438.pdf)
\ No newline at end of file
diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md" "b/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md"
index f7a7965838..5fc41ecb4e 100644
--- "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md"
+++ "b/\344\272\221\345\216\237\347\224\237/Kubernetes/\344\272\221\345\216\237\347\224\237\345\256\271\345\231\250\346\225\231\347\250\213(\344\270\200)-Kubernetes\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md"
@@ -1,6 +1,3 @@
-# 简介
-Kubernetes，希腊语，意舵手。有时简写为“K8s”，其中“8”代表“K”和“s”之间的 8 个字母，是一个开源系统，支持在任何地方部署、扩缩和管理容器化应用。
-
 # 1 Kubernetes架构
 - 整体架构图
 ![](https://img-blog.csdnimg.cn/20201227162743754.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
diff --git "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\345\256\271\345\231\250\350\260\203\345\272\246\345\222\214\346\234\215\345\212\241\347\274\226\346\216\222.md" "b/\344\272\221\345\216\237\347\224\237/Kubernetes/\345\256\271\345\231\250\350\260\203\345\272\246\345\222\214\346\234\215\345\212\241\347\274\226\346\216\222.md"
deleted file mode 100644
index 1b4bfa6789..0000000000
--- "a/\344\272\221\345\216\237\347\224\237/Kubernetes/\345\256\271\345\231\250\350\260\203\345\272\246\345\222\214\346\234\215\345\212\241\347\274\226\346\216\222.md"
+++ /dev/null
@@ -1,74 +0,0 @@
-- 镜像仓库
-解决Docker镜像存储和访问
-- 资源调度
-决定Docker镜像可分发到哪些机器
-
-这些解决后，就该考虑如何在集群中创建容器，即**容器调度**。
-容器创建后如何运作才能对外提供服务，即**容器调度**。
-
-# 1 容器调度
-当服务需要发布的时候，该选择哪些机器部署容器。
-
-若集群机器规模上百台，要发布的服务上百个，就不能靠人肉运维，需要有专门容器调度系统，所以很多基于Docker的容器调度系统，比如Swarm、Mesos。Kubernetes。
-
-它们能解决哪些问题呢？
-
-## 主机过滤
-
-解决容器创建时什么样的机器可以使用：
-
-- 存活过滤
-必须选择存活的节点，因为主机也有可能下线、故障态。
-
-- 硬件过滤
-比如Web集群往往用作计算节点，它的CPU一般配置比较高
-大数据集群往往用作数据存储，它的磁盘一般配置比较高
-
-以上都是针对主机层次的过滤方式。
-
-## 调度策略
-为解决容器创建时选择哪些主机最合适，一般通过给主机打分。
-比如Swarm包含两种类似策略：spread和binpack，都会根据每台主机的可用CPU、内存以及正在运行的容器的数量来给每台主机打分：
-- spread策略
-会选择一个资源使用最少的节点，以使容器尽可能的分布在不同的主机上运行。好处：可以使每台主机的负载都比较平均，而且如果有一台主机有故障，受影响的容器也最少
-- binpack策略
-正好相反，会选择一个资源使用最多的节点，好让容器尽可能的运行在少数机器上，节省资源的同时也避免了主机使用资源的碎片化
-
-### 适用场景
-各主机配置基本相同，使用也较简单，一台主机上只创建一个容器。每次创建容器时，直接从尚未创建过容器的主机中，**随机选择一台**。
-
-某些在线、离线业务混布场景，为达到主机资源使用率最高，需综合考量容器中的任务特点：
-- 在线业务主要使用CPU
-- 离线业务主要使用磁盘和I/O
-
-这两种业务的容器大部分情况下适合**混跑在一起**。
-
-还有一种业务场景，主机资源都充足，每个容器只要划定所用资源限制，理论上跑在一起是没问题。但有时会出现对每个资源的抢占，比如都是CPU密集型或者I/O密集型的业务就**不适合容器混布**一台主机。
-
-# 2 服务编排
-## 服务依赖
-若服务A调度的前提是先有服务B，容器调度时，就要考虑服务间这种依赖关系。
-
-所以Docker提供Docker Compose。允许用户通过一个单独的`docker-compose.yaml`定义一组相互关联的容器组成一个项目，以项目形式管理应用。
-比如要实现一个Web项目，要创建：
-- Web容器比如Tomcat容器
-- 数据库容器比如MySQL容器
-- 负载均衡容器比如Nginx容器等
-
-就可以通过`docker-compose.yaml`配置这个Web项目里包含的三个容器的创建。
-## 服务发现
-容器调度完成后，就可以启动了。但此时容器还不能对外服务，服务消费者并不知道这个新的节点，必须具备服务发现机制，使得新容器节点能够加入到线上服务。
-
-常用的服务发现机制：
-### 基于Nginx的服务发现
-针对提供HTTP服务的。
-
-当有新容器节点时，修改Nginx的节点list配置，然后利用Nginx的reload，重新读取配置，加载新节点。
-
-### 基于注册中心的服务发现
-针对提供RPC服务的。
-
-当有新的容器节点时，需要调用注册中心提供的服务注册接口。使用这种方式时，如果服务部署在多个IDC，就要求容器节点分IDC进行注册，以便实现同IDC内就近访问。以微博的业务为例，微博服务除了部署在内部的两个IDC，还在阿里云上也有部署，这样的话，内部机房上创建的容器节点就应该加入到内部IDC分组，而云上的节点应该加入到阿里云的IDC。
-
-## 弹性扩容
-大部分互联网业务的访问呈现出访问时间的规律性。在高峰期，增加容器的数量，确保服务稳定性；低峰期减少容器数量，减少服务使用的资源成本。可以根据容器的CPU负载，设置一个扩缩容的容器数量或比例。比如可以设定容器的CPU使用率不超过50%，一旦超过这个使用率就扩一倍节点。
\ No newline at end of file
diff --git "a/\344\272\221\345\216\237\347\224\237/ServiceMesh/Istio\346\236\266\346\236\204\350\256\276\350\256\241\345\223\262\345\255\246.md" "b/\344\272\221\345\216\237\347\224\237/ServiceMesh/Istio\346\236\266\346\236\204\350\256\276\350\256\241\345\223\262\345\255\246.md"
deleted file mode 100644
index f188e35634..0000000000
--- "a/\344\272\221\345\216\237\347\224\237/ServiceMesh/Istio\346\236\266\346\236\204\350\256\276\350\256\241\345\223\262\345\255\246.md"
+++ /dev/null
@@ -1,64 +0,0 @@
-# 架构设计
-## 1.0架构
-![](https://img-blog.csdnimg.cn/20210131154633344.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-Istio 服务网格从逻辑上分为数据平面和控制平面。
-### 数据平面
-由一组智能代理（Envoy）组成，被部署为 sidecar。这些代理负责协调和控制微服务之间的所有网络通信。他们还收集和报告所有网格流量的遥测数据。
-#### Envoy
-sidecar集合。Istio 使用 Envoy 代理的扩展版本。Envoy 是用 C++ 开发的高性能代理，用于协调服务网格中所有服务的入站和出站流量。Envoy 代理是唯一与数据平面流量交互的 Istio 组件。
-
-Envoy 代理被部署为服务的 sidecar，在逻辑上为服务增加了 Envoy 的许多内置特性，例如:
-动态服务发现
-负载均衡
-TLS 终端
-HTTP/2 与 gRPC 代理
-熔断器
-健康检查
-基于百分比流量分割的分阶段发布
-故障注入
-丰富的指标
-这种 sidecar 部署允许 Istio 提取大量关于流量行为的信号作为属性。Istio 可以使用这些属性来实施策略决策，并将其发送到监视系统以提供有关整个网格行为的信息。
-
-sidecar 代理模型还允许您向现有的部署添加 Istio 功能，而不需要重新设计架构或重写代码。您可以在设计目标中读到更多关于为什么我们选择这种方法的信息。
-
-由 Envoy 代理启用的一些 Istio 的功能和任务包括:
-流量控制功能：通过丰富的 HTTP、gRPC、WebSocket 和 TCP 流量路由规则来执行细粒度的流量控制。
-网络弹性特性：重试设置、故障转移、熔断器和故障注入。
-安全性和身份验证特性：执行安全性策略以及通过配置 API 定义的访问控制和速率限制。
-基于 WebAssembly 的可插拔扩展模型，允许通过自定义策略实施和生成网格流量的遥测。
-
-### 控制平面
-管理并配置代理来进行流量路由。
-- Pilot，负责将配置分发给 sidecar 组件
-- Citadel，负责安全
-- Mixer，负责遥测和策略，一种插件模型。但存在问题：
-需要和数据平面进行两次通信，但其又是单独部署的进程外的组件，所以每次请求都要和进程外进行通信，降低整个请求的性能。插件模型虽然带来很好的扩展性，但也产生耦合：当需要添加新插件或修改已有插件，都要重新部署 mixer。所以下一版本就需要解耦
-
-Istio 中的流量分为数据平面流量和控制平面流量。数据平面流量是指工作负载的业务逻辑发送和接收的消息。控制平面流量是指在 Istio 组件之间发送的配置和控制消息用来编排网格的行为。Istio 中的流量管理特指数据平面流量。
-## 1.1 
-![](https://img-blog.csdnimg.cn/20210131155633303.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 变化
-adapter：
-mixer 增加了一个进程外的 adapter概念，即把原来集成在组件内部的插件变成了进程外的一个适配器，如此，当你需要修改插件时，无需修改 mixer本身。
-
-gallery：
-负责 istio 的配置验证，提取处理和分发，这些原本都是 pilot 的功能，但他们会导致 pilot 和底层的平台比如 k8s 耦合。
-
-### 缺点
-- 性能
-插件又拆分，导致网络请求次数又增加
-- 易用性
-组件都需要单独部署，维护困难。
-
-可见解耦虽好，但也不是银弹，我们始终还得坚持软件架构的取舍原则！
-MVP 理论：开发团队通过提供最小化可行产品，获取用户反馈，并在最小化可行产品上持续地进行迭代直到产品达到一个相对稳定的状态。
-
-## 回归单体
-复杂是万恶之源，学会停止焦虑，爱上单体一Istio 开发团队
-修正原有架构的复杂性：
-- 维护性
-- 多组件分离的必要性
-- 伸缩性
-- 安全性
-
-![](https://img-blog.csdnimg.cn/2021013116160920.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\344\272\221\345\216\237\347\224\237/ServiceMesh/ServiceMesh\345\256\236\346\210\230(1)-ServiceMesh\347\274\226\347\250\213\346\250\241\345\274\217.md" "b/\344\272\221\345\216\237\347\224\237/ServiceMesh/ServiceMesh\345\256\236\346\210\230(1)-ServiceMesh\347\274\226\347\250\213\346\250\241\345\274\217.md"
index 76b92beb63..7b90e0dd71 100644
--- "a/\344\272\221\345\216\237\347\224\237/ServiceMesh/ServiceMesh\345\256\236\346\210\230(1)-ServiceMesh\347\274\226\347\250\213\346\250\241\345\274\217.md"
+++ "b/\344\272\221\345\216\237\347\224\237/ServiceMesh/ServiceMesh\345\256\236\346\210\230(1)-ServiceMesh\347\274\226\347\250\213\346\250\241\345\274\217.md"
@@ -77,103 +77,21 @@ Fallacies of Distributed Computing Explained：
 在这种模型中，你的每个服务都将有一个伴随代理服务。鉴于服务仅通过Sidecar代理相互通信，因此我们最终得到了类似于下图的部署，可以说就是 sidecar 的网络拓扑组合。
 ![](https://img-blog.csdnimg.cn/20210130185202640.png)
 ![](https://img-blog.csdnimg.cn/20210130185225646.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-### 定义
-Buoyant的CEO William Morgan 指出，代理之间的互连形成了网状网络（mesh network）。 2017年初，William为该平台编写了一个定义，并将其称为Service Mesh：
-服务网格是用于处理服务到服务之间通信的**基础设施层**。它主要负责在现代的云原生应用这种复杂的服务拓扑场景下，进行可靠地**请求分发**。实际上，它通常被实现为一组轻量级的**网络代理**，部署在你的应用代码旁边，**并且对你的应用程序完全透明**。
+Buoyant的首席执行官威廉·摩根（William Morgan）指出，代理之间的互连形成了网状网络（mesh network）。 
+2017年初，William为该平台编写了一个定义，并将其称为Service Mesh：
+> 服务网格是用于处理服务到服务通信的专用基础结构层。它负责通过构成现代云原生应用程序的复杂服务拓扑可靠地传递请求。实际上，服务网格通常被实现为轻量级网络代理的阵列，这些轻量级网络代理与应用程序代码一起部署，而无需了解应用程序。
 
 他的定义中最有力的方面可能是，它摆脱了将代理视为独立组件的想法，并认识到它们形成的网络本身就是有价值的东西。
 ![](https://img-blog.csdnimg.cn/20210130190349989.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 
 ## 2.6 你以为结束了?其实才刚开始-Service Mesh V2
-随着人们将其微服务部署移至更复杂的运行时（如Kubernetes和Mesos），人们已开始使用这些平台提供的工具来正确实现网状网络的想法。他们正从一组独立工作的独立代理转移到一个适当的，集中式的控制平面。
+随着组织将其微服务部署移至更复杂的运行时（如Kubernetes和Mesos），人们已开始使用这些平台提供的工具来正确实现网状网络的想法。他们正从一组独立工作的独立代理转移到一个适当的，有点集中的控制平面。
 ![](https://img-blog.csdnimg.cn/20210130190548117.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 纵观我们的鸟瞰图，我们看到实际的服务流量仍然直接从代理流向代理，但是控制平面知道每个代理实例。控制平面使代理可以实现访问控制和指标收集之类的事情，这需要合作：
 ![](https://img-blog.csdnimg.cn/20210130190613379.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 最近开源的Istio项目是此类系统的最突出示例。
 
-# 3 ServiceMesh 的功能
-## 流量控制
-路由：
-蓝绿部署
-灰度发布
-A/B测试
-
-流量转移
-超时重试
-熔断
-故障注入
-流量镜像
-
-## 策略
-流量控制
-黑白名单
-## 网络安全
-授权及身份认证
-## 可观察性
-指标收集和展示
-日志收集
-分布式追踪
-
-# 4 辨析
-## 4.1 V.S k8s
-### k8s
-负责容器编排调度，本质是管理应用的生命周期，即调度器。给予Service Mesh支持和帮助，因为其 pod 是最小的调度单元，先天支持多容器的部署，便于植入 sidecar 服务。
-### Service Mesh
-解决服务间网络通信问题，本质上是管理服务通信，即代理。是对 k8s 网络功能方面的扩展延伸
-
-## 4.2 V.S API网关
-### API 网关
-- API网关实际上部署在应用的边界，没有侵入到应用内部，主要是对内部的 API 进行聚合和抽象以方便外部的调用，对流量进行抽象。
-![](https://img-blog.csdnimg.cn/20210131143829274.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-API网关有负载均衡、服务发现、流量控制等作用。
-### Service Mesh
-
-- 由Sidecar完全接管发送到应用的请求，处理完成之后再发送到另外的微服务，是对应用内部的网络细节进行描述。![](https://img-blog.csdnimg.cn/20210131143739279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-所以综合来看，功能有重叠，但角色不同。Service Mesh在应用内，API网关在应用之上(边界)。
-
-# 5 Service Mesh标准
-## UDPA ( Universal Data Plane API )
-统一数据平面 API，为不同的数据平面提供统一的 API，方便你的无缝接入，比如 Envoy、Linkerd等数据平面。有了 UDPA 就无需关心他们的实现细节，按标准接入即可。 该标准由云原生基金会提出。
-![](https://img-blog.csdnimg.cn/20210131144215657.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## SMI ( Service Mesh Interface )
-侧重于数据控制平面，为用户提供一个统一的使用体验。 你也发现了，Google 和 aws 并没有参与。![](https://img-blog.csdnimg.cn/20210131144225957.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# Service Mesh产品发展史
-![](https://img-blog.csdnimg.cn/20210131145554362.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-## Linkerd
-第一个Service Mesh产品
-2016年底在GitHub发布 0x
-2017年加入CNCF，4月发布1.0版本
-Conduit - Linkerd2.0: 支持 k8s，轻量级，但是用的 rust，没人会，没人用，无疾而终。
-## Envoy
-2016年9月发布，定位Sidecar代理，第3个从CNCF毕业的产品。
-稳定可靠，性能出众，Istio的默认数据平面，xDS协议成为数据平面的事实标准。
-
-## Istio
-2017年5月发布0.1版本，主角光环：Google, IBM，Lyft 背书。第二代Service Mesh，增加了控制平面，奠定目前Service Mesh的产品形态。
-收购Envoy，直接拥有高水准的数据平面，受到社区强烈追捧。
-
-## AWS App Mesh
-- 2018年re:Invent公布
-- 2019年4月GA发布
-- 支持自家的多种计算资源的部署
-![](https://img-blog.csdnimg.cn/20210131150603901.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## 国内现状
-- 蚂蚁金服: SOFA Mesh, MOSN数据平面
-- 几大云厂商(腾讯、阿里、百度)
-- 华为、微博
-
-## 国际竞争
-- 构建云原生应用的重要一环，巨头云厂商都很重视(市场考虑)
-![](https://img-blog.csdnimg.cn/20210131151105655.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-
-# 总结
+#  3 总结
 ![](https://img-blog.csdnimg.cn/20210130190442747.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 全面理解ServiceMesh在大规模系统中的影响还为时过早。总之这种方法有两个好处
 - 不必编写定制软件来处理微服务体系结构的最终商品代码，这将使许多小型组织可以享受以前仅大型企业才能使用的功能，从而创建各种有趣的用例
diff --git "a/\344\272\221\345\216\237\347\224\237/ServiceMesh/\345\244\252\345\274\272\344\272\206,Istio\347\253\237\347\204\266\346\234\211\350\277\231\344\271\210\345\244\232\345\212\237\350\203\275!.md" "b/\344\272\221\345\216\237\347\224\237/ServiceMesh/\345\244\252\345\274\272\344\272\206,Istio\347\253\237\347\204\266\346\234\211\350\277\231\344\271\210\345\244\232\345\212\237\350\203\275!.md"
deleted file mode 100644
index a2097c720d..0000000000
--- "a/\344\272\221\345\216\237\347\224\237/ServiceMesh/\345\244\252\345\274\272\344\272\206,Istio\347\253\237\347\204\266\346\234\211\350\277\231\344\271\210\345\244\232\345\212\237\350\203\275!.md"
+++ /dev/null
@@ -1,256 +0,0 @@
-# 1 简介
-Istio，希腊语，意扬帆起航。
-![](https://img-blog.csdnimg.cn/20210131152226973.png)
-
-一个完全开源的**服务网格**产品，对分布式应用是透明的。Istio 管理服务之间的流量，实施访问政策并汇总遥测数据，而不需要更改应用代码。Istio 以透明的方式对现有分布式应用进行分层，从而简化了部署复杂性。
-
-也是一个平台，可与任何日志、遥测和策略系统集成。
-服务于微服务架构，并提供保护、连接和监控微服务的统一方法。
-
-在原有的数据平面的基础上，增加了控制平面。
-
-## 为什么会火
-- 发布及时(2017年5月发布0.1版本)
-- 巨头厂商buff 加持
-- 第二代Service Mesh
-- Envoy的加入让Istio如虎添翼
-- 功能强大
-
-## 优点
-- 轻松构建服务网格
-- 应用代码无需更改
-- 功能强大
-
-# 2 核心功能
-## 2.1 流量控制
-路由、流量转移
-流量进出
-网络弹性能力
-测试相关
-
-### 2.1.1 核心资源(CRD) 
-虚拟服务（Virtual Service） 和目标规则（Destination Rule） 是 Istio 流量路由功能的关键拼图。
-#### 2.1.1.1 虚拟服务( Virtual Service )
-![](https://img-blog.csdnimg.cn/20210131163938411.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-虚拟服务让你配置如何在服务网格内将请求路由到服务，这基于 Istio 和平台提供的基本的连通性和服务发现能力。每个虚拟服务包含一组路由规则，Istio 按顺序评估它们，Istio 将每个给定的请求匹配到虚拟服务指定的实际目标地址。您的网格可以有多个虚拟服务，也可以没有，取决于使用场景。
-
-- 将流量路由到给定目标地址
-- 请求地址与真实的工作负载解耦
-- 包含一组路由规则
-- 通常和目标规则( Destination Rule)成对出现
-- 丰富的路由匹配规则
-
-####  2.1.1.2 目标规则( Destination Rule)
-定义虚拟服务路由目标地址的真实地址，即子集。
-设置负载均衡的方式
-- 随机
-- 权重
-- 最少请求数
-
-####  2.1.1.3 网关(Gateway)
-![](https://img-blog.csdnimg.cn/20210131170442239.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-> Egress 不一定使用。
-
-
-#### 服务入口 (Service Entry)
-- 使用服务入口（Service Entry） 来添加一个入口到 Istio 内部维护的服务注册中心，即把外部服务注册到网格中。
-![](https://img-blog.csdnimg.cn/20210131170618942.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-添加了服务入口后，Envoy 代理可以向服务发送流量，就好像它是网格内部的服务一样。
-
-配置服务入口允许您管理运行在网格外的服务的流量，它包括以下几种能力：
-- 为外部目标 redirect 和转发请求，例如来自 web 端的 API 调用，或者流向遗留老系统的服务。
-- 为外部目标定义重试、超时和故障注入策略。
-- 添加一个运行在虚拟机的服务来扩展您的网格。
-- 从逻辑上添加来自不同集群的服务到网格，在 Kubernetes 上实现一个多集群 Istio 网格。
-
-你不需要为网格服务要使用的每个外部服务都添加服务入口。默认情况下，Istio 配置 Envoy 代理将请求传递给未知服务。但是，您不能使用 Istio 的特性来控制没有在网格中注册的目标流量。
-#### Sidecar
-![](https://img-blog.csdnimg.cn/20210131170830502.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-默认情况下，Istio 让每个 Envoy 代理都可以访问来自和它关联的工作负载的所有端口的请求，然后转发到对应的工作负载。您可以使用 sidecar 配置去做下面的事情：
-- 微调 Envoy 代理接受的端口和协议集。
-- 限制 Envoy 代理可以访问的服务集合。
-
-你可能希望在较庞大的应用程序中限制这样的 sidecar 可达性，配置每个代理能访问网格中的任意服务可能会因为高内存使用量而影响网格的性能。
-
-您可以指定将 sidecar 配置应用于特定命名空间中的所有工作负载，或者使用 workloadSelector 选择特定的工作负载。
-
-### 2.1.2 网络弹性和测试
-除了为你的网格导流，Istio 还提供了可选的故障恢复和故障注入功能，使你可以在运行时动态配置这些功能。使用这些特性可以让应用程序运行稳定，确保服务网格能够容忍故障节点，并防止局部故障级联影响到其他节点。
-#### 超时
-超时是 Envoy 代理等待来自给定服务的答复的时间量，以确保服务不会因为等待答复而无限期的挂起，并在可预测的时间范围内调用成功或失败。HTTP 请求的默认超时时间是 15 秒，这意味着如果服务在 15 秒内没有响应，调用将失败。
-
-对于某些应用程序和服务，Istio 的缺省超时可能不合适。例如，超时太长可能会由于等待失败服务的回复而导致过度的延迟；而超时过短则可能在等待涉及多个服务返回的操作时触发不必要地失败。为了找到并使用最佳超时设置，Istio 允许您使用虚拟服务按服务轻松地动态调整超时，而不必修改您的业务代码。
-
-#### 重试
-重试设置指定如果初始调用失败，Envoy 代理尝试连接服务的最大次数。通过确保调用不会因为临时过载的服务或网络等问题而永久失败，重试可以提高服务可用性和应用程序的性能。重试之间的间隔（25ms+）是可变的，并由 Istio 自动确定，从而防止被调用服务被请求淹没。HTTP 请求的默认重试行为是在返回错误之前重试两次。
-
-与超时一样，Istio 默认的重试行为在延迟方面可能不适合您的应用程序需求（对失败的服务进行过多的重试会降低速度）或可用性。您可以在虚拟服务中按服务调整重试设置，而不必修改业务代码。您还可以通过添加每次重试的超时来进一步细化重试行为，并指定每次重试都试图成功连接到服务所等待的时间量。
-
-#### 熔断器
-熔断器是 Istio 为创建具有弹性的微服务应用提供的另一个有用的机制。在熔断器中，设置一个对服务中的单个主机调用的限制，例如并发连接的数量或对该主机调用失败的次数。一旦限制被触发，熔断器就会“跳闸”并停止连接到该主机。使用熔断模式可以快速失败而不必让客户端尝试连接到过载或有故障的主机。
-
-熔断适用于在负载均衡池中的“真实”网格目标地址，您可以在目标规则中配置熔断器阈值，让配置适用于服务中的每个主机
-#### 故障注入
-在配置了网络，包括故障恢复策略之后，可使用 Istio 的故障注入机制来为整个应用程序测试故障恢复能力。故障注入是一种将错误引入系统以确保系统能够承受并从错误条件中恢复的测试方法。使用故障注入特别有用，能确保故障恢复策略不至于不兼容或者太严格，这会导致关键服务不可用。
-
-与其他错误注入机制（如延迟数据包或在网络层杀掉 Pod）不同，Istio 允许在应用层注入错误。这使您可以注入更多相关的故障，例如 HTTP 错误码，以获得更多相关的结果。
-
-可以注入两种故障，它们都使用虚拟服务配置：
-- 延迟
-延迟是时间故障。它们模拟增加的网络延迟或一个超载的上游服务。
-- 终止
-终止是崩溃失败。他们模仿上游服务的失败。终止通常以 HTTP 错误码或 TCP 连接失败的形式出现。
-
-### 流量镜像
-流量镜像，也称为影子流量，是一个以尽可能低的风险为生产带来变化的强大的功能。镜像会将实时流量的副本发送到镜像服务。镜像流量发生在主服务的关键请求路径之外。
-
-在此任务中，首先把流量全部路由到 v1 版本的测试服务。然后，执行规则将一部分流量镜像到 v2 版本。
-
-## 2.2 可观察性
-### 可观察性≠监控
-监控是指从运维角度，被动地审视系统行为和状态，是在系统之外探查系统的运行时状态。
-而可观察性是从开发者的角度主动地探究系统的状态，开发过程中去考虑把哪些系统指标暴露出去。而在原始时期的我们都是通过日志查看系统运行时状态，所以这也是一种理念创新。
-
-### 组成
-#### 指标（Metrics）
-通过聚合的数据来监测你的应用运行情况。为了监控服务行为，Istio 为服务网格中所有出入的服务流量都生成了指标。这些指标提供了关于行为的信息，例如总流量数、错误率和请求响应时间。
-
-除了监控网格中服务的行为外，监控网格本身的行为也很重要。Istio 组件可以导出自身内部行为的指标，以提供对网格控制平面的功能和健康情况的洞察能力。
-
-Istio 指标收集由运维人员配置来驱动。运维人员决定如何以及何时收集指标，以及指标本身的详细程度。这使得它能够灵活地调整指标收集来满足个性化需求。
-Istio中的指标分类:
-##### 代理级别的指标( Proxy-level)
-Istio 指标收集从 **sidecar 代理(Envoy)** 开始。每个代理为通过它的所有流量（入站和出站）生成一组丰富的指标。代理还提供关于它本身管理功能的详细统计信息，包括配置信息和健康信息。
-
-Envoy 生成的指标提供了**资源（例如监听器和集群）粒度**上的网格监控。因此，为了监控 Envoy 指标，需要了解网格服务和 Envoy 资源之间的连接。
-
-Istio 允许运维在每个工作负载实例上**选择生成和收集哪个 Envoy 指标**。默认情况下，Istio 只支持 Envoy 生成的统计数据的一小部分，以避免依赖过多的后端服务，还可以减少与指标收集相关的 CPU 开销。然而，运维可以在需要时轻松地扩展收集到的代理指标集。这支持有针对性地调试网络行为，同时降低了跨网格监控的总体成本。
-
-Envoy 文档包括了 Envoy 统计信息收集的详细说明。Envoy 统计里的操作手册提供了有关控制代理级别指标生成的更多信息。
-
-代理级别指标的例子：
-
-```bash
-# 当前集群中来自于上游服务的总的请求数
-envoy_cluster_internal_upstream_rq{response_code_class="2xx",
-	cluster_name="xds-grpc"} 7163
-
-# 上游服务完成的请求数量
-envoy_cluster_upstream_rq_completed{cluster_name="xds-grpc"} 7164
-# 是SSL连接出错的数量
-envoy_cluster_ssl_connection_error{cluster_name="xds-grpc"} 0
-```
-##### 服务级别的指标( Service-level)
-监控服务通信的面向服务的指标。
-- 四个基本的服务监控需求：延迟、流量、错误和饱和情况。Istio 带有一组默认的仪表板，用于监控基于这些指标的服务行为。
-- 默认的 Istio 指标由 Istio 提供的配置集定义并默认导出到 Prometheus。运维人员可以自由地修改这些指标的形态和内容，更改它们的收集机制，以满足各自的监控需求。
-收集指标任务为定制 Istio 指标生成提供了更详细的信息。
-
-- 服务级别指标的使用完全是可选的。运维人员可以选择关闭指标的生成和收集来满足自身需要。
-
-服务级别指标的例子
-
-```bash
-istio_requests_total{
-  connection_security_policy="mutual_tls",
-  destination_app="details",
-  destination_principal="cluster.local/ns/default/sa/default",
-  destination_service="details.default.svc.cluster.local",
-  destination_service_name="details",
-  destination_service_namespace="default",
-  destination_version="v1",
-  destination_workload="details-v1",
-  destination_workload_namespace="default",
-  reporter="destination",
-  request_protocol="http",
-  response_code="200",
-  response_flags="-",
-  source_app="productpage",
-  source_principal="cluster.local/ns/default/sa/default",
-  source_version="v1",
-  source_workload="productpage-v1",
-  source_workload_namespace="default"
-} 214
-```
-
-#####  控制平面指标(Control plane )
-每一个 Istio 的组件（Pilot、Galley、Mixer）都提供了对自身监控指标的集合。这些指标容许监控 Istio 自己的行为（这与网格内的服务有所不同）。
-
-#### 访问日志
-通过应用产生的事件来监控你的应用。
-访问日志提供了一种从单个工作负载实例的角度监控和理解行为的方法。
-Istio 可以从一组可配置的格式集生成服务流量的访问日志，为运维人员提供日志记录的方式、内容、时间和位置的完全控制。Istio 向访问日志机制暴露了**完整的源和目标元数据**，允许对网络通信进行详细的审查。
-- 生成位置可选
-访问日志可以在本地生成，或者导出到自定义的后端基础设施，包括 Fluentd。
-
-- 日志内容
-应用日志
-Envoy 服务日志：`kubectl logs -l app=demo -C istio-proxy`
-
-Istio 访问日志例子（JSON 格式）：
-
-```bash
-{
-"level": "info",
-"time": "2019-06-11T20:57:35.424310Z",
-"instance": "accesslog.instance.istio-control",
-"connection_security_policy": "mutual_tls",
-"destinationApp": "productpage",
-"destinationIp": "10.44.2.15",
-"destinationName": "productpage-v1-6db7564db8-pvsnd",
-"destinationNamespace": "default",
-"destinationOwner": "kubernetes://apis/apps/v1/namespaces/default/deployments/productpage-v1",
-"destinationPrincipal": "cluster.local/ns/default/sa/default",
-"destinationServiceHost": "productpage.default.svc.cluster.local",
-"destinationWorkload": "productpage-v1",
-"httpAuthority": "35.202.6.119",
-"latency": "35.076236ms",
-"method": "GET",
-"protocol": "http",
-"receivedBytes": 917,
-"referer": "",
-"reporter": "destination",
-"requestId": "e3f7cffb-5642-434d-ae75-233a05b06158",
-"requestSize": 0,
-"requestedServerName": "outbound_.9080_._.productpage.default.svc.cluster.local",
-"responseCode": 200,
-"responseFlags": "-",
-"responseSize": 4183,
-"responseTimestamp": "2019-06-11T20:57:35.459150Z",
-"sentBytes": 4328,
-"sourceApp": "istio-ingressgateway",
-"sourceIp": "10.44.0.8",
-"sourceName": "ingressgateway-7748774cbf-bvf4j",
-"sourceNamespace": "istio-control",
-"sourceOwner": "kubernetes://apis/apps/v1/namespaces/istio-control/deployments/ingressgateway",
-"sourcePrincipal": "cluster.local/ns/istio-control/sa/default",
-"sourceWorkload": "ingressgateway",
-"url": "/productpage",
-"userAgent": "curl/7.54.0",
-"xForwardedFor": "10.128.0.35"
-}
-```
-
-#### 分布式追踪
-通过追踪请求来了解服务之间的调用关系，用于问题的排查以及性能分析。
-分布式追踪通过监控流经网格的单个请求，提供了一种监控和理解行为的方法。追踪使网格的运维人员能够理解服务的依赖关系以及在服务网格中的延迟源。
-
-Istio 支持通过 Envoy 代理进行分布式追踪。代理自动为其应用程序生成追踪 span，只需要应用程序转发适当的请求上下文即可。
-
-Istio 支持很多追踪系统，包括 Zipkin、Jaeger、LightStep、Datadog。运维人员控制生成追踪的采样率（每个请求生成跟踪数据的速率）。这允许运维人员控制网格生成追踪数据的数量和速率。
-
-更多关于 Istio 分布式追踪的信息可以在分布式追踪 FAQ 中找到。
-
-Istio 为一个请求生成的分布式追踪数据：
-![](https://img-blog.csdnimg.cn/20210131230904389.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## 网络安全
-授权及身份认证
-
-## 策略
-限流
-黑白名单
-
-> 参考
-> - https://istio.io/latest/zh/docs/concepts/traffic-management/#network-resilience-and-testing
-> - https://istio.io/latest/zh/docs/concepts/observability/
\ No newline at end of file
diff --git "a/\345\244\247\346\225\260\346\215\256/Scala/Scala-\345\205\245\351\227\250.md" "b/\345\244\247\346\225\260\346\215\256/Scala/Scala-\345\205\245\351\227\250.md"
index cb19270ca5..3cc41ffdcf 100644
--- "a/\345\244\247\346\225\260\346\215\256/Scala/Scala-\345\205\245\351\227\250.md"
+++ "b/\345\244\247\346\225\260\346\215\256/Scala/Scala-\345\205\245\351\227\250.md"
@@ -1,5 +1,6 @@
 # 1 函数式编程思想
 ## 1.1 介绍
+![](https://upload-images.jianshu.io/upload_images/4685968-2217fa78d2aae90a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ![](https://upload-images.jianshu.io/upload_images/4685968-339e97ac6c2f74e1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ![](https://upload-images.jianshu.io/upload_images/4685968-102c9902ed240289.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ![](https://upload-images.jianshu.io/upload_images/4685968-3c5db14174facab2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
diff --git "a/\345\244\247\346\225\260\346\215\256/Spark/MLlib/Spark\346\234\272\345\231\250\345\255\246\344\271\240(1)-\345\210\235\350\257\206\346\234\272\345\231\250\345\255\246\344\271\240.md" "b/\345\244\247\346\225\260\346\215\256/Spark/MLlib/\345\237\272\344\272\216Spark\347\232\204\346\234\272\345\231\250\345\255\246\344\271\240\345\256\236\350\267\265 (\344\270\200) - \345\210\235\350\257\206\346\234\272\345\231\250\345\255\246\344\271\240.md"
similarity index 77%
rename from "\345\244\247\346\225\260\346\215\256/Spark/MLlib/Spark\346\234\272\345\231\250\345\255\246\344\271\240(1)-\345\210\235\350\257\206\346\234\272\345\231\250\345\255\246\344\271\240.md"
rename to "\345\244\247\346\225\260\346\215\256/Spark/MLlib/\345\237\272\344\272\216Spark\347\232\204\346\234\272\345\231\250\345\255\246\344\271\240\345\256\236\350\267\265 (\344\270\200) - \345\210\235\350\257\206\346\234\272\345\231\250\345\255\246\344\271\240.md"
index c575ee43b5..09ca207968 100644
--- "a/\345\244\247\346\225\260\346\215\256/Spark/MLlib/Spark\346\234\272\345\231\250\345\255\246\344\271\240(1)-\345\210\235\350\257\206\346\234\272\345\231\250\345\255\246\344\271\240.md"
+++ "b/\345\244\247\346\225\260\346\215\256/Spark/MLlib/\345\237\272\344\272\216Spark\347\232\204\346\234\272\345\231\250\345\255\246\344\271\240\345\256\236\350\267\265 (\344\270\200) - \345\210\235\350\257\206\346\234\272\345\231\250\345\255\246\344\271\240.md"	
@@ -5,13 +5,25 @@
 ## 1.2 提高竞争力必备
 ![](https://upload-images.jianshu.io/upload_images/16782311-b4f7a63bb90d78b9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
-## 1.3  预备知识
+## 1.3 教程规划
+![](https://upload-images.jianshu.io/upload_images/16782311-f5b26a645b8b7872.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-1dbdee413480deaf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-42f1e89564a43232.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-24d3dda73057c8c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-010b73a47220a05c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-453392fed649e5a1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-f3995056caf205b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-00d28fe35034ccd8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-cdc77c1695ff0288.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/16782311-058e2791d0bcd83b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 1.7  预备知识
 - 了解大数据相关基础知识
 - 熟悉Linux基本命令
 - 熟悉Scala语言的编程方法
 - 有一定的数学基础
 
-## 1.4  环境参数
+## 1.8  环境参数
 - Spark : 2.3.0
 - JDK : 1.8
 - IDE : IDEA
@@ -157,4 +169,14 @@ Spark 可直接适用在生产环境
 ◆考虑研发团队情况,力求技术栈精简统一,避免冗杂
 
 # 参考
-[wiki/机器学习](https://zh.wikipedia.org/wiki/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0)
\ No newline at end of file
+[wiki/机器学习](https://zh.wikipedia.org/wiki/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0)
+
+# X 联系我
+![](http://upload-images.jianshu.io/upload_images/4685968-6a8b28d2fd95e8b7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "图片标题") 
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [知乎](http://www.zhihu.com/people/shi-shu-sheng-)
+
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\345\244\247\346\225\260\346\215\256/\345\217\262\344\270\212\346\234\200\345\277\253!-10\345\260\217\346\227\266\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250\345\256\236\346\210\230(\344\272\224)-\345\210\206\345\270\203\345\274\217\350\256\241\347\256\227\346\241\206\346\236\266MapReduce.md" "b/\345\244\247\346\225\260\346\215\256/\345\217\262\344\270\212\346\234\200\345\277\253!-10\345\260\217\346\227\266\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250\345\256\236\346\210\230(\344\272\224)-\345\210\206\345\270\203\345\274\217\350\256\241\347\256\227\346\241\206\346\236\266MapReduce.md"
new file mode 100644
index 0000000000..b552a051cb
--- /dev/null
+++ "b/\345\244\247\346\225\260\346\215\256/\345\217\262\344\270\212\346\234\200\345\277\253!-10\345\260\217\346\227\266\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250\345\256\236\346\210\230(\344\272\224)-\345\210\206\345\270\203\345\274\217\350\256\241\347\256\227\346\241\206\346\236\266MapReduce.md"
@@ -0,0 +1,48 @@
+![目录](https://upload-images.jianshu.io/upload_images/4685968-d1ad6dbf94d38f20.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 1 MapReduce概述
+![](https://upload-images.jianshu.io/upload_images/4685968-8a3f8a5a26992bb9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 2 MapReduce编程模型之通过wordcount词频统计分析案例入门
+![](https://upload-images.jianshu.io/upload_images/4685968-7563d21e44338bbd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# MapReduce执行流程
+![](https://upload-images.jianshu.io/upload_images/4685968-f92ef9dac3edcb2a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-49d9b19a457d7847.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![](https://upload-images.jianshu.io/upload_images/4685968-e1f014833eae5eb8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- InputFormat
+![](https://upload-images.jianshu.io/upload_images/4685968-1907b9114cc42568.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-3ac372af0238dc7b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-2f4a418b7d835e6b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-b3e573bdacdb712d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- OutputFormat
+OutputFormt接口决定了在哪里以及怎样持久化作业结果。Hadoop为不同类型的格式提供了一系列的类和接口，实现自定义操作只要继承其中的某个类或接口即可。你可能已经熟悉了默认的OutputFormat，也就是TextOutputFormat，它是一种以行分隔，包含制表符界定的键值对的文本文件格式。尽管如此，对多数类型的数据而言，如再常见不过的数字，文本序列化会浪费一些空间，由此带来的结果是运行时间更长且资源消耗更多。为了避免文本文件的弊端，Hadoop提供了SequenceFileOutputformat，它将对象表示成二进制形式而不再是文本文件，并将结果进行压缩。
+# 3 MapReduce核心概念
+![](https://upload-images.jianshu.io/upload_images/4685968-28c6a0131a2dcc95.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ae3ef868da0913d0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## 3.1 Split
+![](https://upload-images.jianshu.io/upload_images/4685968-d6bf69ad9f81e9e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 3.2 InputFormat
+# 4 MapReduce 1.x 架构
+![](https://upload-images.jianshu.io/upload_images/4685968-fb32aecae4a71f2f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![](https://upload-images.jianshu.io/upload_images/4685968-decb115b06993cc7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-a282262365c87c60.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-b232e0cc860fd46d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-f4c54d1443a85677.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 5 MapReduce 2.x 架构
+![](https://upload-images.jianshu.io/upload_images/4685968-263326493524cfda.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 6 Java 实现 wordCount
+![](https://upload-images.jianshu.io/upload_images/4685968-b2f88b1b8ad3d584.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![clean package](https://upload-images.jianshu.io/upload_images/4685968-d81f900ba386685c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![上传到Hadoop服务器](https://upload-images.jianshu.io/upload_images/4685968-7fb51bf009c4e2db.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![全路径没有问题](https://upload-images.jianshu.io/upload_images/4685968-4c23190abdf6b39e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-8f625f9805d6e160.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 7 重构
+![](https://upload-images.jianshu.io/upload_images/4685968-e23904523132b5f9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 8 Combiner编程
+![](https://upload-images.jianshu.io/upload_images/4685968-49b3ab702137d23e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 9 Partitoner
+![](https://upload-images.jianshu.io/upload_images/4685968-bd17e7b57d287240.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-4572a64d4f8206b0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 10 JobHistoryServer
+
diff --git "a/\345\244\247\346\225\260\346\215\256/\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250(\344\272\214) - \345\210\235\346\216\242Hadoop.md" "b/\345\244\247\346\225\260\346\215\256/\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250(\344\272\214) - \345\210\235\346\216\242Hadoop.md"
index 8ff98031ee..042705908f 100644
--- "a/\345\244\247\346\225\260\346\215\256/\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250(\344\272\214) - \345\210\235\346\216\242Hadoop.md"	
+++ "b/\345\244\247\346\225\260\346\215\256/\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250(\344\272\214) - \345\210\235\346\216\242Hadoop.md"	
@@ -2,7 +2,7 @@
 ![](https://upload-images.jianshu.io/upload_images/4685968-4bbf5f7aae048c30.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 # 1 简介
-- 名字由来
+- 名字的由来
 Hadoop这个名字不是一个缩写，而是一个虚构的名字。该项目的创建者，Doug Cutting解释Hadoop的得名 ：“这个名字是我孩子给一个棕黄色的大象玩具命名的。我的命名标准就是简短，容易发音和拼写，没有太多的意义，并且不会被用于别处。小孩子恰恰是这方面的高手。”
 
 - 介绍
@@ -64,22 +64,22 @@ YARN是Yet Another Resource Negotiator的首字母缩写，意为 “另一种
 从上图中可以看到，YARN可以统一调度多种不同的框架，这样不管对运维人员还是开发人员来说，都减轻了不少负担.
 
 ## 3.3 MapReduce
-Hadoop核心组件之一，用于实现分布式并行计算。
+MapReduce是Hadoop核心组件之一，它用于实现分布式并行计算。Hadoop中的MapReduce源自于Google的MapReduce论文，论文发表于2004年12月。MapReduce其实就是Google MapReduce的克隆版，是Google MapReduce的开源实现。
 
-### 特点
+MapReduce特点：
 - 扩展性，如果机器的计算能力不够用，则可以以增加机器的方式来提升集群的计算能力
 - 容错性，当某个计算节点的机器挂掉，会把任务分配给其他节点的机器完成
 - 海量数据离线处理
 
-### 计算流程 - 统计词频
+下图简单展示了MapReduce的计算流程：
 ![](https://upload-images.jianshu.io/upload_images/4685968-2f0d9fcc6e84e6c7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
-
-- Input环节，输入文本
-- Splitting环节，按空格分割每个单词
-- Mapping环节，相同单词都映射到同一节点
-- Shuffling环节，洗牌数据
-- Reducing环节，数据整合
-- 最终结果写入一个文件
+上图是一个统计词频的计算流程
+- 在Input环节中，我们把文本进行输入
+- 然后在Splitting环节按照空格分割每个单词
+- 接着在Mapping环节中把相同的单词都映射到同一个节点上
+- 到了Shuffling环节就会对数据进行洗牌
+- 最后到Reducing环节进行数据的整合
+- 并将最终的结果写入到一个文件中
 
 # 4 Hadoop优势
 ## 4.1  高可靠性
diff --git "a/\345\244\247\346\225\260\346\215\256/\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250(\344\272\224)-\345\210\206\345\270\203\345\274\217\350\256\241\347\256\227\346\241\206\346\236\266MapReduce.md" "b/\345\244\247\346\225\260\346\215\256/\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250(\344\272\224)-\345\210\206\345\270\203\345\274\217\350\256\241\347\256\227\346\241\206\346\236\266MapReduce.md"
deleted file mode 100644
index 0282e5cbcc..0000000000
--- "a/\345\244\247\346\225\260\346\215\256/\345\244\247\346\225\260\346\215\256\345\205\245\351\227\250(\344\272\224)-\345\210\206\345\270\203\345\274\217\350\256\241\347\256\227\346\241\206\346\236\266MapReduce.md"
+++ /dev/null
@@ -1,66 +0,0 @@
-# 1 概述
-源自于Google的MapReduce论文，发表于2004年12月。
-
-Hadoop MapReduce是Google MapReduce的克隆版
-## 优点
-海量数量离线处理
-易开发
-易运行
-## 缺点
-实时流式计算
-
-# 2 MapReduce编程模型
-## wordcount词频统计
-![](https://img-blog.csdnimg.cn/img_convert/d30aef6db2174113c919b20226580eed.png)
-# MapReduce执行流程
-- 将作业拆分成Map阶段和Reduce阶段
-- Map阶段: Map Tasks
-- Reduce阶段、: Reduce Tasks
-## MapReduce编程模型执行步骤
-- 准备map处理的输入数据
-- Mapper处理
-- Shuffle
-- Reduce处理
-- 结果输出
-![](https://img-blog.csdnimg.cn/img_convert/b3514b6cf271ddcffe0b15c1c5a9521d.png)
-- InputFormat
-![](https://img-blog.csdnimg.cn/img_convert/fd11b71639ce1170689a5265dd1b525d.png)
-![](https://img-blog.csdnimg.cn/img_convert/da4cd5f1a39b89744943351ea7897f71.png)
-![](https://img-blog.csdnimg.cn/img_convert/1d604b7e3f0846ce6529d0f603c983d4.png)
-![](https://img-blog.csdnimg.cn/img_convert/74a2e3d81bbb2a2a0781697e85a72f30.png)
-
-#### OutputFormat
-OutputFormt接口决定了在哪里以及怎样持久化作业结果。Hadoop为不同类型的格式提供了一系列的类和接口，实现自定义操作只要继承其中的某个类或接口即可。你可能已经熟悉了默认的OutputFormat，也就是TextOutputFormat，它是一种以行分隔，包含制表符界定的键值对的文本文件格式。尽管如此，对多数类型的数据而言，如再常见不过的数字，文本序列化会浪费一些空间，由此带来的结果是运行时间更长且资源消耗更多。为了避免文本文件的弊端，Hadoop提供了SequenceFileOutputformat，它将对象表示成二进制形式而不再是文本文件，并将结果进行压缩。
-# 3 核心概念
-Split
-InputFormat
-OutputFormat
-Combiner
-Partitioner
-![](https://img-blog.csdnimg.cn/img_convert/b23a2498ad2bb7feef2f9cf94f652d70.png)
-
-## 3.1 Split
-![](https://img-blog.csdnimg.cn/img_convert/1f8b94642f966347e5fbc19579f3a8b5.png)
-## 3.2 InputFormat
-# 4 MapReduce 1.x 架构
-![](https://img-blog.csdnimg.cn/img_convert/978682a09b5d5e6e4c849aade6714cb3.png)
-
-![](https://img-blog.csdnimg.cn/img_convert/4d8cba7a246ba45e895e6b0aac5826c2.png)
-![](https://img-blog.csdnimg.cn/img_convert/1358b4bbd10d5ffcf4cf4f3f28d63b64.png)
-![](https://img-blog.csdnimg.cn/img_convert/040342a516484c19a2ddac1c5216ed98.png)
-![](https://img-blog.csdnimg.cn/img_convert/c643cc847b1bca3a9616f6f9b7f605f5.png)
-# 5 MapReduce 2.x 架构
-![](https://img-blog.csdnimg.cn/img_convert/b3760e5dff90acb65e3c7c0ae1c9f81e.png)
-# 6 Java 实现 wordCount
-![](https://img-blog.csdnimg.cn/img_convert/db8dbeb277b8a8b4ed7bf9dd02f6993e.png)
-![clean package](https://img-blog.csdnimg.cn/img_convert/60c4b7482beb8c6d2db82e1298cda8b3.png)
-![上传到Hadoop服务器](https://img-blog.csdnimg.cn/img_convert/d75de8ab33fec0b89631c4a242d9b5ea.png)
-![全路径没有问题](https://img-blog.csdnimg.cn/img_convert/d7cf025e655cd15ffeac59f2b8df3d84.png)
-![](https://img-blog.csdnimg.cn/img_convert/dd9815d16b7b059ee5370081c8593f6b.png)
-# 7 重构
-![](https://img-blog.csdnimg.cn/img_convert/bc92c2418c746f4a52013bc7a10b4ec4.png)
-# 8 Combiner编程
-![](https://img-blog.csdnimg.cn/img_convert/ed77f8d1c4a79eb349e0b816e9b4d7b3.png)
-# 9 Partitoner
-![](https://img-blog.csdnimg.cn/img_convert/95c49f4305150ef0987d6f6733b7ddaf.png)
-![](https://img-blog.csdnimg.cn/img_convert/9cbb065c8bbba1da3972cbbe1eae4301.png)
\ No newline at end of file
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/Linux/Linux\345\277\205\345\244\207\345\221\275\344\273\244\351\233\206\345\220\210.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/Linux/Linux\345\277\205\345\244\207\345\221\275\344\273\244\351\233\206\345\220\210.md"
index 059eaeb8b2..0111ad7396 100644
--- "a/\346\223\215\344\275\234\347\263\273\347\273\237/Linux/Linux\345\277\205\345\244\207\345\221\275\344\273\244\351\233\206\345\220\210.md"
+++ "b/\346\223\215\344\275\234\347\263\273\347\273\237/Linux/Linux\345\277\205\345\244\207\345\221\275\344\273\244\351\233\206\345\220\210.md"
@@ -19,9 +19,9 @@ which [文件...]
 ```
 ### 实例
 使用指令"which"查看指令"bash"的绝对路径
-![](https://img-blog.csdnimg.cn/img_convert/1ff4d3e9ce5925c99b5961f18e58acc9.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797707_4685968-f799e89b19bd48b3.png)
 上面的指令执行后，输出信息如下所示：
-![](https://img-blog.csdnimg.cn/img_convert/620fbd4236c90d1e6900d372835fc03e.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797739_4685968-1f7df7394763eac3.png)
 
 ## 1.2 cp
 复制文件或目录
@@ -121,10 +121,10 @@ cat 也可以用来制作镜像文件。例如要制作软盘的镜像文件，
 `cat IMG_FILE > /dev/fd0`
 
 ## 1.5 more
-类似 `cat` ，不过会以一页页形式显示，更方便开发者逐页阅读，而最基本的指令就是按空格键（space）就往下一页显示，按 b 键就会往回（back）一页显示，而且还有搜寻字符串功能（类似 vi），使用中的说明文件，请按 h
+类似 `cat` ，不过会以一页一页的形式显示，更方便使用者逐页阅读，而最基本的指令就是按空白键（space）就往下一页显示，按 b 键就会往回（back）一页显示，而且还有搜寻字串的功能（与 vi 相似），使用中的说明文件，请按 h
 `more [-dlfpcsu] [-num] [+/pattern] [+linenum] [fileNames..] `
 ### 参数
-```bash
+```
 -num 一次显示的行数
 -d 提示使用者，在画面下方显示 [Press space to continue, 'q' to quit.] ，如果使用者按错键，则会显示 [Press 'h' for instructions.] 而不是 '哔' 声
 -l 取消遇见特殊字元 ^L（送纸字元）时会暂停的功能
@@ -139,22 +139,14 @@ fileNames 欲显示内容的文档，可为复数个数
 ```
 
 ### 实例
-最基本使用
-```bash
-more hello-world/Dockerfile
 ```
-
-![](https://img-blog.csdnimg.cn/2020121516562229.png)
-
 逐页显示 testfile 文档内容，如有连续两行以上空白行则以一行空白行显示。
-
-```bash
 more -s testfile
-```
+
 从第 20 行开始显示 testfile 之文档内容。
-```bash
 more +20 testfile
 ```
+
 ## 1.6 tac
 从最后一行开始显示内容，并将所有内容输出 
 
@@ -261,7 +253,7 @@ scp [可选参数] file_source file_target
 4. scp local_file remote_ip:remote_file 
 ```
 第1,2个指定了用户名，命令执行后需要再输入密码
-![](https://img-blog.csdnimg.cn/img_convert/cd668dce80ee7e1b1e6ffacf7477c3d8.png)
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337586_16782311-ec0ae36f2fb687ec.png)
 
 第1个仅指定了远程的目录，文件名字不变
 第2个指定了文件名
@@ -306,18 +298,19 @@ scp -P 4588 remote@www.runoob.com:/usr/local/sin.sh /home/administrator
 2.使用scp命令要确保使用的用户具有可读取远程服务器相应文件的权限，否则scp命令是无法起作用的。
 
 # 1.13 awk
-一种处理文本文件的语言，强大的文本分析工具。
-其名取自三位创始人 Alfred Aho、Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符。
+一种处理文本文件的语言，是一个强大的文本分析工具.
+
+之所以叫AWK是因为其取了三位创始人 Alfred Aho，Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符。
 
 ## 语法
-```bash
+```
 awk [选项参数] 'script' var=value file(s)
 或
 awk [选项参数] -f scriptfile var=value file(s)
 ```
 
 ## 选项参数说明：
-```bash
+```
 -F fs or --field-separator fs
 指定输入文件折分隔符，fs是一个字符串或者是一个正则表达式，如-F:。
 -v var=value or --asign var=value
@@ -346,46 +339,50 @@ awk [选项参数] -f scriptfile var=value file(s)
 ```
 
 ### 基本用法
-- `log.txt`文本内容
-![](https://img-blog.csdnimg.cn/img_convert/224cc8b2dca49078cb0c55a4aa8ed362.png)
-#### 用法一
-```bash
+- `log.txt`文本内容如下：
+
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337662_16782311-7c4d9149600e38b9.png)
+
+
+- 用法一
+```
 awk '{[pattern] action}' {filenames}   # 行匹配语句 awk '' 只能用单引号
 ```
-##### 实例
+#### 实例
 - 每行按空格或TAB分割，输出文本中的1、4项
-![](https://img-blog.csdnimg.cn/img_convert/510ecd1b1b446faae72aa516ecdafd54.png)
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361338000_16782311-7c8a68e0fc3290c7.png)
+
 - 格式化输出
-![](https://img-blog.csdnimg.cn/img_convert/051cf40b6eec126985a5ae814d9e7b3e.png)
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337739_16782311-ff6e06e5ae37d13b.png)
 
-#### 用法二
-```bash
+- 用法二：
+```
 awk -F  #-F相当于内置变量FS, 指定分割字符
 ```
-##### 实例
-- 使用`,`分割
-![](https://img-blog.csdnimg.cn/img_convert/6d70af9e815f82f0582103e1a77cf9af.png)
+#### 实例
+- 使用","分割
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337762_16782311-3bbacf96a97a1884.png)
 
-- 或使用内建变量
-![](https://img-blog.csdnimg.cn/img_convert/f7caa8b958f5260829b00abeb072376b.png)
+- 或者使用内建变量
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337654_16782311-47fbe52b5f9d0087.png)
 
-- 使用多个分隔符：先使用`空格`分割，然后对分割结果再使用`,`分割
-![](https://img-blog.csdnimg.cn/img_convert/e8394ceeb3727478934c77190b182fa1.png)
+- 使用多个分隔符.先使用空格分割，然后对分割结果再使用","分割
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337693_16782311-470c7668e8af839f.png)
 
 - 用法三
-```bash
+```
 awk -v  # 设置变量
 ```
 #### 实例
-![](https://img-blog.csdnimg.cn/img_convert/028e0ce945e8586da22da3298d3cd748.png)
-![](https://img-blog.csdnimg.cn/img_convert/392b004e1b46b8175d601c42cb8b5e71.png)
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337864_16782311-01ac3c6820d428fd.png)
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337679_16782311-caaaf8a11434a562.png)
 
-#### 用法四
-```bash
+- 用法四
+```
 awk -f {awk脚本} {文件名}
 ```
-##### 实例
-```bash
+#### 实例
+```
 awk -f cal.awk log.txt
 ```
 
@@ -425,7 +422,7 @@ tree [-aACdDfFgilnNpqstux][-I <范本样式>][-P <范本样式>][目录...]
 ```
 tree
 ```
-![](https://img-blog.csdnimg.cn/img_convert/01a3f664cc24383da1663a06bb400ce5.png)
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361338300_16782311-e6c9bacd21e17203.png)
 
 # 2 磁盘管理
 ## 2.1 pwd
@@ -499,24 +496,20 @@ df [选项]... [FILE]...
 
 ### 实例
 - 显示文件系统的磁盘使用情况统计：
-![](https://img-blog.csdnimg.cn/2020100519432138.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
+![](https://uploadfiles.nowcoder.com/images/20190916/5088755_1568601910877_95BD0D1D78F772066257C351235F9735 "图片标题") 
 第一列指定文件系统的名称
 第二列指定一个特定的文件系统1K-块 1K是1024字节为单位的总内存。用和可用列正在使用中，分别指定的内存量。
 使用列指定使用的内存的百分比，而最后一栏"安装在"指定的文件系统的挂载点。
 
 - 用一个-i选项的df命令的输出显示inode信息而非块使用量。
-![](https://img-blog.csdnimg.cn/20201005194347997.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
+![](https://uploadfiles.nowcoder.com/images/20190916/5088755_1568602617440_5A15A6A7AD23F48A48E27F854AC42AE7 "图片标题") 
 
 - 显示所有的信息:
-![](https://img-blog.csdnimg.cn/20201005194410316.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
+![](https://uploadfiles.nowcoder.com/images/20190916/5088755_1568602688912_C26AAE3739D0CC390F54DAB23F9C5BA3 "图片标题") 
 
 - 我们看到输出的末尾，包含一个额外的行，显示总的每一列。
 -h选项，通过它可以产生可读的格式df命令的输出：
-![](https://img-blog.csdnimg.cn/20201005194431679.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
+![](https://uploadfiles.nowcoder.com/images/20190916/5088755_1568602782036_F5C01B86C9B2C16F93A1E7BB5AAF91D9 "图片标题") 
 
 
 ## 2.6 mount
@@ -682,14 +675,14 @@ groupadd -g 344 linuxde
 ## 4.2 ps 
 显示当前进程 (process) 的状态
 -  -A 显示进程信息
-![](https://img-blog.csdnimg.cn/img_convert/b00a0739f0e51453623020cb118bb11f.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797789_4685968-512aff1e593d1599.png)
 -  -u user  显示指定用户信息
-![](https://img-blog.csdnimg.cn/img_convert/93f350372dc6e56e1f5c33deb7a9876b.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797781_4685968-7c7a71f3b9dfb4b3.png)
 - -ef  显示所有命令，连带命令行
-![](https://img-blog.csdnimg.cn/img_convert/530e8122cb9d5c722a8c1ded891da2be.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797799_4685968-e3ae42585f4e4276.png)
 - pstree |grep java
 查看进程树
-![](https://img-blog.csdnimg.cn/img_convert/a5c74ef6db8c9a0563e61d457ad85ded.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797755_4685968-bd8146a54c354da0.png)
 
 ## 4.3 ifconfig
 IP 地址配置，可以使用 setup 命令启动字符界面来配置
@@ -718,64 +711,25 @@ uname [-amnrsv][--help][--version]
 
 ### 实例
 - 显示系统信息
-![uname -a](https://img-blog.csdnimg.cn/img_convert/f10538eabf283a2688c3b8aca63c443d.png)
+![uname -a](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797876_4685968-08845792c0c83cdf.png)
 
 - 显示计算机类型
-![uname -m](https://img-blog.csdnimg.cn/img_convert/74c3c75a7f8d62af563d6049efadd9c3.png)
+![uname -m](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797869_4685968-9d7b208bab923bad.png)
 
 - 显示计算机名
-![](https://img-blog.csdnimg.cn/img_convert/5759288d0143be3c9c09e7dbccd95663.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797826_4685968-b852a808b54bc7f4.png)
 
 - 显示操作系统发行编号
-![](https://img-blog.csdnimg.cn/img_convert/7428842890670c9b417331464c5210ca.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797880_4685968-cb2a31c803610718.png)
 
 - 显示操作系统名称
-![](https://img-blog.csdnimg.cn/img_convert/e8e3f328d984457a482c02ce523ec387.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797934_4685968-480838647bcb1d54.png)
 
 - 显示系统时间
-![](https://img-blog.csdnimg.cn/img_convert/85dd7cdd6f5ce80ca0cdf0cf24616dd2.png)
-## 4.5 adduser
-用于新增使用者帐号或更新预设的使用者资料。
-
-adduser 与 useradd 指令为同一指令（经由符号连结 symbolic link）。
-
-使用权限：系统管理员。
-
-adduser是增加使用者。相对的，也有删除使用者的指令，userdel。语法:userdel [login ID]
-
-### 参数
-参数说明：
-
-- -c comment 新使用者位于密码档（通常是 /etc/passwd）的注解资料
-- -d home_dir 设定使用者的家目录为 home_dir ，预设值为预设的 home 后面加上使用者帐号 loginid
-- -e expire_date 设定此帐号的使用期限（格式为 YYYY-MM-DD），预设值为永久有效
-- -f inactive_time 范例：
-
-### 实例
-添加一个一般用户
-
-```bash
-# useradd kk //添加用户kk
-```
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797965_4685968-a9b950b9e56c507d.png)
 
-为添加的用户指定相应的用户组
-
-```bash
-# useradd -g root kk //添加用户kk，并指定用户所在的组为root用户组
-```
 
-创建一个系统用户
 
-```bash
-# useradd -r kk //创建一个系统用户kk
-```
-
-为新添加的用户指定/home目录
-
-```bash
-# useradd-d /home/myf kk //新添加用户kk，其home目录为/home/myf
-//当用户名kk登录主机时，系统进入的默认目录为/home/myf
-```
 
 ## rpm -aq|grep php
 查看安装介质
@@ -800,22 +754,23 @@ env |grep PATH
 # echo
 输出变量名
 
-# Linux 查看端口占用
+
+
+# Linux 查看端口占用情况可以使用 lsof 和 netstat 命令
 ## lsof
 lsof(list open files)是一个列出当前系统打开文件的工具.
 
-- 语法
+### 语法
 lsof 查看端口占用语法格式：
 ```
 lsof -i:端口号
 ```
 
-- 实例
+### 实例
 查看服务器 8000 端口的占用情况：
 
-## netstat
-- 查看端口
-![](https://img-blog.csdnimg.cn/20210221125322342.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+##netstat -npl
+查看端口
 
 # mv
 用来为文件或目录改名、或将文件或目录移入其它位置。
@@ -838,7 +793,7 @@ mv 目录名 文件名	:出错
 再如将/usr/student下的所有文件和目录移到当前目录下，命令行为：
 `$ mv /usr/student/*  . `
 
-# rm -r
+#rm -r
 递归删除， -f 表示 force
 
 >somefile
@@ -847,10 +802,10 @@ mv 目录名 文件名	:出错
 which java
 查看 java 进程对应的目录
 
-# who
+#who
 显示当前用户
 
-# users
+#users
 显示当前会话
 
 # zip -r filename.zip filesdir
@@ -988,7 +943,8 @@ du
 ## 6.1 top
 
 实时显示 process 的动态
-```bash
+
+```shell
 d : 改变显示的更新速度，或是在交谈式指令列( interactive command)按 s
 q : 没有任何延迟的显示速度，如果使用者是有 superuser 的权限，则 top 将会以最高的优先序执行
 c : 切换显示模式，共有两种模式，一是只显示执行档的名称，另一种是显示完整的路径与名称S : 累积模式，会将己完成或消失的子行程 ( dead child process ) 的 CPU time 累积起来
@@ -998,17 +954,13 @@ n : 更新的次数，完成后将会退出 top
 b : 批次档模式，搭配 "n" 参数一起使用，可以用来将 top 的结果输出到档案内
 ```
 
-显示进程信息
-
-# top
-显示完整命令
-# top -c
+top -c
 以批处理模式显示程序信息
 
-# top -b
+top -b
 以累积模式显示程序信息
 
-# top -S
+top -S
 设置信息更新次数
 
 top -n 2
@@ -1016,12 +968,12 @@ top -n 2
 //表示更新两次后终止更新显示
 设置信息更新时间
 
-# top -d 3
+top -d 3
 
 //表示更新周期为3秒
 显示指定的进程信息
 
-# top -p 139
+top -p 139
 
 //显示进程号为139的进程信息，CPU、内存占用率等
 显示更新十次后退出
@@ -1100,9 +1052,7 @@ Timer Unit：定时器
 
 systemctl list-units命令可以查看当前系统的所有 Unit 。
 
-```bash
 netstat -nap | grep port
-```
 
 # 7 系统设置
 ## 7.1  yum（ Yellow dog Updater, Modified）
@@ -1142,7 +1092,7 @@ yum clean, yum clean all (= yum clean packages; yum clean oldheaders) :清除缓
 RPM 套件管理方式的出现，让 Linux 易于安装，升级，间接提升了 Linux 的适用度.
 
 ### 参数
-```bash
+```
 -a 　查询所有套件。
 -b<完成阶段><套件档>+或-t <完成阶段><套件档>+ 　设置包装套件的完成阶段，并指定套件档的文件名称。
 -c 　只列出组态配置文件，本参数需配合"-l"参数使用。
@@ -1163,10 +1113,10 @@ RPM 套件管理方式的出现，让 Linux 易于安装，升级，间接提升
 
 ### 实例
 - 安装软件
-![](https://img-blog.csdnimg.cn/img_convert/7c599ff6d1ff990c79db652b0d422d00.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800798213_4685968-6e768569db02b008.png)
 
 - 显示软件安装信息
-![](https://img-blog.csdnimg.cn/img_convert/afe8c01ca86c2a6068c2218f152263c8.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797889_4685968-82311863daa57faa.png)
 
 ## 7.3 chkconfig
 检查，设置系统的各种服务。
@@ -1223,7 +1173,7 @@ Terminate input, or exit shell
 
 
 其实，控制字符都是可以通过stty命令更改的，可在终端中输入命令"stty -a"查看终端配置
-![](https://img-blog.csdnimg.cn/img_convert/f38ef577fe1589be314d96288f1c924b.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797671_4685968-e43eb0501196bbea.png)
 
 
 ## 7.5 kill 
@@ -1303,8 +1253,7 @@ command 要以系统管理者身份（或以 -u 更改为其他人）执行的
 
 ### 实例
 - 切换为root
-![](https://img-blog.csdnimg.cn/20201005194445874.png#pic_center)
-
+![](https://uploadfiles.nowcoder.com/images/20190724/5088755_1563904905075_E14F10977961EBC60E71E5E91BA65926 "图片标题") 
 
 
 ## whereis
@@ -1328,35 +1277,6 @@ fuser最基本的用法是查询某个文件或目录被哪个进程使用：
                             USER        PID ACCESS COMMAND
 3306/tcp:            mysql      2535 F.... mysqld
 ```
-
-# 网络通信
-## nc
-设置路由器的相关参数。
-```bash
-nc [-hlnruz][-g<网关...>][-G<指向器数目>][-i<延迟秒数>][-o<输出文件>][-p<通信端口>][-s<来源位址>][-v...][-w<超时秒数>][主机名称][通信端口...]
-```
-### 参数
--g<网关> 设置路由器跃程通信网关，最多可设置8个。
--G<指向器数目> 设置来源路由指向器，其数值为4的倍数。
--h 在线帮助。
--i<延迟秒数> 设置时间间隔，以便传送信息及扫描通信端口。
--l 使用监听模式，管控传入的资料。
--n 直接使用IP地址，而不通过域名服务器。
--o<输出文件> 指定文件名称，把往来传输的数据以16进制字码倾倒成该文件保存。
--p<通信端口> 设置本地主机使用的通信端口。
--r 乱数指定本地与远端主机的通信端口。
--s<来源位址> 设置本地主机送出数据包的IP地址。
--u 使用UDP传输协议。
--v 显示指令执行过程。
--w<超时秒数> 设置等待连线的时间。
--z 使用0输入/输出模式，只在扫描通信端口时使用。
-
-### 实例
-- 连接localhost 的redis 6379端口
-![](https://img-blog.csdnimg.cn/20201005194505498.png#pic_center)
-- nc 建立通信，然后按 HTTP 协议发请求，接收到百度的响应
-![](https://img-blog.csdnimg.cn/20210221125602564.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
 # 在 vim 命令模式
 :noh  取消/ sth 的搜索结果高亮特效
 ## ip
@@ -1364,7 +1284,7 @@ nc [-hlnruz][-g<网关...>][-G<指向器数目>][-i<延迟秒数>][-o<输出文
 ```
 ip addr show  
 ```
-![](https://img-blog.csdnimg.cn/img_convert/5f4a31dc331becfc6286e8e614b98eb9.png)
+![](https://uploadfiles.nowcoder.com/files/20190317/5088755_1552800797787_4685968-3e2b6e161f3fd27c.png)
 
 # 软件包管理器
 apt-get是Debian、Ubuntu、Linux Mint、elementary OS等Linux发行版的默认软件包管理器
@@ -1376,36 +1296,17 @@ apt-get purge packagename 等同于 apt-get remove packagename --purge
 
 配置文件只包括/etc目录中的软件服务使用的配置信息，不包括home目录中的
 
-## Curl
-利用URL规则在命令行下工作的文件传输工具。支持文件的上传和下载，所以是综合传输工具，但按传统，习惯称curl为下载工具。
-curl支持包括HTTP、HTTPS、ftp等众多协议，还支持POST、cookies、认证、从指定偏移处下载部分文件、用户代理字符串、限速、文件大小、进度条等特征。
-适合网页处理流程和数据检索自动化。
-### 语法
-```clike
-curl  (选项)  (参数)
-```
-### 参数
-```clike
--i/I  通过-I或-head可只打印HTTP头部信息，-i会带上网页内容
--X/--request 指定什么命令
-```
-![](https://img-blog.csdnimg.cn/7dc8964b172348378d4d059e8b0603e3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/f11f36b1dd084268a3b707c3ab8c6c9d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)![](https://img-blog.csdnimg.cn/728954e03c5549bdbb9fec674a244c1b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-
-# 8 Linux常见问题解决方案
-## Repository list的更新日期久远
+# 8 Linux常见问题与解决方案
+## 1 Repository list的更新日期久远
 - yum判断你上次更新repository list的时间太久远(2周以上)，所以不让使用者操作yum，以避免安装到旧的软件包！
-![](https://img-blog.csdnimg.cn/img_convert/986a2dea2586477e92e164ed1a568cbd.png)
-### 解决方案
+![](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337809_16782311-0c28f0cbed11c750.png)
+
+## 解决方案
 - 清除yum repository缓存
-![sudo yum clean all](https://img-blog.csdnimg.cn/img_convert/7ad03eae3d6ee2db4e73d786c162244f.png)
+![sudo yum clean all](https://uploadfiles.nowcoder.com/files/20190601/5088755_1559361337928_16782311-ca4bebd6a0ddae16.png)
 
-## 有些工具安装之后，除了要修改root下的.bashfile(即加环境变量) ，还要修改etc/profile， 两个profile是干什么用的？有啥区别？
-- /etc/profile
-每个用户登录时都会运行的环境变量设置，属于系统级别的环境变量，其设置对所有用户适用
-- .bashfile
-单用户登录时比如root会运行的，只适用于当前用户，且只有在你使用的也是bash作为shell时才行。
+## 问题：linux下有些工具安装之后，除了要修改root下的.bashfile(也就是添加个环境变量) ，还要修改etc/profile 下的环境变量 ， 两个profile是干什么用的？区别？
 
-rpm是red hat、fedora、centos这几个发行版使用的安装包，和其它tar.gz的区别是有个文件头,多了一些信息。rpm包多数是二进制文件,可以直接运行的,但tar.gz包很多是源代码,要编译后才能运行。
-二进制文件和windows下的exe文件一个意思,可以直接运行。
\ No newline at end of file
+解答：
+- /etc/profile ：这个文件是每个用户登录时都会运行的环境变量设置，属于系统级别的环境变量，设置在里 面的东西对所有用户适用
+- .bashfile 是单用户登录时比如root会运行的,只对当前用户适用，而且只有在你使用的也是bash作为shell时才行. rpm是red hat,fedora,centos这几个发行版使用的安装包,和其它tar.gz的区别是有个文件头,多了一些信息。 rpm包多数是二进制文件,可以直接运行的,但tar.gz包很多是源代码,要编译后才能运行。 二进制文件和windows下的exe文件一个意思,可以直接运行。
\ No newline at end of file
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/Linux/\344\270\200\346\226\207\346\220\236\346\207\202select\343\200\201poll\345\222\214epoll\345\214\272\345\210\253.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/Linux/\344\270\200\346\226\207\346\220\236\346\207\202select\343\200\201poll\345\222\214epoll\345\214\272\345\210\253.md"
deleted file mode 100644
index 743dcb131b..0000000000
--- "a/\346\223\215\344\275\234\347\263\273\347\273\237/Linux/\344\270\200\346\226\207\346\220\236\346\207\202select\343\200\201poll\345\222\214epoll\345\214\272\345\210\253.md"
+++ /dev/null
@@ -1,375 +0,0 @@
-# 1 select
-select本质上是通过设置或检查存放fd标志位的数据结构进行下一步处理。
-这带来缺点：
-- 单个进程可监视的fd数量被限制，即能监听端口的数量有限
-单个进程所能打开的最大连接数有`FD_SETSIZE`宏定义，其大小是32个整数的大小（在32位的机器上，大小就是3232，同理64位机器上FD_SETSIZE为3264），当然我们可以对进行修改，然后重新编译内核，但是性能可能会受到影响，这需要进一步的测试
-一般该数和系统内存关系很大，具体数目可以`cat /proc/sys/fs/file-max`察看。32位机默认1024个，64位默认2048。
-![](https://img-blog.csdnimg.cn/20201103231954394.png#pic_center)
-
-- 对socket是线性扫描，即轮询，效率较低：
-仅知道有I/O事件发生，却不知是哪几个流，只会无差异轮询所有流，找出能读数据或写数据的流进行操作。同时处理的流越多，无差别轮询时间越长 - O(n)。
-
-
-当socket较多时，每次select都要通过遍历`FD_SETSIZE`个socket，不管是否活跃，这会浪费很多CPU时间。如果能给 socket 注册某个回调函数，当他们活跃时，自动完成相关操作，即可避免轮询，这就是**epoll**与**kqueue**。
-
-## 1.1 调用过程
-![](https://img-blog.csdnimg.cn/20201103234504233.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-```c
-asmlinkage long sys_poll(struct pollfd * ufds, unsigned int nfds, long timeout)
-{
-	int i, j, fdcount, err;
-	struct pollfd **fds;
-	struct poll_wqueues table, *wait;
-	int nchunks, nleft;
-
-	/* Do a sanity check on nfds ... */
-	if (nfds > NR_OPEN)
-		return -EINVAL;
-
-	if (timeout) {
-		/* Careful about overflow in the intermediate values */
-		if ((unsigned long) timeout < MAX_SCHEDULE_TIMEOUT / HZ)
-			timeout = (unsigned long)(timeout*HZ+999)/1000+1;
-		else /* Negative or overflow */
-			timeout = MAX_SCHEDULE_TIMEOUT;
-	}
-	// 2. 注册回调函数__pollwait
-	poll_initwait(&table);
-	wait = &table;
-	if (!timeout)
-		wait = NULL;
-
-	err = -ENOMEM;
-	fds = NULL;
-	if (nfds != 0) {
-		fds = (struct pollfd **)kmalloc(
-			(1 + (nfds - 1) / POLLFD_PER_PAGE) * sizeof(struct pollfd *),
-			GFP_KERNEL);
-		if (fds == NULL)
-			goto out;
-	}
-
-	nchunks = 0;
-	nleft = nfds;
-	while (nleft > POLLFD_PER_PAGE) { /* allocate complete PAGE_SIZE chunks */
-		fds[nchunks] = (struct pollfd *)__get_free_page(GFP_KERNEL);
-		if (fds[nchunks] == NULL)
-			goto out_fds;
-		nchunks++;
-		nleft -= POLLFD_PER_PAGE;
-	}
-	if (nleft) { /* allocate last PAGE_SIZE chunk, only nleft elements used */
-		fds[nchunks] = (struct pollfd *)__get_free_page(GFP_KERNEL);
-		if (fds[nchunks] == NULL)
-			goto out_fds;
-	}
-
-	err = -EFAULT;
-	for (i=0; i < nchunks; i++)
-		// 
-		if (copy_from_user(fds[i], ufds + i*POLLFD_PER_PAGE, PAGE_SIZE))
-			goto out_fds1;
-	if (nleft) {
-		if (copy_from_user(fds[nchunks], ufds + nchunks*POLLFD_PER_PAGE, 
-				nleft * sizeof(struct pollfd)))
-			goto out_fds1;
-	}
-
-	fdcount = do_poll(nfds, nchunks, nleft, fds, wait, timeout);
-
-	/* OK, now copy the revents fields back to user space. */
-	for(i=0; i < nchunks; i++)
-		for (j=0; j < POLLFD_PER_PAGE; j++, ufds++)
-			__put_user((fds[i] + j)->revents, &ufds->revents);
-	if (nleft)
-		for (j=0; j < nleft; j++, ufds++)
-			__put_user((fds[nchunks] + j)->revents, &ufds->revents);
-
-	err = fdcount;
-	if (!fdcount && signal_pending(current))
-		err = -EINTR;
-
-out_fds1:
-	if (nleft)
-		free_page((unsigned long)(fds[nchunks]));
-out_fds:
-	for (i=0; i < nchunks; i++)
-		free_page((unsigned long)(fds[i]));
-	if (nfds != 0)
-		kfree(fds);
-out:
-	poll_freewait(&table);
-	return err;
-}
-```
-```c
-static int do_poll(unsigned int nfds, unsigned int nchunks, unsigned int nleft, 
-	struct pollfd *fds[], struct poll_wqueues *wait, long timeout)
-{
-	int count;
-	poll_table* pt = &wait->pt;
-
-	for (;;) {
-		unsigned int i;
-
-		set_current_state(TASK_INTERRUPTIBLE);
-		count = 0;
-		for (i=0; i < nchunks; i++)
-			do_pollfd(POLLFD_PER_PAGE, fds[i], &pt, &count);
-		if (nleft)
-			do_pollfd(nleft, fds[nchunks], &pt, &count);
-		pt = NULL;
-		if (count || !timeout || signal_pending(current))
-			break;
-		count = wait->error;
-		if (count)
-			break;
-		timeout = schedule_timeout(timeout);
-	}
-	current->state = TASK_RUNNING;
-	return count;
-}
-```
-
-1. 使用copy_from_user从用户空间拷贝fd_set到内核空间
-2. 注册回调函数`__pollwait`
-![](https://img-blog.csdnimg.cn/908c942c09594f048dc2bebc05608ff9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-3. 遍历所有fd，调用其对应的poll方法（对于socket，这个poll方法是sock_poll，sock_poll根据情况会调用到tcp_poll，udp_poll或datagram_poll）
-4. 以tcp_poll为例，核心实现就是`__pollwait`，即上面注册的回调函数
-5. `__pollwait`，就是把current（当前进程）挂到设备的等待队列，不同设备有不同等待队列，如tcp_poll的等待队列是sk->sk_sleep（把进程挂到等待队列中并不代表进程已睡眠）。在设备收到一条消息（网络设备）或填写完文件数据（磁盘设备）后，会唤醒设备等待队列上睡眠的进程，这时current便被唤醒。
-```c
-void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *_p)
-{
-	struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt);
-	struct poll_table_page *table = p->table;
-
-	if (!table || POLL_TABLE_FULL(table)) {
-		struct poll_table_page *new_table;
-
-		new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);
-		if (!new_table) {
-			p->error = -ENOMEM;
-			__set_current_state(TASK_RUNNING);
-			return;
-		}
-		new_table->entry = new_table->entries;
-		new_table->next = table;
-		p->table = new_table;
-		table = new_table;
-	}
-
-	/* 添加新节点 */
-	{
-		struct poll_table_entry * entry = table->entry;
-		table->entry = entry+1;
-	 	get_file(filp);
-	 	entry->filp = filp;
-		entry->wait_address = wait_address;
-		init_waitqueue_entry(&entry->wait, current);
-		add_wait_queue(wait_address,&entry->wait);
-	}
-}
-```
-```c
-static void do_pollfd(unsigned int num, struct pollfd * fdpage,
-	poll_table ** pwait, int *count)
-{
-	int i;
-
-	for (i = 0; i < num; i++) {
-		int fd;
-		unsigned int mask;
-		struct pollfd *fdp;
-
-		mask = 0;
-		fdp = fdpage+i;
-		fd = fdp->fd;
-		if (fd >= 0) {
-			struct file * file = fget(fd);
-			mask = POLLNVAL;
-			if (file != NULL) {
-				mask = DEFAULT_POLLMASK;
-				if (file->f_op && file->f_op->poll)
-					mask = file->f_op->poll(file, *pwait);
-				mask &= fdp->events | POLLERR | POLLHUP;
-				fput(file);
-			}
-			if (mask) {
-				*pwait = NULL;
-				(*count)++;
-			}
-		}
-		fdp->revents = mask;
-	}
-}
-```
-6. poll方法返回时会返回一个描述读写操作是否就绪的mask掩码，根据这个mask掩码给fd_set赋值
-7. 若遍历完所有fd，还没返回一个可读写的mask掩码，则调schedule_timeout是调用select的进程（也就是current）进入睡眠。当设备驱动发生自身资源可读写后，会唤醒其等待队列上睡眠的进程。若超过一定超时时间（schedule_timeout指定），还没人唤醒，则调用select的进程会重新被唤醒获得CPU，进而重新遍历fd，判断有无就绪的fd
-8. 把fd_set从内核空间拷贝到用户空间
-## 1.2 缺点
-内核需要将消息传递到用户空间，都需要内核拷贝动作。需要维护一个用来存放大量fd的数据结构，使得用户空间和内核空间在传递该结构时复制开销大。
-
-- 每次调用select，都需把fd集合从用户态拷贝到内核态，fd很多时开销就很大
-- 同时每次调用select都需在内核遍历传递进来的所有fd，fd很多时开销就很大
-- select支持的文件描述符数量太小了，默认最大支持1024个
-- 主动轮询效率很低
-# 2 poll
-和select类似，只是描述fd集合的方式不同，poll使用`pollfd`结构而非select的`fd_set`结构。
-管理多个描述符也是进行轮询，根据描述符的状态进行处理，但**poll没有最大文件描述符数量的限制**。
-
-poll和select同样存在一个缺点就是，包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间，而不论这些文件描述符是否就绪，它的开销随着文件描述符数量的增加而线性增大。
-- 它将用户传入的数组拷贝到内核空间
-- 然后查询每个fd对应的设备状态：
-	- 如果设备就绪
-在设备等待队列中加入一项继续遍历
-	- 若遍历完所有fd后，都没发现就绪的设备
-挂起当前进程，直到设备就绪或主动超时，被唤醒后它又再次遍历fd。这个过程经历多次无意义的遍历。
-
-没有最大连接数限制，因其基于链表存储，其缺点：
-- 大量fd数组被整体复制于用户态和内核地址空间间，而不管是否有意义
-- 如果报告了fd后，没有被处理，那么下次poll时会再次报告该fd
-
-所以又有了epoll模型。
-# 3 epoll
-epoll模型修改主动轮询为被动通知，当有事件发生时，被动接收通知。所以epoll模型注册套接字后，主程序可做其他事情，当事件发生时，接收到通知后再去处理。
-
-可理解为**event poll**，epoll会把哪个流发生哪种I/O事件通知我们。所以epoll是事件驱动（每个事件关联fd），此时我们对这些流的操作都是有意义的。复杂度也降到O(1)。
-```c
-asmlinkage int sys_epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
-{
-	int error;
-	struct file *file, *tfile;
-	struct eventpoll *ep;
-	struct epitem *epi;
-	struct epoll_event epds;
-
-	error = -EFAULT;
-	if (copy_from_user(&epds, event, sizeof(struct epoll_event)))
-		goto eexit_1;
-
-	/* Get the "struct file *" for the eventpoll file */
-	error = -EBADF;
-	file = fget(epfd);
-	if (!file)
-		goto eexit_1;
-
-	/* Get the "struct file *" for the target file */
-	tfile = fget(fd);
-	if (!tfile)
-		goto eexit_2;
-
-	/* The target file descriptor must support poll */
-	error = -EPERM;
-	if (!tfile->f_op || !tfile->f_op->poll)
-		goto eexit_3;
-
-	/*
-	 * We have to check that the file structure underneath the file descriptor
-	 * the user passed to us _is_ an eventpoll file. And also we do not permit
-	 * adding an epoll file descriptor inside itself.
-	 */
-	error = -EINVAL;
-	if (file == tfile || !IS_FILE_EPOLL(file))
-		goto eexit_3;
-
-	/*
-	 * At this point it is safe to assume that the "private_data" contains
-	 * our own data structure.
-	 */
-	ep = file->private_data;
-
-	/*
-	 * Try to lookup the file inside our hash table. When an item is found
-	 * ep_find() increases the usage count of the item so that it won't
-	 * desappear underneath us. The only thing that might happen, if someone
-	 * tries very hard, is a double insertion of the same file descriptor.
-	 * This does not rapresent a problem though and we don't really want
-	 * to put an extra syncronization object to deal with this harmless condition.
-	 */
-	epi = ep_find(ep, tfile);
-
-	error = -EINVAL;
-	switch (op) {
-	case EPOLL_CTL_ADD:
-		if (!epi) {
-			epds.events |= POLLERR | POLLHUP;
-
-			error = ep_insert(ep, &epds, tfile);
-		} else
-			error = -EEXIST;
-		break;
-	case EPOLL_CTL_DEL:
-		if (epi)
-			error = ep_remove(ep, epi);
-		else
-			error = -ENOENT;
-		break;
-	case EPOLL_CTL_MOD:
-		if (epi) {
-			epds.events |= POLLERR | POLLHUP;
-			error = ep_modify(ep, epi, &epds);
-		} else
-			error = -ENOENT;
-		break;
-	}
-
-	/*
-	 * The function ep_find() increments the usage count of the structure
-	 * so, if this is not NULL, we need to release it.
-	 */
-	if (epi)
-		ep_release_epitem(epi);
-
-eexit_3:
-	fput(tfile);
-eexit_2:
-	fput(file);
-eexit_1:
-	DNPRINTK(3, (KERN_INFO "[%p] eventpoll: sys_epoll_ctl(%d, %d, %d, %u) = %d\n",
-		     current, epfd, op, fd, event->events, error));
-
-	return error;
-}
-
-```
-
-## 3.1 触发模式
-**EPOLLLT**和**EPOLLET**两种：
-
-- LT，默认的模式（水平触发）
-只要该fd还有数据可读，每次 `epoll_wait` 都会返回它的事件，提醒用户程序去操作，
-- ET是“高速”模式（边缘触发）
-![](https://img-blog.csdnimg.cn/20201103232957391.png#pic_center)
-只会提示一次，直到下次再有数据流入之前都不会再提示，无论fd中是否还有数据可读。所以在ET模式下，read一个fd的时候一定要把它的buffer读完，即读到read返回值小于请求值或遇到EAGAIN错误
-
-epoll使用“事件”的就绪通知方式，通过`epoll_ctl`注册fd，一旦该fd就绪，内核就会采用类似回调机制激活该fd，`epoll_wait`便可收到通知。
-### EPOLLET触发模式的意义
-若用`EPOLLLT`，系统中一旦有大量无需读写的就绪文件描述符，它们每次调用`epoll_wait`都会返回，这大大降低处理程序检索自己关心的就绪文件描述符的效率。
-而采用`EPOLLET`，当被监控的文件描述符上有可读写事件发生时，`epoll_wait`会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小)，那么下次调用`epoll_wait`时，它不会通知你，即只会通知你一次，直到该文件描述符上出现第二次可读写事件才会通知你。这比水平触发效率高，系统不会充斥大量你不关心的就绪文件描述符。
-
-## 3.2 优点
-- 没有最大并发连接的限制，能打开的FD的上限远大于1024（1G的内存上能监听约10万个端口）
-- 效率提升，不是轮询，不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数
-即Epoll最大的优点就在于它只关心“活跃”的连接，而跟连接总数无关，因此在实际的网络环境中，Epoll的效率就会远远高于select和poll
-- 内存拷贝，利用mmap()文件映射内存加速与内核空间的消息传递；即epoll使用mmap减少复制开销。
-- epoll通过内核和用户空间共享一块内存来实现的
-
-表面上看epoll的性能最好，但是在连接数少并且连接都十分活跃的情况下，select和poll的性能可能比epoll好，毕竟epoll的通知机制需要很多函数回调。
-
-epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持，其中epoll是Linux所特有，而select则应该是POSIX所规定，一般操作系统均有实现。
-
-select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数，epoll_create,epoll_ctl和epoll_wait，epoll_create是创建一个epoll句柄；epoll_ctl是注册要监听的事件类型；epoll_wait则是等待事件的产生。
-- 对于第一个缺点，epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时（在epoll_ctl中指定EPOLL_CTL_ADD），会把所有的fd拷贝进内核，而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
-- 对于第二个缺点，epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中，而只在epoll_ctl时把current挂一遍（这一遍必不可少）并为每个fd指定一个回调函数，当设备就绪，唤醒等待队列上的等待者时，就会调用这个回调函数，而这个回调函数会把就绪的fd加入一个就绪链表）。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd（利用schedule_timeout()实现睡一会，判断一会的效果，和select实现中的第7步是类似的）。
-- 对于第三个缺点，epoll没有这个限制，它所支持的FD上限是最大可以打开文件的数目，这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右，具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
-# 4 总结
-select，poll，epoll都是IO多路复用机制，即可以监视多个描述符，一旦某个描述符就绪（读或写就绪），能够通知程序进行相应读写操作。
-但select，poll，epoll本质上都是**同步I/O**，因为他们都需要在读写事件就绪后自己负责进行读写，也就是说这个读写过程是阻塞的，而异步I/O则无需自己负责进行读写，异步I/O的实现会负责把数据从内核拷贝到用户空间。  
-
-- select，poll实现需要自己不断轮询所有fd集合，直到设备就绪，期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表，期间也可能多次睡眠和唤醒交替，但是它是设备就绪时，调用回调函数，把就绪fd放入就绪链表中，并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替，但是select和poll在“醒着”的时候要遍历整个fd集合，而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了，这节省了大量的CPU时间。这就是回调机制带来的性能提升。
-
-- select，poll每次调用都要把fd集合从用户态往内核态拷贝一次，并且要把current往设备等待队列中挂一次，而epoll只要一次拷贝，而且把current往等待队列上挂也只挂一次（在epoll_wait的开始，注意这里的等待队列并不是设备等待队列，只是一个epoll内部定义的等待队列）。这也能节省不少的开销。 
-
-> 参考
-> - Linux下select/poll/epoll机制的比较
-> - https://www.cnblogs.com/anker/p/3265058.html
\ No newline at end of file
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\346\223\215\344\275\234\347\263\273\347\273\237\344\271\213\345\255\230\345\202\250\347\256\241\347\220\206.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\346\223\215\344\275\234\347\263\273\347\273\237\344\271\213\345\255\230\345\202\250\347\256\241\347\220\206.md"
index 2159408336..862ed67299 100644
--- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\346\223\215\344\275\234\347\263\273\347\273\237\344\271\213\345\255\230\345\202\250\347\256\241\347\220\206.md"
+++ "b/\346\223\215\344\275\234\347\263\273\347\273\237/\346\223\215\344\275\234\347\263\273\347\273\237\344\271\213\345\255\230\345\202\250\347\256\241\347\220\206.md"
@@ -17,13 +17,13 @@
     进程不能执行不合适的操作
 
 ## 1.2 要解决的问题
-![](https://img-blog.csdnimg.cn/img_convert/73417f741e1ee33c5db4aca60644dda5.png)
+![](//upload-images.jianshu.io/upload_images/1925650-b5f74a1fd452ea48.png)
 说明：
 在左边的单处理器系统中，如果一个进程想要运行，那么必须将进程地址空间装载到物理内存中才可以运行。
 而右边的是多处理器系统中有多个进程需要进入物理内存执行，这里要解决的问题就是，如何将进程地址空间合理的装载到物理内存中，如何合理的分配使用内存，使得每个进程能正确执行。
 
 ## 1.3 复习：进程地址空间
-![](https://img-blog.csdnimg.cn/img_convert/defe3b11c5679b29e0614e07151d3f8f.png)
+![](//upload-images.jianshu.io/upload_images/1925650-8a18c748dfd80d9e.png)
 ## 1.4 注意
 
 *   进程中的地址不是最终的物理地址
@@ -48,10 +48,10 @@
 支持程序浮动
 需要硬件部件支持，较为常用。
 ## 2.2 动态重定位实现
-![](https://img-blog.csdnimg.cn/img_convert/dbc53548c8c6ffeb06f9b8e125727850.png)
+![](//upload-images.jianshu.io/upload_images/1925650-8278d3738928620e.png)
 # 三、物理内存管理
 ## 3.1 空闲内存管理
-![](https://img-blog.csdnimg.cn/img_convert/2a701aad4a224e8cf8ee307fa078f400.png)
+![](//upload-images.jianshu.io/upload_images/1925650-5f79432c588f4755.png)
 **说明：**我们对物理内存有不同的划分，一种是等长的划分，一种是不等长的划分。
 *   数据结构
     1、位图
@@ -87,20 +87,20 @@
 否则，将块划分为两个大小相等的伙伴，大小为`2^(U-1)`
     *   一直划分下去直到产生大于或等于`s`的最小块。
 ## 3.5 伙伴系统例子
-![](https://img-blog.csdnimg.cn/img_convert/8b05d0e782e16d1f41ce77052f71cff1.png)
+![](//upload-images.jianshu.io/upload_images/1925650-80d1726929a7c03e.png)
 **说明：**从上图中可以看到上面的算法是如何工作的。
 # 四、连续内存管理方案
 ## 4.1 单一连续区
 特点：一段时间内只有一个进程在内存中，简单、内存利用率低。
 这种方案是在早期系统中使用的，有三种不同的布局：
-![](https://img-blog.csdnimg.cn/img_convert/369fa4d30bef6b075cd71be15a344882.png)
+![](//upload-images.jianshu.io/upload_images/1925650-7b6cefd80769c3e6.png)
 ## 4.2 固定分区
 *   把内存空间分割成若干个区域，称为分区
 *   每个分区的大小可以相同也可以不同
 *   分区大小固定不变
 *   每个分区装一个且只能一个进程
 
-![](https://img-blog.csdnimg.cn/img_convert/85d49428a0b5b9415462e7f572ef2736.png)
+![](//upload-images.jianshu.io/upload_images/1925650-01700d944ca4b0a0.png)
 **说明：** 
 不同的进程链分排在不同分区位置。缺点是有的进程链很长，一时得不到分区，但是此时可能有些空闲分区根本没有被使用。
 于是还有右边这种排队方案，就是只有一个进程链，然后哪个分区空闲了，排在首位的进程就进入执行。早期手机中就是采用这种方法。
@@ -127,13 +127,13 @@
 逻辑上相邻的页，物理上不一定相邻。
     *   典型的页面尺寸：`4K`或`4M`
 *   逻辑地址
-![](https://img-blog.csdnimg.cn/img_convert/ebce9ec7fcfbe87e2b8e4c94e7d03e50.png)
+![](//upload-images.jianshu.io/upload_images/1925650-fe0a34d548610ebc.png)
 **说明：**逻辑地址分为页号和页内地址（页内偏移），这种划分是系统自动完成的，对用户是透明的。
 
 *   内存分配
-![](https://img-blog.csdnimg.cn/img_convert/08f5a47d41fe3aeb28e1ed77c4ce8aa4.png)
+![](//upload-images.jianshu.io/upload_images/1925650-d27e9b7067c59da7.png)
 **说明：**可以看到连续的进程地址空间映射到页帧中的物理内存是杂乱的。
-![](https://img-blog.csdnimg.cn/img_convert/7bff8d043dc57fc0db39e9e81f4c1669.png)
+![](//upload-images.jianshu.io/upload_images/1925650-5622e5f0f635f388.png)
 **说明：**对于逻辑地址空间和物理内存空间的杂乱的映射，如何进行映射呢？这里我们需要使用页表来记录这种映射。
 
 **相关数据结构及地址转换**
@@ -153,11 +153,11 @@
     *   内存空间被动态划分为若干长度不相同的区域，称为物理段，每个物理段由起始地址和长度确定
     *   内存分配：以段为单位进行分配，每段在内存中占据连续空间，但各段之间可以不相邻。其实就是将程序分为若干段，每段占用一块内存空间。
 *   逻辑地址
-![](https://img-blog.csdnimg.cn/img_convert/43e7a287490ad753b493ff73f0a11111.png)
+![](//upload-images.jianshu.io/upload_images/1925650-5fecb60814cec01c.png)
 **说明：**和页式类似，逻辑地址分为段号和段内地址。
 不同的是段号和段内地址不是自动划分的。看个例子：
-![](https://img-blog.csdnimg.cn/img_convert/85823a566bd8f55043eca97a0ab73849.png)
-![](https://img-blog.csdnimg.cn/img_convert/dafe09f4a8e0704531cd49ce33918fea.png)
+![](//upload-images.jianshu.io/upload_images/1925650-e858d87ca7a05251.png)
+![](//upload-images.jianshu.io/upload_images/1925650-0ddcd08f29793a7e.png)
 **说明：**同样的，和页式类似，每个段的位置都不一样或不连续。而我们这里使用段表来将逻辑段号和物理内存映射起来。其中段表包含长度和段起始地址。
 
 **相关数据结构及地址转换**
@@ -176,7 +176,7 @@
 *   思想
     用户进程划分：先按段划分，每一段按页面划分
     逻辑地址：
-![](https://img-blog.csdnimg.cn/img_convert/191245eee08bf84849e1896f0214e721.png)
+![](//upload-images.jianshu.io/upload_images/1925650-763cbdfa70eb9755.png)
 内存划分：同页式存储管理方案
 内存分配：以页为单位进行分配
 *   数据结构及有关操作
@@ -187,7 +187,7 @@
 *   地址转换
     由硬件支持
 ## 5.4 小结
-![](https://img-blog.csdnimg.cn/img_convert/96fe198bdc55d2d16a384accc2181bc4.png)
+![](//upload-images.jianshu.io/upload_images/1925650-c876fc726f16178c.png)
 # 六、交换技术
 ## 6.1 内存不足时如何管理
 即如何在一个较小的物理内存空间中运行一个会占用较大地址空间的进程？
@@ -238,7 +238,7 @@
     4、考虑进程的各种属性；不应换出处于等待`I/O`状态的进程
 
 *   进程空间增长的困难及解决
-![](https://img-blog.csdnimg.cn/img_convert/b8d41f259abde3bc7a51447c2f75e632.png)
+![](//upload-images.jianshu.io/upload_images/1925650-035cb42d2831887d.png)
 **说明：**这里给出了两种解决方案，一种是左边的为栈预留一部分空间；一种是右边的让数据区和栈去同向增长，即在一个预留区中增长。
 # 七、虚拟存储技术
 *   所谓虚拟存储技术是指：当进程运行时，先将其一部分装入内存，另一部分暂留在磁盘，当要执行的指令或访问的数据不在内存时，由操作系统自动完成将它们从磁盘调入内存的工作
@@ -251,11 +251,10 @@
   - 对换性(交换性)
   - 虚拟性
 ## 7.1 存储器的层次结构
-- 综合读写速度、存储容量、价格等因素：
-![](https://img-blog.csdnimg.cn/img_convert/a8ffe3b92155ccefeec259d0cb950282.png)
+![](//upload-images.jianshu.io/upload_images/1925650-23b0122043d4a792.png)
 
 ## 7.2 虚拟内存与存储体系
-![](https://img-blog.csdnimg.cn/img_convert/a763ba0dc2b8665cdec636fdc4b32d95.png)
+![](//upload-images.jianshu.io/upload_images/1925650-9bd611a356b53e81.png)
 
 *   把内存与磁盘有机地结合起来使用，从而得到一个容量很大的“内存”，即虚拟内存
 *   虚存是对内存的抽象，构建在存储体系之上，由操作系统协调各存储器的使用
@@ -278,7 +277,7 @@
     *   当内存空间已满，而又需要装入新的页面时，则根据某种算法置换内存中的某个页面，以便装入新的页面
 
 # 八、页表及页表项的设计
-![](https://img-blog.csdnimg.cn/img_convert/d8c66720ad4aa41d35c070c3e919f48c.png)
+![](//upload-images.jianshu.io/upload_images/1925650-655646502f5093c7.png)
 ## 2.1 页表项设计
 
 *   页表由页表项组成
@@ -309,13 +308,13 @@
 *   页表页在内存中若不连续存放，则需要引用页表的地址索引表，即页目录。即一个多级页表结构。
 
 ## 2.3 二级页表结构及地址映射
-![](https://img-blog.csdnimg.cn/img_convert/2df93e4b093202557512d3af4adb0031.png)
+![](//upload-images.jianshu.io/upload_images/1925650-9b2411426fd651e4.png)
 
 **说明：**这里还是`32`位的虚拟地址空间。每个进程有一个页目录，根据页目录得到页表地址，然后从页表中的页表项的页框号找到真正的物理内存地址。`32`位的虚拟地址分为页目录偏移、页表偏移和页内偏移。页目录地址保存在一个寄存器中，根据此地址找到页目录起始地址，然后根据月页目录偏移找到对应的页表地址，根据页表偏移找到页表项，从页表项中取得页框号，然后结合页内偏移找到对应的物理内存。对于二级页表，在`32`位系统中可以表示`4G`的虚拟地址空间。如果需要超过`4G`的虚拟地址空间，则二级页表满足不了。
-![](https://img-blog.csdnimg.cn/img_convert/064e9e2f35c31bb9f956b95d7a34feed.png)
+![](//upload-images.jianshu.io/upload_images/1925650-48df5a3e91d12ae1.png)
 
 ## 2.4 I386页目录和页表项
-![](https://img-blog.csdnimg.cn/img_convert/0d5948d0626a87d8d31d74f21ed456db.png)
+![](//upload-images.jianshu.io/upload_images/1925650-fe37312448f851d3.png)
 
 **说明：**总共有`32`位地址。
 
@@ -329,15 +328,15 @@
 
         *   从物理地址空间出发，系统建立一张页表
     *   页表项记录进程的某个虚拟地址（虚页号）与页框号的映射关系。
-![](https://img-blog.csdnimg.cn/img_convert/ab17f8f963d6053ab4833bca559e540f.png)
+![](//upload-images.jianshu.io/upload_images/1925650-d35306dba1596ba7.png)
 
     **说明：**系统建立一张页表可以节省很大的空间，这被很多`64`位系统采用，但是每次进行运行都需要查整张表，这样会耗费很大的资源，于是我们采用了一个哈希表，这样查找更快。
 
 ## 2.6 地址转换过程及TLB
-![](https://img-blog.csdnimg.cn/img_convert/3ef2d78d67462e5390ab1e64107fbd95.png)
+![](//upload-images.jianshu.io/upload_images/1925650-93a9f4b06c995cd8.png)
 **说明：**上图是虚拟地址通过页表和物理地址映射的关系。这个过程是有内存管理单元完成的。
-![](https://img-blog.csdnimg.cn/img_convert/3d9e2fd4455f8499519de3ed40ce3757.png)
-![](https://img-blog.csdnimg.cn/img_convert/c7d7c74994ddfd6c91900b72c76cf565.png)
+![](//upload-images.jianshu.io/upload_images/1925650-940dd1cfa8751137.png)
+![](//upload-images.jianshu.io/upload_images/1925650-53b274d5444b053d.png)
 ### 2.6.1 快表（TLB）的引入
 *   问题
     页表：两次或两次以上的内存访问。如果是二级页表就要访问两次，四级页表访问四次.`cpu`的指令处理速度与内存指令的访问速度差异较大，`cpu`的速度得不到充分利用。
@@ -348,7 +347,7 @@
 *   快表一般称为相连存储器：按内容并行查找
 *   保证正在运行进程的页表的子集（部分页表项）
 ### 2.6.3 加入TLB后地址转换过程
-![](https://img-blog.csdnimg.cn/img_convert/3ca2afcb9c827e773c497b73ce91e837.png)
+![](//upload-images.jianshu.io/upload_images/1925650-6ffdcac20ac8a7ce.png)
 **说明：**首先根据虚拟地址去查`TLB`，如果能找到页框号，则直接和偏移结合找到对应的物理内存；如果`TLB`中没有页框号，则需要去查页表，之后在找到对应的物理内存；在页表中如果对应的页表项无效，则会出现`page fault`的异常，然后由系统处理之后再进行同样的操作。
 
 ## 2.7 页错误（page fault）
@@ -364,7 +363,7 @@
     2、页面访问违反权限（读/写、用户/内核），比如用户访问内核空间。
 
     3、错误的访问地址，比如
-![](https://img-blog.csdnimg.cn/img_convert/b24f49a860fbce251a8547f431775b90.png)
+![](//upload-images.jianshu.io/upload_images/1925650-a980a0481692ea01.png)
 
 图中标注的位置都是有内容的，如果访问地址指向没有标注（没有内容）的位置，则就是错误的访问地址。
 
@@ -401,7 +400,7 @@
 - 置换范围
 
 计划置换页面的集合是局限在产生缺页中断的进程，还是所有进程的页框？
-![](https://img-blog.csdnimg.cn/img_convert/5ba5d9fdc617b54f97be8bc97873f00d.png)
+![](//upload-images.jianshu.io/upload_images/1925650-4c1a79a64625ccdc.png)
 
 *   置换策略
 
@@ -446,12 +445,12 @@
 *   实现：页面链表法
 ### 3.5.3 第二次机会算法（SCR）
 在先进先出算法的基础上进行该机而来的，此算法按照先进先出算法选择某一页面，检查其访问位`R`，如果为`0`，则置换该页；如果为`1`，则给第二次机会，并将访问位置零，并将其从链头取下放到链尾。
-![](https://img-blog.csdnimg.cn/img_convert/0909ae04b2199d23516f857353446275.png)
+![](//upload-images.jianshu.io/upload_images/1925650-b6c941c5054b28f4.png)
 
 ### 3.5.4 时钟算法（CLOCK）
 
 在第二次机会算法中当给某个页面第二次机会的时候，将其访问位置零，然后将其挂到链尾，这都是需要开销的，于是我们改进为时钟算法。
-![](https://img-blog.csdnimg.cn/img_convert/a509fa0735fee22b755e656821fc1eb3.png)
+![](//upload-images.jianshu.io/upload_images/1925650-5855cf5d77249896.png)
 
 **说明：**其实就是将之前的链表改为了环形链表，当给某个页面第二次机会的时候不需要将其取下然后挂到链尾，只需要移动一下指针即可，这样可以降低开销。
 
@@ -483,19 +482,14 @@
     2、如果第一步失败，则重新扫描，选择第一个（`r=0；m=1`）的页框（本次扫描工程中，对每个跳过的页框，将其使用位置为零）
 
     3、如果第二部失败，指针将回到它的最初位置，并且集合中的所有页框的使用位均为零。重复第一步，并且，如果有必要，重复第二步，这样将可以找到置换的页框。
-### 3.5.6 最近最少使用（LRU）(重点)
-选择最后一次访问时间距离当前时间最长的一页并置换，即置换未使用时间最长的一页。
-性能接近最佳页面置换算法
-
-实现：时间戳或维护一个访问页的栈，导致开销较大。
-
-#### 硬件实现
-- 页面访问顺序0,1,2,3,2,1,0,3,2,3，该图案例只有四页。
-![](https://img-blog.csdnimg.cn/20210321155532697.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-访问第`0`页时先将页的第`0`行置为`1`，然后将第`0`列置为`0`，
-以此类推，在访问完之后将行编号最小的那一页置换出去。我们看到`j`中最小的是第`1`行，于是将第`1`页置换出去。
+### 3.5.6 最近最少使用算法（LRU）(重点)
+选择最后一次访问时间距离当前时间最长的一页并置换，即置换未使用时间最长的一页
+*   性能接近最佳页面置换算法
+*   实现：时间戳或维护一个访问页的栈，导致开销较大。下面看一种硬件实现：
+![案例](//upload-images.jianshu.io/upload_images/1925650-eadfb99586b13197.png)
+**说明：**访问第`0`页时先将页的第`0`行置为`1`，然后将第`0`列置为`0`，
+以此类推，在访问完之后将行编号最小的那一页置换出去
+我们看到`j`中最小的是第`1`行，于是将第`1`页置换出去。当然这里只有四页。
 
 ### 3.5.7 最不经常使用算法（NFU）
 
@@ -513,7 +507,7 @@
 ### 3.5.8 老化算法（AGING）
 
 *   改进（模拟`LRU`）：计数器在加R前先右移一位，`R`位加到计数器的最左端。
-![](https://img-blog.csdnimg.cn/img_convert/7d29ba4d675442ccba6a7309436ec17c.png)
+![](//upload-images.jianshu.io/upload_images/1925650-386d77e062537a7c.png)
 
 这样如果`R`值为零，则计数器没有影响，如果值为`1`，则会变得很大，于是如果一个页面长久不被访问，则计数器值就会越来越小。最后选择值最小的置换出去。
 
@@ -529,12 +523,12 @@
 计算应用`FIFO、LRU、OPT`算法时的缺页次数
 
 **应用`FIFO、LRU`页面置换算法**
-![](https://img-blog.csdnimg.cn/img_convert/cca829a5b1900eebdbe5c7ad6bae4949.png)
+![](//upload-images.jianshu.io/upload_images/1925650-98a56255af42bb11.png)
 
 可以看到`FIFO`发生六次缺页异常，而`LRU`发生四次缺页异常。
 
 **应用OPT页面置换算法**
-![](https://img-blog.csdnimg.cn/img_convert/e293a2c1c95f5787bdfbfb69d5b1f621.png)
+![](//upload-images.jianshu.io/upload_images/1925650-41e624a34bed722e.png)
 
 发生三次缺页异常。
 
@@ -584,12 +578,12 @@
 例子：
 
 分配了一个页框，页面大小为`128`个整数，矩阵`A(128 x 128)`按行存放。
-![](https://img-blog.csdnimg.cn/img_convert/058146d742455e9d6f0dfb1668315981.png)
+![](//upload-images.jianshu.io/upload_images/1925650-27f92a6624e52587.png)
 
 可以看到左边是按列赋值，右边是按行赋值。按列编制就是首先读入第一页（一行，因为矩阵是按行存放的），然后给第`0`个位置赋值，每次读入一行，直到将第`0`列赋值完，读完之后再给第`1`列赋值，这样会产生`128*128`次缺页异常；而按行赋值，第一次读入一页，给第`0`行的所有元素赋值，这样会产生`128`次缺页异常。于是可以看到程序的编制方法对缺页次数是有很大影响的。
 
 ### 3.6.4 分配给进程的页框数与缺页率的关系
-![](https://img-blog.csdnimg.cn/img_convert/c84811e724a893e2d98a50a3a82321db.png)
+![](//upload-images.jianshu.io/upload_images/1925650-86efa6eec17165c5.png)
 **说明：**可以看到页框数越多那么缺页率越低，但是我们不可能给出所有的页框，于是需要找到一个平衡点`W`，超过这个点之后页框数的增加对缺页率的降低有限，这也是工作集算法的出发点。
 ## 3.7 工作集模型
 
@@ -600,10 +594,10 @@
 如果能为进程提供与活跃页面数相等的物理页面数，则可减少缺页中断次数，这是由`Denning`提出的。
 
 *   工作集：一个进程当前正在使用的页框集合
-![](https://img-blog.csdnimg.cn/img_convert/1c90f6c48922c3e9fe77b0e7c1f4f3d0.png)
+![](//upload-images.jianshu.io/upload_images/1925650-feb7c87d0367026e.png)
 
 *   例子
-![](https://img-blog.csdnimg.cn/img_convert/b6d474df8e10b0ca3299d3e9bb65c5f1.png)
+![](//upload-images.jianshu.io/upload_images/1925650-8e203b51036e0b37.png)
 ## 3.8 工作集算法
 
 *   基本思路
@@ -632,8 +626,9 @@
     进程通过一个系统调用（`mmap`）将一个文件（或部分）映射到其虚拟地址空间的一部分，访问这个文件就像访问内存中的一个大数组，而不是对文件进行读写
 *   在多数实现中，在映射共享的页面时不会实际读入页面的内容，而是在访问页面时，页面才会被每次一页的读入，磁盘文件则被当作后备存储。
 *   当进程退出或显式地解除文件映射时，所有被修改页面会写回文件
-![](https://img-blog.csdnimg.cn/img_convert/82656d40d2c7aeb18260d5a12104fbb8.png)
+![](//upload-images.jianshu.io/upload_images/1925650-336ff172ccc06e72.png)
 ## 4.2 支持写时复制技术
-![](https://img-blog.csdnimg.cn/img_convert/88332d145194fc080990dccccbafcd06.png)
+![](//upload-images.jianshu.io/upload_images/1925650-2b675d9951b04615.png)
+
+**说明：**如图，两个进程共享同一块物理内存，每个页面都被标志成了写时复制。注意：共享的物理内存中每个页面都是只读的。如果每个进程想改变某个页面时，就会与只读标记冲突，而系统在检测出页面是写时复制的，则会在内存中复制一个页面，然后进行写操作。新复制的页面对执行写操作的进程是私有的，对其他共享写时复制页面的进程是不可见的。
 
-如图，两个进程共享同一块物理内存，每个页面都被标志成了写时复制。注意：共享的物理内存中每个页面都是只读的。如果每个进程想改变某个页面时，就会与只读标记冲突，而系统在检测出页面是写时复制的，则会在内存中复制一个页面，然后进行写操作。新复制的页面对执行写操作的进程是私有的，对其他共享写时复制页面的进程是不可见的。
\ No newline at end of file
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/CPU\345\217\221\345\261\225\345\217\262.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/CPU\345\217\221\345\261\225\345\217\262.md"
deleted file mode 100644
index cb7259dfef..0000000000
--- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/CPU\345\217\221\345\261\225\345\217\262.md"
+++ /dev/null
@@ -1,175 +0,0 @@
-# 1计算机概述
-## 1.1 发展简史
-### 一阶段（1946~1957）
-电子管计算机
-- 电子管
-![](https://img-blog.csdnimg.cn/20210211013829361.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 第一代计算机"ENIAC"
-![](https://img-blog.csdnimg.cn/20210211013948225.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-世界上第一台电子计算机"ENIAC"于1946年2月14日在美国宾夕法尼亚大学诞生，美国人莫克利（JohnW.Mauchly）和艾克特（J.PresperEckert）发明，主要是有大量的电子管组成，主用于科学计算。
-主要特点：
-- 以电子管作为元器件，所以称电子管计算机
-- 用了18000个电子管，占地150平方米，足有两间房子大，重达30吨，耗电功率约150千瓦，每秒钟可进行5000次运算
-- 电子管空间占用大，耗电量大，易发热，因而工作的时间不能太长
-- 使用机器语言，无系统软件
-- 采用磁鼓、小磁芯作为储存器，存储空间有限
-- 输入/输出设备简单，采用穿孔纸带或卡片，操作复杂，更换程序需要接线
-- 主要用于科学计算，当时美国国防部用它来进行弹道计算
-
-
-### 二阶段（1957~1964）
-晶体管计算机
-- 晶体管
-![](https://img-blog.csdnimg.cn/20210211014640818.png)
-第二代计算机采用的主要元件是晶体管，称为晶体管计算机。计算机软件有了较大发展，程序语言也出现了Fortran，Cobol计算机高级语言，采用了监控程序，这是操作系统的雏形。
-主要特点：
-1、集成度较高，体积小
-2、运算速度快，功耗更低
-3、操作简单，交互方便（有显示器了）
-
-
-### 三阶段（1964~1980）
-中小规模集成电路计算机。
-
-德州仪器的工程师发明了集成电路(IC)
-
-集成电路可在几平方毫米的单晶硅片上集成十几个甚至上百个电子元件。计算机开始采用中小规模的集成电路元件，这一代比上一代更小，耗电更少，功能更强，寿命更长，领域扩大，性能比上一代有很大提高。
-主要特点：
-1、体积更小，寿命更长。
-2、运行计算速度更快。
-3、外围设备考试出现多样化。
-4、有类似操作系统和应用程序，高级语言进一步发展。
-5、应用范围扩大到企业管理和辅助设计等领域。
-### 四阶段（1980~至今）
-超大规模集成电路计算机。
-
-这时期的计算机的体积、重量、功耗进一步减少，运算速度、存储容量、可靠性都有很大提高。
-主要特点：
-1、采用了大规模和超大规模集成电路逻辑元件，体积与第三代相比进一步缩小，可靠性更高，寿命更长。
-2、运算速度加快，每秒可达集千万次到几十亿次。
-3、系统软件和应用软件获得了巨大的发展，软件配置丰富，程序设计部分自动化。
-4、计算机网络技术、多媒体技术、分布式处理技术有了很大的发展，微型计算机大量进入家庭，产品更新速度加快。
-5、计算机在办公自动化、数据库管理、图像处理、语言设别和专家系统等各个领域得到应用，电子商务已开始进入家庭，出现个人电脑(PC)，计算机的发展进入到了一个新的历史时期。
-### 未来的计算机
-超导计算机、纳米计算机、光计算机、DNA计算机、量子计算机和神经网络计算机等，体积更小，运算速度更快，更加智能化，耗电量更小。
-```c
-程序的CPU执行时间 = 指令数×CPI×Clock Cycle Time
-```
-所以提升计算机的性能，可以通过指令数/CPI，好像都太难了。
-因此工程师们，就在CPU上多放晶体管，不断提升CPU的时钟频率，让CPU更快，程序的执行时间就会缩短。
-
-- 从1978年Intel发布的8086 CPU开始，计算机的主频从5MHz开始，不断攀升
-- 1980年代中期的80386能够跑到40MHz
-- 1989年的486能够跑到100MHz
-- 直到2000年的奔腾4处理器，主频已经到达了1.4GHz
-
-## 1.2 分类
-### 超级计算机
-功能最强、运算速度最快、存储容量最大的计算机，多用于国家高科技领域和尖端技术研究。
-
-计算速度单位 TFlop/s。1TFlop/s= 1万亿次浮点计算/s
-Intel(R) Core(TM) 10090K CPU
-
-```bash
-5.3GHz = 59.45 GFlop/s =  0.05945TFlop/s
-```
-![](https://img-blog.csdnimg.cn/20210211163406466.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 大型计算机
-又称大型机、大型主机、主机等，具有高性能，可处理大量数据与复杂的运算。该领域下IBM占据大份额。IBM Z9，NASA的最后一台大型机，使用的Red Hat Enterprise Linux。
-但是造价太高，阿里巴巴提出去 I（IBM）O(Oracle）E（EMC）这种高代价的存储系统维护，而且伸缩性差。
-
-### 迷你计算机(服务器)
-也称为小型机，普通服务器。不需要特殊的空调场所，具备不错的算力，可以完成较复杂的运算。
-普通服务器已替代传统的大型机，成为大规模企业计算的核心。
-
-### 工作站
-高端的通用微型计算机，提供比个人计算机更强大的性能。类似于普通台式电脑， 体积较大，但性能强劲。
-### 微型计算机
-即个人计算机，是最普通的一类计算机，如台式机，笔记本和一体机。
-
-## 1.3 冯·诺伊曼结构
-早期计算机仅含固定用途程序，如果改变程序就得更改结构、重新设计电路。所以需要把程序存储起来，并设计通用电路，即存储程序指令设计通用电路。
-
-- 把需要的程序和数据送至计算机中
-- 长期记忆程序、数据、中间结果及最终运算结果的能力
-- 具备算术、逻辑运算和数据传送等数据加工处理的能力
-- 按照要求将处理结果输出给用户
-![](https://img-blog.csdnimg.cn/20210211165602959.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-#### 冯·诺伊曼瓶颈（von Neumann bottleneck）
-在CPU与存储器之间的流量（资料传输率）与存储器的容量相比起来相当小，在现代电脑中，流量与CPU的工作效率相比之下非常小，在某些情况下（当CPU需要在巨大的数据上运行一些简单指令时），数据流量就成了整体效率非常严重的限制。CPU将会在数据输入或输出存储器时闲置。由于CPU速度远大于存储器读写速率，因此瓶颈问题越来越严重。
-
-在CPU与存储器间的缓存存储器缓解冯·诺伊曼瓶颈。分支预测（branch prediction）算法的创建也帮助缓和了此问题。
-
-程序翻译与程序解释
-
-
-
-
-# 2 CPU的极限-功耗
-[奔腾4的CPU主频从来没有达到过10GHz，最终定格在3.8GHz](https://zh.wikipedia.org/wiki/%E5%A5%94%E8%85%BE4)
-奔腾4主频虽高，但实际性能却配不上同样的主频。
-
-于是不仅让AMD获得喘息之机，更代表“主频时代”终结。后面几代Intel CPU主频不但没上升，反而下降。
-- 至今的最高配置Intel X 系列 CPU
-![](https://img-blog.csdnimg.cn/20210211125640996.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-主频也不过5GHz。
-相较于1978年到2000年，这20年里300倍的主频提升，从2000年到现在的20 年，CPU的主频大概提高3倍。
-
-- 奔腾4时CPU主频进入瓶颈期![](https://img-blog.csdnimg.cn/20190810234622116.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-就是因为功耗。CPU，也称作**超大规模集成电路，Very-Large-Scale Integration，VLSI**。 
-CPU就是让晶体管里面的“开关”不断“打开”/“关闭”，组合完成各种运算和功能。
-
-提高CPU计算速度：
-- 增加密度
-同样的面积，多放晶体管。如果CPU的面积大，晶体管之间的距离变更大，电信号传输的时间就会变长，运算速度自然就慢了。
-- 提升主频
-让晶体管“打开”/“关闭”更快，但是动作快，就要出汗散热，所以有了CPU上的硅脂、风扇、水冷。但设备散热效果也有极限。
-
-因此，CPU里能够放下的晶体管数量和晶体管的“开关”频率也有限。
-一个CPU的功率，可以用这样一个公式来表示：
-```c
-功耗  ≈ 1/2 ×负载电容 × 电压的平方 × 开关频率 × 晶体管数量
-```
-为提升性能，要不断增加晶体管密度，就要把晶体管造得小一点，即提升“制程”。从28nm到5nm，还要提升主频，让开关频率变快。
-
-但功耗过多，CPU散热就跟不上，公式里功耗和电压平方成正比，即可降低电压。
-从5MHz主频的8086到5GHz主频的Intel X，CPU电压从5V下降到了1V。
-# 2 并行优化-阿姆达尔定律
-从90s到本世纪初，“面向摩尔定律编程”的套路越来越用不下去了。奔腾4开始，Intel意识到通过提升主频“难”以性能提升。开始推出多核CPU，提升“吞吐率”而非“响应时间”。即**通过并行提高性能**。
-但要使用这种思想，需满足以下条件:
-- 需要进行的计算，本身即可分解成几个可并行任务
-比如向量的点乘运算
-- 需要能够分解好问题，并确保几个人的结果能够汇总到一起
-- 在“汇总”这个阶段，是没有办法并行进行的，还是得顺序执行，一步一步来
-
-这就引出了阿姆达尔定律（Amdahl’s Law）：
-对一个程序优化后，处理器并行运算之后效率提升的情况可用如下公式表示：
-```c
-优化后的执行时间 = 受优化影响的执行时间/加速倍数+不受影响的执行时间
-```
-比如向量点乘，同时计算向量的一小段点积，就是并行提高部分计算性能。但最终还是要在一个人那汇总相加，这部分时间无法并行优化，即**不受影响的执行时间**。
-
-比如向量
-- 点积需100ns
-- 加法需要20ns
-
-总共需要120ns。这里通过并行4个CPU有了4倍的加速度。那么最终优化后，就有了100/4+20=45ns。
-即使增加并行度来提供加速倍数，比如有100个CPU，整个时间也需要100/100+20=21ns，所以并非越多就肯定越快。
-
-# 3 让性能再次提升
-无论是简单提升主频，还是增加CPU核心数，通过并行提升性能，都会遇到瓶颈。
-仅靠“堆硬件”，已经不能很好地满足性能。于是，工程师们需要从其他方面开始下功夫。
-在“摩尔定律”和“并行计算”之外，在整个计算机组成层面，还有如下：
-## 3.1 大概率事件
-深度学习，整个计算过程中基本都是向量矩阵计算。所以用GPU替代CPU，大幅度提升了深度学习的模型训练过程，Google不满足GPU性能，还推出了TPU。
-
-## 3.2 流水线
-现代的工厂里的生产线叫“流水线”。可以把装配iPhone这样的任务拆分成一个个细分的任务，让每个人都只需要处理一道工序，最大化整个工厂的生产效率。
-CPU就是一个运算工厂，把CPU指令执行的过程进行拆分，细化运行。
-
-## 3.3 预测
-预测下一步而非苦等上一步结果，即提前运算。就像循环访问数组时，你也会猜到下一步会访问数组下一项。比如“分支和冒险”、“局部性原理”。
-
-> 参考
-> - 《深入理解计算机操作系统》
-> - https://www.eda365.com/portal.php?mod=view&aid=53086
\ No newline at end of file
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\345\206\257\302\267\350\257\272\344\276\235\346\233\274\350\256\241\347\256\227\346\234\272\347\273\223\346\236\204.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\345\206\257\302\267\350\257\272\344\276\235\346\233\274\350\256\241\347\256\227\346\234\272\347\273\223\346\236\204.md"
deleted file mode 100644
index c6c3837454..0000000000
--- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\345\206\257\302\267\350\257\272\344\276\235\346\233\274\350\256\241\347\256\227\346\234\272\347\273\223\346\236\204.md"
+++ /dev/null
@@ -1,172 +0,0 @@
-# 1 基本硬件组成
-一台计算机组成至少如下：
-- CPU（Central Processing Unit）
-中央处理器，计算机的所有计算由CPU执行。
--  内存（Memory）
-撰写的程序、打开的浏览器、运行的游戏，都要加载到内存里才能运行。
-程序读取的数据、计算得到的结果，也都要放在内存里。内存越大，能加载的东西自然也就越多。
-存放在内存里的程序和数据，需要被CPU读取，CPU计算完之后，还要把数据写回到内存。然而CPU不能直接插到内存上，反之亦然。
-- 主板（Motherboard）
-一个有着各种各样，有时候多达数十乃至上百个插槽的配件。CPU、内存都要插在主板。
-主板的芯片组（Chipset）和总线（Bus）解决了CPU和内存之间如何通信的问题。
-	- 芯片组
-	控制数据流转
-	- 总线
-	实际数据传输的高速公路。总线速度（Bus Speed）决定数据传输速度
-
-输入（Input）/输出（Output）设备，即**I/O设备**。
-显示器这所谓的**输出设备**。
-鼠标和键盘所谓的**输入设备**。
-硬盘，数据持久化。
-
-显示器、鼠标、键盘和硬盘这些东西并不是一台计算机必须的部分。
-
-其实只需要有I/O设备，能让我们从计算机里输入和输出信息即可。云服务器，只要让计算机能通过网络，SSH远程登陆访问就好了，因此没必要配显示器、鼠标、键盘。
-
-**显卡**（Graphics Card），使用图形界面操作系统的计算机必不可少。
-
-有人可能要说了，我装机的时候没有买显卡，计算机一样可以正常跑起来啊！那是因为，现在的主板都带了内置的显卡。
-
-如果玩游戏，做图形渲染或者跑深度学习应用，就需要买一张单独显卡，因为显卡里有GPU（Graphics Processing Unit，图形处理器），GPU一样可以做各种“计算”的工作。
-
-鼠标、键盘以及硬盘都是插在主板。作为外部I/O设备，它们通过主板上的**南桥**（SouthBridge）芯片组，来控制和CPU之间的通信的。
-
-“南桥”芯片
-- 它在主板上的位置，通常在主板的“南面”
-- 它的作用就是作为“桥”，来连接鼠标、键盘以及硬盘这些外部设备和CPU之间的通信。
-
-以前的主板上通常也有“北桥”芯片，用来作为“桥”，连接CPU和内存、显卡之间的通信。不过现在的主板上的“北桥”芯片的工作，已经被移到了CPU内部。
-
-# 2 冯·诺依曼体系结构
-
-因为手机尺寸的原因，手机制造商们选择把CPU、内存、网络通信，乃至摄像头芯片，都封装到一个芯片，然后再嵌入到手机主板上。
-
-这种方式叫**SoC**，也就是System on a Chip（系统芯片）。
-所以个人电脑和智能手机的硬件组成方式不太一样。
-
-无论是个人电脑/服务器/智能手机，还是Raspberry Pi这样的微型卡片机，都遵循**冯·诺依曼体系结构**（Von Neumann architecture），也叫**存储程序计算机**。
-暗含如下概念：
-- “可编程”计算机
-- “存储”计算机
-
-什么是“不可编程”？？？
-
-计算机是由各种门电路组合而成的，然后通过组装出一个固定的电路版，完成一个特定的计算程序。
-
-一旦需要修改功能，就要重新组装电路。这样的话，计算机就是“不可编程”的，因为程序在计算机硬件层面是“写死”的。
-
-最常见的就是老式计算器，电路板设好了加减乘除，做不了任何计算逻辑固定之外的事情。
-
-
-
-
-我们再来看“存储”计算机。
-
-程序本身是存储在计算机的内存里，可以通过加载不同的程序来解决不同的问题。
-
-有“存储程序计算机”，自然也有不能存储程序的计算机。
-
-典型的就是早年的“Plugboard”这样的插线板式的计算机。整个计算机就是一个巨大的插线板，通过在板子上不同的插头或者接口的位置插入线路，来实现不同的功能。这样的计算机自然是“可编程”的，但是编写好的程序不能存储下来供下一次加载使用，不得不每次要用到和当前不同的“程序”的时候，重新插板子，重新“编程”。
-
-
-可以看到，无论是“不可编程”还是“不可存储”，都会让使用计算机的效率大大下降。
-而这个对于效率的追求，也就是“存储程序计算机”的由来。
-
-冯，基于当时在秘密开发的EDVAC写了一篇报告First Draft of a Report on the EDVAC，描述了他心目中的一台计算机应该长什么样。这篇报告在历史上有个很特殊的简称，叫First Draft。这样，现代计算机的发展就从祖师爷写的一份草案开始了。
-
-#### First Draft里面说了一台计算机应该有哪些部分组成
-
-首先是一个包含
-
-- 算术逻辑单元（Arithmetic Logic Unit，ALU）
-- 处理器寄存器（Processor Register）
-
-的**处理器单元**（Processing Unit），用来完成各种算术和逻辑运算。
-
-因为它能够完成各种数据的处理或者计算工作，因此也有人把这个叫作数据通路（Datapath）或者运算器。
-
-然后是一个包含
-
-- 指令寄存器（Instruction Reigster）
-- 程序计数器（Program Counter）
-
-的**控制器单元**（Control Unit/CU），用来控制程序的流程，通常就是不同条件下的分支和跳转。
-
-在现在的计算机里，上面的算术逻辑单元和这里的控制器单元，共同组成了我们说的CPU。
-
-# 存储器
-存储数据（Data）和指令（Instruction）的**内存**。以及更大容量的**外部存储**，在过去，可能是磁带、磁鼓这样的设备，现在通常就是硬盘。
-## 分类
-### 存储介质
-半 导体存储器：
-内存、U盘、固态硬盘
-
-磁存储器：
-磁带、磁盘
-
-### 存取方式
-随机存储器(RAM)
-随机读取、与位置无关
-串行存储器：
-与位置有关、按顺序查找
-只读存储器(ROM)：
-只读不写
-
-## 层次结构
-![](https://img-blog.csdnimg.cn/20210215190753172.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210215191457293.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-### 原理
-#### 缓存-主存层次
-局部性原理。即在CPU与主存之间增加一层速度快(容量小)的Cache，以解决主存速度不足问题。
-#### 主存-辅存层次
-局部性原理。在主存之外增加辅助存储器(磁盘、SD卡、 U盘等)，以解决主存容量不足的问题。
-# 输入和输出设备
-无论是使用什么样的计算机，都是和输入输出设备打交道。
-
-- 个人电脑的鼠标键盘是输入设备，显示器是输出设备
-- 我们用的智能手机，触摸屏既是输入设备，又是输出设备
-- 跑在各种云上的服务器，则是通过网络来进行输入和输出。这个时候，网卡既是输入设备又是输出设备
-
-> 任何一台计算机的任何一个部件都可以归到运算器、控制器、存储器、输入设备和输出设备中，而所有的现代计算机也都是基于这个基础架构来设计开发的
-
-而所有的计算机程序，也都可以抽象为从**输入设备**读取输入信息，通过**运算器**和**控制器**来执行存储在**存储器**里的程序，最终把结果输出到**输出设备**中。而我们所有撰写的无论高级还是低级语言的程序，也都是基于这样一个抽象框架来进行运作的。
-
-## 通用设计
-### 数据线
-I/O设备与主机之间进行数据交换的传送线。
-
-分为：
-- 单向传输数据线
-- 双向传输数据线
-
-### 状态线
-I/O设备状态向主机报告的信号线
-查询设备是否已经正常连接并就绪
-查询设备是否已经被占用
-
-### 命令线
-- CPU向设备发送命令的信号线
-- 发送读写信号
-- 发送启动停止信号
-
-### 设备选择线
-主机选择I/O设备进行操作的信号线
-对连在总线上的设备进行选择
-
-## CPU与IO设备的通信
-### 程序中断
-当外围IO设备就绪时，向CPU发出中断信号。CPU有专门的电路响应中断信号。
-提供低速设备通知CPU的一种异步的方式，CPU可以高速运转同时兼顾低速设备的响应。
-
-- 程序中断的一个简单流程
-![](https://img-blog.csdnimg.cn/20210215155540999.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-不过频繁切换 CPU 也会降低性能。所以又有如下方式
-### DMA (直接存储器访问)
-DMA直接连接主存与I/O设备
-- 当主存与I/O设备交换信息时，无需中断CPU，可提高 CPU 利用率
-![](https://img-blog.csdnimg.cn/20210215155834806.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-CPU速度与I/O设备速度不一致。
-硬盘与外接显卡里都有。
-
-![](https://img-blog.csdnimg.cn/20210215162402746.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\346\214\207\344\273\244\345\222\214\350\277\220\347\256\227\346\230\257\345\246\202\344\275\225\345\215\217\344\275\234\346\236\204\346\210\220CPU\347\232\204\357\274\237.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\346\214\207\344\273\244\345\222\214\350\277\220\347\256\227\346\230\257\345\246\202\344\275\225\345\215\217\344\275\234\346\236\204\346\210\220CPU\347\232\204\357\274\237.md"
deleted file mode 100644
index 5b4462873b..0000000000
--- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\346\214\207\344\273\244\345\222\214\350\277\220\347\256\227\346\230\257\345\246\202\344\275\225\345\215\217\344\275\234\346\236\204\346\210\220CPU\347\232\204\357\274\237.md"
+++ /dev/null
@@ -1,85 +0,0 @@
-连通“指令”和“计算”这两大功能，才能构建完整的CPU。
-# 1 指令周期（Instruction Cycle）
-计算机每执行一条指令的过程，可分解为如下步骤：
-1. Fetch（取指令）
-指令放在存储器，通过PC寄存器和指令寄存器取出指令的过程，由控制器（Control Unit）操作。
-从PC寄存器找到对应指令地址，据指令地址从内存把具体指令加载到指令寄存器，然后PC寄存器自增
-2. Decode（指令译码）
-据指令寄存器里面的指令，解析成要进行何操作，是R、I、J中的哪一种指令，具体要操作哪些寄存器、数据或内存地址。该阶段也是由控制器执行。
-3. Execute（执行指令）
-实际运行对应的R、I、J这些特定的指令，进行算术逻辑操作、数据传输或者直接的地址跳转。无论是算术操作、逻辑操作的R型指令，还是数据传输、条件分支的I型指令，都由算术逻辑单元（ALU）操作，即由运算器处理。
-如果是一个简单的无条件地址跳转，那可直接在控制器里完成，无需运算器。
-4. 重复1～3
-
-这就是个永动机般的“FDE”循环，即指令周期。
-![](https://img-blog.csdnimg.cn/171cff94c628488b823513f4e38973eb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-CPU还有两个Cycle：
-## Machine Cycle，机器周期或者CPU周期
-CPU内部操作速度很快，但访问内存速度却慢很多。
-每条指令都需要从内存里面加载而来，所以一般把从内存里面读取一条指令的最短时间，称为CPU周期。
-## Clock Cycle，时钟周期及机器主频
-一个CPU周期，通常由几个时钟周期累积。一个CPU周期时间，就是这几个Clock Cycle总和。
-
-对于一个指令周期，取出一条指令，然后执行它，至少需两个CPU周期：
-- 取出指令，至少得一个CPU周期
-- 执行指令，至少也得一个CPU周期
-因为执行完的结果，还要写回内存
-## 三个周期（Cycle）之间的关系
-![](https://img-blog.csdnimg.cn/9d86b21db1494646a5fcde3bd86fb75d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-一个指令周期，包含多个CPU周期，而一个CPU周期包含多个时钟周期。
-# 2 建立数据通路
-名字是什么其实并不重要，一般可以认为，数据通路就是我们的处理器单元，通常由两类原件组成：
-- 操作元件，也叫组合逻辑元件（Combinational Element），就是ALU
-在特定的输入下，根据下面的组合电路的逻辑，生成特定的输出。
-- 存储元件，也叫状态元件（State Element）
-如在计算过程中要用到的寄存器，无论是通用寄存器还是状态寄存器，都是存储元件。
-
-通过数据总线把它们连接起来，就可完成数据存储、处理和传输，即建立了数据通路。
-## 控制器
-可以把它看成只是机械地重复“Fetch - Decode - Execute“循环中的前两个步骤，然后把最后一个步骤，通过控制器产生的控制信号，交给ALU去处理。
-### 控制器将CPU指令解析成不同输出信号
-目前Intel CPU支持2000个以上指令。说明控制器输出的控制信号，至少有2000种不同组合。
-
-运算器里的ALU和各种组合逻辑电路，可认为是一个**固定功能的电路**。
-控制器“翻译”出来的，就是不同控制信号，告诉ALU去做不同计算。正是控制器，才让我们能“编程”实现功能，才铸就了“存储程序型计算机”。
-- 指令译码器将输入的机器码，解析成不同操作码、操作数，然后传输给ALU计算
-![](https://img-blog.csdnimg.cn/cb0596de354c42baad326015b7a6f151.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-# 3 CPU对硬件电路的要求
-搭建CPU，还得在数字电路层面，实现如下功能。
-## ALU
-就是个无状态，根据输入计算输出结果的第一个电路。
-## 支持状态读写的电路元件 - 寄存器
-要有电路能存储上次计算结果。
-
-该计算结果不一定要立刻给下游电路使用，但可在需要时直接用。常见支持状态读写的电路：
-- 锁存器（Latch）
-- D触发器（Data/Delay Flip-flop）电路
-## 自动”电路，按固定周期实现PC寄存器自增
-自动执行Fetch - Decode - Execute。
-
-我们的程序执行，并非靠人工拨动开关执行指令。得有个“自动”电路，无休止执行一条条指令。
-
-看似复杂的各种函数调用、条件跳转，只是修改了PC寄存器保存的地址。PC寄存器里面的地址一修改，计算机即可加载一条指令新指令，往下运行。
-PC寄存器还叫程序计数器，随时间变化，不断计数。数字变大了，就去执行一条新指令。所以，我们需要的就是个自动计数的电路。
-## 译码电路
-无论是decode指令，还是对于拿到的内存地址去获取对应的数据或者指令，都要通过一个电路找到对应数据，就是“译码器”电路。
-
-把这四类电路，通过各种方式组合在一起就能组成CPU。要实现这四种电路中的中间两种，我们还需要时钟电路的配合。下一节，我们一起来看一看，这些基础的电路功能是怎么实现的，以及怎么把这些电路组合起来变成一个CPU。
-# 总结
-至此，CPU运转所需的数据通路和控制器介绍完了，也找出完成这些功能，需要的4种基本电路：
-- ALU这样的组合逻辑电路
-- 存储数据的锁存器和D触发器电路
-- 实现PC寄存器的计数器电路
-- 解码和寻址的译码器电路
-
-> CPU 好像一个永不停歇的机器，一直在不停地读取下一条指令去运行。那为什么 CPU 还会有满载运行和 Idle 闲置的状态呢？
-
-操作系统内核有 idle 进程，优先级最低，仅当其他进程都阻塞时被调度器选中。idle 进程循环执行 HLT 指令，关闭 CPU 大部分功能以降低功耗，收到中断信号时 CPU 恢复正常状态。 CPU在空闲状态就会停止执行，即切断时钟信号，CPU主频会瞬间降低为0，功耗也会瞬间降为0。由于这个空闲状态是十分短暂的，所以你在任务管理器也只会看到CPU频率下降，不会看到降为0。 当CPU从空闲状态中恢复时，就会接通时钟信号，CPU频率就会上升。所以你会在任务管理器里面看到CPU的频率起伏变化。
-
-uptime 命令查看平均负载
-![](https://img-blog.csdnimg.cn/ebdc106681da4dba974a91c1147092a5.png)
-满载运行就是平均负载为1.0(一个一核心CPU)，定义为特定时间间隔内运行队列中的平均线程数。
-load average 表示机器一段时间内的平均load，越低越好。过高可能导致机器无法处理其他请求及操作，甚至死机。
-
-当CUP执行完当前系统分配的任务，为省电，系统将执行空闲任务（idle task），该任务循环执行HLT指令，CPU就会停止指令的执行，且让CPU处于HALT状态，CPU虽停止指令执行，且CPU部分功能模块将会被关闭（以低功耗），但CPU的LAPIC（Local Advanced Programmable Interrupt Controller）并不会停止工作，即CPU将会继续接收外部中断、异常等外部事件（事实上，CPU HALT状态的退出将由外部事件触发）。“Idle 闲置”是一种低功耗的状态，cpu在执行最低功耗的循环指令。实际上并非啥都没干，而是一直在干最最轻松的事儿。
-当CPU接收到这些外部事件，将会从HALT状态恢复，执行中断服务函数，且当中断服务函数执行完毕后，指令寄存器（CS:EIP）将会指向HLT指令的下一条指令，即CPU继续执行HLT指令之后的程序。
\ No newline at end of file
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\350\256\241\347\256\227\346\234\272\346\200\273\347\272\277.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\350\256\241\347\256\227\346\234\272\346\200\273\347\272\277.md"
deleted file mode 100644
index 4a973329dc..0000000000
--- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\350\256\241\347\256\227\346\234\272\346\200\273\347\272\277.md"
+++ /dev/null
@@ -1,67 +0,0 @@
-最常见的USB（Universal Serial Bus），提供了对外连接的接口，不同设备可通过USB接口进行连接，连接的标准也统一了外围设备接口。
-还有 PCI、ISA、Thunderbolt 等总线，都是为解决不同设备间的通信问题。
-
-试想没有总线，新加个输入设备后，为了维持与各个设备的通信又得加一堆线路。而有了 I/O 总线后就像下图达到解耦而统一。
-![](https://img-blog.csdnimg.cn/20210216223715987.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## 分类
-### 片内总线
-芯片内部的总线，寄存器与寄存器之间，寄存器与控制器、运算器之间。
-高集成度芯片内部的信息传输线。
-![](https://img-blog.csdnimg.cn/20210216224741415.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-### 系统总线
-![](https://img-blog.csdnimg.cn/2021021622391799.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-#### 数据总线
-双向传输各个部件的数据信息，数据总线的位数(总线宽度)一般与CPU位数相同(32位、 64位)
-#### 地址总线
-指定源数据或目的数据在内存中的地址，地址总线的位数与存储单元有关。
-
-```bash
-地址总线位数=n
-寻址范围: 0~2^n
-```
-
-#### 控制总线
-发出各种控制信号的传输线，控制信号经由控制总线从一个组件发给另外一个组件，控制总线可监视不同组件之间的状态(就绪/未就绪)。
-
-CPU、主内存、I0设备、各组件之间的信息传输线。
-
-## 仲裁
-总线上可能挂接多个设备，为了解决总线控制权的竞争问题，必须有总线仲裁部件，以某种规则裁决、分配总线的控制权。
-
-多个主设备提出总线控制请求时，一般采用优先级或公平策略进行仲裁。
-按照总线仲裁电路的位置不同，仲裁方式分为:
-- 集中式仲裁;
-- 分布(散)式仲裁;
-
-### 方式
-#### 链式查询
-![](https://img-blog.csdnimg.cn/20210215133514264.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-总线授权信号被依次串行地传送到所连接的外围设
-备上进行比较。
-离总线控制器的逻辑距离决定，越近优先级越高。
-
-优点：
-- 电路复杂度低，仲裁方式简单
-缺点：
-- 优先级低的设备难以获得总线使用权
-- 对电路故障敏感
-
-#### 计时器定时查询
-![](https://img-blog.csdnimg.cn/20210215135726292.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-仲裁控制器对设备编号并使用计数器累计计数，接收到仲裁信号后，往所有设备发出计数值，当查询计数器计数值与发出请求的设备编号一致时，中止查询，该设备获总线控制权。
-
-优先级灵活：计数器初值、设备编号可通过程序设定，优先次序可用程序控制。 
-
-
-#### 独立请求
-![](https://img-blog.csdnimg.cn/20210215140807440.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-各设备均通过专用请求信号线与仲裁器连接，且通过
-独立的授权信号线接收总线批准信号。
-
-优点
-- 响应速度快，优先顺序可动态改变
-
-缺点
-- 设备连线多，总线控制复杂
\ No newline at end of file
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\351\233\266\346\213\267\350\264\235\357\274\210Zero Copy\357\274\211\346\212\200\346\234\257\345\210\260\345\272\225\346\230\257\344\273\200\344\271\210\357\274\237.md" "b/\346\223\215\344\275\234\347\263\273\347\273\237/\351\233\266\346\213\267\350\264\235\357\274\210Zero Copy\357\274\211\346\212\200\346\234\257\345\210\260\345\272\225\346\230\257\344\273\200\344\271\210\357\274\237.md"
deleted file mode 100644
index ba67fb6257..0000000000
--- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\351\233\266\346\213\267\350\264\235\357\274\210Zero Copy\357\274\211\346\212\200\346\234\257\345\210\260\345\272\225\346\230\257\344\273\200\344\271\210\357\274\237.md"	
+++ /dev/null
@@ -1,25 +0,0 @@
-rabbitmq 这么高吞吐量都是因为零拷贝技术，本文以 kafka 为例讲解。
-
-消息从发送到落地保存，broker 维护的消息日志本身就是文件目录，每个文件都是二进制保存，生产者和消费者使用相同格式来处理。
-
-Con获取消息时，服务器先从硬盘读取数据到内存，然后将内存中数据通过 socket 发给Con。
-
-Linux的零拷贝技术：当数据在磁盘和网络之间传输时，避免昂贵的内核态数据拷贝，从而实现快速数据传输。
-Linux平台实现了这样的零拷贝机制，但Windows必须要到Java 8的60更新版本才能“享受”到。
-![](https://img-blog.csdnimg.cn/img_convert/74e0931f6470ebdcadd5ed9fbd8897f2.png)
-- os将数据从磁盘读入到内核空间的页缓存
-- 应用程序将数据从内核空间读入到用户空间缓存
-- 应用程序将数据写回到内核空间到 socket 缓存
-- os将数据从 socket 缓冲区复制到网卡缓冲区，以便将数据经网络发出
-
-该过程涉及：
-- 4 次上下文切换
-- 4 次数据复制
-有两次复制操作是 CPU 完成的。但该过程中，数据完全无变化，仅是从磁盘复制到网卡缓冲区。
-
-而通过“零拷贝”技术，能去掉这些没必要的数据复制操作， 也减少了上下文切换次数。
-现代的 unix 操作系统提供一个优化的代码路径，将数据从页缓存传输到 socket; 
-在 Linux 中，是通过 sendfile 系统调用完成的。Java 提供了访问这个系统调用的方法:**FileChannel.transferTo** API
-![](https://img-blog.csdnimg.cn/img_convert/633eeab1746ea396f2d67bab8fb3fbc2.png)
-使用 sendfile，只需一次拷贝，允许os将数据直接从页缓存发送到网络。
-所以在这个优化的路径中， 只有最后一步：将数据拷贝到网卡缓存中是必须的。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(3)-Kafka\347\232\204\350\207\252\346\210\221\345\256\232\344\275\215.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(3)-Kafka\347\232\204\350\207\252\346\210\221\345\256\232\344\275\215.md"
deleted file mode 100644
index 7c1185ef34..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(3)-Kafka\347\232\204\350\207\252\346\210\221\345\256\232\344\275\215.md"
+++ /dev/null
@@ -1,85 +0,0 @@
-Kafka，具有高水平扩展和高吞吐量的分布式消息系统。
-
-Kafka对消息保存时根据Topic进行归类，发送消息者成为Producer，消息接受者成为Consumer，此外kafka集群有多个kafka实例组成，每个实例(server)称为broker。
-
-无论是Kafka集群,还是producer和consumer都依赖于zookeeper来保证系统可用性，为集群保存一些meta信息。
-
-- 主流MQ对比
-![](https://img-blog.csdnimg.cn/20191113003523808.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-
-**Apache Kafka是消息引擎系统，也是一个分布式流处理平台（Distributed Streaming Platform）**
-
-> Kafka是LinkedIn公司内部2011年孵化的项目。LinkedIn最开始有强烈的数据强实时处理方面的需求，用作LinkedIn的活动流(Activity Stream)和运营数据处理管道(Pipeline) 的基础。其内部的诸多子系统要执行多种类型的数据处理与分析，主要包括业务系统和应用程序性能监控，以及用户行为数据处理等。
-
-# 1 Kafka主要特性
-## 1.1 流特性
-Kafka是一个流处理平台，流平台需如下特性：
-- 可`发布和订阅`流数据，类似MQ
-- 以容错方式`存储`流数据
-- 可在流数据产生时就进行处理(Kafka Stream)
-
-## 1.2 适用场景
-- 基于Kafka，构造实时流数据管道，让系统或应用之间可靠地获取数据
-- 构建实时流式应用程序，处理流数据或基于数据做出反应
-
-# 2 遇到的问题
-## 数据正确性不足
-数据的收集主要采用轮询（Polling），确定轮询间隔时间就成了高度经验化的难题。虽然可采用一些启发式算法（Heuristic）来评估，但一旦指定不当，还是会造成较大数据偏差。
-## 系统高度定制化，维护成本高
-各子系统都需对接数据收集模块，引入了大量定制开销。
-
-LinkedIn尝试过使用ActiveMQ解决这些问题，但并不理想，显然需要有一个“大一统”系统来取代现有的工作方式，它就是Kafka。
-
-Kafka自诞生就是以消息引擎系统的面目出现在大众视野，翻看0.10.0.0之前的官网说明：
-![](https://img-blog.csdnimg.cn/20190825021942674.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190825022242473.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)Kafka社区将其清晰地定位为一个分布式、分区化且带备份功能的日志提交（Commit Log）服务。
-
-> Kafka作者之一Jay Kreps曾经谈及过命名的原因。
-因为Kafka系统的写性能很强，所以找了个作家的名字来命名似乎是一个好主意。大学期间我上了很多文学课，非常喜欢Franz Kafka这个作家，另外为开源软件起这个名字听上去很酷。
-
-Kafka旨在提供如下
-# 3 特性
-- 提供一套API实现生产者和消费者
-- 降低网络传输和磁盘存储开销
-- 实现高伸缩性架构
-
-# 4 流处理
-随Kafka不断完善，Jay等大神们意识到将其开源是个非常棒的主意，因此在2011年Kafka正式进入到Apache基金会孵化并于次年10月顺利毕业成为Apache顶级项目。
-
-在大数据领域，Kafka在承接上下游、串联数据流管道方面发挥了重要的作用：
-`所有的数据几乎都要从一个系统流入Kafka然后再流向下游的另一个系统中`。
-
-这引发了Kafka社区的思考：与其我把数据从一个系统传递到下一个系统中做处理，何不自己实现一套流处理框架？
-Kafka社区于0.10.0.0版本正式推出了流处理组件**Kafka Streams**，也正是从这个版本开始，Kafka正式“变身”为分布式的流处理平台，而不仅仅是消息引擎系统。
-今天Apache Kafka是和Storm/Spark/Flink同等级的实时流处理平台。
-
-## 优势
-### 更易实现端到端的正确性（Correctness）
-Google大神Tyler曾经说过，流处理要最终替代它的“兄弟”批处理需要具备两点核心优势：
-- 实现正确性
-- 提供能够推导时间的工具
-
-**实现正确性是流处理能够匹敌批处理的基石**
-正确性一直是批处理的强项，而`实现正确性的基石则是要求框架能提供精确一次处理语义，即处理一条消息有且只有一次机会能够影响系统状态`
-目前主流的大数据流处理框架都宣称实现了精确一次处理语义，但这是有限定条件的，即它们`只能实现框架内的精确一次处理语义，无法实现端到端`
-因为当这些框架与外部消息引擎系统结合时，无法影响到外部系统的处理语义，所以Spark/Flink从Kafka读取消息之后进行有状态的数据计算，最后再写回Kafka，只能保证在Spark/Flink内部，这条消息对于状态的影响只有一次
-但是计算结果有可能多次写入到Kafka，因为它们不能控制Kafka的语义处理
-相反地，Kafka则不是这样，因为所有的数据流转和计算都在Kafka内部完成，故Kafka可以实现端到端的精确一次处理语义
-
-> 举个例子，使用Kafka计算某网页的PV——我们将每次网页访问都作为一个消息发送的Kafka
-> PV的计算就是我们统计Kafka总共接收了多少条这样的消息即可
-> 精确一次处理语义表示每次网页访问都会产生且只会产生一条消息，否则有可能产生多条消息或压根不产生消息。
-
-## 流式计算的定位
-官网明确**Kafka Streams**是一个用于搭建实时流处理的客户端库而非是一个完整的功能系统。
-
-不能期望Kafka提供类似集群调度、弹性部署等开箱即用的运维特性，需要自己选择适合的工具或系统来帮助Kafka流处理应用实现这些功能。这的确是一个“双刃剑”设计，也是Kafka社区“剑走偏锋”不正面PK其他流计算框架的特意考量。
-大公司的流处理平台一定是大规模部署，因此具备集群调度功能以及灵活的部署方案是不可或缺的要素，但毕竟这世界上还存在着很多中小企业，它们的流处理数据量并不巨大，逻辑也并不复杂，部署几台或十几台机器足以应付。在这样的需求之下，搭建重量级的完整性平台实在是“杀鸡焉用牛刀”，而这正是Kafka流处理组件的用武之地。
-因此未来在流处理框架中，Kafka应该有一席之地。
-
-> Kafka能够被用作分布式存储系统
-Kafka作者之一Jay Kreps曾经专门写过一篇文章阐述为什么能把Kafka用作分布式存储。不过还真没见过在生产环境中把Kafka当作持久化存储来用的。
-
-> 参考
-> - Apache Kafka实战
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(7)-\344\274\230\351\233\205\345\234\260\351\203\250\347\275\262 Kafka \351\233\206\347\276\244.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(7)-\344\274\230\351\233\205\345\234\260\351\203\250\347\275\262 Kafka \351\233\206\347\276\244.md"
deleted file mode 100644
index e1f5682e02..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(7)-\344\274\230\351\233\205\345\234\260\351\203\250\347\275\262 Kafka \351\233\206\347\276\244.md"	
+++ /dev/null
@@ -1,109 +0,0 @@
-生产环境需考量各种因素，结合自身业务需求而制定。看一些考虑因素（以下顺序，可是分了顺序的哦）
-# 1 OS
-- Kafka不是JVM上的中间件吗？Java又是跨平台语言，把Kafka安装到不同的os有啥区别吗？
-区别相当大！
-
-Kafka的确由Scala/Java编写，编译后源码就是“.class”文件。部署到啥OS应该一样，但毋庸置疑，部署在Linux上的生产环境是最多的，具体原因你能谈笑风生吗？
-## 1.1 I/O模型
-I/O模型其实就是os执行I/O指令的方法，主流的I/O模型通常有5种类型：
-1. 阻塞式I/O
-e.g. Java中Socket的阻塞模式
-2. 非阻塞式I/O
-e.g. Java中Socket的非阻塞模式
-3.  I/O多路复用
-e.g. Linux中的系统调用`select`函数
-4. 信号驱动I/O
-e.g. epoll系统调用则介于第三种和第四种模型之间
-5. 异步I/O
-e.g. 很少有Linux支持，反而Windows系统提供了一个叫IOCP线程模型属于该类
-
-I/O模型与Kafka的关系几何？Kafka Client 底层使用了Java的selector，而selector
-- 在Linux上的实现机制是epoll
-- 在Windows平台上的实现机制是select
-
-因为这点，Kafka部署在Linux上更有优势，能获得更高效的I/O性能。
-## 1.2 数据网络传输效率
-- Kafka生产和消费的消息都是通过网络传输，但消息保存在哪呢？
-肯定是磁盘！
-
-故Kafka需在磁盘和网络间进行大量数据传输。在Linux部署Kafka能够享受到零拷贝技术带来的快速数据传输特性。
-## 1.3 社区生态
-社区对Windows平台上发现的Kafka Bug不做任何承诺。
-# 2 磁盘
-## 2.1 机械硬盘 or SSD
-- 前者便宜且容量大，但易坏！
-- 后者性能优势大，但是贵！
-
-建议是使用普通机械硬盘即可。
-- Kafka虽然大量使用磁盘，可多是顺序读写操作，一定程度规避了机械磁盘最大的劣势，即随机读写慢。从这一点上来说，使用SSD并没有太大性能优势，机械磁盘物美价廉
-- 而它因易损坏而造成的可靠性差等缺陷，又由Kafka在软件层面提供机制来保证
-## 2.2 是否应该使用磁盘阵列（RAID）
-使用RAID的主要优势：
-- 提供冗余的磁盘存储空间
-- 提供负载均衡
-
-对于Kafka
-- Kafka自己实现了冗余机制，提供高可靠性
-- 通过分区设计，也能在软件层面自行实现负载均衡
-
-RAID优势也就没有那么明显了。虽然实际上依然有很多大厂确实是把Kafka底层的存储交由RAID，只是目前Kafka在存储这方面提供了越来越便捷的高可靠性方案，因此在线上环境使用RAID似乎变得不是那么重要了。
-
-综上，追求性价比的公司可不搭建RAID，使用普通磁盘组成存储空间即可。使用机械磁盘完全能够胜任Kafka线上环境。
-## 2.3 磁盘容量
-集群到底需要多大？
-Kafka需要将消息保存在磁盘上，这些消息默认会被保存一段时间然后自动被删除。
-虽然这段时间是可以配置的，但你应该如何结合自身业务场景和存储需求来规划Kafka集群的存储容量呢？
-
-假设有个业务
-- 每天需要向Kafka集群发送1亿条消息
-- 每条消息保存两份以防止数据丢失
-- 消息默认保存两周时间
-
-现在假设消息的平均大小是1KB，那么你能说出你的Kafka集群需要为这个业务预留多少磁盘空间吗？
-
-计算：
-- 每天1亿条1KB的消息，存两份
-`1亿 * 1KB * 2 / 1000 / 1000 = 200GB`
-
-- 一般Kafka集群除消息数据还存其他类型数据，比如索引数据
-再为其预留10%磁盘空间，因此总的存储容量就是220GB
-
-- 要存两周，那么整体容量即为
-220GB * 14，大约3TB
-- Kafka支持数据的压缩，假设压缩比是0.75
-那么最后规划的存储空间就是0.75 * 3 = 2.25TB
-
-总之在规划磁盘容量时你需要考虑下面这几个元素：
-- 新增消息数
-- 消息留存时间
-- 平均消息大小
-- 备份数
-- 是否启用压缩
-# 3 带宽
-对于Kafka这种通过网络进行大数据传输的框架，带宽易成为瓶颈。
-
-普通以太网络，带宽主要有两种：
-- 1Gbps的千兆网络
-- 10Gbps的万兆网络
-
-以千兆网络为例，说明带宽资源规划。真正要规划的是所需的Kafka服务器的数量。假设机房环境是千兆网络，即1Gbps，现在有业务，其目标或SLA是在1小时内处理1TB的业务数据。
-
-到底需要多少台Kafka服务器来完成这个业务呢？
-### 计算
-带宽1Gbps，即每秒处理1Gb数据
-假设每台Kafka服务器都是安装在专属机器，即每台Kafka机器上没有混入其他服务
-通常情况下你只能假设Kafka会用到70%的带宽资源，因为总要为其他应用或进程留一些资源。超过70%的阈值就有网络丢包可能性，故70%的设定是一个比较合理的值，也就是说单台Kafka服务器最多也就能使用大约700Mb带宽。
-
-这只是它能使用的最大带宽资源，你不能让Kafka服务器常规性使用这么多资源，故通常要再额外预留出2/3的资源，即
-`单台服务器使用带宽700Mb / 3 ≈ 240Mbps`
-这里的2/3其实是相当保守的，可以结合机器使用情况酌情减少该值
-
-有了240Mbps，可以计算1小时内处理1TB数据所需的服务器数量了。
-根据这个目标，每秒需要处理2336Mb的数据，除以240，约等于10台服务器。
-如果消息还需要额外复制两份，那么总的服务器台数还要乘以3，即30台。
-# 总结
-部署Kafka环境，一开始就要思考好实际场景下业务所需的集群环境，不能仅从单个维度上进行评估。
-
-> 参考
-> - Linux内核模型架构
-> - Kafka核心技术与实战
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(1)-\344\270\272\344\275\225\345\244\247\345\216\202\351\203\275\345\234\250\344\275\277\347\224\250Kafka\346\266\210\346\201\257\345\274\225\346\223\216\347\263\273\347\273\237?.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\270\200)-\344\270\272\344\275\225\345\244\247\345\216\202\351\203\275\345\234\250\344\275\277\347\224\250Kafka\346\266\210\346\201\257\345\274\225\346\223\216\347\263\273\347\273\237?.md"
similarity index 66%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(1)-\344\270\272\344\275\225\345\244\247\345\216\202\351\203\275\345\234\250\344\275\277\347\224\250Kafka\346\266\210\346\201\257\345\274\225\346\223\216\347\263\273\347\273\237?.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\270\200)-\344\270\272\344\275\225\345\244\247\345\216\202\351\203\275\345\234\250\344\275\277\347\224\250Kafka\346\266\210\346\201\257\345\274\225\346\223\216\347\263\273\347\273\237?.md"
index 530686f88b..b3e056579d 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(1)-\344\270\272\344\275\225\345\244\247\345\216\202\351\203\275\345\234\250\344\275\277\347\224\250Kafka\346\266\210\346\201\257\345\274\225\346\223\216\347\263\273\347\273\237?.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\270\200)-\344\270\272\344\275\225\345\244\247\345\216\202\351\203\275\345\234\250\344\275\277\347\224\250Kafka\346\266\210\346\201\257\345\274\225\346\223\216\347\263\273\347\273\237?.md"
@@ -1,43 +1,37 @@
-> 这MQ有啥用？
+# 1 消息引擎是啥?
+较于“消息队列”“消息中间件”，消息引擎系统可能更合适，因为消息队列给出了一个很不明确的暗示，仿佛Kafka是基于队列构建的；而消息中间件说法有过度夸张“中间件”之嫌，让人搞不清楚这个中间件到底干啥的。
 
-MQ是一组规范。
-利用这组规范可以在不同系统间传递语义准确的消息，实现松耦合的异步式数据传递。
+Kafka这种系统在国外有专属名字 - Messaging System，国内很多文献将其简单翻译成消息系统，这片面强调消息主体的作用，而忽视了这类系统引以为豪的消息传递属性，就像引擎一样，具备某种能量转换传输的能力，所以消息引擎更加贴切。
 
- **系统A发送消息给MQ，系统B从MQ中读取A发送的消息**。
+# 2 这类系统有啥用？
+消息引擎系统是一组规范。企业利用这组规范在不同系统之间传递语义准确的消息，实现松耦合的异步式数据传递。
 
-既然MQ是用于在不同系统间传输消息，那
-
-> 如何设计待传输消息的格式？
+#####  **系统A发送消息给消息引擎系统，系统B从消息引擎系统中读取A发送的消息**
+最基础的消息引擎就是做这事：
+- 消息引擎传输的对象是消息
+- 如何传输消息属于消息引擎设计机制的一部分
 
+既然消息引擎是用于在不同系统间传输消息，那
+# 3 如何设计待传输消息的格式？
 一条消息如何才能做到信息表达业务语义且无歧义，同时还能最大限度提供可重用性以及通用性？
-使用成熟解决方案？比如CSV、XML、JSON、Google的Protocol Buffer、Facebook的Thrift。而Kafka使用纯二进制字节序列。当然还是结构化的消息，只是在使用前都将其转换成二进制字节序列。
-
-MQ还要设定具体传输协议
-
-> 如何传输消息?
+使用成熟解决方案？比如CSV、XML、JSON，又或国外大厂开源的一些序列化框架，比如Google的Protocol Buffer或Facebook的Thrift。而Kafka使用纯二进制字节序列。当然还是结构化的消息，只是在使用前都将其转换成二进制字节序列。
 
-## 点对点模型
+消息引擎系统还要设定具体传输协议
+# 4 如何传输消息?
+## 4.1 点对点模型
 也称消息队列模型。系统A发送的消息只能被系统B接收，其他任何系统都不能读取A发送的消息。
 比如电话客服：同一客户呼入电话只能被一位客服人员处理，其它客服人员不能为该客户服务。
-
-点对点模型里一个消息只会被一个消费者消费，和Java的线程池非常类似（Java线程池的任务也只会被一个线程执行）
-## 发布/订阅模型
+## 4.2 发布/订阅模型
 新增主题（Topic）概念，即逻辑语义相近的消息容器。该模型的发送方也称发布者（Publisher），接收方为订阅者（Subscriber）。
 和点对点模型不同，该模型可能存在多个发布者向相同的主题发消息，而订阅者也可能存在多个，它们都能接收到相同主题的消息。
 比如生活中的报纸订阅就是一种发布/订阅模型。
 
-发布订阅模型里一个消息会被多个消费者消费，本质上是一种消息的广播，在多线程编程领域，可以结合观察者模式实现广播功能。
-
 而Kafka同时支持俩种消息引擎模型哦！
-
-> 消息引擎系统 V.S JMS
-
+# 5 消息引擎系统 V.S JMS
 JMS，Java Message Service，也同时支持两种消息引擎模型。严格说它并非传输协议而仅仅是API。不过JMS太有名以至于很多主流消息引擎系统都支持JMS规范，比如RabbitMQ、Kafka。Kafka也未完全遵照JMS规范。
 
-> 为什么要使用MQ?
-
-## 业务开发
-为什么系统A不直接发送消息给系统B，中间还非得隔个消息引擎？
+# 6 为什么要使用消息引擎?
+为什么系统A不能直接发送消息给系统B，中间还非得隔个消息引擎？
 
 为了**削峰填谷**：
 即缓冲上下游瞬时突发流量，使其更平滑。特别是对于那种发送能力很强的上游系统，若无消息引擎保护，“脆弱”的下游系统可能会直接被消息流量压垮导致服务雪崩。
@@ -51,18 +45,9 @@ Kafka具体又是怎么“抗”峰值流量呢？
 点击之后进入到付费页面。这简单流程就包含多个子服务，比如点击预订按钮会调用订单系统生成对应订单，而处理该订单会依次调用下游的多个子系统服务 ，比如调用支付宝和微信支付的接口、查询你的登录信息、验证酒店信息等。上游订单操作比较简单，其TPS远高于处理订单的下游服务，因此上下游系统直接对接，势必会出现下游服务无法及时处理上游订单从而造成订单堆积。特别秒杀时，上游订单流量瞬时增加，可能直接压跨下游子系统服务。
 
 对上游系统限速？这种做法对上游系统而言显然不合理，毕竟问题并不出现在它。所以更常见的办法是引入像Kafka这样的消息引擎系统来对抗这种上下游系统TPS的错配以及瞬时峰值流量。
-引入Kafka后。上游订单服务不再直接与下游子服务交互。当新订单生成后它仅仅是向Kafka Broker发一条订单消息。下游的各个子服务订阅Kafka中的对应主题，并实时从该主题的各自分区（Partition）中获取到订单消息进行处理，从而实现上游订单服务与下游订单处理服务解耦。当秒杀时，Kafka能将瞬时增加的订单流量全部以消息形式保存在对应主题，既不影响上游服务的TPS，同时也给下游子服务留出了充足的时间去消费它们。这就是Kafka这类消息引擎系统的最大意义。、
-
-## 大数据
-在大量使用分布式数据库、分布式计算集群时：
-- 分析用户行为( pageviews ) ，以便设计广告位
-- 对用户的搜索关键词进行统计，分析流行趋势
-- 有些数据，存数据库浪费，直接存硬盘操作效率又低
-
-这时就可用MQ。
-
-> MQ V.S RPC
+引入Kafka后。上游订单服务不再直接与下游子服务交互。当新订单生成后它仅仅是向Kafka Broker发一条订单消息。下游的各个子服务订阅Kafka中的对应主题，并实时从该主题的各自分区（Partition）中获取到订单消息进行处理，从而实现上游订单服务与下游订单处理服务解耦。当秒杀时，Kafka能将瞬时增加的订单流量全部以消息形式保存在对应主题，既不影响上游服务的TPS，同时也给下游子服务留出了充足的时间去消费它们。这就是Kafka这类消息引擎系统的最大意义。
 
+# 7  MQ V.S RPC
 广义上属于数据流模式（dataflow mode）区别。
 常见数据流：
 1. 通过数据库
@@ -74,7 +59,7 @@ RPC和MQ相似，远程调用一个服务也可看做是一个事件，但不同
 - MQ支持重试
 - 允许发布/订阅模式
 
-应该说RPC是介于通过DB和通过MQ之间的数据流模式。
+当然它们还有其他区别。应该说RPC是介于通过DB和通过MQ之间的数据流模式。
 
-> 参考
-> - Apache Kafka实战
\ No newline at end of file
+# 参考
+- Apache Kafka实战
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\270\203) - \344\274\230\351\233\205\345\234\260\351\203\250\347\275\262 Kafka \351\233\206\347\276\244.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\270\203) - \344\274\230\351\233\205\345\234\260\351\203\250\347\275\262 Kafka \351\233\206\347\276\244.md"
new file mode 100644
index 0000000000..7d79f7f122
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\270\203) - \344\274\230\351\233\205\345\234\260\351\203\250\347\275\262 Kafka \351\233\206\347\276\244.md"	
@@ -0,0 +1,127 @@
+既然是集群，必然有多个Kafka节点，只有单节点构成的Kafka伪集群只能用于日常测试，不可能满足线上生产需求。
+真正的线上环境需要考量各种因素，结合自身的业务需求而制定。看一些考虑因素（以下顺序，可是分了顺序的哦）
+
+# 1 操作系统 - OS
+可能你会问Kafka不是JVM上的大数据框架吗？Java又是跨平台的语言，把Kafka安装到不同的操作系统上会有什么区别吗？
+区别相当大！
+
+确实，Kafka由Scala/Java编写，编译后源码就是“.class”文件。
+本来部署到哪个OS应该一样，但是不同OS的差异还是给Kafka集群带来了相当大的影响。
+毋庸置疑，部署在Linux上的生产环境是最多的。
+
+考虑操作系统与Kafka的适配性，Linux系统显然要比其他两个特别是Windows系统更加适合部署Kafka。可具体原因你能谈笑风生吗？
+## 1.1 I/O模型
+I/O模型可以近似认为I/O模型就是OS执行I/O指令的方法。
+主流的I/O模型通常有5种类型：
+1. 阻塞式I/O
+e.g. Java中Socket的阻塞模式
+2. 非阻塞式I/O
+e.g. Java中Socket的非阻塞模式
+3.  I/O多路复用
+e.g. Linux中的系统调用`select`函数
+4. 信号驱动I/O
+e.g. epoll系统调用则介于第三种和第四种模型之间
+5. 异步I/O
+e.g. 很少有Linux支持，反而Windows系统提供了一个叫IOCP线程模型属于该类
+
+我在这里不详细展开每一种模型的实现细节，因为那不是本文重点。
+
+言归正传，I/O模型与Kafka的关系几何？
+Kafka Client 底层使用了Java的selector，而selector
+- 在Linux上的实现机制是epoll
+- 在Windows平台上的实现机制是select
+
+因此在这一点上将Kafka部署在Linux上是有优势的，能够获得更高效的I/O性能。
+## 1.2 数据网络传输效率
+Kafka生产和消费的消息都是通过网络传输的，而消息保存在哪里呢？
+肯定是磁盘！
+故Kafka需要在磁盘和网络间进行大量数据传输。
+Linux有个零拷贝（Zero Copy）技术，就是当数据在磁盘和网络进行传输时避免昂贵内核态数据拷贝从而实现快速数据传输。Linux平台实现了这样的零拷贝机制，但有些令人遗憾的是在Windows平台上必须要等到Java 8的60更新版本才能“享受”到。
+
+一句话，在Linux部署Kafka能够享受到零拷贝技术所带来的快速数据传输特性带来的极致快感。
+
+## 1.3 社区生态
+社区目前对Windows平台上发现的Kafka Bug不做任何承诺。因此，Windows平台上部署Kafka只适合于个人测试或用于功能验证，千万不要应用于生产环境。
+
+# 2 磁盘
+## 2.1 灵魂拷问：机械硬盘 or 固态硬盘
+- 前者便宜且容量大，但易坏！
+- 后者性能优势大，但是贵！
+
+
+建议是使用普通机械硬盘即可。
+- Kafka虽然大量使用磁盘，可多是顺序读写操作，一定程度上规避了机械磁盘最大的劣势，即随机读写慢。从这一点上来说，使用SSD并没有太大性能优势，机械磁盘物美价廉
+- 而它因易损坏而造成的可靠性差等缺陷，又由Kafka在软件层面提供机制来保证
+
+
+## 2.2 是否应该使用磁盘阵列（RAID）
+使用RAID的两个主要优势在于：
+- 提供冗余的磁盘存储空间
+- 提供负载均衡
+
+不过就Kafka而言
+- Kafka自己实现了冗余机制提供高可靠性
+- 通过分区的设计，也能在软件层面自行实现负载均衡
+
+如此说来RAID的优势也就没有那么明显了。虽然实际上依然有很多大厂确实是把Kafka底层的存储交由RAID的，只是目前Kafka在存储这方面提供了越来越便捷的高可靠性方案，因此在线上环境使用RAID似乎变得不是那么重要了。
+综上，追求性价比的公司可以不搭建RAID，使用普通磁盘组成存储空间即可。使用机械磁盘完全能够胜任Kafka线上环境。
+
+## 2.3 磁盘容量
+集群到底需要多大？
+Kafka需要将消息保存在磁盘上，这些消息默认会被保存一段时间然后自动被删除。
+虽然这段时间是可以配置的，但你应该如何结合自身业务场景和存储需求来规划Kafka集群的存储容量呢？
+
+假设有个业务
+- 每天需要向Kafka集群发送1亿条消息
+- 每条消息保存两份以防止数据丢失
+- 消息默认保存两周时间
+
+现在假设消息的平均大小是1KB，那么你能说出你的Kafka集群需要为这个业务预留多少磁盘空间吗？
+
+计算：
+- 每天1亿条1KB的消息，存两份
+`1亿 * 1KB * 2 / 1000 / 1000 = 200GB`
+
+- 一般Kafka集群除消息数据还存其他类型数据，比如索引数据
+再为其预留10%磁盘空间，因此总的存储容量就是220GB
+
+- 要存两周，那么整体容量即为
+220GB * 14，大约3TB
+- Kafka支持数据的压缩，假设压缩比是0.75
+那么最后规划的存储空间就是0.75 * 3 = 2.25TB
+
+总之在规划磁盘容量时你需要考虑下面这几个元素：
+- 新增消息数
+- 消息留存时间
+- 平均消息大小
+- 备份数
+- 是否启用压缩
+
+# 3 带宽
+对于Kafka这种通过网络进行大数据传输的框架，带宽容易成为瓶颈。
+普通的以太网络，带宽主要有两种：1Gbps的千兆网络和10Gbps的万兆网络，特别是千兆网络应该是一般公司网络的标准配置了
+以千兆网络为例，说明带宽资源规划。
+
+真正要规划的是所需的Kafka服务器的数量。
+假设机房环境是千兆网络，即1Gbps，现在有业务，其目标或SLA是在1小时内处理1TB的业务数据。
+那么问题来了，你到底需要多少台Kafka服务器来完成这个业务呢？
+
+### 计算
+带宽1Gbps，即每秒处理1Gb数据
+假设每台Kafka服务器都是安装在专属机器，即每台Kafka机器上没有混入其他服务
+通常情况下你只能假设Kafka会用到70%的带宽资源，因为总要为其他应用或进程留一些资源。超过70%的阈值就有网络丢包可能性，故70%的设定是一个比较合理的值，也就是说单台Kafka服务器最多也就能使用大约700Mb带宽。
+
+这只是它能使用的最大带宽资源，你不能让Kafka服务器常规性使用这么多资源，故通常要再额外预留出2/3的资源，即
+`单台服务器使用带宽700Mb / 3 ≈ 240Mbps`
+这里的2/3其实是相当保守的，可以结合机器使用情况酌情减少该值
+
+有了240Mbps，可以计算1小时内处理1TB数据所需的服务器数量了。
+根据这个目标，每秒需要处理2336Mb的数据，除以240，约等于10台服务器。
+如果消息还需要额外复制两份，那么总的服务器台数还要乘以3，即30台。
+
+# 总结
+与其盲目上马一套Kafka环境然后事后费力调整，不如在一开始就思考好实际场景下业务所需的集群环境。在考量部署方案时需要通盘考虑，不能仅从单个维度上进行评估。
+
+# 参考
+- Linux内核模型架构
+- Kafka核心技术与实战
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\270\211) -Kafka\347\232\204\350\207\252\346\210\221\344\277\256\345\205\273.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\270\211) -Kafka\347\232\204\350\207\252\346\210\221\344\277\256\345\205\273.md"
new file mode 100644
index 0000000000..81f79dafd1
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\270\211) -Kafka\347\232\204\350\207\252\346\210\221\344\277\256\345\205\273.md"	
@@ -0,0 +1,94 @@
+![](https://ask.qcloudimg.com/http-save/1752328/8rqrjgtw9c.png)**Apache Kafka是消息引擎系统，也是一个分布式流处理平台（Distributed Streaming Platform）**
+
+> Kafka是LinkedIn公司内部孵化的项目。LinkedIn最开始有强烈的数据强实时处理方面的需求，其内部的诸多子系统要执行多种类型的数据处理与分析，主要包括业务系统和应用程序性能监控，以及用户行为数据处理等。
+
+遇到的主要问题：
+
+- 数据正确性不足
+数据的收集主要采用轮询（Polling），确定轮询间隔时间就成了高度经验化的难题。虽然可以采用一些启发式算法（Heuristic）来帮助评估，但一旦指定不当，还是会造成较大的数据偏差。
+- 系统高度定制化，维护成本高
+各子系统都需要对接数据收集模块，引入了大量的定制开销和人工成本
+
+LinkedIn工程师尝试过使用ActiveMQ解决这些问题，但并不理想
+
+显然需要有一个“大一统”的系统来取代现有的工作方式，而这个系统就是Kafka。
+
+Kafka自诞生伊始是以消息引擎系统的面目出现在大众视野中的
+
+如果翻看0.10.0.0之前的官网说明
+
+![](https://ask.qcloudimg.com/http-save/1752328/v04sy696a4.png)
+
+![](https://ask.qcloudimg.com/http-save/1752328/4vt1maxayq.png)Kafka社区将其清晰地定位为一个分布式、分区化且带备份功能的日志提交（Commit Log）服务。
+
+> Kafka作者之一Jay Kreps曾经谈及过命名的原因。
+> 因为Kafka系统的写性能很强，所以找了个作家的名字来命名似乎是一个好主意。大学期间我上了很多文学课，非常喜欢Franz Kafka这个作家，另外为开源软件起这个名字听上去很酷。
+
+Kafka旨在提供三个方面的特性：
+
+- 提供一套API实现生产者和消费者
+- 降低网络传输和磁盘存储开销
+- 实现高伸缩性架构。
+
+随着Kafka的不断完善，Jay等大神们终于意识到将其开源惠及更多的人是一个非常棒的主意，因此在2011年Kafka正式进入到Apache基金会孵化并于次年10月顺利毕业成为Apache顶级项目。
+
+特别在大数据领域，Kafka在承接上下游、串联数据流管道方面发挥了重要的作用：
+
+`所有的数据几乎都要从一个系统流入Kafka然后再流向下游的另一个系统中`
+
+这引发了Kafka社区的思考：与其我把数据从一个系统传递到下一个系统中做处理，何不自己实现一套流处理框架？
+
+Kafka社区于0.10.0.0版本正式推出了流处理组件**Kafka Streams**，也正是从这个版本开始，Kafka正式“变身”为分布式的流处理平台，而不仅仅是消息引擎系统了
+
+今天Apache Kafka是和Storm/Spark/Flink同等级的实时流处理平台。
+
+国内对Kafka是流处理平台的认知还尚不普及，其核心的流处理组件Kafka Streams更是少有大厂在使用
+
+随着在Kafka峰会上各路大神们的鼎力宣传，如今利用Kafka构建流处理平台的案例层出不穷，而了解并有意愿使用Kafka Streams的厂商也是越来越多
+
+# 优势
+
+## 更易实现端到端的正确性（Correctness）
+
+Google大神Tyler曾经说过，流处理要最终替代它的“兄弟”批处理需要具备两点核心优势
+
+- 实现正确性
+- 提供能够推导时间的工具
+
+**实现正确性是流处理能够匹敌批处理的基石**
+
+正确性一直是批处理的强项，而`实现正确性的基石则是要求框架能提供精确一次处理语义，即处理一条消息有且只有一次机会能够影响系统状态`
+
+目前主流的大数据流处理框架都宣称实现了精确一次处理语义，但这是有限定条件的，即它们`只能实现框架内的精确一次处理语义，无法实现端到端`
+
+因为当这些框架与外部消息引擎系统结合时，无法影响到外部系统的处理语义，所以Spark/Flink从Kafka读取消息之后进行有状态的数据计算，最后再写回Kafka，只能保证在Spark/Flink内部，这条消息对于状态的影响只有一次
+
+但是计算结果有可能多次写入到Kafka，因为它们不能控制Kafka的语义处理
+
+相反地，Kafka则不是这样，因为所有的数据流转和计算都在Kafka内部完成，故Kafka可以实现端到端的精确一次处理语义
+
+> 举个例子，使用Kafka计算某网页的PV——我们将每次网页访问都作为一个消息发送的Kafka
+> PV的计算就是我们统计Kafka总共接收了多少条这样的消息即可
+> 精确一次处理语义表示每次网页访问都会产生且只会产生一条消息，否则有可能产生多条消息或压根不产生消息。
+
+## 流式计算的定位
+
+官网上明确**Kafka Streams**是一个用于搭建实时流处理的客户端库而非是一个完整的功能系统
+
+不能期望着Kafka提供类似于集群调度、弹性部署等开箱即用的运维特性，需要自己选择适合的工具或系统来帮助Kafka流处理应用实现这些功能。
+
+这的确是一个“双刃剑”的设计，也是Kafka社区“剑走偏锋”不正面PK其他流计算框架的特意考量
+
+大型公司的流处理平台一定是大规模部署的，因此具备集群调度功能以及灵活的部署方案是不可或缺的要素
+
+但毕竟这世界上还存在着很多中小企业，它们的流处理数据量并不巨大，逻辑也并不复杂，部署几台或十几台机器足以应付。在这样的需求之下，搭建重量级的完整性平台实在是“杀鸡焉用牛刀”，而这正是Kafka流处理组件的用武之地
+
+因此从这个角度来说，未来在流处理框架中，Kafka应该是有一席之地的。
+
+> Kafka能够被用作分布式存储系统
+
+Kafka作者之一Jay Kreps曾经专门写过一篇文章阐述为什么能把Kafka用作分布式存储。不过我觉得你姑且了解下就好了，我从没有见过在实际生产环境中，有人把Kafka当作持久化存储来用 。
+
+# 参考
+
+- Apache Kafka实战
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(2)-Kafka\346\266\210\346\201\257\346\250\241\345\236\213\346\240\270\345\277\203\346\246\202\345\277\265.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\272\214) - \346\221\270\346\270\205 Kafka \347\232\204\"\351\273\221\350\257\235\".md"
similarity index 99%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(2)-Kafka\346\266\210\346\201\257\346\250\241\345\236\213\346\240\270\345\277\203\346\246\202\345\277\265.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\272\214) - \346\221\270\346\270\205 Kafka \347\232\204\"\351\273\221\350\257\235\".md"
index 18715eeab2..ab2d89f564 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(2)-Kafka\346\266\210\346\201\257\346\250\241\345\236\213\346\240\270\345\277\203\346\246\202\345\277\265.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\344\272\214) - \346\221\270\346\270\205 Kafka \347\232\204\"\351\273\221\350\257\235\".md"	
@@ -1,4 +1,4 @@
-2qKafka属分布式消息引擎系统,提供一套完备的消息发布与订阅解决方案。
+Kafka属分布式消息引擎系统,提供一套完备的消息发布与订阅解决方案。
 
 在Kafka中，发布订阅的对象是主题（Topic），可为每个业务、每个应用甚至是每类数据都创建专属的主题。
 
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(4) -Kafka\351\227\250\346\264\276\347\237\245\345\244\232\345\260\221.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\345\233\233) -Kafka\351\227\250\346\264\276\347\237\245\345\244\232\345\260\221.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(4) -Kafka\351\227\250\346\264\276\347\237\245\345\244\232\345\260\221.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\345\256\236\346\210\230/Kafka\345\256\236\346\210\230(\345\233\233) -Kafka\351\227\250\346\264\276\347\237\245\345\244\232\345\260\221.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\346\236\266\346\236\204\350\247\243\346\236\2201\344\271\213\350\203\214\346\231\257\345\217\212\346\236\266\346\236\204\344\273\213\347\273\215.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\346\236\266\346\236\204\350\247\243\346\236\2201\344\271\213\350\203\214\346\231\257\345\217\212\346\236\266\346\236\204\344\273\213\347\273\215.md"
index 9cd392fcb2..8e4bc4660e 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\346\236\266\346\236\204\350\247\243\346\236\2201\344\271\213\350\203\214\346\231\257\345\217\212\346\236\266\346\236\204\344\273\213\347\273\215.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\346\236\266\346\236\204\350\247\243\346\236\2201\344\271\213\350\203\214\346\231\257\345\217\212\346\236\266\346\236\204\344\273\213\347\273\215.md"
@@ -181,3 +181,16 @@ public void sendMessage() throws InterruptedException{
     
 # 参考
 [Kafka 设计解析（一）：Kafka 背景及架构介绍](https://www.infoq.cn/article/kafka-analysis-part-1/)
+
+# X 交流学习
+![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+
+
+## [博客](https://blog.csdn.net/qq_33589510)
+
+
+
+## [Github](https://github.com/Wasabi1234)
+
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\351\253\230\346\200\247\350\203\275\345\216\237\347\220\206\345\210\206\346\236\220.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\351\253\230\346\200\247\350\203\275\345\216\237\347\220\206\345\210\206\346\236\220.md"
index 2c90d94a66..65c0b5f134 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\351\253\230\346\200\247\350\203\275\345\216\237\347\220\206\345\210\206\346\236\220.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Kafka/Kafka\351\253\230\346\200\247\350\203\275\345\216\237\347\220\206\345\210\206\346\236\220.md"
@@ -214,5 +214,25 @@ Kafka 中存储的一般都是海量的消息数据，为了避免日志文件
 ## 4.3 消息写入的性能
 我们现在大部分企业仍然用的是机械结构的磁盘，如果把消息以随机的方式写入到磁盘，那么磁盘首先要做的就是寻址，也就是定位到数据所在的物理地址，在磁盘上就要找到对应的柱面、磁头以及对应的扇区;这个过程相对内 存来说会消耗大量时间，为了规避随机读写带来的时间消耗，kafka 采用顺序写的方式存储数据。
 
-即使是这样，但是频繁的 I/O 操作仍然会造成磁盘的性能瓶颈，所以 kafka 还有一个性能策略。
+即使是这样，但是频繁的 I/O 操作仍然会造成磁盘的性能瓶颈，所以 kafka 还有一个性能策略
+
+## 4.4 零拷贝
+消息从发送到落地保存，broker 维护的消息日志本身就是文件目录，每个文件都是二进制保存，生产者和消费者使用相同的格式来处理。在消费者获取消息时，服务器先从硬盘读取数据到内存，然后把内存中的数据原封不动的通 过 socket 发送给消费者。
+
+虽然这个操作描述起来很简单， 但实际上经历了很多步骤
+![](https://uploadfiles.nowcoder.com/files/20190425/5088755_1556181389694_16782311-d53f60c9f4f81137.png)
+
+▪ 操作系统将数据从磁盘读入到内核空间的页缓存
+▪ 应用程序将数据从内核空间读入到用户空间缓存中
+▪ 应用程序将数据写回到内核空间到 socket 缓存中
+▪ 操作系统将数据从 socket 缓冲区复制到网卡缓冲区，以便将数据经网络发出
+
+这个过程涉及到 4 次上下文切换以及 4 次数据复制，并且有两次复制操作是由 CPU 完成。但是这个过程中，数据完全没有进行变化，仅仅是从磁盘复制到网卡缓冲区。
+
+通过“零拷贝”技术，可以去掉这些没必要的数据复制操作， 同时也会减少上下文切换次数。现代的 unix 操作系统提供 一个优化的代码路径，用于将数据从页缓存传输到 socket; 
+在 Linux 中，是通过 sendfile 系统调用来完成的。
+Java 提供了访问这个系统调用的方法:FileChannel.transferTo API
+![](https://uploadfiles.nowcoder.com/files/20190425/5088755_1556181389536_16782311-4bbdb21c5f83a6e9.png)
+
+使用 sendfile，只需要一次拷贝就行，允许操作系统将数据直接从页缓存发送到网络上。所以在这个优化的路径中， 只有最后一步将数据拷贝到网卡缓存中是需要的。
 
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204Hash\347\264\242\345\274\225.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/Hash\347\264\242\345\274\225.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204Hash\347\264\242\345\274\225.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/Hash\347\264\242\345\274\225.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\230\345\202\250\345\274\225\346\223\216\344\270\216\351\200\202\347\224\250\345\234\272\346\231\257.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/InnoDB\345\255\230\345\202\250\345\274\225\346\223\216/MySQL\345\255\230\345\202\250\345\274\225\346\223\216\344\270\216\351\200\202\347\224\250\345\234\272\346\231\257.md"
similarity index 89%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\230\345\202\250\345\274\225\346\223\216\344\270\216\351\200\202\347\224\250\345\234\272\346\231\257.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/InnoDB\345\255\230\345\202\250\345\274\225\346\223\216/MySQL\345\255\230\345\202\250\345\274\225\346\223\216\344\270\216\351\200\202\347\224\250\345\234\272\346\231\257.md"
index 63aa67f843..246f7748ab 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\230\345\202\250\345\274\225\346\223\216\344\270\216\351\200\202\347\224\250\345\234\272\346\231\257.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/InnoDB\345\255\230\345\202\250\345\274\225\346\223\216/MySQL\345\255\230\345\202\250\345\274\225\346\223\216\344\270\216\351\200\202\347\224\250\345\234\272\346\231\257.md"
@@ -1,3 +1,7 @@
+> 全是干货的技术号：
+> 本文已收录在github，欢迎 star/fork：
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
 # 0 存储引擎的意义
 ## 为什么是可插拔式的？
 可插拔存储引擎体系结构使
@@ -47,13 +51,15 @@ MySQL在 ≥  5.1版本中不再支持。
 # 4 MEMORY（亦称HEAP）
 在内存中创建临时表来存储数据。
 
-出发点是速度 采用的逻辑存储介质是内存。
+初衷是速度，逻辑存储介质是内存。
+
 
+每个基于该引擎的表实际对应一个磁盘文件，文件名和表名相同，类型为.frm。
 磁盘文件只存储表结构，数据存储在内存，所以使用该种引擎的表拥有极高插入、更新和查询效率。
 
-默认使用哈希（Hash）索引，速度比使用B+Tree快，也可使用B+树索引。
+默认使用的是哈希（Hash）索引，速度比B+Tree快，也可使用B+树索引。
 
-由于这种存储引擎所存储的数据保存在内存中，无法持久化！所以其保存的数据具有不稳定性，比如如果mysqld进程发生异常会造成这些数据的消失，所以该存储引擎下的表的生命周期很短，一般只使用一次。
+由于这种存储引擎所存储的数据保存在内存中，所以无法持久化！其保存的数据具有不稳定性，如果mysqld进程发生异常，这些数据可能消失，所以该存储引擎下的表的生命周期很短，一般只使用一次。
 
 ## 适用场景
 如果需要该数据库中一个用于查询的临时表。
@@ -98,36 +104,37 @@ Merge允许将一组使用MyISAM存储引擎的并且表结构相同（即每张
 
 场景：MySQL中没有物化视图，视图的效率极低，故数据仓库中数据量较大的每天、每周或者每个月都创建一个单一的表的历史数据的集合可以通过Merge存储引擎合并为一张表。
 ## Federated
-该存储引擎可以 将不同的MySQL服务器联合起来，逻辑上组成一个完整的数据库。非常适合数据库分布式应用。
-
-可以使你在本地数据库中访问远程数据库中的数据，针对federated存储引擎表的查询会被发送到远程数据库的表上执行，本地不存储任何数据。
+该存储引擎可以不同的Mysql服务器联合起来，逻辑上组成一个完整的数据库。
+这种存储引擎非常适合数据库分布式应用。
+Federated存储引擎可以使你在本地数据库中访问远程数据库中的数据，针对federated存储引擎表的查询会被发送到远程数据库的表上执行，本地是不存储任何数据的。
 
 场景： dblink。
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNzkzZGU3MGVjZWJlYjZhMg?x-oss-process=image/format,png)
 
-### 缺点
-1. 对本地虚拟表的结构修改，并不会修改远程表的结构
+![image](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNzkzZGU3MGVjZWJlYjZhMg?x-oss-process=image/format,png)
+
+缺点：
 
-2. truncate 命令，会清除远程表数据
+1.对本地虚拟表的结构修改，并不会修改远程表的结构
+
+2.truncate 命令，会清除远程表数据
 
 3. drop命令只会删除虚拟表，并不会删除远程表
 
-4. 不支持 alter table 命令
+4.不支持 alter table 命令
+
+5. select count(*), select * from limit M, N 等语句执行效率非常低，数据量较大时存在很严重的问题，但是按主键或索引列查询，则很快，如以下查询就非常慢（假设 id 为主索引）
 
-5. select count(*), select * from limit M, N 等语句执行效率非常低，数据量较大时存在严重问题，但若按主键或索引列查询，则很快，如以下查询就非常慢（假设 id 为主索引）
-```sql
 select id from db.tablea where id >100 limit 10 ;
-```
+
 而以下查询就很快：
-```sql
+
 select id from db.tablea where id >100 and id<150
-```
 
 6.  如果虚拟虚拟表中字段未建立索引，而实体表中为此字段建立了索引，此种情况下，性能也相当差。但是当给虚拟表建立索引后，性能恢复正常。
 
-7. 类似 `where name like "str%" limit 1` 的查询，即使在 name 列上创建索引，也会导致查询过慢，是因为federated会将所有满足条件的记录读取到本地，再进行 limit 处理。
+7. 类似 where name like "str%" limit 1 的查询，即使在 name 列上创建了索引，也会导致查询过慢，是因为federated引擎会将所有满足条件的记录读取到本，再进行 limit 处理。
 
-## Cluster/NDB
+# Cluster/NDB
 该存储引擎用于多台数据机器联合提供服务以提高整体性能和安全性。适合数据量大、安全和性能要求高的场景。
 
 CAP理论。CAP理论(Brewer’s CAP Theorem) ，是说Consistency(一致性), Availability(可用性), Partition tolerance(分布) 三部分在系统实现只可同时满足二点，没法三者兼顾。如果对"一致性"要求高，且必需要做到"分区"，那么就要牺牲可用性;而对大型网站，可用性与分区容忍性优先级要高于数据一致性，一般会尽量朝着 A、P 的方向设计，然后通过其它手段保证对于一致性的商务需求。
@@ -138,14 +145,12 @@ MySQL5.5版本之前默认数据库引擎，由早期的ISAM所改良，提供IS
 因此，几年发展后，MySQL引入InnoDB，以强化参照完整性与并发违规处理机制，取代了MyISAM。
 
 每个MyISAM表，由存储在硬盘上的3个文件组成，每个文件都以表名称为文件主名，并搭配不同扩展名区分文件类型：
-- .frm
-每个基于该引擎的表实际对应一个磁盘文件，文件名和表名相同。保存表结构定义，此文件非MyISAM引擎的一部分
-- .MYD
-存放真正的数据
-- .MYI
-存储索引信息
+- .frm－－存储资料表定义，此文件非MyISAM引擎的一部分
+- .MYD－－存放真正的资料
+- .MYI－－存储索引信息。
+
+MyISAM使用表锁机制优化并发读写操作，但需要经常运行OPTIMIZE TABLE命令恢复被更新机制所浪费的空间，否则碎片也会随之增加，最终影响数据访问性能。
 
-MyISAM使用表锁优化并发读写操作，但需经常运行**OPTIMIZE TABLE**命令恢复被更新机制所浪费的空间，否则碎片也会随之增加，最终影响数据访问性能。
 
 MyISAM强调快速读取操作，主要用于高并发select，这也是MySQL深受Web开发喜爱原因：Web场景下大量操作都是读数据，所以大多数虚拟主机提供商和Internet平台提供商（Internet Presence Provider，IPP）只允许MyISAM格式。
 
@@ -254,12 +259,17 @@ InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型，这两
 10\. MyISAM不支持外键，需通过其他方式弥补。
 
 #### 根据引擎特性的优化
+
 如何对InnoDB引擎的表做最优的优化：
 
 1.使用自增列(INT/BIGINT类型)做主键，这时候写入顺序是自增的，和B+数叶子节点分裂顺序一致，这时候存取效率是最高的
 
 2.该表不指定自增列做主键，同时也没有可以被选为主键的唯一索引(上面的条件)，这时候InnoDB会选择内置的ROWID作为主键，写入顺序和ROWID增长顺序一致。
 
-> 参考
-> - https://zh.wikipedia.org/wiki/MyISAM 
-> - https://zhuanlan.zhihu.com/p/19965157
\ No newline at end of file
+
+
+参考
+- https://zh.wikipedia.org/wiki/MyISAM 
+- https://zhuanlan.zhihu.com/p/19965157
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/InnoDB\345\255\230\345\202\250\345\274\225\346\223\216/MySQL\347\232\204B+Tree\347\264\242\345\274\225\345\256\236\347\216\260\345\216\237\347\220\206.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/InnoDB\345\255\230\345\202\250\345\274\225\346\223\216/MySQL\347\232\204B+Tree\347\264\242\345\274\225\345\256\236\347\216\260\345\216\237\347\220\206.md"
new file mode 100644
index 0000000000..c839378296
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/InnoDB\345\255\230\345\202\250\345\274\225\346\223\216/MySQL\347\232\204B+Tree\347\264\242\345\274\225\345\256\236\347\216\260\345\216\237\347\220\206.md"
@@ -0,0 +1,286 @@
+> 全是干货的技术号：
+> 本文已收录在github，欢迎 star/fork：
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
+# 1 数据结构及算法基础
+## 1.1 索引的本质
+官方定义：索引（Index）是帮助MySQL高效获取数据的数据结构
+本质：索引是数据结构
+
+查询是数据库的最主要功能之一。我们都希望查询速度能尽可能快，因此数据库系统的设计者会从查询算法角度优化
+
+最基本的查询算法当然是[顺序查找](http://en.wikipedia.org/wiki/Linear_search)（linear search），这种复杂度为O(n)的算法在数据量很大时显然是糟糕的
+好在CS的发展提供了很多更优秀的查找算法，如[二分查找](http://en.wikipedia.org/wiki/Binary_search_algorithm)（binary search）、[二叉树查找](http://en.wikipedia.org/wiki/Binary_search_tree)（binary tree search）等
+稍微分析一下会发现，每种查找算法都只能应用于特定数据结构，如二分查找要求被检索数据有序，而二叉树查找只能应用于[二叉查找树](http://en.wikipedia.org/wiki/Binary_search_tree)，但`数据本身的组织结构不可能完全满足各种数据结构`（例如，理论上不可能同时将两列都按顺序进行组织）
+所以，在数据之外，数据库系统还维护着`满足特定查找算法的数据结构`，这些数据结构以某种方式引用（指向）数据，这样就可以`在这些数据结构上实现高级查找算法`
+这种ADT，就是索引
+![图1  一个例子](http://upload-images.jianshu.io/upload_images/4685968-c65be07333dbfb27.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+图1展示了一种可能的索引方式
+左边是数据表，两列14条记录，最左边是数据记录的物理地址（注意逻辑上相邻的记录在磁盘上并不一定物理相邻）
+为加快`Col2`的查找，可维护一个右边所示二叉查找树，每个节点分别包含索引键值及一个指向对应数据记录物理地址的指针，这样就可以运用二叉查找在O(log2 N)内取到相应数据
+
+虽然这是一个货真价实的索引，但实际数据库系统几乎没有使用二叉查找树或其进化品种[红黑树](http://en.wikipedia.org/wiki/Red-black_tree)（red-black tree）实现
+## 1.2 经典常用索引类型:B Tree和B+Tree
+目前大部分数据库系统及文件系统都采用B Tree或其变种B+Tree作为索引结构
+### 1.2.1 B Tree
+定义数据记录为一个二元组[key, data]
+- key为记录的键值，对于不同数据记录，key互不相同
+- data为数据记录除key外的数据
+
+B Tree有如下特点:
+- d为大于1的一个正整数，称为B-Tree的度
+- h为一个正整数，称为B-Tree的高度
+- 每个非叶节点由n-1个key和n个指针组成，其中d<=n<=2d
+- 每个叶节点最少包含一个key和两个指针，最多包含2d-1个key和2d个指针，叶节点的指针均为null
+- 所有叶节点具有相同的深度，等于树高h
+- key和指针互相间隔，节点两端是指针
+- 一个节点中的key从左到右非递减排列
+- 所有节点组成树结构
+- 每个指针要么为null，要么指向另外一个节点
+- 如果某个指针在节点node最左边且不为null，则其指向节点的所有key小于>v(key1),v(key1)为node的第一个key的值
+- 如果某个指针在节点node最右边且不为null，则其指向节点的所有key大于v(keym),v(keym)为node的最后一个key的值。
+- 如果某个指针在节点node的左右相邻key分别是keyi,keyi+1且不为null，则其指向节点的所有key小于v(keyi+1)且大于v(keyi)
+![图2 d=2的B-Tree示意图](http://upload-images.jianshu.io/upload_images/4685968-34e495925cfc43c9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+由于B Tree的特性,按key检索数据的算法非常直观
+- 首先从根节点二分查找
+- 如果找到则返回对应节点的data
+- 否则对相应区间的指针指向的节点递归进行查找
+- 直到找到目标节点/null指针，查找成功/失败
+```java
+bTreeSearch(node, key) {
+    if(node == null) return null;
+    foreach(node.key) {
+        if(node.key[i] == key) return node.data[i];
+            if(node.key[i] > key) return bTreeSearch(point[i]->node);
+    }
+    return bTreeSearch(point[i+1]->node);
+}
+data = bTreeSearch(root, my_key);
+```
+关于B-Tree有一系列有趣的性质，例如一个度为d的B-Tree，设其索引N个key，则其树高h的上限为![](http://upload-images.jianshu.io/upload_images/4685968-34aa3d92c8cdc09c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+检索一个key，其查找节点个数的渐进时间复杂度为![](http://upload-images.jianshu.io/upload_images/4685968-701af563134cfe20.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+从这点可以看出，B Tree是一个非常有效率的索引数据结构
+### 1.2.2 B+Tree
+B-Tree有许多变种，其中最常见的是B+Tree，MySQL普遍用其实现索引
+与B Tree相比，B+Tree有以下不同点
+- 每个节点的指针上限为2d
+- 内节点只存key
+- 叶节点不存指针,叶节点指向被索引的数据而不是其他叶节点
+    - innodb中,指向的是主键
+    - myshaym中指向的是数据的物理地址
+![图3 一个简单的B+Tree](http://upload-images.jianshu.io/upload_images/4685968-05ffda612519b2a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+由于并不是所有节点都具有相同的域，因此B+Tree中叶节点和内节点一般大小不同
+这点与B Tree不同，虽然B Tree中不同节点存放的key和指针可能数量不一致，但是每个节点的域和上限是一致的，所以在实现中B Tree往往对每个节点申请同等大小的空间
+
+一般来说，B+Tree比B Tree更适合实现外存储索引结构
+####1.2.2.1  带有顺序访问指针的B+Tree
+在经典B+Tree的基础上进行了优化，增加了顺序访问指针
+![图4](http://upload-images.jianshu.io/upload_images/4685968-dfd900ea7ac5deee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+如图4所示，在B+Tree的每个叶节点增加一个指向相邻叶节点指针，形成带有顺序访问指针的B+Tree
+此优化的目的是提高区间访问的性能，例如图4中如果要查询key为从18到49的所有数据记录，当找到18后，只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点，极大提高了区间查询效率
+##1.3 为什么使用B Tree（B+Tree）
+红黑树也可用来实现索引，但是文件系统及数据库系统普遍采用B/+Tree,何也?
+
+一般来说，索引本身也很大，不可能全存内存，往往以索引文件的形式存在磁盘
+
+索引查找过程中就要产生磁盘I/O消耗，相对于内存存取，I/O存取的消耗要高几个数量级，所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。
+
+B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生.B+树只需要去遍历叶子节点就可以实现整棵树的遍历.而且在数据库中基于范围的查询是非常频繁的，而B树不支持这样的操作（或者说效率太低）.　　
+
+换句话说，索引的结构组织要尽量减少查找过程中磁盘I/O的存取次数
+##1.4 主存存取原理
+计算机使用的主存基本都是随机读写存储器（RAM），抽象出一个十分简单的存取模型来说明RAM的工作原理
+![图5  4x4的主存模型
+](http://upload-images.jianshu.io/upload_images/4685968-c01748789e4b937d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+从抽象角度看，主存是一系列的存储单元组成的矩阵，每个存储单元存储固定大小的数据
+每个存储单元有唯一的地址，现代主存的编址规则比较复杂，这里将其简化成一个二维地址：通过一个行地址和一个列地址可以唯一定位到一个存储单元
+- 存取过程
+当系统需要读取主存时，将地址信号通过地址总线传给主存，主存读到地址信号后，解析信号并定位到指定存储单元，然后将此存储单元数据放到数据总线，供其它部件读取
+
+- 写主存
+过程类似，系统将要写入单元地址和数据分别放在地址总线和数据总线上，主存读取两个总线的内容，做相应的写操作
+
+这里可以看出，主存存取的时间仅与存取次数呈线性关系，因为不存在机械操作，两次存取的数据的“距离”不会对时间有任何影响，例如，先取A0再取A1和先取A0再取D3的时间消耗是一样的
+## 1.5  磁盘存取原理
+索引一般以文件形式存储在磁盘上，索引检索需要磁盘I/O
+与主存不同，磁盘I/O存在机械消耗，因此磁盘I/O时间消耗巨大
+![图6 磁盘的整体结构示意图](http://upload-images.jianshu.io/upload_images/4685968-7f653ab7b3304e21.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+磁盘由大小相同且同轴的圆形盘片组成，磁盘可以转动（各磁盘必须同步转动）
+在磁盘的一侧有磁头支架，磁头支架固定了一组磁头，每个磁头负责存取一个磁盘的内容。磁头不能转动，但是可以沿磁盘半径方向运动（实际是斜切向运动），每个磁头同一时刻也必须是同轴的，即从正上方向下看，所有磁头任何时候都是重叠的（不过目前已经有多磁头独立技术，可不受此限制）
+![图7 磁盘结构的示意图](http://upload-images.jianshu.io/upload_images/4685968-33af042b686050a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+盘片被划分成一系列同心环，圆心是盘片中心，每个同心环叫做一个磁道，所有半径相同的磁道组成一个柱面。磁道被沿半径线划分成一个个小的段，每个段叫做一个扇区，每个扇区是磁盘的最小存储单元。为了简单起见，我们下面假设磁盘只有一个盘片和一个磁头。
+
+当需要从磁盘读取数据时，系统会将数据逻辑地址传给磁盘，磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址，即确定要读的数据在哪个磁道，哪个扇区
+为了读取这个扇区的数据，需要将磁头放到这个扇区上方，为了实现这一点，磁头需要移动对准相应磁道，这个过程叫做寻道，所耗费时间叫做寻道时间，然后磁盘旋转将目标扇区旋转到磁头下，这个过程耗费的时间叫做旋转时间
+## 1.6  局部性原理与磁盘预读
+由于存储介质特性，磁盘本身存取就比主存慢，再加上机械运动耗费，磁盘的存取速度往往是主存的几百万分之一，因此为了提高效率，要尽量减少磁盘I/O。
+为了达到这个目的，磁盘往往不是严格按需读取，而是每次都会预读，即使只需要一个字节，磁盘也会从这个位置开始，顺序向后读取一定长度的数据放入内存。这样做的理论依据是计算机科学中著名的局部性原理：
+`当一个数据被用到时，其附近的数据也通常会马上被使用`
+`程序运行期间所需要的数据通常比较集中`
+由于磁盘顺序读取的效率很高（不需要寻道时间，只需很少的旋转时间），因此对于具有局部性的程序来说，预读可以提高I/O效率
+
+`预读的长度一般为页（page）的整数倍`
+页是存储器的逻辑块，操作系统往往将主存和磁盘存储区分割为连续的大小相等的块，每个存储块称为一页（在许多操作系统中，页大小通常为4k），主存和磁盘以页为单位交换数据
+当程序要读取的数据不在主存中时，会触发一个缺页异常，此时系统会向磁盘发出读盘信号，磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中，然后异常返回，程序继续运行
+## 1.7  B/+Tree索引的性能分析
+一般使用磁盘I/O次数评价索引结构的优劣
+
+### B Tree分析
+检索一次最多需要访问h个节点
+数据库系统的设计者巧妙利用了磁盘预读原理，将一个节点的大小设为等于一个页，这样每个节点只需要一次I/O就可以完全载入
+为了达到这个目的，在实际实现B-Tree还需要使用如下技巧：
+-  每次新建节点时，直接申请一个页的空间，这样就保证一个节点物理上也存储在一个页里，加之计算机存储分配都是按页对齐的，就实现了一个node只需一次I/O
+- B-Tree中一次检索最多需要h-1次I/O（根节点是常驻内存的），渐进复杂度为O(h)=O(logdN)。
+一般实际应用中，出度d是非常大的数字，通常超过100，因此h非常小（通常不超过3）
+
+综上所述，用B-Tree作为索引结构效率是非常高的
+
+### 红黑树
+h明显要深的多。由于逻辑上很近的节点（父子）物理上可能很远，无法利用局部性，所以红黑树的I/O渐进复杂度也为O(h)，效率明显比B-Tree差很多
+
+B+Tree更适合外存索引，原因和内节点出度d有关
+从上面分析可以看到，`d越大索引的性能越好`
+`出度的上限取决于节点内key和data的大小`：
+```
+dmax=floor(pagesize/(keysize+datasize+pointsize))
+```
+floor表示向下取整。由于B+Tree内节点去掉了data域，因此可以拥有更大的出度，更好的性能
+# 2. MySQL索引实现
+索引属于存储引擎级别的概念，不同存储引擎对索引的实现方式是不同的，主要讨论MyISAM和InnoDB两个存储引擎的索引实现方式
+##2.1 MyISAM索引实现
+使用B+Tree作为索引结构，叶节点data域存放数据记录的地址
+![图8 MyISAM索引的原理图](http://upload-images.jianshu.io/upload_images/4685968-3c528e050ba99593.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+设Col1为主键，则图8是一个MyISAM表的主索引（Primary key）示例
+可以看出MyISAM的索引文件仅仅保存数据记录的地址
+
+在MyISAM中，主/辅索引在结构上没有任何区别，只是主索引要求`key唯一`，而辅索引`key可重复`
+
+如果我们在Col2上建立一个辅索引
+![图9 Col2上建立的辅索引](http://upload-images.jianshu.io/upload_images/4685968-ca234341ecc259d0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+同样也是一颗B+Tree，data域保存数据记录的地址。
+因此，MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引，如果指定的Key存在，则取出其data域的值，然后以data域的值为地址，读取相应数据记录。
+
+MyISAM的索引方式也叫做“非聚集”的，之所以这么称呼是为了与InnoDB的聚集索引区分
+##2.2 InnoDB索引实现
+虽然InnoDB也使用B+Tree作为索引结构，但具体实现方式却与MyISAM截然不同
+
+第一个重大区别是
+- `InnoDB的数据文件本身就是索引文件`
+  - MyISAM索引文件和数据文件是分离的，索引文件仅保存数据记录的地址
+  - 而在InnoDB中，表数据文件本身就是按B+Tree组织的一个索引结构，这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键，因此InnoDB表数据文件本身就是主索引
+![图10 InnoDB主索引（同时也是数据文件）示意图](http://upload-images.jianshu.io/upload_images/4685968-36a10ea5ce6fdea4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+可以看到叶节点包含了完整的数据记录。这种索引叫做`聚集索引`
+因为InnoDB的数据文件本身要按主键聚集，所以InnoDB要求表必须有主键（MyISAM可以没有），如果没有显式指定，则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键，如果不存在这种列，则MySQL自动为InnoDB表生成一个隐含字段作为主键，这个字段长度为6个字节，类型为长整形
+
+第二个与MyISAM索引的不同是
+- InnoDB的辅索引data域存储相应记录主键的值而不是地址。换句话说，InnoDB的所有辅助索引都引用主键作为data域
+![图11 定义在Col3上的一个辅索引](http://upload-images.jianshu.io/upload_images/4685968-9e8eb904029b3bce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+这里以英文字符的ASCII码作为比较准则
+聚集索引这种实现方式使得按主键的搜索十分高效，但是辅助索引搜索需要检索两遍索引：
+  -   首先检索辅助索引获得主键
+  - 然后用主键到主索引中检索获得记录
+
+知道了InnoDB的索引实现后，就很容易明白为什么不建议使用过长的字段作为主键，因为所有辅索引都引用主索引，过长的主索引会令辅索引变得过大
+再如，用非单调的字段作为主键在InnoDB中不是个好主意，因为InnoDB数据文件本身是一颗B+Tree，非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整，十分低效，而使用自增字段作为主键则是一个很好的选择
+
+## 聚簇索引
+聚簇索引并不是一种单独的索引类型，而是一种`数据存储方式`。
+
+具体的细节依赖其实现方式，但InnoDB 的聚簇索引在同一结构中`保存了B-Tree索引和数据行`，是对磁盘上实际数据重新组织以按指定的一个或多个列的值排序的算法。
+- 特点
+存储数据的顺序和索引顺序一致。
+
+一般情况下主键会默认创建聚簇索引，且`一张表只允许存在一个聚簇索引`。
+
+当表有聚簇索引时，它的数据实际上存放在索引的叶子页（leaf page），`‘聚簇’`表示数据行和相邻的键值进错的存储在一起。`因为无法同时把数据行存放在两个不同地方，所以在一个表中只能有一个聚簇索引` （不过，覆盖索引可以模拟多个聚簇索引的情况）。
+
+InnoDb将通过主键聚集数据。
+
+如果没有定义主键，InnoDB 会选择一个唯一的非空索引代替
+如果没有这样的索引，InnoDB 会隐式定义一个主键来作为聚簇索引
+InnoDB值聚集在同一个页面中的记录,包含相邻键值的页面可能会相距很远
+
+## InnoDB 和 MyISAM的数据分布对比
+聚簇索引和非聚簇索引的数据分布有区别，以及对应的主键索引和二级索引的数据分布也有区别
+来看看InnoDB和MyISAM是如何存储下面的这个表的
+```
+CREATE TABLE layout_test(
+
+　　　　col1 int not null,
+
+　　　　col2 int not null,
+
+ 　　　  primary key (col1),
+
+　　　　key(col2)
+
+　　);
+```
+假设该表的主键取值为1-1w，按照随机顺序插入，并使用`OPTIMIZE TABLE`命令优化
+换句话说，数据在磁盘的存储方式已经最优，但进行的顺序是随机的
+列col2的值时从1-100之间随机赋值，所以有很多重复的值
+
+### MyISAM 的数据分布
+MyIsam按照数据插入的顺序存储在磁盘上
+
+实际上，MyISAM 中主键索引和其他索引在结构上没有什么不同
+主键索引就是一个名为PRIMARY的唯一非空索引
+
+### InnoDB 的数据分布
+因为InnoDB支持聚簇索引，索引使用非常不同的方式存储同样的数据。在InnoDB中，聚簇索引“是”表，所以不像myISAM那样需要独立的行存储
+
+### 聚簇索引的一些重要优点：
+- 可以把相关的数据保存在一起
+例如，实现电子邮箱时，可以根据用户id来聚集数据这样只需要从磁盘读取少数的数据页就能获取某个用户的全部邮件。如果没有使用聚簇索引，则每封邮件都可能导致一次I/O
+- 数据访问更快
+聚簇索引将索引和数据保存在同一个B-Tree中，因此从聚簇索引中获取数据通常比非聚簇索引中快
+- 使用覆盖索引扫描的查询可以直接使用页节点中的主键值。
+
+ 
+
+　　聚簇索引的缺点：
+
+　　聚簇索引最大限度的提高了io密集型应用的性能，但如果数据全部存放在内存中，则访问的顺序就没那么重要了，聚簇索引也就没有什么优势了。
+
+　　插入速度严重依赖插入顺序。按照主键的顺序插入是加载数据到innodb表中速度最快的方式。但如果不是按照主键顺序加载数据，那么加载完成后最好使用OPTIMIZE TABLE 命令来重新组织一下表。
+
+　　更新聚簇索引的代价很高，因为会强制InooDB将每个更新的数据移动到新的位置。
+
+　　基于聚簇索引的表在插入行，或者主键被更新导致需要移动行的时候，可能面临’页分裂（page split）‘的问题。当行的主键值要求必须将这一行插入到某个已满的页中时。存储引擎，存储引擎会将该页分裂成两个页面来容纳该行，这就是一次页分裂操作。页分裂会导致表占用更多的存储空间。
+
+　　聚簇索引可能导致全表扫描变慢，尤其是行比较稀疏，或者由于页分裂导致数据存储不连续的时候。
+
+　　二级索引（非聚簇索引）可能比想象的要更大，因为在二级索引的子节点包含了最优一个几点可能让人有些疑惑，为什么二级索引需要两次索引查找？答案在于二级索引中保存的“行指针”的实质。要记住，二级索引叶子节点保存的不是只想物理位置的指针，而是行的主键值。
+
+　　这意味着通过二级索引进行查找行，存储引擎需要找到二级索引的子节点获得对应的主键值，然后根据这个值去聚簇索引总超找到对应的行。这里做了重复的工作：两次B-Tree查找，而不是一次。对于InnoDB，自适应哈希索引能够减少这样重复工作。
+
+在《数据库原理》一书中是这么解释聚簇索引和非聚簇索引的区别的：
+- 聚簇索引的叶子节点就是数据节点
+- 非聚簇索引的叶子节点仍然是索引节点，只不过有指向对应数据块的指针。
+
+因此，MYSQL中不同的数据存储引擎对聚簇索引的支持不同就很好解释了。
+下面，我们可以看一下MYISAM和INNODB两种引擎的索引结构。
+
+如原始数据为：
+![](https://upload-images.jianshu.io/upload_images/4685968-ece2b523748fda67.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+MyISAM引擎的数据存储方式,如图
+![](https://upload-images.jianshu.io/upload_images/4685968-6ad71a2daeace2bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+MYISAM是按列值与行号来组织索引的
+它的叶子节点中保存的实际上是指向存放数据的物理块的指针。
+MYISAM引擎的索引文件（.MYI）和数据文件(.MYD)是相互独立的。
+
+而InnoDB按聚簇索引的形式存储数据，所以它的数据布局有着很大的不同。它存储数据的结构大致如下：
+![](https://upload-images.jianshu.io/upload_images/4685968-15f63d367a077aa4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+注：聚簇索引中的每个叶子节点包含主键值、事务ID、回滚指针(rollback pointer用于事务和MVCC）和余下的列(如col2)。
+
+INNODB的二级索引与主键索引有很大的不同。InnoDB的二级索引的叶子包含主键值，而不是行指针(row pointers)，这减小了移动数据或者数据页面分裂时维护二级索引的开销，因为InnoDB不需要更新索引的行指针。其结构大致如下：
+![](https://upload-images.jianshu.io/upload_images/4685968-1d56a6bc5696d4cc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+INNODB和MYISAM的主键索引与二级索引的对比：
+![](https://upload-images.jianshu.io/upload_images/4685968-d234ca8591982647.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+InnoDB的的二级索引的叶子节点存放的是KEY字段加主键值。因此，通过二级索引查询首先查到是主键值，然后InnoDB再根据查到的主键值通过主键索引找到相应的数据块。而MyISAM的二级索引叶子节点存放的还是列值与行号的组合，叶子节点中保存的是数据的物理地址。所以可以看出MYISAM的主键索引和二级索引没有任何区别，主键索引仅仅只是一个叫做PRIMARY的唯一、非空的索引，且MYISAM引擎中可以不设主键。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL5.7\345\256\211\350\243\205.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/Mac-\344\270\213-MySQL5-7-22\347\232\204\345\256\211\350\243\205.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL5.7\345\256\211\350\243\205.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/Mac-\344\270\213-MySQL5-7-22\347\232\204\345\256\211\350\243\205.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL count()\345\207\275\346\225\260.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL count()\345\207\275\346\225\260.md"
deleted file mode 100644
index c446198286..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL count()\345\207\275\346\225\260.md"	
+++ /dev/null
@@ -1,73 +0,0 @@
-为统计记录数，由SELECT返回。假如有如下数据：
-- 所有记录
-![](https://img-blog.csdnimg.cn/img_convert/bdc833650ffa00c374d040b6a3645fab.png)
-- 统计行的总数
-![](https://img-blog.csdnimg.cn/img_convert/45f4593176cfd0f35a4214394690d6b7.png)
-- 计算 Zara 的记录数
-![](https://img-blog.csdnimg.cn/img_convert/e04db05f173e28beac8991188a9ecff8.png)
-
-count(1)、count(*) 都是检索表中所有记录行的数目，不论其是否包含null值。
-count(1)比count(*)效率高。
-
-count(字段)是检索表中的该字段的**非空**行数，不统计这个字段值为null的记录。
-
-任何情况下最优选择
-
-```sql
-SELECT COUNT(1) FROM tablename
-```
-尽量减少类似：
-```java
-SELECT COUNT(*) FROM tablename WHERE COL = 'value' 
-```
-杜绝：
-
-```java
-SELECT COUNT(COL) FROM tablename WHERE COL2 = 'value' 
-```
-
-如果表没有主键，那么count（1）比count（*）快
-如果有主键，那么count（主键，联合主键）比count（*）快
-如果表只有一个字段，count（*）最快
-
-count(1)跟count(主键)一样，只扫描主键。
-count(*)跟count(非主键)一样，扫描整个表
-明显前者更快一些。
-# 执行效果
-## count(1) V.S count(*)
-当表的数据量大些时，对表作分析之后，使用count(1)还要比使用count(*)用时多！
- 
-从执行计划来看，count(1)和count(*)的效果是一样的。 但是在表做过分析之后，count(1)会比count(*)的用时少些（1w以内数据量），不过差不了多少。 
- 
-如果count(1)是聚索引,id,那肯定是count(1)快。但是差的很小的。 
-因为count(*) 会自动优化指定到那一个字段。所以没必要去count(1)，用count(*)，sql会帮你完成优化的 因此：count(1)和count(*)基本没有差别！ 
- 
-## count(1) and count(字段)
-- count(1) 会统计表中的所有的记录数，包含字段为null 的记录
-- count(字段) 会统计该字段在表中出现的次数，忽略字段为null 的情况。即不统计字段为null 的记录。 
-
-## count(*) 和 count(1)和count(列名)区别  
-
-执行效果上：  
-count(*)包括了所有的列，相当于行数，在统计结果的时候，不会忽略列值为NULL  
-count(1)包括了忽略所有列，用1代表代码行，在统计结果的时候，不会忽略列值为NULL  
-count(列名)只包括列名那一列，在统计结果的时候，会忽略列值为空（这里的空不是只空字符串或者0，而是表示null）的计数，即某个字段值为NULL时，不统计。
-
-# 执行效率
-列名为主键，count(列名)会比count(1)快  
-列名不为主键，count(1)会比count(列名)快  
-如果表多个列并且没有主键，则 count（1） 的执行效率优于 count（*）  
-如果有主键，则 select count（主键）的执行效率是最优的  
-如果表只有一个字段，则 select count（*）最优。
-# 实例
-![](https://img-blog.csdnimg.cn/bb6fd75177d9428abfaa837ef5879604.png)
-![](https://img-blog.csdnimg.cn/fda1c0fe3b09444a80c138d1806bea59.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-```sql
-select name, count(name), count(1), count(*), count(age), count(distinct(age))
-from counttest
-group by name;
-```
-![](https://img-blog.csdnimg.cn/9f7da59b31ad4ae78c41efcbfaf303c7.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-MyISAM有表元数据的缓存，例如行，即COUNT(*)值，对于MyISAM表的COUNT(*)无需消耗太多资源，但对于Innodb，就没有这种元数据，CONUT（*）执行较慢。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL-count()\345\207\275\346\225\260\345\217\212\345\205\266\344\274\230\345\214\226.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL-count()\345\207\275\346\225\260\345\217\212\345\205\266\344\274\230\345\214\226.md"
new file mode 100644
index 0000000000..61357e058b
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL-count()\345\207\275\346\225\260\345\217\212\345\205\266\344\274\230\345\214\226.md"
@@ -0,0 +1,108 @@
+很简单，就是为了统计记录数
+由SELECT返回
+
+为了理解这个函数，让我们祭出 employee_tbl 表
+![所有记录](https://upload-images.jianshu.io/upload_images/4685968-c6f571a93a8d2480.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![统计行的总数](https://upload-images.jianshu.io/upload_images/4685968-1bcf595e704c1b10.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![计算 Zara 的记录数](https://upload-images.jianshu.io/upload_images/4685968-ab0b92ffe7be03ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+注意：由于 SQL 查询对大小写不敏感，所以在 WHERE 条件中，无论是写成 ZARA 还是 Zara，结果都是一样的
+
+
+# count(1),count(*),count(字段)区别
+## count(1)和count(*)
+### 作用
+都是检索表中所有记录行的数目，不论其是否包含null值
+### 区别
+count(1)比count(*)效率高
+
+二 . count(字段)与count(1)和count(*)的区别
+
+count(字段)的作用是检索表中的这个字段的非空行数，不统计这个字段值为null的记录
+
+
+- 任何情况下SELECT COUNT(1) FROM tablename是最优选择
+- 尽量减少SELECT COUNT(*) FROM tablename WHERE COL = ‘value’ 这种
+- 杜绝SELECT COUNT(COL) FROM tablename WHERE COL2 = ‘value’ 的出现
+
+- 如果表没有主键，那么count（1）比count（*）快
+- 如果有主键，那么count（主键，联合主键）比count（*）快
+- 如果表只有一个字段，count（*）最快
+
+count(1)跟count(主键)一样，只扫描主键。
+count(*)跟count(非主键)一样，扫描整个表
+明显前者更快一些。
+执行效果：
+
+
+1.  count(1) and count(*)
+
+当表的数据量大些时，对表作分析之后，使用count(1)还要比使用count(*)用时多了！ 
+从执行计划来看，count(1)和count(*)的效果是一样的。 但是在表做过分析之后，count(1)会比count(*)的用时少些（1w以内数据量），不过差不了多少。 
+ 
+如果count(1)是聚索引,id,那肯定是count(1)快。但是差的很小的。 
+因为count(*),自动会优化指定到那一个字段。所以没必要去count(1)，用count(*)，sql会帮你完成优化的 因此：count(1)和count(*)基本没有差别！ 
+ 
+2. count(1) and count(字段)
+两者的主要区别是
+（1） count(1) 会统计表中的所有的记录数，包含字段为null 的记录。
+（2） count(字段) 会统计该字段在表中出现的次数，忽略字段为null 的情况。即不统计字段为null 的记录。 
+
+count(*) 和 count(1)和count(列名)区别  
+
+执行效果上：  
+count(*)包括了所有的列，相当于行数，在统计结果的时候，不会忽略列值为NULL  
+count(1)包括了忽略所有列，用1代表代码行，在统计结果的时候，不会忽略列值为NULL  
+count(列名)只包括列名那一列，在统计结果的时候，会忽略列值为空（这里的空不是只空字符串或者0，而是表示null）的计数，即某个字段值为NULL时，不统计。
+
+执行效率上：  
+列名为主键，count(列名)会比count(1)快  
+列名不为主键，count(1)会比count(列名)快  
+如果表多个列并且没有主键，则 count（1） 的执行效率优于 count（*）  
+如果有主键，则 select count（主键）的执行效率是最优的  
+如果表只有一个字段，则 select count（*）最优。
+# 实例
+```
+mysql> create table counttest(name char(1), age char(2));
+Query OK, 0 rows affected (0.03 sec)
+
+mysql> insert into counttest values
+    -> ('a', '14'),('a', '15'), ('a', '15'), 
+    -> ('b', NULL), ('b', '16'), 
+    -> ('c', '17'),
+    -> ('d', null), 
+    ->('e', '');
+Query OK, 8 rows affected (0.01 sec)
+Records: 8  Duplicates: 0  Warnings: 0
+
+mysql> select * from counttest;
++------+------+
+| name | age  |
++------+------+
+| a    | 14   |
+| a    | 15   |
+| a    | 15   |
+| b    | NULL |
+| b    | 16   |
+| c    | 17   |
+| d    | NULL |
+| e    |      |
++------+------+
+8 rows in set (0.00 sec)
+
+mysql> select name, count(name), count(1), count(*), count(age), count(distinct(age))
+    -> from counttest
+    -> group by name;
++------+-------------+----------+----------+------------+----------------------+
+| name | count(name) | count(1) | count(*) | count(age) | count(distinct(age)) |
++------+-------------+----------+----------+------------+----------------------+
+| a    |           3 |        3 |        3 |          3 |                    2 |
+| b    |           2 |        2 |        2 |          1 |                    1 |
+| c    |           1 |        1 |        1 |          1 |                    1 |
+| d    |           1 |        1 |        1 |          0 |                    0 |
+| e    |           1 |        1 |        1 |          1 |                    1 |
++------+-------------+----------+----------+------------+----------------------+
+5 rows in set (0.00 sec)
+```
+
+MyISAM有表元数据的缓存，例如行，即COUNT(*)值，对于MyISAM表的COUNT(*)无需消耗太多资源，但对于Innodb，就没有这种元数据，CONUT（*）执行较慢。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\230\345\202\250\350\277\207\347\250\213\344\270\216\345\207\275\346\225\260.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL-\345\255\230\345\202\250\350\277\207\347\250\213\344\270\216\345\207\275\346\225\260.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\230\345\202\250\350\277\207\347\250\213\344\270\216\345\207\275\346\225\260.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL-\345\255\230\345\202\250\350\277\207\347\250\213\344\270\216\345\207\275\346\225\260.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL8.0\345\256\236\346\210\230(\344\272\214) - \346\225\260\346\215\256\345\272\223\350\256\276\350\256\241.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL8.0\345\256\236\346\210\230/MySQL8.0\345\256\236\346\210\230(\344\272\214) - \346\225\260\346\215\256\345\272\223\350\256\276\350\256\241.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL8.0\345\256\236\346\210\230(\344\272\214) - \346\225\260\346\215\256\345\272\223\350\256\276\350\256\241.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL8.0\345\256\236\346\210\230/MySQL8.0\345\256\236\346\210\230(\344\272\214) - \346\225\260\346\215\256\345\272\223\350\256\276\350\256\241.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\264\346\227\266\350\241\250.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\264\346\227\266\350\241\250\345\272\224\347\224\250.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\264\346\227\266\350\241\250.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\264\346\227\266\350\241\250\345\272\224\347\224\250.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\273\344\273\216\345\244\215\345\210\266\343\200\201\350\257\273\345\206\231\345\210\206\347\246\273\345\217\212\351\253\230\345\217\257\347\224\250.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\273\344\273\216\345\244\215\345\210\266\343\200\201\350\257\273\345\206\231\345\210\206\347\246\273\345\217\212\351\253\230\345\217\257\347\224\250.md"
deleted file mode 100644
index e389d05943..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\273\344\273\216\345\244\215\345\210\266\343\200\201\350\257\273\345\206\231\345\210\206\347\246\273\345\217\212\351\253\230\345\217\257\347\224\250.md"
+++ /dev/null
@@ -1,282 +0,0 @@
-# 1 单机 =》集群
-随着数据量的增大，读写并发的增加，系统可用性要求的提升，单机 MySQL 出现危机：
-- 容量问题，难以扩容，考虑数据库拆分、**分库分表**
-- 读写压力，QPS 过大，特别是分析类需求会影响到业务事务，考虑多机集群、**主从复制**
-- 高可用性不足，易宕机，考虑故障转移、MHA/MGR/Orchestrator
-- 高峰时数据库连接数经常超过上限
-
-> 一致性问题，考虑分布式事务，X/A 柔性事务
-
-读写分离的实现是基于主从复制架构：一主多从，只写主库，主库会自动将数据同步到从库。
-
-## 为什么要读写分离？
-高并发场景下MySQL的一种优化方案，依靠主从复制使得MySQL实现了数据复制为多份，增强了抵抗 高并发读请求的能力，提升了MySQL查询性能同时，也提升了数据的安全性。当某一个MySQL节点，无论是主库还是从库故障时，还有其他的节点中存储着全量数据，保证数据不会丢失。
-
-主库将变更写binlog日志，然后从库连接到主库后，从库有个I/O线程，将主库的binlog日志拷贝到本地，写入一个中继日志。
-接着从库中有一个SQL线程会从中继日志读取binlog，然后执行binlog日志中的内容。即在本地再次执行一遍SQL，确保跟主库的数据相同。
-
-# 2 MySQL主从复制
-##  2.1 发展史
-
-2000年，MySQL 3.23.15版本引入了复制
-2002年，MySQL 4.0.2版本分离 IO 和 SQL 线程，引入了 relay log
-2010年，MySQL 5.5版本引入半同步复制
-2016年，MySQL 在5.7.17中引入 InnoDB Group Replication
-
-
-## 2.2 核心
-- 主库写 binlog
-- 从库 relay log
-![](https://img-blog.csdnimg.cn/20210201232044541.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## binlog格式
-- ROW
-记录详细但日志量会比较大
-- Statement
-只是记录SQL，记录简单
-没有查询语句
-- Mixed
-
-```sql
-# 查看binlog
-mysqlbinlog -vv mysql-bin.000005
-```
-
-## 异步复制
-异步复制：经典的主从复制，Primary-Secondary Replication，2000年MySQL 3.23.15版本引入 Replication。
-
-传统的MySQL复制提供了一种简单的主从复制方案。有一个主（source）并且有一或多个从（replicas）。主数据库execute事务，将其commit，然后将它们稍后（异步）发送给从数据库，以re-executed（在基于语句的复制中）或apply（在基于行的复制中）。它是一个无共享系统，默认情况下所有服务器都具有数据的完整副本。
-
-- MySQL Asynchronous Replication
-![](https://img-blog.csdnimg.cn/20210202121510248.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20210202123049502.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-
-### 优点
-简单
-### 缺点
-- 网络或机器故障，会造成数据不一致
-
-
-
-
-SQL的每个增删改的会改变数据的操作，除了更新数据外，对这个增删改操作还会写入一个日志文件，记录这个操作的日志，即binlog。
-
-mysql 5.7新版本的并行复制，多个SQL线程，每个线程从relay日志里读一个库的
-日志，重放。
-从库同步主库数据的过程是**串行化**的，即主库上并行的操作，在从库上会串行执行。
-由于从库从主库拷贝日志以及串行执行SQL的特点，在高并发下就有延时，从库数据一定比主库慢，所以经常出现，刚写入主库的数据读不到，要过几十甚至几百ms才能读到。
-从库串行化过程：
-1. 读取binlog日志
-2. 写relay日志、应用日志变更到自己本地数据
-
-从库的I/O线程，读取主库的binlog日志时，老版本是单线程，5.6.x之后的新版本改为多线程读取。
-
-若主库宕机时，恰好数据还没同步到从库，则有些数据可能在从库上没有，可能就这么丢失了。
-
-所以MySQL实际上在这有两个机制
-
-半同步复制，它向协议添加了一个同步步骤。这意味着主数据库在提交时等待从数据库确认已接收到事务。只有这样，主数据库才会恢复提交操作。
-## 半同步复制
-2010 年引入Semisynchronous Replication，5.5 可用，解决主库数据丢失问题，保证 Source 和 Replica 的最终一致性。
-需要启用插件。
-![](https://img-blog.csdnimg.cn/20210202122803481.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210202123017251.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-1. 主库写入binlog日志后，会强制立即将数据同步到从库
-2. 从库将日志写入自己的relay log后，会返回ack给主库
-3. 主库接收到至少一个从库的ack后才会认为写操作完成
-
-> 上面的图片可看到经典的异步MySQL复制协议（及其半同步变量）的示意图。不同实例之间的箭头表示服务器之间交换的消息或服务器与客户端应用程序之间交换的消息。
-
-
-## 组复制
-2016年引入，5.7 开始，启用插件。
-![](https://img-blog.csdnimg.cn/20210202125837266.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-基于 Paxos 协议实现的组复制，保证数据一致性。
-
-
-组复制是一种可用于实施容错系统的技术。复制组是一组服务器，每个服务器都有自己的完整数据副本（无共享复制方案），并通过消息传递相互交互。通信层提供了一组保证，例如原子消息和总订单消息传递。这些功能非常强大，可以转化为非常有用的抽象，可以用来构建更高级的数据库复制解决方案。
-
-MySQL组复制建立在这些属性和抽象之上，并在所有复制协议中实现多源更新。一个复制组由多个服务器组成，该组中的每个服务器可以随时独立执行事务。但是，所有读写事务只有在组批准后才提交。换句话说，对于任何读写事务，组都需要决定是否提交，因此提交操作不是来自原始服务器的单方面决定。只读事务无需组内的任何协调即可立即提交。
-
-当读写事务准备好在原始服务器上提交时，服务器自动广播写值（已更改的行）和相应的写集（已更新的行的唯一标识符）。由于事务是通过原子广播发送的，因此该组中的所有服务器都将接收该事务，否则将不会。如果他们收到了，那么相对于之前发送的其他事务，他们都将以相同的顺序收到它。因此，所有服务器都以相同的顺序接收相同的交易集，并且为交易建立了全局总订单。
-
-但是，在不同服务器上同时执行的事务之间可能存在冲突。通过在称为认证的过程中检查并比较两个不同并发事务的写集，可以检测到此类冲突。在认证过程中，冲突检测是在行级别执行的：如果在不同服务器上执行的两个并发事务更新同一行，则存在冲突。冲突解决过程指出，已首先订购的事务在所有服务器上提交，而已订购第二的事务中止，因此在原始服务器上回滚并由组中的其他服务器丢弃。例如，如果t1和t2在不同的站点上同时执行，都更改了同一行，并且t2在t1之前排序，则t2赢得了冲突，并且t1被回滚。这实际上是一个分布式的首次提交胜出规则。请注意，如果两个事务之间的冲突经常发生，那么在同一个服务器上启动它们是一个好习惯，在那里，它们有机会在本地锁管理器上进行同步，而不必由于认证而回滚。
-
-对于应用和外部化已认证的交易，如果不破坏一致性和有效性，组复制允许服务器偏离交易的约定顺序。组复制是最终的一致性系统，这意味着一旦传入流量减慢或停止，所有组成员将具有相同的数据内容。当流量在流动时，可以按略有不同的顺序对事务进行外部化，或者对某些成员先进行外部化。例如，在多主要模式下，尽管尚未应用全局顺序中较早的远程事务，但是本地事务可能会在认证后立即被外部化。当证明过程确定交易之间没有冲突时，这是允许的。在单主模式下，在主服务器上，并发，无冲突的本地事务以与组复制所同意的全局顺序不同的顺序进行提交和外部化的可能性很小。在不接受来自客户端的写操作的辅助服务器上，事务始终按照约定的顺序进行提交和外部化。
-
-下图描述了MySQL组复制协议，通过将其与MySQL复制（甚至MySQL半同步复制）进行比较，您可以看到一些区别。请注意，为清楚起见，此图中缺少一些基本的共识和Paxos相关的消息。
-![](https://img-blog.csdnimg.cn/20210202123232152.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# 3 主从复制的缺点及解决方案
-## 3.1 主从延迟
-- 只能数据分片，把数据量做小
-
-### 主从同步适用场景
-推荐在读 >> 写，且读时对数据时效性要求不高时采用。所以可以考虑用MySQL的并行复制，但问题是那是库级别的并行，所以有时作用不是很大。
-
-### 主从延迟严重解决方案
-1. 分库 : 将一个主库拆分，每个主库的写并发就降低了，主从延迟即可忽略不计
-2. 打开MySQL支持的并行复制，多个库并行复制，若某个库的写入并发特别高，写并发达到了2000/s，并行复制还是没意义。二八法则，很多时候比如说，就是少数的几个订单表，写入了2000/s，其他几十个表10/s。
-从库开启多线程，并行读取relay log中不同库的日志，然后**并行重放不同库的日志**，这是**库级别的并行**。
-3. 重构代码 : 重构代码，插入数据后，直接更新，不查询
-4. 若确实存在必须先插入，立马要求查询，然后立马就反过来执行一些操作，对这个查询设置**直连主库**(不推荐，这会导致读写分离失去意义)
-
-## 3.2 应用侧需要配合读写分离框架
-
-### 读写分离
-借助于主从复制，我们现在有了多个 MySQL 服务器示例。
-如果借助这个新的集群，改进我们的业务系统数据处理能力？
-
-最简单的就是配置多个数据源，实现读写分离
-#### 动态切换数据源
-1. 基于 Spring/Spring Boot，配置多个数据源(例如2个，master 和 slave)
-2. 根据具体的 Service 方法是否会操作数据，注入不同的数据源，1.0版本
-
-优化：
-1.1：基于操作 AbstractRoutingDataSource 和自定义注解 readOnly 之类的，简化自动切换数据源
-1.2：支持配置多个从库
-1.3：支持多个从库的负载均衡
-![](https://img-blog.csdnimg.cn/2021020217201480.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-#### 框架
-“动态切换数据源”版问题：
-- 代码侵入性强
-- 降低侵入性会导致”写后立即读”不一致问题
-写时（还没同步到从库），立马读（从库），导致你 insert 数据后去查却查不到！
-
-改进方式，ShardingSphere-jdbc 的 Master-Slave 功能
-1）SQL 解析和事务管理，自动实现读写分离
-2）解决”写完读”不一致的问题
-只要一个事务中解析到有写，所有读都读主库，而无需我们业务代码处理。
-
-#### 数据库中间件
-“框架版本”的问题？
-- 对业务系统还是有侵入
-- 对已存在的旧系统改造不友好
-
-优化方案：MyCat/ShardingSphere-Proxy 的 Master-Slave 功能
-- 需要部署一个中间件，规则配置在中间件
-- 模拟一个 MySQL 服务器，对业务系统无侵入
-
-但是该方案需要单独部署中间件，需要运维成本和领导审批，所以一般开发人员使用框架方案。
-
-## 3.3  无法高可用
-### 3.3.1 为什么要高可用
-1、读写分离，提升读的处理能力
-2、故障转移，提供 failover 能力
-
-加上业务侧连接池的心跳重试，实现断线重连，业务不间断，降低RTO和RPO。
-
-高可用意味着，更少的不可服务时间。一般用SLA/SLO衡量。
-
-```bash
-1年 = 365天 = 8760小时
-99 = 8760 * 1% = 8760 * 0.01 = 87.6小时
-99.9 = 8760 * 0.1% = 8760 * 0.001 = 8.76小时
-99.99 = 8760 * 0.0001 = 0.876小时 = 0.876 * 60 = 52.6分钟
-99.999 = 8760 * 0.00001 = 0.0876小时 = 0.0876 * 60 = 5.26分钟
-```
-
-###  3.3.2 failover，故障转移，灾难恢复
-容灾：热备与冷备
-对于主从来说，就是主挂了，某一个从，变成主，整个集群来看，正常对外提供服务。
-常见的一些策略：
-- 多个实例不在一个主机/机架上
-- 跨机房和可用区部署
-- 两地三中心容灾高可用方案
-
-###  3.3.3 高可用方案
-####  3.3.3.1 主从手动切换
-如果主节点挂掉，将某个从改成主；重新配置其他从节点。修改应用数据源配置。
-缺点：
-1. 可能数据不一致
-2. 需要人工干预
-3. 代码和配置的侵入性
-
-#### 3.3.3.2 主从手动切换
-用 LVS+Keepalived 实现多个节点的探活+请求路由。
-配置 VIP 或 DNS 实现配置不变更。
-缺点：
-- 手工处理主从切换
-- 大量的配置和脚本定义
-
-只能算半自动。
-#### 3.3.3.2 MHA
-MHA，Master High Availability，目前在 MySQL 高可用方面是一个相对成熟的解决方案，它由日本 DeNA 公司的 youshimaton（现就职于 Facebook）开发，是一套优秀的作为 MySQL 高可用性环境下故障切换和主从提升的高可用软件。
-![](https://img-blog.csdnimg.cn/20210202210732135.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-基于 Perl 语言开发，一般能在30s内实现主从切换。
-切换时，直接通过 SSH 复制主节点的日志。
-
-缺点：
-- 需要配置 SSH 信息
-- 至少3台
-#### 3.3.3.2 MGR
-不借助外部力量，只使用 MySQL 本身。如果主节点挂掉，将自动选择某个从改成主；无需人工干预，基于组复制，保证数据一致性。
-![](https://img-blog.csdnimg.cn/20210202211228144.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-缺点：
-- 外部获得状态变更需要读取数据库
-- 外部需要使用 LVS/VIP 配置
-
-特点：
-- 高一致性
-基于分布式Paxos协议实现组复制，保证数据一致性
-- 高容错性
-自动检测机制，只要不是大多数节点都宕机就可继续工作，内置防脑裂保护机制
-- 高扩展性
-节点的增加与移除会自动更新组成员信息，新节点加入后，自动从其他节点同步增量数据，直到与其他节点数据一致
-- 高灵活性
-提供单主模式和多主模式，单主模式在主库宕机后能够自动选主，所有写入都在主节点进行，多主模式支持多节点写入
-
-适用场景：
-- 弹性复制
-需要非常流畅的复制基础架构的环境，其中服务器的数量必须动态地增长或缩减，而最少尽可能的痛苦。
-![](https://img-blog.csdnimg.cn/20210202212608960.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 高可用分片
-Sharding is a popular approach to achieve write scale-out. Users can use MySQL Group Replication to implement highly available shards. Each shard
-can map into a Replication Group.
-分片是实现写横向扩展的一种流行方法。用户可以使用MySQL组复制来实现高度可用的分片。每个分片可以映射到副本组。
-![](https://img-blog.csdnimg.cn/20210202212748415.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-#### 3.3.3.4 MySQL Cluster
-完整的数据库层高可用解决方案。
-**MySQL InnoDB Cluster**是一个高可用的框架，构成组件： 
-- MySQL Group Replication
-提供DB的扩展、自动故障转移
-- MySQL Router
-轻量级中间件，提供应用程序连接目标的故障转移。MySQL Router是一个轻量级的中间件，可以提供负载均衡和应用连接的故障转移。它是MySQL团队为MGR量身打造的，通过使用Router和Shell,用户可以利用MGR实现完整的数据库层的解决方案。如果您在使用MGR，请一定配合使用Router和Shell，可以理解为它们是为MGR而生的，会配合MySQl 的开发路线图发展的工具。
-- MySQL Shell
-新的MySQL客户端，多种接口模式。可以设置群组复制及Router。MySQL Shell是MySQL团队打造的一个统一的客户端， 它可以对MySQL执行数据操作和管理。它支持通过JavaScript，Python，SQL对关系型数据模式和文档型数据模式进行操作。使用它可以轻松配置管理InnoDB Cluster。
-![](https://img-blog.csdnimg.cn/20210202224102202.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-#### 3.3.3.5 Orchestrator
-如果主节点挂掉，将某个从改成主。
-一款MySQL高可用和复制拓扑管理工具，支持复制拓扑结构的调整，自动故障转移和手动主从切换等。后端数据库用MySQL或SQLite存储元数据，并提供Web界面展示MySQl 复制的拓扑关系及状态，通过Web可更改MySQL实例的复制关系和部分配置信息，同时也提供命令行和API接口，方便运维管理。
-
-特点:
-1. 自动发现MySQL的复 制拓扑，并且在web.上展示;
-2. 重构复制关系， 可以在web进行拖图来进行复制关系变更;
-3. 检测主异常，并可以自动或手动恢复，通过Hooks进行自定义脚本;
-4. 支持命令行和web界面管理复制。
-
-基于 Go 语言开发，实现了中间件本身的高可用。
-
-两种部署方式
-orchestrator/raft：
-1. 数据一致性由orchestrator的raft协议保证
-2. 数据库之间不通信
-orchestrator/[galera | xtradb cluster | innodb cluster]:
-1. 数据一致性由数据库集群保证
-2. 数据库结点之间通信
-
-如果不部署client
-1. 使用HTTP (/api/leader-check)查询并路由到主节点
-
-优势：
-能直接在 UI 界面
-拖拽改变主从关系
-![](https://img-blog.csdnimg.cn/20210202231819583.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-> 参考 
-> - https://dev.mysql.com/doc/refman/5.7/en/group-replication-primary-secondary-replication.html
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md"
new file mode 100644
index 0000000000..9125ffa1bf
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206.md"
@@ -0,0 +1,9 @@
+![](https://upload-images.jianshu.io/upload_images/4685968-b40eda7b5b5c49a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-7463f430351db603.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5d0342d163b314e8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-2e7df3be40419f33.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-3b640dbb3a5da633.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![](https://upload-images.jianshu.io/upload_images/4685968-ceb361c1b209317a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-88ac5820f7e3b9bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-b88c1fb19afc860f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\272\213\345\212\241\345\237\272\347\241\200.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\272\213\345\212\241\345\237\272\347\241\200.md"
deleted file mode 100644
index 0910e6a885..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\272\213\345\212\241\345\237\272\347\241\200.md"
+++ /dev/null
@@ -1,49 +0,0 @@
-# 1 什么是事务
-一种`可靠、一致的`方式,访问和操作数据库中数据的程序单元。
-
-# 2 ACID性质
-并非任意的对数据库的操作序列都是数据库事务。数据库事务拥有以下四个特性，习惯上被称之为ACID特性。
-
-## 原子性（Atomicity）
-一次事务包含在其中的对数据库的操作中的操作要么全部成功，要么全部失败。
-
-## 一致性（Consistency）
-跨表、跨行、跨事务，数据库始终保持一致状态。
-
-事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束
-## 隔离性（Isolation）
-可见性，保护事务不会互相干扰, 包含4种隔离级别多个事务并发执行时，一个事务的执行不应影响其他事务的执行
-## 持久性（Durability）
-事务提交成功后,不会丢数据。如电源故障, 系统崩溃。
-
-# 3 案例
-某人要在商店使用电子货币购买100元的东西，当中至少包括两个操作：
-- 该人账户减少100元
-- 商店账户增加100元
-
-支持事务的数据库管理系统（transactional DBMS）就是要确保以上两个操作（整个“事务”）都能完成，或一起取消；否则就会出现100元平白消失或出现的情况。
-
-但在现实情况下，失败的风险很高。在一个数据库事务的执行过程中，有可能会遇上事务操作失败、数据库系统／操作系统出错，甚至是存储介质出错等情况。这便需要DBMS对一个执行失败的事务执行恢复操作，将其数据库状态恢复到一致状态（数据的一致性得到保证的状态）。为了实现将数据库状态恢复到一致状态的功能，DBMS通常需要维护事务日志以追踪事务中所有影响数据库数据的操作。
-
-# 4 MySQL 事务操作的 SQL
-
-```sql
-BEGIN TRANSACTION;
-UPDATE t user SET amount = amount-100 WHERE username = 'Java';
-UPDATE t user SET amount = amount+ 100 WHERE username = 'Edge';
-COMMIT
--- ROLL BACK
-```
-那么问题来了，Java中如何实现操作呢？
-- JDBC事务管理流程
-![](https://img-blog.csdnimg.cn/20200315002539801.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-```java
-Connection conn = getConnection();
-conn.setAutoCommit(false);
-Statement stmt1 = conn. prepareStatement(updateUser1SQL); 
-stmt1.executeUpdate();
-Statement stmt2 = conn.prepareStatement(updateUser2SQL); 
-stmt2.executeUpdate();
-conn.commit(); // or conn.rollback (); 事务的提交/回滚
-```
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\210\206\345\214\272\350\241\250.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\210\206\345\214\272\350\241\250.md"
index 7cbdcfc6ab..1a73d258df 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\210\206\345\214\272\350\241\250.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\210\206\345\214\272\350\241\250.md"
@@ -1,11 +1,9 @@
-确认MySQL服务器是否支持分区表
+![](https://upload-images.jianshu.io/upload_images/4685968-cab954a703c038a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ![说明支持](https://upload-images.jianshu.io/upload_images/4685968-c570ae4d4cd2d917.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
-# 分区表的特点
-在逻辑上为一个表,在物理上存储在多个文件中
-- 红框为关键
-![](https://upload-images.jianshu.io/upload_images/4685968-0e3c424f1e55fe64.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
-- 左非分区,右分区表
-![](https://upload-images.jianshu.io/upload_images/4685968-1489520be07a8067.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#分区表的特点
+![](https://upload-images.jianshu.io/upload_images/4685968-482f42dea7d24e87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![红框为关键](https://upload-images.jianshu.io/upload_images/4685968-0e3c424f1e55fe64.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![左非分区,右分区表](https://upload-images.jianshu.io/upload_images/4685968-1489520be07a8067.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 #按hash分区
 内部                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
 
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\210\206\345\272\223\345\210\206\350\241\250.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\210\206\345\272\223\345\210\206\350\241\250.md"
index 556cb4a6c1..02eca0c459 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\210\206\345\272\223\345\210\206\350\241\250.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\210\206\345\272\223\345\210\206\350\241\250.md"
@@ -1,172 +1,20 @@
-# 1 何时才分库分表？
-业务飞速发展，数据规模急速膨胀，单机数据库已无法满足业务发展。
-
-传统的将数据集中存储至单一数据节点的解决方案，在容量、性能、可用性和运维成本这三方面难于满足海量数据场景。在单库单表数据量超过一定容量水位的情况下，索引树层级增加，磁盘 IO 也很可能出现压力，会导致很多问题。
-
-- 性能
-由于MySQL采用 B+树索引，数据量超过阈值时，索引深度的增加也将使得磁盘访问的 IO 次数增加，进而导致查询性能的下降；高并发访问请求也使得集中式数据库成为系统的最大瓶颈。
-
-- 可用性
-服务化的无状态型，能够达到较小成本的随意扩容，这必然导致系统的最终压力都落在数据库之上。而单一的数据节点，或者简单的主从架构，已经越来越难以承担。从运维成本方面考虑，当一个数据库实例中的数据达到阈值以上，数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。
-- 业务数据
-不同模块的数据，比如用户数据和用户关系数据，全都存储在一个主库中，一旦主库发生故障，所有的模块儿都会受到影响。
-
-> 主从复制只能减轻读压力，但无法解决容量问题。
-
-影响：
-- 无法执行 DDL，比如添加一列，或者增加索引，都会直接影响线上业务，导致长时间的数据库无响应。
-- 无法备份，与上面类似，备份会自动先 lock 数据库的所有表，然后导出数据，量大了就没法执行了
-- 影响性能与稳定性，系统越来越慢，随时可能会出现主库延迟高，主从延迟很高，且不可控，对业务系统有极大的破坏性影响。
-
-在 4 核 8G 的云服务器上对 MySQL5.7 做 Benchmark，大概可以支撑 500TPS 和 10000QPS，MySQL对于写入性能要弱于数据查询的能力，那么随着系统写入请求量的增长，数据库系统如何来处理更高的并发写请求呢？
-
-这些问题你可以归纳成，数据库的写入请求量大造成的性能和可用性方面的问题，要解决这些问题，你所采取的措施就是对数据进行分片，对数据进行分片，可很好分摊数据库的读写压力，也可突破单机的存储瓶颈，而常见的一种方式就是“分库分表”。
-
-如何使用正确的分库分表呢？很多人会在查询时不使用分区键或在查询时使用大量连表查询。用好分库分表没你想的那么容易。
-
-## 从读写分离到数据库拆分
-主从结构解决了高可用，读扩展，但单机容量不变，单机写性能无法解决。
-
-提升容量 =》分库分表，分布式，多个数据库，作为数据分片的集群提供服务。
-降低单个节点的写压力。提升整个系统的数据容量上限。
-
-## 分库分表的好处
-- 分库分表后，每个节点只保存部分的数据，这样可以有效地减少单个数据库节点和单个数据表中存储的数据量，在解决了数据存储瓶颈的同时也能有效的提升数据查询的性能
-- 数据被分配到多个数据库节点上，那么数据的写入请求也从请求单一主库变成了请求多个数据分片节点，在一定程度上也会提升并发写入的性能
-
-
-
-# 2 概念辨析
-分库和分表是两码事，可能光分库不分表，也可能光分表不分库。
-
-分库分表是跟着公司业务发展走，业务发展越好，用户越多，数据量越大，请求量越大，那单个数据库一定扛不住。因为单表数据量太大，会极大影响你的 SQL执行性能，到了后面你的SQL可能就跑的很慢。经验来看，单表到几百万时性能就会相对差点，就该分表。
-
-## 分表
-把一个表的数据放到多个表中，然后查询时，就查一个表。
-
-比如按用户id分表，将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内，比如每个表就固定在200万以内。
-
-## 分库
-单库一般达到2000并发，亟需扩容，合适的单库并发值推荐在1000/s。可将一个库的数据拆分到多个库，访问时就访问一个库。
-
-- 分库分表的由来
-![](https://img-blog.csdnimg.cn/20190712220954195.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-# 3 分库分表中间件
-## cobar
-阿里b2b团队开发，proxy层方案，但已停止维护。
-- [Github地址](https://github.com/alibaba/cobar/)
-![](https://img-blog.csdnimg.cn/20210203103643124.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-不支持读写分离、存储过程、跨库join和分页。
-
-## TDDL
-淘宝团队开发，client层方案
-- [Github地址](https://github.com/alibaba/tb_tddl)
-![](https://img-blog.csdnimg.cn/20190712202411687.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-不支持join、多表查询等语法，支持读写分离。
-使用的也不多，因为还依赖淘宝的diamond配置管理系统，而且已被阿里云商用，不再开源。
-
-##  atlas
--  [Github地址](https://github.com/Qihoo360/Atlas)
-![](https://img-blog.csdnimg.cn/20210203104038930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-360开源的，proxy层方案，但六年前就不维护了。
-
-## sharding-jdbc（shardingsphere）
--  [Github地址](https://github.com/apache/incubator-shardingsphere)
-![](https://img-blog.csdnimg.cn/20190712204304352.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210203104142767.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)最初由当当开源，client层方案。
-SQL语法支持较多，支持分库分表、读写分离、分布式id生成、柔性事务（最大努力送达型事务、TCC事务）。被大量公司使用，我司也在用。现在已经升级为Apache组织的项目。
-
-这种client层方案的优点：
-不用部署，运维成本低，无需代理层的二次转发请求，性能很高
-但遇到升级啥的需要各个系统都重新升级版本再发布，各个系统都需要耦合sharding-jdbc的依赖。
-
-## mycat
--  [Github地址](https://github.com/MyCATApache/Mycat-Server)
-![](https://img-blog.csdnimg.cn/20190712204603810.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-基于cobar改造，proxy层方案，支持的功能非常完善，社区活跃。但相比sharding jdbc年轻一些。
-
-proxy层方案的缺点：
-需要部署，自己及运维一套中间件，运维成本高，但是好处在于对于各个项目是透明的，如果遇到升级之类的都是自己中间件那里搞就行了。
-
-## 选型
-推荐使用sharding-jdbc和mycat：
-- 小型公司选用sharding-jdbc，client层方案轻便，而且维护成本低，不需要额外增派人手，而且中小型公司系统复杂度会低一些，项目也没那么多
-- 中大型公司最好还是选用mycat这类proxy层方案，因为可能大公司系统和项目非常多，团队很大，人员充足，那么最好是专门弄个人来研究和维护mycat，然后大量项目直接透明使用即可
-
-# 4 微服务架构理论：扩展立方体
-- X 轴：clone 整个系统无差别复制，集群
-针对全部数据，常见的比如数据库复制，即主从结构，备份和高可用
-- Y 轴：解耦不同功能复制，业务拆分
-针对业务分类数据，比如垂直的分库分表，即分布式服务化、微服务架构
-- Z 轴：拆分不同数据扩展，数据分片
-针对任意数据，比如水平分库分表，即分布式架构、任意扩容
-![](https://img-blog.csdnimg.cn/20210203125927427.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-# 5 数据库拆分
-- 数据库如何拆分
-![](https://img-blog.csdnimg.cn/20190712221229332.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## 5.1 水平拆分
-把一个表的数据给弄到多个库的多个表里，但每个库的表结构都一样，只不过每个库中表放的数据不同，所有库表的数据加起来就是全部数据。关注点在数据的特点。
-
-### 水平拆分的意义
-- 将数据均匀放更多的库，然后用多个库抗更高并发
-- 多个库的存储进行扩容
-
-## 5.2 垂直拆分（拆库）
-### 解决问题
-- 服务不能复用
-- 连接数不够
-
-将一个数据库，拆分成多个提供不同业务数据处理能力的数据库，关注点在于业务相关性。
-
-例如拆分所有订单的数据和产品的数据，变成两个独立的库，数据结构发生了变化，SQL 和关联关系也必随之改变。
-原来一个复杂 SQL 直接把一批订单和相关的产品都查了出来，现在得改写 SQL 和程序。
-- 先查询订单库数据，拿到这批订单对应的所有产品 id
-- 再根据产品 id list去产品库查询所有产品信息
-- 最后再业务代码里进行组装把一个有很多字段的表给拆分成多个表或库
-
-每个库表的结构都不一样，每个库表都包含部分字段。
-
-一般将较少的访问频率很高的字段放到一个表，然后将较多的访问频率很低的字段放到另外一个表。
-因为数据库有缓存，访问频率高的行字段越少，可在缓存里缓存更多行，性能就越好。这个一般在表这个层面做的较多。
-
-> 现有中间件都可实现分库分表后，根据你指定的某个字段值，比如userid，自动路由到对应库，然后再自动路由到对应表。
-
-# 8  分库分表的方式
-## 按range分
-就是每个库一段连续的数据，一般按比如时间范围来的，但是这种一般较少用，因为很容易产生热点问题，大量的流量都打在最新的数据上了
-
-- 好处
-后面扩容的时候，就很容易，因为你只要预备好，给每个月都准备一个库就可以了，到了一个新的月份的时候，自然而然，就会写新的库了
-
-- 缺点
-但是大部分的请求，都是访问最新的数据。实际生产用range，要看场景，你的用户不是仅仅访问最新的数据，而是均匀的访问现在的数据以及历史的数据
-## 按某字段hash
-均匀分散，最为常用。
-- 好处
-可以平均分配没给库的数据量和请求压力
-
-- 坏处
-扩容起来比较麻烦，会有一个数据迁移的过程
-
 # 1 Sharding
 把数据库横向扩展到多个物理节点的一种有效方式，主要是为了突破数据库单机服务器的 I/O 瓶颈，解决数据库扩展问题。
 
 Sharding可简单定义为将大数据库分布到多个物理节点上的一个分区方案。每一个分区包含数据库的某一部分，称为一个shard，分区方式可以是任意的，并不局限于传统的水平分区和垂直分区。
 一个shard可以包含多个表的内容甚至可以包含多个数据库实例中的内容。每个shard被放置在一个数据库服务器上。一个数据库服务器可以处理一个或多个shard的数据。系统中需要有服务器进行查询路由转发，负责将查询转发到包含该查询所访问数据的shard或shards节点上去执行。
 
-# 垂直切分/水平切分
-## MySQL的扩展方案
+# 2 垂直切分/水平切分
+## 2.1 MySQL的扩展方案
 - Scale Out 水平扩展
 一般对数据中心应用，添加更多机器时，应用仍可很好利用这些资源提升自己的效率从而达到很好的扩展性
 - Scale Up 垂直扩展
 一般对单台机器，Scale Up指当某个计算节点添加更多的CPU Cores，存储设备，使用更大的内存时，应用可以很充分的利用这些资源来提升自己的效率从而达到很好的扩展性
 
-## MySQL的Sharding策略
+## 2.2 MySQL的Sharding策略
 1. 垂直切分：按功能模块拆分，以解决`表与表之间`的I/O竞争
 e.g. 将原来的老订单库，切分为基础订单库和订单流程库。数据库之间的`表结构不同`
-![](https://img-blog.csdnimg.cn/20200829110221177.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20200829110221177.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 
 2. 水平切分：将`同个表`的数据分块，保存至不同的数据库
 以解决单表中数据量增长压力。这些数据库中的`表结构完全相同`
@@ -226,65 +74,43 @@ MySQL的一张表分成多表后，每个小表都是完整的一张表，都对
 
 ### 4.1.1 查询切分
 - 将key和库的映射关系单独记录在一个数据库。
-![](https://img-blog.csdnimg.cn/20200829114917514.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20200829114917514.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 - 优点
 key和库的映射算法可以随便自定义
 - 缺点
 引入了额外的单点
 
 ### 4.1.2 范围切分
-按某字段的区间拆分，比较常用的是时间字段。
-
-在内容表里有“创建时间”的字段，而我们也是按时间来查看一个人发布的内容。我们可能会要看昨天的内容，也可能会看一个月前发布的内容，这时即可按创建时间的区间来分库分表。比如可以把一个月的数据放入一张表，查询时即可根据创建时间先定位数据存储在哪个表，再按查询条件查询。
-
 - 按照时间区间或ID区间切分。
-![](https://img-blog.csdnimg.cn/20200829115657202.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-#### 适用场景
-一般列表数据可使用这种拆分方式。
-比如一个人某时间段的订单、发布的内容。但这种方式可能存在明显热点，因为肯定会更关注最近用户买了啥，发了啥，所以查询的 QPS 会更多，对性能有一定影响。
-使用这种拆分规则后，数据表要提前建立好，否则如果时间到了次年元旦，DBA却忘记了建表，那么次年的数据就没有库表可写了。
-
+![](https://img-blog.csdnimg.cn/20200829115657202.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 - 优点
 单表容量可控，水平扩展很方便。
 - 缺点
 无法解决集中写入的瓶颈问题。
 
-### 4.1.3 Hash切分
-#### 适用场景
-按照某字段的哈希值做拆分，适用于实体表，比如用户表，内容表，一般按这些实体表的 ID 字段拆分。
-比如把用户表拆分成 16 个库，64 张表，可先对用户 ID 做哈希将 ID 尽量打散，然后再对 16 取余，这样就得到了分库后的索引值；对 64 取余，就得到了分表后的索引值。
-
+### 4.1.3 Hash切分（重点）
 - 一般都是采用hash切分。
-![](https://img-blog.csdnimg.cn/20200829112801122.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20200829112801122.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 
-数据水平切分后我们希望易于水平扩展，所以推荐采用mod 2^n这种一致性Hash。
+数据水平切分后我们希望是一劳永逸或者是易于水平扩展的，所以推荐采用mod 2^n这种一致性Hash。
 
 比如一个订单库，分库分表方案是32*32，即通过UserId后四位mod 32分到32个库中，同时再将UserId后四位Div 32 Mod 32将每个库分为32个表，共计分为1024张表。
 > 线上部署情况为8个集群(主从)，每个集群4个库。
-![](https://img-blog.csdnimg.cn/20200829112716345.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20200829112716345.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 
 为什么说这易于水平扩展？分析如下场景：
 #### 数据库性能达到瓶颈
 1. 现有规则不变，可直接扩展到32个数据库集群。
 ![](https://img-blog.csdnimg.cn/20200829120226464.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 2. 如果32个集群也无法满足需求，那么将分库分表规则调整为(32*2^n)*(32⁄2^n)，可以达到最多1024个集群。
-![](https://img-blog.csdnimg.cn/20200829120300693.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20200829120300693.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 #### 单表容量达到瓶颈
 或1024都无法满足。
-![](https://img-blog.csdnimg.cn/20200829120344287.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20200829120344287.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 假如单表都突破200G，200*1024=200T
 没关系，32 * (32 * 2^n)，这时分库规则不变，单库里的表再裂变，当然，在目前订单这种规则下（用userId后四位 mod）还是有极限的，因为只有四位，所以最多拆8192个表。
 
-# 选择分片键
-分库分表的问题就是引入了分库分表键，即分片键，也就是我们对数据库做分库分表所依据的字段。
-无论是哈希拆分还是区间拆分，都要选取一个字段，这带来一个问题：之后所有查询都需要带上该字段，才能找到数据所在的库和表，否则就只能向所有的数据库和数据表发送查询命令。如果拆成了 16 个库和 64 张表，那么一次数据的查询会变成 16*64=1024 次查询，查询的性能肯定是极差的。
-
-## 解决方案
-比如在用户库中使用 ID 作为分区键，这时若需按昵称查询用户，可按昵称作为分区键再做次拆分，但这样会极大增加存储成本，若以后还需要按注册时间查询，怎么办呢，再做次拆分？
-所以最合适的是建立一个昵称和 ID 的映射表，在查询时先通过昵称查询到 ID，再通过 ID 查询完整数据，这个表也可是分库分表的，也需占用一定存储空间，但因为表中只有两个字段，所以相比重新做次拆分还是省不少空间。
-
-
+## 选择分片键
 - 尽量避免跨分区查询的发生(无法完全避免)
 - 尽量使各个分片中的数据平均
 ## 如何存储无需分片的表
@@ -299,7 +125,8 @@ key和库的映射算法可以随便自定义
 - 在一个节点中部署多个数据库,每个数据库包含一个切片
 
 # 5 分库分表后的难题
-##  5.1 全局唯一ID生成方案
+##  全局唯一ID生成方案
+方案很多，主流的如下：
 ### 数据库自增ID
 使用
 - `auto_increment_increment `
@@ -307,7 +134,7 @@ key和库的映射算法可以随便自定义
 
 系统变量让MySQL以期望的值和偏移量来增加`auto_increment`列的值。
 - 优点
-最简单，不依赖于某节点，较普遍采用，但需要非常仔细的配置服务器哦！
+最简单，不依赖于某节点，较普遍采用但需要非常仔细的配置服务器哦！
 - 缺点
 单点风险、单机性能瓶颈。不适用于一个节点包含多个分区表的场景。
 
@@ -318,23 +145,23 @@ key和库的映射算法可以随便自定义
 - 缺点
 需要单独的数据库集群。
 
-### Redis缓存
+### Redis等缓存NoSQL服务
 避免了MySQL性能低的问题。
 
-### Snowflake（雪花算法）
+###  Snowflake（雪花算法）
 - 优点
-高性能高可用、易拓展
+高性能高可用、易拓展。 
 - 缺点
-需要独立的集群以及ZK
-
+需要独立的集群以及ZK。
 ### 各种GUID、Random算法
 - 优点
-简单
+简单。
 - 缺点
-生成ID较长，且有重复几率
-### 业务字段
-为减少运营成本并减少额外风险，排除所有需要独立集群的方案，采用了带有业务属性的方案： `时间戳+用户标识码+随机数`
-#### 优点
+生成ID较长，且有重复几率。
+### 业务字段（美团的实践方案）
+为减少运营成本并减少额外风险，美团排除了所有需要独立集群的方案，采用了带有业务属性的方案： `时间戳+用户标识码+随机数`
+
+#### 优点：
 - 方便、成本低
 - 基本无重复的可能
 - 自带分库规则，这里的用户标识码即为`userID的后四位`，在查询场景，只需订单号即可匹配到相应库表而无需用户ID，只取四位是希望订单号尽可能短，评估后四位已足。
@@ -343,21 +170,19 @@ key和库的映射算法可以随便自定义
 #### 缺点
 - 长度稍长，性能要比int/bigint的稍差。
 
+
 ## 事务
 分库分表后，由于数据存到了不同库，数据库事务管理出现困难。如果依赖数据库本身的分布式事务管理功能去执行事务，将付出高昂的性能代价；如果由应用程序去协助控制，形成程序逻辑上的事务，又会造成编程方面的负担。
 - 解决方案
 比如美团，是将整个订单领域聚合体切分，维度一致，所以对聚合体的事务是支持的。
 
-## 数据库特性
-多表的 join 在单库时可通过一个 SQL 完成，但拆分到多个数据库后就无法跨库执行 SQL，好在  join 语法一般都被禁止使用，都是把两个表的数据取出后在业务代码里做筛选。
-分库分表后，难免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上，这时，表的关联操作将受到限制，我们无法join位于不同分库的表，也无法join分表粒度不同的表，结果原本一次询能够完成的业务，可能需要多次查询才能完成。
+## 跨库跨表的join问题
+分库分表之后，难免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上，这时，表的关联操作将受到限制，我们无法join位于不同分库的表，也无法join分表粒度不同的表，结果原本一次询能够完成的业务，可能需要多次查询才能完成。
 - 解决方案
 垂直切分后，就跟join说拜拜了；水平切分后，查询的条件一定要在切分的维度内。
 比如查询具体某个用户下的订单等；
 禁止不带切分的维度的查询，即使中间件可以支持这种查询，可以在内存中组装，但是这种需求往往不应该在在线库查询，或者可以通过其他方法转换到切分的维度来实现。
 
-在未分库分表前，查询数据总数时只需 SQL 执行 count()，现在数据被分散到多个库表，就要考虑其他方案，比方说将计数的数据单独存储在一张表或记录在 Redis。
-
 ## 额外的数据管理和运算压力
 额外的数据管理负担，最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题，这些都可以通过应用程序解决，但必然引起额外的逻辑运算。
 例如，对于一个记录用户成绩的用户数据表userTable，业务要求查出成绩最好的100位，在进行分表前，只需一个order by即可。但分表后，将需要n个order by语句，分别查出每一个分表前100名用户数据，然后再对这些数据进行合并计算，才能得出结果。
@@ -368,4 +193,5 @@ key和库的映射算法可以随便自定义
 
 参考
 - https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html
-- 《Java工程师面试突击第1季》
\ No newline at end of file
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\227\347\254\246\344\270\262\347\264\242\345\274\225.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/MySQL\346\200\216\344\271\210\347\273\231\345\255\227\347\254\246\344\270\262\345\212\240\347\264\242\345\274\225\357\274\237.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\227\347\254\246\344\270\262\347\264\242\345\274\225.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/MySQL\346\200\216\344\271\210\347\273\231\345\255\227\347\254\246\344\270\262\345\212\240\347\264\242\345\274\225\357\274\237.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/MySQL\346\231\256\351\200\232\347\264\242\345\274\225\345\222\214\345\224\257\344\270\200\347\264\242\345\274\225\345\214\272\345\210\253.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/MySQL\346\231\256\351\200\232\347\264\242\345\274\225\345\222\214\345\224\257\344\270\200\347\264\242\345\274\225\345\214\272\345\210\253.md"
new file mode 100644
index 0000000000..24b12eb385
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/MySQL\346\231\256\351\200\232\347\264\242\345\274\225\345\222\214\345\224\257\344\270\200\347\264\242\345\274\225\345\214\272\345\210\253.md"
@@ -0,0 +1,209 @@
+# 1 概念区分
+- 普通索引和唯一索引
+普通索引可重复，唯一索引和主键一样不能重复。
+唯一索引可作为数据的一个合法验证手段，例如学生表的身份证号码字段，我们人为规定该字段不得重复，那么就使用唯一索引。（一般设置学号字段为主键）
+- 主键和唯一索引
+主键保证数据库里面的每一行都是唯一的，比如身份证，学号等，在表中要求唯一，不重复。唯一索引的作用跟主键的作用一样。
+不同的是，在一张表里面只能有一个主键，主键不能为空，唯一索引可以有多个，唯一索引可以有一条记录为空，即保证跟别人不一样就行。
+比如学生表，在学校里面一般用学号做主键，身份证则弄成唯一索引；而到了教育局，他们就把身份证号弄成主键，学号换成了唯一索引。
+选谁做表的主键，要看实际应用，主键不能为空。
+# 2 身份证案例
+某居民系统，每人有唯一身份证号。如果系统要按身份证号查姓名，会执行如下SQL
+```sql
+select name from CUser where id_card = 'ooxx';
+```
+
+然后你肯定会在`id_card`字段建索引。但`id_card`字段较大，不推荐将其做主键。于是现有俩选择：
+1. 给`id_card`字段创建唯一索引
+2. 创建一个普通索引
+
+假定业务代码已保证不会写入重复身份证号，这两选择逻辑上都正确。但考虑性能，选哪个呢？
+
+再看如下案例：假设字段 k 上的值都不重复。
+- InnoDB的索引组织结构：
+![](https://img-blog.csdnimg.cn/20200907125311641.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+接下来分析性能。
+
+#  3 查询性能分析
+
+```sql
+select id from T where k=4
+```
+
+通过B+树从树根开始层序遍历到叶节点，可认为数据页内部通过二分法搜索。
+- 普通索引
+查找到满足条件的第一个记录(4,400)后，需继续查找下个记录，直到碰到第一个不满足k=4的记录
+- 唯一索引
+由于具备唯一性，查找到第一个满足条件的记录后，就会停止检索
+
+看起来性能差距很微小，不过是多遍历了重复数据而已，继续往下看。
+
+- InnoDB数据按数据页单位读写
+即读一条记录时，并非将该一个记录直接从磁盘读出，而是以页为单位，将其整体从磁盘读入内存。
+
+因此普通索引，要多做一次“查找和判断下一条记录”的操作，即一次指针寻找和一次计算。
+如果k=4记录恰为该数据页的最后一个记录，那么要取下个记录，还得读取到下个数据页，操作就稍微复杂了。
+对整型字段，一个数据页可存近千key，因此这种情况概率其实也较低。因此计算平均性能差异时，可认为该操作对现在的CPU开销成本忽略不计。
+
+再分析更新性能前，首先需要掌握如下InnoDB小知识点哦~
+# 4 change buffer
+## 4.1 基本概念
+一种特殊的数据结构，当这些页不在缓冲池中时，这些高速缓存会将更改缓存到辅助索引页面。可能由INSERT，UPDATE或DELETE操作（DML）导致的缓冲更改将在以后通过其他的**读取操作**将页加载到缓冲池中时合并。
+![](https://img-blog.csdnimg.cn/20200907124820810.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+> 如上图可见，change buffer用的是buffer pool里的内存，所以不能无限增长。change buffer大小可通过参数innodb_change_buffer_max_size动态设置。
+> 比如设置为50：change buffer的大小最多只能占用buffer pool的50%。
+
+当需要更新一个数据页时，若该页
+- 在内存，直接更新
+- 不在内存，在不影响数据一致性下，InnoDB 会将这些更新操作缓存于change buffer，而无需从磁盘读入页。
+
+在下次查询访问该数据页时，才将数据页读入内存，然后执行change buffer中与这个页有关的操作。通过该方式即可保证该数据逻辑的正确性。
+
+> change buffer 实际上也是可持久化的数据，即它不仅在内存中有拷贝，也会被写进磁盘。
+
+## 4.2 merge
+将change buffer中的操作应用到原数据页，得到最新结果的过程。
+
+###  4.2.1 触发时机
+1. 访问该数据页
+2. 系统后台线程定期merge
+3. 数据库正常关闭（shutdown）的过程
+
+若能将更新操作先记录在change buffer，减少读盘，语句执行速度便会明显提升。且数据读入内存需占用buffer pool，因此也能降低内存占用，提高内存利用率。
+
+## 4.3 何时使用change buffer
+- 对于唯一索引，更新操作都要先判断该操作是否违反唯一性约束：
+比如，要插入(4,400)，要先判断表中是否已存在k=4记录，就必须要将数据页读入内存来判断。若都已读入内存，那直接更新内存自然很快，没必要使用change buffer。
+因此，唯一索引的更新不能使用change buffer，**只有普通索引可使用**。
+
+## 4.4 适用场景
+难道普通索引的所有场景，使用change buffer都可加速吗？
+
+注意merge才是真正进行数据更新时刻，change buffer主要是将记录的变更动作缓存。所以在一个数据页做merge前，change buffer记录变更越多（即该数据页上要更新的次数越多），收益越大。
+
+- 写多读少业务，页面在写完后马上被访问到的概率较小，change buffer使用效果最好。常见为账单、日志类系统。
+- 写后马上查询，将先新记录在change buffer，但之后由于立即访问该数据页，又很快触发merge，这样的话随机访问IO次数不会减少，反而增加change buffer维护代价，change buffer起了副作用。
+
+
+# 5 更新性能分析
+往表中插入一个新记录(4,400)，InnoDB会做什么呢？
+
+需要区分该记录要更新的目标页是否在内存：
+## 5.1 在内存
+- 唯一索引
+找到3和5之间位置，判断到没有冲突，插入值，语句执行结束。
+- 普通索引
+找到3和5之间位置，插入值，语句执行结束。
+
+普通索引和唯一索引对更新语句性能影响的差别，只是一个判断，耗费微小CPU时间。
+
+## 5.2 不在内存
+- 唯一索引
+需将数据页读入内存，判断到没有冲突，插入值，语句执行结束。
+
+- 普通索引
+将更新记录在change buffer，语句执行结束。
+
+将数据从磁盘读入内存涉及随机IO访问，是数据库里面成本最高操作之一。而change buffer减少随机磁盘访问，所以更新性能提升明显。
+
+
+
+# 6 实践中的索引选择
+普通索引和唯一索引究竟如何抉择？这两类索引在查询性能上没差别，主要考虑对更新性能影响。所以，推荐尽量选择普通索引。
+
+如果所有更新后面，都紧跟对该记录的查询，那么该关闭change buffer。
+而在其他情况下，change buffer都能提升更新性能。
+普通索引和change buffer的配合使用，对于数据量大的表的更新优化还是很明显的。
+
+在使用机械硬盘时，change buffer机制的收效非常显著。
+所以，当你有一个类似“历史数据”的库，并且出于成本考虑用机械硬盘时，应该关注这些表里的索引，尽量使用普通索引，把change buffer 开大，确保“历史数据”表的数据写速度。
+
+# 6 change buffer 和 redo log
+WAL 提升性能的核心机制，也是尽量减少随机读写，这两个概念易混淆。
+所以，这里我把它们放到了同一个流程里来说明区分。
+
+
+## 6.1 插入流程
+
+```sql
+insert into t(id,k) values(id1,k1),(id2,k2);
+```
+
+假设当前k索引树的状态，查找到位置后，k1所在数据页在内存(InnoDB buffer pool)，k2数据页不在内存。
+
+- 带change buffer的更新流程图，图中两个箭头都是后台操作，不影响更新响应。
+
+![](https://img-blog.csdnimg.cn/20200907161720661.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+该更新做了如下操作：
+1. Page1在内存，直接更新内存
+2. Page2不在内存，就在change buffer区，缓存下“往Page2插一行记录”的信息
+3. 将前两个动作记入redo log
+
+之后事务完成。执行该更新语句成本很低，只写两处内存，然后写一处磁盘（前两次操作合在一起写了一次磁盘），还是顺序写。
+
+## 6.2 怎么处理之后的读请求？
+```sql
+select * from t where k in (k1, k2);
+```
+
+读语句紧随更新语句，内存中的数据都还在，此时这俩读操作就与系统表空间和 redo log 无关。所以在图中就没画这俩。
+
+- 带change buffer的读过程
+![](https://img-blog.csdnimg.cn/20200907171043850.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+读Page1时，直接从内存返回。
+WAL之后如果读数据，是不是一定要读盘，是不是一定要从redo log里面把数据更新以后才可以返回？其实不用。
+看上图状态，虽然磁盘上还是之前数据，但这里直接从内存返回结果，结果正确。
+
+要读Page2时，需把Page2从磁盘读入内存，然后应用change buffer里面的操作日志，生成一个正确版本并返回结果。
+可见直到需读Page2时，该数据页才被读入内存。
+
+所以，要简单对比这俩机制对更新性能影响
+- redo log 主要节省随机写磁盘的IO消耗（转成顺序写）
+- change buffer主要节省随机读磁盘的IO消耗
+
+
+# 6 总结
+由于唯一索引用不了change buffer的优化机制，因此如果业务可以接受，从性能角度，推荐优先考虑非唯一索引。
+
+## 6.1 关于到底是否使用唯一索引
+主要纠结在“业务可能无法确保”。本文前提是“业务代码已经保证不会写入重复数据”下，讨论性能问题。
+- 如果业务不能保证，或者业务就是要求数据库来做约束，那么没得选，必须创建唯一索引。这种情况下，本文意义在于，如果碰上大量插入数据慢、内存命中率低时，多提供一个排查思路。
+- 然后，在一些“归档库”的场景，可考虑使用唯一索引的。比如，线上数据只需保留半年，然后历史数据保存在归档库。此时，归档数据已是确保没有唯一键冲突。要提高归档效率，可考虑把表的唯一索引改普通索引。
+
+## 6.2 如果某次写入使用change buffer，之后主机异常重启，是否会丢失change buffer的数据？
+不会丢失。
+虽然是只更新内存，但在事务提交时，我们把change buffer的操作也记录到redo log，所以崩溃恢复时，change buffer也能找回。
+
+## 6.3 merge的过程是否会把数据直接写回磁盘？
+### merge执行流程
+1. 从磁盘读入数据页到内存（老版本数据页）
+2. 从change buffer找出该数据页的change buffer 记录(可能有多个），依次应用，得到新版数据页
+3. 写redo log
+该redo log包含数据的变更和change buffer的变更
+
+至此merge过程结束。
+这时，数据页和内存中change buffer对应磁盘位置都尚未修改，是脏页，之后各自刷回自己物理数据，就是另外一过程。
+
+# 问题思考
+在构造第一个例子的过程，通过session A的配合，让session B删除数据后又重新插入一遍数据，然后就发现explain结果中，rows字段从10001变成37000多。
+而如果没有session A的配合，只是单独执行delete from t 、call idata()、explain这三句话，会看到rows字段其实还是10000左右。这是什么原因呢？
+
+如果没有复现，检查
+- 隔离级别是不是RR（Repeatable Read，可重复读）
+- 创建的表t是不是InnoDB引擎
+
+
+为什么经过这个操作序列，explain的结果就不对了？
+delete 语句删掉了所有的数据，然后再通过call idata()插入了10万行数据，看上去是覆盖了原来10万行。
+但是，session A开启了事务并没有提交，所以之前插入的10万行数据是不能删除的。这样，之前的数据每行数据都有两个版本，旧版本是delete之前数据，新版本是标记deleted的数据。
+这样，索引a上的数据其实有两份。
+
+然后你会说，不对啊，主键上的数据也不能删，那没有使用force index的语句，使用explain命令看到的扫描行数为什么还是100000左右？（潜台词，如果这个也翻倍，也许优化器还会认为选字段a作为索引更合适）
+是的，不过这个是主键，主键是直接按照表的行数来估计的。而表的行数，优化器直接用的是`show table status`的值。
+大家的机器如果IO能力比较差的话，做这个验证的时候，可以把`innodb_flush_log_at_trx_commit` 和 `sync_binlog` 都设置成0。
+
+参考
+- https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\264\242\345\274\225\346\234\200\345\267\246\345\214\271\351\205\215\345\216\237\345\210\231\345\217\212\344\274\230\345\214\226\345\216\237\347\220\206.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/MySQL\347\264\242\345\274\225\346\234\200\345\267\246\345\214\271\351\205\215\345\216\237\345\210\231\345\217\212\344\274\230\345\214\226\345\216\237\347\220\206.md"
similarity index 98%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\264\242\345\274\225\346\234\200\345\267\246\345\214\271\351\205\215\345\216\237\345\210\231\345\217\212\344\274\230\345\214\226\345\216\237\347\220\206.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/MySQL\347\264\242\345\274\225\346\234\200\345\267\246\345\214\271\351\205\215\345\216\237\345\210\231\345\217\212\344\274\230\345\214\226\345\216\237\347\220\206.md"
index 97620d79ec..e0a189676b 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\264\242\345\274\225\346\234\200\345\267\246\345\214\271\351\205\215\345\216\237\345\210\231\345\217\212\344\274\230\345\214\226\345\216\237\347\220\206.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/MySQL\347\264\242\345\274\225\346\234\200\345\267\246\345\214\271\351\205\215\345\216\237\345\210\231\345\217\212\344\274\230\345\214\226\345\216\237\347\220\206.md"
@@ -8,11 +8,11 @@
 
 
 - 创建表
-![](https://img-blog.csdnimg.cn/20200717010752915.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 插入数据![](https://img-blog.csdnimg.cn/20200717011210977.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20200717010752915.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+- 插入数据![](https://img-blog.csdnimg.cn/20200717011210977.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 
 - InnoDB索引组织结构
-![](https://img-blog.csdnimg.cn/20200717011304619.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20200717011304619.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 
 SQL查询语句的执行流程：
 1. 在k索引树找到k=3，取得 ID 300
@@ -71,7 +71,7 @@ CREATE TABLE `tuser` (
 B+树这种索引，可利用索引的“最左前缀”，来定位记录。
 
 为了直观地说明这个概念，用（name，age）联合索引分析。
-![](https://img-blog.csdnimg.cn/20200717013408249.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20200717013408249.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 
 索引项按照索引定义出现的字段顺序排序。
 
@@ -220,9 +220,9 @@ MySQL5.6前，只能从ID3开始个个回表，到主键索引上找数据行，
 
 这两个过程的执行流程图：
 - 无索引下推执行流程
-![](https://img-blog.csdnimg.cn/20200717015457694.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20200717015457694.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 - 索引下推执行流程
-![](https://img-blog.csdnimg.cn/20200717015536407.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20200717015536407.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 
 两个图里面，每一个虚线箭头表示回表一次。
 无索引下推执行流程，在(name,age)索引里特意去掉age的值，这过程InnoDB并不看age的值，只按顺序把“name第一个字是’张’”的记录一条条取出来回表，回表4次。
@@ -241,11 +241,12 @@ MySQL5.6前，只能从ID3开始个个回表，到主键索引上找数据行，
 - OR语句前后没有同时使用索引
 - 数据类型出现隐式转化（如varchar不加单引号，可能会自动转int型）
 
+
 ### 3.4.2 索引选择性与前缀索引
 ```sql
 CREATE INDEX index_ name ON table(col_ name(n));
 ```
-![](https://img-blog.csdnimg.cn/20200726144819969.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20200726144819969.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 
 - 索引的选择性
 不重复的索引值和表的记录数的比值
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\272\213\345\212\241\351\232\224\347\246\273\345\216\237\347\220\206\345\217\212undo log.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/\344\275\240\347\234\237\347\232\204\346\207\202MySQL\347\232\204\344\272\213\345\212\241\351\232\224\347\246\273\346\234\272\345\210\266\345\220\227\357\274\237.md"
similarity index 84%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\272\213\345\212\241\351\232\224\347\246\273\345\216\237\347\220\206\345\217\212undo log.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/\344\275\240\347\234\237\347\232\204\346\207\202MySQL\347\232\204\344\272\213\345\212\241\351\232\224\347\246\273\346\234\272\345\210\266\345\220\227\357\274\237.md"
index 1ad047e23d..094d9509cb 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\272\213\345\212\241\351\232\224\347\246\273\345\216\237\347\220\206\345\217\212undo log.md"	
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\216\237\347\220\206/\344\275\240\347\234\237\347\232\204\346\207\202MySQL\347\232\204\344\272\213\345\212\241\351\232\224\347\246\273\346\234\272\345\210\266\345\220\227\357\274\237.md"
@@ -1,3 +1,7 @@
+> 全是干货的技术号：
+> 本文已收录在github，欢迎 star/fork：
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
 默认使用MySQL 5.5后的InnoDB引擎。
 
 # 1 隔离性与隔离级别
@@ -78,52 +82,29 @@ V1、V2（事务在执行期间，即未提交前，看到的数据全程一致
 
 事务启动时的视图可认为是静态的，不受其他事务更新影响。
 
-# 4 实现事务隔离 - undo log
+#  4 事务隔离的实现 - undo log
 MySQL的每条记录在更新时都会同时记录一条回滚操作。记录上的最新值，通过回滚操作，都可得到前一个状态的值。
 
 ## 4.1 示例
-假设一个值从1被按顺改成2、3、4
+假设一个值从1被按顺序改成2、3、4
 
 - undo log中的记录：
 ![](https://img-blog.csdnimg.cn/20200911032936782.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-> 回滚段(rollback segment)
 
-当前值4，但在查询该记录时，在不同时刻启动事务有不同read-view。
+当前值4，但在查询这记录时，不同时刻启动事务有不同read-view。
 在视图A、B、C，该记录的值分别是1、2、4，同一记录在系统中可存在多版本，即多版本并发控制（MVCC）。
 
 对read-view A，要得到1，就必须将当前值依次执行图中所有的回滚操作得到。
-即使现在有另外一个事务正在将4改成5，这个事务跟read-view A、B、C对应的事务不会冲突。
+即使现在有另外一个事务正在将4改成5，这个事务跟read-view A、B、C对应的事务是不会冲突的。
 
-## 何时删除undo log
-不需要时才删除。即系统会自己判断，当没有事务再用到这些undo log，undo log就会被删除。
+## 何时删除undo log？
+不需要时才删除。
+即系统会自己判断，当没有事务再用到这些undo log，undo log就会被删除。
 
-## 何时不需要undo log
+## 何时不需要undo log？
 当系统里没有比该undo log更早的read-view时。
 
-## 意义
-保证事务的原子性。
-- 事务回滚, 一致性读、崩溃恢复
-- 记录事务回滚时所需的撤消操作
-- 一条 INSERT 语句，对应一条 DELETE 的 undo log
-- 每个 UPDATE 语句，对应一条相反 UPDATE 的 undo log
-
-## 保存位置
-- system tablespace (MySQL 5.7默认) 
-- undo tablespaces (MySQL 8.0默认)
-
-# 多版本并发控制（MVCC）
-使InnoDB支持一致性读
-- READ COMMITTED
-- REPEATABLE READ
-
-让查询不被阻塞、无需等待被其他事务持有的锁，这种技术手段可以增加并发度。
-
-InnoDB保留被修改行的旧版本。查询正在被其他事务更新的数据时，会读取更新之前的版本。每行数据都存在一个版本号，每次更新时都更新该版本。这种技术在数据库领域的使用并不普遍。某些数据库，以及某些MySQL存储引擎都不支持。
-
-聚簇索引的更新=替换更新
-二级索引的更新=删除+新建
-
-# 避免长事务
+# 5 避免长事务
 长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问db里的任何数据，所以该事务提交之前，db里它可能用到的回滚记录都必须保留，导致大量占存储。
 
 在MySQL 5.5及以前，undo log是跟数据字典一起放在ibdata文件，即使长事务最终提交，回滚段被清理，文件也不会变小。
@@ -131,8 +112,10 @@ InnoDB保留被修改行的旧版本。查询正在被其他事务更新的数
 除了对回滚段影响，长事务还占用锁资源，可能拖慢全库。
 
 # 6 事务启动方式
+开发同学并不是有意长事务，通常误用。其实MySQL的事务启动方式有以下几种：
+
 ## 6.1 显式启动事务
-begin 或 start transaction 开启事务：
+begin 或 start transaction。配套的
 - 提交语句 commit
 - 回滚语句 rollback
 
@@ -196,4 +179,6 @@ set autocommit=0
 > Percona的pt-kill这个工具不错，推荐。
 
 在业务功能测试阶段要求输出所有的general_log，分析日志行为提前发现问题。
-使用的MySQL 5.6或更新版本，把innodb_undo_tablespaces设置成2或更大值。如果真的出现大事务导致回滚段过大，这样设置后清理起来更方便。
\ No newline at end of file
+使用的MySQL 5.6或更新版本，把innodb_undo_tablespaces设置成2或更大值。如果真的出现大事务导致回滚段过大，这样设置后清理起来更方便。
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\227\347\254\246\351\233\206\351\200\211\346\213\251.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\227\347\254\246\351\233\206\344\275\240\350\277\230\345\234\250\344\275\277\347\224\250\351\224\231\350\257\257\347\232\204utf8\357\274\237.md"
similarity index 94%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\227\347\254\246\351\233\206\351\200\211\346\213\251.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\227\347\254\246\351\233\206\344\275\240\350\277\230\345\234\250\344\275\277\347\224\250\351\224\231\350\257\257\347\232\204utf8\357\274\237.md"
index dc7f1d8d57..cd1c1c6a1d 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\227\347\254\246\351\233\206\351\200\211\346\213\251.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\255\227\347\254\246\351\233\206\344\275\240\350\277\230\345\234\250\344\275\277\347\224\250\351\224\231\350\257\257\347\232\204utf8\357\274\237.md"
@@ -9,6 +9,10 @@ MySQL的“utf8”不是真正的UTF-8。“utf8”只支持每个字符最多
 - MySQL一直没有修复这个bug，他们在2010年发布了一个叫作“utf8mb4”的字符集，绕过这个问题。
 ![](https://img-blog.csdnimg.cn/20200124024614228.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
+
+
+
+
 几乎所有的网络应用都使用了Unicode字符集。Unicode字符集包含了上百万个字符。最简单的编码是UTF-32，每个字符使用32位。这样做最简单，因为一直以来，计算机将32位视为数字，而计算机最在行的就是处理数字。但问题是，这样太浪费空间了。
 
 UTF-8可以节省空间，在UTF-8中，字符“C”只需要8位，一些不常用的字符，比如“”需要32位。其他的字符可能使用16位或24位。一篇类似本文这样的文章，如果使用UTF-8编码，占用的空间只有UTF-32的四分之一左右。
@@ -17,7 +21,7 @@ MySQL从4.1版本开始支持UTF-8，也就是2003年，而今天使用的UTF-8
 旧版的UTF-8标准（RFC 2279）最多支持每个字符6个字节。2002年3月28日，MySQL开发者在第一个MySQL 4.1预览版中使用了RFC 2279。
 同年9月，他们对MySQL源代码进行了一次调整：“UTF8现在最多只支持3个字节的序列”。
 
-# utf8mb4字符集
+# utfmb4字符集
 支持BMP和补充字符。
 
 每个多字节字符最多需要四个字节。
@@ -44,6 +48,8 @@ WHERE utf8mb3_tbl.utf8mb3_col = utf8mb4_tbl.utf8mb4_col;
 2. 如果我们可以确定不需要存放多种语言，就没必要非得使用UTF8或者其他UNICODE字符类型，这回造成大量的存储空间浪费。
 3. MySQL的数据类型可以精确到字段，所以当我们需要大型数据库中存放多字节数据的时候，可以通过对不同表不同字段使用不同的数据类型来较大程度减小数据存储量，进而降低I0操作次数并提高缓存命中率。
 
+> 这里是引用
+
 # 总结
 如果你在使用MySQL或MariaDB，不要再用“utf8”编码，而用“utf8mb4”。
 
@@ -53,4 +59,8 @@ WHERE utf8mb3_tbl.utf8mb3_col = utf8mb4_tbl.utf8mb4_col;
 
 参考
 - https://medium.com/@adamhooper/in-mysql-never-use-utf8-use-utf8mb4-11761243e434
-- [MySQL 官方手册](https://dev.mysql.com/doc/refman/8.0/en/charset-unicode.html)
\ No newline at end of file
+- [MySQL 官方手册](https://dev.mysql.com/doc/refman/8.0/en/charset-unicode.html)
+
+
+
+![](https://img-blog.csdnimg.cn/20200729215420194.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\237\272\347\241\200.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\277\205\347\237\245\345\277\205\344\274\232.md"
similarity index 98%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\237\272\347\241\200.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\277\205\347\237\245\345\277\205\344\274\232.md"
index b0d9fcf804..3fc57d9608 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\237\272\347\241\200.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\277\205\347\237\245\345\277\205\344\274\232.md"
@@ -1,24 +1,21 @@
-几乎所有的业务项目都会涉及数据存储，虽然当前各种NoSQL和文件系统大行其道，但MySQL等关系型数据库因为满足ACID、可靠性高、对开发友好，仍最常被用于存储重要数据。
-
 # 更改表名
 ## Table 从 db_a 要搬到 db_b
  
-```sql
+```
 RENAME TABLE db_a.old_table TO db_b.new_table;
 ```
-
-## MySQL Table 改名字(重命名)
-```sql
+## MySQL Table 改名字(重新命名)
+```
 RENAME TABLE old_table TO new_table;
 ```
-
-## MySQL 两个 Table 互换名
-```sql
+## MySQL 兩個 Table 要互換名字
+```
 RENAME TABLE old_table TO tmp_table,
 new_table TO old_table,
 tmp_table TO new_table;
 ```
 
+
 # 分页
 - 方式1：
 `select * from table order by id limit m, n; `
@@ -254,4 +251,6 @@ mysql> SELECT  a.runoob_id, a.runoob_author, b.runoob_count  FROM  runoob_tbl  a
 
 以上实例中使用了 RIGHT JOIN，该语句会读取右边的数据表 tcount_tbl 的所有选取的字段数据，即便在左侧表 runoob_tbl 中没有对应的runoob_author 字段值。
 
-![image](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMmQ5OTBjYWJlMDVkZDVlNy5naWY)
\ No newline at end of file
+![image](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMmQ5OTBjYWJlMDVkZDVlNy5naWY)
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\346\231\256\351\200\232\347\264\242\345\274\225\345\222\214\345\224\257\344\270\200\347\264\242\345\274\225\345\214\272\345\210\253.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\346\231\256\351\200\232\347\264\242\345\274\225\345\222\214\345\224\257\344\270\200\347\264\242\345\274\225\345\214\272\345\210\253.md"
deleted file mode 100644
index 9d1d12c241..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\346\231\256\351\200\232\347\264\242\345\274\225\345\222\214\345\224\257\344\270\200\347\264\242\345\274\225\345\214\272\345\210\253.md"
+++ /dev/null
@@ -1,167 +0,0 @@
-# 1 概念区分
-## 普通索引 V.S 唯一索引
-普通索引可重复，唯一索引和主键一样不能重复。
-唯一索引可作为数据的一个合法验证手段，例如学生表的身份证号码字段，我们人为规定该字段不得重复，那么就使用唯一索引。（一般设置学号字段为主键）
-## 主键 V.S 唯一索引
-主键保证DB的每一行都是唯一、不重复，比如身份证，学号等，不重复。
-唯一索引的作用跟主键一样。
-不同的是，在一张表里面只能有一个主键，主键不能为空，但唯一索引可以有多个。唯一索引可以有一条记录为null。
-
-比如学生表：
-- 在学校，一般用学号做主键，身份证号作为唯一索引
-- 在教育局，就把身份证号弄成主键，学号作为唯一索引
-
-所以选谁做主键，取决于业务需求。
-# 2 案例
-某居民系统，每人有唯一身份证号。若系统要按身份证号查姓名：
-```sql
-select name from CUser where id_card = 'ooxx';
-```
-估计你会在`id_card`建索引。但`id_card`字段较大，不推荐做主键。于是现在有如下选择：
-1. 在`id_card`创建唯一索引
-2. 创建一个普通索引
-
-假定业务代码已经确保不会写入重复身份证号，这两个选择逻辑上都是正确的。
-性能优化角度考虑，选择唯一索引还是普通索引呢？
-
-假设字段 k 上的值都不重复。
-- InnoDB索引结构
-![](https://img-blog.csdnimg.cn/20200907125311641.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-接下来分析性能。
-
-#  3 查询性能
-```sql
-select id from T where k=4
-```
-通过B+树从root开始层序遍历到叶节点，数据页内部通过二分搜索：
-- 普通索引
-查找到满足条件的第一个记录(4,400)后，需查找下个记录，直到碰到第一个不满足`k=4`的记录
-- 唯一索引
-由于索引具备唯一性，查到第一个满足条件的，就会停止搜索
-
-看起来性能差距很小。
-
-InnoDB数据按数据页单位读写。即读一条记录时，并非将该一个记录从磁盘读出，而以页为单位，将其整体读入内存。
-
-所以普通索引，多了一次“查找和判断下一条记录”的操作，即一次指针寻找和一次计算。
-若`k=4`记录恰为该数据页的最后一个记录，则此时要取下个记录，还得读取下个数据页。
-对整型字段，一个数据页可存近千个key，因此这种情况概率其实也很低。因此计算平均性能差异时，可认为该操作成本对现在CPU开销忽略不计。
-
-# 4 更新性能
-往表中插入一个新记录(4,400)，InnoDB会有什么反应？
-
-这要看该记录要更新的目标页是否在内存：
-## 在内存
-- 普通索引
-找到3和5之间的位置，插入值，结束。
-- 唯一索引
-找到3和5之间的位置，`判断到没有冲突`，插入值，结束。只是一个判断的差别，耗费微小CPU时间。
-
-## 不在内存
-- 唯一索引
-将数据页读入内存，判断到没有冲突，插入值，结束。
-
-- 普通索引
-将更新记录在change buffer，结束。
-
-将数据从磁盘读入内存涉及随机I/O访问，是DB里成本最高的操作之一。而change buffer可以减少随机磁盘访问，所以更新性能提升明显。
-
-# 5 索引选择的最佳实践
-普通索引、唯一索引在查询性能上无差别，主要考虑更新性能。所以，推荐尽量选择普通索引。
-
-若所有更新后面，都紧跟对该记录的查询，那就该关闭change buffer。其它情况下，change buffer都能提升更新性能。
-普通索引和change buffer的配合使用，对于数据量大的表的更新优化还是明显的。
-
-在使用机械硬盘时，change buffer的收益也很大。
-
-所以，当你有个类似“历史数据”的库，并且出于成本考虑用机械硬盘，应该关注这些表里的索引，尽量使用普通索引，把change buffer 开大，确保“历史数据”表的数据写性能。
-
-# 6 change buffer 和 redo log
-WAL 提升性能的核心机制，也是尽量减少随机读写，它们有啥区别呢？
-## 插入流程
-```sql
-insert into t(id,k) values(id1,k1),(id2,k2);
-```
-假设当前k索引树的状态，查找到位置后：
-- k1所在数据页在内存(InnoDB buffer pool)
-- k2数据页不在内存
-
-看如下流程：
-### 带change buffer的更新流程
-
-> 图中箭头都是后台操作，不影响更新的响应。
-
-![](https://img-blog.csdnimg.cn/20200907161720661.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-该更新做了如下操作：
-1. Page1在内存，直接更新内存
-2. Page2不在内存，就在change buffer区，缓存一个“往Page2插一行记录”的信息
-3. 将前两个动作记入redo log
-
-之后事务完成。执行该更新语句成本很低，只写两处内存，然后写一处磁盘（前两次操作合在一起写了一次磁盘），还是顺序写。
-
-## 处理之后的读请求
-```sql
-select * from t where k in (k1, k2);
-```
-读语句紧随更新语句，内存中的数据都还在，所以此时这俩读操作就与系统表空间和 redo log 无关。
-
-### 带change buffer的读过程
-![](https://img-blog.csdnimg.cn/20200907171043850.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-读Page1时，直接从内存返回。
-- WAL之后如果读数据，是不是一定要读盘，是不是一定要从redo log把数据更新之后才可以返回？
-其实不用。看上图状态，虽然磁盘上还是之前数据，但这里直接从内存返回结果，结果正确。
-
-要读Page2时，需把Page2从磁盘读入内存，然后应用change buffer里的操作日志，生成一个正确版本并返回结果。可见直到需读Page2时，该数据页才被读入内存。
-
-综上，这俩机制的更新性能：
-- redo log 主要节省随机写磁盘的I/O消耗（转成顺序写）
-- change buffer主要节省随机读磁盘的I/O消耗
-
-#  7 总结
-由于唯一索引用不了change buffer，若业务可以接受，从性能角度，优先考虑非唯一索引。
-
-## 到底何时使用唯一索引
-问题在于“业务可能无法确保”。本文前提是“业务代码已经保证不会写入重复数据”，才讨论性能问题。
-- 如果业务不能保证或业务就是要求数据库来做约束
-没得选，必须创建唯一索引。那本文意义的在于，如果碰上大量插入数据慢、内存命中率低时，多提供了一个排查思路。
-- “归档库”场景，可考虑使用唯一索引
-比如，线上数据只需保留半年，然后历史数据保存在归档库。此时，归档数据已是确保没有唯一键冲突。要提高归档效率，可考虑把表的唯一索引改普通索引。
-
-## 若某次写入使用了change buffer，之后主机异常重启，是否会丢失change buffer数据
-不会丢失。
-虽然是只更新内存，但在事务提交时，change buffer的操作也被记录到了redo log。
-所以崩溃恢复时，change buffer也能找回。
-
-## merge时是否会把数据直接写回磁盘
-### merge流程
-1. 从磁盘读入数据页到内存（老版本数据页）
-2. 从change buffer找出该数据页的change buffer 记录(可能多个），依次应用，得到新版数据页
-3. 写redo log
-该redo log包含数据的变更和change buffer的变更
-
-至此merge结束。
-这时，数据页和内存中change buffer对应磁盘位置都尚未修改，是脏页，之后各自刷回自己物理数据，就是另外一过程。
-
-# 问题思考
-在构造第一个例子的过程，通过session A的配合，让session B删除数据后又重新插入一遍数据，然后就发现explain结果中，rows字段从10001变成37000多。
-而如果没有session A的配合，只是单独执行delete from t 、call idata()、explain这三句话，会看到rows字段其实还是10000左右。这是什么原因呢？
-
-如果没有复现，检查
-- 隔离级别是不是RR（Repeatable Read，可重复读）
-- 创建的表t是不是InnoDB引擎
-
-为什么经过这个操作序列，explain的结果就不对了？
-delete 语句删掉了所有的数据，然后再通过call idata()插入了10万行数据，看上去是覆盖了原来10万行。
-但session A开启了事务并没有提交，所以之前插入的10万行数据是不能删除的。这样，之前的数据每行数据都有两个版本，旧版本是delete之前数据，新版本是标记deleted的数据。
-这样，索引a上的数据其实有两份。
-
-不对啊，主键上的数据也不能删，那没有使用force index的语句，使用explain命令看到的扫描行数为什么还是100000左右？（潜台词，如果这个也翻倍，也许优化器还会认为选字段a作为索引更合适）
-是的，不过这个是主键，主键是直接按照表的行数来估计的。而表的行数，优化器直接用的是`show table status`的值。
-大家的机器如果IO能力比较差的话，做这个验证的时候，可以把`innodb_flush_log_at_trx_commit` 和 `sync_binlog` 都设成0。
-
-> 参考
-> - https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html
-> - https://time.geekbang.org/column/article/70848
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\346\237\245\350\257\242\344\274\230\345\214\226.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\346\237\245\350\257\242\344\274\230\345\214\226.md"
deleted file mode 100644
index 36a8b99f2a..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\346\237\245\350\257\242\344\274\230\345\214\226.md"
+++ /dev/null
@@ -1,340 +0,0 @@
-# 1 SQL优化
-## show status 
-了解各种SQL的执行效率：
-```sql
-# 查看本session的sql执行效率
-show status like 'Com_%';
-
-# 查看全局的统计结果
-SHOW GLOBAL STATUS LIKE  'Com_%'
-
-# 查看服务器的状态
-show global status;
-```
-结果：
-- Com_select:执行select操作的次数，一次查询只累加1
-![](https://img-blog.csdnimg.cn/20210602130612162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- Com_insert:执行insert操作的次数，对于批量插入的insert操作，只累加一次
-- Com_update
-执行update操作的次数
-- Com_delete
-执行delete的次数
-
-上面的参数是对所有存储引擎的表进行累计，下面参数是针对
-### InnoDB存储引擎
-- Innodb_rows_read
-![](https://img-blog.csdnimg.cn/2021061318083423.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-SELECT查询返回的行数
-- Innodb_rows_insered
-执行inser操作插入的行数
-- Innodb_rows_updated
-执行UPDATE操作更新的行数
-- Innodb_rows_deleted
-执行DELETE操作删除的行数
-
-通过上述参数可以了解当前DB应用是插入更新为主还是查询为主，以及各类的SQL执行比例。
-- 对于更新操作的计算，是对执行次数的计数，无论提交还是回滚都会进行累加对于事务形的应用，通过Com_commit和Com_rollback可以了解事务提交和回滚的情况，对于回滚操作非常频繁的数据库，可能意味着应用编写存在的问题
-
-*   Connections:试图连接MySql服务器的次数
-*   Uptime：服务器工作时间
-*   Slow_queries:慢查询的次数
-
-# 2 定位执行效率低的SQL语句
-1.  慢查询日志
-`--log-show-queries[=file_name]`选项去启动
-mysqlId写一个包含所有执行时间超过`long_querty_time`秒的sql语句的日志文件
-2.  show processlist
-慢查询日志在查询结束后才记录，所以在应用反应执行效率出现问题的时候查询慢查询日志并不能定位问题
-可以使用`show processlist`命令查看当前Mysql在进行的线程，包括线程的状态，是否锁表等，可以实时查看SQL的执行情况，同时对一些锁表进行优化。
-
-#   3 [explain 执行计划](https://javaedge.blog.csdn.net/article/details/108297734)
-
-# 4  简单的优化方案
-### 分析表
-本语句可以用于分析和存储表的关键字分布。分析结果可使得系统得到准确的统计信息。使得SQL能够生成正确的执行计划。
-
-若用户感觉实际执行计划并不预期的执行计划，执行一次分析表可能会解决问题
-```sql
-mysql> analyze table t;
-+-------------------+---------+----------+----------+
-| Table             | Op      | Msg_type | Msg_text |
-+-------------------+---------+----------+----------+
-| common_mistakes.t | analyze | status   | OK       |
-+-------------------+---------+----------+----------+
-1 row in set (0.01 sec)
-```
-### 检查表
-检查表的作用是检查一个表或多个表是否有错误，也可以检查视图是否错误
-```sql
-mysql> check table t;
-+-------------------+-------+----------+----------+
-| Table             | Op    | Msg_type | Msg_text |
-+-------------------+-------+----------+----------+
-| common_mistakes.t | check | status   | OK       |
-+-------------------+-------+----------+----------+
-1 row in set (0.01 sec)
-```
-
-### 优化表
-如果删除了表的一大部分，或者如果已经对可变长度的行表（含varchar、blob、text列）的表进行改动，则使用optimize 进行表优化，这个命令**可以使表中的空间碎片进行合并、并且可以消除由于删除或者更新造成的空间浪费**
-
-```sql
-mysql> optimize table t;
-+-------------------+----------+----------+-------------------------------------------------------------------+
-| Table             | Op       | Msg_type | Msg_text                                                          |
-+-------------------+----------+----------+-------------------------------------------------------------------+
-| common_mistakes.t | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
-| common_mistakes.t | optimize | status   | OK                                                                |
-+-------------------+----------+----------+-------------------------------------------------------------------+
-2 rows in set (0.04 sec)
-```
-
-对于innodb引擎的表，可以通过设置innodb_file_per_taable参数，设置InnoDb为独立表空间模式，这样每个数据库的每个表都会生成一个独立的idb文件，用于存储表的数据和索引，可以一定程度减少Innodb表的空间回收问题,另外，在删除大量数据后，Innodb表可以通过alter table但是不锈钢引擎方式来回收不用的空间
-
-```sql
-alter table payment enigine=innodb;
-```
-
-**ANALYZE,CHECK,OPTIMIZE,ALTER TABLE执行期间都是对表进行锁定，因此要在数据库不频繁的时候执行相关的操作**
-
-### 拆分表
-分区将数据在物理上分隔开，不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。
-这样，当对这个表进行查询时，只需要在表分区中进行扫描，而不必进行全表扫描，明显缩短了查询时间，
-另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O，一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。
-对数据量大的时时表可采取此方法。可按月自动建表分区。
-
-# 存储过程与触发器的区别
-两者唯一的区别是触发器不能用EXECUTE语句调用，而是在用户执行Transact-SQL语句时自动触发（激活）执行。
-触发器是在一个修改了指定表中的数据时执行的存储过程。
-通常通过创建触发器来强制实现不同表中的逻辑相关数据的引用完整性和一致性。
-触发器不同于存储过程，触发器主要是通过事件执行触发而被执行的，
-存储过程可以通过存储过程名称名字而直接调用。
-当对某一表进行诸如UPDATE、INSERT、DELETE这些操作时，SQLSERVER就会自动执行触发器所定义的SQL语句，从而确保对数据的处理必须符合这些SQL语句所定义的规则。
-
-# 数据库优化方向
-（1）、根据服务层面：配置mysql性能优化参数
-（2）、从系统层面增强mysql的性能：优化数据表结构、字段类型、字段索引、分表，分库、读写分离等等
-（3）、从数据库层面增强性能：优化SQL语句，合理使用字段索引。
-（4）、从代码层面增强性能：使用缓存和NoSQL数据库方式存储，如MongoDB/Memcached/Redis来缓解高并发下数据库查询的压力。
-（5）、减少数据库操作次数，尽量使用数据库访问驱动的批处理方法。
-（6）、不常使用的数据迁移备份，避免每次都在海量数据中去检索。
-（7）、提升数据库服务器硬件配置，或者搭建数据库集群。
-（8）、编程手段防止SQL注入：使用JDBC PreparedStatement按位插入或查询；正则表达式过滤（非法字符串过滤）
-
-
-#### 大批量的插入数据
-当用load导入数据，适当的设置可以提供导入的速度。对于MyISAM存储引擎的表，可以通过以下方式快速导入大量的数据
-```sql
-alter table tab_name disable keys;
-loading the data
-alter table tab_name disable keys;
-```
-
-disable keys和enable keys 用来打开或者关闭MyISAM表非索引的更新。在导入大量的数据到一个非空的MyISAM表，通过设置这两个命令，可以提高导入的效率
-对于Innodb类型的表不能使用上面的方式提高导入效率
-因为Innodb类型的表是按照主键的顺序保存，所有将导入的数据按照主键的顺序排序，可以有效地提高导入数据的效率
-
-在导入数据强执行SET UNIQUE_CHECKS=0，关闭唯一性校验，在导入结束后执行SET UNIQUE_CHECKS=1.恢复唯一性校验，可以提高导入的效率，如果应用使用自动提交的方式，建议在导入前执行SET AUTOCOMMIT=0时，关闭自动提交，导入结束后再执行SET AUTOCOMMIT=1，打开自动提交，也可以提高导入的效率
-
-#### 优化insert语句
-
-*   如果同时从一个客户端插入很多行，应尽量使用多个值表的insert语句，这种方式将大大缩减客户端与数据库之间的连接、关闭等消耗，使得效率比分开执行的单个insert语句快（大部分情况下，使用多个值表的insert语句那比单个insert语句快上好几倍）。
-
-```sql
-insert into test values(1,2),(1,3)...
-```
-*   如果从不同客户插入很多行，可以通过使用insert delayed语句提高更高的速度，delayed的含义是让insert语句马上执行，其实数据都被放到内存的队列中，并没有真正写入磁盘，这比每条语句分别插入要快的多；LOW_PRIORITY刚好相反，在所有其他用户对表的读写完成后才可以进行
-*   将索引文件和数据文件分在不同的磁盘上存放（利用建表中的选项）
-*   如果进行批量插入，可以通过增加bulk_insert_buffer_size变量值的方法来通过速度，但是，这只能对MyISAM表使用。
-*   当从一个文本文件装载一个表时，使用LOAD DATA INFILE。这通常比使用很多INSERT语句块快20倍
-
-#### ORDER BY优化
-InnoDB引擎下，通过有序排序索引顺序扫描，这种方式在使用explain分析查询的时候显示为Using Index，无需额外排序，操作效率较高：
-- 索引的列顺序和Order By子句的顺序完全一致
-- 索引中所有列的方向(升序,降序)和Order by子句完全一致
-- Order by中的字段全部在关联表中的第一张表中
-```sql
-explain select customer_id from customer order by store_id;
-```
-- 表
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtODk0MjI2N2U1ZDg0MTliMC5wbmc?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMDExNDE4OTU3M2Q3YzFkOC5wbmc?x-oss-process=image/format,png)
-- 违反规则，则filesort
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMjJkZGRhYWExMzk3Y2QzYi5wbmc?x-oss-process=image/format,png)
-第二张通过返回数据进行排序，也就是通常说的Filesort排序，所有不是通过索引直接返回排序结果的排序豆角Filesort排序。
-Filesort并不代表通过磁盘文件进行排序，而只是说明进行了一个排序操作，至于排序操作是否进行了磁盘文件或临时表等，则取决于MySQL服务器对排序参数的设置和需要排序数据的大小-myshim引擎
-```sql
-explain select * from customer order by store_id;
-```
-
-Filesort是通过相应排序算法，将取得的数据在**sort_buffer_size**系统变量设置的内存排序区进行排序。若内存装载不下，它会将磁盘上的数据进行分块，再对各个数据块进行排序，然后将各个块合并成有序的结果集。**sort_buffer_size**设置的排序区是每个线程独占的，所有同一个时刻，MySQL存在多个sort buffer排序区
-
-优化目标：尽量减少额外排序，**通过索引直接返回有序数据**：
-where和ordery by使用相同的索引，并且order by的顺序和索引顺序相同，并且order by的字段都是升序或者都是降序。否则肯定需要额外的排序操作，这样就会出现filesort。
-#### 优化group by
-若查询包括group by，但想要避免排序结果的消耗，可指定group by null。
-
-##### 优化嵌套查询
-子查询
-
-```sql
-explain select * from customer where customer_id 
-	not in(select customer_id from payment)
-```
-可以被更有效率的连接替代：
-```sql
-explain select * from customer a left join payment b 
-	on a.customer_id=b.customer_id where b.customer id is null
-```
-
-连接之所用更有效率是因为MySQL不需要在内存中创建临时表来完成这个逻辑上需要两步的查询工作。
-###### 优化分页查询
-一般分页查询，通过创建覆盖索引能较好地提高性能。
-
-比如`limit 1000,20`，此时MySQL排序出前1020条数据后，仅需要返回第**1001~1020**条记录，前1000条数据都被抛弃，查询和排序代价过高。
-
-- 优化
-可增加一个字段**last_page_record**，记录上一页和最后一页的编号。通过
-```sql
-explain select ... where last_page_record < ... desc limt ..
-```
-但若排序字段出现大量重复，不适用这种方式优化。
-
-# MySQL常用技巧
-#### 正则表达式的使用
-| 序列 | 序列说明 |
-| --- | --- |
-| ^ | 字符串的开始处进行排序 |
-| $ | 在字符串的末尾处进行匹配 |
-| . | 匹配任意单个字符串，包括换行服 |
-| [...] | 匹配括号内的任意字符 |
-| {FNXX==XXFN} | 匹配不出括号内任意字符 |
-| a* | 匹配零个或多个a(包括空串) |
-| a+ | 匹配一个或多个a（不包括字符串) |
-| a? | 匹配一个或零个a |
-| a1\ | a2 | 匹配a1或a2 |
-| a(m) | 匹配m个a |
-| a(m,) | 匹配m个或更多a |
-| a(m,n) | 匹配m到n个a |
-| a(,n) | 匹配0到n个a |
-| (...) | 将模式元素组成单一元素 |
-
-使用
-
-```sql
-select 'abcdefg' regexp '^a';
-.....
-```
-
-#### 如果range()提取随机行
-随机抽取某些行
-
-```sql
-select * from categrory order by rand() limit 5;
-```
-
-#### 利用group by的with rollup 子句
-使用group by的with rollup可以检索更多分组聚合信息：
-```sql
-select date_from(payment_date, '%Y-%M'), staff_id, sum(amount)
-from payment
-group by date_formate(payment_date, '%Y-%M'), staff_id;
-```
-
-#### 用BIT GROUP FUNCTIONS做统计
-使用GROUP BY语句和BIT_AND、BIT_OR函数完成统计工作，这两个函数的一般用途就是做数值之间的逻辑。
-
-# 优化数据库对象
-## 优化表类型
-表需要使用何种数据类型工具应用来判断，虽然考虑字段的长度会有一定的冗余，但是不推荐让很多字段都留有大量的冗余，这样既浪费磁盘的存储空间，同时在应用操作时也浪费物理内存MySQL，可以使用函数procedure analyse对当前的表进行分析
-```sql
-select * from tb1_name procedure analyse();
-select * from tb2_name procedure analyse(16,256);
-```
-
-输出的每一类信息都对数据表中的列的数据类型提出优化建议。第二语句告诉procedure anaylse不要为那些包含的值多余16个或256个字节的enum类型提出建议，如果没有这个限制，输出的信息可能很长。
-ENUM定义通常很难阅读，通过输出信息，可以将表中的部分字段修改为效率更高的字段。
-
-### 拆分
-#### 重置拆分
-把主码和一些列放到一个表，然后把住码和另外的列放到另一个表。
-
-- 好处
-**可以将常用的列放在一起，不常用的列放在一起，使得数据行变少，一个数据页可以存放更多的数据，在查询时会减少I/O次数**，缺点：**管理冗余，查询所有数据需要用join操作**
-
-#### 水平拆分
-根据一列或多列数据把数据行放到两个独立的表中：水平拆分会给应用增加复杂度，它通常在查询时需要多个表名，查询所有数据需要UNION操作，缺点：**只要索引关键字不大，则在索引查询时，表中增加了2-3倍的数据量，查询时也增加了读一个索引的磁盘次数，所有说拆分要考虑数据量的增长速度**。
-
-#### 常用场景
-- 表很大，分割后可以降低在查询时需要读的数据和索引的页数，同时也降低了索引的层数，提高查询速度
-- 表中的数据本来就有独立性，例如表中分别记录各个地区的数据或者不同时期的数据，特别是有些数据常用，而有些数据不常用
-- 需要把数据存放在多个介质上：如账单：最近三个月数据存在一个表中，3个月之前的数据存放在另一个表，成功一年的可以存储在单独的存储介质中。
-
-### 逆规范化
-
-数据库设计时需要满足规范化，但是规范化程度越高，产生的关系就越多。
-关系越多直接结果就是表直接的连接操作越频繁，而表连接的操作是性能较低的操作，直接影响到查询的数据。
-
-反规范化的好处在于降低连接操作的需求，降低外码和索引的数目，还可以减少表的树木，相应带来的问题可能出现数据的完整性问题。加快查询速度，但是降低修改速度。好的索引和其他方法经常能够解决性能问题，而不必采用反规范这种方法
-采用的反规范化技术
-
-*   增加冗余列：指在多个表中具有相同的列，它常用来在查询时避免连接操作
-*   增加派生列：指增加的列来自其他表中的数据，由其他表中的数据经过计算生成。增加的派生列其他作业是在查询时减少连接操作，避免使用集函数
-*   重新组表：指如果许多用户需要查看两个表连接出来的结果数据，则把这两个表查询组成一个表来减少连接而提高性能
-*   分割表
-
-维护数据的完整性
-
-*   批处理维护是指对复制列或派生列的修改积累一定的时间后，运行一批处理作业或修改存储过程对复制或派生列进行修改，这只能对实时性要求不高的情况下使用
-*   数据的完整性也可由应用逻辑来实现，这就要求必须在同一事务中对所有涉及的表进行增、删、改操作。用应用逻辑来实现数据的完整性风险较大，因为同一逻辑必须在所有的应用中使用和维护，容易遗漏。特别是在需求变化时，不易于维护
-*   使用触发器，**对数据的任何修改立即触发对复制列或者派生列的相应修改**，触发器是实时的，而且相应的处理逻辑只在一个地方出现，易于维护，一般来说，是解决这类问题比较好的方法
-
-#### 中间表
-对于数据量较大的表，在其上进行统计查询通常会效率很低，并且还要考虑统计查询是
-否会对在线的应用产生负面影响。通常在这种情况下，使用中间表可以提高统计查询的效率
-session 表记录了客户每天的消费记录，表结构如下：
-
-```sql
-CREATE TABLE session (
-cust_id varchar(10) , --客户编号
-cust_amount DECIMAL(16,2), --客户消费金额
-cust_date DATE, --客户消费时间
-cust_ip varchar(20) –客户IP 地址
-)
-```
-
-由于每天都会产生大量的客户消费记录,所以session 表的数据量很大,现在业务部门有
-一具体的需求：希望了解最近一周客户的消费总金额和近一周每天不同时段用户的消费总金
-额。针对这一需求我们通过2 种方法来得出业务部门想要的结果。
-方法1：在session 表上直接进行统计,得出想要的结果。
-
-```sql
-select sum(cust_amount) from session where cust_date>adddate(now(),-7);
-```
-
-方法2：创建中间表tmp_session，表结构和源表结构完全相同。
-
-```sql
-CREATE TABLE tmp_session (
-cust_id varchar(10) , --客户编号
-cust_amount DECIMAL(16,2), --客户消费金额
-cust_date DATE, --客户消费时间
-cust_ip varchar(20) –客户IP 地址
-) ;
-```
-
-```sql
-insert into tmp_session select * from session where cust_date>adddate(now(),-7);
-```
-
-转移要统计的数据到中间表,然后在中间表上进行统计，得出想要的结果。
-在中间表上给出统计结果更为合适,原因是源数据表(session 表)
-**cust_date 字段没有索引并且源表的数据量较大，所以在按时间进行分时段统计时效率**
-很低，这时可以在中间表上对cust_date 字段创建单独的索引来提高统计查询的速度。
-中间表在统计查询中经常会用到，其优点如下:
-
-1.  中间表复制源表部分数据，并且与源表相“隔离”，在中间表上做统计查询不
-    会对在线应用产生负面影响.
-2.  中间表上可以灵活的添加索引或增加临时用的新字段,从而达到提高统计查询
-    效率和辅助统计查询作用。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204B+Tree\347\264\242\345\274\225.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204B+Tree\347\264\242\345\274\225.md"
deleted file mode 100644
index 8244ef5c33..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204B+Tree\347\264\242\345\274\225.md"
+++ /dev/null
@@ -1,374 +0,0 @@
-# 1 数据结构及算法基础
-## 1.1 索引到底是什么？
-官方定义：索引（Index）是帮助MySQL高效获取数据的数据结构，即索引是数据结构。
-其出现就是为了提高数据查询效率，就像书的目录。
-
-既然是查询，就主要需要从查询算法角度优化。
-- 最基本的查询算法[顺序查找](http://en.wikipedia.org/wiki/Linear_search)（linear search），复杂度为O(n)的算法在数据量大时是糟糕的。
-- 更优秀的查找算法，如二分查找要求被检索数据有序，二叉树查找只能应用于[二叉查找树](http://en.wikipedia.org/wiki/Binary_search_tree)，但`数据本身的组织结构不可能完全满足各种数据结构`
-
-所以，在数据之外，数据库系统还维护着`满足特定查找算法的数据结构`，这些数据结构以某种方式引用（指向）数据，这样就可以`在这些数据结构上实现高级查找算法`
-这种ADT，就是索引。
-
-- 一种可能的索引方式
-![图1  一个例子](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtYzY1YmUwNzMzM2RiZmIyNy5wbmc?x-oss-process=image/format,png)
-
-左边是数据表，两列14条记录，最左边是数据记录的物理地址。
-为加快`Col2`的查找，可维护一个右边所示二叉查找树，每个节点分别包含索引键值及一个指向对应数据记录物理地址的指针，这样就可以运用二叉查找在O(log2 N)内取到相应数据。
-但实际数据库系统几乎没有使用二叉查找树或其进化品种[红黑树](http://en.wikipedia.org/wiki/Red-black_tree)（red-black tree）实现
-
-
-## 1.4 主存存取原理
-计算机使用的主存基本都是随机读写存储器（RAM），抽象出一个十分简单的存取模型来说明RAM的工作原理
-
-从抽象角度看，主存是一系列的存储单元组成的矩阵，每个存储单元存储固定大小的数据
-每个存储单元有唯一的地址，现代主存的编址规则比较复杂，这里将其简化成一个二维地址：通过一个行地址和一个列地址可以唯一定位到一个存储单元
-- 存取过程
-当系统需要读取主存时，将地址信号通过地址总线传给主存，主存读到地址信号后，解析信号并定位到指定存储单元，然后将此存储单元数据放到数据总线，供其它部件读取
-
-- 写主存
-过程类似，系统将要写入单元地址和数据分别放在地址总线和数据总线上，主存读取两个总线的内容，做相应的写操作
-
-这里可以看出，主存存取的时间仅与存取次数呈线性关系，因为不存在机械操作，两次存取的数据的“距离”不会对时间有任何影响，例如，先取A0再取A1和先取A0再取D3的时间消耗是一样的
-## 1.5  磁盘存取原理
-索引一般以文件形式存储在磁盘上，索引检索需要磁盘I/O。与主存不同，磁盘I/O存在机械消耗，因此磁盘I/O时间消耗巨大。
-
-磁盘由大小相同且同轴的圆形盘片组成，磁盘可以转动（各磁盘必须同步转动）
-在磁盘的一侧有磁头支架，磁头支架固定了一组磁头，每个磁头负责存取一个磁盘的内容。磁头不能转动，但是可以沿磁盘半径方向运动（实际是斜切向运动），每个磁头同一时刻也必须是同轴的，即从正上方向下看，所有磁头任何时候都是重叠的（不过目前已经有多磁头独立技术，可不受此限制）
-
-盘片被划分成一系列同心环，圆心是盘片中心，每个同心环叫做一个磁道，所有半径相同的磁道组成一个柱面。磁道被沿半径线划分成一个个小的段，每个段叫做一个扇区，每个扇区是磁盘的最小存储单元。为了简单起见，我们下面假设磁盘只有一个盘片和一个磁头。
-
-当需要从磁盘读取数据时，系统会将数据逻辑地址传给磁盘，磁盘的控制电路按照寻址逻辑将逻辑地址翻译成物理地址，即确定要读的数据在哪个磁道，哪个扇区
-为了读取这个扇区的数据，需要将磁头放到这个扇区上方，为了实现这一点，磁头需要移动对准相应磁道，这个过程叫做寻道，所耗费时间叫做寻道时间，然后磁盘旋转将目标扇区旋转到磁头下，这个过程耗费的时间叫做旋转时间
-## 1.6  局部性原理与磁盘预读
-由于存储介质特性，磁盘本身存取就比主存慢，再加上机械运动耗费，磁盘存取速度往往是主存的几百万分之一，因此要提高效率，必须减少磁盘I/O。
-为了达到这个目的，磁盘往往也不是严格按需读取，而是每次都会预读，即使只需要一个字节，磁盘也会从这个位置开始，顺序向后再读取一定长度的数据放入内存。
-这样做的理论依据是计算机科学中著名的局部性原理：
-`当一个数据被用到时，其附近的数据也通常会马上被使用`，`程序运行期间所需要的数据通常比较集中`。
-由于磁盘顺序读取的效率很高（无需寻道时间，只需很少的旋转时间），因此对于具有局部性的程序来说，预读可以提高I/O效率。
-
-`预读的长度一般为页（page）的整数倍`。innodb 默认一次读取 16k 。
-页是存储器的逻辑块，os往往将主存和磁盘存储区分割为连续的大小相等的块，每个存储块称为一页（许多 os 的页大小一般为4k），主存和磁盘以页为单位交换数据。
-当程序要读取的数据不在主存中时，会触发缺页异常，系统会向磁盘发出读盘信号，磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中，然后异常返回，程序继续运行。
-
-## 1.7 性能分析
-一般使用磁盘I/O次数评价索引结构的优劣
-
-### 1.7.1 为什么不用平衡二叉树？
-平衡二叉树只有两个分支，而B+树的分支≥2；
-B+树的层数只会小于平衡二叉树，层数越少，在查询时所需要的 I/O 硬盘访问越少，查询速度相对更快，提高了对系统资源的利用率。
-
-### 1.7.2 为什么不用红黑树？
-h明显要深的多。由于逻辑上很近的节点（父子）物理上可能很远，无法利用局部性，所以红黑树的I/O渐进复杂度也为O(h)，效率明显比B-Tree差很多
-
-B+Tree更适合外存索引，原因和内节点出度d有关
-从上面分析可以看到，`d越大索引的性能越好`
-`出度的上限取决于节点内key和data的大小`：
-```bash
-dmax=floor(pagesize/(keysize+datasize+pointsize))
-```
-floor表示向下取整。由于B+Tree内节点去掉了data域，因此可以拥有更大的出度，更好的性能。
-
-### 1.7.3 B Tree分析
-定义数据记录为一个二元组[key, data]
-- key为记录的键值，对于不同数据记录，key互不相同
-- data为数据记录除key外的数据
-
-B Tree有如下特点:
-- d为大于1的一个正整数，称为B-Tree的度
-- h为一个正整数，称为B-Tree的高度
-- 每个非叶节点由n-1个key和n个指针组成，其中d<=n<=2d
-- 每个叶节点最少包含一个key和两个指针，最多包含2d-1个key和2d个指针，叶节点的指针均为null
-- 所有叶节点具有相同的深度，等于树高h
-- key和指针互相间隔，节点两端是指针
-- 一个节点中的key从左到右非递减排列
-- 所有节点组成树结构
-- 每个指针要么为null，要么指向另外一个节点
-- 如果某个指针在节点node最左边且不为null，则其指向节点的所有key小于>v(key1),v(key1)为node的第一个key的值
-- 如果某个指针在节点node最右边且不为null，则其指向节点的所有key大于v(keym),v(keym)为node的最后一个key的值。
-- 如果某个指针在节点node的左右相邻key分别是keyi,keyi+1且不为null，则其指向节点的所有key小于v(keyi+1)且大于v(keyi)
-
-由于B Tree的特性,按key检索数据的算法非常直观
-- 首先从根节点二分查找
-- 如果找到则返回对应节点的data
-- 否则对相应区间的指针指向的节点递归进行查找
-- 直到找到目标节点/null指针，查找成功/失败
-```java
-bTreeSearch(node, key) {
-    if(node == null) return null;
-    foreach(node.key) {
-        if(node.key[i] == key) return node.data[i];
-            if(node.key[i] > key) return bTreeSearch(point[i]->node);
-    }
-    return bTreeSearch(point[i+1]->node);
-}
-data = bTreeSearch(root, my_key);
-```
-关于B-Tree有一系列有趣的性质，例如一个度为d的B-Tree，设其索引N个key，则其树高h的上限为![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMzRhYTNkOTJjOGNkYzA5Yy5wbmc?x-oss-process=image/format,png)
-检索一个key，其查找节点个数的渐进时间复杂度为![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNzAxYWY1NjMxMzRjZmUyMC5wbmc?x-oss-process=image/format,png)
-从这点可以看出，B Tree是一个非常有效率的索引数据结构。
-
-检索一次最多需要访问h个节点。数据库系统的设计者巧妙利用了磁盘预读原理（磁盘数据存储是采用块的形式存储的，每个块的大小为4K，每次IO进行数据读取时，同一个磁盘块的数据可以一次性读取出来），将一个节点的大小设为块的整数倍16K，这样每个磁盘块只需要一次I/O就可以完全载入内存。
-
-为了达到这个目的，在实际实现B-Tree还需要使用如下技巧：
-1. 每次新建节点时，直接申请一个页的空间，保证一个节点物理上也存储在一个页里，而且计算机存储分配都是按页对齐，就实现了一个node只需一次I/O
-2. B-Tree中一次检索最多需要h-1次I/O（根节点是常驻内存的），渐进复杂度为O(h)=O(logdN)
-
-以InnoDB的一个整数字段索引为例，N差不多是1200。树高是4时，可存1200的3次方，即17亿。考虑到根的数据块总在内存，一个10亿行的表上一个整数字段的索引，查找一个值最多只需要访问3次磁盘。其实，树的第二层也有很大概率在内存中，那么访问磁盘的平均次数就更少了。
-综上所述，用B-Tree作为索引结构效率是非常高的。但是其内部的非叶节点也存储了 data 数据，所以一个节点里也存不了多少数据。
-![](https://img-blog.csdnimg.cn/20200830223131662.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-
-### 1.7.4 B+树对B树的优势差异
-![](https://img-blog.csdnimg.cn/20200831013206413.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-与B Tree相比，B+Tree有以下不同点：
-- 每个节点的指针上限为2d
-- 内节点只存key
-- 叶节点不存指针,叶节点指向被索引的数据而不是其他叶节点
-    - innodb中,指向的是主键
-    - myshaym中指向的是数据的物理地址
-由于并不是所有节点都具有相同的域，因此B+Tree中叶节点和内节点一般大小不同
-这点与B Tree不同，虽然B Tree中不同节点存放的key和指针可能数量不一致，但是每个节点的域和上限是一致的，所以在实现中B Tree往往对每个节点申请同等大小的空间。
-#### 结构
--  B+ 树的非叶节点不存储数据，且所有数据节点之间指针串联（即链表）。B+Tree的每个叶节点增加一个指向相邻叶节点指针，形成带有顺序访问指针的B+Tree。此优化的目的是提高区间访问的性能，例如要查询key为从18到49的所有数据记录，当找到18后，只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点，极大提高区间查询效率
-
-- B 树子结点带数据，且兄弟节点之间无指针串联
-
-#### 查询性能
-最简单的对比测试，假设范围查询 [0,N-1] ：
-- B+ 树，只需要确定范围查询的边界节点，然后遍历即可
-时间复杂度粗略算做 `2logN + N` (2logN：两个范围边界值的查找)
--  B 树可能就需要一个个查找
-B 树就是 `NlogN` 
-
-范围越大，查询性能差异越明显。
-
-### 为什么不用 B*树？
-![](https://img-blog.csdnimg.cn/20200831012857264.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-B*树是B+树的变种：
-1. 关键字个数限制问题，B+树初始化的关键字初始化个数是cei(m/2)，b*树的初始化个数为（cei(2/3*m)）
-2. B+树节点满时就会分裂，而B*树节点满时会检查兄弟节点是否满（因为每个节点都有指向兄弟的指针），如果兄弟节点未满则向兄弟节点转移关键字，如果兄弟节点已满，则从当前节点和兄弟节点各拿出1/3的数据创建一个新的节点出来
-# 2 索引的实现
-索引属于存储引擎部分，不同存储引擎索引实现方式不同。
-本文只讨论MyISAM和InnoDB两个存储引擎的索引实现方式
-
-## 2.1 MyISAM索引
-使用B+Tree作为索引结构，叶节点data域存放数据记录的地址
-
-- MyISAM索引的原理图
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtM2M1MjhlMDUwYmE5OTU5My5wbmc?x-oss-process=image/format,png)
-设Col1为主键，则上图是一个MyISAM表的主索引（Primary key），可见MyISAM的索引文件仅保存数据记录的地址。
-
-MyISAM的主/辅索引在结构上无任何区别，只是主索引要求`key唯一`，辅索引`key可重复`
-
-如果在Col2上建立一个辅索引
-- Col2上建立的辅索引
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtY2EyMzQzNDFlY2MyNTlkMC5wbmc?x-oss-process=image/format,png)
-同样也是一颗B+Tree，data域保存数据记录的地址。
-因此，MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引，如果指定的Key存在，则取出其data域的值，然后以data域的值为地址，读取相应数据记录。
-
-MyISAM的索引方式也叫“非聚集”，是为了与InnoDB的聚集索引区别。
-
-#### `注意 聚集 = 聚簇`
-## 2.2 InnoDB 索引
-虽然InnoDB也使用B+Tree作为索引结构，但具体实现方式却与MyISAM截然不同
-
-### `InnoDB的数据文件本身就是索引文件`
-- MyISAM
-索引文件和数据文件分离，索引仅保存数据的地址
-- InnoDB
-表数据文件本身就是按B+Tree组织的一个索引结构，该树的叶节点data域保存了完整的数据记录。
-索引的key：数据表的主键，因此InnoDB表数据文件本身就是主索引。
-
--  InnoDB主索引（也是数据文件）
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMzZhMTBlYTVjZTZmZGVhNC5wbmc?x-oss-process=image/format,png)
-
-InnoDB的数据文件本身要按主键聚集，所以InnoDB要求表必须有主键（MyISAM可无），如果没有显式指定，则MySQL会自动选择一个可以唯一标识数据记录的列作为主键。
-不存在这种列，则MySQL自动为InnoDB表生一个隐含字段作为主键，这个字段长度为6个字节，类型为长整形
-
-### InnoDB的辅索引data域存储相应记录主键的值而非地址
-即InnoDB的所有辅助索引都引用主键作为data域。
-
-- 定义在Col3上的一个辅索引
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtOWU4ZWI5MDQwMjliM2JjZS5wbmc?x-oss-process=image/format,png)
-这里以英文字符的ASCII码作为比较准则
-聚集索引这种实现方式使得按主键的搜索十分高效，但是辅助索引搜索需要检索两遍索引：
-  -   首先检索辅助索引获得主键
-  - 然后用主键到主索引中检索获得记录
-
-知道了InnoDB的索引实现后，就很容易明白为什么不建议使用过长的字段作为主键，因为所有辅索引都引用主索引，过长的主索引会令辅索引变得过大
-再如，用非单调的字段作为主键在InnoDB中不是个好主意，因为InnoDB数据文件本身是一颗B+Tree，非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整，十分低效，而使用自增字段作为主键则是一个很好的选择。
-
-InnoDB表都根据主键顺序以索引形式存放，该存储方式的表称为索引组织表。
-而InnoDB又使用的B+树索引模型，所以数据都是存储于B+树。
-
-**每一个索引在InnoDB里面就对应一棵B+树。**
-
-- 主键列id，字段k，在k上有索引的建表语句
-![](https://img-blog.csdnimg.cn/20200716231848265.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-表中R1~R5的(id,k)值分别为(100,1)、(200,2)、(300,3)、(500,5)、(600,6)
-- 两棵树的示意图，即InnoDB的索引组织结构
-![](https://img-blog.csdnimg.cn/20200716232154390.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-
-根据叶节点内容，索引类型分为
-### 主键索引(聚簇索引)
-InnoDB的主键索引也称聚簇索引、聚集索引（clustered index）。主键索引的叶节点存整行数据。
-
-聚簇索引并非一种单独的索引类型，而是一种`数据存储方式`。
-细节依赖其实现方式，但InnoDB 的聚簇索引实际上在同一个结构中`保存了B-Tree索引和数据行`，是对磁盘上实际数据重新组织以按指定的一个或多个列的值排序的算法。
-
-每个 InnoDB 表都有一个称为聚集索引的特殊索引，用于存储行数据。通常，聚集索引与主键同义。为了从查询、插入和其他数据库操作中获得最佳性能，了解 InnoDB 如何使用聚集索引来优化常见的查找和 DML 操作非常重要。
-- 在表上定义主键时，InnoDB 将其用作聚集索引。应该为每个表定义一个主键。若没有逻辑唯一且非空的列或列集使用主键，请添加自增列。自增列值是唯一的，并在插入新行时自动添加
-- 若未定义主键，则 InnoDB 使用第一个 UNIQUE 索引，所有键列都定义为 NOT NULL 作为聚集索引。
-- 若表没有主键或合适的唯一索引，InnoDB 会在包含行 ID 值的合成列上生成一个名为 **GEN_CLUST_INDEX** 的隐藏聚集索引。行按 InnoDB 分配的行 ID 排序。行 ID 是一个 6 字节的字段，随着插入新行而单调增加。因此，按行 ID 排序的行在物理上是按插入顺序排列的。
-
-- 特点
-存储数据的顺序和索引顺序一致。
-
-一般情况下主键会默认创建聚簇索引，`一张表只允许存在一个聚簇索引`。
-
-当表有聚簇索引，数据实际存在索引叶子页（leaf page）中。
-- `聚簇`
-数据行和相邻的键值交错的存储在一起，InnoDb通过主键聚集数据。
-`因无法同时把数据行存放在两个不同地方，所以在一个表只能有一个聚簇索引` （不过，覆盖索引可以模拟多个聚簇索引）。
-
-- 若未定义主键，InnoDB 会选择一个唯一的非空索引代替
-- 若无这样的索引，InnoDB 会隐式定义一个主键来作为聚簇索引
-- InnoDB值聚集在同一个页面中的记录，包含相邻键值的页面可能会相距很远。
-
-#### 聚集索引如何加快查询速度
-通过聚集索引访问一行很快，因为索引搜索直接指向包含行数据的页面。若表很大，与使用与索引记录不同的页面存储行数据的存储组织相比，聚簇索引体系结构通常可以节省磁盘 I/O。
-
-#### 二级索引与聚集索引的关系
-聚集索引以外的索引称为二级索引。在 InnoDB 中，二级索引中的每条记录都包含该行的主键列，以及为二级索引指定的列。 InnoDB 使用这个主键值来搜索聚集索引中的行。
-
-如果主键很长，二级索引会占用更多的空间，所以主键短是有利的。
-### 非主键索引
-也叫非聚簇索引、辅助索引、二级索引secondary index）。
-
-非主键索引的叶节点是主键值。
-
-主键索引 	V.S 普通索引的查询
-- select * from T where ID=500，主键查询，只需搜ID这棵B+树
-- select * from T where k=5，普通索引查询，需先搜k索引树，得到ID的值为500，再到ID索引树搜索（回表）。
-
-> 回表：InnoDB在普通索引a上查到主键id的值后，再根据一个个主键id的值到主键索引上去查整行数据的过程。
-
-非主键索引的查询需要多扫描一棵索引树。因此`尽量使用主键查询`，减少回表。
-
-InnoDB和MyISAM是如何存储下面的这个表的
-```sql
-CREATE TABLE layout_test(
-　　　　col1 int not null,
-　　　　col2 int not null,
- 　　　 primary key (col1),
-　　　　key(col2)
-);
-```
-假设该表的主键取值为1~10000，按随机顺序插入，并使用`OPTIMIZE TABLE`命令优化。
-即数据在磁盘的存储方式已最优，但进行的顺序是随机的。
-列col2的值时从1~100之间随机赋值，所以有很多重复值。
-
-## MyISAM 数据分布
-MyIsam按数据插入的顺序存储在磁盘。实际上，MyISAM 中主键索引和其他索引在结构上没有什么不同。主键索引就是一个名为PRIMARY的唯一非空索引。
-
-## InnoDB 的数据分布
-而InnoDB支持聚簇索引，在InnoDB中，聚簇索引“是”表，不像myISAM那样需要独立的行存储。
-
-### 聚簇索引优点
-- 把相关数据保存在一起
-例如，实现电子邮箱时，可以根据用户id来聚集数据，这样只需要从磁盘读取少数数据页，就能获取某个用户的全部邮件。若未使用聚簇索引，则每封邮件都可能导致一次I/O。
-- 数据访问更快
-聚簇索引将索引和数据保存在同一B-Tree，从聚簇索引中获取数据通常比非聚簇索引中快
-- 覆盖索引扫描的查询可以直接使用页节点中的主键值
-
-### 聚簇索引缺点
-聚簇索引最大限度提高了I/O密集型应用性能，但若数据全存内存，则访问顺序就没那么重要，聚簇索引也没啥优势了。
-
-- 插入速度严重依赖插入的顺序
-按主键的顺序插入是加载数据到innodb表中速度最快的。
-但若不是按主键顺序，则加载后最好使用OPTIMIZE TABLE重新组织表。
-
-- 更新聚簇索引的代价高
-因为会强制InooDB将每个更新的数据移动到新位置。
-
-基于聚簇索引的表在插入行，或主键被更新导致需要移动行时，可能产生页分裂（page split）。
-当行的主键值要求必须将该行插入到某个满页时。存储引擎会将该页分裂成两个页面来容纳该行，这就是一次页分裂。页分裂会导致表占用更多存储空间。
-
-聚簇索引可能导致全表扫描变慢，尤其是行比较稀疏，或由于页分裂导致数据存储不连续时。
-
-二级索引（非聚簇索引）可能比想象的要更大，因为在二级索引的子节点包含了最优一个几点可能让人有些疑惑
-- 为什么二级索引需要两次索引查找？
-二级索引中保存的“行指针”的本质：不是物理地址的指针，而是行的主键值。所以通过二级索引查找行，引擎需要找到二级索引的子节点获得对应主键值，然后根据该值去聚簇索引找到对应行。
-出现重复工作：两次B-Tree查找，而非一次。对于InnoDB，自适应哈希索引能够减少这样重复。
-
-《数据库原理》解释聚簇索引和非聚簇索引区别：
-- 聚簇索引的叶节点就是数据节点
-- 非聚簇索引的叶节点仍是索引节点，只不过有指向对应数据块的指针
-
-MYISAM和INNODB两种引擎的索引结构。
-- 原始数据
-![](https://img-blog.csdnimg.cn/20210601201544160.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- MyISAM数据存储
-![](https://img-blog.csdnimg.cn/20210601201636760.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-MYISAM按列值与行号组织索引，叶子节点保存的是指向存放数据的物理块的指针。
-MYISAM引擎的索引文件（.MYI）和数据文件(.MYD)是相互独立的。
-
-而InnoDB按聚簇索引存储数据，存储数据的结构如下：
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTE1ZjYzZDM2N2EwNzdhYTQucG5n?x-oss-process=image/format,png)
-
-
-注：聚簇索引中的每个叶子节点包含主键值、事务ID、回滚指针(rollback pointer用于事务和MVCC）和余下的列(如col2)。
-
-INNODB的二级索引与主键索引有很大的不同。InnoDB的二级索引的叶子包含主键值，而不是行指针(row pointers)，这减小了移动数据或者数据页面分裂时维护二级索引的开销，因为InnoDB不需要更新索引的行指针。其结构大致如下：
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTFkNTZhNmJjNTY5NmQ0Y2MucG5n?x-oss-process=image/format,png)
-INNODB和MYISAM的主键索引与二级索引的对比：
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQyMzRjYTg1OTE5ODI2NDcucG5n?x-oss-process=image/format,png)
-
-InnoDB的的二级索引的叶子节点存放的是KEY字段加主键值。因此，通过二级索引查询首先查到是主键值，然后InnoDB再根据查到的主键值通过主键索引找到相应的数据块。而MyISAM的二级索引叶子节点存放的还是列值与行号的组合，叶子节点中保存的是数据的物理地址。所以可以看出MYISAM的主键索引和二级索引没有任何区别，主键索引仅仅只是一个叫做PRIMARY的唯一、非空的索引，且MYISAM引擎中可以不设主键。
-
-# 3 索引的维护
-B+树为维护索引的有序，插入新值时需要做必要维护。
-
-上图为例，插入新行ID 700，只需在R5的记录后面插入。如果新插入ID 400，就麻烦了，需要逻辑上挪动后面数据，腾出位置。
-更坏的结果是，如果R5所在数据页已满，需申请新数据页，然后挪部分数据过去。该过程称为页分裂，影响性能。
-
-页分裂还影响数据页的利用率。原本放在一个页的数据，现在分两页，整体空间利用率降低。有分裂就有合并。当相邻两个页由于删除数据，利用率很低后，会将数据页合并。合并过程可认为是分裂过程的逆过程。
-
-## 案例
-建表规范：建表语句一定要有自增主键。
-![](https://img-blog.csdnimg.cn/20210602141922445.png)
-到底哪些场景下应该自增主键？
-自增主键一般这么定义： 
-```sql
-NOT NULL PRIMARY KEY AUTO_INCREMENT
-```
-- 考虑性能
-插新记录可不指定ID，系统会获取当前ID最大值加1作为新记录ID，即自增主键符合递增插入场景。每插入新记录，都是追加，不涉及挪动其他记录，也不会触发叶节点分裂。
-而有业务逻辑的字段做主键，不易保证有序插入，因此写数据成本较高。
-
-- 考虑存储空间
-假设表中确实有个唯一字段身份证号，应该用身份证号做主键，还是自增字段？
-因为非主键索引的叶节点都是主键值。
-- 身份证号做主键，每个二级索引的叶节点占约20字节
-- 整型主键，只要4字节，长整型（bigint）8字节
-
-主键长度越小，普通索引的叶节点越小，普通索引占用空间就越小。
-因此从性能和空间考虑，自增主键往往更合理。
-
-有无场景适合用业务字段做主键？
-场景如下：
-1. 只有一个索引
-2. 该索引须是唯一索引
-
-即KV场景。
-因为没有其他索引，所以不用考虑其他索引的叶节点大小。
-要优先考虑“尽量使用主键查询”原则，直接将该索引设为主键，避免每次查询要搜两棵树。
-
-> 参考
-> - 《MySQL实战》
-> - 《高性能 MySQL》
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204DDL\343\200\201DML\344\270\216DCL\350\257\255\345\217\245.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204DDL\343\200\201DML\344\270\216DCL\350\257\255\345\217\245.md"
deleted file mode 100644
index 34b83aaada..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204DDL\343\200\201DML\344\270\216DCL\350\257\255\345\217\245.md"
+++ /dev/null
@@ -1,11 +0,0 @@
-SQL 语句主要可以划分为以下 3 个类别。
-- DDL（Data Definition Languages）语句
-数据定义语言，这些语句定义了不同的数据段、数据库、表、列、索引等数据库对象的定义。常用的语句关键字主要包括 create、drop、alter等。
-
-- DML（Data Manipulation Language）语句
-数据操纵语句，用于添加、删除、更新和查询数据库记录，并检查数据完整性，常用的语句关键字主要包括 insert、delete、udpate 和select 等。(增添改查）
-
-- DCL（Data Control Language）语句
-数据控制语句，用于控制不同数据段直接的许可和访问级别的语句。这些语句定义了数据库、表、字段、用户的访问权限和安全级别。主要的语句关键字包括 grant、revoke 等。
-
-DDL 是数据定义语言的缩写，简单来说，就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作，而不涉及到表的定义、结构的修改，更不会涉及到其他对象。DDL 语句更多的被数据库管理员（DBA）所使用，一般开发人员很少使用。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204Order\346\216\222\345\272\217\345\256\236\347\216\260\345\216\237\347\220\206.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204Order\346\216\222\345\272\217\345\256\236\347\216\260\345\216\237\347\220\206.md"
deleted file mode 100644
index 20b272b64c..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204Order\346\216\222\345\272\217\345\256\236\347\216\260\345\216\237\347\220\206.md"
+++ /dev/null
@@ -1,269 +0,0 @@
-这天风和日丽，我还在工位上苦练摸鱼技术
-![](https://img-blog.csdnimg.cn/20210413101116274.png)
-
-突然接到产品的☎️，又来需求？
-![](https://img-blog.csdnimg.cn/20210413101305688.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-只见猖狂的产品又开始口若黄河：我现在要查询到城市是“上海”的所有用户名，并且还要按用户名排序，返回前1000人的名字、年龄。
-
-我急忙正襟危坐，从一堆库表中翻出相关责任表，找到其建表语句：
-![](https://img-blog.csdnimg.cn/d7dcbdb63b7941e1a0dc4b32472be8f5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-对照表结构，再看看产品需求
-![](https://img-blog.csdnimg.cn/20210413134110379.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-太容易了！SQL随手一写：
-![](https://img-blog.csdnimg.cn/cd6e5cb9ac87471aa4674d377e4a605b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-真理往往就是这么简单而朴实，一个需求好像就完美解决了。但作为一个性能优化giao手，考虑要尽量避免全表扫描，于是给 **city** 字段加个索引。
-建完索引，自然还需要使用explain验证一下：
-```java
-explain select city, name, age from citizen where city = '上海' order by name limit 1000;
-+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
-| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |
-+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
-|  1 | SIMPLE      | citizen | NULL       | ALL  | city          | NULL | NULL    | NULL |   32 |   100.00 | Using where; Using filesort |
-+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
-1 row in set, 1 warning (0.00 sec)
-```
-Extra字段的 **Using filesort** 表示需要排序，**MySQL会给每个线程分配一块内存（sort_buffer）用于排序**。
-
-这时魔鬼产品突然凑过来问：给我看看你代码咋写的
-
-> 你这么写你真的懂MySQL底层怎么执行order by的吗？
-
-我惊醒了，还真没想过MySQL的底层实现。
-
-> 产品经理冷笑道：你知道你的 city 索引长啥样吗？
-
-我自己建立的，我咋可能不知道！随手直接画出
-- city字段的索引示意图
-![](https://img-blog.csdnimg.cn/20210406195738724.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)小产品，你可好好看了，这里 `id_x ~ id_(x+n)` 的数据都满足**city=上海**。
-
-> 那你倒是说说这条SQL的执行流程？
-
-![](https://img-blog.csdnimg.cn/555519cdc0e046379d5e4ec3a6206371.png)
-
-不知道了吧，我来告诉你吧：
-1. 初始化**sort_buffer**，放入`city, name, age`三字段
-2. 从索引`city`找到第一个满足`city=上海`的主键id， 即`id_x`；
-3. 到id索引取出整行，取`city, name, age`三个字段的值，存入**sort_buffer**
-4. 从索引`city`取下一个记录的主键id
-5. 重复3、4，直到city值不满足查询条件，即主键`id_y`
-6. 对**sort_buffer**数据按`name`做快排
-7. 取排序后结果的前1000行，返回给client
-
-这就是
-# 全字段排序
-执行流程示意图：
-![](https://img-blog.csdnimg.cn/20210412145927471.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-**按name排序** 这一操作可能：
-- 在内存中完成
-- 或需要外部排序
-
-这取决于：
-- 排序所需的内存
-若`待排序数据量 < sort_buffer_size`，就在内存中排序。
-- 参数**sort_buffer_size**
-MySQL为排序开辟的内存（sort_buffer）的大小。若待排序数据量太大，内存放不下，则需利用磁盘临时文件辅助排序。
-
-产品又开始炫技了，又问到：
-
-> order by语句何时会使用临时文件？
-
-这？这还真又触及到我的知识盲区了！
-![](https://img-blog.csdnimg.cn/20210413134237949.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-```sql
-SET optimizer_trace='enabled=on';
-
-/* 使用 @a 保存 Innodb_rows_read 的初始值 */
-select VARIABLE_VALUE into @a 
-from performance_schema.session_status 
-where variable_name = 'Innodb_rows_read';
-
-select city, name,age
-from citizen
-where city='上海'
-order by name
-limit 1000;
-
-/* 查看 OPTIMIZER_TRACE 输出 */
-SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G
-
-/* 使用 @b 保存 Innodb_rows_read 的当前值 */
-select VARIABLE_VALUE into @b 
-from performance_schema.session_status
-where variable_name = 'Innodb_rows_read';
-
-/* 计算 Innodb_rows_read 的差值 */
-select @b-@a;
-```
-查看 **OPTIMIZER_TRACE** 结果中的 **number_of_tmp_files** 字段确认是否使用临时文件。
-
-```sql
-"filesort_execution": [
-],
-"filesort_summary": {
-	"rows": 4000
-	"examined_rows": 4000,
-	"number_of_tmp_files": 12,
-	"sort_buffer_size": 32664 ,
-	"sort_mode": "<sort_key, packed_additional_fields>"
-```
-- **number_of_tmp_files**
-排序过程中使用的临时文件数。为啥需要12个文件？内存放不下时，就需要使用外部排序，外部排序一般使用归并排序。
-MySQL将需要排序的数据分成12份，每一份单独排序后存在这些临时文件中。然后把这12个有序文件再合并成一个有序的大文件。
-
-若 **sort_buffer_size** 超过需排序的数据量大小，则 **number_of_tmp_files** 就是0，即排序可直接在内存完成。
-
-否则就需要放在临时文件中排序。**sort_buffer_size**越小，需要分成的份数越多，**number_of_tmp_files**的值就越大。
-- **examined_rows**
-参与排序的行数。测试表有4000条满足city='上海’的记录，所以该参数为4000。
-
-- **sort_mode** 的**packed_additional_fields**
-排序过程对字符串做了“紧凑”处理。即使name字段的定义是varchar(16)，在排序过程中还是要按实际长度分配空间。
-
-`select @b-@a` 的结果4000，即`整个执行过程只扫描了`4000行。
-
-> 为避免对结论造成干扰，我把**internal_tmp_disk_storage_engine**设置成MyISAM。否则，`select
-> @b-@a`的结果会显示为4001。
-> 因为查询**OPTIMIZER_TRACE**表时，需要用到临时表，而**internal_tmp_disk_storage_engine**的默认值是InnoDB。若使用InnoDB，把数据从临时表取出时，会让**Innodb_rows_read**的值加1。
-
-我惊奇地望着产品，像瞻仰伟人一般，不如你继承我的代码吧，让我来做产品？
-![](https://img-blog.csdnimg.cn/20210413142731670.png)
-# rowid排序
-上面的算法，只是读了一遍原表数据，剩下的操作都是在**sort_buffer**和临时文件中执行。
-
-这就存在问题：若查询要返回的字段很多，则：
-- **sort_buffer**要放的字段数就会很多=》
-- 内存能放下的行数就会变少=》
-- 就要分成很多临时文件=》
-- 排序性能就会很差。
-
-**所以若单行很大，该算法的效率可不够行哦。**
-![](https://img-blog.csdnimg.cn/20210413143147457.png)
-
-产品老大又开始发难，那你知道若**MySQL认为排序的单行长度太大，它会咋样**？
-
-现在修改个参数，让MySQL采用另外一种算法。
-```bash
-SET max_length_for_sort_data = 16;
-```
-- **max_length_for_sort_data**
-MySQL用于控制**用于排序的行数据的长度**。若单行长度超过该值，MySQL就认为单行太大，要换个算法。
-
-`city、name、age` 三字段的定义总长度36，那你看我把**max_length_for_sort_data**设为16会咋样？
-
-新的算法放入**sort_buffer**的字段，只有待排序列（name字段）和主键id。
-但这时，排序的结果就因少了`city`和`age`字段值，不能直接返回了，整个执行流程变成如下：
-1. 初始化`sort_buffer`，确定放入两个字段，即`name`和`id`
-2. 从`city`找到第一个满足 **city=上海** 的主键id，即`id_x`
-3. 到`id`取出整行，取name、id这俩字段，存入`sort_buffer`
-4. 从`city`取下一个记录的主键id
-5. 重复3、4，直到不满足**city=上海**，即`id_y`
-6. 对`sort_buffer`数据按name排序
-7. 遍历排序结果，取前1000行，并按照id的值回到原表中取出city、name和age三个字段返回给client
-
-![](https://img-blog.csdnimg.cn/20210413144202883.png)
-听到这里，感觉明白了一些：产品你别急，你看我画下这个`rowid排序`执行过程的示意图，对不对？
-![](https://img-blog.csdnimg.cn/20210412150358621.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-你看这个和你之前画的全字段排序示意图对比，其实就是多访问了一次表citizen的主键索引，即step7。
-
-> resultSet是个逻辑概念，实际上MySQL服务端从排序后的**sort_buffer**中依次取出id，然后到原表查到city、name和age这三字段的结果，无需在服务端再耗费内存存储结果，而是直接返回给client。
-
-这时查看rowid排序的**OPTIMIZER_TRACE**结果：
-```sql
-"filesort_execution": [
-],
-"filesort_summary": {
-	"rows": 4000
-	"examined_rows": 4000,
-	"number_of_tmp_files": 10,
-	"sort_buffer_size": 32728 ,
-	"sort_mode": "<sort_key, rowid>"
-```
-- `select @b-@a`结果变成5000
-因为这时除了排序过程，在排序完成后，还要根据id去原表取值。由于语句是limit 1000，因此会多读1000行。
-- **sort_mode** 变成了 **<sort_key, rowid>**
-表示参与排序的只有name和id字段
-- **number_of_tmp_files** 变成10
-因为这时参与排序的行数虽然还是4000，但每行都变小了，因此需排序的总数据量就小了，需要的临时文件也就少咯。
-
-产品最后总结到：
-- 若MySQL认为排序内存太小，会影响排序效率，就会采用rowid排序
-这样排序过程中一次可以排序更多行，但最后需要回表取数据
-- 若MySQL认为内存够大，会优先选择全字段排序
-把需要字段都放到sort_buffer，这样排序后就直接从内存返回查询结果，不用回表。
-
-所以MySQL就是：若内存够，就多利用内存，尽量减少磁盘访问。
-
-对InnoDB，**rowid排序会要求回表，多造成了磁盘读，因此不会被优先选择**。
-所以MySQL排序是个高成本操作。
-- 是不是所有order by都需排序呢？若不排序就能得到正确的结果，那对系统的消耗会小很多，语句的执行时间也会变得更短。
-并非所有order by都需排序操作。MySQL之所以需要生成临时表，并且在临时表上做排序，是因为原来的数据都是无序的。
-
-- 如果能保证从city索引上取出来的行，天生就是按name递增排序，是不是就可以不用再排序了？
-是的。
-
-所以可以创建一个`city,name`联合索引：
-```sql
-alter table t add index citizen(city, name);
-```
-
-- 该索引的示意图
-![](https://img-blog.csdnimg.cn/202104111826435.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-依然可以用树搜索定位到第一个满足city='上海’的记录，并且能确保接下来按顺序取“下一条记录”的遍历过程，只要city是上海，name值一定有序。
-这样整个查询过程的流程就变成：
-1. 从索引(city,name)找到第一个满足city='上海’条件的主键id
-2. 到主键id索引取出整行，取name、city、age三个字段的值，作为结果集的一部分直接返回
-3. 从索引(city,name)取下一个记录主键id
-4. 重复步骤2、3，直到查到第1000条记录，或者是不满足city='上海’条件时循环结束
-
-- 引入(city,name)联合索引后，查询语句的执行计划
-![](https://img-blog.csdnimg.cn/20210412151401991.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-可见，该查询过程无需临时表，也无需排序。
-- 使用 explain 查看(city,name)联合索引，查询语句的执行计划
-
-```sql
- explain select city, name, age from citizen where city = '上海' order by name limit 1000;
-+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
-| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |
-+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
-|  1 | SIMPLE      | citizen | NULL       | ref  | city,name       | name | 51    | const |   4000 |   100.00 | Using index condition |
-+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
-1 row in set, 1 warning (0.00 sec)
-```
-可见Extra字段中没有Using filesort了，也就是不需要排序了。而且由于(city,name)这个联合索引本身有序，所以该查询也不用把4000行全都读一遍，只要找到满足条件的前1000条记录即可退出。在这个例子里，只需扫描1000次。
-
-该语句的执行流程有没有可能进一步简化呢？
-- 覆盖索引
-索引上的信息足够满足查询请求，不需要再回到主键索引上去取数据。
-
-按覆盖索引，可以再优化一下这个查询语句的执行流程。
-针对这个查询，可以创建一个city、name和age的联合索引，对应的SQL语句就是：
-```sql
-alter table t add index city_user_age(city, name, age);
-```
-这时，对于city字段的值相同的行来说，还是按照name字段的值递增排序的，此时的查询语句也就不再需要排序了。这样整个查询语句的执行流程就变成了：
-1. 从索引(city,name,age)找到第一个满足city='上海’条件的记录，取出其中的city、name和age这三个字段的值，作为结果集的一部分直接返回
-2. 从索引(city,name,age)取下一个记录，同样取出这三个字段的值，作为结果集的一部分直接返回
-3. 重复2，直到查到第1000条记录或不满足city='上海'
-
-引入 `(city,name,age)` 联合索引，查询语句的执行流程
-![](https://img-blog.csdnimg.cn/20210412151620798.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- explain查看`(city,name,age)`联合索引查询语句的执行计划
-
-```sql
-explain select city, name, age from citizen where city = '上海' order by name limit 1000;
-+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
-| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |
-+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
-|  1 | SIMPLE      | citizen | NULL       | ref  | city,name,age        | age | 51    | const |   4000 |   100.00 | Using where; Using index |
-+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+
-1 row in set, 1 warning (0.00 sec)
-```
-Extra字段里面多了“Using index”，说明使用了覆盖索引，性能上会快很多。
-但这并非说要为了每个查询能用上覆盖索引，就要把语句中涉及的字段都建上联合索引，毕竟索引也很占空间，而且修改新增都会导致索引改变，还是具体业务场景具体分析。
-
-> 参考
-> - “order by”是怎么工作的？
-> - https://blog.csdn.net/Linuxhus/article/details/112672434?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242
-> - 天天写order by，你知道Mysql底层执行原理吗？
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204binlog.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204binlog\350\257\246\350\247\243.md"
similarity index 96%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204binlog.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204binlog\350\257\246\350\247\243.md"
index 69681dfe30..8dfb68637a 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204binlog.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204binlog\350\257\246\350\247\243.md"
@@ -6,7 +6,7 @@
 二进制日志，记录对数据发生或潜在发生更改的SQL语句，并以二进制形式保存在磁盘。
 
 # 2 Binlog 的作用
-复制、恢复和审计。
+主要作用：复制、恢复和审计。
 
 # 3 开启Binlog
 ## 3.1 查询当前 MySQL 是否支持 binlog
@@ -159,4 +159,6 @@ show binlog events in 'mysql-bin.000001' from 666 limit 1, 2;
 
 
 参考
-- https://blog.csdn.net/vhomes/article/details/8082734
\ No newline at end of file
+- https://blog.csdn.net/vhomes/article/details/8082734
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\273\351\224\256.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204\344\270\273\351\224\256\350\257\246\350\247\243.md"
similarity index 84%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\273\351\224\256.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204\344\270\273\351\224\256\350\257\246\350\247\243.md"
index 98922c7c9e..41e0cfa320 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\344\270\273\351\224\256.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\347\232\204\344\270\273\351\224\256\350\257\246\350\247\243.md"
@@ -1,5 +1,9 @@
-主键(primary key)，一列 (或一组列)，其值能够唯一区分表中的每个行。唯一标识表中每行的这个列(或这组列)称为主键。主键用来表示一个特定的行。没有主键，更新或删除表中特定行很困难，因为没有安全方法保证只涉及相关的行而不误伤其他行！
-主键索引自动创建，如果不自增就会造成频繁页分裂，导致性能降低。
+> 全是干货的技术号：
+> 本文已收录在github，欢迎 star/fork：
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
+# 主键(primary key)
+一列 (或一组列)，其值能够唯一区分表中的每个行。唯一标识表中每行的这个列(或这组列)称为主键。主键用来表示一个特定的行。没有主键，更新或删除表中特定行很困难，因为没有安全方法保证只涉及相关的行而不误伤其他行！
 
 一个顾客表可以使用顾客编号列，而订单表可以使用订单ID，雇员表可以使用雇员ID或雇员社会保险号。
 
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\350\201\232\345\220\210\345\207\275\346\225\260.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\350\201\232\345\220\210\345\207\275\346\225\260.md"
deleted file mode 100644
index 47dc27bdb8..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\350\201\232\345\220\210\345\207\275\346\225\260.md"
+++ /dev/null
@@ -1,133 +0,0 @@
-一般情况下，我们需要的聚合数据(总和，平均数，最大最小值等)并不总是存储在表中。 但可以通过执行存储数据的计算来获取它。
-
-例如，由于orderDetails表仅存储每个项目的数量和价格，无法通过从orderdetails表直接查询获得每个订单的总金额。必须为每个订单查询项目的数量和价格，并计算订单的总额。
-要在查询中执行此类计算，就要使用聚合函数了。
-
-聚合函数对一组值执行计算并返回单个值。
-
-MySQL提供了许多聚合函数，包括AVG，COUNT，SUM，MIN，MAX等。
-除COUNT函数外，其它聚合函数在执行计算时会忽略NULL值。
-# AVG
-计算一组值的平均值。
-
-```sql
-AVG(expression)
-```
-
-可以使用AVG()函数通过使用以下查询来计算products表中所有产品的平均价格：
-```sql
-mysql> SELECT AVG(buyPrice) average_buy_price
-FROM products;
-+-------------------+
-| average_buy_price |
-+-------------------+
-| 54.395182         |
-+-------------------+
-1 row in set
-```
-# COUNT
-返回表中的行数。
-比如获取products表中的产品数量：
-```sql
-mysql> SELECT COUNT(*) AS Total
-FROM products;
-+-------+
-| Total |
-+-------+
-|   110 |
-+-------+
-1 row in set
-```
-COUNT()函数有几个表单，如COUNT(*)和COUNT(DISTINCT expression)
-
-# SUM()
-返回一组值的总和。如果找不到匹配行，则SUM()函数返回NULL值。
-
-比如获得每个产品的总销售量，搭配GROUP BY：
-```sql
-mysql> SELECT productCode,sum(priceEach * quantityOrdered) total
-FROM orderdetails
-GROUP by productCode;
-+-------------+-----------+
-| productCode | total     |
-+-------------+-----------+
-| S10_1678    | 90157.77  |
-| S700_3505   | 84992.25  |
-****** 此处省略了一大波数据 ********
-| S700_3962   | 78919.06  |
-| S700_4002   | 71753.93  |
-| S72_1253    | 42692.53  |
-| S72_3212    | 47550.40  |
-+-------------+-----------+
-109 rows in set
-```
-要更详细地查看结果，可以将orderdetails表连接到products表中：
-```sql
-SELECT P.productCode,
-       P.productName,
-       SUM(priceEach * quantityOrdered) total
-FROM orderdetails O
-INNER JOIN products  P ON O.productCode = P.productCode
-GROUP by productCode
-ORDER BY total;
-SQL
-执行上面查询语句，得到以下结果 - 
-mysql> SELECT P.productCode,
-       P.productName,
-       SUM(priceEach * quantityOrdered) total
-FROM orderdetails O
-INNER JOIN products  P ON O.productCode = P.productCode
-GROUP by productCode
-ORDER BY total;
-+-------------+---------------------------------------------+-----------+
-| productCode | productName                                 | total     |
-+-------------+---------------------------------------------+-----------+
-| S24_1937    | 1939 Chevrolet Deluxe Coupe                 | 28052.94  |
-| S24_3969    | 1936 Mercedes Benz 500k Roadster            | 29763.39  |
-| S24_2972    | 1982 Lamborghini Diablo                     | 30972.87  |
-| S24_2840    | 1958 Chevy Corvette Limited Edition         | 31627.96  |
-****** 此处省略了一大波数据 ************************************************
-| S12_3891    | 1969 Ford Falcon                            | 152543.02 |
-| S12_1099    | 1968 Ford Mustang                           | 161531.48 |
-| S10_4698    | 2003 Harley-Davidson Eagle Drag Bike        | 170686.00 |
-| S10_1949    | 1952 Alpine Renault 1300                    | 190017.96 |
-| S12_1108    | 2001 Ferrari Enzo                           | 190755.86 |
-| S18_3232    | 1992 Ferrari 360 Spider red                 | 276839.98 |
-+-------------+---------------------------------------------+-----------+
-109 rows in set
-```
-
-
-# MAX()
-返回一组值中的最大值。
-```sql
-MAX(expression)
-```
-
-例如，获取products表中最昂贵的产品
-```sql
-mysql> SELECT MAX(buyPrice) highest_price FROM products;
-+---------------+
-| highest_price |
-+---------------+
-| 103.42        |
-+---------------+
-1 row in set
-```
-
-# MIN()
-返回一组值中的最小值
-```sql
-MIN(expression)
-```
-
-比如在products表查找最低价格产品：
-```sql
-mysql> SELECT MIN(buyPrice) lowest_price FROM Products;
-+--------------+
-| lowest_price |
-+--------------+
-| 15.91        |
-+--------------+
-1 row in set
-```
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\350\241\214\351\224\201\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\350\241\214\351\224\201\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
deleted file mode 100644
index 823b294b8e..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\350\241\214\351\224\201\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
+++ /dev/null
@@ -1,96 +0,0 @@
-MySQL的行锁是在引擎层由各个引擎实现。MyISAM不支持行锁，意味着并发控制只能用表锁，对于这种引擎的表，同一张表上任何时刻只能有一个更新在执行，这就会影响到业务并发度。
-InnoDB支持行锁的，这也是MyISAM被InnoDB替代的重要原因之一。
-
-行锁就是针对数据表中行记录的锁。事务A更新了一行，而这时候事务B也要更新同一行，则必须等事务A的操作完成后才能更新。
-
-# 两阶段锁
-> id是表t的主键。
-
-|事务 1| 事务 2
-|--|--|--|
-|begin <br> update t set k=k+1 where id=1;<br>  update t set k=k+1 where id=2;|
-||begin <br> update t set k=k+2 where id=1;
-|commit |
-
-事务B的update语句会被阻塞，直到事务A执行commit后，事务B才能执行。
-
-事务A持有的两个记录的行锁，都是在commit时才释放。
-**InnoDB事务中，行锁在需要时才加，但不是不需要了就立刻释放，而要等事务结束时才释放。**
-
-这就是两阶段锁协议。
-
-如果你的事务中需要锁多个行，要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
-# 案例
-顾客A要在影院B购买电影票，业务涉及操作：
-1. 从顾客A账户余额中扣除电影票价
-2. 给影院B的账户余额增加这张电影票价
-3. 记录一条交易日志
-
-要完成交易，需要update两条记录，insert一条记录。
-为保证交易的原子性，要把三操作放在一个事务。
-### 如何安排三语句在事务中的顺序？
-若同时顾客C要在影院B买票，这两事务冲突部分就是语句2。因为它们要更新同一个影院账户的余额，要修改同一行数据。
-
-两阶段锁协议下不论怎样安排语句顺序，所有操作需要的`行锁都在事务提交时才释放`。
-所以，若把2安排在最后，比如3、1、2，则影院账户余额这行锁的时间就最少。最大程度减少了事务之间的锁等待，提升并发度。
-由于你的正确设计，影院余额这一行的行锁在一个事务中不会停留很长时间。但这并未完全解决问题。
-
-影院做活动，低价预售一年内所有电影票，活动只做一天。于是活动开始时，你的MySQL就挂了。
-登上服务器一看，CPU消耗近100%，但整个数据库每秒执行不到100个事务。
-什么原因？
-
-# 死锁和死锁检测
-当并发系统不同线程出现循环资源依赖，涉及的线程都在等待别的线程释放资源，就会导致这几线程都无限等待，死锁。
-
-|事务 1| 事务 2
-|--|--|--|
-|begin <br> update t set k=k+1 where id=1;|begin;
-|| update t set k=k+1 where id=2;
-|update t set k=k+1 where id=2; |
-| |update t set k=k+1 where id=1;
-
-- 事务1在等待事务2释放`id=2`的行锁
-- 事务2在等待事务1释放`id=1`的行锁
-- 事务1和事务2在互相等待对方的资源释放，导致死锁
-
-## 死锁后的策略
-### 超时等待
-该超时时间可通过参数**innodb_lock_wait_timeout**设置。
-
-InnoDB中，**innodb_lock_wait_timeout**默认值50s，若采用策略1，当死锁后，第一个被锁住的线程要50s才超时退出，然后其他线程才可能继续。对在线服务，这等待时间无法接受！
-也不可能直接把这时间设成一个小值。这样当死锁时，确实很快解开，但若不是因为死锁，而只是正常的锁等待呢？所以，超时时间设太短，会误伤友军。
-### 主动死锁检测
-发现死锁后，主动回滚死锁链中的某一事务，让其他事务继续执行。
-将参数**innodb_deadlock_detect**设置为on，开启该逻辑。
-
-正常情况采用该策略，而且**innodb_deadlock_detect**默认值就是on。
-主动死锁检测在发生死锁时，能够快速发现并处理，但有额外负担：
-每当一个事务被锁，就看看它所依赖的线程是否被别人锁住。如此循环，最后判断是否出现循环等待，即死锁。
-
-- 若是说到的所有事务都更新同一行数据？
-每个新来的被堵住的线程，都要判断会不会由于自己的加入导致了死锁，时间复杂度O(n)。若有1000个并发线程要同时更新同一行，则死锁检测操作就是100万量级。虽然最终检测结果是没有死锁，但这期间要消耗大量CPU。因此，你就会看到CPU占用率很高，但是每秒却执行不了几个事务。
-
-## 优化热点行更新
-### 死锁检测关掉
-问题在于，死锁检测耗费大量CPU资源。若你能确保该业务一定不会出现死锁，可以临时把死锁检测关掉。但这操作有一定风险，因为业务设计时一般不会把死锁当做一个严重错误：
-- 毕竟出现死锁，就回滚，然后通过业务重试一般就没问题，业务无损
-- 而关掉死锁检测意味着可能会出现大量超时，业务有损
-
-### 控制并发度
-如果并发能够控制住，比如同一行同时最多10个线程更新，那么死锁检测成本低了，就不会出现这问题。
-一个直接的想法，在客户端做并发控制。但很快发现这不太可行，因为客户端很多的！！！
-因此并发控制要做在数据库服务端。若有中间件，可考虑在中间件实现。若团队有能修改MySQL源码的人，也可做在MySQL。
-
-####  基本思路
-对于同行更新，在进入引擎之前排队。这样在InnoDB内部就不会有大量死锁检测工作。
-
-若团队没有DB专家，不能实现这样方案，能否做设计优化？
-### 分段锁
-可以考虑将一行改成逻辑上的多行，以减少锁冲突。
-
-以影院账户为例，可考虑放在多条记录，比如10个记录，影院的账户总额等于这10个记录值总和。这样每次给影院账户加金额时，随机选其中一条记录加。这样每次冲突概率变成原来1/10，减少锁等待个数，也就减少了死锁检测的CPU消耗。
-
-这方案看上去无损，但这类方案需根据业务逻辑做详细设计。若账户余额可能减少，比如退票操作，则此时就需要考虑当一部分行记录变成0时，代码要有特殊处理。
-
-但调整语句顺序并不能完全避免死锁。所以以上方案都只能减少死锁对数据库影响。
-减少死锁的主要方向也就是控制访问相同资源的并发事务量。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\345\217\212\344\270\273\344\273\216\345\220\214\346\255\245\345\273\266\346\227\266\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\345\217\212\344\270\273\344\273\216\345\220\214\346\255\245\345\273\266\346\227\266\350\247\243\345\206\263\346\226\271\346\241\210.md"
new file mode 100644
index 0000000000..3c92b96e3d
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\350\257\273\345\206\231\345\210\206\347\246\273\345\217\212\344\270\273\344\273\216\345\220\214\346\255\245\345\273\266\346\227\266\350\247\243\345\206\263\346\226\271\346\241\210.md"
@@ -0,0 +1,48 @@
+读写分离的实现是基于主从复制架构：一主多从，只写主库，主库会自动将数据同步到从库。
+
+# 1 为什么要读写分离？
+主库将变更写binlog日志，然后从库连接到主库后，从库有个I/O线程，将主库的binlog日志拷贝到本地，写入一个中继日志。
+
+接着从库中有一个SQL线程会从中继日志读取binlog，然后执行binlog日志中的内容。即在本地再次执行一遍SQL，确保跟主库的数据相同。
+
+# 2 MySQL主从复制原理
+MySQL的每个增删改的会改变数据的操作，除了更新数据外，对这个增删改操作还会写入一个日志文件，记录这个操作的日志，即binlog。
+
+mysql 5.7新版本的并行复制，多个SQL线程，每个线程从relay日志里读一个库的
+日志，重放。
+
+从库同步主库数据的过程是**串行化**的，即主库上并行的操作，在从库上会串行执行。
+由于从库从主库拷贝日志以及串行执行SQL的特点，在高并发下就有延时，从库数据一定比主库慢，所以经常出现，刚写入主库的数据读不到，要过几十甚至几百ms才能读到。
+
+从库串行化过程：
+1. 读取binlog日志
+2. 写relay日志、应用日志变更到自己本地数据
+
+从库的IO线程，读取主库的binlog日志时，老版本是单线程；
+5.6.x之后的新版本支持多线程读取
+
+另外的问题，若主库突然宕机，恰好数据还没同步到从库，那么有些数据可能在从库上是没有的，可能就这么丢失了。
+
+所以MySQL实际上在这有两个机制
+# 3 半同步复制
+semi-sync，解决主库数据丢失问题。
+
+1. 主库写入binlog日志后，会强制立即将数据同步到从库
+2. 从库将日志写入自己的relay log后，会返回ack给主库
+3. 主库接收到至少一个从库的ack后才会认为写操作完成
+
+## 4 并行复制
+解决主从同步延时问题。
+
+从库开启多线程，并行读取relay log中不同库的日志，然后**并行重放不同库的日志**，这是**库级别的并行**。
+
+# 5 MySQL主从同步适用场景
+
+建议一般在读远远多于写，且读的时候一般对数据时效性要求没那么高的时候采用。所以我们可以考虑的就是，你可以用MySQL的并行复制，但问题是那是库级别的并行，所以有时候作用不是很大。
+此时,通常来说，我们会对于那种写后立马就要保证可以查到的场景，采用强制读主库的方式，确保你肯定可以读到数据。其实用一些数据库中间件也是没问题的。
+
+一般若主从延迟较为严重：
+1. 分库 : 将一个主库拆分，每个主库的写并发就降低了，主从延迟即可忽略不计
+2. 打开MySQL支持的并行复制，多个库并行复制，若某个库的写入并发特别高，写并发达到了2000/s，并行复制还是没意义。二八法则，很多时候比如说，就是少数的几个订单表，写入了2000/s，其他几十个表10/s
+3. 重构代码 : 重构代码，插入数据后，直接更新，不查询
+4. 若确实存在必须先插入，立马要求查询，然后立马就反过来执行一些操作，对这个查询设置**直连主库**(不推荐，这么搞导致读写分离的意义丧失)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\351\224\201.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\351\224\201.md"
deleted file mode 100644
index 95f52d2483..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\351\224\201.md"
+++ /dev/null
@@ -1,391 +0,0 @@
-# 1 MySQL锁
-## 1.1 表锁
-- 开销小，加锁快
-- 不会出现死锁
-- 锁定粒度大，发生锁冲突的概率最高，并发度最低
-
-## 1.2 行锁
-- 开销大，加锁慢
-- 会出现死锁
-- 锁定粒度小，发生锁冲突的概率最低，并发度最高
-
-## 1.3 页锁
-- 开销和加锁时间介于表锁和行锁之间
-- 会出现死锁
-- 锁定粒度介于表锁和行锁之间，并发度一般
-
-## 1.4 引擎与锁
-- ` MyISAM`和MEMORY`支持表锁`
-- BDB支持页锁,也支持表锁
-- `Innodb`既支持行锁，也支持表锁，`默认行锁`
-
-
-## 1.5 查询表锁争用情况
-检查`table_locks_waited`和`table_locks_immediate`状态变量分析
-- table_locks_immediate : 可以立即获取锁的次数
-- table_locks_waited : 不能立即获取锁，需要等待锁的次数
-
-![image](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMzY5NmUyN2ZmYTk3YjBkZg)
-
-![image](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNDZiZDg5NzI2MmNmZWExZQ)
-
-> table_locks_waited 的值越高，则说明存在严重的表级锁的争用情况
-
-
-# 2 MyISAM的表锁
-ＭySQL的表锁有两种模式
-- 表共享读锁（Table Read Lock）
-- 表独占写锁（Table Write Lock）
-
-## 2.1 表锁兼容性
-锁模式的兼容：
-| 是否兼容 | 请求none | 请求读锁 | 请求写锁 |
-| --- | --- | --- | --- |
-| 当前处于读锁 | 是 | 是 | 否 |
-| 当前处于写锁 | 是 | 否 | 否 |
-
-> ＭyISAM表的读操作不会阻塞其他用户对同一表的读请求，但会阻塞对同一表的写请求。
-ＭyISAM表的写操作会阻塞其他用户对同一表的读和写请求。
-ＭyISAM表的读和写操作之间，以及写和写操作之间是串行的。即当某一线程获得对一个表的写锁后，只有持有锁的线程可对表进行更新，其他线程的读、写操作都会阻塞，直到锁被释放为止。
-
-## 2.2 如何加表锁
-对于 **MyISAM** 引擎
-- 执行`select`前，会自动给涉及的所有表加 **读**
-- 执行更新（update,delete,insert)会自动给涉及到的表加 **写**
-
-无需直接显式用`lock table`命令。
-
-对于给MyISAM显式加锁，**一般是为了模拟事务操作，实现对某一个时间点多个表一致性读取**
-
-### 2.2.1  实例
-- 订单表 **orders**
-记录各订单的总金额`total`
-
-- 订单明细表 **order_detail**
-记录各订单每一产品的金额小计`subtotal`
-
-假设需检查这两个表的金额合计是否相符，可能执行如下SQL
-
-```sql
-Select sum(total) from orders;
-Select sum(subtotal) from order_detail;
-```
-
-如果不先给这两个表加锁，就可能产生错误的结果：因为第一条语句执行过程中，`order_detail`表可能已发生改变：
-因此,正确写法应该如下
-
-```sql
-Lock tables orders read local, order_detail read local;
-Select sum(total) from orders;
-Select sum(subtotal) from order_detail;
-Unlock tables;
-```
-
-在`Lock tables`时加的 **local** 选项，以满足MyISAM表并发插入时，允许其他用户在表尾插入记录。
-
-在用`Lock tables`给表显式加表锁时，必须同时取得所有涉及表的锁，并且MySQL不支持锁升级：即在执行`Lock tables`后，只能访问显式加锁的这些表，不能访问未加锁的表；
-同时，如果加的是读锁，那么只能执行查询，而不能执行更新。在自动加锁情况下也这样，MySQL会一次获得SQL语句所需要的全部锁，这也正是MyISAM表不会死锁的原因。
-
-## 2.3  tips
-当使用`lock tables`时,不仅需要一次锁定用到的所有表
-且同一表在SQL语句中出现多少次,就要通过与SQL语句中别名锁多少次
-```
-lock table actor read
-```
-会提示错误
-```
-select a.first_name.....
-```
-需要对别名分别锁定
-```
-lock table actor as a read,actor as b read;
-```
-
-# 3 MyISAM的并发锁
-在一定条件下，`MyISAM`也支持并发插入和读取
-
-## 3.1 系统变量 : concurrent_insert
-控制其并发插入的行为,其值分别可以为
-*   0 不允许并发插入,所有插入对表加互斥锁
-*   1 只要表中无空洞,就允许并发插入.
-MyISAM允许在一个读表的同时，另一个进程从表尾插入记录(MySQL的默认设置)
-*   2 无论MyISAM表中有无空洞,都强制在表尾并发插入记录
-若无读线程,新行插入空洞中
-
-可以利用`MyISAM`的并发插入特性，来解决应用中对同表查询和插入的锁争用
-例如,将`concurrent_insert`系统变量设为2，总是允许并发插入;
-同时,通过定期在系统空闲时段执行OPTIONMIZE TABLE语句来整理空间碎片，收到因删除记录而产生的中间空洞
-
-`删除操作`不会重整整个表,只是把 行 标记为删除,在表中留下`空洞`
-MyISAM倾向于在可能时填满这些空洞,插入时就会重用这些空间,无空洞则把新行插到表尾
-
-## 3.2  MyISAM的锁调度
-`MyISAM`的读和写锁互斥,读操作串行的
-
-- 一个进程请求某个`MyISAM`表的读锁,同时另一个进程也请求同表的写锁，MySQL如何处理呢？
-写进程先获得锁!!! 
-不仅如此,即使读进程先请求先到锁等待队列,写请求后到,写锁也会插到读请求之前!!!
-
-这是因为MySQL认为`写请求一般比读请求重要`
-这也正是`MyISAM`表`不适合有大量更新 / 查询`操作应用的原因
-大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞
-
-幸好,我们可以通过一些设置来调节`MyISAM`的调度行为
-*   指定启动参数`low-priority-updates`
-使MyISAM引擎默认给予读请求以优先权利
-*   执行命令`SET LOW_PRIORITY_UPDATES=1`
-使该连接发出的更新请求优先级降低
-*   指定INSERT、UPDATE、DELETE语句的`LOW_PRIORITY`属性
-降低该语句的优先级
-
-虽然上面3种方法都是要么更新优先，要么查询优先，但还是可以用其来解决查询相对重要的应用（如用户登录系统）中，读锁等待严重的问题
-
-另外，MySQL也提供了一种折中的办法来调节读写冲突;
-即给系统参数`max_write_lock_count`设置一个合适的值;
-当一个表的读锁达到这个值后，MySQL便暂时将写请求的优先级降低，给读进程一定获得锁的机会
-
-# 4 InnoDB锁
-InnoDB与MyISAM的最大不同有两点
-- 支持事务
-- 采用行锁
-
-行级锁和表级锁本来就有许多不同之处，另外，事务的引入也带来了一些问题。
-
-### 查看Innodb行锁争用情况
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTI3YzQzMDJiMzllYjY5MDQucG5n)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTNiMjVjZmM1ZWVhNzg0Y2MucG5n)
->如果发现争用比较严重，如`Innodb_row_lock_waits`和`Innodb_row_lock_time_avg`的值比较高
-
-### 查询information_schema相关表来查看锁情况
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTgwNmRkNGMwZjI3NGQ3MmEucG5n)
-
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LThlYjgyYmU2NThkYzQwMTkucG5n)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTJlYzI1MGVmNjJmNDhkYWYucG5n)
-
-### 设置Innodb monitors
-进一步观察发生锁冲突的表，数据行等，并分析锁争用的原因
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWM4OTk1MDM0ODQ5MzNiNTcucG5n)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTAwMTA3MGU1NDIwNWM4ZTEucG5n)
-### 停止监视器
-![drop table innodb_monitor](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTQxYzVlMzc2MmE3MWU1YWQucG5n)
-
-默认情况每15秒会向日志中记录监控的内容;
-如果长时间打开会导致.err文件变得非常巨大;
-所以确认原因后,要删除监控表关闭监视器,或者通过使用--console选项来启动服务器以关闭写日志功能
-
-## 4.4  InnoDB的行锁
-InnoDB支持以下两种类型的`行锁`
-- 共享锁（读锁S）
-若事务 T 对数据对象 A 加了 S 锁;
-则事务 T 可以读 A 但不能修改 A;
-其它事务只能再对它加 S 锁,而不能加 X 锁,直到 T 释放 A 上的 S 锁;
-这保证了,其他事务可以读 A，但在事务 T 释放 S 锁之前，不能对 A 做任何修改操作.
-- 排他锁（写锁X）
-若事务 T 对数据对象A加 X 锁;
-事务 T 可以读 A 也可以修改 A;
-其他事务不能对 A 加任何锁,直到 T 释放 A 上的锁;
-这保证了,其他事务在 T 释放 A 上的锁之前不能再读取和修改 A .
-
->MySQL InnoDB默认行级锁
-行级锁都是基于索引的,若一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住
-
-
-为了允许行/表锁共存，实现多粒度锁，InnoDB还有两种内部使用的：
-### 意向锁（Intention Locks）
-这两种意向锁都是表级锁：
-- 意向共享锁（IS）
-事务打算给数据行共享锁，事务在给一个数据行加共享锁前必须先取得该表的IS锁
-- 意向排他锁（IX）
-事务打算给数据行加排他锁，事务在给一个数据行加排他锁前必须先取得该表的IX锁
-
-| 当前锁/是否兼容/请求锁 | X | IX | S | IS |
-| --- | --- | --- | --- | --- |
-| X | 冲突 | 冲突 | 冲突 | 冲突 |
-| IX | 冲突 | 兼容 | 冲突 | 兼容 |
-| S | 冲突 | 冲突 | 兼容 | 兼容 |
-| IS | 冲突 | 兼容 | 兼容 | 兼容 |
-
-如果一个事务请求的锁模式与当前锁兼容，InnoDB就请求的锁授予该事务，反之如果两者不兼容，该事务就要等待锁释放
-
-意向锁是InnoDB自动加的，不需用户干预.
-对于UPDATE、DELETE和INSERT语句，InnoDB会自动给涉及及数据集加排他锁（Ｘ）;
-对于普通SELECT语句，InnoDB不会加任何锁.
-
-
-对于SELECT语句,可以通过以下语句显式地给记录加读/写锁
-- 共享锁（Ｓ）
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWE4Mjg0M2FiNTQ5MWM2YzgucG5n)
-
-*   排他锁（X）
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTcyMjMzNzUyNTU5M2FhYWYucG5n)
-共享锁语句主要用在需要数据依存关系时确认某行记录是否存在;
-并确保没有人对这个记录UPDATE或DELETE.
-但如果当前事务也需要对该记录进行更新,则很有可能造成死锁;
-对于锁定行记录后需要进行更新操作的应用,应该使用排他锁语句.
-
-此外还有自增锁（auto-in）和 lock tables/DDL等表级锁
-查看锁：
-```sql
-SHOW ENGINE INNODB STATUS;
-```
-## 4.5 实例
-### 4.5.1  Innodb共享锁
-| session_1 | session_2 |
-| --- | --- |
-| set autocommit=0,select * from actor where id =1 | set autocommit=0,select * from actor where id =1 |
-| 当前seesion对id为1的记录加入共享锁 select * from actor where id =1 lock in share mode |  |
-|  | 其他seesion仍然可以查询，并对该记录加入 select * from actor where id =1 lock in share mode |
-| 当前session对锁定的记录进行更新，等待锁 update。。。where id=1 |  |
-|  | 当前session对锁定记录进行更新，则会导致死锁退出 update。。。where id=1 |
-| 获得锁，更新成功 |
-
-### 4.5.2  Innodb排他锁
-| session_1 | session_2 |
-| --- | --- |
-| set autocommit=0,select * from actor where id =1 | set autocommit=0,select * from actor where id =1 |
-| 当前seesion对id为1的记录加入for update 共享锁 select * from actor where id =1 for update |  |
-|  | 可查询该记录select *from actor where id =1,但是不能再记录共享锁，会等待获得锁select *from actor where id =1 for update |
-| 更新后释放锁 update。。。 commit |  |
-|  | 其他session，获得锁，得到其他seesion提交的记录 |
-
-## 4.6 行锁的实现
-行锁是通过给索引上的索引项加锁来实现
-如果没有索引，InnoDB将通过隐藏的聚簇索引来对记录加锁
-*   Record Locks:对索引项加锁
-*   Gap lock:对索引项之的“间隙“，第一条记录前的”间隙“，或最后一条记录后的”间隙“，加锁
-*   Next-key lock：前两种的组合，对记录及其前面的间隙加锁
-
-行锁实现特点意味着：
-`如果不通过索引条件检索数据,那么Innodb将对表的所有记录加锁，和表锁一样`
-
-### 间隙锁（Next-Key锁）
-当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;
-对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁（Next-Key锁）.
-
-举例来说，假如emp表中只有101条记录，其empid的值分别是1,2,...,100,101，下面的SQL：
-InnoDB 不仅会对符合条件的 empid 值为 101 的记录加锁;
-也会对 `empid`大于`101`（这些记录并不存在）的“间隙”加锁
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWNiNDY5YzhiNWNlN2VmZGMucG5n)
-#### 作用
-- 防止幻读,以满足相关隔离级别的要求
-对于上例,若不使用间隙锁,如果其他事务插入 empid 大于 100 的任何记录,;
-那么本事务如果再次执行上述语句,就会发生幻读
-- 满足其恢复和复制的需要
-在使用范围条件检索并锁定记录时;
-InnoDB 这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待;
-因此,在实际开发中,尤其是并发插入较多的应用;
-我们要尽量优化业务逻辑,尽量使用`相等条件来访问更新数据`,避免使用范围条件.
-
-## 4.7 when 使用表锁
-对于InnoDB,在绝大部分情况下都应该使用行锁
-因为`事务`,`行锁`往往是我们选择InnoDB的理由
-
-但在个别特殊事务中,也可以考虑`使用表锁`
-* 事务需要更新大部分数据，表又较大
-若使用默认的行锁,不仅该事务执行效率低(因为需要对较多行加锁,加锁是需要耗时的);
-而且可能造成其他事务长时间锁等待和锁冲突;
-这种情况下可以考虑使用表锁来提高该事务的执行速度
-* 事务涉及多个表，较复杂，很可能引起死锁，造成大量事务回滚
-这种情况也可以考虑一次性锁定事务涉及的表，从而避免死锁、减少数据库因事务回滚带来的开销
-
-当然，应用中这两种事务不能太多，否则，就应该考虑使用`ＭyISAＭ`
-
- 在InnoDB下 ,使用表锁要注意
-- 使用`LOCK TALBES`虽然可以给`InnoDB`加表锁
-表锁不是由`InnoDB`引擎层管理的,而是由其上一层ＭySQL Server负责;
-仅当`autocommit=0、innodb_table_lock=1（默认设置）`,InnoDB 引擎层才知道MySQL加的表锁,ＭySQL Server才能感知InnoDB加的行锁;
-这种情况下，InnoDB才能自动识别涉及表锁的死锁
-否则，InnoDB将无法自动检测并处理这种死锁
-- 在用`LOCK TALBES`对`InnoDB`锁时要注意,要将`autocommit`设为0，否则ＭySQL不会给表加锁
-事务结束前,不要用`UNLOCK TALBES`释放表锁,因为它会隐式地提交事务
-COMMIT或ROLLBACK不能释放用`LOCK TALBES`加的表锁，必须用UNLOCK TABLES释放表锁，正确的方式见如下语句
-- 需要写表t1并从表t读
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTk2NDgyZmY2ODlkNzc1NzUucG5n)
-# 5 死锁
-**ＭyISAM表锁是deadlock free的，这是因为ＭyISAM总是一次性获得所需的全部锁，要么全部满足，要么等待，因此不会出现死锁**
-
-但在InnoDB中，除单个SQL组成的事务外，锁是逐步获得的，这就决定了InnoDB发生死锁是可能的
-
-发生死锁后，InnoDB一般都能自动检测到，并使一个事务释放锁并退回，另一个事务获得锁，继续完成事务
-
-- 但在涉及外部锁，或涉及锁的情况下，InnoDB并不能完全自动检测到死锁
-**这需要通过设置锁等待超时参数innodb_lock_wait_timeout来解决**
-需要说明的是，这个参数并不是只用来解决死锁问题，在并发访问比较高的情况下，如果大量事务因无法立即获取所需的锁而挂起，会占用大量计算机资源，造成严重性能问题，甚至拖垮数据库
-我们通过设置合适的锁等待超时阈值，可以避免这种情况发生。
-
-通常来说，死锁都是应用设计的问题，通过调整业务流程、数据库对象设计、事务大小、以及访问数据库的SQL语句，绝大部分都可以避免
-
-下面就通过实例来介绍几种死锁的常用方法。
-- 应用中，不同的程序会并发存取多个表
-尽量约定以`相同的顺序`访问表
-- 程序批处理数据时
-`事先对数据排序`,保证每个线程按固定的顺序来处理记录
-- 在事务中,要更新记录
-应`直接申请排他锁,而不应该先申请共享锁`
-- 在`可重复读`下,如果两个线程同时对相同条件记录用`SELECT...ROR UPDATE`加排他写锁
-在没有符合该记录情况下，两个线程都会加锁成功
-程序发现记录尚不存在，就试图插入一条新记录，如果两个线程都这么做，就会出现死锁
-这种情况下，将隔离级别改成READ COMMITTED，就可以避免问题
-- 当隔离级别为READ COMMITED时，如果两个线程都先执行`SELECT...FOR UPDATE`
-判断是否存在符合条件的记录，没有 -> 插入记录;
-此时，只有一个线程能插入成功，另一个线程会出现锁等待.
-当第１个线程提交后，第２个线程会因主键重出错，但虽然这个线程出错了，却会获得一个排他锁！这时如果有第３个线程又来申请排他锁，也会出现死锁.
-对于这种情况，可以直接做插入操作，然后再捕获主键重异常，或者在遇到主键重错误时，总是执行ROLLBACK释放获得的排他锁
-
-**如果出现死锁，可以用SHOW INNODB STATUS命令来确定最后一个死锁产生的原因和改进措施。**
-
-# 6 总结
-## 6.1 **MyISAM**的表锁
-- 共享读锁之间是兼容的,但`共享读锁和排他写锁`之间,以及`排他写锁之间`互斥,即读写串行
-- 在一定条件下,`ＭyISAM`允许查询/插入并发,可利用这一点来解决应用中对同一表查询/插入的锁争用问题
-- `ＭyISAM`默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置`LOW_PRIPORITY_UPDATES`参数或在INSERT、UPDATE、DELETE语句中指定`LOW_PRIORITY`选项来调节读写锁的争用
-- 由于表锁的锁定粒度大,读写又是串行的,因此如果更新操作较多,`ＭyISAM`表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突
-
-## 6.2 对于**InnoDB表**
-- 行锁基于索引实现
-如果不通过索引访问数据,InnoDB会使用表锁
-- 间隙锁机制及使用间隙锁的原因
-- 不同的隔离级别下，InnoDB的锁机制和一致性读策略不同
-- ＭySQL的恢复和复制对InnoDB锁机制和一致性读策略也有较大影响
-- 锁冲突甚至死锁很难完全避免
-
-# 7 索引与锁
-在了解InnoDB的锁特性后，用户可以通过设计和SQL调整等措施减少锁冲突和死锁
-*   尽量使用较低的隔离级别
-*   精心设计索引，并尽量使用索引访问数据，使加锁更精确，从而减少锁冲突的机会。
-
-
-利用索引优化锁
-- 索引可以减少锁定的行数
-- 索引可以加快处理速度,同时也加快了锁的释放
-
-
-*   选择合理的事务大小，小事务发生锁冲突的几率也更小
-*   给记录集显式加锁时，最好一次性请求足够级别的锁。比如要修改数据的话，最好直接申请排他锁，而不是先申请共享锁，修改时再请求排他锁，这样容易产生死锁。
-*   不同的程序访问一组表时，应尽量约定以相同的顺序访问各表，对一个表而言，尽可能以固定的顺序存取表中的行。这样可以大减少死锁的机会。
-*   尽量用相等条件访问数据，这样可以避免间隙锁对并发插入的影响。
-*   不要申请超过实际需要的锁级别；除非必须，查询时不要显示加锁。
-*   对于一些特定的事务，可以使用表锁来提高处理速度或减少死锁的可能
-
-## 索引的维护和优化
-删除重复和冗余的索引
-primary key(id) ,unique key (id) ，index(id)
-主键索引、唯一索引、单列索引
-
-- 注意加粗的联合索引
-Index(a),**index(a,b)**
-primary key(id),**index(a,id)**
-
-
-- 删除重复和冗余的索引
-![](https://img-blog.csdnimg.cn/20210219154350316.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-查找未被使用过的索引
-![](https://img-blog.csdnimg.cn/20210219154317840.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 更新索引统计信息及减少索引碎片
-analyze table    table_ name
-optimize table table_ name
-使用不当会导致锁表
-
-> 参考
-> - [MySQL中的锁（表锁、行锁）](https://www.cnblogs.com/chenqionghe/p/4845693.html)
-> - https://developer.aliyun.com/article/3780
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\351\224\201\346\234\272\345\210\266.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\351\224\201\346\234\272\345\210\266.md"
new file mode 100644
index 0000000000..8146a87a26
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\351\224\201\346\234\272\345\210\266.md"
@@ -0,0 +1,526 @@
+# 1 MySQL的三种锁
+
+## 1.1 表锁
+
+- 开销小，加锁快
+- 不会出现死锁
+- 锁定粒度大，发生锁冲突的概率最高，并发度最低
+
+## 1.2 行锁
+
+- 开销大，加锁慢
+- 会出现死锁
+- 锁定粒度小，发生锁冲突的概率最低，并发度最高
+
+## 1.3 页锁
+
+- 开销和加锁时间介于表锁和行锁之间
+- 会出现死锁
+- 锁定粒度介于表锁和行锁之间，并发度一般
+
+## 1.4 引擎与锁
+
+- `MyISAM`和MEMORY`支持表锁`
+- BDB支持页锁,也支持表锁
+- `Innodb`既支持行锁,也支持表锁,`默认行锁`
+
+## 1.5 查询表锁争用情况
+
+检查`table_locks_waited`和`table_locks_immediate`状态变量分析
+
+- table\_locks\_immediate : 可以立即获取锁的次数
+- table\_locks\_waited : 不能立即获取锁，需要等待锁的次数
+
+![image](https://ask.qcloudimg.com/http-save/1752328/lkgm9mwgkx.png)
+
+![image](https://ask.qcloudimg.com/http-save/1752328/akzru4lezm.png)
+
+> table\_locks\_waited 的值越高，则说明存在严重的表级锁的争用情况
+
+# 2 表锁模式（MyISAM)
+
+ＭySQL的表锁有两种模式
+
+- 表共享读锁（Table Read Lock）
+- 表独占写锁（Table Write Lock）
+
+## 2.1 表锁兼容性
+
+锁模式的兼容如下表
+
+| 是否兼容 | 请求none | 请求读锁 | 请求写锁 |
+|:----|:----|:----|:----|
+| 当前处于读锁 | 是 | 是 | 否 |
+| 当前处于写锁 | 是 | 否 | 否 |
+
+
+> 可见，对ＭyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;
+
+对ＭyISAM表的写操作,则会阻塞其他用户对同一表的读和写请求;
+
+ＭyISAM表的读和写操作之间,以及写和写操作之间是串行的!(当某一线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作.其他线程的读、写操作都会等待,直到锁被释放为止)
+
+## 2.2 如何加表锁
+
+对于 **MyISAM** 引擎
+
+- 执行`select`前,会自动给涉及的所有表加 **读**
+- 执行更新（update,delete,insert)会自动给涉及到的表加 **写**
+
+不需要用户直接显式用`lock table`命令
+
+对于给MyISAM显式加锁，**一般是为了在一定程度上模拟事务操作，实现对某一个时间点多个表一致性读取**
+
+### 2.2.1  实例
+
+- 订单表 -  **orders**
+记录各订单的总金额`total`
+- 订单明细表 - **order\_detail**
+记录各订单每一产品的金额小计`subtotal`
+
+假设我们需要检查这两个表的金额合计是否相符,可能就需要执行如下两条SQL
+
+图片上传失败...(image-3017e3-1547370332969)
+
+这时,如果不先给这两个表加锁,就可能产生错误的结果;
+
+因为第一条语句执行过程中,`order_detail`表可能已经发生了改变.
+
+因此,正确写法应该如下
+
+图片上传失败...(image-8081d7-1547370332969)
+
+### 2.2.2  注意点
+
+- 上面的例子在LOCK TABLES时加了‘local’选项，其作用就是在满足MyISAM表并发插入条件的情况下，允许其他用户在表尾插入记录
+- 在用LOCK TABLES给表显式加表锁时，必须同时取得所有涉及表的锁，并且MySQL支持锁升级;
+也就是说，在执行LOCK TABLES后，只能访问显式加锁的这些表，不能访问未加锁的表；
+同时，如果加的是读锁，那么只能执行查询操作，而不能执行更新操作
+其实，在自动加锁的情况下也基本如此，MySQL会一次获得SQL语句所需要的全部锁.这也正是MyISAM表不会出现死锁（Deadlock Free）的原因
+
+| session1 | session2 |
+|:----|:----|
+| 获得表 `film_text` 的读锁 lock table film\_text read; | ​ |
+| 可select \* from film\_text | 可select \* from film\_text |
+| 不能查询没有锁定的表 :select \* from film | 可查询/更新未锁定的表: select \* from film |
+| 插入或更新锁定表会提示错误 update...from film\_text | 更新锁定表会等待 update...from film\_text |
+| 释放锁 unlock tables | 等待 |
+| ​ | 获得锁，更新成功 |
+
+
+##2.3  tips
+
+当使用`lock tables`时,不仅需要一次锁定用到的所有表
+
+且同一表在SQL语句中出现多少次,就要通过与SQL语句中别名锁多少次
+
+```
+lock table actor read
+```
+
+会提示错误
+
+```
+select a.first_name.....
+```
+
+需要对别名分别锁定
+
+```
+lock table actor as a read,actor as b read;
+```
+
+# 3 MyISAM的并发锁
+
+在一定条件下，`MyISAM`也支持并发插入和读取
+
+## 3.1 系统变量 : concurrent\_insert
+
+控制其并发插入的行为,其值分别可以为
+
+- 0 不允许并发插入,所有插入对表加互斥锁
+- 1 只要表中无空洞,就允许并发插入.
+MyISAM允许在一个读表的同时，另一个进程从表尾插入记录(MySQL的默认设置)
+- 2 无论MyISAM表中有无空洞,都强制在表尾并发插入记录
+若无读线程,新行插入空洞中
+
+可以利用`MyISAM`的并发插入特性，来解决应用中对同表查询和插入的锁争用
+
+例如,将`concurrent_insert`系统变量设为2，总是允许并发插入;
+
+同时,通过定期在系统空闲时段执行OPTIONMIZE TABLE语句来整理空间碎片，收到因删除记录而产生的中间空洞
+
+`删除操作`不会重整整个表,只是把 行 标记为删除,在表中留下`空洞`
+
+MyISAM倾向于在可能时填满这些空洞,插入时就会重用这些空间,无空洞则把新行插到表尾
+
+## 3.2  MyISAM的锁调度
+
+`MyISAM`的读和写锁互斥,读操作串行的
+
+- 一个进程请求某个`MyISAM`表的读锁,同时另一个进程也请求同表的写锁，MySQL如何处理呢？
+写进程先获得锁!!! 
+不仅如此,即使读进程先请求先到锁等待队列,写请求后到,写锁也会插到读请求之前!!!
+
+这是因为MySQL认为`写请求一般比读请求重要`
+
+这也正是`MyISAM`表`不适合有大量更新 / 查询`操作应用的原因
+
+大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞
+
+幸好,我们可以通过一些设置来调节`MyISAM`的调度行为
+
+- 指定启动参数`low-priority-updates`
+使MyISAM引擎默认给予读请求以优先权利
+- 执行命令`SET LOW_PRIORITY_UPDATES=1`
+使该连接发出的更新请求优先级降低
+- 指定INSERT、UPDATE、DELETE语句的`LOW_PRIORITY`属性
+降低该语句的优先级
+
+虽然上面3种方法都是要么更新优先，要么查询优先，但还是可以用其来解决查询相对重要的应用（如用户登录系统）中，读锁等待严重的问题
+
+另外，MySQL也提供了一种折中的办法来调节读写冲突;
+
+即给系统参数`max_write_lock_count`设置一个合适的值;
+
+当一个表的读锁达到这个值后，MySQL便暂时将写请求的优先级降低，给读进程一定获得锁的机会
+
+\* \* \*
+
+# 4 InnoDB锁
+
+InnoDB与MyISAM的最大不同有两点
+
+- 支持事务
+- 采用行锁
+
+行级锁和表级锁本来就有许多不同之处，另外，事务的引入也带来了一些新问题
+
+## 4.1 事务
+
+一组SQL语句组成的逻辑处理单元
+
+- 原子性(Actomicity)
+事务是一个原子操作单元，其对数据的修改，要么全都执行，要么全都不执行
+- 一致性（Consistent）
+在事务开始和完成时，数据都必须保持一致状态
+这意味着所有相关的数据规则都必须应用于事务的修改，以保持完整性
+事务结束时，所有的内部数据结构（如B树索引或双向链表）也都必须是正确的
+- 隔离性（Isolation）
+一个事务所做的修改在最终提交前对其他事务不可见
+- 持久性（Durability）
+一旦事务提交，它对于数据的修改会持久化到DB
+
+## 4.2  事务的问题
+
+相对于串行处理来说，并发事务处理能大大增加数据库资源的利用率，提高数据库系统的事务吞吐量，从而可以支持可以支持更多的用户
+
+但并发事务处理也会带来一些问题，主要包括以下几种情况
+
+- **更新丢失（Lost Update）**
+当多个事务选择同一行,然后基于最初选定值更新该行时，由于事务隔离性，最后的更新覆盖了其他事务所做的更新.
+例如，两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本，然后保存更改后的副本，这样就覆盖了原始文档。最后保存其更改保存其更改副本的编辑人员覆盖另一个编辑人员所做的修改;
+如果在一个编辑人员完成并提交事务之前，另一个编辑人员不能访问同一文件，则可避免此问题
+- **脏读（Dirty Reads）**
+一个事务正在对一条记录做修改，在该事务提交前，这条记录的数据就处于不一致状态
+这时，另一个事务也来读取同一条记录，读取了这些未提交的数据
+- **不可重复读（Non-Repeatable Reads）**
+一个事务在读取某些数据已经发生了改变、或某些记录已经被删除
+- **幻读（Phantom Reads)**
+一个事务按相同的查询条件重新读取以前检索过的数据，却发现其他事务插入了满足其查询条件的新数据
+
+## 4.3  事务隔离级别
+
+在并发事务的问题中，“更新丢失”通常应该是完全避免的;
+
+但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此，`防止更新丢失应该是应用的责任`
+
+“脏读”、“不可重复读”和“幻读”,其实都是数据库`读一致性`问题,必须由数据库提供一定的事务隔离机制来解决
+
+数据库实现事务隔离的方式，基本可以分为以下两种
+
+- 在读取数据前,对其`加锁`,防止其他事务对数据进行修改
+- `不加任何锁`,通过一定机制生成一个`数据请求时间点的一致性数据快照`,并用这个快照来提供一定级别(语句级或事务级)的一致性读取.
+从用户的角度,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做**数据多版本并发控制**（ＭultiVersion Concurrency Control,MVCC），也经常称为**多版本数据库**
+
+数据库的事务隔离级别越严格,并发副作用越小,但付出的代价也越大
+
+因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”矛盾
+
+为了解决“隔离”与“并发”的矛盾，ANSI SQL定义了４种隔离级别
+
+| 隔离级别/读数据一致性及允许的并发副作用 | 读数据一致性 | 脏读 | 不可重复读 | 幻读 |
+|:----|:----|:----|:----|:----|
+| 未提交读（Read uncommitted） | 最低级别，只能保证不读取物理上损坏的数据 | 是 | 是 | 是 |
+| 已提交度（Read committed） | 语句级 | 否 | 是 | 是 |
+| 可重复读（Repeatable read） | 事务级 | 否 | 否 | 是 |
+| 可序列化（Serializable） | 最高级别，事务级 | 否 | 否 | 否 |
+
+
+### 查看Innodb行锁争用情况
+
+![](https://ask.qcloudimg.com/http-save/1752328/23iw6tno0r.png)
+
+![](https://ask.qcloudimg.com/http-save/1752328/w5v3hts9fh.png)
+
+> 如果发现争用比较严重，如`Innodb_row_lock_waits`和`Innodb_row_lock_time_avg`的值比较高
+
+### 查询information\_schema相关表来查看锁情况
+
+![](https://ask.qcloudimg.com/http-save/1752328/f3dosa879q.png)
+
+![](https://ask.qcloudimg.com/http-save/1752328/q2rr67aov7.png)
+
+![](https://ask.qcloudimg.com/http-save/1752328/tzrfw5ebm9.png)
+
+### 设置Innodb monitors
+
+进一步观察发生锁冲突的表，数据行等，并分析锁争用的原因
+
+![](https://ask.qcloudimg.com/http-save/1752328/nc5e4b2ntz.png)
+
+![](https://ask.qcloudimg.com/http-save/1752328/m7spki6u7v.png)
+
+### 停止监视器
+
+![drop table innodb_monitor](https://ask.qcloudimg.com/http-save/1752328/5c9bcsych7.png)
+
+默认情况每15秒会向日志中记录监控的内容;
+
+如果长时间打开会导致.err文件变得非常巨大;
+
+所以确认原因后,要删除监控表关闭监视器,或者通过使用--console选项来启动服务器以关闭写日志功能
+
+## 4.4  InnoDB的行锁
+
+InnoDB支持以下两种类型的`行锁`
+
+- 共享锁（读锁S）
+若事务 T 对数据对象 A 加了 S 锁;
+则事务 T 可以读 A 但不能修改 A;
+其它事务只能再对它加 S 锁,而不能加 X 锁,直到 T 释放 A 上的 S 锁;
+这保证了,其他事务可以读 A，但在事务 T 释放 S 锁之前，不能对 A 做任何修改操作.
+- 排他锁（写锁X）
+若事务 T 对数据对象A加 X 锁;
+事务 T 可以读 A 也可以修改 A;
+其他事务不能对 A 加任何锁,直到 T 释放 A 上的锁;
+这保证了,其他事务在 T 释放 A 上的锁之前不能再读取和修改 A .
+
+> MySQL InnoDB默认行级锁
+
+行级锁都是基于索引的,若一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住
+
+为了允许行/表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁（Intention Locks）
+
+这两种意向锁都是`表锁`
+
+- 意向共享锁（IS）
+事务打算给数据行共享锁;
+事务在给一个数据行加共享锁前必须先取得该表的IS锁
+- 意向排他锁（IX）
+事务打算给数据行加排他锁;
+事务在给一个数据行加排他锁前必须先取得该表的IX锁
+
+| 当前锁/是否兼容/请求锁 | X | IX | S | IS |
+|:----|:----|:----|:----|:----|
+| X | 冲突 | 冲突 | 冲突 | 冲突 |
+| IX | 冲突 | 兼容 | 冲突 | 兼容 |
+| S | 冲突 | 冲突 | 兼容 | 兼容 |
+| IS | 冲突 | 兼容 | 兼容 | 兼容 |
+
+
+ 如果一个事务请求的锁模式与当前锁兼容，InnoDB就请求的锁授予该事务;
+
+反之，如果两者两者不兼容，该事务就要等待锁释放
+
+意向锁是InnoDB自动加的，不需用户干预.
+
+对于UPDATE、DELETE和INSERT语句，InnoDB会自动给涉及及数据集加排他锁（Ｘ）;
+
+对于普通SELECT语句，InnoDB不会加任何锁.
+
+对于SELECT语句,可以通过以下语句显式地给记录加读/写锁
+
+- 共享锁（Ｓ）
+![](https://ask.qcloudimg.com/http-save/1752328/dziotert3k.png)
+- 排他锁（X）
+![](https://ask.qcloudimg.com/http-save/1752328/llcmgd958r.png)
+
+共享锁语句主要用在需要数据依存关系时确认某行记录是否存在;
+
+并确保没有人对这个记录UPDATE或DELETE.
+
+但如果当前事务也需要对该记录进行更新,则很有可能造成死锁;
+
+对于锁定行记录后需要进行更新操作的应用,应该使用排他锁语句.
+
+## 4.5 实例
+
+### 4.5.1  Innodb共享锁
+
+| session\_1 | session\_2 |
+|:----|:----|
+| set autocommit=0,select \* from actor where id =1 | set autocommit=0,select \* from actor where id =1 |
+| 当前seesion对id为1的记录加入共享锁 select \* from actor where id =1 lock in share mode | ​ |
+| ​ | 其他seesion仍然可以查询，并对该记录加入 select \* from actor where id =1 lock in share mode |
+| 当前session对锁定的记录进行更新，等待锁 update。。。where id=1 | ​ |
+| ​ | 当前session对锁定记录进行更新，则会导致死锁退出 update。。。where id=1 |
+
+
+| 获得锁，更新成功 |
+
+### 4.5.2  Innodb排他锁
+
+| session\_1 | session\_2 |
+|:----|:----|
+| set autocommit=0,select \* from actor where id =1 | set autocommit=0,select \* from actor where id =1 |
+| 当前seesion对id为1的记录加入for update 共享锁 select \* from actor where id =1 for update | ​ |
+| ​ | 可查询该记录select _from actor where id =1,但是不能再记录共享锁，会等待获得锁select_ from actor where id =1 for update |
+| 更新后释放锁 update。。。 commit | ​ |
+| ​ | 其他session，获得锁，得到其他seesion提交的记录 |
+
+
+## 4.6 行锁的实现
+
+通过给索引上的索引项加锁来实现。
+如果没有索引，InnoDB将通过隐藏的聚簇索引对记录加锁。
+
+- Record Locks:对索引项加锁
+- Gap lock:对索引项之的“间隙“，第一条记录前的”间隙“，或最后一条记录后的”间隙“，加锁
+- Next-key lock：前两种的组合，对记录及其前面的间隙加锁
+
+行锁实现特点意味着：
+`如果不通过索引条件检索数据，那么Innodb将对表的所有记录加锁，和表锁一样`
+
+### 间隙锁（Next-Key锁）
+
+当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;
+
+对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁（Next-Key锁）.
+
+举例来说，假如emp表中只有101条记录，其empid的值分别是1,2,...,100,101，下面的SQL：
+
+InnoDB 不仅会对符合条件的 empid 值为 101 的记录加锁;
+
+也会对 `empid`大于`101`（这些记录并不存在）的“间隙”加锁
+
+![](https://ask.qcloudimg.com/http-save/1752328/6kjsh66yg1.png)
+
+#### 间隙锁的目的
+
+- 防止幻读,以满足相关隔离级别的要求
+对于上例,若不使用间隙锁,如果其他事务插入 empid 大于 100 的任何记录,;
+那么本事务如果再次执行上述语句,就会发生幻读
+- 满足其恢复和复制的需要
+在使用范围条件检索并锁定记录时;
+InnoDB 这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待;
+因此,在实际开发中,尤其是并发插入较多的应用;
+我们要尽量优化业务逻辑,尽量使用`相等条件来访问更新数据`,避免使用范围条件.
+
+## 4.7 when 使用表锁
+
+对于InnoDB,在绝大部分情况下都应该使用行锁
+
+因为`事务`,`行锁`往往是我们选择InnoDB的理由
+
+但在个别特殊事务中,也可以考虑`使用表锁`
+
+- 事务需要更新大部分数据，表又较大
+若使用默认的行锁,不仅该事务执行效率低(因为需要对较多行加锁,加锁是需要耗时的);
+而且可能造成其他事务长时间锁等待和锁冲突;
+这种情况下可以考虑使用表锁来提高该事务的执行速度
+- 事务涉及多个表，较复杂，很可能引起死锁，造成大量事务回滚
+这种情况也可以考虑一次性锁定事务涉及的表，从而避免死锁、减少数据库因事务回滚带来的开销
+
+当然，应用中这两种事务不能太多，否则，就应该考虑使用`ＭyISAＭ`
+
+ 在InnoDB下 ,使用表锁要注意
+
+- 使用`LOCK TALBES`虽然可以给`InnoDB`加表锁
+表锁不是由`InnoDB`引擎层管理的,而是由其上一层ＭySQL Server负责;
+仅当`autocommit=0、innodb_table_lock=1（默认设置）`,InnoDB 引擎层才知道MySQL加的表锁,ＭySQL Server才能感知InnoDB加的行锁;
+这种情况下，InnoDB才能自动识别涉及表锁的死锁
+否则，InnoDB将无法自动检测并处理这种死锁
+- 在用`LOCK TALBES`对`InnoDB`锁时要注意,要将`autocommit`设为0，否则ＭySQL不会给表加锁
+事务结束前,不要用`UNLOCK TALBES`释放表锁,因为它会隐式地提交事务
+COMMIT或ROLLBACK不能释放用`LOCK TALBES`加的表锁，必须用UNLOCK TABLES释放表锁，正确的方式见如下语句
+- 需要写表t1并从表t读
+![](https://ask.qcloudimg.com/http-save/1752328/l80atyna87.png)
+
+# 5 死锁
+
+**ＭyISAM表锁是deadlock free的，这是因为ＭyISAM总是一次性获得所需的全部锁，要么全部满足，要么等待，因此不会出现死锁**
+
+但在InnoDB中，除单个SQL组成的事务外，锁是逐步获得的，这就决定了InnoDB发生死锁是可能的
+
+发生死锁后，InnoDB一般都能自动检测到，并使一个事务释放锁并退回，另一个事务获得锁，继续完成事务
+
+- 但在涉及外部锁，或涉及锁的情况下，InnoDB并不能完全自动检测到死锁
+**这需要通过设置锁等待超时参数innodb\_lock\_wait\_timeout来解决**
+需要说明的是，这个参数并不是只用来解决死锁问题，在并发访问比较高的情况下，如果大量事务因无法立即获取所需的锁而挂起，会占用大量计算机资源，造成严重性能问题，甚至拖垮数据库
+我们通过设置合适的锁等待超时阈值，可以避免这种情况发生。
+
+通常来说，死锁都是应用设计的问题，通过调整业务流程、数据库对象设计、事务大小、以及访问数据库的SQL语句，绝大部分都可以避免
+
+下面就通过实例来介绍几种死锁的常用方法。
+
+- 应用中，不同的程序会并发存取多个表
+尽量约定以`相同的顺序`访问表
+- 程序批处理数据时
+`事先对数据排序`,保证每个线程按固定的顺序来处理记录
+- 在事务中,要更新记录
+应`直接申请排他锁,而不应该先申请共享锁`
+- 在`可重复读`下,如果两个线程同时对相同条件记录用`SELECT...ROR UPDATE`加排他写锁
+在没有符合该记录情况下，两个线程都会加锁成功
+程序发现记录尚不存在，就试图插入一条新记录，如果两个线程都这么做，就会出现死锁
+这种情况下，将隔离级别改成READ COMMITTED，就可以避免问题
+
+- 当隔离级别为READ COMMITED，若两线程都先执行`SELECT...FOR UPDATE`
+判断是否存在符合条件的记录，没有 -> 插入记录;
+此时，只有一个线程能插入成功，另一个线程会锁等待。
+当第１个线程提交后，第２个线程会因主键重复而出错，虽然这线程出错，却会获得一个排他锁！这时若有第３线程来申请排他锁，也会出现死锁。
+对这种情况，可直接做插入操作，然后再捕获主键重复异常，或在遇到主键重错误时，总是执行ROLLBACK释放获得的排他锁。
+
+**如果出现死锁，可以用SHOW INNODB STATUS命令来确定最后一个死锁产生的原因和改进措施。**
+
+# 6 总结
+
+## 6.1 **MyISAM**的表锁
+
+- 共享读锁之间是兼容的,但`共享读锁和排他写锁`之间,以及`排他写锁之间`互斥,即读写串行
+- 在一定条件下,`ＭyISAM`允许查询/插入并发,可利用这一点来解决应用中对同一表查询/插入的锁争用问题
+- `ＭyISAM`默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置`LOW_PRIPORITY_UPDATES`参数或在INSERT、UPDATE、DELETE语句中指定`LOW_PRIORITY`选项来调节读写锁的争用
+- 由于表锁的锁定粒度大,读写又是串行的,因此如果更新操作较多,`ＭyISAM`表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突
+
+## 6.2 对于**InnoDB表**
+
+- 行锁基于索引实现
+如果不通过索引访问数据,InnoDB会使用表锁
+- 间隙锁机制及使用间隙锁的原因
+- 不同的隔离级别下，InnoDB的锁机制和一致性读策略不同
+- ＭySQL的恢复和复制对InnoDB锁机制和一致性读策略也有较大影响
+- 锁冲突甚至死锁很难完全避免
+
+# 7 索引与锁
+
+在了解InnoDB的锁特性后，用户可以通过设计和SQL调整等措施减少锁冲突和死锁
+
+- 尽量使用较低的隔离级别
+- 精心设计索引，并尽量使用索引访问数据，使加锁更精确，从而减少锁冲突的机会。
+![](https://ask.qcloudimg.com/http-save/1752328/32fb7ibjas.png)
+- 选择合理的事务大小，小事务发生锁冲突的几率也更小
+- 给记录集显式加锁时，最好一次性请求足够级别的锁。比如要修改数据的话，最好直接申请排他锁，而不是先申请共享锁，修改时再请求排他锁，这样容易产生死锁。
+- 不同的程序访问一组表时，应尽量约定以相同的顺序访问各表，对一个表而言，尽可能以固定的顺序存取表中的行。这样可以大减少死锁的机会。
+- 尽量用相等条件访问数据，这样可以避免间隙锁对并发插入的影响。
+- 不要申请超过实际需要的锁级别；除非必须，查询时不要显示加锁。
+- 对于一些特定的事务，可以使用表锁来提高处理速度或减少死锁的可能
+![](https://ask.qcloudimg.com/http-save/1752328/xjr8zsmj3l.png)
+![image](https://ask.qcloudimg.com/http-save/1752328/3sr884g7ur.png)
+![image](https://ask.qcloudimg.com/http-save/1752328/1yrcmxdpcb.png)
+![image](https://ask.qcloudimg.com/http-save/1752328/0azj5ye5fj.png)
+
+![image](https://ask.qcloudimg.com/http-save/1752328/g2r8rkdbv1.png)
+
+# 参考
+
+[MySQL中的锁（表锁、行锁）](https://www.cnblogs.com/chenqionghe/p/4845693.html)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\346\211\247\350\241\214\350\256\241\345\210\222.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\344\274\230\345\214\226/MySQL\346\211\247\350\241\214\350\256\241\345\210\222.md"
similarity index 72%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\346\211\247\350\241\214\350\256\241\345\210\222.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\344\274\230\345\214\226/MySQL\346\211\247\350\241\214\350\256\241\345\210\222.md"
index fb3a029e22..4c5486bdad 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\346\211\247\350\241\214\350\256\241\345\210\222.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\344\274\230\345\214\226/MySQL\346\211\247\350\241\214\350\256\241\345\210\222.md"
@@ -1,6 +1,13 @@
+> 全是干货的技术号：
+> 本文已收录在github，欢迎 star/fork：
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
 explain或者desc获取MySQL如何执行select语句的信息。
 ![](https://img-blog.csdnimg.cn/20200830055230457.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
+
 结果包含很多列
+
 # 1 各列字段说明
 ## 1.1 id
  
@@ -30,9 +37,9 @@ d.dname = 'SALES');
 select_ type
 ```
 ## 1.2 select_type
-区分SELECT的类型，是普通查询还是联合查询还是子查询:
-- simple（简单表，不用表连接或子查询）
-- primary（主查询，外部查询）
+主要用来分辨SELECT的类型，是普通查询还是联合查询还是子查询:
+- simple（简单表，即不用表连接或子查询）
+- primary（主查询，即外部查询）
 - union（union中的第二个或者后面的查询语句）
 - subquery（子查询中的第一个select）
 
@@ -52,16 +59,14 @@ type列描述如何连接表。
 
 ### 1.4.1 ALL
 最简单暴力的全表扫描，MySQL遍历全表找到匹配行，效率最差。
-对来自先前表的行的每个组合进行全表扫描。若该表是未标记为const的第一个表，则通常不好，并且在所有其他情况下通常性能也非常糟糕。
-
-一般可以通过增加索引来避免ALL，这些索引允许基于早期表中的常量值或列值从表中检索行。
+对来自先前表的行的每个组合进行全表扫描。如果该表是未标记为const的第一个表，则通常不好，并且在所有其他情况下通常性能也非常糟糕。一般来说，可以通过添加索引来避免ALL，这些索引允许基于早期表中的常量值或列值从表中检索行。
 ```sql
 explain select * from film where rating > 9;
 ```
 ### 1.4.2 index
 连接类型与ALL相同，除了扫描索引树外。这发生于两种方式：
-1. 若索引是查询的覆盖索引，并且可用于满足表中所需的所有数据，则仅扫描索引树
-这种情况下，`Extra`列显示`Using index`。仅索引扫描通常比ALL更快，因索引大小通常小于表数据
+1. 如果索引是查询的覆盖索引，并且可用于满足表中所需的所有数据，则仅扫描索引树。
+在这种情况下，`Extra`列显示`Using index`。仅索引扫描通常比ALL更快，因为索引的大小通常小于表数据。
 2. 使用对索引的读取执行全表扫描，以按索引顺序查找数据行。Extra列不显示 `Using index`。
 
 当查询仅使用属于单个索引一部分的列时，MySQL可以使用此连接类型。
@@ -69,13 +74,11 @@ explain select * from film where rating > 9;
 ```sql
 explain select title from film;
 ```
+
 ###  1.4.3 range
-使用索引查询行，仅检索给定范围内的行。
-`key`列指示使用的哪个索引。`key_len`包含使用的最长的键部分。
-此类型的`ref`=`NULL`。
-![](https://img-blog.csdnimg.cn/2021060216003320.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+使用索引查询行，仅检索给定范围内的行。输出行中的`key`列指示使用的哪个索引。`key_len`包含使用的最长的键部分。此类型的`ref`列为`NULL`。
 
-当使用 `=, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, or IN()`操作符将`key`列与常量进行比较时，可以使用range：
+当使用 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, LIKE, or IN()操作符将`key`列与常量进行比较时，可以使用range：
 索引范围扫描，常见<,<=,>,>=,between
 
 ```sql
@@ -91,7 +94,6 @@ SELECT * FROM tbl_name
 SELECT * FROM tbl_name
   WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
 ```
-
 ### 1.4.4 index_subquery
 此连接类型类似于unique_subquery。它代替了`IN`子查询，但适用于以下形式的子查询中的非唯一索引：
 
@@ -178,53 +180,30 @@ SELECT * FROM tbl_name
 ## 1.7 ref
 哪个字段或常数与 key 一起被使用
 ## 1.8 rows
-该查询一共扫描了多少行。
-这是一个估计值，不准确。
-
-```sql
-mysql> explain insert into t(c, d) (select c + 1, d from t force index (c) order by c desc limit 1);
-+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------+
-| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                |
-+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------+
-|  1 | INSERT      | t     | NULL       | ALL   | NULL          | NULL | NULL    | NULL | NULL |     NULL | NULL                                 |
-|  1 | SIMPLE      | t     | NULL       | index | NULL          | c    | 5       | NULL |    1 |   100.00 | Backward index scan; Using temporary |
-+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+--------------------------------------+
-2 rows in set, 1 warning (0.00 sec)
-```
+此查询一共扫描了多少行，这个是一个估计值，不准确。
 ## 1.9 filtered
 此查询条件所过滤的数据的百分比
 ## 1.10 extra
-额外信息：
-
+额外的信息。
 ### using filesort
-性能消耗大，需要额外的一次查询（排序）。
 使用EXPLAIN可以检查MySQL是否可以使用索引来解析ORDER BY子句：
-- Extra列不包含Using filesort，则使用索引，并且不执行文件排序
-- Extra列包含Using filesort，则不使用索引，而是执行全文件排序
+- 如果EXPLAIN输出的Extra列不包含Using filesort，则使用索引，并且不执行文件排序
+- 如果EXPLAIN输出的Extra列包含正在使用文件排序，则不使用索引，而是执行全文件的排序
 ![](https://img-blog.csdnimg.cn/20200830055447215.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 EXPLAIN不能区分优化器是否在内存中执行文件排序。在优化程序trace输出中可以看到内存文件排序的使用。查找filesort_priority_queue_optimization即可。
 
-对于单索引，若
-- 排序和查找是同一字段，则不会出现该方式
-- 不是，则会出现。
-
-对于联合索引，不能跨列（最左匹配原则）
 ### using temporary
-- 建立临时表保存中间结果，查询完成之后把临时表删除。
-### using index（性能提升）
-表示当前的查询是覆盖索引，直接从索引中读取数据，而无需访问原数据表（回表）。即需要使用到的列，全部都在索引中。
-- 若同时出现**using where**，则索引被用来执行索引键值的查找
-- 若没有，则索引被用来读取数据，而不是真的查找
-
-### using index condition
-MySQL 5.6后加入的新特性（Index Condition Pushdown）。会先条件过滤索引，过滤完索引后找到所有符合索引条件的数据行，随后用 WHERE 子句中的其他条件去过滤这些数据行。
-![](https://img-blog.csdnimg.cn/2021060216160788.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### using where（需回表查询）
-使用where进行条件过滤。
-假设 age 为索引，如下 SQL
-```sql
-select age,name from ... where age = 
-```
+建立临时表保存中间结果，查询完成之后把临时表剧除
+![](https://img-blog.csdnimg.cn/20200830064404262.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
+### using index
+表示当前的查询是覆盖索引，直接从索明中读取数据，而不用访问数据表。
+如果阿时出现using where表示索引被用来执行索引健值的查找；
+如果没有，表示索引被用来读取数据，而不是真的查找
+
+
+### using where
+使用where进行条件过滤
 
 ### using join buffer
 使用连接缓存
@@ -292,6 +271,7 @@ select * from ....where....
 ```sql
 select * from information_schema.optimizer_trace
 ```
+参考
+- https://dev.mysql.com/doc/refman/8.0/en/explain-output.html#explain-join-types
 
-> 参考
-> - https://dev.mysql.com/doc/refman/8.0/en/explain-output.html#explain-join-types
\ No newline at end of file
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\344\274\230\345\214\226/MySQL\346\237\245\350\257\242\344\274\230\345\214\226.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\344\274\230\345\214\226/MySQL\346\237\245\350\257\242\344\274\230\345\214\226.md"
new file mode 100644
index 0000000000..640016c17d
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\344\274\230\345\214\226/MySQL\346\237\245\350\257\242\344\274\230\345\214\226.md"
@@ -0,0 +1,317 @@
+# 1 SQL优化
+通过show status命令了解各种sql的执行效率
+
+```sql
+查看本session的sql执行效率
+show status like 'Com_%';
+查看全局的统计结果
+SHOW GLOBAL STATUS LIKE  'Com_%'
+查看服务器的状态
+show global status;
+```
+
+结果
+
+*   Com_select:执行select操作的次数，依次查询之累加1
+*   Com_insert:执行insert操作的次数，对于批量插入的insert操作，只累加依次
+*   Com_update:执行update操作的次数
+*   Com_delete:执行delete的次数
+
+上面的参数是对所有存储引擎的表进行累计，下面参数是针对InnoDB存储引擎的，累加的算法也是略有不同的
+
+*   Innodb_rows_read:SELECT查询返回的行数
+*   Innodb_rows_insered:执行inser操作插入的行数
+*   Innodb_rows_updated:执行UPDATE操作更新的行数
+*   Innodb_rows_deleted执行DELETE操作删除的行数
+
+通过上述的参数可以了解当前数据库的应用是插入更新为主还是查询操作为主，以及各类的SQL的执行比例是多少。对于更新操作的计算，是对执行次数的计数，无论提交还是回滚都会进行累加对于事务形的应用，通过Com_commit和Com_rollback可以了解事务提交和回滚的情况，对于回滚操作非常频繁的数据库，可能意味着应用编写存在的问题
+
+*   Connections:试图连接MySql服务器的次数
+*   Uptime：服务器工作时间
+*   Slow_queries:慢查询的次数
+
+# 2 定位执行效率低的SQL语句
+1.  慢查询日志
+`--log-show-queries[=file_name]`选项去启动
+mysqlId写一个包含所有执行时间超过`long_querty_time`秒的sql语句的日志文件
+2.  show processlist命令
+慢查询日志在查询结束后才记录，所以在应用反应执行效率出现问题的时候查询慢查询日志并不能定位问题
+可以使用`show processlist`命令查看当前Mysql在进行的线程，包括线程的状态，是否锁表等，可以实时查看sql的执行情况，同时对一些锁表进行优化
+
+# 3  通过explain分析执行sql的执行计划
+
+
+# 4  简单的优化方法
+
+本语句可以用于分析和存储表的关键字分布，分析的结果可以使得系统得到准确的统计信息使得sql，能够生成正确的执行计划。如果用户感觉实际执行计划并不预期的执行计划，执行一次分析表可能会解决问题
+
+```
+analyze table payments;
+```
+
+检查表：检查表：检查表的作用是检查一个表或多个表是否有错误,也可以检查视图是否错误
+
+```
+check table payment;
+```
+
+优化表:如果删除了表的一大部分，或者如果已经对可变长度的行表（含varchar、blob、text列）的表进行改动，则使用optimize 进行表优化，这个命令**可以使表中的空间碎片进行合并、并且可以消除由于删除或者更新造成的空间浪费**
+
+```
+optimize table payment;
+```
+
+对于innodb引擎的表，可以通过设置innodb_file_per_taable参数，设置InnoDb为独立表空间模式，这样每个数据库的每个表都会生成一个独立的idb文件，用于存储表的数据和索引，可以一定程度减少Innodb表的空间回收问题,另外，在删除大量数据后，Innodb表可以通过alter table但是不锈钢引擎方式来回收不用的空间
+
+```
+alter table payment enigine=innodb;
+```
+
+**ANALYZE,CHECK,OPTIMIZE,ALTER TABLE执行期间都是对表进行锁定，因此要在数据库不频繁的时候执行相关的操作**
+
+
+
+
+- 拆分表： 分区将数据在物理上分隔开，不同分区的数据可以制定保存在处于不同磁盘上的数据文件里。
+这样，当对这个表进行查询时，只需要在表分区中进行扫描，而不必进行全表扫描，明显缩短了查询时间，
+另外处于不同磁盘的分区也将对这个表的数据传输分散在不同的磁盘I/O，一个精心设置的分区可以将数据传输对磁盘I/O竞争均匀地分散开。
+对数据量大的时时表可采取此方法。可按月自动建表分区。
+
+# 存储过程与触发器的区别
+两者唯一的区别是触发器不能用EXECUTE语句调用，而是在用户执行Transact-SQL语句时自动触发（激活）执行。
+触发器是在一个修改了指定表中的数据时执行的存储过程。
+通常通过创建触发器来强制实现不同表中的逻辑相关数据的引用完整性和一致性。
+触发器不同于存储过程，触发器主要是通过事件执行触发而被执行的，
+存储过程可以通过存储过程名称名字而直接调用。
+当对某一表进行诸如UPDATE、INSERT、DELETE这些操作时，SQLSERVER就会自动执行触发器所定义的SQL语句，从而确保对数据的处理必须符合这些SQL语句所定义的规则。
+
+# 面试数据库优化问题回答方向:
+（1）、根据服务层面：配置mysql性能优化参数
+（2）、从系统层面增强mysql的性能：优化数据表结构、字段类型、字段索引、分表，分库、读写分离等等
+（3）、从数据库层面增强性能：优化SQL语句，合理使用字段索引。
+（4）、从代码层面增强性能：使用缓存和NoSQL数据库方式存储，如MongoDB/Memcached/Redis来缓解高并发下数据库查询的压力。
+（5）、减少数据库操作次数，尽量使用数据库访问驱动的批处理方法。
+（6）、不常使用的数据迁移备份，避免每次都在海量数据中去检索。
+（7）、提升数据库服务器硬件配置，或者搭建数据库集群。
+（8）、编程手段防止SQL注入：使用JDBC PreparedStatement按位插入或查询；正则表达式过滤（非法字符串过滤）
+
+
+#### 大批量的插入数据
+
+当用load导入数据，适当的设置可以提供导入的速度
+对于MyISAM存储引擎的表，可以通过以下方式快速导入大量的数据
+
+```
+alter table tab_name disable keys;
+loading the data
+alter table tab_name disable keys;
+```
+
+disable keys和enable keys 用来打开或者关闭MyISAM表非索引的更新。在导入大量的数据到一个非空的MyISAM表，通过设置这两个命令，可以提高导入的效率
+对于Innodb类型的表不能使用上面的方式提高导入效率
+因为Innodb类型的表是按照主键的顺序保存，所有将导入的数据按照主键的顺序排序，可以有效地提高导入数据的效率
+
+在导入数据强执行SET UNIQUE_CHECKS=0，关闭唯一性校验，在导入结束后执行SET UNIQUE_CHECKS=1.恢复唯一性校验，可以提高导入的效率，如果应用使用自动提交的方式，建议在导入前执行SET AUTOCOMMIT=0时，关闭自动提交，导入结束后再执行SET AUTOCOMMIT=1，打开自动提交，也可以提高导入的效率
+
+#### 优化insert语句
+
+*   如果同时从一个客户端插入很多行，应尽量使用多个值表的insert语句，这种方式将大大缩减客户端与数据库之间的连接、关闭等消耗，使得效率比分开执行的单个insert语句快（大部分情况下，使用多个值表的insert语句那比单个insert语句快上好几倍）。
+
+    ```
+    insert into test values(1,2),(1,3)...
+    ```
+
+    *   如果从不同客户插入很多行，可以通过使用insert delayed语句提高更高的速度，delayed的含义是让insert语句马上执行，其实数据都被放到内存的队列中，并没有真正写入磁盘，这比每条语句分别插入要快的多；LOW_PRIORITY刚好相反，在所有其他用户对表的读写完成后才可以进行
+    *   将索引文件和数据文件分在不同的磁盘上存放（利用建表中的选项）
+    *   如果进行批量插入，可以通过增加bulk_insert_buffer_size变量值的方法来通过速度，但是，这只能对MyISAM表使用。
+    *   当从一个文本文件装载一个表时，使用LOAD DATA INFILE。这通常比使用很多INSERT语句块快20倍
+
+#### 优化ORDER BY语句
+*   第一种通过有序排序索引顺序扫描，这种方式在使用explain分析查询的时候显示为Using Index，不需要额外的排序，操作效率较高-innodb引擎
+![条件](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtODM2N2U0ODJhODBhYWYzYS5wbmc?x-oss-process=image/format,png)
+
+
+    ```
+    explain select customer_id from customer order by store_id;
+    ```
+![表](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtODk0MjI2N2U1ZDg0MTliMC5wbmc?x-oss-process=image/format,png)
+![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMDExNDE4OTU3M2Q3YzFkOC5wbmc?x-oss-process=image/format,png)
+![违反规则,则filesort](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMjJkZGRhYWExMzk3Y2QzYi5wbmc?x-oss-process=image/format,png)
+
+
+*   第二张通过返回数据进行排序，也就是通常说的Filesort排序，所有不是通过索引直接返回排序结果的排序豆角Filesort排序。Filesort并不代表通过磁盘文件进行排序，而只是说明进行了一个排序操作，至于排序操作是否进行了磁盘文件或临时表等，则取决于MySql服务器对排序参数的设置和需要排序数据的大小-myshim引擎
+
+    ```
+    explain select * from customer order by store_id;
+    ```
+
+    Filesort是通过相应的排序算法，将取得的数据在sort_buffer_size系统变量设置的内存排序区进行排序，如果内存装载不下，它会将磁盘上的数据进行分块，再对各个数据块进行排序，然后将各个块合并成有序的结果集。sort_buffer_size设置的排序区是每个线程独占的，所有同一个时刻，MySql存在多个sort buffer排序区
+
+优化目标:尽量减少额外的排序，**通过索引直接返回有序数据**.where和ordery by 使用相同的索引，并且order by的顺序和索引顺序相同，并且order by的字段都是升序或者都是降序。否则肯定需要额外的排序操作，这样就会出现filesort
+
+#### 优化group by 语句
+
+如果查询包括group by 但用户想要避免排序结果的消耗，可以指定group by null
+
+##### 优化嵌套查询
+
+子查询可以被更有效率的连接替代
+
+```
+explain select * from customer where customer_id not in(select customer_id from payment)
+
+explain select * from customer a left join payment b on a.customer_id=b.customer_id where b.customer id is null
+```
+
+**连接之所用更有效率是因为mysql不需要在内存中创建临时表来完成这个逻辑上需要两个步骤的查询工作**
+
+###### 优化分页查询
+
+一般分页查询，通过创建覆盖索引能够比较好地提高性能。一个场景是"limit 1000,20",此时Mysql排序出前1020条数据后仅仅需要返回第1001到1020条记录，前1000条数据都被抛弃，查询和排序代价非常高
+
+优化方式：可以增加一个字段last_page_record.记录上一页和最后一页的编号，通过
+
+```
+explain select ...where last_page_record<... desc limt ..
+```
+
+如果排序字段出现大量重复字段，不适用这种方式进行优化
+
+## MySql常用技巧
+
+#### 正则表达式的使用
+
+| 序列 | 序列说明 |
+| --- | --- |
+| ^ | 字符串的开始处进行排序 |
+| $ | 在字符串的末尾处进行匹配 |
+| . | 匹配任意单个字符串，包括换行服 |
+| [...] | 匹配括号内的任意字符 |
+| {FNXX==XXFN} | 匹配不出括号内任意字符 |
+| a* | 匹配零个或多个a(包括空串) |
+| a+ | 匹配一个或多个a（不包括字符串) |
+| a? | 匹配一个或零个a |
+| a1\ | a2 | 匹配a1或a2 |
+| a(m) | 匹配m个a |
+| a(m,) | 匹配m个或更多a |
+| a(m,n) | 匹配m到n个a |
+| a(,n) | 匹配0到n个a |
+| (...) | 将模式元素组成单一元素 |
+
+使用
+
+```
+select 'abcdefg' regexp '^a';
+.....
+```
+
+#### 如果range()提取随机行
+
+随机抽取某些行
+
+```
+select * from categrory order by rand() limit 5;
+```
+
+#### 利用group by的with rollup 子句
+
+使用Group By的with rollup可以检索更多分组聚合信息
+
+```
+select date_from(payment_date,'%Y-%M'),staff_id,sum(amount) from payment group by date_formate(payment_date,'%Y-%M'),staff_id;
+```
+
+#### 用BIT GROUP FUNCTIONS做统计
+
+使用GROUP BY语句和BIT_AND、BIT_OR函数完成统计工作，这两个函数的一般用途就是做数值之间的逻辑
+
+* * *
+
+## 优化数据库对象
+
+#### 优化表类型
+
+表需要使用何种数据类型工具应用来判断，虽然考虑字段的长度会有一定的冗余，但是不推荐让很多字段都留有大量的冗余，这样既浪费磁盘的存储空间，同时在应用操作时也浪费物理内存mysql，可以使用函数procedure analyse对当前的表进行分析
+
+```
+//输出的每一类信息都对数据表中的列的数据类型提出优化建议。第二语句高数procedure anaylse不要为那些包含的值多余16个或者256个字节的enum类型提出建议，如果没有这个限制，输出的信息可能很长；ENUM定义通常很难阅读，通过输出信息，可以将表中的部分字段修改为效率更高的字段
+select * from tb1_name procedure analyse();
+select * from tb2_name procedure analyse(16,256);
+```
+
+#### 通过拆分提高表的访问效率
+
+*   重置拆分，把主码和一些列放到一个表，然后把住码和另外的列放到另一个表， 好处**可以将常用的列放在一起，不常用的列放在一起，使得数据行变少，一个数据页可以存放更多的数据，在查询时会减少I/O次数**，缺点：**管理冗余，查询所有数据需要用join操作**
+*   水平拆分。根据一列或多列数据把数据行放到两个独立的表中：水平拆分会给应用增加复杂度，它通常在查询时需要多个表名，查询所有数据需要UNION操作，缺点：**只要索引关键字不大，则在索引查询时，表中增加了2-3倍的数据量，查询时也增加了读一个索引的磁盘次数，所有说拆分要考虑数据量的增长速度**。常用场景
+    *   表很大，分割后可以降低在查询时需要读的数据和索引的页数，同时也降低了索引的层数，提高查询速度
+    *   表中的数据本来就有独立性，例如表中分别记录各个地区的数据或者不同时期的数据，特别是有些数据常用，而有些数据不常用
+    *   需要把数据存放在多个介质上：如账单：最近三个月数据存在一个表中，3个月之前的数据存放在另一个表，成功一年的可以存储在单独的存储介质中。
+
+#### 逆规范化
+
+数据库设计时需要瞒住规范化，但是规范化程度越高，产生的关系就越多，**关系越多直接结果就是表直接的连接操作越频繁，而表连接的操作是性能较低的操作，直接影响到查询的数据。**
+**反规范化的好处在于降低连接操作的需求，降低外码和索引的数目，还可以减少表的树木，相应带来的问题可能出现数据的完整性问题。加快查询速度，但是降低修改速度。好的索引和其他方法经常能够解决性能问题，而不必采用反规范这种方法**
+采用的反规范化技术
+
+*   增加冗余列：指在多个表中具有相同的列，它常用来在查询时避免连接操作
+*   增加派生列：指增加的列来自其他表中的数据，由其他表中的数据经过计算生成。增加的派生列其他作业是在查询时减少连接操作，避免使用集函数
+*   重新组表：指如果许多用户需要查看两个表连接出来的结果数据，则把这两个表查询组成一个表来减少连接而提高性能
+*   分割表
+
+维护数据的完整性
+
+*   批处理维护是指对复制列或派生列的修改积累一定的时间后，运行一批处理作业或修改存储过程对复制或派生列进行修改，这只能对实时性要求不高的情况下使用
+*   数据的完整性也可由应用逻辑来实现，这就要求必须在同一事务中对所有涉及的表进行增、删、改操作。用应用逻辑来实现数据的完整性风险较大，因为同一逻辑必须在所有的应用中使用和维护，容易遗漏。特别是在需求变化时，不易于维护
+*   使用触发器，**对数据的任何修改立即触发对复制列或者派生列的相应修改**，触发器是实时的，而且相应的处理逻辑只在一个地方出现，易于维护，一般来说，是解决这类问题比较好的方法
+
+#### 使用中间表提高统计查询速度
+
+对于数据量较大的表，在其上进行统计查询通常会效率很低，并且还要考虑统计查询是
+否会对在线的应用产生负面影响。通常在这种情况下，使用中间表可以提高统计查询的效率
+session 表记录了客户每天的消费记录，表结构如下：
+
+```
+CREATE TABLE session (
+cust_id varchar(10) , --客户编号
+cust_amount DECIMAL(16,2), --客户消费金额
+cust_date DATE, --客户消费时间
+cust_ip varchar(20) –客户IP 地址
+)
+```
+
+由于每天都会产生大量的客户消费记录,所以session 表的数据量很大,现在业务部门有
+一具体的需求：希望了解最近一周客户的消费总金额和近一周每天不同时段用户的消费总金
+额。针对这一需求我们通过2 种方法来得出业务部门想要的结果。
+方法1：在session 表上直接进行统计,得出想要的结果。
+
+```
+select sum(cust_amount) from session where cust_date>adddate(now(),-7);
+```
+
+方法2：创建中间表tmp_session，表结构和源表结构完全相同。
+
+```
+CREATE TABLE tmp_session (
+cust_id varchar(10) , --客户编号
+cust_amount DECIMAL(16,2), --客户消费金额
+cust_date DATE, --客户消费时间
+cust_ip varchar(20) –客户IP 地址
+) ;
+```
+
+```
+insert into tmp_session select * from session where cust_date>adddate(now(),-7);
+```
+
+转移要统计的数据到中间表,然后在中间表上进行统计，得出想要的结果。
+在中间表上给出统计结果更为合适,原因是源数据表(session 表)
+**cust_date 字段没有索引并且源表的数据量较大，所以在按时间进行分时段统计时效率**
+很低，这时可以在中间表上对cust_date 字段创建单独的索引来提高统计查询的速度。
+中间表在统计查询中经常会用到，其优点如下:
+
+1.  中间表复制源表部分数据，并且与源表相“隔离”，在中间表上做统计查询不
+    会对在线应用产生负面影响.
+2.  中间表上可以灵活的添加索引或增加临时用的新字段,从而达到提高统计查询
+    效率和辅助统计查询作用。
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\346\263\250\345\205\245.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\346\263\250\345\205\245\345\216\237\347\220\206.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\346\263\250\345\205\245.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/SQL\346\263\250\345\205\245\345\216\237\347\220\206.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/\346\210\221\346\230\257\345\246\202\344\275\225\344\270\200\346\255\245\346\255\245\350\256\251\345\205\254\345\217\270\347\232\204MySQL\346\224\257\346\222\221\344\272\277\347\272\247\346\265\201\351\207\217\347\232\204.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/\346\210\221\346\230\257\345\246\202\344\275\225\344\270\200\346\255\245\346\255\245\350\256\251\345\205\254\345\217\270\347\232\204MySQL\346\224\257\346\222\221\344\272\277\347\272\247\346\265\201\351\207\217\347\232\204.md"
deleted file mode 100644
index 7fd3d540bd..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/\346\210\221\346\230\257\345\246\202\344\275\225\344\270\200\346\255\245\346\255\245\350\256\251\345\205\254\345\217\270\347\232\204MySQL\346\224\257\346\222\221\344\272\277\347\272\247\346\265\201\351\207\217\347\232\204.md"
+++ /dev/null
@@ -1,131 +0,0 @@
-#  1 主从读写分离
-大部分互联网业务都是读多写少，因此优先考虑DB如何支撑更高查询数，首先就需要区分读、写流量，这才方便针对读流量单独扩展，即主从读写分离。
-
-若前端流量突增导致从库负载过高，DBA会优先做个从库扩容上去，这样对DB的读流量就会落到多个从库，每个从库的负载就降了下来，然后开发再尽力将流量挡在DB层之上。
-
-> Cache V.S MySQL读写分离
-> 由于从开发和维护的难度考虑，引入缓存会引入复杂度，要考虑缓存数据一致性，穿透，防雪崩等问题，并且也多维护一类组件。所以推荐优先采用读写分离，扛不住了再使用Cache。
-
-## 1.1 core
-主从读写分离一般将一个DB的数据拷贝为一或多份，并且写入到其它的DB服务器中：
-- 原始DB为主库，负责数据写入
-- 拷贝目标DB为从库，负责数据查询
-
-所以主从读写分离的关键：
-- 数据的拷贝
-即主从复制
-- 屏蔽主从分离带来的访问DB方式的变化
-让开发人员使用感觉依旧在使用单一DB
-
-# 2 主从复制
-MySQL的主从复制依赖于binlog，即记录MySQL上的所有变化并以二进制形式保存在磁盘上二进制日志文件。
-
-主从复制就是将binlog中的数据从主库传输到从库，一般异步：主库操作不会等待binlog同步完成。
-## 2.1 主从复制的过程
-- 从库在连接到主节点时会创建一个I/O线程，以请求主库更新的binlog，并把接收到的binlog写入relay log文件，主库也会创建一个log dump线程发送binlog给从库
-- 从库还会创建一个SQL线程，读relay log，并在从库中做回放，最终实现主从的一致性
-
-使用独立的log dump线程是异步，避免影响主库的主体更新流程，而从库在接收到信息后并不是写入从库的存储，是写入一个relay log，这是为避免写入从库实际存储会比较耗时，最终造成从库和主库延迟变长。
-
-- 主从异步复制的过程![](https://img-blog.csdnimg.cn/d03b032126af44019d8785523c3e8203.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-基于性能考虑，主库写入流程并没有等待主从同步完成就返回结果，极端情况下，比如主库上binlog还没来得及落盘，就发生磁盘损坏或机器掉电，导致binlog丢失，主从数据不一致。不过概率很低，可容忍。
-
-> 主库宕机后，binlog丢失导致的主从数据不一致也只能手动恢复。
-
-主从复制后，即可：
-- 在写入时只写主库
-- 在读数据时只读从库
-
-这样即使写请求会锁表或锁记录，也不会影响读请求执行。高并发下，可部署多个从库共同承担读流量，即一主多从支撑高并发读。
-
-从库也能当成个备库，以避免主库故障导致数据丢失。
-
-那无限制地增加从库就能支撑更高并发吗？
-NO！从库越多，从库连接上来的I/O线程越多，主库也要创建同样多log dump线程处理复制的请求，对于主库资源消耗较高，同时受限于主库的网络带宽，所以一般一个主库最多挂3～5个从库。
-## 2.2 主从复制的副作用
-比如发朋友圈这一操作，就存在数据的：
-- 同步操作
-如更新DB
-- 异步操作
-如将朋友圈内容同步给审核系统
-
-所以更新完主库后，会将朋友圈ID写入MQ，由Consumer依据ID在从库获取朋友圈信息再发给审核系统。
-**此时若主从DB存在延迟，会导致在从库取不到朋友圈信息，出现异常！**
-
-- 主从延迟对业务的影响示意图
-![](https://img-blog.csdnimg.cn/80fea7d207da46f3a7f82592e16a4f6d.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-## 2.3 避免主从复制的延迟
-这咋办呢？其实解决方案有很多，核心思想都是 **尽量不去从库查询数据**。因此针对上述案例，就有如下方案：
-### 2.3.1 数据冗余
-可在发MQ时，不止发送朋友圈ID，而是发给Consumer需要的所有朋友圈信息，避免从DB重新查询数据。
-
-> 推荐该方案，因为足够简单，不过可能造成单条消息较大，从而增加消息发送的带宽和时间。
-###  2.3.2 使用Cache
-在同步写DB的同时，把朋友圈数据写Cache，这样Consumer在获取朋友圈信息时，优先查询Cache，这也能保证数据一致性。
-
-该方案适合新增数据的场景。若是在更新数据场景下，先更新Cache可能导致数据不一致。比如两个线程同时更新数据：
-> - 线程A把Cache数据更新为1
-> - 另一个线程B把Cache数据更新为2
-> - 然后线程B又更新DB数据为2
-> - 线程A再更新DB数据为1
-
-最终DB值（1）和Cache值（2）不一致！
-###  2.3.3 查询主库
-可以在Consumer中不查询从库，而改为查询主库。
-
-使用要慎重，要明确查询的量级不会很大，是在主库的可承受范围之内，否则会对主库造成较大压力。
-
-若非万不得已，不要使用该方案。因为要提供一个查询主库的接口，很难保证其他人不滥用该方法。
-
-主从同步延迟也是排查问题时容易忽略。
-有时会遇到从DB获取不到信息的诡异问题，会纠结代码中是否有一些逻辑把之前写入内容删除了，但发现过段时间再去查询时又能读到数据，这基本就是主从延迟问题。
-所以，一般把从库落后的时间作为一个重点DB指标，做监控和报警，正常时间在ms级，达到s级就要告警。
-
-> 主从的延迟时间预警，那如何通过哪个数据库中的哪个指标来判别？ 在从从库中，通过监控show slave
-> status\G命令输出的Seconds_Behind_Master参数的值判断，是否有发生主从延时。
-> 这个参数值是通过比较sql_thread执行的event的timestamp和io_thread复制好的
-> event的timestamp(简写为ts)进行比较，而得到的这么一个差值。
-> 但如果复制同步主库bin_log日志的io_thread线程负载过高，则Seconds_Behind_Master一直为0，即无法预警，通过Seconds_Behind_Master这个值来判断延迟是不够准确。其实还可以通过比对master和slave的binlog位置。
-
-# 3 如何访问DB
-使用主从复制将数据复制到多个节点，也实现了DB的读写分离，这时，对DB的使用也发生了变化：
-- 以前只需使用一个DB地址
-- 现在需使用一个主库地址，多个从库地址，且需区分写入操作和查询操作，再结合“分库分表”，复杂度大大提升。
-
-为降低实现的复杂度，业界涌现了很多DB中间件解决DB的访问问题，大致分为：
-## 3.1 应用程序内部
-如TDDL（ Taobao Distributed Data Layer），以代码形式内嵌运行在应用程序内部。可看成是一种数据源代理，它的配置管理多个数据源，每个数据源对应一个DB，可能是主库或从库。
-当有一个DB请求时，中间件将SQL语句发给某个指定数据源，然后返回处理结果。
-#### 优点
-简单易用，部署成本低，因为植入应用程序内部，与程序一同运行，适合运维较弱的小团队。
-#### 缺点
-缺乏多语言支持，都是Java语言开发的，无法支持其他的语言。版本升级也依赖使用方的更新。
-## 3.2 独立部署的代理层方案
-如Mycat、Atlas、DBProxy。
-
-这类中间件部署在独立服务器，业务代码如同在使用单一DB，实际上它内部管理着很多的数据源，当有DB请求时，它会对SQL语句做必要的改写，然后发往指定数据源。
-#### 优点
-- 一般使用标准MySQL通信协议，所以可支持多种语言
-- 独立部署，所以方便维护升级，适合有运维能力的大中型团队
-#### 缺点
-所有的SQL语句都需要跨两次网络：从应用到代理层和从代理层到数据源，所以在性能上会有一些损耗。
-# 4 总结
-可以把主从复制引申为存储节点之间互相复制存储数据的技术，可以实现数据冗余，以达到备份和提升横向扩展能力。
-
-使用主从复制时，需考虑：
- - 主从的一致性和写入性能的权衡
-若保证所有从节点都写入成功，则写性能一定受影响；若只写主节点就返回成功，则从节点就可能出现数据同步失败，导致主从不一致。互联网项目，一般优先考虑性能而非数据的强一致性
-- 主从的延迟
-会导致很多诡异的读取不到数据的问题
-
-很多实际案例：
-- Redis通过主从复制实现读写分离
-- Elasticsearch中存储的索引分片也可被复制到多个节点
-- 写入到HDFS中，文件也会被复制到多个DataNode中
-
-不同组件对于复制的一致性、延迟要求不同，采用的方案也不同，但设计思想是相通的。
-# FAQ
-> 若大量订单，通过userId hash到不同库，对前台用户订单查询有利，但后台系统页面需查看全部订单且排序，SQL执行就很慢。这该怎么办呢？
-
-由于后台系统不能直接查询分库分表的数据，可考虑将数据同步至一个单独的后台库或同步至ES。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/.md"
deleted file mode 100755
index e69de29bb2..0000000000
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis V.S Memcached.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis V.S Memcached.md"
deleted file mode 100644
index 3be1d924e6..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis V.S Memcached.md"	
+++ /dev/null
@@ -1,42 +0,0 @@
-看看 Redis 作者在 Stack Overflow 问答给出的几个对比维度。
-# 1 不应该太在意的维度
-## 1.1 性能
-- 平均到单核的性能，在单条数据不大时，Redis更快。以前因为Redis是单线程的，只能使用一个核。而Memcached是多线程的，所以对一个实例来说，性能上肯定是Memcached占优势。但现在Redis 6.x 已经支持多线程，Redis 在这点也没劣势了。
-- memcached可通过单个可执行文件和TCP端口使用多个内核，而无需客户端的帮助的多线程、非阻塞 IO 模式。memcached可以更快地获得大约100k的大值数据。Redis最近对大值（不稳定的分支）进行了很多改进，但是在这种场景下，memcached仍然更快（不过这个回答已经是很多年前了，现在优化的应该也没劣势了）。
-这里的重点是：`任何一个查询都不应该成为它们每秒可以提供的查询的瓶颈`。
-# 2 应该在意的维度
-## 2.1 内存利用率
-- 对于简单的键/值对，memcached的内存利用率更高。
-- 而如果Redis采用hash结构存储键/值对，由于其组合式的压缩，其内存利用率会高于Memcached。
-
-## 2.2 持久化和复制（可靠性）
-MemCached 不支持数据持久化，断电或重启后数据消失，所以只能作为缓存使用，但其稳定性是有保证的。
-
-Redis 支持数据持久化和数据恢复，允许单点故障，但是同时也会付出性能的代价。这样启动更快，不需要从关系数据库加载数据，从自己的 dump 文件加载即可。
-
-- Memcached都不支持。
-- 仅在Redis中可用的两个功能。即使你的目标是建立缓存，它也可以帮助你在升级或重启后仍然保留数据。
-## 2.3 所需的数据类型
-- Memcached是一个内存中键值存储，用于存储来自数据库调用，API调用或页面呈现结果的小数据块（字符串、对象）。通常需要将数据拿到客户端（即只能在客户端完成计算）来进行类似的修改再set回去，这大大增加了网络I/O的次数和数据大小。
-- 在Redis中，提供更多复杂的数据类型，因此也能支持更多应用场景。即使仅考虑缓存场景，通常也可以在单个操作中完成更多操作，而无需在客户端处理数据（即Redis支持服务器端数据计算），该操作通常和普通的GET和SET一样快。因此，如果你不仅需要GET / SET，还需要更复杂的功能，则Redis可以提供很大帮助（请考虑使用时间轴缓存）。
-##   2.4 集群模式
-- memcached没有原生的集群模式，需要依靠客户端来实现往集群中分片写入数据
-- 但是redis目前是原生支持cluster模式的，redis官方就是支持redis cluster集群模式的，比memcached来说要更好
-
-## 2.5 应用场景
-Memcached：动态系统中减轻数据库负载，提升性能；做缓存，适合多读少写。
-
-Redis：适用于对读写效率要求都很高，数据处理业务复杂和对安全性要求较高的系统。
-
-# 3 开发前的考量
-- Memcached 单个 KV 大小有限，一个 V 最大只支持 1M，而 Redis 最大支持 512M。
-- Memcached 只做缓存，对可靠性无要求；而 Redis 更倾向于内存数据库，因此对对可靠性方面要求比较高
-- Memcached 只是个单一 KV 缓存；而 Redis 则是一个数据结构内存数据库，支持多种数据类型，因此 Redis 除单纯缓存作用外，还可以处理一些简单的逻辑运算，Redis 不仅可以缓存，而且还可以作为数据库用
-- Redis 还可集群分布式，即集群本身均衡客户端请求，各个节点可以交流，可拓展行、可维护性更强大。而 Memcached 的集群则需通过第三方才能搭建
-
-# 4 总结
-没有场景，就难以选型，但Redis在很多事情上都是有意义的，因为即使你不想将其用作数据库，也可以通过使用更多功能来解决更多问题，不只是缓存，甚至是消息队列，排名等。
-
-> 参考
-> - https://stackoverflow.com/questions/2873249/is-memcached-a-dinosaur-in-comparison-to-redis
-> - https://github.com/memcached/memcached/wiki
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/RedisCluster.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/RedisCluster.md"
deleted file mode 100644
index e69de29bb2..0000000000
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206\345\217\212\350\277\207\346\234\237key\345\244\204\347\220\206.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206\345\217\212\350\277\207\346\234\237key\345\244\204\347\220\206.md"
deleted file mode 100644
index 54ebe3ee6d..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\270\273\344\273\216\345\244\215\345\210\266\345\216\237\347\220\206\345\217\212\350\277\207\346\234\237key\345\244\204\347\220\206.md"
+++ /dev/null
@@ -1,215 +0,0 @@
-在Redis复制的基础上（不包括Redis Cluster或Redis Sentinel作为附加层提供的高可用功能），使用和配置主从复制非常简单，能使得 【Redis从服务器】（下文称R）能精确得复制 【Redis主服务器】（下文称M）的内容。
-每当 R 和 M 之间的连接断开时， R 会自动重连到 M，并且无论这期间 M 发生了什么， R 都将尝试让自身成为 M 的精确副本。
-# 1 依赖机制
-该系统的运行依靠如下重要的机制：
-## 1.1 更新 R
-当一个 M 和一个 R 连接正常时， M 会发送一连串命令流保持对 R 的更新，以便将自身数据集的改变复制给 R，这包括客户端的写入、key 的过期或被逐出等
-## 1.2 部分重同步
-当 M 和 R 断连后，因为网络问题、或者是主从意识到连接超时， R 重新连接上 M 并会尝试进行部分重同步：它会尝试只获取在断开连接期间内丢失的命令流。
-## 1.3 全量重同步
-当无法进行部分重同步时， R 会请求全量重同步。
-这涉及到一个更复杂过程，比如M需要创建所有数据的快照，将之发送给 R ，之后在数据集更改时持续发送命令流到 R。
-
-Redis使用默认的异步复制，低延迟且高性能，适用于大多数 Redis 场景。但是，R会异步确认其从M周期接收到的数据量。
-
-客户端可使用 WAIT 命令来请求同步复制某些特定的数据。但WAIT命令只能确保在其他 Redis 实例中有指定数量的已确认的副本：在故障转移期间，由于不同原因的故障转移或是由于 Redis 持久性的实际配置，故障转移期间确认的写入操作可能仍然会丢失。
-# 2 Redis 复制特点
-- Redis 使用异步复制，R 和 M 之间异步地确认处理的数据量
-- 一个 M 可有多个 R
-- R 可接受其他 R 的连接
-除了多个 R 可以连接到同一 M，R 间也可以像层级连接其它 R。Redis 4.0起，所有 sub-R 将会从 M 收到完全一样的复制流
-- Redis 复制在 M 侧是非阻塞的
-M 在一或多 R 进行初次同步或者是部分重同步时，可以继续处理查询请求
-- 复制在 R 侧大部分也是非阻塞
-当 R 进行初次同步时，它可以使用旧数据集处理查询请求，假设在 redis.conf 中配置了让 Redis 这样做。否则，你可以配置如果复制流断开， Redis R 会返回一个 error 给客户端。但在初次同步后，旧数据集必须被删除，同时加载新的数据集。 R 在这个短暂的时间窗口内（如果数据集很大，会持续较长时间），会阻塞到来的连接请求。自 Redis 4.0 开始，可以配置 Redis 使删除旧数据集的操作在另一个不同的线程中进行，但是，加载新数据集的操作依然需要在主线程中进行并且会阻塞 R
-- 复制可被用在可伸缩性，以便只读查询可以有多个 R 进行（例如 O(N) 复杂度的慢操作可以被下放到 R ），或者仅用于数据安全和高可用
-- 可使用复制来避免 M 将全部数据集写入磁盘造成的开销：一种典型的技术是配置你的 M 的 `redis.conf`以避免对磁盘进行持久化，然后连接一个 R ，配置为不定期保存或是启用 AOF。但是，这个设置必须小心处理，因为重启的 M 将从一个空数据集开始：如果一个 R 试图与它同步，那么这个 R 也会被清空！
-
-# 1 单机“危机”
-- 容量瓶颈
-- 机器故障
-- QPS瓶颈
-
-![](https://img-blog.csdnimg.cn/20200904132333485.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-
-- 一主多从
-![](https://img-blog.csdnimg.cn/20200904150126617.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
- 
-## 主从复制作用
-- 数据副本
-- 扩展读性能
-
-1. 一个M可以有多个R
-2. 一个R只能有一个M
-3. 数据流向是单向的，M => R
-
-# 2 实现复制的操作
-## 2.1 命令：Rof 
-- 异步执行，很耗时间
-![](https://img-blog.csdnimg.cn/20200904150903762.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-无需重启，但是不便于配置管理。
-## 2.2 配置
-```shell
-Rof ip port
-R-read-only yes
-```
-虽然可统一配置，但需重启。
-# 3 全量复制
-1. M执行`bgsave`，在本地生成一份RDB
-![](https://img-blog.csdnimg.cn/7c7eda61e919428d9090b56a4c61505e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-2. M将RDB发给salve，若RDB复制时间＞60s（repl-timeout）
-![](https://img-blog.csdnimg.cn/6cfb55cdc5ca441c9de0cc67d060590e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-则replica就会认为复制失败，可适当调大该参数(对于千兆网卡的机器，一般每秒传输100MB，6G文件，很可能超过60s)
-![](https://img-blog.csdnimg.cn/5a4ce67217eb4f41a622d630d9268ccf.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)	
-3. M在生成RDB时，会将所有新的写命令缓存在内存中，在salve保存了rdb之后，再将新的写命令复制给salve
-4. 若在复制期间，内存缓冲区持续消耗超过64MB，或者一次性超过256MB，则停止复制，复制失败
-5. R node接收到RDB之后，清空自己的旧数据，然后重新加载RDB到自己的内存中，同时**基于旧的数据版本**对外提供服务
-6. 如果R开启了AOF，那么会立即执行BGREWRITEAOF，重写AOF
-
-![](https://img-blog.csdnimg.cn/20210401150137560.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-RDB生成、RDB通过网络拷贝、R旧数据的清理、R aof rewrite，很耗费时间。
-如果复制的数据量在4G~6G之间，那么很可能全量复制时间消耗到1分半到2分钟。
-## 3.1 全量复制开销
-1. bgsave时间
-2. RDB文件网络传输时间
-3. 从节点清空数据时间
-4. 从节点加载RDB的时间
-5. 可能的AOF重写时间
-## 3.2 全量同步细节
-M 开启一个后台save进程，以便生成一个 RDB 文件。同时它开始缓冲所有从客户端接收到的新的写入命令。当后台save完成RDB文件时， M 将该RDB数据集文件发给 R， R会先将其写入磁盘，然后再从磁盘加载到内存。再然后 M 会发送所有缓存的写命令发给 R。这个过程以指令流的形式完成并且和 Redis 协议本身的格式相同。
-
-当主从之间的连接因为一些原因崩溃之后， R 能够自动重连。如果 M 收到了多个 R 要求同步的请求，它会执行一个单独的后台保存，以便于为多个 R 服务。
-### 加速复制
-默认情况下，M接收SYNC命令后执行BGSAVE，将数据先保存到磁盘，若磁盘性能差，则**写入磁盘会消耗大量性能**。
-因此在Redis 2.8.18时进行改进，可以设置无需写入磁盘直接发生RDB快照给R，加快复制速度。
-
-复制SYNC策略：磁盘或套接字。仅仅接受差异就无法继续复制过程的新副本和重新连接副本需要进行所谓的“完全同步”。 RDB文件从主数据库传输到副本数据库。传输可以通过两种不同的方式进行：1）支持磁盘：Redis主服务器创建一个新过程，将RDB文件写入磁盘。后来，该文件由父进程逐步传输到副本。 2）无盘：Redis主服务器创建一个新进程，该进程将RDB文件直接写入副本套接字，而完全不接触磁盘。使用磁盘支持的复制，在生成RDB文件的同时，只要生成RDB文件的当前子级完成工作，就可以将更多副本排入队列并与RDB文件一起使用。如果使用无盘复制，则一旦传输开始，新的副本将排队，并且当当前副本终止时将开始新的传输。当使用无盘复制时，主服务器在开始传输之前等待一段可配置的时间（以秒为单位），以希望多个副本可以到达并且传输可以并行化。使用慢速磁盘和快速（大带宽）网络时，无盘复制效果更好。
-修改配置: 
-```java
-repl-diskless-sync yes (默认no)
-```
-# 4 增量复制
-1. 如果全量复制过程中，M-R网络连接中断，那么salve重连M时，会触发增量复制
-2. M直接从自己的backlog中获取部分丢失的数据，发送给R node
-3. msater就是根据R发送的psync中的offset来从backlog中获取数据的
-![](https://img-blog.csdnimg.cn/20200905001841252.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-# 5  M关闭持久化时的复制安全性
-在使用 Redis 复制功能时的设置中，推荐在 M 和 R 中启用持久化。
-当不可能启用时，例如由于非常慢的磁盘性能而导致的延迟问题，应该配置实例来避免重启后自动重新开始复制。
-
-关闭持久化并配置了自动重启的 M 是危险的：
-1. 设置节点 A 为 M 并关闭它的持久化设置，节点 B 和 C 从 节点 A 复制数据
-2. 节点 A 宕机，但它有一些自动重启系统可重启进程。但由于持久化被关闭了，节点重启后其数据集是空的！
-3. 这时B、C 会从A复制数据，但A数据集空，因此复制结果是它们会销毁自身之前的数据副本！
-
-当 Redis Sentinel 被用于高可用并且 M 关闭持久化，这时如果允许自动重启进程也是很危险的。例如， M 可以重启的足够快以致于 Sentinel 没有探测到故障，因此上述的故障模式也会发生。
-任何时候数据安全性都是很重要的，所以如果 M 使用复制功能的同时未配置持久化，那么自动重启进程这项就该被禁用。
-
-> 用Redis主从同步，写入Redis的数据量太大，没加频次控制，导致每秒几十万写入，主从延迟过大，运维频频报警，在主库不挂掉的情况下，这样大量写入会不会造成数据丢失？
-> 若主从延迟很大，数据会堆积到redis主库的发送缓冲区，会导致主库OOM。
-
-# 6 复制工作原理
-- 每个 M 都有一个 replication ID ：一个较大的伪随机字符串，标记了一个给定的数据集。
-![](https://img-blog.csdnimg.cn/20200905221152322.png#pic_center)
-每个 M 也持有一个偏移量，M 将自己产生的复制流发送给 R 时，发送多少个字节的数据，自身的偏移量就会增加多少，目的是当有新的操作修改自己的数据集时，它可据此更新 R 的状态。
-![](https://img-blog.csdnimg.cn/20200905221515483.png#pic_center)
-
-复制偏移量即使在没有一个 R 连接到 M 时，也会自增，所以基本上每一对给定的
-`Replication ID, offset`
-都会标识一个 M 数据集的确切版本。
-
-## psync
-R使用`psync`从M复制，psync runid offset
-![](https://img-blog.csdnimg.cn/20200905233312575.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-
-M会根据自身情况返回响应信息：
-- 可能是FULLRESYNC runid offset触发全量复制
-- 可能是CONTINUE触发增量复制
-
-R 连接到 M 时，它们使用 PSYNC 命令来发送它们记录的旧的 M replication ID 和它们至今为止处理的偏移量。通过这种方式， M 能够仅发送 R 所需的增量部分。
-但若 M 的缓冲区中没有足够的命令积压缓冲记录，或者如果 R 引用了不再知道的历史记录（replication ID），则会转而进行一个全量重同步：在这种情况下， R 会得到一个完整的数据集副本，从头开始。即：
-- 若R重连M，那么M仅会复制给R缺少的部分数据
-- 若第一次连接M，那么会触发全量复制
-
-Redis使用复制保证数据同步，以2.8版本为界：
-## 2.8前性能较差的复制和命令传播
-首先是从服务器发生同步操作sync，主服务器执行bgsave生成一个全量RDB文件，然后传输给从服务器。
-同时主服务器会把这一过程中执行的写命令写入缓存区。从服务器会把RDB文件进行一次全量加载。
-加载完毕后，主服务器会把缓存区中的写命令传给从服务器。从服务器执行命令后，主从服务器的数据就一致了。
-这种方式每次如果网络出现故障，故障重连后都要进行全量数据的复制。对主服务器的压力太大，也会增加主从网络传输的资源消耗。
-## 2.8后的优化
-增加部分重同步功能，就是同步故障后的一部分数据，而非全量数据。这种优化在量级非常大的情况下效率提升很明显。
-
-## 4.0的PSYNC2
-# 7 复制的完整流程
-![](https://img-blog.csdnimg.cn/20190705083122154.png)
-> R如果跟M有网络故障，断开连接会自动重连。
-> M如果发现有多个R都重新连接，仅会启动一个rdb save操作，用一份数据服务所有R。
-
-1. R启动，仅保存M的信息，包括M的`host`和`ip`，但复制流程尚未开始M host和ip配置在 `redis.conf` 中的 Rof
-2. R内部有个定时任务，每s检查是否有新的M要连接和复制，若发现，就跟M建立socket网络连接。
-3. R发送ping命令给M
-4. 口令认证 - 若M设置了requirepass，那么salve必须同时发送Mauth的口令认证
-5. M **第一次执行全量复制**，将所有数据发给R
-6. M后续持续将写命令，异步复制给R
-
-## heartbeat
-主从节点互相都会发送heartbeat信息。
-M默认每隔10秒发送一次heartbeat，salve node每隔1秒发送一个heartbeat。
-
-# 8 断点续传
-Redis 2.8开始支持主从复制的断点续传
-![](https://img-blog.csdnimg.cn/2019070508465819.png)
-
-主从复制过程，若网络连接中断，那么可以接着上次复制的地方，继续复制下去，而不是从头开始复制一份。
-
-## M和R都会维护一个offset
-- M在自身基础上累加offset，R亦是
-- R每秒都会上报自己的offset给M，同时M保存每个R的offset
-
-M和R都要知道各自数据的offset，才能知晓互相之间的数据不一致情况。
-
-## backlog
-M会在内存中维护一个backlog，默认1MB。M给R复制数据时，也会将数据在backlog中同步写一份。
-
-`backlog主要是用做全量复制中断时候的增量复制`。
-
-M和R都会保存一个replica offset还有一个M id，offset就是保存在backlog中的。若M和R网络连接中断，R会让M从上次replica offset开始继续复制。但若没有找到对应offset，就会执行resynchronization。
-
-## M run id
-- info server，可见M run id
-![](https://img-blog.csdnimg.cn/20200905232843801.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-
-根据host+ip定位M node，是不靠谱的，如果M node重启或者数据出现了变化，那么R node应该根据不同的run id区分，run id不同就做全量复制。
-如果需要不更改run id重启redis，可使用：
-```shell
-redis-cli debug reload
-```
-# 9 无磁盘化复制
-M在内存中直接创建RDB，然后发送给R，不会在自己本地持久化。
-只需要在配置文件中开启` repl-diskless-sync yes `即可.
-```shell
-等待 5s 再开始复制，因为要等更多 R 重连
-repl-diskless-sync-delay 5
-```
-# 10 处理过期key
-Redis 的过期机制可以限制 key 的生存时间。此功能取决于 Redis 实例计算时间的能力，但是，即使使用 Lua 脚本更改了这些 key，Redis Rs 也能正确地复制具有过期时间的 key。
-
-为实现功能，Redis 不能依靠主从使用同步时钟，因为这是一个无法解决的问题并且会导致 race condition 和数据不一致，所以 Redis 使用三种主要的技术使过期的 key 的复制能够正确工作：
-1. R 不会让 key 过期，而是等待 M 让 key 过期。当一个 M 让一个 key 到期（或由于 LRU 删除）时，它会合成一个 DEL 命令并传输到所有 R
-2. 但由于这是 M 驱动的 key 过期行为，M 无法及时提供 DEL 命令，所以有时 R 的内存中仍可能存在逻辑上已过期的 key 。为处理该问题，R 使用它的逻辑时钟以报告只有在不违反数据集的一致性的读取操作（从主机的新命令到达）中才存在 key。用这种方法，R 避免报告逻辑过期的 key 仍然存在。在实际应用中，使用 R 程序进行缩放的 HTML 碎片缓存，将避免返回已经比期望的时间更早的数据项
-3. 在Lua脚本执行期间，不执行任何 key 过期操作
-当一个Lua脚本运行时，概念上讲，M 中的时间是被冻结的，这样脚本运行的时候，一个给定的键要么存在or不存在。这可以防止 key 在脚本中间过期，保证将相同的脚本发送到 R ，从而在二者的数据集中产生相同的效果。
-
-一旦 R 被提升 M ，它将开始独立过期 key，而不需要任何旧 M 帮助。
-
-# 11 重新启动和故障转移后的部分重同步
-Redis 4.0 开始，当一个实例在故障转移后被提升为 M 时，它仍然能够与旧 M 的 R 进行部分重同步。为此，R 会记住旧 M 的旧 replication ID 和复制偏移量，因此即使询问旧的 replication ID，也可以将部分复制缓冲提供给连接的 R 。
-
-但是，升级的 R 的新 replication ID 将不同，因为它构成了数据集的不同历史记录。例如，M 可以返回可用，并且可以在一段时间内继续接受写入命令，因此在被提升的 R 中使用相同的 replication ID 将违反一对复制标识和偏移对只能标识单一数据集的规则。
-
-另外，R 在关机并重新启动后，能够在 RDB 文件中存储所需信息，以便与 M 进行重同步。这在升级的情况下很有用。当需要时，最好使用 SHUTDOWN 命令来执行 R 的保存和退出操作。
-
-> 参考
-> - https://raw.githubusercontent.com/antirez/redis/2.8/00-RELEASENOTES
-> - https://redis.io/topics/replication
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\272\213\345\212\241\345\222\214\344\271\220\350\247\202\351\224\201.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\272\213\345\212\241\345\222\214\344\271\220\350\247\202\351\224\201.md"
deleted file mode 100644
index a04d3073b0..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\272\213\345\212\241\345\222\214\344\271\220\350\247\202\351\224\201.md"
+++ /dev/null
@@ -1,191 +0,0 @@
-MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可一次执行多个命令， 并且带有以下两个重要的保证：
-- 事务是一个单独的隔离操作：事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中，不会被其他客户端发送来的命令请求所打断
-- 事务是一个原子操作：事务中的命令要么全部被执行，要么全部都不执行
-
-得益于单线程模型的内存处理，没有并发事务，所以无隔离级别概念。
-
-EXEC 命令负责触发并执行事务中的所有命令：
-- 如果客户端在使用 MULTI 开启了一个事务之后，却因为断线而没有成功执行 EXEC ，那么事务中的所有命令都不会被执行
-- 如果客户端成功在开启事务之后执行 EXEC ，那么事务中的所有命令都会被执行
-
-当使用 AOF 时， Redis 会使用单个 write(2) 命令将事务写入到磁盘中。
-
-然而，如果 Redis 服务器因为某些原因被管理员杀死或硬件故障，则可能只有部分事务命令会被成功落盘。
-
-如果 Redis 在重新启动时发现 AOF 文件出了这样问题，则它会退出，并汇报一个错误。
-
-使用redis-check-aof程序可修复这一问题：它会移除 AOF 文件中不完整事务的信息，确保服务器顺利启动。
-
-从 2.2 版本开始，Redis 还可以通过乐观锁（optimistic lock）实现 CAS （check-and-set）操作。
-
-# 用法
-MULTI 命令用于开启一个事务，它总返回 OK 。 
-MULTI 执行之后， 客户端可以继续向服务器发送任意多条命令， 这些命令不会立即被执行， 而是被放到一个队列， 当 EXEC命令被调用时， 所有队列中的命令才会被执行。
-
-调用 DISCARD ， 客户端可以清空事务队列， 并放弃执行事务。
-
-以下是一个事务例子， 它原子地增加了 foo 和 bar 两个键的值：
-
-```bash
-> MULTI
-OK
-> INCR foo
-QUEUED
-> INCR bar
-QUEUED
-> EXEC
-1) (integer) 1
-2) (integer) 1
-```
-
-EXEC 命令的响应是一个数组， 数组中的每个元素都是执行事务中的命令所产生的回复。 其中， 回复元素的先后顺序和命令发送的先后顺序一致。
-
-当客户端处于事务状态时， 所有传入的命令都会返回一个内容为 QUEUED 的状态回复（status reply）， 这些被入队的命令将在 EXEC 命令被调用时执行。
-
-# 事务中的错误
-使用事务时可能会遇上以下两种错误：
-## 事务在执行 EXEC 之前
-入队的命令可能会出错。比如说，命令可能会产生语法错误（参数数量错误，参数名错误，等等），或者其他更严重的错误，比如内存不足（如果服务器使用 maxmemory 设置了最大内存限制的话）。
-
-发生在 EXEC 执行之前的错误，客户端以前的做法是检查命令入队所得的返回值：
-- 如果命令入队时返回 QUEUED ，则入队成功
-- 否则，即入队失败
-如果有命令在入队时失败，则大部分客户端都会停止并取消该事务。
-
-但从 Redis 2.6.5 开始，服务器会对命令入队失败的情况进行记录，并在客户端调用 EXEC 命令时，拒绝执行并自动放弃这个事务。
-
-Redis 2.6.5 以前， Redis 只执行事务中那些入队成功的命令，而忽略那些入队失败的命令。 而新的处理方式则使得在流水线（pipeline）中包含事务变得简单，因为发送事务和读取事务的响应都只需要和服务器进行一次通讯。
-
-## 命令可能在 EXEC 调用之后失败
-事务中的命令可能处理了错误类型的键，比如将列表命令用在了字符串键上面，诸如此类。
-至于那些在 EXEC 命令执行之后所产生的错误， 并没有对它们进行特别处理： 即使事务中有某个/某些命令在执行时产生了错误， 事务中的其他命令仍然会继续执行。
-
-从协议的角度来看这个问题，会更容易理解一些。 以下例子中， LPOP 命令的执行将出错， 尽管调用它的语法是正确的：
-
-```bash
-Trying 127.0.0.1...
-Connected to localhost.
-Escape character is '^]'.
-MULTI
-+OK
-SET a 3
-abc
-+QUEUED
-LPOP a
-+QUEUED
-EXEC
-*2
-+OK
--ERR Operation against a key holding the wrong kind of value
-```
-
-EXEC 返回两条bulk-string-reply： 第一条是 OK ，而第二条是 -ERR 。 至于怎样用合适的方法来表示事务中的错误， 则是由客户端自己决定的。
-
-- Redis 不会停止执行事务中的命令
-**即使事务中有某条/某些命令执行失败了， 事务队列中的其他命令仍然会继续执行**。
-
-以下例子展示的是另一种情况， 当命令在入队时产生错误， 错误会立即被返回给客户端：
-
-```bash
-MULTI
-+OK
-INCR a b c
--ERR wrong number of arguments for 'incr' command
-```
-
-因为调用 INCR 命令的参数格式不正确， 所以这个 INCR 命令入队失败。
-
-# 为什么 Redis 不支持回滚（roll back）
-使用过MySQL的， 都会好奇为何 “Redis 在事务失败时不进行回滚，而是继续执行余下的命令”。
-
-## 优点
-- Redis 命令只会因为错误的语法而失败（并且这些问题不能在入队时发现）或命令用在了错误类型的key：即从实用性来看，失败的命令是由编程错误导致，而这些错误应该在开发过程中被发现，而不该出现在生产环境。
-- 因为无需支持回滚，所以 Redis 可保持简单快速
-
-有人认为 Redis 处理事务的做法会产生 bug ， 但注意通常情况下， 回滚并不能解决编程错误带来的问题。 举个例子， 如果你本来想通过 INCR 命令将value加上 1 ， 却不小心加上了 2 ， 又或者对错误类型的键执行了 INCR ， 回滚是没有办法处理这些情况的。
-
-# 放弃事务
-当执行 DISCARD 命令时， 事务会被放弃， 事务队列会被清空， 并且客户端会从事务状态中退出：
-
-```bash
-> SET foo 1
-OK
-> MULTI
-OK
-> INCR foo
-QUEUED
-> DISCARD
-OK
-> GET foo
-"1"
-```
-
-# check-and-set 操作实现乐观锁
-WATCH 命令可以为 Redis 事务提供 check-and-set （CAS）行为。
-
-被 WATCH 的键会被监视，并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了， 那么整个事务都会被取消， EXEC 返回nil-reply来表示事务已经失败。
-
-## 案例
-原子地为某值+1（假设 INCR 不存在）。
-可能会这样做：
-```bash
-val = GET mykey
-val = val + 1
-SET mykey $val
-```
-这个实现在只有一个客户端时执行得很好。 但多个客户端同时对同一个键操作时， 就会产生竞态。
-比如两个客户端 A 和 B 都读取了键原来的值， 比如 10 ， 则两个客户端都会将键的值设为 11 ， 但正确的结果应该是 12。
-
-WATCH即可解决这类问题：
-
-```bash
-WATCH mykey
-val = GET mykey
-val = val + 1
-MULTI
-SET mykey $val
-EXEC
-```
-在 WATCH 执行后， EXEC 执行前， 若有其他客户端修改了 mykey 的值， 则当前客户端事务失败。 程序需要做的， 就是不断重试该操作， 直到没有发生冲突。
-
-这种锁称作乐观锁，大多数情况下， 不同的客户端会访问不同键， 碰撞情况一般很少， 所以通常并不需要重试。
-
-## WATCH
-WATCH 使得 EXEC 命令需要有条件地执行：事务只能在所有被监视键都没有被修改的前提下执行， 如果这个前提不能满足的话，事务就不会被执行。
-
-WATCH 命令可被调用多次。 对键的监视从 WATCH 执行之后开始生效， 直到调用 EXEC 为止。
-
-用户还可以在单个 WATCH 命令中监视任意多个键，如下：
-```bash
-redis> WATCH key1 key2 key3
-OK
-```
-
-当 EXEC 被调用时， 不管事务是否成功执行， 对所有键的监视都会被取消。
-
-另外， 当客户端断开连接时， 该客户端对键的监视也会被取消。
-
-使用无参的 UNWATCH 命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务， 有时候程序需要同时对多个键进行加锁， 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时， 就可以使用 UNWATCH 命令来取消目前对键的监视， 中途放弃这个事务， 并等待事务的下次尝试。
-
-### 使用 WATCH 实现 ZPOP
-WATCH 可以用于创建 Redis 没有内置的原子操作。举个例子， 以下代码实现了原创的 ZPOP 命令， 它可以原子地弹出有序集合中分值（score）最小的元素：
-
-```bash
-WATCH zset
-element = ZRANGE zset 0 0
-MULTI
-ZREM zset element
-EXEC
-```
-
-程序只要重复执行这段代码， 直到 EXEC 的返回值不是nil-reply响应即可。
-
-# Redis 脚本和事务
-Redis 中的脚本本身就是一种事务， 所以任何在事务可完成的事， 在脚本里面也能完成。
-一般使用脚本还更简单更快。
-
-因为脚本 Redis 2.6 才引入， 所以导致 Redis 同时存在两种处理事务的方案。
-
-不过Redis并不打算在短时间内就移除事务， 因为事务提供了一种即使不使用脚本， 也可避免竞争条件的方法， 而且事务实现不复杂。
-
-不过不远的将来， 可能所有用户都会只使用脚本实现事务。真这样，则Redis会废弃并移除事务。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\272\214\350\277\233\345\210\266\345\256\211\345\205\250.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\272\214\350\277\233\345\210\266\345\256\211\345\205\250\347\232\204\345\216\237\347\220\206.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\272\214\350\277\233\345\210\266\345\256\211\345\205\250.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\272\214\350\277\233\345\210\266\345\256\211\345\205\250\347\232\204\345\216\237\347\220\206.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\345\270\203\345\274\217\346\246\202\350\247\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\345\270\203\345\274\217\346\246\202\350\247\210.md"
new file mode 100644
index 0000000000..7cd62c9c97
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\345\270\203\345\274\217\346\246\202\350\247\210.md"
@@ -0,0 +1,52 @@
+# 1 Redis分布式算法原理
+
+## 1.1 传统分布式算法
+
+![举个例子](http://upload-images.jianshu.io/upload_images/4685968-5c1cb1df981c0b7c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+ ![](http://upload-images.jianshu.io/upload_images/4685968-29e7f518af44976c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-42557a8e685b46d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![蓝色表与4个节点时相同槽](http://upload-images.jianshu.io/upload_images/4685968-0d4f3d16c4265f4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-a94bed84b8cb3e26.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-e927570534e48183.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 1.2 Consistent hashing一致性算法原理
+- 环形 hash 空间：按照常用的 hash 算法来将对应的 key 哈希到一个具有 232 个桶的空间，即（0-232-1）的数字空间中，现在我们将这些数字头尾相连，想象成一个闭合的环形
+- 把数据通过一定的 hash 算法映射到环上
+- 将机器通过一定的 hash 算法映射到环上
+- 节点按顺时针转动，遇到的第一个机器，就把数据放在该机器上 
+![](http://upload-images.jianshu.io/upload_images/4685968-2476ec31828ad0c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-02b2746f7824f19c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![ ](http://upload-images.jianshu.io/upload_images/4685968-22f8b7bff7177de6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+在移除 or 添加一个 cache 时，他能够尽可能小的改变已经存在 key 映射关系
+![](http://upload-images.jianshu.io/upload_images/4685968-2f12b00ac8807fce.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![删除CacheB后,橙色区为被影响范围](http://upload-images.jianshu.io/upload_images/4685968-70f0b98dbeee1a9f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-55a087651d90f824.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![也许心中的分布式这样的](http://upload-images.jianshu.io/upload_images/4685968-69729b4979a27a37.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![但实际会这样拥挤-即倾斜性](http://upload-images.jianshu.io/upload_images/4685968-b26fc49122f8898b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+##1.3 Hash倾斜性
+![](http://upload-images.jianshu.io/upload_images/4685968-52cc24cb673f8313.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+为解决此类事件,引入了虚拟节点
+![](http://upload-images.jianshu.io/upload_images/4685968-9a233266814a9bfd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](http://upload-images.jianshu.io/upload_images/4685968-0e85f1c5a6b813cf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![服务器台数n,新增服务器数m](http://upload-images.jianshu.io/upload_images/4685968-c3f4ec8e890d2e9d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#2 Redis分布式环境配置
+![启动两台Redis-server](http://upload-images.jianshu.io/upload_images/4685968-420a6f79a5543ddb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 3  Redis分布式服务端及客户端启动
+![](http://upload-images.jianshu.io/upload_images/4685968-3d82384b1a8dd628.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#4封装分布式Shared Redis API
+##4.1 SharedJedis源码解析
+![ShardedJedis.png](http://upload-images.jianshu.io/upload_images/4685968-9c7c2de780d0c75a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+##封装RedisSharedPool
+##测试代码
+##集成测试
+#5 Redis分布式环境验证
+![](http://upload-images.jianshu.io/upload_images/4685968-19822fc1998dd190.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 6 集群和分布式
+
+- 分布式：不同的业务模块拆分到不同的机器上，解决高并发的问题。 工作形态 redis服务器各工作方式不同一般称为Redis分布式
+
+- 集群：同一个业务部署在多台机器上，提高系统可用性 是物理形态,一般称Tomcat集群。
+集群可能运行着一个或多个分布式系统，也可能根本没有运行分布式系统；分布式系统可能运行在一个集群上，也可能运行在不属于一个集群的多台（2台也算多台）机器上。
+
+你前台页面有10个用户，分别发送了1个请求，那么如果不是集群的话，那这10个请求需要并行在一台机器上处理，如果每个请求都是1秒钟，那么就会有一个人等待10秒钟，有一个人等待9秒钟，以此类推；那么现在在集群环境下，10个任务并分发到10台机器同时进行，那么每个人的等待时间都还是1秒钟；
+当然，你说的浪费确实是，如果系统的并发不是很高，只有一台或者两台机器就能处理的话，那确实是有很大的浪费。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201\350\256\276\350\256\241\346\274\224\350\277\233.md"
similarity index 75%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201\350\256\276\350\256\241\346\274\224\350\277\233.md"
index 48a30c757f..a39cf41310 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\345\270\203\345\274\217\351\224\201\350\256\276\350\256\241\346\274\224\350\277\233.md"
@@ -1,7 +1,6 @@
-
 # 1 本地锁
 常用的即 synchronize 或 Lock 等 JDK 自带的锁，只能锁住当前进程，仅适用于单体架构服务。
-而在分布式多服务实例场景下必须使用分布式锁![](https://img-blog.csdnimg.cn/20210104194442558.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+而在分布式多服务实例场景下必须使用分布式锁![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0bcaaa28e0c74e119df12e2d3c0c61e4~tplv-k3u1fbpfcp-zoom-1.image)
 
 # 2 分布式锁
 ##  2.1 分布式锁的原理
@@ -11,7 +10,7 @@
 - 否则等待，直到释放锁
  可通过自旋方式自旋
 
-“占坑”可以去Redis、DB、任何所有服务都能访问的地方。![](https://img-blog.csdnimg.cn/20210104194550475.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+“占坑”可以去Redis、DB、任何所有服务都能访问的地方。![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f7962905d0b747c8af8299d8260c8f65~tplv-k3u1fbpfcp-zoom-1.image)
 ##  2.2 分布式锁演进
 ### 一阶段
 ```java
@@ -29,12 +28,13 @@ if(lock) {
 	return getCatalogJsonFromDbwithRedisLock();
 }
 ```
-![](https://img-blog.csdnimg.cn/img_convert/8ec86d95e0679b0d1e7e6e0e5b9ab292.png)
+
+![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/acd72bf7562d4320a55998a5443059d5~tplv-k3u1fbpfcp-watermark.image)
 #### 问题场景
 - setnx占好了坑，但是业务代码异常或程序在执行过程中宕机，即没有执行成功删除锁逻辑，导致死锁
 解决方案：设置锁的自动过期，即使没有删除，会自动删除。
 ### 阶段二
-![](https://img-blog.csdnimg.cn/20210105185953183.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a0a0ba673ec14337a67565ab9ea3726a~tplv-k3u1fbpfcp-zoom-1.image)
 
 
 ```java
@@ -64,7 +64,7 @@ if(lock) {
 
 
 ### 阶段三
-![](https://img-blog.csdnimg.cn/20210105192411515.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3c230951bf234056b3ebfd984d3ebabf~tplv-k3u1fbpfcp-zoom-1.image)
 
 
 
@@ -89,7 +89,7 @@ else {
 ```
 
 ### 阶段四
-![](https://img-blog.csdnimg.cn/20210105192929558.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00c3436cff7c451b87b869d6f26fe6fd~tplv-k3u1fbpfcp-zoom-1.image)
 
 已经拿到了 lockvalue ，有了 UUID，但是过期了现在！其他人拿到所锁设置了新值，于是 if 后将别人的锁删了！！也就是删除锁不是原子操作。
 
@@ -108,7 +108,7 @@ if(uuid.equals(lockValue)) {
 删除锁必须保证原子性。使用redis+Lua脚本。
 
 ### 阶段五
- - 确保加锁/解锁都是原子操作![](https://img-blog.csdnimg.cn/20210105210314181.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+ - 确保加锁/解锁都是原子操作![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79a5843dacc646719ee61e0cfb717f19~tplv-k3u1fbpfcp-zoom-1.image)
 
 ```lua
 String script = 
@@ -118,7 +118,7 @@ String script =
 		return 0 
 	end";
 ```
-保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性。 更难的事情，锁的自动续期。 
+保证加锁【占位+过期时间】和删除锁【判断+删除】的原子性。 
 # 总结
 其实更麻烦的事情，还有锁的自动续期。所以不管是大厂还是中小型公司，我们都是直接选择解决了这些问题的 Redisson！不重复造轮子，但也要知道该框架到底解决了哪些问题，方便我们遇到问题时也能快速排查定位。
 下一篇我们就开始 redisson 讲解他是如何做到锁续期的~
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\347\211\207.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\347\211\207.md"
index 5843913023..4acf678d93 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\347\211\207.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\210\206\347\211\207.md"
@@ -2,175 +2,57 @@
 > 本文已收录在github，欢迎 star/fork：
 > https://github.com/Wasabi1234/Java-Interview-Tutorial
 
-分片，Redis 数据的分布方式，分片就是将数据拆分到多个 Redis 实例，这样每个实例将只是所有键的一个子集。
-
-# 1 为什么要分区？
-随着请求量和数据量的增加，一台机器已无法满足需求，就需要把数据和请求分散到多台机器，这就需要引入分布式存储。
-
-## 1.1 分布式存储的特性
-- 增强可用性
-如果数据库的某个节点出现故障，在其他节点的数据仍然可用
-- 维护方便
-如果数据库的某个节点出现故障，需要修复数据，只需修复该节点
-- 均衡I/O
-可以把不同的请求映射到各节点以平衡 I/O，改善整个系统性能
-- 改善查询性能
-对分区对象的查询可以仅搜索自己关心的节点，提高检索速度
-
-分布式存储首先要解决把整个数据集按分区规则映射到多个节点的问题，即把数据集划分到多个节点，每个节点负责整体数据的一个子集：
-1. 分片可以让Redis管理更大的内存，Redis将可以使用所有机器的内存。如果没有分区，你最多只能使用一台机器的内存。
-2. 分片使Redis的计算能力通过简单地增加计算机得到成倍提升，Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。
+分片就是将数据拆分到多个 Redis 实例，这样每个实例将只是所有键的一个子集。
 
-# 有哪些分片方案？
-假设：
-- 有 4 个 Redis 实例 R0，R1，R2，R3
-- 很多表示用户的键，像 user:1，user:2
+# 1 分片有什么作用？
+1. 分片可以让Redis管理更大的内存，Redis将可以使用所有机器的内存。如果没有分区，你最多只能使用一台机器的内存。
+2. 分片使Redis的计算能力通过简单地增加计算机得到成倍提升,Redis的网络带宽也会随着计算机和网卡的增加而成倍增长。
 
+# 2 分片方案
+假想我们有 4 个 Redis 实例 R0，R1，R2，R3；
+很多表示用户的键，像 user:1，user:2等。
 有如下方案可映射键到指定 Redis 节点。
 
-## 范围分区(range partitioning)
-也叫顺序分区，最简单的分区方式。通过映射对象的范围到指定的 Redis 实例来完成分片。
-
-- 假设用户从 ID 1 ~ 33 进入实例 R0，34 ~ 66 进入R1
-
-![](https://img-blog.csdnimg.cn/20210430155203966.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 优点
-- 键值业务相关
-- 可顺序访问
-同一范围内的范围查询不需要跨节点，提升查询速度
-- 支持批量操作
-
-###  缺点
-- 数据分散度易倾斜
-- 需要一个映射范围到实例的表格。该表需要管理，不同类型的对象都需要一个表，所以范围分片在 Redis 中常常并不可取，因这要比其他分片可选方案低效得多。
-
-### 产品
-- BigTable
-- HBase
-- MySQL
-- Oracle
-
-## 2.2 哈希分区(hash partitioning)
-传统分布式算法，适于任何键，不必是 `object_name:<id>` 形式：
-1. 使用一个哈希函数(例如crc32) ，将key转为一个数字，比如93024922
-2. 对该数据进行取模，将其转换为一个 0 到 3 之间数字，该数字即可映射到4个 节点之一。93024922 模 4 等于 2，所以键 foobar 存储到 R2
-![](https://img-blog.csdnimg.cn/20210430155222501.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 2.2.1 分类
-#### 2.2.1.1 节点取余分区
-##### 4redis节点
-![](https://img-blog.csdnimg.cn/20210505141558125.png)
-20 个数据
-![](https://img-blog.csdnimg.cn/20210505141612173.png)
-数据分布
-![](https://img-blog.csdnimg.cn/20210505141633953.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-##### 5redis节点
-数据分布
-![](https://img-blog.csdnimg.cn/20210505141711115.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-> 蓝色表与4个节点时是相同的槽。
-
-可见，redis0只有20命中、redis1只有1命中、redis2只有2命中、redis3只有3命中。最终命中率是: 4/20=20%
-
--  hash(key) % nodes
-![](https://img-blog.csdnimg.cn/20210430170928276.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-#### 数据迁移
-当添加一个节点时
-![](https://img-blog.csdnimg.cn/20210430171024286.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 多倍扩容
-![](https://img-blog.csdnimg.cn/20210430171121829.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-客户端分片：哈希+取余。
-节点伸缩：数据节点关系变化，导致数据迁移。迁移数量和添加节点数量有关：建议翻倍扩容。
-优点：实现简单
-缺点：当扩容或收缩节点时，需要迁移的数据量大（虽然翻倍扩容可以相对减少迁移量）
-
-#### 2.2.1.2  一致性哈希分区（Consistent hashing）
-##### 原理
-- 环形 hash 空间
-按常用 hash 算法，将对应的 key hash到一个具有 `2^32` 个桶的空间，即（0 ~  `2^32` - 1）的数字空间中。
-
-将这些数字头尾相连，想象成一个闭合环形：
-- 把数据通过一定的 hash 算法映射到环上
-- 将机器通过一定的 hash 算法映射到环上
-- 节点按顺时针转动，遇到的第一个机器，就把数据放在该机器
-
-- 把对象映射到hash空间
-![](https://img-blog.csdnimg.cn/20210511100911394.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-#### 把cache映射到hash空间
-基本思想就是将对象和cache都映射到同一个hash数值空间中， 并且使用相同的hash算法
-
-```bash
-hash(cache A) = key A;
-hash(cache C) = key C;
-```
-在移除 or 添加一个 cache 时，能够尽可能小的改变已存在的 key 映射关系
-#### Consistent hashing 一致性算法
-![](https://img-blog.csdnimg.cn/c9800de2da2844eb93882a508076573c.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-##### 移除 Cache
-- 删除CacheB后,橙色区为被影响范围
-![](https://img-blog.csdnimg.cn/d1ef70c6454e4346bcdbe69c4c8a919e.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-##### 添加Cache![](https://img-blog.csdnimg.cn/dfe4a2f89dae48e4abf7017814330f7c.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 理想的分布式
-![](https://img-blog.csdnimg.cn/20210505145541903.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-现实却很拥挤-即倾斜性：
-![](https://img-blog.csdnimg.cn/20210505145603981.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-###### Hash倾斜性
-![](https://img-blog.csdnimg.cn/bf37282868b044848234ac52d86b748f.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-为解决该问题，引入虚拟节点
-![](https://img-blog.csdnimg.cn/503943a48f194bf385efecd88bfc37f1.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- 虚拟节点
-![](https://img-blog.csdnimg.cn/20210505145715687.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-命中率计算公式：服务器台数n，新增服务器数m
-```java
-(1 - n/(n + m) ) * 100%
-```
-
-![](https://img-blog.csdnimg.cn/20210430171238577.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 一致性哈希-扩容
-![](https://img-blog.csdnimg.cn/20210430171258523.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-客户端分片：哈希+顺时针(优化取余)
-节点伸缩：只影响邻近节点，但还是有数据迁移
-翻倍伸缩：保证最小迁移数据和负载均衡
-#### 2.2.1.3 虚拟槽哈希分区(Redis Cluster采用)
-- 虚拟槽分配
-![](https://img-blog.csdnimg.cn/img_convert/9c34e9351e74c1aacf6954ce66dba007.png)
-- 预设虚拟槽
-每个槽映射一个数据子集, 一般比节点数大
-- 良好的哈希函数
-例如CRC16
-- 服务端管理节点、槽、数据
-
-### 特点
-- 数据分散度高
-- 键值分布业务无关
-- 无法顺序访问
-- 支持批量操作
-
-### 产品
-- 一致性哈希Memcache
-- Redis Cluster
-- ...
+##  2.1 范围分片(range partitioning)
+最简单的分片方式。通过映射对象的范围到指定的 Redis 实例来完成分片。
+例如，可假设用户从 ID 0 ~ 10000 进入实例 R0，10001 ~ 20000 进入实例 R1。
+
+这套办法行得通，并且事实上在实践中被很多人采用。
+
+###  2.1.1 缺点
+需要一个映射范围到实例的表格。该表需要管理，不同类型的对象都需要一个表，所以范围分片在 Redis 中常常并不可取，因这要比其他分片可选方案低效得多。
+
+## 2.2 `哈希分片(hash partitioning)`
+该模式适于任何键，不必是 `object_name:<id>` 形式，就像这样简单：
+1. 使用一个哈希函数(例如crc32) ，将键名转为一个数字，比如93024922
+2. 对该数据进行取模，将其转换为一个 0 到 3 之间数字，该数字即可映射到4个 节点之一。93024922 模 4 等于 2，所以键 foobar 应当存储到 R2
 
 哈希分片的一种高端形式称为一致性哈希(consistent hashing)，被一些 **Redis 客户端和代理实现**。
 
 # 3 分片的各种实现
-可由软件栈中的不同部分来承担。
+分片可由软件栈中的不同部分来承担。
+
 ## 3.1 客户端分片
 客户端直接选择正确节点来写入和读取指定键，许多 Redis 客户端实现了客户端分片。
+
 ## 3.2 代理协助分片
 客户端发送请求到一个可以理解 Redis 协议的代理上，而不是直接发送到 Redis 实例。代理会根据配置好的分片模式，来保证转发我们的请求到正确的 Redis 实例，并返回响应给客户端。
-Redis 和 Memcached 的代理 Twemproxy 都实现了代理协助的分片。
+Redis 和 Memcached 的代理 Twemproxy 都实现了代理协助的分片.
+
 ## 3.3 查询路由
+
 可发送你的查询到一个随机实例，该实例会保证转发你的查询到正确节点。
 Redis 集群在客户端的帮助下，实现了查询路由的一种混合形式，请求不是直接从 Redis 实例转发到另一个，而是客户端收到重定向到正确的节点。
 
 # 4 分片的缺点
 Redis 的一些特性与分片在一起时玩的不是很好：
+
 - 涉及多个键的操作通常不支持。例如，无法直接对映射在两个不同 Redis 实例上的键执行交集
 - 涉及多个键的事务不能使用
 - 分片的粒度是键，所以不能使用一个很大的键来分片数据集，例如一个很大的sorted set
 - 当使用了分片，数据处理变得更复杂。例如，你需要处理多个 RDB/AOF 文件，备份数据时需要聚合多个实例和主机的持久化文件
 - 添加和删除容量也很复杂。例如，Redis 集群具有运行时动态添加和删除节点的能力来支持透明地再均衡数据，但是其他方式，像客户端分片和代理都不支持这个特性。但有一种称为预分片(Presharding)的技术在这一点上能帮上忙。
+
 # 5 数据存储or缓存？
 尽管无论是将 Redis 作为数据存储还是缓存，Redis 分片概念上都是一样的。
 - 但作为数据存储时有个重要局限：当 Redis 作为数据存储时，一个给定的键总是映射到相同 Redis 实例。
@@ -181,6 +63,7 @@ Redis 的一些特性与分片在一起时玩的不是很好：
 主要概念：
 - 如果 Redis 用作缓存，使用一致性哈希来实现伸缩扩展很容易
 - 如果 Redis 用作存储，使用固定的键到节点的映射，所以节点的数量必须固定不能改变。否则，当增删节点时，就需要一个支持再平衡节点间键的系统，当前只有 Redis 集群可以做到这点。
+
 # 6 预分片
 分片存在一个问题，除非我们使用 Redis 作为缓存，否则增加和删除节点都是件麻烦事，而使用固定的键和实例映射要简单得多。
 
@@ -191,6 +74,7 @@ Redis 的一些特性与分片在一起时玩的不是很好：
 这样，当数据存储增长，需要更多 Redis 服务器，你要做的就是简单地将实例从一台服务器移动到另外一台。当你新添加了第一台服务器，你就需要把一半的 Redis 实例从第一台服务器搬到第二台，以此类推。
 
 使用 Redis 复制，就可以在很小或者根本不需要停机的时间内完成移动数据：
+
 1. 在新服务器上启动一个空实例
 2. 移动数据，配置新实例为源实例的从服务
 3. 停止客户端
@@ -200,6 +84,8 @@ Redis 的一些特性与分片在一起时玩的不是很好：
 7. 最后关闭掉旧服务器上不再使用的实例
 
 # 7 Redis分片实现
+探讨完 Redis 分片理论，如何实践呢？又应该使用什么系统呢？
+
 ##  7.1 Redis 集群
 Redis 集群是自动分片和高可用的首选方式。一旦 Redis 集群以及支持 Redis 集群的客户端可用，Redis 集群将会成为 Redis 分片的事实标准。
 
@@ -216,8 +102,12 @@ Twemproxy 支持在多个 Redis 实例间自动分片，若节点不可用，还
 从根本上说，Twemproxy 是介于客户端和 Redis 实例之间的中间层，这就可以在最下的额外复杂性下可靠地处理我们的分片。这是当前建议的处理 Redis 分片的方式。
 
 ##  7.3 支持一致性哈希的客户端
+
 Twemproxy 之外的可选方案，是使用实现了客户端分片的客户端，通过一致性哈希或者别的类似算法。有多个支持一致性哈希的 Redis 客户端，例如 Redis-rb 和 Predis。
-查看完整的 Redis 客户端列表，看看是不是有支持你的编程语言的，并实现了一致性哈希的成熟客户端即可。
+查看完整的 Redis 客户端列表，看看是不是有支持你的编程语言的，并实现了一致性哈希的成熟客户端即可~
+
+参考
+- https://redis.io/topics/partitioning
+
 
-> 参考
-> - https://redis.io/topics/partitioning
\ No newline at end of file
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\221\275\344\273\244.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\221\275\344\273\244.md"
index e8d1ca0c3c..1704ae2675 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\221\275\344\273\244.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\221\275\344\273\244.md"
@@ -315,7 +315,7 @@ Redis 4.0.0 起，HSET 是万数值，允许多个字段/值对。
 如果 key 指定的哈希集不存在，会创建一个新的哈希集并与 key 关联
 如果字段已存在，该操作无效果
 
-# 4 list
+# 4 list 结构
 双向列表，适用于最新列表，关注列表
 
 ## 1. lpush
@@ -364,9 +364,9 @@ start 和 end 偏移量都是基于0的下标，即list的第一个元素下标
 移除并且返回 key 对应的 list 的第一个元素
 ### 返回值
 bulk-string-reply返回第一个元素的值，或者当 key 不存在时返回 nil。
-## 7 rpop
+##7.  rpop
 移除并返回存于 key 的 list 的最后一个元素。
-### 返回值
+###返回值
 bulk-string-reply最后一个元素的值，或者当 key 不存在的时候返回 nil
 ## 8 bl-pop key [key ...] timeout
 阻塞列表的弹出
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\244\215\345\210\266.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\244\215\345\210\266.md"
new file mode 100644
index 0000000000..fad2963a4d
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\244\215\345\210\266.md"
@@ -0,0 +1,207 @@
+> 全是干货的技术号：
+> 本文已收录在github，欢迎 star/fork：
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
+在Redis复制的基础上（不包括Redis Cluster或Redis Sentinel作为附加层提供的高可用功能），使用和配置主从复制非常简单，能使得从 Redis 服务器（下文称 slave）能精确得复制主 Redis 服务器（下文称 master）的内容。每次当 slave 和 master 之间的连接断开时， slave 会自动重连到 master 上，并且无论这期间 master 发生了什么， slave 都将尝试让自身成为 master 的精确副本。
+
+该系统的运行依靠三个重要机制：
+1. 当一个 master 实例和一个 slave 实例连接正常时， master 会发送一连串命令流保持对 slave 的更新，以便将自身数据集的改变复制给 slave，这包括客户端的写入、key 的过期或被逐出等等
+2. 当 master 和 slave 断连后，因为网络问题、或者是主从意识到连接超时， slave 重新连接上 master 并会尝试进行部分重同步：这意味着它会尝试只获取在断开连接期间内丢失的命令流
+3. 当无法进行部分重新同步时， slave 会请求全量重同步。这涉及到一个更复杂过程，比如master 需要创建所有数据的快照，将之发送给 slave ，之后在数据集更改时持续发送命令流到 slave
+
+
+Redis使用默认的异步复制，低延迟且高性能，适用于大多数 Redis 场景。但是，slave会异步确认其从master周期接收到的数据量。
+
+客户端可使用 WAIT 命令来请求同步复制某些特定的数据。但是，WAIT 命令只能确保在其他 Redis 实例中有指定数量的已确认的副本：在故障转移期间，由于不同原因的故障转移或是由于 Redis 持久性的实际配置，故障转移期间确认的写入操作可能仍然会丢失。
+
+# Redis 复制特点
+- Redis 使用异步复制，slave 和 master 之间异步地确认处理的数据量
+- 一个 master 可以拥有多个 slave
+- slave 可以接受其他 slave 的连接。除了多个 slave 可以连接到同一 master ， slave 之间也可以像层级连接其它 slave。Redis 4.0 起，所有的 sub-slave 将会从 master 收到完全一样的复制流
+- Redis 复制在 master 侧是非阻塞的，即master 在一或多 slave 进行初次同步或者是部分重同步时，可以继续处理查询请求
+- 复制在 slave 侧大部分也是非阻塞的。当 slave 进行初次同步时，它可以使用旧数据集处理查询请求，假设在 redis.conf 中配置了让 Redis 这样做的话。否则，你可以配置如果复制流断开， Redis slave 会返回一个 error 给客户端。但是，在初次同步之后，旧数据集必须被删除，同时加载新的数据集。 slave 在这个短暂的时间窗口内（如果数据集很大，会持续较长时间），会阻塞到来的连接请求。自 Redis 4.0 开始，可以配置 Redis 使删除旧数据集的操作在另一个不同的线程中进行，但是，加载新数据集的操作依然需要在主线程中进行并且会阻塞 slave
+- 复制可被用在可伸缩性，以便只读查询可以有多个 slave 进行（例如 O(N) 复杂度的慢操作可以被下放到 slave ），或者仅用于数据安全和高可用
+- 可使用复制来避免 master 将全部数据集写入磁盘造成的开销：一种典型的技术是配置你的 master 的 `redis.conf`以避免对磁盘进行持久化，然后连接一个 slave ，配置为不定期保存或是启用 AOF。但是，这个设置必须小心处理，因为重启的 master 将从一个空数据集开始：如果一个 slave 试图与它同步，那么这个 slave 也会被清空！
+
+# 1 单机“危机”
+- 容量瓶颈
+- 机器故障
+- QPS瓶颈
+
+
+
+![](https://img-blog.csdnimg.cn/20200904132333485.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+- 一主多从
+![](https://img-blog.csdnimg.cn/20200904150126617.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+## 主从复制作用
+- 数据副本
+- 扩展读性能
+
+## 总结
+1. 一个master可以有多个slave
+2. 一个slave只能有一个master
+3. 数据流向是单向的，master => slave
+
+# 2 实现复制的操作
+如下两种实现方式：
+
+### slaveof 命令
+- 异步执行,很耗时间
+![](https://img-blog.csdnimg.cn/20200904150903762.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+无需重启，但是不便于配置的管理。
+
+### 配置
+```shell
+slaveof ip port
+slave-read-only yes
+```
+虽然可统一配置，但是需要重启。
+
+# 3 全量复制
+1. master执行`bgsave`，在本地生成一份RDB快照client-output-buffer-limit slave 256MB 64MB 60
+2. master node将RDB快照发送给salve node，若RDB复制时间超过60秒（repl-timeout），那么slave node就会认为复制失败，可适当调大该参数(对于千兆网卡的机器，一般每秒传输100MB，6G文件，很可能超过60s)
+3. master node在生成RDB时，会将所有新的写命令缓存在内存中，在salve node保存了rdb之后，再将新的写命令复制给salve node
+4. 若在复制期间，内存缓冲区持续消耗超过64MB，或者一次性超过256MB，那么停止复制，复制失败
+5. slave node接收到RDB之后，清空自己的旧数据，然后重新加载RDB到自己的内存中，同时**基于旧的数据版本**对外提供服务
+6. 如果slave node开启了AOF，那么会立即执行BGREWRITEAOF，重写AOF
+
+RDB生成、RDB通过网络拷贝、slave旧数据的清理、slave aof rewrite，很耗费时间
+
+如果复制的数据量在4G~6G之间，那么很可能全量复制时间消耗到1分半到2分钟
+## 3.1 全量复制开销
+1. bgsave时间
+2. RDB文件网络传输时间
+3. 从节点清空数据时间
+4. 从节点加载RDB的时间
+5. 可能的AOF重写时间
+
+## 3.2 全量同步细节
+master 开启一个后台save进程，以便生成一个 RDB 文件。同时它开始缓冲所有从客户端接收到的新的写入命令。当后台save完成RDB文件时， master 将该RDB数据集文件发给 slave， slave会先将其写入磁盘，然后再从磁盘加载到内存。再然后 master 会发送所有缓存的写命令发给 slave。这个过程以指令流的形式完成并且和 Redis 协议本身的格式相同。
+
+当主从之间的连接因为一些原因崩溃之后， slave 能够自动重连。如果 master 收到了多个 slave 要求同步的请求，它会执行一个单独的后台保存，以便于为多个 slave 服务。
+
+# 4 增量复制
+
+1. 如果全量复制过程中，master-slave网络连接中断，那么salve重连master时，会触发增量复制
+2. master直接从自己的backlog中获取部分丢失的数据，发送给slave node
+3. msater就是根据slave发送的psync中的offset来从backlog中获取数据的
+![](https://img-blog.csdnimg.cn/20200905001841252.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+# 5  master关闭持久化时的复制安全性
+在使用 Redis 复制功能时的设置中，推荐在 master 和 slave 中启用持久化。
+当不可能启用时，例如由于非常慢的磁盘性能而导致的延迟问题，应该配置实例来避免重启后自动重新开始复制。
+
+关闭持久化并配置了自动重启的 master 是危险的：
+1. 设置节点 A 为 master 并关闭它的持久化设置，节点 B 和 C 从 节点 A 复制数据
+2. 节点 A 宕机，但它有一些自动重启系统可重启进程。但由于持久化被关闭了，节点重启后其数据集是空的！
+3. 这时B、C 会从A复制数据，但A数据集空，因此复制结果是它们会销毁自身之前的数据副本！
+
+当 Redis Sentinel 被用于高可用并且 master 关闭持久化，这时如果允许自动重启进程也是很危险的。例如， master 可以重启的足够快以致于 Sentinel 没有探测到故障，因此上述的故障模式也会发生。
+任何时候数据安全性都是很重要的，所以如果 master 使用复制功能的同时未配置持久化，那么自动重启进程这项就该被禁用。
+
+# 6 复制工作原理
+- 每个 master 都有一个 replication ID ：一个较大的伪随机字符串，标记了一个给定的数据集。
+![](https://img-blog.csdnimg.cn/20200905221152322.png#pic_center)
+每个 master 也持有一个偏移量，master 将自己产生的复制流发送给 slave 时，发送多少个字节的数据，自身的偏移量就会增加多少，目的是当有新的操作修改自己的数据集时，它可据此更新 slave 的状态。
+![](https://img-blog.csdnimg.cn/20200905221515483.png#pic_center)
+
+复制偏移量即使在没有一个 slave 连接到 master 时，也会自增，所以基本上每一对给定的
+`Replication ID, offset`
+都会标识一个 master 数据集的确切版本。
+
+## psync
+slave使用`psync`从master复制，psync runid offset
+![](https://img-blog.csdnimg.cn/20200905233312575.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+master会根据自身情况返回响应信息：
+- 可能是FULLRESYNC runid offset触发全量复制
+- 可能是CONTINUE触发增量复制
+
+slave 连接到 master 时，它们使用 PSYNC 命令来发送它们记录的旧的 master replication ID 和它们至今为止处理的偏移量。通过这种方式， master 能够仅发送 slave 所需的增量部分。
+但若 master 的缓冲区中没有足够的命令积压缓冲记录，或者如果 slave 引用了不再知道的历史记录（replication ID），则会转而进行一个全量重同步：在这种情况下， slave 会得到一个完整的数据集副本，从头开始。即：
+- 若slave重连master，那么master仅会复制给slave缺少的部分数据
+- 若第一次连接master，那么会触发全量复制
+
+
+# 7 复制的完整流程
+![](https://img-blog.csdnimg.cn/20190705083122154.png)
+
+> slave如果跟master有网络故障，断开连接会自动重连。
+> master如果发现有多个slave都重新连接，仅会启动一个rdb save操作，用一份数据服务所有slave。
+
+
+1. slave启动，仅保存master的信息，包括master的`host`和`ip`，但复制流程尚未开始master host和ip配置在 `redis.conf` 中的 slaveof
+2. slave内部有个定时任务，每s检查是否有新的master要连接和复制，若发现，就跟master建立socket网络连接。
+3. slave发送ping命令给master
+4. 口令认证 - 若master设置了requirepass，那么salve必须同时发送masterauth的口令认证
+5. master **第一次执行全量复制**，将所有数据发给slave
+6. master后续持续将写命令，异步复制给slave
+
+## heartbeat
+主从节点互相都会发送heartbeat信息。
+master默认每隔10秒发送一次heartbeat，salve node每隔1秒发送一个heartbeat。
+
+# 8 断点续传
+Redis 2.8开始支持主从复制的断点续传
+![](https://img-blog.csdnimg.cn/2019070508465819.png)
+
+主从复制过程，若网络连接中断，那么可以接着上次复制的地方，继续复制下去，而不是从头开始复制一份。
+
+## master和slave都会维护一个offset
+- master在自身基础上累加offset，slave亦是
+- slave每秒都会上报自己的offset给master，同时master保存每个slave的offset
+
+master和slave都要知道各自数据的offset，才能知晓互相之间的数据不一致情况。
+
+## backlog
+master会在内存中维护一个backlog，默认1MB。master给slave复制数据时，也会将数据在backlog中同步写一份。
+
+`backlog主要是用做全量复制中断时候的增量复制`。
+
+master和slave都会保存一个replica offset还有一个master id，offset就是保存在backlog中的。若master和slave网络连接中断，slave会让master从上次replica offset开始继续复制。但若没有找到对应offset，就会执行resynchronization。
+
+## master run id
+- info server，可见master run id
+![](https://img-blog.csdnimg.cn/20200905232843801.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+根据host+ip定位master node，是不靠谱的，如果master node重启或者数据出现了变化，那么slave node应该根据不同的run id区分，run id不同就做全量复制。
+如果需要不更改run id重启redis，可使用：
+```shell
+redis-cli debug reload
+```
+
+# 9 无磁盘化复制
+master在内存中直接创建RDB，然后发送给slave，不会在自己本地持久化。
+只需要在配置文件中开启` repl-diskless-sync yes `即可.
+
+```shell
+等待 5s 再开始复制，因为要等更多 slave 重连
+repl-diskless-sync-delay 5
+```
+
+# 10 处理过期key
+Redis 的过期机制可以限制 key 的生存时间。此功能取决于 Redis 实例计算时间的能力，但是，即使使用 Lua 脚本更改了这些 key，Redis slaves 也能正确地复制具有过期时间的 key。
+
+为实现这功能，Redis 不能依靠主从使用同步时钟，因为这是一个无法解决的问题并且会导致 race condition 和数据不一致，所以 Redis 使用三种主要的技术使过期的 key 的复制能够正确工作：
+1. slave 不会让 key 过期，而是等待 master 让 key 过期。当一个 master 让一个 key 到期（或由于 LRU 算法删除）时，它会合成一个 DEL 命令并传输到所有 slave
+2. 但由于这是 master 驱动的 key 过期行为，master 无法及时提供 DEL 命令，所以有时 slave 的内存中仍然可能存在逻辑上已过期的 key 。为了处理这问题，slave 使用它的逻辑时钟以报告只有在不违反数据集的一致性的读取操作（从主机的新命令到达）中才存在 key。用这种方法，slave 避免报告逻辑过期的 key 仍然存在。在实际应用中，使用 slave 程序进行缩放的 HTML 碎片缓存，将避免返回已经比期望的时间更早的数据项
+3. 在Lua脚本执行期间，不执行任何 key 过期操作。当一个Lua脚本运行时，从概念上讲，master 中的时间是被冻结的，这样脚本运行的时候，一个给定的键要么存在要么不存在。这可以防止 key 在脚本中间过期，保证将相同的脚本发送到 slave ，从而在二者的数据集中产生相同的效果。
+
+一旦 slave 被提升 master ，它将开始独立过期 key，而不需要任何旧 master 帮助。
+
+# 11 重新启动和故障转移后的部分重同步
+Redis 4.0 开始，当一个实例在故障转移后被提升为 master 时，它仍然能够与旧 master 的 slave 进行部分重同步。为此，slave 会记住旧 master 的旧 replication ID 和复制偏移量，因此即使询问旧的 replication ID，也可以将部分复制缓冲提供给连接的 slave 。
+
+但是，升级的 slave 的新 replication ID 将不同，因为它构成了数据集的不同历史记录。例如，master 可以返回可用，并且可以在一段时间内继续接受写入命令，因此在被提升的 slave 中使用相同的 replication ID 将违反一对复制标识和偏移对只能标识单一数据集的规则。
+
+另外，slave 在关机并重新启动后，能够在 RDB 文件中存储所需信息，以便与 master 进行重同步。这在升级的情况下很有用。当需要时，最好使用 SHUTDOWN 命令来执行 slave 的保存和退出操作。
+
+# 参考
+- https://raw.githubusercontent.com/antirez/redis/2.8/00-RELEASENOTES
+- https://redis.io/topics/replication
+
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\274\223\345\255\230\350\256\276\350\256\241\344\270\216\344\274\230\345\214\226.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\346\210\230(\345\215\201\344\270\200)-\347\274\223\345\255\230\350\256\276\350\256\241\344\270\216\344\274\230\345\214\226.md"
similarity index 96%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\274\223\345\255\230\350\256\276\350\256\241\344\270\216\344\274\230\345\214\226.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\346\210\230(\345\215\201\344\270\200)-\347\274\223\345\255\230\350\256\276\350\256\241\344\270\216\344\274\230\345\214\226.md"
index 12860f6992..c299f82d6a 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\274\223\345\255\230\350\256\276\350\256\241\344\270\216\344\274\230\345\214\226.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\346\210\230(\345\215\201\344\270\200)-\347\274\223\345\255\230\350\256\276\350\256\241\344\270\216\344\274\230\345\214\226.md"
@@ -40,4 +40,11 @@
 ![](https://upload-images.jianshu.io/upload_images/4685968-7d75ad50fb061886.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ![](https://upload-images.jianshu.io/upload_images/4685968-9c35476632374417.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ![](https://upload-images.jianshu.io/upload_images/4685968-371eb28f1ca145ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
-![](https://upload-images.jianshu.io/upload_images/4685968-1d36eaf419a294d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
\ No newline at end of file
+![](https://upload-images.jianshu.io/upload_images/4685968-1d36eaf419a294d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\344\271\235)-\345\210\235\350\257\206Redis-Cluster.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\344\271\235)-\345\210\235\350\257\206Redis-Cluster.md"
new file mode 100644
index 0000000000..d728645fea
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\344\271\235)-\345\210\235\350\257\206Redis-Cluster.md"
@@ -0,0 +1,283 @@
+![](https://upload-images.jianshu.io/upload_images/4685968-9e3978a20d088d2f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-0b8a25febdd73bc5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-397cdab9d102b1e1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-e835e7bd7a129c98.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-28f196e69438f4df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-3b896a6dada8f607.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-84403d51441b5e71.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-11f5d002dc59473a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-20a18cadd97352e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ad3766f87a3b5b22.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-505823a6aa081432.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-809120aab3f754af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-de9442bd35ba5f70.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ddbdf0781738e890.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5108f1c6042f5457.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![数据迁移](https://upload-images.jianshu.io/upload_images/4685968-1d36353273dc3497.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-1a60126747aeaf8c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-98099535817e9ed6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 5 一致性哈希
+![ 一致性哈希](https://upload-images.jianshu.io/upload_images/4685968-df06e15f8e5dcd72.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![一致性哈希-扩容](https://upload-images.jianshu.io/upload_images/4685968-53d3268af0887b39.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-67886cff0043e59b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 6 虚拟槽哈希分区(Redis Cluster所采用)
+![](https://upload-images.jianshu.io/upload_images/4685968-37c6294ccef83ee2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![虚拟槽分配](https://upload-images.jianshu.io/upload_images/4685968-ba651baf710a1a03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 7 Redis Cluster基本架构
+![单机架构](https://upload-images.jianshu.io/upload_images/4685968-ab9f4f0c66695b2d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![分布式架构](https://upload-images.jianshu.io/upload_images/4685968-d68891804098b248.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![ Redis Cluster 架构](https://upload-images.jianshu.io/upload_images/4685968-861c1ccffa50d10e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![节点](https://upload-images.jianshu.io/upload_images/4685968-a4eb84b1c1793a92.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![meet](https://upload-images.jianshu.io/upload_images/4685968-a8fbd445c86b89a0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-bad6744bbbd01d7c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![指派槽](https://upload-images.jianshu.io/upload_images/4685968-b3a29aa7dfd5d533.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![客户端与指派槽](https://upload-images.jianshu.io/upload_images/4685968-6c566e2336db8ed9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![Redis Cluster特性](https://upload-images.jianshu.io/upload_images/4685968-61150d478ef6761f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 8 Redis Cluster 安装配置
+![两种安装](https://upload-images.jianshu.io/upload_images/4685968-69b155bef61ad030.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![原生命令安装-理解架构](https://upload-images.jianshu.io/upload_images/4685968-0696ad3d693425d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![配置开启Redis-1](https://upload-images.jianshu.io/upload_images/4685968-a3808f51ac8af354.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![配置开启Redis-2](https://upload-images.jianshu.io/upload_images/4685968-ba4ce2748d55b3d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![meet](https://upload-images.jianshu.io/upload_images/4685968-dd7de93fff10d64b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![Cluster节点主要配置](https://upload-images.jianshu.io/upload_images/4685968-a51d088623dce906.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![分配槽](https://upload-images.jianshu.io/upload_images/4685968-8fb8ccfdbfb78499.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![设置主从](https://upload-images.jianshu.io/upload_images/4685968-0b94f3d798f86122.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+Redis 3.0之后，节点之间通过去中心化的方式，提供了完整的`sharding`、`replication`（复制机制仍使用原有机制，并且具备感知主备的能力）、failover解决方案，称为Redis Cluster
+即：将proxy/sentinel的工作融合到了普通Redis节点里
+#1 拓扑结构
+一个Redis Cluster由多个Redis节点组成。不同的节点组服务的数据无交集，每个节点对应数据`sharding`的一个分片
+
+节点组内部分为主备2类，对应前面叙述的`master`和`slave`。两者数据准实时一致，通过异步化的主备复制机制保证
+
+一个节点组有且仅有一个master，同时有0到多个slave。只有master对外提供写服务，读服务可由master/slave提供
+![Redis Cluster结构](http://upload-images.jianshu.io/upload_images/4685968-792382954475bda4.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+key-value全集被分成了5份，5个slot（实际上Redis Cluster有 16384 [0-16383] 个slot，每个节点服务一段区间的slot，这里面仅仅举例）。A和B为master节点，对外提供写服务。分别负责1/2/3和4/5的slot。A/A1和B/B1/B2之间通过主备复制的方式同步数据。
+
+上述的5个节点，两两通过Redis Cluster Bus交互，相互交换如下的信息：
+- 数据分片（slot）和节点的对应关系
+- 集群中每个节点可用状态
+- 集群结构发生变更时，通过一定的协议对配置信息达成一致。数据分片的迁移、主备切换、单点master的发现和其发生主备关系变更等，都会导致集群结构变化
+- publish/subscribe（发布/订阅）功能，在Cluster版内部实现所需要交互的信息
+
+Redis Cluster Bus通过单独的端口进行连接，由于`Bus是节点间的内部通信机制`，交互的是字节序列化信息。相对Client的字符序列化来说，效率较高。
+
+Redis Cluster是一个`去中心化的分布式实现方案`，客户端和集群中任一节点连接，然后通过后面的交互流程，逐渐的得到全局的数据分片映射关系。
+# 2 配置的一致性
+对于去中心化的实现，集群的拓扑结构并不保存在单独的配置节点上，后者的引入同样会带来新的一致性问题。
+那么`各自为政的节点间，如何对集群的拓扑达成一致`，是Redis Cluster配置机制要解决的问题。
+Redis Cluster通过引入2个自增的`epoch`变量，来使得集群配置在各个节点间最终达成一致。
+## 2.1 配置信息数据结构
+Redis Cluster中的每个节点(Node)都保存了集群的配置信息，并且存储在clusterState中，结构如下
+![](http://upload-images.jianshu.io/upload_images/4685968-09881b61974a22de.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- clusterState 记录了从集群中某个节点视角，来看集群配置状态
+- currentEpoch 表示整个集群中最大的版本号，集群信息每变更一次，改版本号都会自增
+- nodes 是一个列表，包含了本节点所感知的，集群所有节点的信息（clusterNode），也包含自身的信息。
+- clusterNode 记录了每个节点的信息，其中包含了节点本身的版本 Epoch；自身的信息描述：节点对应的数据分片范围（slot）、为master时的slave列表、为slave时的master节点
+- 每个节点包含一个全局唯一的NodeId
+- 当集群的数据分片信息发生变更（数据在节点间迁移时），Redis Cluster 仍然保持对外服务
+- 当集群中某个master宕机，Redis Cluster 会自动发现，并触发故障转移的操作。将master的某个slave晋升为新的 master。
+
+由此可见，每个节点都保存着Node视角的集群结构。它描述了数据的分片方式，节点主备关系，并通过Epoch 作为版本号实现集群结构信息的一致性，同时也控制着数据迁移和故障转移的过程。
+# 2.2 信息交互
+去中心化的架构不存在统一的配置中心。在Redis Cluster中，这个配置信息交互通过Redis Cluster Bus来完成（独立端口）。Redis Cluster Bus 上交互的信息结构如下：
+![Redis Cluster 交互信息数据结构](http://upload-images.jianshu.io/upload_images/4685968-059a84c403a03a70.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+clusterMsg 中的`type`指明了消息的类型，配置信息的一致性主要依靠PING/PONG。每个节点向其他节点频繁的周期性的发送PING/PONG消息。
+对于消息体中的  `Gossip`部分，包含了sender/receiver 所感知的其他节点信息，接受者根据这些Gossip 跟新对集群的认识。
+
+对于大规模的集群，如果每次PING/PONG 都携带着所有节点的信息，则网络开销会很大。此时Redis Cluster 在每次PING/PONG，只包含了随机的一部分节点信息。由于交互比较频繁，短时间的几次交互之后，集群的状态也会达成一致。
+## 2.3 一致性的达成
+当Cluster 结构不发生变化时，各个节点通过gossip 协议在几轮交互之后，便可以得知Cluster的结构信息，达到一致性的状态
+但是当`集群结构发生变化时（故障转移/分片迁移等）`，优先得知变更的节点通过`Epoch变量`，将自己的最新信息扩散到Cluster，并最终达到一致。
+
+clusterNode 的Epoch描述的单个节点的信息版本；
+
+clusterState 的`currentEpoch `描述的是集群信息的版本，它可以辅助Epoch 的自增生成
+因为currentEpoch 是维护在每个节点上的，在集群结构发生变更时，Cluster 在一定的时间窗口控制更新规则，来保证每个节点的currentEpoch都是最新的
+### 更新规则
+- 当某个节点率先知道了变更时，将自身的`currentEpoch` 自增，并使之成为集群中的最大值
+再用自增后的`currentEpoch` 作为新的`Epoch` 版本
+- 当某个节点收到了比自己大的`currentEpoch`时，更新自己的`currentEpoch`
+- 当收到Redis Cluster Bus 消息中的某个节点的`Epoch` > 自身的时，将更新自身的内容
+- 当Redis Cluster Bus 消息中，包含了自己没有的节点时，将其加入到自身的配置中
+
+上述的规则保证了`信息的更新是单向的`，最终`朝着Epoch更大的信息收敛`
+同时`Epoch`也随着`currentEpoch`的增加而增加，最终将各节点信息趋于稳定
+
+## sharding
+不同节点分组服务于相互无交集的分片（sharding）
+`Redis Cluster 不存在单独的proxy或配置服务器`
+所以需要将客户端路由到目标的分片
+
+### 1、数据分片（slot）
+Redis Cluster 将所有的数据划分为16384 `[0-16383]` 个分片，每个分片负责其中一部分
+每一条数据（key/value）根据key值,通过数据分布算法（`一致性哈希`）映射到16384 个slot中的一个
+
+数据分布算法
+```
+slotId = crc16(key) % 16384
+```
+客户端根据slotId 决定将请求路由到哪个Redis 节点。
+`Cluster 不支持跨节点的单命令`，如：sinterstore，如果涉及的2个key对应的slot 在不同的Node，则执行失败。
+通常Redis的key都是带有业务意义的，如：
+- Product:Trade:20180890310921230001
+- Product:Detail:20180890310921230001
+
+当在集群中存储时，上述同一商品的交易和详情可能会存储在不同的节点上，进而对于这2个key `不能以原子的方式操作`。
+为此，Redis引入了`HashTag`的概念，使得数据分布算法可以根据key 的某一部分进行计算，让相关的2 条记录落到同一个数据分片
+```
+商品交易记录key：Product:Trade:{20180890310921230001}
+商品详情记录key：Product:Detail:{20180890310921230001}
+```
+Redis 会根据 {} 之间的字符串作为数据分布式算法的输入。
+### 2、客户端的路由
+Redis Cluster的客户端相比单机Redis 需要`具备路由语义的识别能力，且具备一定的路由缓存能力`
+当Client 访问的key 不在当前Redis 节点的slots中，Redis 会返回给Client 一个moved命令。并告知其正确的路由信息
+![](http://upload-images.jianshu.io/upload_images/4685968-3a5f6135cc317d30.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+当Client 接收到moved 后，再次请求新的Redis时，此时Cluster 的结构又可能发生了变化。此时有可能再次返回moved 
+Client 会根据moved响应，更新其内部的路由缓存信息，以便后续的操作直接找到正确的节点，减少交互次数。
+
+当Cluster 在数据重新分布过程中时，可以通过ask 命令控制客户端的路由，如下所示：
+![](http://upload-images.jianshu.io/upload_images/4685968-641dec0621eae883.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+​​​​上图中，slot 1 需要迁移到新节点上，此时如果客户端已经完成迁移的key，节点将相应ask 告知客户端想目标节点重试。
+
+`ask命令`和`moved 命令`的不同
+- moved 会更新Client数据路由
+- ask 只是重定向新节点，但是后续的相同slot 仍会路由到旧节点
+
+迁移的过程可能会持续一段时间，这段时间某个slot 的数据，同时可能存在于新旧 2 个节点。
+由于`move 操作会使Client 的路由缓存变更`，如果新旧节点对于迁移中的slot 所有key 都回应moved，客户端的路由缓存会频繁变更。`因此引入ask 类型消息，将重定向和路由缓存分离`
+
+### 3 分片的迁移
+在一个稳定的Redis Cluster 中，每个slot 对应的节点都是确定的。在某些情况下，节点和分片需要变更：
+
+新的节点作为master加入；
+
+某个节点分组需要下线；
+
+负载不均衡需要调整slot 分布。
+
+此时需要进行分片的迁移，迁移的触发和过程控制由外部系统完成。Redis Cluster 只提供迁移过程中需要的原语，包含下面 2 种：
+
+```
+节点迁移状态设置：迁移前标记源/目标节点。
+key迁移的原子化命令：迁移的具体步骤。
+```
+
+![image.gif](https://upload-images.jianshu.io/upload_images/4685968-a0c0cbce1ac8db3e.gif?imageMogr2/auto-orient/strip)
+
+下面的Demo会介绍slot 1 从节点A 迁移到B的过程。
+
+![image](http://upload-images.jianshu.io/upload_images/4685968-6b2abb0000b656df.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+1、向节点B发送状态变更命令，将B的对应slot 状态置为importing。
+
+2、向节点A发送状态变更命令，将A对应的slot 状态置为migrating。
+
+3、针对A上的slot 的所有key，分别向A 发送migrate 命令，告知A 将对应的key 迁移到B。
+
+当A节点的状态置为migrating 后，表示对应的slot 正在从A迁出，为保证该slot 数据的一致性。A此时提供的写服务和通常状态下有所区别，对于某个迁移中的slot：
+
+```
+如果Client 访问的key 尚未迁出，则正常的处理该key；
+
+如果key已经迁出或者key不存在，则回复Client ASK 信息让其跳转到B处理；
+```
+
+![image.gif](https://upload-images.jianshu.io/upload_images/4685968-15adb67163125ab4.gif?imageMogr2/auto-orient/strip)
+
+当节点B 状态变成importing 后，表示对应的slot 正在向B迁入。即使B 能对外提供该slot 的读写服务，但是和通常情况下有所区别：
+
+```
+当Client的访问不是从ask 跳转的，说明Client 还不知道迁移。有可能操作了尚未迁移完成的，处在A上面的key，如果这个key 在A上被修改了，则后续会产生冲突。
+
+所以对于该slot 上所有非ask 跳转的操作，B不会进行操作，而是通过moved 让Client 跳转至A执行。
+```
+
+![image.gif](https://upload-images.jianshu.io/upload_images/4685968-bc6d748bbd9de821.gif?imageMogr2/auto-orient/strip)
+
+这样的状态控制，保证了同一个key 在迁移之前总是在源节点执行。迁移后总是在目标节点执行，从而杜绝了双写的冲突。迁移过程中，新增加的key 会在目标节点执行，源节点不会新增key。使得迁移有界限，可以在某个确定的时刻结束。
+
+单个key 的迁移过程可以通过原子化的migrate 命令完成。对于A/B的slave 节点，是通过主备复制，从而达到增删数据。
+
+当所有key 迁移完成后，Client 通过 cluster setslot 命令设置B的分片信息，从而包含了迁入的slot。设置过程中会让Epoch自增，并且是Cluster 中的最新值。然后通过相互感知，传播到Cluster 中的其他节点。
+
+failover
+
+同Sentinel 一样，Redis Cluster 也具备一套完整的故障发现、故障状态一致性保证、主备切换机制。
+
+1、failover的状态变迁
+
+1）故障发现：当某个master 宕机时，宕机时间如何被集群其他节点感知。
+
+2）故障确认：多个节点就某个master 是否宕机如何达成一致。
+
+3）slave选举：集群确认了某个master 宕机后，如何将它的slave 升级成新的master；如果有多个slave，如何选择升级。
+
+4）集群结构变更：成功选举成为master后，如何让整个集群知道，以更新Cluster 结构信息。
+
+2、故障发现
+
+Redis Cluster 节点间通过Redis Cluster Bus 两两周期性的PING/PONG 交互。当某个节点宕机时，其他Node 发出的PING消息没有收到响应，并且超过一定时间（NODE_TIMEOUT）未收到，则认为该节点故障，将其置为PFAIL状态（Possible Fail）。后续通过Gossip 发出的PING/PONG消息中，这个节点的PFAIL 状态会传播到集群的其他节点。
+
+Redis Cluster 的节点两两通过TCP 保持Redis Cluster Bus连接，当对PING 无反馈时，可能是节点故障，也可能是TCP 链接断开。如果是TCP 断开导致的误报，虽然误报消息会因为其他节点的正常连接被忽略，但是也可以通过一定的方式减少误报。Redis Cluster 通过 预重试机制 排除此类误报：当 NODE_TIMEOUT/2 过去了，但是还未收到响应，则重新连接重发PING 消息，如果对端正常，则在很短的时间内就会有响应。
+
+3、故障确认
+
+对于网络分隔的情况，某个节点（B）并没有故障，但是和A 无法连接，但是和C/D 等其他节点可以正常联通。此时只会有A 将 B 标记为PFAIL 状态，其他节点认为B 正常。此时A 和C/D 等其他节点信息不一致，Redis Cluster 通过故障 确认协议 达成一致。
+
+集群中每个节点都是Gossip 的接收者，A 也会接收到来自其他节点的Gossip 消息，被告知B 是否处于PFAIL 状态。当A收到来气其他master 节点对于 B 的PFAIL 达到一定数量后，会将B的PFAIL 状态升级为 FAIL 状态。表示B 已经确认为故障态，后面会发起slave 选举流程。
+
+A节点内部的集群信息中，对于B的状态从PFAIL 到 FAIL 的变迁，如下图所示：
+
+![image](http://upload-images.jianshu.io/upload_images/4685968-f47db767dcb624af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+4、slave选举
+
+上图中，B是A的master，并且B 已经被集群公认是FAIL 状态了，那么A 发起竞选，期望成为新的master。
+
+如果B 有多个slave （A/E/F）都认知到B 处于FAIL 状态了，A/E/F 可能会同时发起竞选。当B的slave 个数 >= 3 时，很有可能产生多轮竞选失败。为了减少冲突的出现，优先级高的slave 更有可能发起竞选，从而提升成功的可能性。这里的优先级是slave的数据最新的程度，数据越新的（最完整的）优先级越高。
+
+slave 通过向其他master发送FAILVOER_AUTH_REQUEST 消息发起竞选，master 收到后回复FAILOVER_AUTH_ACK 消息告知是否同意。slave 发送FAILOVER_AUTH_REQUEST 前会将currentEpoch 自增，并将最新的Epoch 带入到FAILOVER_AUTH_REQUEST 消息中，如果自己未投过票，则回复同意，否则回复拒绝。
+
+5、结构变更通知
+
+当slave 收到过半的master 同意时，会替代B 成为新的master。此时会以最新的Epoch 通过PONG 消息广播自己成为master，让Cluster 的其他节点尽快的更新拓扑结构。
+
+当B 恢复可用之后，它手续爱你仍然认为自己是master，但逐渐的通过Gossip 协议得知A 已经替代了自己，然后降级为A的slave。
+
+可用性和性能
+
+Redis Cluster 还提供了一些方法可以提升性能和可用性。
+
+1、Redis Cluster的读写分离
+
+对于读写分离的场景，应用对于某些读请求允许舍弃一定的数据一致性，以换取更高的吞吐量。此时希望将读请求交给slave处理，以分担master的压力。
+
+通过分片映射关系，某个slot 一定对应着一个master节点。Client 通过moved 命令，也只会路由到各个master中。即使Client 将请求直接发送到slave上，也会回复moved 到master去处理。
+
+为此，Redis Cluster 引入了readonly 命令。Client 向slave发送该命令后，不再moved 到master处理，而是自己处理，这成为slave的readonly 模式。通过readwrite命令，可以将slave的readonly模式重置。
+
+2、master单点保护
+
+假如Cluster 的初始状态如下所示：
+
+![image](http://upload-images.jianshu.io/upload_images/4685968-46c4b24509500a59.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+上图中A、B两个master 分别有自己的slave，假设A1 发生宕机，结构变为如下所示：
+
+![image](http://upload-images.jianshu.io/upload_images/4685968-ae7bf7d78dbe590f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+此时A 成为了单点，一旦A 再次宕机，将造成不可用。此时Redis Cluster 会把B 的某个slave （如 B1 ）进行副本迁移，变成A的slave。如下所示：
+
+![image](http://upload-images.jianshu.io/upload_images/4685968-eb58cf21569dca6c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+这样集群中每个master 至少有一个slave，使得Cluster 具有高可用。集群中只需要保持 2*master+1 个节点，就可以保持任一节点宕机时，故障转移后继续高可用。
+![](https://upload-images.jianshu.io/upload_images/4685968-6f6b0095e5402aa6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(9)-\347\274\226\347\240\201.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\205\253)-Sentinal.md"
similarity index 100%
rename from "Netty/Netty\346\272\220\347\240\201\350\247\243\346\236\220/Netty\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230(9)-\347\274\226\347\240\201.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\205\253)-Sentinal.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\215\201\344\270\211)-Redis\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\215\201\344\270\211)-Redis\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\272\221\345\271\263\345\217\260CacheCloud.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\215\201\344\272\214)-Redis\344\272\221\345\271\263\345\217\260CacheCloud.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\344\272\221\345\271\263\345\217\260CacheCloud.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\215\201\344\272\214)-Redis\344\272\221\345\271\263\345\217\260CacheCloud.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\274\200\345\217\221\350\247\204\350\214\203.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\215\201\344\272\224)-\345\274\200\345\217\221\350\247\204\350\214\203.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\274\200\345\217\221\350\247\204\350\214\203.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\215\201\344\272\224)-\345\274\200\345\217\221\350\247\204\350\214\203.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\206\205\345\255\230\347\256\241\347\220\206.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\215\201\345\205\255)-\345\206\205\345\255\230\347\256\241\347\220\206.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\206\205\345\255\230\347\256\241\347\220\206.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\256\236\350\267\265(\345\215\201\345\205\255)-\345\206\205\345\255\230\347\256\241\347\220\206.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\270\270\350\247\201\346\225\205\351\232\234\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\270\270\350\247\201\346\225\205\351\232\234\350\247\243\345\206\263\346\226\271\346\241\210.md"
deleted file mode 100644
index a0814b3480..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\270\270\350\247\201\346\225\205\351\232\234\350\247\243\345\206\263\346\226\271\346\241\210.md"
+++ /dev/null
@@ -1,65 +0,0 @@
-# 1 重启和故障转移后的部分重同步
-Redis 4.0 开始，当一个实例在故障转移后被提升为 master 时，它仍然能够与旧 master 的 slave 进行部分重同步。为此，slave 会记住旧 master 的旧 replication ID 和复制偏移量，因此即使询问旧的 replication ID，也可以将部分复制缓冲提供给连接的 slave 。
-
-但是，升级的 slave 的新 replication ID 将不同，因为它构成了数据集的不同历史记录。例如，master 可以返回可用，并且可以在一段时间内继续接受写入命令，因此在被提升的 slave 中使用相同的 replication ID 将违反一对复制标识和偏移对只能标识单一数据集的规则。
-
-另外，slave 在关机并重新启动后，能够在 RDB 文件中存储所需信息，以便与 master 进行重同步。这在升级的情况下很有用。当需要时，最好使用 SHUTDOWN 命令来执行 slave 的保存和退出操作。
-
-# 2 主从数据不一致
-主从网络延时：
-## 主多从少
-部分重同步。可以通过命令 PSYNC master_run_id offset 执行。
-## 主少从多
-全量复制，覆盖。这种情况是因为从节点读写模式导致的，关闭从节点读写模式，或者删除从节点数据，重新从主节点全量复制。
-# 3 数据延迟
-编写外部程序监听主从节点的复制偏移量，延迟较大时发出报警或通知客户端，切换到主节点或其他节点。
-
-设置从节点`slave-serve-stale-data`为no,除INFO和SLAVOF命令之外的任何请求都会返回一个错误“SYNC with master in progress”。
-
-当副本失去与mater的连接时或仍在进行复制时，副本可以如下方式起作用：
-- 若 `replica-serve-stale-data` 设为“是”（默认值），则副本仍会回复客户端请求，可能带有过期数据，或者，如果这是第一次同步，则数据集可能只是空的
-- 若将`replica-serve-stale-data`设为no，则该副本将对除以下信息以外的所有命令返回错误“SYNC with master in progress”：INFO，REPLICAOF，AUTH，PING，SHUTDOWN，REPLCONF，ROLE，CONFIG ，SUBSCRIBE，UNSUBSCRIBE，PSUBSCRIBE，PUNSUBSCRIBE，PUBLISH，PUBSUB，COMMAND，POST，HOST和LATENCY
-# 4 脏数据
-## 4.1 脏数据产因
-### 4.1.1 Redis 删除策略
-是因为读到了过期数据。
-
-读到过期数据是 Redis 删除策略导致：
-#### 惰性删除
-master 每次读取命令时都会检查K是否超时，若超时则执行 del 命令删除键对象，之后异步把 del 命令 slave 节点，这样可以保证数据复制的一致性，slave 永远不会主动去删除超时数据。
-#### 定时删除
-Redis 的 master 节点在内部定时任务，会循环采样一定数量的键，当发现采样的键过期时，会执行 del 命令，之后再同步个 slave 节点。
-#### 主动删除
-当前已用内存超过 maxMemory 限定时，触发主动清理策略。主动设置的前提是设置了 maxMemory 的值
-注：如果数据大量超时，master 节点采样速度跟不上过期的速度，而且 master 节点没有读取过期键的操作，那 slave 节点是无法收到 del 命令的，这时从节点上读取的数据已经是超时的了。
-### 4.1.2 从节点可写
-如果从节点（默认读模式）是读写模式，可能误写入从节点的数据，后期就会成为脏数据。
-## 4.2 解决方案
-### 忽略
-比如 12306 查余票、双十一秒杀的库存，你会发现经常就是前后不一致的数据。因为你查询时得到的数据，就是需要允许写错误。
-### 选择性强制读主
-但是真正下单扣库存时，你就必须确保数据的正确性
-选择强制读 master，slave间接变为备份服务器（某个业务）。
-### 从节点只读
-防止 slave 写入脏数据。
-### Redis自身优化
-Redis3.2 版本解决了 Redis 删除策略导致的过期数据，在此版本中 slave 读数据前，会检查K过期时间，以决定是否返回数据。
-
-
-# 5 数据安全性
-## 5.1 关闭主节点持久化
-
-为提升Redis性能，一般会关闭主节点持久化的功能（这样所有数据都会持久化在 slave），因为主从同步时，master 都会 bgsave rdb。但这样也会带来复制的安全性问题。
-
-在使用 Redis 复制功能时的设置中，推荐在 master 和在 slave 中启用持久化。当不可能启用时，例如由于非常慢的磁盘性能而导致的延迟问题，应该禁用主节点自动重启功能。
-
-为什么关闭了持久化并配置了自动重启的 master 是危险的呢：
-1. 关闭 Master 的持久化设置，Replica1 和 Replica2 从 Master 复制数据。此时 Master 只有内存数据，没有磁盘数据了。
-2. 当 master 宕机，由于自动重启机制重启了，但重启后由于持久化被关闭了，Master数据集为空！
-3. 重启后的 Master，发现 runId 发生变化，也会重新和从节点建立连接，两个从节点会发起复制请求，从Master 复制数据，但 Master 此时数据集为空，因此复制的结果是它们会销毁自身之前的数据副本而变成空数据集。
-![](https://img-blog.csdnimg.cn/20210410164103333.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-### 5.1.1 解决方案
-- 牺牲性能，开启 Master 的持久化功能。
-- 为了性能，依旧选择关闭，那就让主节点不自动重启，比如不要有Docker或脚本等自动重启机制。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\200\247\350\203\275\344\274\230\345\214\226\346\214\207\345\215\227.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\200\247\350\203\275\344\274\230\345\214\226\346\214\207\345\215\227.md"
deleted file mode 100644
index c0bf06bbb6..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\200\247\350\203\275\344\274\230\345\214\226\346\214\207\345\215\227.md"
+++ /dev/null
@@ -1,166 +0,0 @@
-# 1 客户端优化
-## pipeline 批量操作
-## 连接池应用
-
-# 2 合理的淘汰机制
-- 设置合理的内存大小
-- 设置合理的过期时间
-- 选择合适的淘汰策略
-
-# 3 内存优化
-https://redis.io/topics/memory-optimization 
-不同大小，底层数据结构不同：
-
-```bash
-hash-max-ziplist-value 64 
-zset-max-ziplist-value 64
-```
-# 4 CPU优化
-不要阻塞，特别是 lua 脚本，不要有长时间睡眠操作，不然其它操作全部阻塞！
-谨慎使用范围操作
-SLOWLOG get 10 默认10ms，默认只保留最后的128条
-
-# 5 key设计
-## 可读性和可管理性
-以业务名(或数据库名)为前缀(防止key冲突)，用冒号分隔，比如业务名:表名:id
-##  简洁性
-保证语义的前提下，控制key的长度，当key较多时，内存占用也不容忽视
-## 不要含特殊字符
-比如:包含空格、换行、单双引号以及其他转义字符
-# 6 value设计
-- 拒绝bigkey(防止网卡流量、慢查询)， string类型控制在10KB以内， hash、 list. set. zset元素 个数不要超过5000
-- 选择适合的数据类型
-- 控制key的生命周期，redis不是垃圾桶
-
-
-# 7 分区
-多个业务系统，共用一个redis，还是应该分开。规划好key，特别是前缀，a.b.c.d
-
-如果缓存数据变大了，就可以分区，但注意按业务垂直拆分，避免 key 的冲突。
-一般小公司研发团队都是需要申请缓存资源直接得到对应的 ip 地址，但大公司最多只能得到key/token。
-
-# 8 Big Key
-那么什么样的Key才算是Big Key呢? 
-一般key的值大于10KB时可以算是Big Key了。
-如下场景都有可能遇到Big Key，比如：
-- 粉丝列表
-- 统计数据，比如PV或者UV统计
-- 使用不当的数据缓存，比如通过String保存序列化后的用户数据等
-
-出现了Big Key你也不用非常紧张，因为某些场景下不得不使用，而不应该是你的使用不当造成的。下面我们看一下如何进行发现与优化
-## 查询 Big Key
-可以使用脚本进行查询，大概思路就是使用 scan 游标查询 key，然后使用 memory usage key 获取这个 key 与 value 的字节数，这样就能很方便的得出结论进行优化。比如
-
-```python
-import sys
-import redis
-
-def check_big_key(r, k):
-  bigKey = False
-  length = 0 
-  try:
-    type = r.type(k)
-    if type == "string":
-      length = r.strlen(k)
-    elif type == "hash":
-      length = r.hlen(k)
-    elif type == "list":
-      length = r.llen(k)
-    elif type == "set":
-      length = r.scard(k)
-    elif type == "zset":
-      length = r.zcard(k)
-  except:
-    return
-  if length > 10240:
-    bigKey = True
-  if bigKey :
-    print db,k,type,length
-
-def find_big_key_normal(db_host, db_port, db_password, db_num):
-  r = redis.StrictRedis(host=db_host, port=db_port, password=db_password, db=db_num)
-  for k in r.scan_iter(count=1000):
-    check_big_key(r, k)
-
-def find_big_key_sharding(db_host, db_port, db_password, db_num, nodecount):
-  r = redis.StrictRedis(host=db_host, port=db_port, password=db_password, db=db_num)
-  cursor = 0
-  for node in range(0, nodecount) :
-    while True:
-      iscan = r.execute_command("iscan",str(node), str(cursor), "count", "1000")
-      for k in iscan[1]:
-        check_big_key(r, k)
-      cursor = iscan[0]
-      print cursor, db, node, len(iscan[1])
-      if cursor == "0":
-        break;
-  
-if __name__ == '__main__':
-  if len(sys.argv) != 4:
-     print 'Usage: python ', sys.argv[0], ' host port password '
-     exit(1)
-  db_host = sys.argv[1]
-  db_port = sys.argv[2]
-  db_password = sys.argv[3]
-  r = redis.StrictRedis(host=db_host, port=int(db_port), password=db_password)
-  nodecount = r.info()['nodecount']
-  keyspace_info = r.info("keyspace")
-  for db in keyspace_info:
-    print 'check ', db, ' ', keyspace_info[db]
-    if nodecount > 1:
-      find_big_key_sharding(db_host, db_port, db_password, db.replace("db",""), nodecount)
-    else:
-      find_big_key_normal(db_host, db_port, db_password, db.replace("db", ""))
-```
-可以通过 python find_bigkey host 6379 password 来执行，默认大 key 的阈值为 10240(10KB)，也就是对于 string 类型的 value 大于 10240 的认为是大 key，对于 list 的话如果 list 长度大于 10240 认为是大 key，对于 hash 的话如果 field 的数目大于 10240 认为是大 key。另外默认该脚本每次搜索 1000 个 key，对业务的影响比较低，不过最好在业务低峰期进行操作，避免 scan 命令对业务的影响。
-
-## 删除 Big Key
-Redis4.0 新特性 - Lazy Free
-当删除键的时候, redis 提供异步延时释放 key 内存的功能，把 key 释放操作放在 bio(Background I/O) 单独的子线程处理中，减少删除 big key 对 redis 主线程的阻塞。有效地避免删除 big key 带来的性能和可用性问题。因此删除 Big Key 时使用 unlink 操作。
-
-# 9 复制
-## 9.1 避免全量复制
-Redis 复制分为全量复制和部分复制，但全量复制的开销很大。如何尽量避免全量复制呢？
-### 解决方案
-- 当某个 Replica 第一次去挂到 master 上时，无法避免要做一次全量复制，那又该如何尽量降低开销呢？
-既然第一次无法避免，那就选在集群低峰时间（凌晨）挂载 Replica
-- 选举Replica为主节点
-如果有某个Replica已经有了主节点数据了，那就将其选举为 master。通过人为或者哨兵实现选举。
-#### 复制积压缓冲区不足
-master 生成 RDB 同步到 Replica，Replica 加载 RDB 这段时间里，master 的所有写命令都会保存到一个复制缓冲队列（如果主从之间网络抖动，进行部分复制也是执行该逻辑），待 Replica 加载完 RDB，拿 offset 值到该队列判断，若在该队列，则把该队列从 offset 到末尾全部同步过来，这个队列的默认值为 1M。若发现 offset 不在队列，就会发起全量复制。
-所以增大复制缓冲区的配置 **repl_backlog_size** 默认是 1M，可以设置大一些，从而增大 offset 的命中率。那么如何估算该值呢？一般我们网络故障时间一般是min级别，所以如下公式：
-```go
-理想值 = 根据当前QPS估算每分钟可写入多少字节 ✖️ 可能发生故障的min
-```
-## 9.2 避免复制风暴
-### 9.2.1 单主节点复制风暴
-一般发生在 **Master 挂在多个 Replica 场景**。
-当 Master 重启恢复后，其 master 下所有 Replica 检测到 RunID 变化，导致所有 Replica 向 master 做全量复制。此时 Master 会为 Replica 创建 RDB 快照，若在快照创建完毕前，有多个 Replica 尝试与 Master 进行全量同步，那么其他的 Replica 将共享这份 RDB 快照。尽管 Redis 对这个问题做了优化，即只生成一份 RDB，但仍需多次传输，开销很大。同时给多个 Replica 发送快照，可能会使 Master 的网络带宽消耗严重，造成 Master 延迟变大，极端情况会出现主从断开，导致复制失败。
-#### 解决方案
-- 采用树状复制结构（一传二，二传四，分摊网络压力）
-- 当 Master 宕机，选举一台 Replica 晋升为 Master，减少全量复制的产生
-
-
-### 9.2.2 单机多主复制风暴
-一台机器，多个主节点（树状复制结构）。
-因为Redis是单线程结构，所以发挥不了 CPU多核的优势，于是有的人为了省钱就在同一机器部署多个 redis 主节点了。
-#### 解决方案
-- 应当把主节点尽量分散在多台机器上，避免在单台机器上部署过多的主节点
-- 当主节点所在机器故障后提供故障恢复转移机制，避免机器恢复后进行密集的全量复制
-
-# 禁止使用
-
-
-#  使用经验
-## 性能
-- 线程数（4~8）与连接数（redis 服务端 10000）
-- 监控系统读写比（ 至少9：1）和缓存命中率
-
-## 容量
-1) 做好容量评估，合理使用缓存资源
-## 资源管理和分配
-- 尽量每个业务集群单独使用自己的Redis，不混用； 
-
-- 控制Redis资源的申请与使用，规范环境和Key的管理（以一线互联网为例）； 
- 
- - 监控CPU 100%（单线程），所以此时基本不响应了，需要优化高延迟操作。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226\347\255\226\347\225\245\345\246\202\344\275\225\350\247\243\345\206\263\346\225\260\346\215\256\344\270\242\345\244\261\344\270\215\345\256\211\345\205\250\351\227\256\351\242\230.md"
similarity index 92%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226\347\255\226\347\225\245\345\246\202\344\275\225\350\247\243\345\206\263\346\225\260\346\215\256\344\270\242\345\244\261\344\270\215\345\256\211\345\205\250\351\227\256\351\242\230.md"
index 2745ef775b..1c76e14fa6 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\214\201\344\271\205\345\214\226\347\255\226\347\225\245\345\246\202\344\275\225\350\247\243\345\206\263\346\225\260\346\215\256\344\270\242\345\244\261\344\270\215\345\256\211\345\205\250\351\227\256\351\242\230.md"
@@ -27,7 +27,7 @@ MySQL宕机，你都没法去找数据恢复到Redis里面去，Redis的数据
 Redis提供了两种持久化方式：
 
 ### Redis RDB - 快照
-按指定时间间隔执行数据集的时间点快照，类似于MySQL Dump的 frm 文件。
+RDB 按指定时间间隔执行数据集的时间点快照，类似于MySQL Dump。
 
 ### Redis AOF - 命令日志
 AOF 会记录服务器接收的每个写操作，这些操作将在服务器启动时再次执行，以重建原始数据集。使用与Redis协议本身相同的格式记录命令，并且仅采用`append-only`方式。当日志太大时，Redis可以在后台重写日志。类似于MySQL Binlog、Hbase HLog。在Redis重启时，通过回放日志中的写入指令来重构整个数据。
@@ -42,13 +42,16 @@ Redis Server在有多db 中存储的K.V可理解为Redis的一个状态。当发
 基于全量的持久化就是在某个时刻，将Redis的所有数据持久化到硬盘中，形成一个快照。当Redis 重启时，通过加载最近一个快照数据，可以将 Redis 恢复至最近一次持久化状态上。
 
 ## 2.1 触发方式
-#### save
+#### save 命令
 save 可以由客户端显示触发，也可在redis shutdown 时触发。
 save本身是`单线程串行`方式执行，因此当数据量大时，可能会发生Redis Server的长时间卡顿。但其备份期间不会有其他命令执行，因此备份时期 ` 数据的状态始终是一致性 `的。
 
 若存在老的RDB文件，则新的会替换老的，时间复杂度O(N)。
 
 ![](https://img-blog.csdnimg.cn/20200903015740674.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+
+
 #### bgsave
 bgsave 也可由
 - 客户端显式触发
@@ -57,7 +60,8 @@ bgsave 也可由
 
 bgsave命令在执行时，会`fork`一个子进程。子进程提交完成后，会立即给客户端返回响应，备份操作在后台异步执行，期间不会影响Redis的正常响应。
 
-对于bgsave来说，当父进程Fork完子进程之后，异步任务会将`当前的内存状态作为一个版本进行复制`。在复制过程中产生的变更，不会反映在这次备份当中。
+对于bgsave来说，当父进程Fork完子进程之后，异步任务会将`当前的内存状态作为一个版本进行复制`
+在复制过程中产生的变更，不会反映在这次备份当中。
 
 ##### 不用命令，而使用配置
 在Redis的默认配置中，当满足下面任一条件，会自动触发 bgsave 的执行：
@@ -115,34 +119,32 @@ RDB每次在fork子进程来执行RDB快照数据文件生成的时候，如果
 ## 2.2 恢复流程
 当Redis重新启动时，会从本地磁盘加载之前持久化的文件。当恢复完成之后，再受理后续的请求操作。
 # 3 AOF（append only file）- 增量模式
-RDB记录的是每个状态的全量数据，AOF记录的则是每条**写命令的记录**，通过所有写命令的执行，最后恢复出最终的数据状态。
+
+RDB记录的是每个状态的全量数据，AOF记录的则是每条写命令的记录，通过所有写命令的执行，最后恢复出最终的数据状态。
 ![](https://img-blog.csdnimg.cn/20190707123200343.png)
 
-- 其文件生成如下：
+- 其文件的生成如下：
 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTlmNDA3N2Y0YTAyMjk0NGIucG5n?x-oss-process=image/format,png)
-
-但该模式默认是关闭的，需手动打开
-![](https://img-blog.csdnimg.cn/20210206142749412.png)
-
 ## 3.1 写入流程
 ### AOF的三种策略
 ![](https://img-blog.csdnimg.cn/20200903164454130.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
 #### always
-- 每次刷新缓冲区，都会同步触发同步操作。但因为每次写操作都会触发同步，所以该策略会大大降低Redis的吞吐量。当然了，该模式会拥有最高的容错性。
+- 每次刷新缓冲区，都会同步触发同步操作。因为每次的写操作都会触发同步，所以该策略会降低Redis的吞吐量，但该 模式会拥有最高的容错能力。
 ![](https://img-blog.csdnimg.cn/20200903212610656.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
 
 #### every second
-- 每秒异步的触发同步操作。
+- 每秒异步的触发同步操作，为Redis的`默认配置`。
 ![](https://img-blog.csdnimg.cn/20200903213251820.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
 
 #### no
-- 由os决定何时同步，该方式下Redis无法决定何时落地，因此不可控。
+- 由操作系统决定何时同步，该方式下Redis无法决定何时落地，因此不可控。
 ![](https://img-blog.csdnimg.cn/20200903213521521.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
 #### 对比
 |  命令  |  always  |  everysec  |  no  |
 | --- | --- | --- | --- |
-|  优点  |  不丢数据  |  1 fsync/s，丢1s数据  |  默认，无需设置  |
-|  缺点  |  I/O开销大，一般的STAT盘只有几百TPS  |  丢1s数据 |  不可控  |
+|  优点  |  不丢失数据  |  每秒1次fsync，丢1秒数据  |  无需设置  |
+|  缺点  |  IO开销大，一般的STAT盘只有几百TPS  |  丢1秒数据 |  不可控  |
 
 ## 3.2 回放流程
 AOF的回放时机也是在`机器启动时`，`一旦存在AOF，Redis就会选择增量回放`。
@@ -264,4 +266,6 @@ Redis 4.0 开始支持 RDB 和 AOF 的混合持久化（默认关闭，可以通
 
 参考
 - https://redis.io/topics/persistence
-- 《Redis设计与实现》
\ No newline at end of file
+- 《Redis设计与实现》
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\225\260\346\215\256\347\273\223\346\236\204\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\225\260\346\215\256\347\273\223\346\236\204\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
deleted file mode 100644
index 8cf19f5f5b..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\225\260\346\215\256\347\273\223\346\236\204\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
+++ /dev/null
@@ -1,552 +0,0 @@
-# 1 概述
-## 数据结构和内部编码
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWEyYzI5MzFmYzlkMGQ1OGEucG5n?x-oss-process=image/format,png)
-## 无传统关系型数据库的 Table 模型
-schema 所对应的db仅以编号区分。同一 db 内，key 作为顶层模型，它的值是扁平化的。即 db 就是key的命名空间。
-key的定义通常以 `:` 分隔，如：`Article:Count:1`
-常用的Redis数据类型有：string、list、set、map、sorted-set
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWEwNzBkZWExMTNlYWY4ZDEucG5n?x-oss-process=image/format,png)
-## redisObject通用结构
-Redis中的所有value 都是以object 的形式存在的，其通用结构如下
-![](https://img-blog.csdnimg.cn/2021061610354559.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- type 数据类型
-指 string、list 等类型
-- encoding 编码方式
-指的是这些结构化类型具体的实现方式，同一个类型可以有多种实现。e.g. string 可以用int 来实现，也可以使用char[] 来实现；list 可以用ziplist 或者链表来实现
-- lru
-本对象的空转时长，用于有限内存下长时间不访问的对象清理
-- refcount
-对象引用计数，用于GC
-- ptr 数据指针
-指向以 encoding 方式实现这个对象实际实现者的地址。如：string 对象对应的SDS地址（string的数据结构/简单动态字符串）
-
-## 单线程
-![](https://img-blog.csdnimg.cn/20210205224515421.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-### 单线程为何这么快？
-- 纯内存
-- 非阻塞I/O
-- 避免线程切换和竞态消耗
-![](https://img-blog.csdnimg.cn/20210205224529469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 一次只运行一条命令
-- 拒绝长(慢)命令
-keys, flushall, flushdb, slow lua script, mutil/exec, operate big value(collection)
-- 其实不是单线程
-fysnc file descriptor
-close file descriptor
-
-# 2 string
-Redis中的 string 可表示很多语义
-- 字节串（bits）
-- 整数
-- 浮点数
-
-redis会根据具体的场景完成自动转换，并根据需要选取底层的实现方式。
-例如整数可以由32-bit/64-bit、有符号/无符号承载，以适应不同场景对值域的要求。
-
-- 字符串键值结构，也能是 JSON 串或 XML 结构
-![](https://img-blog.csdnimg.cn/20210205224713852.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-## 内存结构
-在Redis内部，string的内部以 int、SDS（简单动态字符串 simple dynamic string）作为存储结构
-- int 用来存放整型
-- SDS 用来存放字节/字符和浮点型SDS结构
-### SDS
-```c
-typedef struct sdshdr {
-    // buf中已经占用的字符长度
-    unsigned int len;
-    // buf中剩余可用的字符长度
-    unsigned int free;
-    // 数据空间
-    char buf[];
-}
-```
-- 结构图![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQ3OWZlMGFhYzZmYmU0NGQucG5n?x-oss-process=image/format,png)
-存储的内容为“Redis”，Redis采用类似C语言的存储方法，使用'\0'结尾（仅是定界符）。
-SDS的free 空间大小为0，当free > 0时，buf中的free 区域的引入提升了SDS对字符串的处理性能，可以减少处理过程中的内存申请和释放次数。
-### buf 的扩容与缩容
-当对SDS 进行操作时，如果超出了容量。SDS会对其进行扩容，触发条件如下：
-- 字节串初始化时，buf的大小 = len + 1，即加上定界符'\0'刚好用完所有空间
-- 当对串的操作后小于1M时，扩容后的buf 大小 = 业务串预期长度 * 2 + 1，也就是扩大2倍。
-- 对于大小 > 1M的长串，buf总是留出 1M的 free空间，即2倍扩容，但是free最大为 1M。
-### 字节串与字符串
-SDS中存储的内容可以是ASCII 字符串，也可以是字节串。由于SDS通过len 字段来确定业务串的长度，因此业务串可以存储非文本内容。对于字符串的场景，buf[len] 作为业务串结尾的'\0' 又可以复用C的已有字符串函数。
-### SDS编码的优化
-value 在内存中有2个部分：redisObject和ptr指向的字节串部分。
-在创建时，通常要分别为2个部分申请内存，但是对于小字节串，可以一次性申请。
-
-incr userid:pageview (单线程:无竞争)。缓存视频的基本信息(数据源在MySQL)
-![](https://img-blog.csdnimg.cn/20210205230250128.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-```java
-public VideoInfo get(Long id) {
-	String redisKey = redisPrefix + id;
-	VideoInfo videoInfo e redis.get(redisKey);
-	if (videoInfo == null) {
-		videoInfo = mysql.get(id);
-		if (videoInfo != null) {
-			// 序列化
-			redis.set(redisKey serialize(videoInfo)):
-		}
-	}
-}			
-```
-
-
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWUwMjdmOWUwYmY4NWQ2MzgucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LThhYmU4MTk1OTZkNzE1ZmYucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQyYzYzMTk4ZDMzZWU2Y2QucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTZlMDM2MWI2NGRhODI1MzMucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTIxZWVjZDkwNzI2MmJjN2QucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTYxYTUwYWI3Y2QzN2FhYjkucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTRhOTg4NmI4ZGEyNGZlZDIucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJiODU2MDBiM2I1Mjg4ODgucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTI3NjAzOTRkNTk5M2FkODgucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQwNzZjOWM1ZGQwZjUwNzMucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWMwODNlYzM5MzQ0ZmEyM2EucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQxYzRmZGNlN2Q3ZDA2YTQucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTJlN2RiOGNiNTU2Mzg3ZWQucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTBhODMxZmE4NjQwNmJjMjkucG5n?x-oss-process=image/format,png)
-![String类型的value基本操作](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTdjZGRlZTM3NGU4M2I3ZDYucG5n?x-oss-process=image/format,png)
-除此之外，string 类型的value还有一些CAS的原子操作，如：get、set、set value nx（如果不存在就设置）、set value xx（如果存在就设置）。
-
-String 类型是二进制安全的，也就是说在Redis中String类型可以包含各种数据，比如一张JPEG图片或者是一个序列化的Ruby对象。一个String类型的值最大长度可以是512M。
-
-在Redis中String有很多有趣的用法
-*   把String当做原子计数器，这可以使用INCR家族中的命令来实现：[INCR](https://github.com/antirez/redis-doc/blob/master/commands/incr), [DECR](https://github.com/antirez/redis-doc/blob/master/commands/decr), [INCRBY](https://github.com/antirez/redis-doc/blob/master/commands/incrby)。
-*   使用[APPEND](https://github.com/antirez/redis-doc/blob/master/commands/append)命令来给一个String追加内容。
-*   把String当做一个随机访问的向量（Vector），这可以使用[GETRANGE](https://github.com/antirez/redis-doc/blob/master/commands/getrange)和 [SETRANGE](https://github.com/antirez/redis-doc/blob/master/commands/setrange)命令来实现
-*   使用[GETBIT](https://github.com/antirez/redis-doc/blob/master/commands/getbit) 和[SETBIT](https://github.com/antirez/redis-doc/blob/master/commands/setbit)方法，在一个很小的空间中编码大量的数据，或者创建一个基于Redis的Bloom Filter 算法。
-# List
-可从头部（左侧）加入元素，也可以从尾部（右侧）加入元素。有序列表。
-
-像微博粉丝，即可以list存储做缓存。
-```bash
-key = 某大v
-
-value = [zhangsan, lisi, wangwu]
-```
-所以可存储一些list型的数据结构，如：
-- 粉丝列表
-- 文章的评论列表
-
-可通过lrange命令，即从某元素开始读取多少元素，可基于list实现分页查询，这就是基于redis实现简单的高性能分页，可以做类似微博那种下拉不断分页的东西，性能高，就一页一页走。
-
-搞个简单的消息队列，从list头推进去，从list尾拉出来。
-
-List类型中存储一系列String值，这些String按照插入顺序排序。
-
-## 5.1 内存数据结构
-List 类型的 value对象，由 linkedlist 或 ziplist 实现。
-当 List `元素个数少并且元素内容长度不大`采用ziplist 实现，否则使用linkedlist
-
-### 5.1.1 linkedlist实现
-链表的代码结构
-```c
-typedef struct list {
-  // 头结点
-  listNode *head;
-  // 尾节点
-  listNode *tail;
-  // 节点值复制函数
-  void *(*dup)(void * ptr);
-  // 节点值释放函数
-  void *(*free)(void *ptr);
-  // 节点值对比函数
-  int (*match)(void *ptr, void *key);
-  // 链表长度
-  unsigned long len;  
-} list;
-
-// Node节点结构
-typedef struct listNode {
-	struct listNode *prev;
-	struct listNode *next;
-	void *value;
-} listNode;
-```
-linkedlist 结构图
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTg1NzAyZDg2NzYxZDFiZjMucG5n?x-oss-process=image/format,png)
-### 5.1.2 ziplist实现
-存储在连续内存
-![](https://img-blog.csdnimg.cn/ac58e5b93d294a958940f681d4b165e5.png)
-- zlbytes
-ziplist 的总长度
-- zltail
-指向最末元素。
-- zllen
-元素的个数。
-- entry
-元素内容。
-- zlend
-恒为0xFF，作为ziplist的定界符
-
-linkedlist和ziplist的rpush、rpop、llen的时间复杂度都是O(1)：
-- ziplist的lpush、lpop都会牵扯到所有数据的移动，时间复杂度为O(N)
-由于List的元素少，体积小，这种情况还是可控的。
-
-ziplist的Entry结构：
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTJkNWQ1YWI3NGRhYmZhOTkucG5n?x-oss-process=image/format,png)
-记录前一个相邻的Entry的长度，便于双向遍历，类似linkedlist的prev指针。
-ziplist是连续存储，指针由偏移量来承载。
-Redis中实现了2种方式实现：
-- 当前邻 Entry的长度小于254 时，使用1字节实现
-- 否则使用5个字节
-
-> 当前一个Entry长度变化时，可能导致后续的所有空间移动，虽然这种情况发生可能性较小。
-
-Entry内容本身是自描述的，意味着第二部分（Entry内容）包含了几个信息：Entry内容类型、长度和内容本身。而内容本身包含：类型长度部分和内容本身部分。类型和长度同样采用变长编码：
-- 00xxxxxx ：string类型；长度小于64，0~63可由6位bit 表示，即xxxxxx表示长度
-- 01xxxxxx|yyyyyyyy ： string类型；长度范围是[64, 16383]，可由14位 bit 表示，即xxxxxxyyyyyyyy这14位表示长度。
-- 10xxxxxx|yy..y(32个y) : string类型，长度大于16383.
-- 1111xxxx ：integer类型，integer本身内容存储在xxxx 中，只能是1~13之间取值。也就是说内容类型已经包含了内容本身。
-- 11xxxxxx ：其余的情况，Redis用1个字节的类型长度表示了integer的其他几种情况，如：int_32、int_24等。
-由此可见，ziplist 的元素结构采用的是可变长的压缩方法，针对于较小的整数/字符串的压缩效果较好
-
-- LPUSH命令
-在头部加入一个新元素
-- RPUSH命令
-在尾部加入一个新元素
-
-当在一个空的K执行这些操作时，会创建一个新列表。当一个操作清空了一个list时，该list对应的key会被删除。若使用一个不存在的K，就会使用一个空list。
-```bash
-LPUSH mylist a&nbsp;&nbsp; # 现在list是 "a"
-LPUSH mylist b&nbsp;&nbsp; # 现在list是"b","a"
-RPUSH mylist c&nbsp;&nbsp; # 现在list是 "b","a","c" (注意这次使用的是 RPUSH)
-```
-list的最大长度是`2^32 – 1`个元素（4294967295，一个list中可以有多达40多亿个元素）。
-
-从时间复杂度的角度来看，Redis list类型的最大特性是：即使是在list的头端或者尾端做百万次的插入和删除操作，也能保持稳定的很少的时间消耗。在list的两端访问元素是非常快的，但是如果要访问一个很大的list中的中间部分的元素就会比较慢了，时间复杂度是O(N)
-## 适用场景
-- 社交中使用List进行时间表建模，使用 LPUSH 在用户时间线中加入新元素，然后使用 LRANGE 获得最近加入元素
-- 可以把[LPUSH] 和[LTRIM] 命令结合使用来实现定长的列表，列表中只保存最近的N个元素
-- 做MQ，依赖BLPOP这种阻塞命令
-# Set
-类似List，但无序且其元素不重复。
-
-向集合中添加多次相同的元素，集合中只存在一个该元素。在实际应用中，这意味着在添加一个元素前不需要先检查元素是否存在。
-
-支持多个服务器端命令来从现有集合开始计算集合，所以执行集合的交集，并集，差集都很快。
-
-set的最大长度是`2^32 – 1`个元素（一个set中可多达40多亿个元素）。
-## 内存数据结构
-Set在Redis中以intset 或 hashtable存储：
-- 对于Set，HashTable的value永远为NULL
-- 当Set中只包含整型数据时，采用intset作为实现
-
-### intset
-核心元素是一个字节数组，从小到大有序的存放元素
-![](https://img-blog.csdnimg.cn/20200911231000505.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-结构图
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTczN2MzY2NiZDAxMzcxZjEucG5n?x-oss-process=image/format,png)
-因为元素有序排列，所以SET的获取操作采用二分查找，复杂度为O(log(N))。
-
-进行插入操作时：
-- 首先通过二分查找到要插入位置
-- 再对元素进行扩容
-- 然后将插入位置之后的所有元素向后移动一个位置
-- 最后插入元素
-
-时间复杂度为O(N)。为使二分查找的速度足够快，存储在content 中的元素是定长的。
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTkwODQ2NzBlNzk2MTJlMGIucG5n?x-oss-process=image/format,png)
-当插入2018 时，所有的元素向后移动，并且不会发生覆盖。
-当Set 中存放的整型元素集中在小整数范围[-128, 127]内时，可大大的节省内存空间。
-IntSet支持升级，但是不支持降级。
-
-- Set 基本操作
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LThkYmNkMTJiMDRlZmJjZmQucG5n?x-oss-process=image/format,png)
-## 适用场景
-无序集合，自动去重，数据太多时不太推荐使用。
-直接基于set将系统里需要去重的数据扔进去，自动就给去重了，如果你需要对一些数据进行快速的全局去重，你当然也可以基于JVM内存里的HashSet进行去重，但若你的某个系统部署在多台机器呢？就需要基于redis进行全局的set去重。
-
-可基于set玩交集、并集、差集操作，比如交集：
-- 把两个人的粉丝列表整一个交集，看看俩人的共同好友
-- 把两个大v的粉丝都放在两个set中，对两个set做交集
-
-全局这种计算开销也大。
-
-- 记录唯一的事物
-比如想知道访问某个博客的IP地址，不要重复的IP，这种情况只需要在每次处理一个请求时简单的使用SADD命令就可以了，可确保不会插入重复IP
-
-- 表示关系
-你可以使用Redis创建一个标签系统，每个标签使用一个Set表示。然后你可以使用SADD命令把具有特定标签的所有对象的所有ID放在表示这个标签的Set中
-如果你想要知道同时拥有三个不同标签的对象，那么使用SINTER
-
-- 可使用[SPOP](https://github.com/antirez/redis-doc/blob/master/commands/spop) 或 [SRANDMEMBER](https://github.com/antirez/redis-doc/blob/master/commands/srandmember) 命令从集合中随机的提取元素。
-
-# Hash/Map
-一般可将结构化的数据，比如一个对象（前提是这个对象未嵌套其他的对象）给缓存在redis里，然后每次读写缓存的时候，即可直接操作hash里的某个字段。
-```json
-key=150
-
-value={
-  “id”: 150,
-  “name”: “zhangsan”,
-  “age”: 20
-}
-```
-hash类的数据结构，主要存放一些对象，把一些简单的对象给缓存起来，后续操作的时候，可直接仅修改该对象中的某字段的值。
-```c
-value={
-  “id”: 150,
-  “name”: “zhangsan”,
-  “age”: 21
-}
-```
-因为Redis本身是一个K.V存储结构，Hash结构可理解为subkey - subvalue
-这里面的subkey - subvalue只能是
-- 整型
-- 浮点型
-- 字符串
-
-因为Map的 value 可表示整型和浮点型，因此Map也可以使用` hincrby` 对某个field的value值做自增操作。
-
-## 内存数据结构
-hash有HashTable 和 ziplist 两种实现。对于数据量较小的hash，使用ziplist 实现。
-### HashTable 实现
-HashTable在Redis 中分为3 层，自底向上分别是：
-- dictEntry：管理一个field - value 对，保留同一桶中相邻元素的指针，以此维护Hash 桶中的内部链
-- dictht：维护Hash表的所有桶链
-- dict：当dictht需要扩容/缩容时，用户管理dictht的迁移
-
-dict是Hash表存储的顶层结构
-```c
-// 哈希表（字典）数据结构，Redis 的所有键值对都会存储在这里。其中包含两个哈希表。
-typedef struct dict {
-    // 哈希表的类型，包括哈希函数，比较函数，键值的内存释放函数
-    dictType *type;
-    // 存储一些额外的数据
-    void *privdata;
-    // 两个哈希表
-    dictht ht[2];
-    // 哈希表重置下标，指定的是哈希数组的数组下标
-    int rehashidx; /* rehashing not in progress if rehashidx == -1 */
-    // 绑定到哈希表的迭代器个数
-    int iterators; /* number of iterators currently running */
-} dict;
-```
-Hash表的核心结构是dictht，它的table 字段维护着 Hash 桶，桶（bucket）是一个数组，数组的元素指向桶中的第一个元素（dictEntry）。
-
-```c
-typedef struct dictht { 
-    //槽位数组
-    dictEntry **table; 
-    //槽位数组长度
-    unsigned long size; 
-    //用于计算索引的掩码 
-    unsigned long sizemask;
-    //真正存储的键值对数量
-    unsigned long used; 
-} dictht;
-```
-结构图![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTEwYzRhMWFkMGY3MDExMTcucG5n?x-oss-process=image/format,png)
-Hash表使用【链地址法】解决Hash冲突。当一个 bucket 中的 Entry 很多时，Hash表的插入性能会下降，此时就需要增加bucket的个数来减少Hash冲突。
-#### Hash表扩容
-和大多数Hash表实现一样，Redis引入负载因子判定是否需要增加bucket个数：
-```bash
-负载因子 = Hash表中已有元素 / bucket数量
-```
-扩容后，bucket 数量是原先2倍。目前有2 个阀值：
-- 小于1 时一定不扩容
-- 大于5 时一定扩容
-
-- 在1 ~ 5 之间时，Redis 如果没有进行`bgsave/bdrewrite` 操作时则会扩容
-- 当key - value 对减少时，低于0.1时会进行缩容。缩容之后，bucket的个数是原先的0.5倍
-### ziplist 实现
-这里的 ziplist 和List#ziplist的实现类似，都是通过Entry 存放元素。
-不同的是，Map#ziplist的Entry个数总是2的整数倍：
--  第奇数个Entry存放key
-- 下个相邻Entry存放value
-
-ziplist承载时，Map的大多数操作不再是O(1)了，而是由Hash表遍历，变成了链表的遍历，复杂度变为O(N)
-由于Map相对较小时采用ziplist，采用Hash表时计算hash值的开销较大，因此综合起来ziplist的性能相对好一些
-
-哈希键值结构
-![](https://img-blog.csdnimg.cn/919d84762fc44637b12de9e9ebf11a94.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTE1MTAyZWVkODNjMzA4ZWMucG5n?x-oss-process=image/format,png)
-特点：
-- Map的map
-- Small redis
-- field不能相同，value可相同
-
-```bash
-hget key field O(1)
-# 获取 hash key 对应的 field 的 value
-
-hset key field value O(1)
-#  设置 hash key 对应的 field 的 value
-
-hdel key field O(1)
-# 删除 hash key 对应的 field 的 value
-```
-## 实操
-```bash
-127.0.0.1:6379> hset user:1:info age 23
-(integer) 1
-127.0.0.1:6379> hget user:1:info age
-"23"
-127.0.0.1:6379> hset user:1:info name JavaEdge
-(integer) 1
-127.0.0.1:6379> hgetall user:1:info
-1) "age"
-2) "23"
-3) "name"
-4) "JavaEdge"
-127.0.0.1:6379> hdel user:1:info age
-(integer) 1
-127.0.0.1:6379> hgetall user:1:info
-1) "name"
-2) "JavaEdge"
-```
-
-```bash
-hexists key field O(1)
-# 判断hash key是否有field
-hlen key O(1)
-# 获取hash key field的数量
-```
-
-```bash
-127.0.0.1:6379> hgetall user:1:info
-1) "name"
-2) "JavaEdge"
-127.0.0.1:6379> HEXISTS user:1:info name
-(integer) 1
-127.0.0.1:6379> HLEN user:1:info
-(integer) 1
-```
-
-```bash
-hmget key field1 field2... fieldN O(N)
-# 批量获取 hash key 的一批 field 对应的值
-hmset key field1 value1 field2 value2...fieldN valueN O(N)
-# 批量设置 hash key的一批field value
-
-```
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWU3NjM0MmRkMDlkNjAwNzYucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTc3M2FjMWI5NGVhZDA3MTAucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTFmZDkzNTYyMWNjYzg5NTUucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTJhODk3MjllYTA0ODg2YmQucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTM3Y2ZiOTcxY2JlMDhiOTQucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTFmMzJmMGJkMjk0MDFhZmEucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTE3OTRmMjk0NTMzZjQ2ZGMucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWVhMTVmZDBkNjg2YjlkNmQucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTFmNzRhMjEwOTE5YjJhN2UucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LThhMWE1MjNhNWE4NDJiOTAucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTI1OThiNTVlN2FlNzA1ZGMucG5n?x-oss-process=image/format,png)
-![方便单条更新,但是信息非整体,不便管理](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LThiODAyYmFhODgyNzBlZTgucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWE2ZDZhOGZmMmVmYTM4NmIucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTIwMjg4MjgzZDE1ODUyYWQucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWI1YTNmZGQwODdjMTIzM2MucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTJjNzdkNWQzYTc1OTc5NzMucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTQ0NGYzNWU3MzM5MzRjNTkucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTA5OTc2M2M1YzM0YjM4YzUucG5n?x-oss-process=image/format,png)
-Redis Hashes 保存String域和String值之间的映射，所以它们是用来表示对象的绝佳数据类型（比如一个有着用户名，密码等属性的User对象）
-```
-| `1` | `@cli` |
-
-| `2` | `HMSET user:1000 username antirez password P1pp0 age 34` |
-
-| `3` | `HGETALL user:1000` |
-
-| `4` | `HSET user:1000 password 12345` |
-
-| `5` | `HGETALL user:1000` |
-```
-一个有着少量数据域（这里的少量大概100上下）的hash，其存储方式占用很小的空间，所以在一个小的Redis实例中就可以存储上百万的这种对象
-
-Hash的最大长度是2^32 – 1个域值对（4294967295，一个Hash中可以有多达40多亿个域值对）
-# Sorted sets(zset)
-有序集合，去重但可排序，写进去时候给个分数，可以自定义排序规则。比如想根据时间排序，则写时可以使用时间戳作为分数。
-
-排行榜：将每个用户以及其对应的什么分数写进去。
-```bash
-127.0.0.1:6379> zadd board 1.0 JavaEdge
-(integer) 1
-```
-
-获取排名前100的用户：
-```bash
-127.0.0.1:6379> zrevrange board 0 99
-1) "JavaEdge"
-```
-用户在排行榜里的排名：
-```bash
-127.0.0.1:6379> zrank board JavaEdge
-(integer) 0
-```
-
-```bash
-127.0.0.1:6379> zadd board 85 zhangsan
-(integer) 1
-127.0.0.1:6379> zadd board 72 wangwu
-(integer) 1
-127.0.0.1:6379> zadd board 96 lisi
-(integer) 1
-127.0.0.1:6379> zadd board 62 zhaoliu
-(integer) 1
-
-# 获取排名前3的用户
-127.0.0.1:6379> zrevrange board 0 3
-1) "lisi"
-2) "zhangsan"
-3) "wangwu"
-4) "zhaoliu"
-
-127.0.0.1:6379> zrank board zhaoliu
-(integer) 1
-```
-类似于Map的key-value对，但有序
-- key ：key-value对中的键，在一个Sorted-Set中不重复
-- value ： 浮点数，称为 score
-- 有序 ：内部按照score 从小到大的顺序排列
-## 基本操作
-由于Sorted-Set 本身包含排序信息，在普通Set 的基础上，Sorted-Set 新增了一系列和排序相关的操作：
-- Sorted-Set的基本操作
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTI2OTNlZDUxNGU0Njc5MTgucG5n?x-oss-process=image/format,png)
-## 内部数据结构
-Sorted-Set类型的valueObject 内部结构有两种：
-1. ziplist
-![](https://img-blog.csdnimg.cn/20200911183043109.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-实现方式和Map类似，由于Sorted-Set包含了Score的排序信息，ziplist内部的key-value元素对的排序方式也是按照Score递增排序的，意味着每次插入数据都要移动之后的数据，因此ziplist适于元素个数不多，元素内容不大的场景。
-2. skiplist+hashtable
-![](https://img-blog.csdnimg.cn/20200911183355830.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-更通用的场景，Sorted-Set使用sliplist来实现。
-### 8.2.1 zskiplist
-和通用的跳表不同的是，Redis为每个level 对象增加了span 字段，表示该level 指向的forward节点和当前节点的距离，使得getByRank类的操作效率提升
-- 数据结构
-![](https://img-blog.csdnimg.cn/20200911184359226.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-- 结构示意图![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTk5OGJhNjJjZTQ1MTE1YTcucG5n?x-oss-process=image/format,png)
-
-每次向skiplist 中新增或者删除一个节点时，需要同时修改图标中红色的箭头，修改其forward和span的值。
-
-需要修改的箭头和对skip进行查找操作遍历并废弃过的路径是吻合的。span修改仅是+1或-1。
-zskiplist 的查找平均时间复杂度 O(Log(N))，因此add / remove的复杂度也是O(Log(N))。因此Redis中新增的span 提升了获取rank（排序）操作的性能，仅需对遍历路径相加即可（矢量相加）。
-
-还有一点需要注意的是，每个skiplist的节点level 大小都是随机生成的（1-32之间）。
-- zskiplistNode源码
-![](https://img-blog.csdnimg.cn/20200911185457885.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-### 8.2.2 hashtable
-zskiplist 是zset 实现顺序相关操作比较高效的数据结构，但是对于简单的zscore操作效率并不高。Redis在实现时，同时使用了Hashtable和skiplist，代码结构如下：
-![](https://img-blog.csdnimg.cn/20200911190122653.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-Hash表的存在使得Sorted-Set中的Map相关操作复杂度由O(N)变为O(1)。
-
-Redis有序集合类型与Redis的集合类型类似，是非重复的String元素的集合。不同之处在于，有序集合中的每个成员都关联一个Score，Score是在排序时候使用的，按照Score的值从小到大进行排序。集合中每个元素是唯一的，但Score有可能重复。
-
-使用有序集合可以很高效的进行，添加，移除，更新元素的操作（时间消耗与元素个数的对数成比例）。由于元素在集合中的位置是有序的，使用get ranges by score或者by rank（位置）来顺序获取或者随机读取效率都很高。（本句不确定，未完全理解原文意思，是根据自己对Redis的浅显理解进行的翻译）访问有序集合中间部分的元素也非常快，所以可以把有序集合当做一个不允许重复元素的智能列表，你可以快速访问需要的一切：获取有序元素，快速存在测试，快速访问中间的元素等等。
-
-简短来说，使用有序集合可以实现很多高性能的工作，这一点在其他数据库是很难实现的。
-
-## 应用
-*   在大型在线游戏中创建一个排行榜，每次有新的成绩提交，使用[ZADD]命令加入到有序集合中。可以使用[ZRANGE]命令轻松获得成绩名列前茅的玩家，你也可以使用[ZRANK]根据一个用户名获得该用户的分数排名。把ZRANK 和 ZRANGE结合使用你可以获得与某个指定用户分数接近的其他用户。这些操作都很高效。
-
-*   有序集合经常被用来索引存储在Redis中的数据。比如，如果你有很多用户，用Hash来表示，可以使用有序集合来为这些用户创建索引，使用年龄作为Score，使用用户的ID作为Value，这样的话使用[ZRANGEBYSCORE]命令可以轻松和快速的获得某一年龄段的用户。zset有个ZSCORE的操作，用于返回单个集合member的分数，它的操作复杂度是O(1)，这就是收益于你这看到的hash table。这个hash table保存了集合元素和相应的分数，所以做ZSCORE操作时，直接查这个表就可以，复杂度就降为O(1)了。
-
-而跳表主要服务范围操作，提供O(logN)的复杂度。
-# Bitmaps
-位图类型，String类型上的一组面向bit操作的集合。由于 strings是二进制安全的blob，并且它们的最大长度是512m，所以bitmaps能最大设置 2^32个不同的bit。
-# HyperLogLogs
-pfadd/pfcount/pfmerge。
-在redis的实现中，使用标准错误小于1％的估计度量结束。这个算法的神奇在于不再需要与需要统计的项相对应的内存，取而代之，使用的内存一直恒定不变。最坏的情况下只需要12k，就可以计算接近2^64个不同元素的基数。 
-# GEO
-geoadd/geohash/geopos/geodist/georadius/georadiusbymember 
-Redis的GEO特性在 Redis3.2版本中推出，这个功能可以将用户给定的地理位置（经、纬度）信息储存起来，并对这些信息进行操作。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\246\202\350\277\260.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\246\202\350\277\260.md"
deleted file mode 100644
index ed548a417c..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\346\246\202\350\277\260.md"
+++ /dev/null
@@ -1,161 +0,0 @@
-# 简介
-Redis，全名`RE`mote `DI`ctionary `S`erver，开源的高性能的KV内存数据库，支持数据持久化。
-开源的支持多种数据结构的基于键值的存储服务系统，高性能、功能丰富。
-
-提供了Java , C/C++ , C# , PHP , JavaScript ,Perl, Object-C , Python , Ruby , Erlang等客户端
-- 从2010年3月15日起, Redis的开发工作由VMware主持
-- 从2013年5月开始, Redis的开发由Pivotal赞助
-# 1 高性能
-- 底层使用ANSI C语言编写，纯内存数据库，所以读取速度快
-- 通讯采用epolI非阻塞I/O多路复用机制，减少了线程切换时上下文的切换和竞争
-- Redis采用单线程的模型，保证了每个操作的原子性，也减少了线程的上下文切换和竞争
-- Redis存储结构多样化，不同的数据结构对数据存储进行了优化加快读取的速度
-- Redis采用自己实现的事件分离器，效率比较高，内部采用非阻塞的执行方式，吞吐能力比较大。
-
-Redis能读的速度是11w次/s,写的速度是81000次/s。
-
-官方bench-mark数据：测试完成了50个并发执行100000个请求。设置和获取的值是一个256字节字符串。 
-结果：读的速度是10000次/s，写的速度是81000次/s。redis尽量少写多读，符合缓存的适用要求。单机redis支撑万级， 如果10万+可采用主从复制的模式。
-
-
-# 单线程
-Redis 作为一个进程，一直是多线程的。
-## IO线程
-- redis 6前（2020年5月），单线程
-- redis 6后，多线程，NIO模型 ==> 主要的性能提升点 
-
-## 内存处理线程
-- 单线程
-高性能的核心
-
-
-## 原因
-- 无需各种锁的性能消耗
-- 单线程多进程的集群方案
-- CPU 消耗
-
-## 单进程单线程优势
-- 代码更清晰，处理逻辑更简单
-- 不用去考虑各种锁的问题，不存在加锁释放锁操作，没有因为可能出现死锁而导致的性能消耗
-- 不存在多进程或者多线程导致的切换而消耗CPU
-
-### 单进程单线程弊端
-- 无法发挥多核CPU性能，不过可以通过在单机开多个Redis实例来完善
-
-# 2 线程安全
-Redis 操作都是单线程，原子性的。多线程其实体现在数据解析和同步数据。底层内部的核心操作还是单线程的。
-
-# 3 丰富的功能
-## 3.1 数据结构
-string、hash、list、set、sorted set，raw、int、ht、zipmap、linkedlist、ziplist、intset。
-## 3.2  持久化
-- RDB持久化
-- AOF持久化
-- 4.0 引入RDB- AOF混合持久化
-
-## 3.3 主从模式
-## 3.4 哨兵
-## 集群
-## 模块化
-
-
-# 4 适用场景
-缓存、分布式锁、队列、集合、GEO、BitMap、消息队列等。
-
-- 主流互联网微服务架构下的 redis
-
-![](https://img-blog.csdnimg.cn/20201221160230748.JPG?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
-# 常见缓存中间件对比
-![](https://img-blog.csdnimg.cn/20201221170911359.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-# 发展史
-
-## Redis2.6
-1)键的过期时间支持毫秒。
-2)从节点支持只读功能。
-## Redis2.8
-1)可以用bind命令绑定多个IP地址。
-2)发布订阅添加了pub/sub.
-3) Redis Sentinel第二版, 相比于Redis2.6的Redis Sentinel, 此版本已经变成生产可用。
-## Redis3.0 (里程碑)
-1) Redis最大的改动就是添加Redis的分布式实现Redis Cluster。
-## Redis3.2
-Redis3.2在2016年5月6日正式发布，相比于Redis3.0主要特征如下:
-1)添加GE0相并功能。
-2)新的List编码类型: quicklist.
-## Redis4.0 (重大改版)
-1)提供了模块系统，方便第三方开发者拓展Redis的功能。
-2)提供了新的缓存剔除算法: LFU (Last Frequently Used)，并对已有算法进行了优化。
-3)提供了非阻塞del和flushall/flushdb功能,有效解决删除了bigkey可能造成的Redis阻塞。
-4)提供了RDB-AOF混合持久化格式，充分利用了'AOF和RDB各自优势。
-
-## Redis5.0
-1)新的Stream数据类型。
-2)客户经常连接和断开连接时性能更好。
-## Redis6.0
-1) 多线程。多线程部分只是用来处理网络数据的读写和协议解析，执行命令仍然是单线程。
-
-# 使用场景
-## 业务数据缓存
-- 通用数据缓存
-string，int，list，map
-
-- 实时热数据，最新N条数据
-- 会话缓存，token缓存
-
-## 业务数据处理
-- 非严格一致性要求的数据：评论，点击等
-- 业务数据去重：订单处理的幂等校验等
-- 业务数据排序：排名，排行榜等
-
-## 全局一致计数
-- 全局流控计数
-简单的限流组件
-- 秒杀的库存计算
-- 抢红包
-- 全局ID生成
-
-## 高效统计计数
-- id去重
-记录访问ip等全局bitmap操作
-- UV、PV等访问量
-非严格一致性要求
-## 发布订阅与Stream
-- Pub-Sub 模拟队列 subscribe comments publish comments java
-![](https://img-blog.csdnimg.cn/20210205234355569.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- Redis Stream 是 Redis 5.0 版本新增加的数据结构。 Redis Stream 主要用于MQ。可参考 https://www.runoob.com/redis/redis-stream.html![](https://img-blog.csdnimg.cn/20210205234412361.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## 分布式锁
-- 获取锁 原子性操作 
-`SET dlock my_random_value NX PX 30000`
-
-- 释放锁，lua脚本，保证原子性+单线程，从而具有事务性 
-
-```lua
-if redis.call("get",KEYS[1]) == ARGV[1] then 
-	return redis.call("del",KEYS[1]) 
-else 
-	return 0 
-end 
-```
-
-关键点：原子性、互斥、超时
-
-# Redis Lua
-类似于数据库的存储过程，mongodb的js脚本。
-open resty = nginx + lua jit。
-
-
-直接执行 
-
-```bash
-eval "return'hello java'" 0 
-eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value
-```
-
-
-预编译 
-script load script脚本片段 
-返回一个SHA-1签名 shastring 
-
-```bash
-evalsha shastring keynum [key1 key2 key3 ...] [param1 param2 param3 ...]
-```
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\203\255\347\202\271key\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\203\255\347\202\271key\350\247\243\345\206\263\346\226\271\346\241\210.md"
deleted file mode 100644
index 5e9ebe35a6..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\203\255\347\202\271key\350\247\243\345\206\263\346\226\271\346\241\210.md"
+++ /dev/null
@@ -1,66 +0,0 @@
-# 1 热点key的产生原因
-## 1.1 用户消费的数据>>>生产的数据
-- 比如秒杀活动、热点微博、热评，某件商品被数万次点击浏览或购买时，就会造成热点问题
-- 被大量发布、浏览的热点新闻、热点评论等读多写少场景也会产生热点问题
-
-## 1.2 请求的分片过于集中，突破单点性能极限
-在服务端读数据进行访问时，往往会对数据进行分片，此过程中会在某一主机 Server 上对相应的 Key 进行访问，当访问超过 Server 极限时，就会导致热点 Key 问题。
-
-# 2  热点Key的危害
-![](https://img-blog.csdnimg.cn/20210327153406368.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 流量过于集中，突破物理网卡的极限
-- 请求过多，缓存分片服务被打垮
-- 穿透DB
-
-当某热点Key请求在某一主机上超过该主机网卡上限时，由于流量过度集中，导致服务器中其它服务无法正常进行
-=》
-热点过于集中，热点Key缓存过多，超过目前的缓存容量，就会导致缓存分片服务被打垮
-=》
-缓存服务崩溃，此时再有请求产生，会缓存到后台DB，导致缓存穿透，进一步还会导致缓存雪崩。
-
-# 3 解决方案
-通常的解决方案主要集中在对客户端和Server端进行改造。
-## 3.1 服务端缓存方案
-![](https://img-blog.csdnimg.cn/20210327160424686.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-Client会将请求发送到Server，而Server是多线程服务，本地就具有一个基于Cache LRU策略的缓存空间。当Server本身拥堵时，Server不会将请求进一步发送给DB而是直接返回，只有当Server本身畅通时才会将Client请求发送至DB，并且将该数据重新写入缓存。此时就完成了缓存的访问跟重建。
-
-### 缺陷
-- 缓存失效，多线程构建缓存问题
-- 缓存丢失，缓存构建问题
-- 脏读
-
-## 3.2 使用Memcache、Redis
-![](https://img-blog.csdnimg.cn/2021032716131733.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-在客户端单独部署缓存。使用过程中Client首先访问服务层，再对同一主机上的缓存层进行访问。该种解决方案具有就近访问、速度快、没有带宽限制的优点。但也存在问题：
-- 内存资源浪费
-- 脏读
-## 3.3 本地缓存
-### 缺陷
-- 需要提前获知热点
-- 缓存容量有限
-- 不一致性时间增长
-- 热点Key遗漏
-
-## 3.4 随机后缀
-使用Redis做缓存，那可以把一个热点Key的缓存查询压力，分散到多个Redis节点。
-
-**加随机后缀。**
-### 场景
-在一个非常热点的数据，数据更新不是很频繁，但是查询非常频繁，要保证基本保证100%的缓存命中率，该怎么处理？
-
-
-核心思想：空间换时间，即同一热点key保留2份：
-- 不带后缀
-不带的后缀的有TTL
-- 带后缀
-带后缀的没有TTL
-
-先查询不带后缀的，查询不到，则：
-1. 后端查询DB更新缓存
-2. 查询带后缀返回给调用方
-
-![](https://img-blog.csdnimg.cn/20210326111830360.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-这样即可尽可能避免缓存击穿。
-
-> 参考
-> - https://www.alibabacloud.com/help/zh/doc-detail/67252.htm
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\232\204Sentinel\345\223\250\345\205\265\345\216\237\347\220\206.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\232\204Sentinel\345\223\250\345\205\265\345\216\237\347\220\206.md"
deleted file mode 100644
index 1f37d67466..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\232\204Sentinel\345\223\250\345\205\265\345\216\237\347\220\206.md"
+++ /dev/null
@@ -1,302 +0,0 @@
-# 1 Redis Sentinel的意义
-- master宕机了咋整？等运维手工从主切换，再通知所有程序把地址统统改一遍重新上线？那么服务就会停滞很久，显然对于大型系统这是灾难性的！
-所以必须有高可用方案，当故障发生时可自动从主切换，程序也不用重启，不必手动运维。Redis 官方就提供了这样一种方案 —— Redis Sentinel(哨兵)。
-
-sentinal，哨兵，redis集群架构中非常重要的一个组件，主要功能如下
-- 集群监控
-监控Redis master和slave进程的正常工作
-- 消息通知
-如果某个Redis实例有故障，那么哨兵负责发送报警消息给管理员
-- 故障转移
-若master node宕机，会自动转移到slave node上
-- 配置中心
-若发生故障转移，通知client客户端新的master地址
-
-哨兵本身也是分布式，作为一个集群运行：
-- 故障转移时，判断一个master node是否宕机，需要大部分哨兵都同意，涉及分布式选举
-- 即使部分哨兵节点宕机，哨兵集群还是能正常工作
-
-> 目前采用的是sentinal 2版本，sentinal 2相对于sentinal 1来说，重写了很多代码，主要是让故障转移的机制和算法变得更加健壮和简单
-
-哨兵 + Redis主从的部署架构不保证数据零丢失，只保证redis集群的高可用性。
-
-# 为何2个节点无法正常工作
-必须部署2个以上的节点。若仅部署2个实例，quorum=1
-```
-+----+         +----+
-| M1 |---------| R1 |
-| S1 |         | S2 |
-+----+         +----+
-```
-Configuration: `quorum = 1`
-
-master宕机，s1和s2中只要有1个哨兵认为master宕机就可以进行切换，同时会在s1和s2中选举出一个执行故障转移.
-但此时，需要majority，也就是大多数哨兵都是运行的，2个哨兵的majority就是2
-
-> 2个哨兵的majority=2
-> 3个哨兵的majority=2
-> 4个哨兵的majority=2
-> 5个哨兵的majority=3
-
-2个哨兵都运行着，就可以允许执行故障转移
-
-若整个M1和S1运行的机器宕机了，那么哨兵仅剩1个，此时就无majority来允许执行故障转移，虽然另外一台机器还有一个R1，但故障转移不会执行
-
-# 3节点哨兵集群
-```
-       +----+
-       | M1 |
-       | S1 |
-       +----+
-          |
-+----+    |    +----+
-| R2 |----+----| R3 |
-| S2 |         | S3 |
-+----+         +----+
-```
-Configuration: quorum = 2，majority
-
-若M1节点宕机了，还剩下2个哨兵，S2和S3可以一致认为master宕机了，然后选举出一个来执行故障转移
-
-同时3个哨兵的`majority`是2，所以余存的2个哨兵运行着，就可执行故障转移
-
-
-
-# 2 Redis Sentinel 架构
-## Redis Sentinel故障转移
-1. 多个sentinel发现并确认master有问题。
-2. 选举出一个sentinel作为领导。
-3. 选出一个slave作为master.
-4. 通知其余slave成为新的master的slave.
-5. 通知客户端主从变化
-6. 等待老的master复活成为新master的slave
-![](https://img-blog.csdnimg.cn/20210420141013774.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-- 可监控多套
-![](https://img-blog.csdnimg.cn/2021042014284067.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# 3 安装与配置
-1. 配置开启主从节点
-2. 配置开启sentinel监控主节点。(sentinel是特殊的redis)
-3. 实际应该多机器
-4. 详细配置节点
-
-![](https://img-blog.csdnimg.cn/20210420143559745.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210420143538431.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-Redis 主节点
-
-```bash
-[启动]
-redis-server redis- 7000.conf
-
-[配置]
-port 7000
-daemonize yes
-pidfile /var/run/redis-7000.pid
-logfile "7000.log"
-dir "/opt/soft/redis/data/"
-```
-
-Redis 从节点
-```bash
-[启动]
-redis-server redis-7001.conf
-redis-server redis-7002.conf
-
-slave-1[配置]
-
-port 7001
-daemonize yes
-pidfile /var/run/redis-7001.pid
-logfile "7001.log"
-dir "/opt/soft/redis/data/"
-slaveof 127.0.0.1 7000
-
-slave-2[配置]
-port 7002
-daemonize yes
-pidfile /var/run/redis-7002.pid
-logfile "7002.log"
-dir "/opt/soft/redis/data/"
-slaveof 127.0.0.1 7000
-```
-
-Sentinel 主要配置
-
-```bash
-port $(port)
-dir "/opt/soft/redis/data/"
-logfile " $(port).log"
-sentinel monitor mymaster 127.0.0.1 7000 2
-sentinel down-after-milliseconds mymaster 30000
-sentinel parallel-syncs mymaster 1
-sentinel failover-timeout mymaster 180000
-```
-
-## 演示
-![](https://img-service.csdnimg.cn/img_convert/407d586e9e68f5ab618429f486f47b77.png)
-- 主节点配置![](https://img-service.csdnimg.cn/img_convert/e0ccc6ea9b2d8cfac7e5c18188bff594.png)
-![](https://img-blog.csdnimg.cn/20210206170428942.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- 重定向![](https://img-service.csdnimg.cn/img_convert/14364230c56b886b6beadc566d2b13a7.png)
-- 打印检查配置文件![](https://img-service.csdnimg.cn/img_convert/91ea7d183e7b6432231735be89605743.png)
-- 启动
-![](https://img-blog.csdnimg.cn/20210206170745522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-# 4 客户端
-- 客户端实现基本原理-1
-![](https://img-service.csdnimg.cn/img_convert/577082c68e80768c379c3e6a27ed6139.png)
-- 客户端实现基本原理-2
-![](https://img-service.csdnimg.cn/img_convert/36a24d0612871f9201a1336d410e4ec5.png)
-
-- 客户端实现基本原理-3 验证![](https://img-service.csdnimg.cn/img_convert/a920bd693c109e0613fef5c5fbb81c91.png)
-- 客户端实现基本原理-4 通知(发布订阅))
-![](https://img-service.csdnimg.cn/img_convert/a24f586a657f8dfadfa32da4de87d319.png)
-![](https://img-blog.csdnimg.cn/20210420162740248.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## 客户端接入流程
-1. Sentinel地址集合
-2. masterName
-3. 不是代理模式
-
-```java
-JedisSentinelPool sentinelPool = new JedisSentinelPool(masterName, sentinelSet, poolConfig, timeout);
-
-Jedis jedis = null;
-try {
-	jedis = redisSentinelPool.getResource();
-	//jedis command
-} catch (Exception e) {
-	logger.error(e.getMessage(), e);
-} finally {
-	if (jedis != null)
-		jedis.close();
-}
-```
-
-# 5 定时任务
-1. 每10s 每个 sentinel 对 master 和 replica 执行 INFO 命令
-- 发现 replica  节点
-- 确认主从关系
-![](https://img-blog.csdnimg.cn/20210419164949341.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-2. 每 2s 每个 sentinel 通过 master 节点的channel交换信息(pub/sub)
-- 通过 _sentinel_ :java频道交互
-- 交互对节点的"看法”和自身信息
-![](https://img-blog.csdnimg.cn/20210420153539965.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-3. 每 1s 每个 sentinel 对其他 sentinel 和 redis 执行ping
-- 心跳检测，失败判定依据
-![](https://img-blog.csdnimg.cn/2021042015015784.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# 6 主观下线和客观下线
-## 主观下线(Subjectively Down,SDOWN)
-`单个` Sentinel 节点对服务器做出的下线判断，即单个 Sentinel 认为某个服务下线（有可能是接收不到订阅，之间的网络不通等一系列原因）。
-
-注意主观下线是每个sentinel节点对Redis节点失败的"**偏见**”。所以还需要客观下线机制。
-## 客观下线(Objectively Down,ODOWN) 
-`多个` Sentinel 实例在对同一个服务器做出 SDOWN 判断，并且通过命令互相交流之后，得出的服务器下线判断，然后开启 failover。
-
-只有在足够数量( 超过quorum个)的 Sentinel 都将一个服务器标记为主观下线之后， 服务器才会被标记为客观下线（ODOWN）。只有当 Master 被认定为客观下线时，才会发生故障迁移。
-
-## 仲裁
-仲裁指配置文件中的 `quorum` 参数。某个 Sentinel 先将 Master 节点标记为主观下线，然后会将这个判定通过 `sentinel is-master-down-by-addr` 命令询问其他 Sentinel 节点是否也同样认为该 addr 的 Master 节点要做主观下线。
-![](https://img-blog.csdnimg.cn/20210420160002275.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-```bash
-sentinel monitor <masterName> <ip> < port> <quorum>
-sentinel monitor myMaster 127.0.0.1 6379 2
-# 
-sentinel down-after-milliseconds < masterName> < timeout>
-sentinel down-after-milliseconds mymaster 30000
-```
-最后当达成这一共识的 Sentinel 个数达到前面说的 quorum 设置的值时，该 Master 节点会被认定为客观下线并进行故障转移。
-`quorum` 值一般设为 Sentinel 个数的二分之一加 1，例如 3 个 Sentinel 就设为 2。
-
-综上可得
-## 哨兵工作原理
-1. 每个 Sentinel 以 1次/s 向它所知的 Master，Slave 以及其他 Sentinel 节点发送一个 PING
-2. 若一个实例距离最后一次有效回复 PING 的时间超过配置文件 `down-after-milliseconds`，则该实例会被 Sentinel 标记为 **主观下线**
-3. 若一个 Master 被标记为主观下线，那么正在监视这个 Master 的所有 Sentinel 要以 1次/s 确认 Master 是否真的进入主观下线状态
-4. 当有足够数量 Sentinel（大于等于配置文件指定的值）在指定时间范围内确认 Master 的确进入主观下线状态，则 Master 会被标记为 **客观下线**
-5. 若 Master 处于 ODOWN 状态，则投票自动选出新Master。将剩余从节点指向新Master继续数据复制
-6. 在正常情况下，每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master，Slave 发送 INFO 命令；当 Master 被 Sentinel 标记为客观下线时，Sentinel 向已下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次；
-7. 若没有足够数量 Sentinel 同意 Master 已经下线，Master 的**客观下线**状态会被移除。若 Master 重新向 Sentinel 的 PING 命令返回有效回复，Master 的主观下线状态就会被移除。
-
-# 7 领导者选举
-原因：只有一个sentinel节点完成故障转移。
-选举：通过sentinel is-master-down-by-addr命令都希望成为领导者：
-1. 每个做主观下线的Sentinel节点向其他Sentinel节点发送命令，要求将它设置为领导者
-2. 收到命令的Sentinel节点如果没有同意通过其他Sentinel节点发送的命令，那么将同意该请求，否则拒绝
-3. 如果该Sentinel节点发现自己的票数已经超过Sentinel集合半数且超过quorum，则将成为领导者
-4. 如果此过程有多个Sentinel节点成为了领导者，那么将等待一段时间重新选举
-
-- 选举实例
-![](https://img-service.csdnimg.cn/img_convert/780616dab829e1a2bcc249be5fe34ea1.png)
-# 8 故障转移
-### 领导者节点完成
-1. 从slave节点中选出一个“合适的"节点作为新的master节点
-2. 对上面的slave节点执行slaveof no one命令让其成为master节点
-3. 向剩余slave节点发送命令，让它们成为新master节点的slave节点，复制规则和parallel-syncs参数有关
-4. 更新对原来master节点配置为slave，并保持着对其关注，当其恢复后命令它去复制新的master节点。
-
-### 选择合适的slave节点
-1. 选择slave priority(slave节点优先级)最高的slave节点，如果存在划返同，不存在则继续。
-2. 选择复制偏移量最大的slave节点(复制的最完整)，若存在则返回，不存在则
-继续。
-3. 选择runId最小的slave节点。
-
-# 常见开发运维问题
-## 节点运维
-- 机器下线:例如过保等情况
-- 机器性能不足:例如CPU、内存、硬盘、网络等
-- 节点自身故障:例如服务不稳定等
-
-
-主节点
-`sentinel failover <masterName>`
-![](https://img-blog.csdnimg.cn/20200910035357117.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-节点下线
-- 从节点:临时下线还是永久下线,例如是否做一些清理工作。但是要考虑读写分离的情况。
-- Sentinel节点: 同上。
-
-节点上线
-- 主节点: sentinel failover进行替换
-- 从节点: slaveof即可, sentinel节点可以感知
-- sentinel节点:参考其他sentinel节点启动即可。
-
-从节点的作用
-1. 副本：高可用基础
-2. 拓展读能力
-![](https://img-blog.csdnimg.cn/20200909232846263.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-
-由于Redis Sentinel只会对主节点进行故障转移,对从节点采取主观的下线,所以需要自定义一个客户端来监控对应的事件
-
-三个消息
-- +switch-master :切换主节点(从节点晋升主节点)
-- +convert-to-slave :切换从节点(原主节点降为从节点)
-- +sdown:主观下线。
-
-# 总结
-- Redis Sentinel是Redis的高可用实现方案：
-故障发现、故障自动转移、配置中心、客户端通知。
-
-- Redis Sentinel从2.8版本开始才正式生产可用
-- 尽可能在不同物理机上部署Redis Sentinel的所有节点
-- Redis Sentinel中的Sentinel节点个数应该≥3，且最好为奇数，便于选举
-- Redis Sentinel中的数据节点与普通数据节点无差异
-- 客户端初始化时连接的是Sentinel节点集合，不再是具体的Redis节点，但
-Sentinel只是配置中心，并非代理
-- Redis Sentinel通过三个定时任务实现了Sentinel节点对于主节点、从节点、
-其余Sentinel节点的监控
-- Redis Sentinel在对节点做失败判定时分为主观下线和客观下线
-- 看懂Redis Sentinel故障转移日志对于Redis Sentinel以及问题排查非常有帮助
-- Redis Sentinel实现读写分离高可用可以依赖Sentinel节点的消息通知,获取Redis数据节点的状态变化
-
-参考
-- https://hellokangning.github.io/zh/post/redis-sentinel-client-connection/
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\232\204\350\277\207\346\234\237\347\255\226\347\225\245\345\222\214\345\206\205\345\255\230\346\267\230\346\261\260\347\255\226\347\225\245.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\232\204\350\277\207\346\234\237\347\255\226\347\225\245\345\222\214\345\206\205\345\255\230\346\267\230\346\261\260\347\255\226\347\225\245.md"
index 96bd113256..fc6b89a619 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\232\204\350\277\207\346\234\237\347\255\226\347\225\245\345\222\214\345\206\205\345\255\230\346\267\230\346\261\260\347\255\226\347\225\245.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\232\204\350\277\207\346\234\237\347\255\226\347\225\245\345\222\214\345\206\205\345\255\230\346\267\230\346\261\260\347\255\226\347\225\245.md"
@@ -1,14 +1,13 @@
+> 全是干货的技术号：
+> 本文已收录在github，欢迎 star/fork：
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
 # 1 设置带过期时间的 key
-```bash
-# 时间复杂度：O（1），最常用的方式
-expire key seconds
 
-# 字符串独有的方式
-setex(String key, int seconds, String value)
+```shell
+expire key seconds
+时间复杂度：O（1）
 ```
-除了string独有设置过期时间方法，其他类型都需要依靠expire方法来设置时间。
-如果没有设置时间，那缓存就是永不过期。
-如果设置了过期时间，之后又想让缓存永不过期，使用persist key。
 
 设置`key`的过期时间。超时后，将会自动删除该`key`。在Redis的术语中一个`key`的相关超时是volatile的。
 
@@ -64,126 +63,40 @@ EXPIRE 族的命令能够将过期项与给定key关联，但代价是该key使
 即使运行时的实例，也始终会检查计算机时钟，例如，如果将一个key设置为 1000 秒，然后在将来设置计算机时间 2000 秒，则该key将立即过期，而不是持续 1000 秒。
 
 # 2 Redis 如何使key过期
-- 被动方式 - 惰性删除
-- 主动方式 - 定期删除
+键的过期方式有两种：被动方式 - 惰性删除，主动方式 - 定期删除。
 
-为保证 Redis 的高性能，所以不会单独安排一个线程专门去删除。
 ## 2.1 惰性删除
-key过期时不删除，每次获取key时，再去检查是否过期。
-若过期，则删除，返回null。
-
-### 优点
-删除操作只发生在从数据库取出key时，且只删除当前key，所以对CPU时间占用较少。此时删除已非做不可（若还不删除，就会获取到已过期key）。
-
-当查询该key时，Redis再很懒惰地检查是否删除。这和 Spring 的延迟初始化有着异曲同工之妙。
-###  缺点
-但这是不够的，因为有过期key，永远不会再访问。若大量key在超出超时时间后，很久一段时间内，都没有被获取过，则可能发生内存泄露（无用垃圾占用了大量内存）。
-
-无论如何，这些key都应过期，因此还需要定期 Redis 在具有过期集的key之间随机测试几个key。已过期的所有key将从key空间中删除。
-
-## 定时删除
-在设置key的过期时间的同时，为该key创建一个定时器，让定时器在key的过期时间来临时，对key进行删除。
+当客户端尝试访问key时，key会被动过期，即Redis会检查该key是否设置了过期时间，如果过期了就会删除，也不会返回任何东西。
+注意并非是key到期了就会被自动删除，而是当查询该key时，Redis再很懒惰地检查是否删除。这和 spring 的延迟初始化有着异曲同工之妙。
 
-### 优点
-保证内存被尽快释放
+当然，这是不够的，因为有过期的key，永远不会再访问。无论如何，这些key都应过期，因此请定期 Redis 在具有过期集的key之间随机测试几个key。已过期的所有key将从key空间中删除。
 
-### 缺点
-- 若过期key很多，删除这些key会占用很多的CPU时间，在CPU时间紧张的情况下，CPU不能把所有的时间用来做要紧的事儿，还需要去花时间删除这些key
-- 定时器的创建耗时，若为每一个设置过期时间的key创建一个定时器（将会有大量的定时器产生），性能影响严重
-
-所以没人用
 ## 2.2 定期删除
-每隔一段时间执行一次删除过期key操作。
-
-### 优点：
-- 通过限制删除操作的时长和频率，来减少删除操作对CPU时间的占用--处理"定时删除"的缺点
-- 定期删除过期key--处理"惰性删除"的缺点
-
-### 缺点
-- 在内存友好方面，不如"定时删除"
-- 在CPU时间友好方面，不如"惰性删除"
-
-难点
-- 合理设置删除操作的执行时长（每次删除执行多长时间）和执行频率（每隔多长时间做一次删除）（这个要根据服务器运行情况来定了）
-
-
 具体来说，如下 Redis 每秒 10 次：
 1. 测试 20 个带有过期的随机键
 2. 删除找到的所有已过期key
 3. 如果超过 25% 的key已过期，从步骤 1 重新开始
 
-这是一个微不足道的概率算法，假设我们的样本代表整个key空间，继续过期，直到可能过期的key百分比低于 25%。
+这是一个微不足道的概率算法，基本上假设我们的样本代表整个key空间，继续过期，直到可能过期的key百分比低于 25%。
 这意味着在任何给定时刻，使用内存的已过期的最大键量等于最大写入操作量/秒除以 4。
 
-## Redis采用的过期策略
-惰性删除+定期删除。
-
-### 惰性删除流程
-在进行get或setnx等操作时，先检查key是否过期：
-- 若过期，删除key，然后执行相应操作
-- 若没过期，直接执行相应操作
-
-### 定期删除流程
-简单而言，对指定个数个库的每一个库随机删除小于等于指定个数个过期key。
-
-- 遍历每个数据库（就是redis.conf中配置的"database"数量，默认为16）
-- 检查当前库中的指定个数个key（默认是每个库检查20个key，注意相当于该循环执行20次，循环体时下边的描述）
-- 如果当前库中没有一个key设置了过期时间，直接执行下一个库的遍历
-- 随机获取一个设置了过期时间的key，检查该key是否过期，如果过期，删除key
-- 判断定期删除操作是否已经达到指定时长，若已经达到，直接退出定期删除。
-
-**注意**：
-- 对于定期删除，在程序中有一个全局变量current_db来记录下一个将要遍历的库，假设有16个库，我们这一次定期删除遍历了10个，那此时的current_db就是11，下一次定期删除就从第11个库开始遍历，假设current_db等于15了，那么之后遍历就再从0号库开始（此时current_db==0）
-
-由于在实际中并没有操作过定期删除的时长和频率，所以这两个值的设置方式作为疑问。
-
-## RDB对过期key处理
-过期key对RDB没有任何影响。
-
-*   从内存数据库持久化数据到RDB文件
-    *   持久化key之前，会检查是否过期，过期的key不进入RDB文件
-*   从RDB文件恢复数据到内存数据库
-    *   数据载入数据库之前，会对key先进行过期检查，如果过期，不导入数据库（主库情况）
-
-## AOF处理过期key
-过期key对AOF没有任何影响。
-
-### 从内存数据库持久化到AOF文件
-- 当key过期后，还没有被删除，此时进行执行持久化操作（该key是不会进入aof文件的，因为没有发生修改命令）
-- 当key过期后，在发生删除操作时，程序会向aof文件追加一条del命令（在将来的以aof文件恢复数据的时候该过期的键就会被删掉）
-### AOF重写
-重写时，会先判断key是否过期，已过期的key不会重写到aof文件
-
 ## 2.3 在复制链路和 AOF 文件中处理过期的方式
 为了在不牺牲一致性的情况下获得正确行为，当key过期时，DEL 操作将同时在 AOF 文件中合成并获取所有附加的从节点。这样，过期的这个处理过程集中到主节点中，还没有一致性错误的可能性。
 
 但是，虽然连接到主节点的从节点不会独立过期key（但会等待来自master的 DEL），但它们仍将使用数据集中现有过期的完整状态，因此，当选择slave作为master时，它将能够独立过期key，完全充当master。
 
-默认每台Redis服务器有16个数据库，默认使用0号数据库，所有的操作都是对0号数据库的操作
-```shell
-# 设置数据库数量。默认为16个库，默认使用DB 0，可使用"select 1"来选择一号数据库
-# 注意：由于默认使用0号数据库，那么我们所做的所有的缓存操作都存在0号数据库上，
-# 当你在1号数据库上去查找的时候，就查不到之前set过的缓存
-# 若想将0号数据库上的缓存移动到1号数据库，可以使用"move key 1"
-databases 16
-```
 
-- memcached只是用了惰性删除，而redis同时使用了惰性删除与定期删除，这也是二者的一个不同点（可以看做是redis优于memcached的一点）
-- 对于惰性删除而言，并不是只有获取key的时候才会检查key是否过期，在某些设置key的方法上也会检查（eg.setnx key2 value2：该方法类似于memcached的add方法，如果设置的key2已经存在，那么该方法返回false，什么都不做；如果设置的key2不存在，那么该方法设置缓存key2-value2。假设调用此方法的时候，发现redis中已经存在了key2，但是该key2已经过期了，如果此时不执行删除操作的话，setnx方法将会直接返回false，也就是说此时并没有重新设置key2-value2成功，所以对于一定要在setnx执行之前，对key2进行过期检查）
+可是，很多过期key，你没及时去查，定期删除也漏掉了，大量过期key堆积内存，Redis内存殆耗尽！
 
+因此还需有内存淘汰机制！
 
-可是，很多过期key，你没及时去查，定期删除也漏掉了，大量过期key堆积内存，Redis内存殆耗尽！
-因此内存满时，还需有内存淘汰机制！这就是 Redis 自己 **主动删除** 数据了！
 # 3 内存淘汰
 ## 3.1 内存淘汰策略
-- 配置项
-![](https://img-blog.csdnimg.cn/20210320233745559.png)
-
 ![](https://img-blog.csdnimg.cn/20200906224634410.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
 
 ### noeviction（Redis默认策略）
-![](https://img-blog.csdnimg.cn/20210321154718846.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-不删除任何东西，只需在写操作中返回错误。即不会继续服务写请求 (DEL 请求可以继续服务)，读请求可以继续进行。这可保证不会丢失数据，但是会让线上的业务不能持续进行。
+不会继续服务写请求 (DEL 请求可以继续服务)，读请求可以继续进行。这样
+可以保证不会丢失数据，但是会让线上的业务不能持续进行。
 
 - config.c
 ```c
@@ -194,17 +107,17 @@ createEnumConfig("maxmemory-policy", NULL,
 ```
 
 ### allkeys-random
-当内存不足以容纳新写入的数据时，在键空间中，随机移除某key。
+当内存不足以容纳新写入数据时，在键空间中，随机移除某key。凭啥随机呢，至少也是把最近最少使用的key删除。
+
 
-但是凭啥随机呢，至少也是把最近最少使用的key删除。
-### allkeys-lru
+### allkeys-lru(Least Recently Used)
 当内存不足以容纳新写入数据时，在键空间中，移除最近最少使用的key，没有设置过期时间的 key 也会被淘汰。
 ### allkeys-lfu(Least Frequently Used)
 LRU的关键是看页面**最后一次被使用到发生调度的时间长短**，而LFU关键是看**一定时间段内页面被使用的频率**。
-### volatile-lru（最常用）
+### volatile-lru
 尝试淘汰设置了过期时间的 key，最少使用的 key 优先被淘汰。
 没有设置过期时间的 key 不会被淘汰，这样可以保证需要持久化的数据不会突然丢失。
-**区别于 allkey-lru，这个策略要淘汰的只是过期的 key 集。**
+ 区别于 allkey-lru，这个策略要淘汰只是过期的 key 集合。
 ### volatile-lfu
 ### volatile-random
 淘汰的 key 是过期 key 集合中随机的 key。
@@ -243,5 +156,8 @@ public class LRUCache<K, V> extends LinkedHashMap<K, V> {
 }
 ```
 
-> 参考
-> - https://redis.io/commands#generic
+参考
+- https://redis.io/commands#generic
+
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md"
deleted file mode 100644
index 23f5b22324..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\347\275\221\347\273\234\351\200\232\344\277\241\345\215\217\350\256\256.md"
+++ /dev/null
@@ -1,229 +0,0 @@
-一切网络通信，皆需要双方遵守协议才能互联。Redis协议在以下几点之间做出了折衷：
-- 简单的实现
-- 快速被计算机解析
-- 简单到可被人工解析
-
-# 网络层
-Redis在TCP端口6379上监听到来的连接，客户端连接到来时，Redis服务器为此创建一个TCP连接。在客户端与服务器端之间传输的每个Redis命令或者数据都以`\r\n`结尾。
-
-## 请求
-Redis接收由不同参数组成的命令。一旦收到命令，将会立刻被处理，并响应给客户端。
-
-## 新的统一请求协议
-新的统一协议在Redis 1.2中引入，在Redis 2.0中，成为与Redis服务器通讯的标准方式。
-
-在这个统一协议里，发送给Redis服务端的所有参数都是二进制安全的。
-
-如下是通用形式：
-
-```bash
-*<number of arguments> CR LF
-$<number of bytes of argument 1> CR LF
-<argument data> CR LF
-...
-$<number of bytes of argument N> CR LF
-<argument data> CR LF
-```
-
-示例：
-
-```bash
-➜  ~ nc localhost 6379
-keys *
-*2
-$18
-user:sign:5:202101
-$18
-seckill_vouchers:6
-```
-上面的命令看上去像是单引号字符串，所以可在查询中看到每个字节的准确值：
-```bash
-"*2\r\n$18\r\nuser:sign:5:202101\r\n$18\r\nseckill_vouchers:6\r\n"
-```
-
-在Redis的响应中也使用这样的格式。批量回复时，这种格式用于每个参数$6\r\nmydata\r\n。 实际的统一请求协议是Redis用于返回列表项，并调用 Multi-bulk回复。 仅仅是N个以以*\r\n为前缀的不同批量回复，是紧随的参数（批量回复）数目。
-
-## 响应
-Redis用不同的响应类型回复命令。它可能从服务器发送的第一个字节开始校验回复类型：
-- 单行回复
-回复的第一个字节将是`+`
-
-```bash
-set java edge
-+OK
-```
-- 错误消息
-回复的第一个字节将是`-`
-
-```bash
-keys*
--ERR unknown command `keys*`, with args beginning with:
-```
-- 整型数字
-回复的第一个字节将是`:`
-- 批量回复
-回复的第一个字节将是`$`
-```bash
-keys *
-*2
-$18
-user:sign:5:202101
-$18
-seckill_vouchers:6
-```
-
-- 多个批量回复
-回复的第一个字节将是`*`
-
-### Simple Strings
-状态回复（或者单行回复）以“+”开始以“\r\n”结尾的单行字符串形式。例如：
-
-"+OK\r\n"
-客户端库将在“+”后面返回所有数据，正如上例中字符串“OK”一样。
-
-
-### Errors
-错误回复发送类似于状态回复。唯一的不同是第一个字节用“-”代替“+”。
-
-错误回复仅仅在一些意料之外的事情发生时发送，例如：如果你试图执行一个操作来应付错误的数据类型，或者如果命令不存在等等。所以当收到一个错误回复时，客户端将会出现一个异常。
-
-
-### Integers
-这种回复类型只是用CRLF结尾字符串来表示整型，用一个字节的“：”作为前缀。例如：“：0\r\n”，或者“:1000\r\n”是整型回复。
-
-像INCR或者LASTAVE命令用整型回复作为实际回复值，此时对于返回的整型没有特殊的意思。它仅仅是为INCR、LASTSAVE的UNIX时间等增加数值。
-
-一些命令像EXISTS将为true返回1，为false返回0。
-
-其它命令像SADD、SREM和SETNX如果操作实际完成了的话将返回1，否则返回0。
-
-接下来的命令将回复一个整型回复：SETNX、DEL、EXISTS、INCR、INCRBY、DECR、DECRBY、DBSIZE、LASTSAVE、RENAMENX、MOVE、LLEN、SADD、SREM、SISMEMBER、SCARD。
-
-
-### Bulk Strings
-批量响应被服务器用于返回一个单二进制安全字符串。
-
-```bash
-C: GET mykey
-S: $6\r\nfoobar\r\n
-```
-
-服务器发送第一行响应，该行以“$”开始后面跟随实际要发送的字节数，随后是CRLF，然后发送实际数据，随后是2个字节的额外数据用于最后的CRLF。服务器发送的准确序列如下：
-
-```bash
-"$6\r\nfoobar\r\n"
-```
-
-如果请求的值不存在，批量响应将使用特殊的值-1来作为数据长度，例如：
-
-```bash
-C: GET nonexistingkey
-S: $-1
-```
-
-当请求的对象不存在时，客户端库API不会返回空字符串，而会返回空对象。例如：Ruby库返回‘nil’，而C库返回NULL（或者在回复的对象里设置指定的标志）等等。
-
-
-# Arrays
-像命令LRNGE需要返回多个值（列表的每个元素是一个值，而LRANGE需要返回多于一个单元素）。使用多批量写是有技巧的，用一个初始行作为前缀来指示多少个批量写紧随其后。批量回复的第一个字节总是*，例如：
-
-```bash
-C: LRANGE mylist 0 3
-s: *4
-s: $3
-s: foo
-s: $3
-s: bar
-s: $5
-s: Hello
-s: $5
-s: World
-```
-
-正如您可以看到的多批量回复是以完全相同的格式使用Redis统一协议将命令发送给服务器。
-
-服务器发送的第一行是*4\r\n，用于指定紧随着4个批量回复。然后传送每个批量写。
-
-如果指定的键不存在，则该键被认为是持有一个空的列表，且数值0被当作多批量计数值来发送，例如：
-
-```bash
-C: LRANGE nokey 0 1
-S: *0
-```
-
-当BLPOP命令超时时，它返回nil多批量回复。这种类型多批量回复的计数器是-1，且值被当作nil来解释。例如：
-
-```bash
-C: BLPOP key 1
-S: *-1
-```
-
-当这种情况发生时，客户端库API将返回空nil对象，且不是一个空列表。这必须有别于空列表和错误条件（例如：BLPOP命令的超时条件）。
-
-多批量回复中的Nil元素
-多批量回复的单元素长度可能是-1，为了发出信号这个元素被丢失且不是空字符串。这种情况发送在SORT命令时，此时使用GET模式选项且指定的键丢失。一个多批量回复包含一个空元素的例子如下：
-
-```bash
-S: *3
-S: $3
-S: foo
-S: $-1
-S: $3
-S: bar
-```
-
-第二个元素是空。客户端库返回如下:
-
-```bash
-["foo",nil,"bar"]
-```
-
-# 多命令和管道
-客户端能使用同样条件为了发出多个命令。管道用于支持多命令能够被客户端用单写操作来发送，它不需要为了发送下一条命令而读取服务器的回复。所有回复都能在最后被读出。
-
-通常Redis服务器和客户端拥有非常快速的连接，所以在客户端的实现中支持这个特性不是那么重要，如果一个应用需要在短时间内发出大量的命令，管道仍然会非常快。
-
-旧协议发送命令
-在统一请求协议出现前，Redis用不同的协议发送命令，现在仍然支持，它简单通过手动telnet。在这种协议中，有两种类型的命令：
-
-内联命令：简单命令其参数用空格分割字符串。非二进制安全。
-批量命令：批量命令准确如内联命令，但是最后的参数用特殊方式来处理用于保证最后参数二进制安全。 内联命令
-最简单的发送Redis命令的方式是通过内联命令。下面是一个使用内联命令聊天的服务器/客户端的例子（服务器聊天用S:开始，客户端聊天用C:开始）。
-
-```bash
-C: PING
-S: +PONG
-```
-
-下面是另外一个内联命令返回整数的例子：
-
-```bash
-C: EXISTS somekey
-S: :0
-```
-
-因为‘somekey’不存在，所以服务器返回‘:0’。
-
-注意：EXISTS命令带有一个参数。参数用空格分割。
-
-批量命令
-
-一些命令当用内联命令发送时需要一种特殊的格式用于支持最后参数二进制安全。这种命令用最后参数作为“字节计数器”，然后发送批量数据（因为服务器知道读取多少个字节，所以是二进制安全的）。
-
-请看下面的例子：
-
-```bash
-C: SET mykey 6
-C: foobar
-S: +OK
-```
-
-这条命令的最后一个参数是‘6’。这用于指定随后数据的字节数，即字符串“foobar”。注意：虽然这个字节流是以额外的两个CRLF字节结尾的。
-
-所有批量命令都是用这种准确的格式：用随后数据的字节数代替最后一个参数，紧跟着后面是组成参数本身的字节和CRLF。为了更清楚程序，下面是通过客户端发送字符串的例子：
-
-```bash
-"SET mykey 6\r\nfoobar\r\n"
-```
-
-Redis有一个内部列表，用于表示哪些命令是内联，哪些命令是批量，所以你不得不发送相应的命令。强烈建议使用新的统一请求协议来代替老的协议。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\351\224\256\350\277\207\346\234\237\347\255\226\347\225\245.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\351\224\256\350\277\207\346\234\237\347\255\226\347\225\245.md"
new file mode 100644
index 0000000000..186c2ab4df
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\351\224\256\350\277\207\346\234\237\347\255\226\347\225\245.md"
@@ -0,0 +1,87 @@
+**1、设置过期时间**
+
+*   expire key time(s)--这是最常用的方式
+*   setex(String key, int seconds, String value)--字符串独有的方式
+
+
+**注意**：
+
+*   除了string独有设置过期时间方法，其他类型都需要依靠expire方法来设置时间
+*   如果没有设置时间，那缓存就是永不过期
+*   如果设置了过期时间，之后又想让缓存永不过期，使用persist key
+
+**2、三种过期策略**
+
+*   定时删除
+    *   含义：在设置key的过期时间的同时，为该key创建一个定时器，让定时器在key的过期时间来临时，对key进行删除
+    *   优点：保证内存被尽快释放
+    *   缺点：
+        *   若过期key很多，删除这些key会占用很多的CPU时间，在CPU时间紧张的情况下，CPU不能把所有的时间用来做要紧的事儿，还需要去花时间删除这些key
+        *   定时器的创建耗时，若为每一个设置过期时间的key创建一个定时器（将会有大量的定时器产生），性能影响严重
+        *   没人用
+*   惰性删除
+    *   含义：key过期的时候不删除，每次从数据库获取key的时候去检查是否过期，若过期，则删除，返回null。
+    *   优点：删除操作只发生在从数据库取出key的时候发生，而且只删除当前key，所以对CPU时间的占用是比较少的，而且此时的删除是已经到了非做不可的地步（如果此时还不删除的话，我们就会获取到了已经过期的key了）
+    *   缺点：若大量的key在超出超时时间后，很久一段时间内，都没有被获取过，那么可能发生内存泄露（无用的垃圾占用了大量的内存）
+*   定期删除
+    *   含义：每隔一段时间执行一次删除过期key操作
+    *   优点：
+        *   通过限制删除操作的时长和频率，来减少删除操作对CPU时间的占用--处理"定时删除"的缺点
+        *   定期删除过期key--处理"惰性删除"的缺点
+    *   缺点
+        *   在内存友好方面，不如"定时删除"
+        *   在CPU时间友好方面，不如"惰性删除"
+    *   难点
+        *   合理设置删除操作的执行时长（每次删除执行多长时间）和执行频率（每隔多长时间做一次删除）（这个要根据服务器运行情况来定了）
+
+**注意**：
+
+*   上边所说的数据库指的是内存数据库，默认情况下每一台redis服务器有16个数据库（关于数据库的设置，看下边代码），默认使用0号数据库，所有的操作都是对0号数据库的操作
+```
+# 设置数据库数量。默认为16个库，默认使用DB 0，可以使用"select 1"来选择一号数据库
+# 注意：由于默认使用0号数据库，那么我们所做的所有的缓存操作都存在0号数据库上，
+# 当你在1号数据库上去查找的时候，就查不到之前set过得缓存
+# 若想将0号数据库上的缓存移动到1号数据库，可以使用"move key 1"
+databases 16
+```
+*   memcached只是用了惰性删除，而redis同时使用了惰性删除与定期删除，这也是二者的一个不同点（可以看做是redis优于memcached的一点）
+*   对于惰性删除而言，并不是只有获取key的时候才会检查key是否过期，在某些设置key的方法上也会检查（eg.setnx key2 value2：该方法类似于memcached的add方法，如果设置的key2已经存在，那么该方法返回false，什么都不做；如果设置的key2不存在，那么该方法设置缓存key2-value2。假设调用此方法的时候，发现redis中已经存在了key2，但是该key2已经过期了，如果此时不执行删除操作的话，setnx方法将会直接返回false，也就是说此时并没有重新设置key2-value2成功，所以对于一定要在setnx执行之前，对key2进行过期检查）
+
+**3、Redis采用的过期策略**
+
+惰性删除+定期删除
+
+*   惰性删除流程
+    *   在进行get或setnx等操作时，先检查key是否过期，
+    *   若过期，删除key，然后执行相应操作；
+    *   若没过期，直接执行相应操作
+*   定期删除流程（简单而言，对指定个数个库的每一个库随机删除小于等于指定个数个过期key）
+    *   遍历每个数据库（就是redis.conf中配置的"database"数量，默认为16）
+        *   检查当前库中的指定个数个key（默认是每个库检查20个key，注意相当于该循环执行20次，循环体时下边的描述）
+            *   如果当前库中没有一个key设置了过期时间，直接执行下一个库的遍历
+            *   随机获取一个设置了过期时间的key，检查该key是否过期，如果过期，删除key
+            *   判断定期删除操作是否已经达到指定时长，若已经达到，直接退出定期删除。
+
+**注意**：
+
+*   对于定期删除，在程序中有一个全局变量current_db来记录下一个将要遍历的库，假设有16个库，我们这一次定期删除遍历了10个，那此时的current_db就是11，下一次定期删除就从第11个库开始遍历，假设current_db等于15了，那么之后遍历就再从0号库开始（此时current_db==0）
+*   **由于在实际中并没有操作过定期删除的时长和频率，所以这两个值的设置方式作为疑问？**
+
+**4、RDB对过期key的处理**
+
+过期key对RDB没有任何影响
+
+*   从内存数据库持久化数据到RDB文件
+    *   持久化key之前，会检查是否过期，过期的key不进入RDB文件
+*   从RDB文件恢复数据到内存数据库
+    *   数据载入数据库之前，会对key先进行过期检查，如果过期，不导入数据库（主库情况）
+
+**5、AOF对过期key的处理**
+
+过期key对AOF没有任何影响
+
+*   从内存数据库持久化数据到AOF文件：
+    *   当key过期后，还没有被删除，此时进行执行持久化操作（该key是不会进入aof文件的，因为没有发生修改命令）
+    *   当key过期后，在发生删除操作时，程序会向aof文件追加一条del命令（在将来的以aof文件恢复数据的时候该过期的键就会被删掉）
+*   AOF重写
+    *   重写时，会先判断key是否过期，已过期的key不会重写到aof文件
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md"
new file mode 100755
index 0000000000..63562af4c0
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redlock\345\210\206\345\270\203\345\274\217\351\224\201.md"
@@ -0,0 +1,45 @@
+# 什么是 RedLock
+
+权威的基于 Redis 实现分布式锁的方式，此种方式比原先的单节点的方法更安全。它可以保证以下特性：
+
+1. 安全特性：互斥访问，即永远只有一个 client 能拿到锁
+2. 避免死锁：最终 client 都可能拿到锁，不会出现死锁的情况，即使原本锁住某资源的 client crash 了或者出现了网络分区
+3. 容错性：只要大部分 Redis 节点存活就可以正常提供服务
+
+# 怎么在单节点上实现分布式锁
+
+> SET resource_name my_random_value NX PX 30000
+
+主要依靠上述命令，该命令仅当 Key 不存在时（NX保证）set 值，并且设置过期时间 3000ms （PX保证），值 my_random_value 必须是所有 client 和所有锁请求发生期间唯一的，释放锁的逻辑是：
+
+```lua
+if redis.call("get",KEYS[1]) == ARGV[1] then
+    return redis.call("del",KEYS[1])
+else
+    return 0
+end
+```
+
+上述实现可以避免释放另一个client创建的锁，如果只有 del 命令的话，那么如果 client1 拿到 lock1 之后因为某些操作阻塞了很长时间，此时 Redis 端 lock1 已经过期了并且已经被重新分配给了 client2，那么 client1 此时再去释放这把锁就会造成 client2 原本获取到的锁被 client1 无故释放了，但现在为每个 client 分配一个 unique 的 string 值可以避免这个问题。至于如何去生成这个 unique string，方法很多随意选择一种就行了。
+
+# Redlock 算法
+
+算法很易懂，起 5 个 master 节点，分布在不同的机房尽量保证可用性。为了获得锁，client 会进行如下操作：
+
+1. 得到当前的时间，微妙单位
+2. 尝试顺序地在 5 个实例上申请锁，当然需要使用相同的 key 和 random value，这里一个 client 需要合理设置与 master 节点沟通的 timeout 大小，避免长时间和一个 fail 了的节点浪费时间
+3. 当 client 在大于等于 3 个 master 上成功申请到锁的时候，且它会计算申请锁消耗了多少时间，这部分消耗的时间采用获得锁的当下时间减去第一步获得的时间戳得到，如果锁的持续时长（lock validity time）比流逝的时间多的话，那么锁就真正获取到了。
+4. 如果锁申请到了，那么锁真正的 lock validity time 应该是 origin（lock validity time） - 申请锁期间流逝的时间
+5. 如果 client 申请锁失败了，那么它就会在少部分申请成功锁的 master 节点上执行释放锁的操作，重置状态
+
+## 失败重试
+
+如果一个 client 申请锁失败了，那么它需要稍等一会在重试避免多个 client 同时申请锁的情况，最好的情况是一个 client 需要几乎同时向 5 个 master 发起锁申请。另外就是如果 client 申请锁失败了它需要尽快在它曾经申请到锁的 master 上执行 unlock 操作，便于其他 client 获得这把锁，避免这些锁过期造成的时间浪费，当然如果这时候网络分区使得 client 无法联系上这些 master，那么这种浪费就是不得不付出的代价了。
+
+## 放锁
+
+放锁操作很简单，就是依次释放所有节点上的锁就行了
+
+## 性能、崩溃恢复和 fsync
+
+如果我们的节点没有持久化机制，client 从 5 个 master 中的 3 个处获得了锁，然后其中一个重启了，这是注意 **整个环境中又出现了 3 个 master 可供另一个 client 申请同一把锁！** 违反了互斥性。如果我们开启了 AOF 持久化那么情况会稍微好转一些，因为 Redis 的过期机制是语义层面实现的，所以在 server 挂了的时候时间依旧在流逝，重启之后锁状态不会受到污染。但是考虑断电之后呢，AOF部分命令没来得及刷回磁盘直接丢失了，除非我们配置刷回策略为 fsnyc = always，但这会损伤性能。解决这个问题的方法是，当一个节点重启之后，我们规定在 max TTL 期间它是不可用的，这样它就不会干扰原本已经申请到的锁，等到它 crash 前的那部分锁都过期了，环境不存在历史锁了，那么再把这个节点加进来正常工作。
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\220\257\345\212\250\351\205\215\347\275\256\346\226\207\344\273\266redis-conf.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/redis3-2\345\220\257\345\212\250\351\205\215\347\275\256\346\226\207\344\273\266redis-conf\350\257\264\346\230\216.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/Redis\345\220\257\345\212\250\351\205\215\347\275\256\346\226\207\344\273\266redis-conf.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/Redis/redis3-2\345\220\257\345\212\250\351\205\215\347\275\256\346\226\207\344\273\266redis-conf\350\257\264\346\230\216.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\344\270\272\345\225\245Redis Cluster\350\256\276\350\256\241\346\210\22016384\344\270\252\346\247\275\357\274\237.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\344\270\272\345\225\245Redis Cluster\350\256\276\350\256\241\346\210\22016384\344\270\252\346\247\275\357\274\237.md"
deleted file mode 100644
index cee7f47012..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\344\270\272\345\225\245Redis Cluster\350\256\276\350\256\241\346\210\22016384\344\270\252\346\247\275\357\274\237.md"	
+++ /dev/null
@@ -1,32 +0,0 @@
-# 消息大小考虑
-crc16()一共可以有：
-```bash
-2^16 -1=65535
-```
-不同的余数，代表bitmap  有 65535 bit。所以bitmap的大小可以计算为
-
-```bash
-65535 / 8 (8bit/byte)/1024(1k)=7.99 Kbytes
-```
-
-尽管crc16能得到65535个值，但redis选择16384个slot，是因为16384的消息只占用了2k，而65535则需要8k。
-
-正常的心跳包携带节点的完整配置，可以以幂等方式替换旧配置以更新旧配置。这意味着它们包含原始形式的节点的插槽配置，该节点使用2K的空间和16384个slot，但使用65535的插槽会使用令人望而却步的 8K 的空间。
-
-```bash
-65k = 8 * 8 (8 bit/byte) * 1024(1k) = 8K bitmap size
-```
-## 为什么要传全量的slot状态？
-因为分布式场景，基于状态的设计更合理，状态的传播具有幂等性。
-# 集群规模设计考虑
-同时，由于其他设计权衡，Redis Cluster 不太可能扩展到超过 1000 个主节点。集群设计最多支持1000个分片，16384是相对比较好的选择，需要保证在最大集群规模下，slot均匀分布场景下，每个分片平均分到的slot不至于太小。
-所以16384是在正确的范围内，以确保每个 master 有足够的插槽，最多 1000 个 maters，但这个数量足够小，可以轻松地将插槽配置作为原始位图传播。
-
-在小集群中，位图将难以压缩，因为当 N 小时，位图将设置的槽位/N 位占很大比例的位。
-## 为什么不考虑压缩？
-集群规模较小的场景下，每个分片负责大量的slot，很难压缩。
-
-简而言之，它是消息大小和主机持有的平均slot数之间权衡的结果。
-
-参考
-- https://github.com/redis/redis/issues/2576
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\350\247\243\345\206\263Redis\347\232\204\345\271\266\345\217\221\347\253\236\344\272\211\351\227\256\351\242\230.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\350\247\243\345\206\263Redis\347\232\204\345\271\266\345\217\221\347\253\236\344\272\211\351\227\256\351\242\230.md"
deleted file mode 100644
index 07b6285907..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\246\202\344\275\225\350\247\243\345\206\263Redis\347\232\204\345\271\266\345\217\221\347\253\236\344\272\211\351\227\256\351\242\230.md"
+++ /dev/null
@@ -1,6 +0,0 @@
-# Redis的并发竞争问题
-- 多客户端同时并发写一个key，可能本来应该先到的数据后到了，导致数据版本出错
-- 多客户端同时获取一个key，修改值之后再写回去，只要顺序错了，数据也错了
-# 解决方案
-其实Redis本身就有解决这个问题的CAS类的乐观锁方案。
-![](https://img-blog.csdnimg.cn/20190509175418361.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\272\224\347\224\250\350\256\276\350\256\241/Redis\347\274\223\345\255\230\351\233\252\345\264\251\343\200\201\347\274\223\345\255\230\347\251\277\351\200\217\343\200\201\347\274\223\345\255\230\345\207\273\347\251\277\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\272\224\347\224\250\350\256\276\350\256\241/Redis\347\274\223\345\255\230\351\233\252\345\264\251\343\200\201\347\274\223\345\255\230\347\251\277\351\200\217\343\200\201\347\274\223\345\255\230\345\207\273\347\251\277\350\247\243\345\206\263\346\226\271\346\241\210.md"
deleted file mode 100644
index 315a4c4d28..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\345\272\224\347\224\250\350\256\276\350\256\241/Redis\347\274\223\345\255\230\351\233\252\345\264\251\343\200\201\347\274\223\345\255\230\347\251\277\351\200\217\343\200\201\347\274\223\345\255\230\345\207\273\347\251\277\350\247\243\345\206\263\346\226\271\346\241\210.md"
+++ /dev/null
@@ -1,167 +0,0 @@
-这些缓存常见异常问题，说实话，中小公司一般都遇不到，但如果是高并发项目，几百万流量的，就需要谨慎对待了。
-
-
-# 1 缓存雪崩
-## 1.1 什么是缓存雪崩?
-由于
-- 设置缓存时，key都采用了相同expire
-- 更新策略
-- 数据热点
-- 缓存服务宕机
-
-等原因，可能导致缓存数据同一时刻大规模不可用，或者都更新。
-
-比如商城马上就要到双十一零点，很快就会迎来一波抢购，这波商品时间比较集中的放入了缓存，假设缓存一个小时。那么到了凌晨一点钟的时候，这批商品的缓存就都过期了。而对这批商品的访问查询，都落到了数据库上，对于数据库而言，就会产生周期性的压力波峰。
-
-集中过期，其实不是太致命，最致命的是缓存服务器某个节点宕机：
-- 自然形成的缓存雪崩，一定是在某个时间段集中创建缓存，那么这时DB也可顶住压力，无非就是对DB产生周期性压力
-- 而缓存服务节点的宕机，这时所有缓存 key 都没了，请求全部打入 DB，对DB造成的压力不可预知，很可能瞬间就把DB压垮，这需要通过主从集群哨兵等解决。
-
-做电商项目的时候，一般是采取不同分类商品，缓存不同周期。在同一分类中的商品，加上一个随机因子。这样能尽可能分散缓存过期时间，而且，热门类目的商品缓存时间长一些，冷门类目的商品缓存时间短一些，也能节省缓存服务的资源。
-
-## 1.2 解决方案
-- 更新策略在时间上做到比较均匀
-- 使用的热数据尽量分散到不同的机器上
-- 多台机器做主从复制或者多副本，实现高可用
-### 服务降级
-实现熔断限流机制，对系统进行负载能力控制
-
-### 随机失效时间
-在原有失效时间基础上增加一个随机值，比如1~5分钟的随机，这样每个缓存的过期时间重复率就会降低，集体失效概率也会大大降低。
-### 互斥锁
-### 双缓存
-用得少。我们有两个缓存，缓存 A 和缓存 B。假设缓存 A 的失效时间为 20 分钟，缓存 B 不设失效时间。自己做缓存预热操作。然后：
-a. 从缓存 A 读数据库，有则直接返回
-b. A 没有数据，直接从 B 读数据，直接返回，并且异步启动一个更新线程。
-c. 更新线程同时更新缓存 A 和缓存 B。
-# 2 缓存穿透
-## 2.1 什么是缓存穿透？
-高并发查询不存在的key，导致将压力都直接透传到DB。
-
-- 那为什么会多次透传呢？
-因为缓存不存在该数据，一直为空。 
-
-> 注意让缓存能够区分key 是不存在 or 查询得到一个空值。 
-> 例如：访问**id=-1**的数据。可能出现绕过Redis频繁访问DB，称为缓存穿透，多出现在查询为null的情况不被缓存时。
-
-## 2.2 解决方案
-### 布隆过滤器（ Bloom过滤）或RoaringBitmap
-提供一个能迅速判断请求是否有效的拦截机制。
-比如利用布隆过滤器，内部维护一系列合法有效的 key。从而能迅速判断出，请求所携带的 Key 是否合法有效：
-- 若不合法，则直接返回，避免直接查询DB。
-### 缓存空值key
-如果从数据库查询的对象为空，也放入缓存，只是设定的缓存过期时间较短，比如设置为 60 s。
-
-这样第一次不存在也会被加载会记录，下次拿到有这个key。
-
-### 完全以缓存为准
-更简单粗暴的方法，若一个查询返回的数据为空（不管是数据不存在，还是系统故障），仍把该空结果进行缓存，但它的过期时间会很短，最长不超过5min。
-```java
-if(list == null) {
-    // key value 有效时间 时间单位
-    redisTemplate.opsForValue().set(navKey,null,10, TimeUnit.MINUTES);
-} else {
-    redisTemplate.opsForValue().set(navKey,result,7,TimeUnit.DAYS);
-}
-```
-### 异步更新
-使用 延迟异步加载 的策略2，这样业务前端不会触发更新，只有我们数据更新时后端去主动更新。
-
-### 服务降级
-
-### 互斥锁（不推荐）
-问题根本在于限制处理线程的数量，即key的更新操作添加全局互斥锁。
-
-在缓存失效时（判断拿出来的值为空），不是立即去load db，而是
-- 先使用缓存工具的某些带成功操作返回值的操作（Redis的SETNX）去set一个mutex key
-- 当操作返回成功时，再load db的操作并回设缓存；否则，就重试整个get缓存的方法。
-
-```java
-public String get(key) {
-      String value = redis.get(key);
-      if (value == null) { // 缓存已过期
-          // 设置超时，防止del失败时，下次缓存过期一直不能load db
-		  if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { // 设置成功
-               value = db.get(key);
-                      redis.set(key, value, expire_secs);
-                      redis.del(key_mutex);
-          } else {
-            		// 其他线程已load db并回设缓存，重试获取缓存即可
-                    sleep(50);
-                    get(key);  //重试
-          }
-        } else { // 缓存未过期
-            return value;      
-        }
- }
-```
-
-### 提前"使用互斥锁（不推荐）
-在value内部设置1个超时值(timeout1)， timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候，马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。伪代码如下：
-
-```java
-v = memcache.get(key);  
-if (v == null) {  
-    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  
-        value = db.get(key);  
-        memcache.set(key, value);  
-        memcache.delete(key_mutex);  
-    } else {  
-        sleep(50);  
-        retry();  
-    }  
-} else {  
-    if (v.timeout <= now()) {  
-        if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  
-            // extend the timeout for other threads  
-            v.timeout += 3 * 60 * 1000;  
-            memcache.set(key, v, KEY_TIMEOUT * 2);  
-  
-            // load the latest value from db  
-            v = db.get(key);  
-            v.timeout = KEY_TIMEOUT;  
-            memcache.set(key, value, KEY_TIMEOUT * 2);  
-            memcache.delete(key_mutex);  
-        } else {  
-            sleep(50);  
-            retry();  
-        }  
-    }  
-} 
-```
-
-# 3 缓存击穿
-- 击穿针对的是某一个key缓存
-- 而雪崩是很多key
-
-某key失效时，正好有高并发请求访问该key。 
-
-通常使用【缓存 + 过期时间】帮助我们加速接口访问速度，减少后端负载，同时保证功能的更新，一般情况下这种模式已基本满足需求。
-
-但若同时出现如下问题，可能对系统十分致命：
-- 热点key，访问量非常大
-比如秒杀时。
-- 缓存的构建需要时间（可能是个复杂过程，例如复杂SQL、多次I/O、多个接口依赖）
-
-于是就会导致：
-在缓存失效瞬间，有大量线程构建缓存，导致后端负载加剧，甚至可能让系统崩溃。
-![](https://img-blog.csdnimg.cn/20200911165012921.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-## 解决方案
-缓存击穿，是指一个 key 非常热点，在不停的扛着大并发，大并发集中对这一个点进行访问，当这个 key 在失效的瞬间，持续的大并发就穿破缓存，直接请求数据库，就像在一个屏障上凿开了一个洞。做类电商项目的时候，把这货就称为 “爆款”。
-其实，大多数情况下这种爆款很难对数据库服务器造成压垮性的压力。达到这个级别的公司没有几家的。所以，对主打商品都是早早的做好了准备，让缓存永不过期。即便某些商品自己发酵成了爆款，也是直接设为
-### 永不过期
-
-从 redis 上看，确实没有设置过期时间。这就保证不会出现热点 key 过期，即 “物理” 不过期
-
-### “逻辑” 过期
-功能上看，若不过期，不就成静态数据了？
-所以我们把过期时间存在 key 对应的 value。若发现要过期了，通过一个后台异步线程进行缓存构建，即 “逻辑” 过期。
-
-### 服务降级
-### 缓存为准
-使用异步线程负责维护缓存的数据，定期或根据条件触发更新，这样就不会触发更新。
-### 限流
-使用 Hystrix 或 Sentinel。
-
-> 参考
-> - https://www.iteye.com/blog/carlosfu-2269687
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\347\274\223\345\255\230\351\233\252\345\264\251\343\200\201\347\274\223\345\255\230\347\251\277\351\200\217\343\200\201\347\274\223\345\255\230\345\207\273\347\251\277.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\347\274\223\345\255\230\351\233\252\345\264\251\343\200\201\347\274\223\345\255\230\347\251\277\351\200\217\343\200\201\347\274\223\345\255\230\345\207\273\347\251\277.md"
new file mode 100644
index 0000000000..1af9a04526
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\347\274\223\345\255\230\351\233\252\345\264\251\343\200\201\347\274\223\345\255\230\347\251\277\351\200\217\343\200\201\347\274\223\345\255\230\345\207\273\347\251\277.md"
@@ -0,0 +1,116 @@
+> 全是干货的技术号：
+> 本文已收录在github，欢迎 star/fork：
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
+# 1 缓存雪崩
+## 1.1 什么是缓存雪崩?
+- 由于设置缓存时，key都采用了相同expire，导致缓存在某刻同时失效，请求全部直到DB，DB瞬时负载过重而雪崩。
+
+## 1.2 解决方案
+在原有失效时间基础上增加一个随机值，比如1~5分钟的随机，这样每个缓存的过期时间重复率就会降低，集体失效概率也会大大降低。
+
+- 首先环境保证redis高可用，主从+哨兵，redis cluster，避免全盘崩溃。
+- 接着用户并发请求后，服务会先通过hystrix限流&降级，再查本地Ehcache缓存，若没有再查redis，都没有，就查MySQL。
+- 最后，将MySQL中的结果写入ehcache和redis、即使 redis挂了，通过redis持久化，也能快速恢复缓存数据
+
+# 2 缓存穿透
+## 2.1 什么是缓存穿透？
+
+出现Redis中不存在的缓存数据。例如：访问id=-1的数据。可能出现绕过redis依然频繁访问数据库，称为缓存穿透，多出现在查询为null的情况不被缓存时。
+
+## 2.2 解决方案
+
+很多种方法可以有效解决缓存穿透，最常见的布隆过滤器，将所有可能存在的数据哈希到一个足够大的bitmap中，一个一定不存在的数据会被这个bitmap拦截掉，从而避免了对底层存储系统的查询压力。
+
+也有一个更为简单粗暴的方法，如果一个查询返回的数据为空（不管是数据不存在，还是系统故障），我们仍然把这个空结果进行缓存，但它的过期时间会很短，最长不超过五分钟。
+```java
+if(list==null){
+    // key value 有效时间 时间单位
+    redisTemplate.opsForValue().set(navKey,null,10, TimeUnit.MINUTES);
+}else{
+    redisTemplate.opsForValue().set(navKey,result,7,TimeUnit.DAYS);
+}
+```
+
+# 3 缓存击穿
+缓存雪崩的区别在于这里针对某一key缓存，而前者是很多key。
+
+通常使用缓存 + 过期时间的策略来帮助我们加速接口的访问速度，减少了后端负载，同时保证功能的更新，一般情况下这种模式已经基本满足要求了。
+
+但如下两个问题如果同时出现，可能就会对系统造成致命的危害：
+1. 这个key是一个热点key，访问量非常大
+2. 缓存的构建是需要一定时间的。（可能是一个复杂计算，例如复杂的sql、多次IO、多个依赖(各种接口)等等）
+
+于是就会出现一个致命问题：在缓存失效的瞬间，有大量线程来构建缓存（见下图），造成后端负载加大，甚至可能会让系统崩溃 。
+
+![](https://img-blog.csdnimg.cn/20200911165012921.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+## 3.2 解决方案
+### 互斥锁
+在缓存失效时（判断拿出来的值为空），不是立即去load db，而是
+- 先使用缓存工具的某些带成功操作返回值的操作（Redis的SETNX）去set一个mutex key
+- 当操作返回成功时，再load db的操作并回设缓存；否则，就重试整个get缓存的方法。
+
+```java
+public String get(key) {
+      String value = redis.get(key);
+      if (value == null) { // 缓存已过期
+          // 设置超时，防止del失败时，下次缓存过期一直不能load db
+		  if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { // 设置成功
+               value = db.get(key);
+                      redis.set(key, value, expire_secs);
+                      redis.del(key_mutex);
+          } else {
+            		// 其他线程已load db并回设缓存，重试获取缓存即可
+                    sleep(50);
+                    get(key);  //重试
+          }
+        } else { // 缓存未过期
+            return value;      
+        }
+ }
+```
+
+### 提前"使用互斥锁(mutex key)：
+在value内部设置1个超时值(timeout1)， timeout1比实际的memcache timeout(timeout2)小。当从cache读取到timeout1发现它已经过期时候，马上延长timeout1并重新设置到cache。然后再从数据库加载数据并设置到cache中。伪代码如下：
+
+```java
+v = memcache.get(key);  
+if (v == null) {  
+    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  
+        value = db.get(key);  
+        memcache.set(key, value);  
+        memcache.delete(key_mutex);  
+    } else {  
+        sleep(50);  
+        retry();  
+    }  
+} else {  
+    if (v.timeout <= now()) {  
+        if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {  
+            // extend the timeout for other threads  
+            v.timeout += 3 * 60 * 1000;  
+            memcache.set(key, v, KEY_TIMEOUT * 2);  
+  
+            // load the latest value from db  
+            v = db.get(key);  
+            v.timeout = KEY_TIMEOUT;  
+            memcache.set(key, value, KEY_TIMEOUT * 2);  
+            memcache.delete(key_mutex);  
+        } else {  
+            sleep(50);  
+            retry();  
+        }  
+    }  
+} 
+```
+
+### 限流
+如使用 hystrix 或者 sentinel限流即可。现在该技术已较成熟，足够使用。
+
+
+参考
+
+- https://www.iteye.com/blog/carlosfu-2269687
+
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\351\224\256\345\200\274\346\225\260\346\215\256\345\272\223\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/Redis/\351\224\256\345\200\274\346\225\260\346\215\256\345\272\223\347\232\204\345\237\272\346\234\254\346\236\266\346\236\204.md"
deleted file mode 100644
index e69de29bb2..0000000000
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230Redis\345\222\214Memcached\347\232\204\345\257\271\346\257\224.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230Redis\345\222\214Memcached\347\232\204\345\257\271\346\257\224.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230Redis\345\222\214Memcached\347\232\204\345\257\271\346\257\224.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230Redis\345\222\214Memcached\347\232\204\345\257\271\346\257\224.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\345\244\247\350\241\214\347\274\223\345\255\230\346\233\264\346\226\260\344\271\213\351\201\223.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\345\244\247\350\241\214\347\274\223\345\255\230\346\233\264\346\226\260\344\271\213\351\201\223.md"
new file mode 100644
index 0000000000..1e6060144d
--- /dev/null
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\345\244\247\350\241\214\347\274\223\345\255\230\346\233\264\346\226\260\344\271\213\351\201\223.md"
@@ -0,0 +1,59 @@
+好些人在写更新缓存时，**先删除缓存，然后再更新数据库**，而后续的操作会把数据再装载的缓存中。
+**然而，这个逻辑是错误的**。试想，两个并发操作，一个是更新操作，另一个是查询操作，更新操作删除缓存后，查询操作没有命中缓存，先把老数据读出来后放到缓存中，然后更新操作更新了数据库。于是，在缓存中的数据还是老的数据，导致缓存中的数据是脏的，而且还一直这样脏下去了。
+
+不知道为什么这么多人用的都是这个逻辑，总结一下几个缓存更新的Design Pattern。
+
+这里，我们先不讨论更新缓存和更新数据这两个事是一个事务的事，或是会有失败的可能，我们先假设更新数据库和更新缓存都可以成功的情况
+
+更新缓存的的Design Pattern有四种
+# 1  Cache Aside Pattern
+最常用的pattern。具体逻辑如下：
+*   **失效**：应用程序先从cache取数据，没有得到，则从数据库中取数据，成功后，放到缓存中
+*   **命中**：应用程序从cache中取数据，取到后返回
+*   **更新**：先把数据存到数据库中，成功后，再让缓存失效
+![Cache-Aside-Design-Pattern-Flow-Diagram](https://upload-images.jianshu.io/upload_images/4685968-83e446d9f389ed97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![Updating Data using the Cache-Aside Pattern - Flow Diagram](https://upload-images.jianshu.io/upload_images/4685968-8c125a181ca19273.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+注意，我们的更新是先更新数据库，成功后，让缓存失效
+
+一个查询操作，一个更新操作的并发
+首先，没有了删除cache数据的操作，而是先更新数据库中的数据，此时，缓存依然有效，所以，并发的查询操作拿的是没有更新的数据，但是，更新操作马上让缓存的失效了，后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题，后续的查询操作一直都在取老的数据。
+
+这是标准的design pattern，包括Facebook的论文《[Scaling Memcache at Facebook](https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf)》也使用了这个策略
+为什么不是写完数据库后更新缓存？可以看一下Quora上的这个问答《[Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?](https://www.quora.com/Why-does-Facebook-use-delete-to-remove-the-key-value-pair-in-Memcached-instead-of-updating-the-Memcached-during-write-request-to-the-backend)》，主要是怕两个并发的写操作导致脏数据。
+
+那么，是不是Cache Aside这个就不会有并发问题了？
+不是的，比如，一个是`读操作`，但是没有命中缓存，然后就到数据库中取数据，此时来了一个`写操作`，写完数据库后，让缓存失效，然后，之前的那个读操作再把老的数据放进去，所以，会造成脏数据。
+
+这个情形理论上会出现，不过，实际上出现的概率可能非常低，因为需要发生在读缓存时缓存失效，而且并发着有一个写操作。
+而实际上数据库的写操作会比读操作慢得多，而且还要锁表，而读操作必需在写操作前进入数据库操作，而又要晚于写操作更新缓存，所有的这些条件都具备的概率基本并不大
+
+这也就是Quora上的那个答案里说的，要么通过2PC或是Paxos协议保证一致性，要么就是拼命的降低并发时脏数据的概率，而Facebook使用了这个降低概率的玩法，因为2PC太慢，而Paxos太复杂。当然，最好还是为缓存设置上过期时间。
+# 2 Read/Write Through Pattern
+上面的Cache Aside，应用代码需要维护两个数据存储，一个是缓存，一个是数据库,应用程序比较啰嗦。
+而`Read/Write Through`是把更新数据库的操作由缓存自己代理，所以，对于应用层来说，就简单很多。
+可理解为，应用认为后端就是一个单一的存储，而存储自己维护自己的Cache。
+## 2.1  Read Through
+Read Through 就是在查询操作中更新缓存，也就是说，当缓存失效的时候（过期或LRU换出）
+- Cache Aside是由`调用方负责`把数据加载入缓存
+- Read Through则用`缓存服务`自己来加载，从而对应用方是透明的
+## 2.2  Write Through
+和Read Through相仿，不过是在更新数据时发生
+当有数据更新时
+- 如果没有命中缓存，直接更新数据库，然后返回
+- 如果命中了缓存，则更新缓存，然后再由Cache自己更新数据库（这是一个同步操作）
+
+下图中的Memory可以理解为就是我们例子里的数据库
+![A write-through cache with no-write allocation](https://upload-images.jianshu.io/upload_images/4685968-b36fc349d396e41a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 3 Write Behind Caching Pattern
+Write Behind 又叫 Write Back
+在更新数据的时候，只更新缓存，不更新数据库，而我们的缓存会异步地批量更新数据库
+这个设计的好处就是
+- 让数据的I/O操作飞快无比（因为直接操作内存嘛 ）
+- 因为异步，write back还可以合并对同一个数据的多次操作，所以性能的提高是相当可观的
+
+但是，其带来的问题是，数据不是强一致性的，而且可能会丢失（我们知道Unix/Linux非正常关机会导致数据丢失，就是因为这个事）。
+
+另外，Write Back实现逻辑比较复杂，因为他需要track哪些数据是被更新的，需要刷到持久层上。
+操作系统的write back会在仅当这个cache需要失效的时候，才会被真正持久化，比如，内存不够了，或是进程退出了等情况，这又叫lazy write
+![A write-back cache with write allocation](https://upload-images.jianshu.io/upload_images/4685968-46e57dad1299715c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/Java\351\253\230\346\200\247\350\203\275\347\263\273\347\273\237\347\274\223\345\255\230\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/Java\351\253\230\346\200\247\350\203\275\347\263\273\347\273\237\347\274\223\345\255\230\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
index f22b2aedaf..6ba0533ac0 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/Java\351\253\230\346\200\247\350\203\275\347\263\273\347\273\237\347\274\223\345\255\230\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/Java\351\253\230\346\200\247\350\203\275\347\263\273\347\273\237\347\274\223\345\255\230\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
@@ -1,3 +1,5 @@
+![](https://img-blog.csdnimg.cn/2020081311184130.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
 # 1 屈服于现实的磁盘
 MQ都使用磁盘来存储消息。这样服务器下电也不会丢数据。绝大多数用于生产系统的服务器，都会使用多块磁盘组成磁盘阵列，这样即使其中的一块异常，也可把数据从其他磁盘中恢复。
 
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ-\346\225\264\345\220\210Spring\345\274\200\345\217\221.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ-\346\225\264\345\220\210Spring\345\274\200\345\217\221.md"
index c380f814a0..9236b301f2 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ-\346\225\264\345\220\210Spring\345\274\200\345\217\221.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ-\346\225\264\345\220\210Spring\345\274\200\345\217\221.md"
@@ -1,4 +1,13 @@
-# 1 [相关源码](https://github.com/Wasabi1234/RabbitMQ_Tutorial)
+> 全是干货的技术号：
+> 本文已收录在github，欢迎 star/fork：
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
+[相关源码](https://github.com/Wasabi1234/RabbitMQ_Tutorial)
+# 1 你将学到
+- RabbitMQ 整合 Spring AMQP实战
+- RabbitMQ   整合 Spring Boot实战
+- RabbitMQ 整合 Spring Cloud实战
+
 # 2 SpringAMQP用户管理组件 - RabbitAdmin
 RabbitAdmin 类可以很好的操作 rabbitMQ，在 Spring 中直接进行注入即可
 
@@ -9,16 +18,25 @@ RabbitAdmin 的底层实现
 - 从 Spring 容器中获取 Exchange、Bingding、Routingkey 以及Queue 的 @Bean 声明
 - 然后使用 rabbitTemplate 的 execute 方法进行执行对应的声明、修改、删除等一系列 RabbitMQ 基础功能操作。例如添加交换机、删除一个绑定、清空一个队列里的消息等等
 - 依赖结构
-![](https://img-blog.csdnimg.cn/20190701130301812.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190701130301812.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+RabbitAdmin实现了4个Interface： AmqpAdmin, ApplicationContextAware, ApplicationEventPublisherAware,InitializingBean。
 
-RabbitAdmin实现了4个Interface： 
-- AmqpAdmin
-- ApplicationContextAware
-- ApplicationEventPublisherAware
-- InitializingBean
 ### AmqpAdmin
 为AMQP指定一组基本的便携式AMQP管理操作
-![](https://img-blog.csdnimg.cn/20190701131344162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190701131344162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+### ApplicationEventPublisherAware
+实现该接口的类，通过函数setApplicationEventPublisher()获得它执行所在的ApplicationEventPublisher。
+![在这里插入图片描述](https://img-blog.csdnimg.cn/20190701155733978.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+### ApplicationContextAware
+实现该接口的类，通过函数setApplicationContext()获得它执行所在的ApplicationContext。一般用来初始化object
+![](https://img-blog.csdnimg.cn/20190702041941650.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+### InitializingBean
+![](https://img-blog.csdnimg.cn/20190702042042485.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+若class中实现该接口，在Spring Container中的bean生成之后，自动调用函数afterPropertiesSet()。
+因其实现了InitializingBean接口,其中只有一个方法,且在Bean加载后就执行
+该功能可以被用来检查是否所有的mandatory properties都设置好
+
 - 以上Interfaces的执行顺序
 ApplicationEventPublisherAware -> ApplicationContextAware -> InitializingBean.
 
@@ -30,20 +48,20 @@ RabbitAdmin借助于 ApplicationContextAware 和 InitializingBean来获取我们
 下面是RabbitAdmin中afterPropertiesSet()函数的代码片段。这里在创建connection的时候调用函数initialize()。
 于是以此为突破口进行源码分析
 - RabbitAdmin#afterPropertiesSet
-![](https://img-blog.csdnimg.cn/20190702050143931.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702050143931.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 这里
 
 最后分别调用函数declareExchanges(),declareQueues(),declareBindings()来声明RabbitMQ Entity
 
 - 先定义了三个集合,利用applicationContext.getBeansOfType来获得container中的Exchange，Queue,Binding声明放入集合中
-![](https://img-blog.csdnimg.cn/20190702050619423.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702050619423.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 
 
-- 然后调用filterDeclarables()来过滤不能declareable的bean![](https://img-blog.csdnimg.cn/20190702051006589.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+- 然后调用filterDeclarables()来过滤不能declareable的bean![](https://img-blog.csdnimg.cn/20190702051006589.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 按照RabbitMQ的方式拼接
-![](https://img-blog.csdnimg.cn/20190702055307309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702055307309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 使用rabbitTemplate执行交互
-![](https://img-blog.csdnimg.cn/20190702055350722.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702055350722.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 ## 2.2 实操
 
 回顾一下消费者配置
@@ -63,24 +81,24 @@ RabbitAdmin借助于 ApplicationContextAware 和 InitializingBean来获取我们
     TopicExchange : 多关键字匹配
 
 ```
-![](https://img-blog.csdnimg.cn/20190702065621582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702065621582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 
-- 测试代码![](https://img-blog.csdnimg.cn/20190702065336309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 查看管控台![](https://img-blog.csdnimg.cn/20190702065439712.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190702065520705.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+- 测试代码![](https://img-blog.csdnimg.cn/20190702065336309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+- 查看管控台![](https://img-blog.csdnimg.cn/20190702065439712.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702065520705.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 3 SpringAMQP - RabbitMQ声明式配置使用
 SpringAMQP 声明即在 rabbit 基础 API 里面声明一个 exchange、Bingding、queue。使用SpringAMQP 去声明，就需要使用 @Bean 的声明方式
-![](https://img-blog.csdnimg.cn/20190702082752208.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 查看管控台![](https://img-blog.csdnimg.cn/20190702090044875.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190702092111226.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702082752208.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+- 查看管控台![](https://img-blog.csdnimg.cn/20190702090044875.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702092111226.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 3 消息模板 - RabbitTemplate
 上节中最后提到,这是与与 SpringAMQP 整合发送消息的关键类,它提供了丰富的发送消息方法
 包括可靠性投递消息方法、回调监听消息接口 `ConfirmCallback`、返回值确认接口 `ReturnCallback `等.
 同样我们需要注入到 Spring 容器中，然后直接使用.
 RabbitTemplate 在 Spring 整合时需要实例化，但是在 Springboot 整合时，在配置文件里添加配置即可
 - 先声明bean
-![](https://img-blog.csdnimg.cn/20190702094556582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 测试![](https://img-blog.csdnimg.cn/20190702095800431.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702094556582.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+- 测试![](https://img-blog.csdnimg.cn/20190702095800431.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 4 SpringAMQP消息容器-SimpleMessageListenerContainer
 这个类非常的强大，我们可以对他进行很多的设置，用对于消费者的配置项，这个类都可以满足。它有监听单个或多个队列、自动启动、自动声明功能。
 - 设置事务特性、事务管理器、事务属性、事务并发、是否开启事务、回滚消息等。但是我们在实际生产中，很少使用事务，基本都是采用补偿机制
@@ -91,11 +109,11 @@ RabbitTemplate 在 Spring 整合时需要实例化，但是在 Springboot 整合
 
 > SimpleMessageListenerContainer 可以进行动态设置，比如在运行中的应用可以动态的修改其消费者数量的大小、接收消息的模式等。
 - 很多基于 RabbitMQ 的自制定化后端管控台在进行设置的时候，也是根据这一去实现的
-![](https://img-blog.csdnimg.cn/20190702103609861.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190702103830482.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190702103941759.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190702104108857.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190702104212629.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702103609861.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702103830482.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702103941759.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702104108857.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190702104212629.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 5 SpringAMQP消息适配器-MessageListenerAdapter
 消息监听适配器,通过反射将消息处理委托给目标监听器的处理方法,并进行灵活的消息类型转换.
 允许监听器方法对消息内容类型进行操作,完全独立于RabbitMQ API
@@ -109,47 +127,47 @@ RabbitTemplate 在 Spring 整合时需要实例化，但是在 Springboot 整合
 注意：发送响应消息仅在使用ChannelAwareMessageListener入口点（通常通过Spring消息监听器容器）时可用。 用作MessageListener不支持生成响应消息。
 
 ## 源码分析
-![](https://img-blog.csdnimg.cn/20190703112518590.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703112518590.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 继承自`AbstractAdaptableMessageListener`类,实现了`MessageListener`和`ChannelAwareMessageListener`接口
 而`MessageListener`和`ChannelAwareMessageListener`接口的`onMessage`方法就是具体容器监听队列处理队列消息的方法
 
 
-![](https://img-blog.csdnimg.cn/20190703112741904.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703112741904.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 ## 实操
 - 委托类MessageDelegate,类中定义的方法也就是目标监听器的处理方法
-![](https://img-blog.csdnimg.cn/20190703110706732.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 配置类代码![](https://img-blog.csdnimg.cn/20190703110826994.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703110706732.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+- 配置类代码![](https://img-blog.csdnimg.cn/20190703110826994.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 运行测试代码
-![](https://img-blog.csdnimg.cn/20190703110927685.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703110927685.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 结果
-![](https://img-blog.csdnimg.cn/2019070311101753.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/2019070311101753.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 
 
 从源码分析小节中的成员变量,我们可以看出使用MessageListenerAdapter处理器进行消息队列监听处理
 - 如果容器没有设置setDefaultListenerMethod
 则处理器中默认的处理方法名是`handleMessage`
 - 如果设置了setDefaultListenerMethod
-![](https://img-blog.csdnimg.cn/20190703113640114.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703113640114.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 则处理器中处理消息的方法名就是setDefaultListenerMethod方法参数设置的值
-![](https://img-blog.csdnimg.cn/20190703113929481.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703113929481.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 
 也可以通过setQueueOrTagToMethodName方法为不同的队列设置不同的消息处理方法。
 `MessageListenerAdapter`的`onMessage`方法
 - 如果将参数改为String运行会出错!应当是字节数组,这时就需要使用转换器才能保证正常运行
-![](https://img-blog.csdnimg.cn/20190703114102112.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703114102112.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 使用转换器
-![](https://img-blog.csdnimg.cn/20190703114713677.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703114734657.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703115029275.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703114713677.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703114734657.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703115029275.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 测试代码运行成功!
-![](https://img-blog.csdnimg.cn/20190703120217719.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703120434742.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703120525213.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703120217719.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703120434742.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703120525213.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 6 消息转换器 - MessageConverter
 我们在进行发送消息的时候，正常情况下消息体为二进制的数据方式进行传输，如果希望内部帮我们进行转换，或者指定自定义的转换器，就需要用到 `MessageConverter `了
 
 - 我们自定义常用转换器,都需要实现这个接口,然后重写其中的两个方法
-![](https://img-blog.csdnimg.cn/20190703124244469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703124244469.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 ## 常见的转换器
 - Json 转换器 - jackson2JsonMessageConverter
 Java 对象的转换功能
@@ -160,29 +178,29 @@ Java对象的映射关系
 比如图片类型、PDF、PPT、流媒体
 ## 实操
 - Order类
-![](https://img-blog.csdnimg.cn/20190703124732639.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703124859608.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 配置JSON转换器![](https://img-blog.csdnimg.cn/20190703125153881.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703124732639.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703124859608.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+- 配置JSON转换器![](https://img-blog.csdnimg.cn/20190703125153881.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 测试代码
-![](https://img-blog.csdnimg.cn/2019070312561814.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703125706999.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/2019070312561814.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703125706999.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 配置Java对象转换器
-![](https://img-blog.csdnimg.cn/20190703130109286.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703130109286.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 测试代码及结果
-![](https://img-blog.csdnimg.cn/20190703130309588.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 多个Java对象映射转换![](https://img-blog.csdnimg.cn/20190703130554776.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703130309588.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+- 多个Java对象映射转换![](https://img-blog.csdnimg.cn/20190703130554776.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 测试代码及结果
-![](https://img-blog.csdnimg.cn/2019070313131050.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703131346801.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/2019070313131050.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703131346801.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 全局转换器
-![](https://img-blog.csdnimg.cn/20190703182004723.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703182004723.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 图片转换器实现
-![](https://img-blog.csdnimg.cn/20190703182523521.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703182523521.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - PDF转换器实现
-![](https://img-blog.csdnimg.cn/20190703182650464.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703185719599.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703182650464.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703185719599.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 测试代码及结果
-![](https://img-blog.csdnimg.cn/20190703185752598.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20190703185834535.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703185752598.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20190703185834535.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 7 RabbitMQ与SpringBoot2.x整合实战
 ## 7.1 配置详解
 - publisher-confirms 
@@ -194,41 +212,41 @@ Java对象的映射关系
 > 在生产端还可以配置其他属性，比如发送重试、超时时间、次数、间隔等
 ## Pro
 - 配置文件
-![](https://img-blog.csdnimg.cn/20190703191738927.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703191738927.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 主配置
-![](https://img-blog.csdnimg.cn/20190703190758641.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703190758641.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 
 - 添加一个自定义的交换机
-![](https://img-blog.csdnimg.cn/20190703225705132.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703225705132.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 添加一个Q
-![](https://img-blog.csdnimg.cn/20190703230430261.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703230430261.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 建立绑定关系
-![](https://img-blog.csdnimg.cn/20190703230941421.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703231011535.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190703233404929.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703230941421.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703231011535.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703233404929.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 测试及结果
-![](https://img-blog.csdnimg.cn/20190703235354725.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190703235354725.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 ## Con配置
 消费端的 RabbitListener 是一个组合注解，里面可以注解配置 。
 @QueueBinding @Queue @Exchange 直接通过这个组合注解一次性搞定消费端交换机、队列、绑定、路由、并且配置监听功能等。
 
 - 将Pro中的绑定全部删除,再启动Con的sb服务
-![](https://img-blog.csdnimg.cn/20190704000336742.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190704000401500.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704000336742.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704000401500.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 ## 发送一个 Java 实体对象
 - 在Con声明队列、交换机、routingKey基本配置
-![](https://img-blog.csdnimg.cn/20190704092634274.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704092634274.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - Con
-![](https://img-blog.csdnimg.cn/20190704092733133.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704092733133.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 > Payload 注解中的路径要跟Pro的实体路径完全一致，要不然会找到不到该类,这里为了简便就不写一个 common.jar 了，在实际开发里面，这个 Java Bean 应该放在 common.jar中
 
 - 注意实体要实现 Serializable 序列化接口，要不然发送消息会失败
-![](https://img-blog.csdnimg.cn/20190704093852589.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704093852589.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - Pro 照样跟着写一个发消息的方法
-![](https://img-blog.csdnimg.cn/20190704094016584.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704094016584.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 测试代码及结果
-![](https://img-blog.csdnimg.cn/20190704094212455.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190704094310427.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704094212455.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704094310427.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 8  RabbitMQ & Spring Cloud Stream整合实战
 Spring Cloud全家桶在整个中小型互联网公司异常的火爆,Spring Cloud Stream也就渐渐的被大家所熟知,本小节主要来绍RabbitMQ与Spring Cloud Stream如何集成
 ## 8.1 编程模型
@@ -239,11 +257,11 @@ Spring Cloud全家桶在整个中小型互联网公司异常的火爆,Spring Clo
 外部消息传递系统和应用程序之间的桥接提供的生产者和消费者消息（由目标绑定器创建）
 - 消息
 生产者和消费者用于与目标绑定器（以及通过外部消息传递系统的其他应用程序）通信的规范数据结构
-![](https://img-blog.csdnimg.cn/20190704100414674.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704100414674.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 ## 8.2 应用模型
 Spring Cloud Stream应用程序由中间件中立核心组成。该应用程序通过Spring Cloud Stream注入其中的输入和输出通道与外界通信。通过中间件特定的Binder实现，通道连接到外部代理。
-![](https://img-blog.csdnimg.cn/20190704100544237.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-## 8.3 RabbitMQ绑定概述![](https://img-blog.csdnimg.cn/20190704101501891.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704100544237.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+## 8.3 RabbitMQ绑定概述![](https://img-blog.csdnimg.cn/20190704101501891.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 默认情况下，RabbitMQ Binder实现将每个目标映射到TopicExchange。对于每个使用者组，Queue绑定到该TopicExchange。每个使用者实例都为其组的Queue具有相应的RabbitMQ Consumer实例。对于分区生成器和使用者，队列以分区索引为后缀，并使用分区索引作为路由键。对于匿名使用者（没有组属性的用户），使用自动删除队列（具有随机的唯一名称）。
 
 Barista接口: Barista接口是定义来作为后面类的参数，这一接口定义来通道类型和通道名称，通道名称是作为配置用，通道类型则决定了app会使用这一 通道进行发送消息还是从中接收消息
@@ -260,12 +278,12 @@ Barista接口: Barista接口是定义来作为后面类的参数，这一接口
 ## 8.5 实操
 ### Pro
 - pom核心文件
-![](https://img-blog.csdnimg.cn/20190704105257283.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704105257283.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - Sender
-![](https://img-blog.csdnimg.cn/20190704110913136.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704110913136.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 注解`@EnableBinding`声明了这个应用程序绑定了2个通道：INPUT和OUTPUT。这2个通道是在接口`Barista`中定义的（Spring Cloud Stream默认设置）。所有通道都是配置在一个具体的消息中间件或绑定器中
 - Barista接口
-![](https://img-blog.csdnimg.cn/20190704112934707.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704112934707.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - @Input
 声明了它是一个输入类型的通道，名字是Barista.INPUT_CHANNEL，也就是position3的input_channel。这一名字与上述配置app2的配置文件中position1应该一致，表明注入了一个名字叫做input_channel的通道，它的类型是input，订阅的主题是position2处声明的mydest这个主题  
 
@@ -282,36 +300,35 @@ Barista接口: Barista接口是定义来作为后面类的参数，这一接口
 
 ### Con
 - Pom核心文件
-![](https://img-blog.csdnimg.cn/20190704113317198.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704113317198.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 应用启动类
-![](https://img-blog.csdnimg.cn/20190704113434327.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704113434327.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - Barista接口
-![](https://img-blog.csdnimg.cn/20190704113713148.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704113713148.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 配置文件
-![](https://img-blog.csdnimg.cn/20190704114526499.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704114526499.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 接收
-![](https://img-blog.csdnimg.cn/20190704114755779.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704114755779.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 启动Con服务,查看管控台
-![](https://img-blog.csdnimg.cn/2019070411503383.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190704115119708.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/201907041151564.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/2019070411503383.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20190704115119708.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/201907041151564.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 - 运行Pro测试代码及结果
-![](https://img-blog.csdnimg.cn/2019070411543336.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/2019070411543336.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 ![](https://img-blog.csdnimg.cn/20190704115517511.png)
 ![](https://img-blog.csdnimg.cn/2019070411555535.png)
 
 # 9 总结
 本文我们学习了Spring AMQP的相关知识,通过实战对RabbitMQ集成Spring有了直观的认识，这样为
 我们后续的学习、工作使用都打下了坚实的基础，最后我们整合了SpringBoot与Spring Cloud Stream,更方便更高效的集成到我们的应用服务中去!
-
-> 参考
-> -  [SpringAMQP 用户管理组件 RabbitAdmin 以及声明式配置](https://juejin.im/post/5c541218e51d450134320378) [Spring Boot
-> - RabbitMQ源码分析](https://zhuanlan.zhihu.com/p/54450318) [SpringAMQP 之 RabbitTemplate](https://juejin.im/post/5c5c29efe51d457fff4102f1)
-> [SpringAMQP 消息容器 -
-> SimpleMessageListenerContainer](https://juejin.im/user/5c3dfed2e51d4552232fc9cd)
-> [MessageListenerAdapter详解](https://www.jianshu.com/p/d21bafe3b9fd)
-> [SpringAMQP 消息转换器 -
-> MessageConverter](https://juejin.im/post/5c5d925de51d457fc75f7a0c)
-> [RabbitMQ 与 SpringBoot2.X
-> 整合](https://juejin.im/post/5c64e3fd6fb9a049d132a557) [Spring Cloud
-> Stream](https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/2.2.0.RELEASE/spring-cloud-stream.html#spring-cloud-stream-reference)
\ No newline at end of file
+# 参考
+[SpringAMQP 用户管理组件 RabbitAdmin 以及声明式配置](https://juejin.im/post/5c541218e51d450134320378)
+[Spring Boot - RabbitMQ源码分析](https://zhuanlan.zhihu.com/p/54450318)
+[SpringAMQP 之 RabbitTemplate](https://juejin.im/post/5c5c29efe51d457fff4102f1)
+[SpringAMQP 消息容器 - SimpleMessageListenerContainer](https://juejin.im/user/5c3dfed2e51d4552232fc9cd)
+[MessageListenerAdapter详解](https://www.jianshu.com/p/d21bafe3b9fd)
+[SpringAMQP 消息转换器 - MessageConverter](https://juejin.im/post/5c5d925de51d457fc75f7a0c)
+[RabbitMQ 与 SpringBoot2.X 整合](https://juejin.im/post/5c64e3fd6fb9a049d132a557)
+[Spring Cloud Stream](https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/2.2.0.RELEASE/spring-cloud-stream.html#spring-cloud-stream-reference)
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\346\266\210\350\264\271\347\253\257ACK\345\217\212\351\207\215\345\233\236\351\230\237\345\210\227.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\346\266\210\350\264\271\347\253\257ACK\345\217\212\351\207\215\345\233\236\351\230\237\345\210\227.md"
deleted file mode 100644
index ec95dfc29d..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\346\266\210\350\264\271\347\253\257ACK\345\217\212\351\207\215\345\233\236\351\230\237\345\210\227.md"
+++ /dev/null
@@ -1,38 +0,0 @@
-# 1 ACK和NACK
-当设置` autoACK=false` 时，就可以使用手工ACK。
-其实手工方式包括了手工ACK、手工NACK。
-
-- 手工 ACK 时，会发送给Broker一个应答，代表消息处理成功，Broker就可回送响应给Pro
-- NACK 则表示消息处理失败，如果设置了重回队列，Broker端就会将没有成功处理的消息重新发送
-
-## 使用方式
-Con消费时，若由于业务异常，可**手工 NACK** 记录日志，然后进行补偿
-```java
-void basicNack(long deliveryTag, 
-			   boolean multiple,
-			   boolean requeue)
-```
-
-如果由于服务器宕机等严重问题，就需要**手工 ACK** 保障Con消费成功
-```java
-void basicAck(long deliveryTag, boolean multiple)
-```
-
-# 2 消费端的重回队列
-重回队列针对没有处理成功的消息，将消息重新投递给Broker。
-重回队列会把消费失败的消息重新添加到队列尾端，供Con重新消费。
-一般在实际应用中，都会关闭重回队列，即设置为false。
-
-# 3 代码实战
-- Con，关闭自动签收功能
-![](https://img-blog.csdnimg.cn/20190630181703179.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 自定义Con，对第一条消息(序号0)进行NACK，并设置重回队列
-![](https://img-blog.csdnimg.cn/20190630181751190.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- Pro 对消息设置序号,以便区分
-![](https://img-blog.csdnimg.cn/20190630182157417.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 启动Con，查看管控台
-![](https://img-blog.csdnimg.cn/20190630181903266.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190630181927663.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 启动Pro,这里第一条消息由于我们调用了NACK，并且设置了重回队列，所以会导致该条消息一直重复发送，消费端就会一直循环消费
-![](https://img-blog.csdnimg.cn/20190630182519169.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20190630183129436.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247-\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247-\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.md"
index 32a00e81ac..e5b3a72d38 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247-\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247-\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.md"
@@ -1,115 +1,164 @@
-# 1 RabbitMQ
-一款分布式消息中间件，基于erlang开发， 具备语言级别的高并发处理能力。和Spring框架是同一家公司。支持持久化、高可用。
-## 核心概念
+> 全是干货的技术号：
+> 本文已收录在
+>
+> github：
+>
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+> 
+> 码云：
+> 
+>https://gitee.com/JavaEdge/Java-Interview-Tutorial
+>
+> 欢迎 star/fork
+
+# 1 极速了解MQ
+> 介绍Rabbitmg用于解决分布式事务必须掌握的5个核心概念
+
+一款分布式消息中间件，基于erlang语言开发， 具备语言级别的高并发处理能力。和Spring框架是同一家公司。
+支持持久化、高可用
+
+## 核心5个概念:
 1. Queue: 真正存储数据的地方
 2. Exchange: 接收请求，转存数据
 3. Bind: 收到请求后存储到哪里
 4. 消息生产者:发送数据的应用
 5. 消息消费者: 取出数据处理的应用
-![](https://img-blog.csdnimg.cn/2019110903371374.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-# 2 分布式事务问题
-> 分布式事务是一个业务问题，不能脱离具体场景。
+![](https://img-blog.csdnimg.cn/2019110903371374.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+
+
+# 2、分布式事务问题
+> 分布式事务是一个业务问题，不能脱离具体的场景。
+
+## 2.1 分布式事务的几种解决方案
+● 基于数据库XA/ JTA协议的方式
+需要数据库厂商支持; JAVA组件有atomikos等
+● 异步校对数据的方式
+支付宝、微信支付主动查询支付状态、对账单的形式;
+● 基于可靠消息(MQ)的解决方案
+异步场景;通用性较强;拓展性较高
+● TCC编程式解决方案
+严选、阿里、蚂蚁金服自己封装的DTX
+
+本文目标:针对所有人群，学会基于可靠消息来解决分布式事务问题。
+分布式事务的解决方案，业务针对性很强，重要的是思路，而不是照搬
 
 - 美团点评系统架构
-![](https://img-blog.csdnimg.cn/20191109111135363.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191109111135363.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
-## 多系统间的分布式事务问题
-![](https://img-blog.csdnimg.cn/20191109111326448.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+## 2.2 多系统间的分布式事务问题
+![](https://img-blog.csdnimg.cn/20191109111326448.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
 - 用户下单生成订单
-![](https://img-blog.csdnimg.cn/20191109111524174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 需要传递订单数据，由此产生两个事务一致性问题
-![](https://img-blog.csdnimg.cn/20191109111712542.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## 错误案例
-![](https://img-blog.csdnimg.cn/20191109112532574.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-### 当接口调用失败时
-订单系统事务回滚，提示用户操作失败。
-自以为这样的接口调用写法，就不会有分布式事务问题。
-
-### 接口调用成功或失败
-都会产生分布式事务问题：
-- 接口调用成功
-订单系统数据库事务提交失败，运单系统没有回滚，产生数据
-- 接口调用超时
-订单系统数据库事务回滚，运单系统接口继续执行，产生数据
-
-所以都会导致数据不一致问题。
-# 3 正确实现分布式事务（五步法）
-- 之前都是订单系统直接HTTP请求运单系统的接口，出问题了!![](https://img-blog.csdnimg.cn/2019110911365199.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 因此考虑发消息给MQ，异步暂存
-![](https://img-blog.csdnimg.cn/20191109113742318.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191109111524174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- 需要传递订单数据,由此产生两个事务一致性问题
+![](https://img-blog.csdnimg.cn/20191109111712542.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+## 错误的案例
+![](https://img-blog.csdnimg.cn/20191109112532574.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+### 当接口调用失败时，订单系统事务回滚,提示用户操作失败
+`误以为这样的接口调用写法，就不会有分布式事务问题`
+
+### 接口调用成功或者失败，都会产生分布式事务问题:
+1. 接口调用成功，订单系统数据库事务提交失败，运单系统没有回滚，产生数据
+2. 接口调用超时，订单系统数据库事务回滚，运单系统接口继续执行，产生数据
+
+上述两种情况，都会导致数据不一致的问题
+
+# 3、实现分布式事务 - 五步法
+> 通过MQ解决分布式事务的5个步骤, 以及分布式事务处理中要注意的地方
+
+- 之前都是订单系统发送HTTP请求运单系统的接口,出问题了!![](https://img-blog.csdnimg.cn/2019110911365199.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- 因此我们考虑发消息给MQ, 异步暂存!
+![](https://img-blog.csdnimg.cn/20191109113742318.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
 ## 3.1 整体设计思路
-![](https://img-blog.csdnimg.cn/2019110911390025.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-外卖下订单后，慢慢等待运单中心数据生成，并非强制要求同时。但要保证：
-- 可靠生产
-保证消息一定要发送到Rabitmq服务
-- 可靠消费
-保证消息取出来一定正确消费掉
+![](https://img-blog.csdnimg.cn/2019110911390025.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+外卖下订单后,可以慢慢等待运单中心数据生成,并非强制要求同时性
+
+
+1. 可靠生产:保证消息一定要发送到Rabitmq服务
+2. 可靠消费:保证消息取出来一定正确消费掉
 
 最终使多方数据达到一致。
-## 实现步骤
-### 步骤1 - 可靠的消息生产记录消息发送
-![](https://img-blog.csdnimg.cn/20191110002502550.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-#### 隐患
-- 可能消息发送失败：
-![](https://img-blog.csdnimg.cn/20191110002547799.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-为确保数据一定成功发送到MQ。在同一事务中，增加一个记录表的操作, 记录`每一条发往MQ的数据以及它的发送状态`。
-- 于是在订单系统中增加一个本地信息表
-![](https://img-blog.csdnimg.cn/20191110002746532.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-不再通过HTTP请求直接调用运单系统接口，而是使用MQ：
-生成订单时，也保存本地信息表
-![](https://img-blog.csdnimg.cn/20191110003415706.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20191110003248457.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20191110004913354.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 步骤2-可靠消息生产(修改消息发送状态)
-- 利用RabbitMQ的事务发布确认机制(confirm)：开启后，MQ准确受理消息会返回回执
-![](https://img-blog.csdnimg.cn/20191110003908628.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 然后就能知道如何更新本地信息表
+
+## 3.2 步骤1 - 可靠的消息生产记录消息发送
+![](https://img-blog.csdnimg.cn/20191110002502550.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- 存在隐患 - 可能消息发送失败呀!
+![](https://img-blog.csdnimg.cn/20191110002547799.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+➢ 为了确保数据一定成功发送到MQ。
+➢ 在同一事务中，增加一个记录表的操作, 记录`每一条发往MQ的数据以及它的发送状态`
+于是我们在订单系统中增加一个本地信息表![](https://img-blog.csdnimg.cn/20191110002746532.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+于是在代码实践中,不再通过HTTP接口调用运单系统接口,而是使用MQ
+
+生成订单时,也保存本地信息表
+![](https://img-blog.csdnimg.cn/20191110003248457.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191110003415706.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191110004913354.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+## 3.3 步骤2 - 可靠消息生产(修改消息发送状态)
+- 利用RabbitMQ事务发布确认机制(confirm)
+开启后，MQ准确受理消息会返回回执
+![](https://img-blog.csdnimg.cn/20191110003908628.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- 然后就能知道如何更新本地信息表了
 ![](https://img-blog.csdnimg.cn/2019111000394665.png)
 
-- 确保在SpringBoot项目中开启Confirm机制
-![](https://img-blog.csdnimg.cn/20191110004341395.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-#### 代码实现
-![](https://img-blog.csdnimg.cn/201911100049513.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20191110005151992.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+-确保在SB中开启Confirm机制
+![](https://img-blog.csdnimg.cn/20191110004341395.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+![](https://img-blog.csdnimg.cn/201911100049513.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191110005151992.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
-- 若出现回执没收到、消息状态修改失败等特殊情况
-兜底方案：定时检查消息表，超时没发送成功，再次重发。
+- 如果出现回执没收到、消息状态修改失败等特殊情况
+`兜底方案:定时检查消息表，超时没发送成功，再次重发`
 
-### 步骤3 - 可靠消息处理(正常处理)
-- 运单系统收到消息数据后，突然宕机或访问运单DB时，DB突然宕机，消息数据不就丢了？
-![](https://img-blog.csdnimg.cn/20191110010205178.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+## 3.4 步骤3 - 可靠消息处理(正常处理)
+- 运单系统收到消息数据后,突然宕机,或者访问运单DB时,DB突然宕机,消息数据不就丢了吗!!!
+![](https://img-blog.csdnimg.cn/20191110010205178.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+于是需要以下特性:
 
-于是还需要如下处理：
 ➢  幂等性
 防止重复消息数据的处理，一次用户操作，只对应一次数据处理
-![](https://img-blog.csdnimg.cn/20191110010346674.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191110010346674.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 ➢  开启`手动ACK模式`
 由消费者控制消息的重发/清除/丢弃
-![](https://img-blog.csdnimg.cn/20191110010518296.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 步骤4 - 可靠消息处理(消息重发)
-![](https://img-blog.csdnimg.cn/2019111001144842.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-消费者处理失败，需要MQ重发给消费者。出现异常一般会重试几次，由消费者自身记录重试次数，并进行次数控制。
-
-### 步骤五 - 可靠消息处理(消息丢弃)
-消费者处理失败，直接丢弃或者转移到死信队列(DLQ)。`重试次数过多、消息内容格式错误等情况，通过线上预警机制通知运维`。
-![](https://img-blog.csdnimg.cn/20191110012037386.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-# 4 总结
-## MQ实现分布式事务分析
-### 优点
+![](https://img-blog.csdnimg.cn/20191110010518296.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+### 3.5 步骤4 - 可靠消息处理(消息重发)
+![](https://img-blog.csdnimg.cn/2019111001144842.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+消费者处理失败，需要MQ再次重发给消费者。
+出现异常一般会重试几次，由消费者自身记录重试次数，并进行次数控制(不会永远重试!)
+
+## 3.6 步骤五 - 可靠消息处理(消息丢弃)
+消费者处理失败，直接丢弃或者转移到死信队列(DLQ)
+`重试次数过多、消息内容格式错误等情况，通过线上预警机制通知运维人员`
+![](https://img-blog.csdnimg.cn/20191110012037386.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+# 4 总结及扩展
+## 4.1 MQ方案的优点和缺点
+口优点
 1. 通用性强
 2. 拓展性强
 3. 方案成熟
 
-### 缺点
-- 基于消息中间件，只适合异步场景
-- 消息处理会有延迟，需要业务上能够容忍
+口缺点
+4. 基于消息中间件，只适合异步场景
+5. 消息处理会有延迟，需要业务上能够容忍
+
+尽量避免分布式事务;
+尽量将非核心事务做成异步;
+
+## 4.2 拓展
+### 分布式事务解决方案的理论依据
+CAP理论
+BASE理论
+2PC协议
+3PC协议
+Paxos算法.
+Raft一致性协议
 
-尽量避免分布式事务，尽量将非核心事务做成异步。
+ 
+# 参考
+[美团配送系统架构演进实践](https://tech.meituan.com/2018/07/26/peisong-sys-arch-evolution.html)
 
-> 参考
-> - [美团配送系统架构演进实践](https://tech.meituan.com/2018/07/26/peisong-sys-arch-evolution.html)
\ No newline at end of file
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213-\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227\357\274\210Priority Queue\357\274\211.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213-\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227\357\274\210Priority Queue\357\274\211.md"
index 57eabe0ba4..81d8a24d87 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213-\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227\357\274\210Priority Queue\357\274\211.md"	
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213-\344\274\230\345\205\210\347\272\247\351\230\237\345\210\227\357\274\210Priority Queue\357\274\211.md"	
@@ -50,4 +50,6 @@ ch.queueDeclare("my-priority-queue", true, false, false, args);
 
 参考
 - https://www.rabbitmq.com/priority.html
-- https://www.rabbitmq.com/queues.html#optional-arguments
\ No newline at end of file
+- https://www.rabbitmq.com/queues.html#optional-arguments
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\346\266\210\350\264\271\347\253\257\346\266\210\346\201\257\351\231\220\346\265\201.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\346\266\210\350\264\271\347\253\257\346\266\210\346\201\257\351\231\220\346\265\201.md"
index 4ffae094dc..5474575c72 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\346\266\210\350\264\271\347\253\257\346\266\210\346\201\257\351\231\220\346\265\201.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RabbitMQ/RabbitMQ\351\253\230\347\272\247\347\211\271\346\200\247\344\271\213\346\266\210\350\264\271\347\253\257\346\266\210\346\201\257\351\231\220\346\265\201.md"
@@ -1,11 +1,8 @@
-# 1 消息过载
-假设RabbitMQ服务器有上万条未处理消息，随便打开一个消费端，会造成巨量消息瞬间全部推送过来，然而我们单个客户端无法同时处理这么多数据。
-
-
-还比如说单个Pro一分钟产生了几百条数据，但是单个Con一分钟可能只能处理60条，这时Pro-Con不平衡。通常Pro没办法做限制，所以Con就需要做一些限流措施，否则如果超出最大负载，可能导致Con性能下降，服务器卡顿甚至崩溃。
-
-因此，我们需要Con限流。
+# 1 消息过载场景
+假设Rabbitmq服务器有上万条未处理的消息，随便打开一个消费端，会造成巨量消息瞬间全部推送过来，然而我们单个客户端无法同时处理这么多数据。此时很有可能导致服务器崩溃，严重的可能导致线上的故障。
+还有一些其他的场景，比如说单个Pro一分钟产生了几百条数据，但是单个Con一分钟可能只能处理60条，这时Pro-Con不平衡的。通常Pro没办法做限制，所以Con就需要做一些限流措施，否则如果超出最大负载，可能导致Con性能下降，服务器卡顿甚至崩溃等一系列严重后果。
 
+因此，我们需要Con限流
 # 2 Con限流机制
 RabbitMQ提供了一种qos (服务质量保证)功能，在非自动确认消息的前提下，若一定数目的消息 (通过基于Con或者channel设置Qos的值) 未被确认前，不消费新的消息。
 
@@ -15,16 +12,13 @@ RabbitMQ提供了一种qos (服务质量保证)功能，在非自动确认消息
 ## 限流设置API 
 ## basicQos
 - QoS
-![](https://img-blog.csdnimg.cn/2020111416443320.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/2020111416443320.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 
 请求特定设置“服务质量（quality of service）”。 这些设置强加数据的服务器将需要确认之前，为消费者发送的消息数量限制。 因此，他们提供消费者发起的流量控制的一种手段。
-![](https://img-blog.csdnimg.cn/20201114163559162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
 
-```java
-void BasicQos(uint prefetchSize, ushort prefetchCount,
-				bool global);
-```
 
+![](https://img-blog.csdnimg.cn/20201114163559162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+void BasicQos(uint prefetchSize, ushort prefetchCount, bool global);
 - prefetchSize: 单条消息的大小限制，Con通常设置为0，表示不做限制
 - prefetchCount: 一次最多能处理多少条消息
 - global: 是否将上面设置true应用于channel级别还是取false代表Con级别
@@ -32,11 +26,8 @@ void BasicQos(uint prefetchSize, ushort prefetchCount,
 > prefetchSize和global这两项，RabbitMQ没有实现，不研究
 > prefetchCount在 `autoAck=false` 的情况下生效，即在自动应答的情况下该值无效，所以必须手工ACK。
 
-```java
 void basicAck(Integer deliveryTag，boolean multiple)
-```
-
-调用该方法就会主动回送给Broker一个应答，表示这条消息我处理完了，你可以给我下一条了。参数multiple表示是否批量签收，由于我们是一次处理一条消息，所以设置为false。
+调用这个方法就会主动回送给Broker一个应答，表示这条消息我处理完了，你可以给我下一条了。参数multiple表示是否批量签收，由于我们是一次处理一条消息，所以设置为false
 
 
 #  3 代码实战
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\236\346\210\230(1)-\347\256\200\344\273\213.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/ RocketMQ\345\256\236\346\210\230(\344\270\200) - \347\256\200\344\273\213.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\236\346\210\230(1)-\347\256\200\344\273\213.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/ RocketMQ\345\256\236\346\210\230(\344\270\200) - \347\256\200\344\273\213.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\242\346\210\267\347\253\257\346\230\257\345\246\202\344\275\225\346\204\237\347\237\245Broker\350\212\202\347\202\271\347\232\204\357\274\237.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ Client\345\246\202\344\275\225\345\234\250\351\233\206\347\276\244\346\236\266\346\236\204\344\270\255\345\256\232\344\275\215Broker\350\212\202\347\202\271\357\274\237.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\242\346\210\267\347\253\257\346\230\257\345\246\202\344\275\225\346\204\237\347\237\245Broker\350\212\202\347\202\271\347\232\204\357\274\237.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ Client\345\246\202\344\275\225\345\234\250\351\233\206\347\276\244\346\236\266\346\236\204\344\270\255\345\256\232\344\275\215Broker\350\212\202\347\202\271\357\274\237.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\236\346\210\230(3)-\346\266\210\346\201\257\347\232\204\346\234\211\345\272\217\346\200\247.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ \351\253\230\347\272\247\347\211\271\346\200\247-\346\266\210\346\201\257\347\232\204\346\234\211\345\272\217\346\200\247.md"
similarity index 81%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\236\346\210\230(3)-\346\266\210\346\201\257\347\232\204\346\234\211\345\272\217\346\200\247.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ \351\253\230\347\272\247\347\211\271\346\200\247-\346\266\210\346\201\257\347\232\204\346\234\211\345\272\217\346\200\247.md"
index 512c017712..8c58b83f98 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\236\346\210\230(3)-\346\266\210\346\201\257\347\232\204\346\234\211\345\272\217\346\200\247.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ \351\253\230\347\272\247\347\211\271\346\200\247-\346\266\210\346\201\257\347\232\204\346\234\211\345\272\217\346\200\247.md"	
@@ -1,48 +1,56 @@
 # 1 为什么需要消息有序
-996一辈子了，准备去银行存取款，对应两个异步短信消息，要保证先存后取：
-- M1 存钱
-- M2 取钱
+996 一年终于攒了十万存在银行卡里准备存取款，对应两个异步的短信消息，要保证先存后取：
+- M1 - 存钱
+- M2 - 取钱
 ![](https://img-blog.csdnimg.cn/20191110204432867.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
-而MQ默认发消息到不同Q显然是行不通的，会乱序。
-因此，需发往同一Q，依赖队列的先进先出机制。
+而MQ默认发消息到不同queue显然是行不通的，会乱序。因此，需要发往同一queue，依靠其先进先出机制。
+
 # 2 基本概念
-有序消息，又叫顺序消息(FIFO消息)，指消息的消费顺序和产生顺序相同。
+有序消息又叫顺序消息(FIFO 消息)，指消息的消费顺序和产生顺序相同。
 
-如订单的生成、付款、发货，这串消息必须按序处理。顺序消息又可分为：
+比如订单的生成、付款、发货，这串消息必须按顺序处理。
+顺序消息又分为如下：
 ## 2.1 全局顺序
-一个Topic内所有的消息都发布到同一Q，按FIFO顺序进行发布和消费：
+一个Topic内所有的消息都发布到同一个queue，按照FIFO顺序进行发布和消费：
 ![](https://img-blog.csdnimg.cn/20191110205132371.png)
+
 ### 适用场景
 性能要求不高，所有消息严格按照FIFO进行消息发布和消费的场景。
+
 ## 2.2 分区顺序
-对于指定的一个Topic，所有消息按`sharding key`进行区块(queue)分区，同一Q内的消息严格按FIFO发布和消费。
+对于指定的一个Topic，所有消息按`sharding key`进行区块(queue)分区，同一queue内的消息严格按FIFO发布和消费。
 
 - Sharding key是顺序消息中用来区分不同分区的关键字段，和普通消息的Key完全不同。
 ![](https://img-blog.csdnimg.cn/20191110205442842.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
 ### 适用场景
 性能要求高，根据消息中的sharding key去决定消息发送到哪个queue。
+
 ## 2.3 对比
 - 发送方式对比
 ![](https://img-blog.csdnimg.cn/20191110210418100.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
 # 3 如何保证消息顺序？
 在MQ模型中，顺序需由3个阶段去保障
 1. 消息被发送时保持顺序
 2. 消息被存储时保持和发送的顺序一致
 3. 消息被消费时保持和存储的顺序一致
+
 ![](https://img-blog.csdnimg.cn/20191110210708395.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
 # 4 RocketMQ 有序消息实现原理
 ![](https://img-blog.csdnimg.cn/20191110210921254.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
 RocketMQ消费端有两种类型：
 - MQPullConsumer
 - MQPushConsumer
-![](https://img-blog.csdnimg.cn/20201114141638859.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20201114141638859.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 
 底层都是通过pull机制实现，pushConsumer是一种API封装而已。
 - `MQPullConsumer` 由用户控制线程，主动从服务端获取消息，每次获取到的是一个`MessageQueue`中的消息。
 	- `PullResult`中的 `List<MessageExt> msgFoundList`
-![](https://img-blog.csdnimg.cn/20201114142006113.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20201114142006113.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 
 - `MQPushConsumer`由用户注册`MessageListener`来消费消息，在客户端中需要保证调用`MessageListener`时消息的顺序性
 ![](https://img-blog.csdnimg.cn/20191110212543478.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
@@ -56,6 +64,7 @@ RocketMQ消费端有两种类型：
 ![](https://img-blog.csdnimg.cn/20191110213124288.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 - 判断是并发的还是有序的,对应不同服务实现类
 ![](https://img-blog.csdnimg.cn/20191110213603167.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+
 # 5 有序消息的缺陷
 发送顺序消息无法利用集群的Failover特性，因为不能更换MessageQueue进行重试。
 
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\236\346\210\230(2)-\346\236\266\346\236\204.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\236\346\210\230(2)-\346\236\266\346\236\204.md"
deleted file mode 100644
index 020e893f26..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\345\256\236\346\210\230(2)-\346\236\266\346\236\204.md"
+++ /dev/null
@@ -1,45 +0,0 @@
-# 1 角色
-RocketMQ由四个角色组成：
-- Producer
-消息生产者
-- Consumer
-消费者
-- Broker
-MQ服务，负责接收、分发消息
-- NameServer
-负责MQ服务之间的协调
-# 2 架构设计
-![](https://img-blog.csdnimg.cn/20191025001711704.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-## NameServer-MQ服务注册发现中心
-提供轻量级服务发现和路由。
-每个名称服务器记录完整的路由信息，提供相应的读写服务，并支持快速存储扩展。
-
-> NameServer 充当路由信息提供者。生产者/消费者客户查找主题以查找相应的broker列表。
-#  3 搭建
-## 配置
-- runserver.sh 设置小点
-![](https://img-blog.csdnimg.cn/20191103145158719.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-- runbroker.sh 设置小点
-![](https://img-blog.csdnimg.cn/20191103145746578.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-## 启动
-```bash
-nohup sh bin/mqnamesrv > logs/namesrv.log 2>&1 &
-```
-```bash
-nohup sh bin/mqbroker -n localhost:9876 > 
-~/logs/rocketmqlogs/broker.log 2>&1 &
-```
-- 启动报错
-![](https://img-blog.csdnimg.cn/20191103151015192.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-- 查看日志![](https://img-blog.csdnimg.cn/20191103150942509.png)
-- 改启动文件,添加JAVA_HOME变量![](https://img-blog.csdnimg.cn/20191103154507585.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 启动成功
-![](https://img-blog.csdnimg.cn/20191103154618638.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-启动broker
-```bash
-nohup sh bin/mqbroker -c conf/broker.conf -n localhost:9876 > logs/broker.log 2>&1 &
-```
-![](https://img-blog.csdnimg.cn/20191103155747201.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-# remoting模块架构
-![](https://img-blog.csdnimg.cn/20201008002847207.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\347\224\237\344\272\247\346\266\210\346\201\257\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\347\224\237\344\272\247\346\266\210\346\201\257\346\272\220\347\240\201\350\247\243\346\236\220.md"
index f8fc857373..7eadc53df1 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\347\224\237\344\272\247\346\266\210\346\201\257\346\272\220\347\240\201\350\247\243\346\236\220.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/RocketMQ/RocketMQ\347\224\237\344\272\247\346\266\210\346\201\257\346\272\220\347\240\201\350\247\243\346\236\220.md"
@@ -1,20 +1,27 @@
-> 基于最新的release-4.7.1代码分析。
+基于最新的release-4.7.1代码分析。
+
+- 客户端是个单独模块，在rocketmq/client
+![](https://img-blog.csdnimg.cn/20200810004153816.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
 
-- 客户端是个单独模块
-![](https://img-blog.csdnimg.cn/20200810004153816.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 # 从UT看Producer API
-阅读源码，不推荐从入口开始看到底，毕竟你太年轻，把握不住它的底。我们可以带着问题去研究源码：Producer是如何发消息的？
 
-推荐从UT用例入手。通过这些小流程，debug查看关键流程即可。
+阅读源码，不推荐从入口开始看到底，毕竟你也看不到底。而应该带着问题分析源码：Producer是如何发消息的。
 
-RocketMQ客户端的Producer的所有测试用例都在DefaultMQProducerTest类，这就很方便我们快速了解Producer。
+推荐从UT单元测试用例入手。因为UT用例都是测试代码中的一个小流程。
+规范的开源框架，单元测试覆盖率都很高，很容易找到我们所需流程所对应的用例。因此从这些用例入手，调试跟踪调用栈，看清关键代码流程。
 
-- 主要测试用例
-![](https://img-blog.csdnimg.cn/20200810010435935.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+先分析一下RocketMQ客户端的单元测试，看看Producer API应该如何使用。
 
-- Producer相关的UML图
-![](https://img-blog.csdnimg.cn/20210530204636430.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+Producer的所有测试用例都在同个测试类 DefaultMQProducerTest，可以了解到Producer的主要功能。
+
+- 主要测试用例
+![](https://img-blog.csdnimg.cn/20200810010435935.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+init和terminate是测试开始初始化和测试结束销毁时需要执行的代码
+testXXX方法都是各种场景发消息的测试用例
 
+- Producer相关的核心类和接口
+![](https://img-blog.csdnimg.cn/2020081000535348.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 
 - 门面模式（Facade Pattern）
 给客户端提供了一个可以访问系统的接口，隐藏系统内部的复杂性。
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\345\246\202\344\275\225\344\277\235\350\257\201MQ\344\270\215\351\207\215\345\244\215\346\266\210\350\264\271\346\266\210\346\201\257(\345\271\202\347\255\211\346\200\247).md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\345\246\202\344\275\225\344\277\235\350\257\201MQ\344\270\215\351\207\215\345\244\215\346\266\210\350\264\271\346\266\210\346\201\257(\345\271\202\347\255\211\346\200\247).md"
deleted file mode 100644
index 667fdedb77..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\345\246\202\344\275\225\344\277\235\350\257\201MQ\344\270\215\351\207\215\345\244\215\346\266\210\350\264\271\346\266\210\346\201\257(\345\271\202\347\255\211\346\200\247).md"
+++ /dev/null
@@ -1,35 +0,0 @@
-消费消息需要考虑：
-- 会不会重复消费
-- 能不能避免重复消费
-- 重复消费了也别造成系统异常可以吗
-
-使用MQ如何保证幂等性也是架构设计考虑的问题。
-
-rabbitmq、rocketmq、kafka，都可能会出现消费重复消费，因为这个问题不是MQ自身保证的，是我们开发自己需要保证的。
-
-# 何时重复消费？
-kafka有个offset概念，每个消息写进去，都有一个offset，代表他的序号，然后consumer消费了消息后，每隔一段时间，会把自己消费过的消息的offset提交一下，代表我已经消费过了，下次我要是重启啥的，你就让我继续从上次消费到的offset来继续消费。
-
-但凡事有意外，比如重启系统，碰到急的，直接kill进程再重启。这会导致consumer有些消息处理了，但是没来得及提交offset。重启之后，少数消息会再次消费。
-
-其实重复消费不可怕，可怕的是你没考虑到重复消费之后，如何**保证幂等性**。
-
-比如你有个系统，消费一条往DB插一条，要是你一个消息重复两次，你不就插入两条，这数据不就错了？
-但你要是消费到第二次时，自己判断一下已消费了，直接扔了，不就只保留了一条数据！
-
-一条数据重复出现两次，DB里就只有一条数据，这就保证了系统的幂等性。
-
-幂等性，就一个数据或一个请求，给你重复来多次，你得确保对应的数据是不会改变的，不能出错。
-
-所以
-# 怎么保证消息队列消费的幂等性？
-还是得结合业务来思考，大体思路如下：
-- 写DB，你先根据主键查一下，如果这数据都有了，你就别插入了，update之
-- 写redis，那没问题了，反正每次都是set，天然幂等
-- 其它场景，你需要让生产者发送每条消息时，里面加一个全局唯一id，然后你这里消费到了之后，先根据这个id去redis查一下，之前消费过吗？
-	- 如果没有消费过
-你就处理，然后这个id写redis
-	- 如果消费过了
-那你就别处理了，保证别重复处理相同的消息
-
-还有比如基于DB的唯一键保证重复数据不会重复插入多条。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\345\256\236\347\216\260\345\244\215\345\210\266\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\345\256\236\347\216\260\345\244\215\345\210\266\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
index 4fde83eb63..55f87e17ad 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\345\256\236\347\216\260\345\244\215\345\210\266\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\345\256\236\347\216\260\345\244\215\345\210\266\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
@@ -1,3 +1,4 @@
+
 MQ
 - 在Pro、Con端，依靠业务代码，配合请求确认机制保证
 - 在服务端，采用持久化和复制
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227(1)-\346\266\210\346\201\257\351\230\237\345\210\227\347\232\204\346\204\217\344\271\211.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227\357\274\210\344\270\200\357\274\211- \346\266\210\346\201\257\351\230\237\345\210\227\347\232\204\346\204\217\344\271\211.md"
similarity index 100%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227(1)-\346\266\210\346\201\257\351\230\237\345\210\227\347\232\204\346\204\217\344\271\211.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227\357\274\210\344\270\200\357\274\211- \346\266\210\346\201\257\351\230\237\345\210\227\347\232\204\346\204\217\344\271\211.md"
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227(2)- MQ\351\200\211\345\236\213.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211- MQ\351\200\211\345\236\213.md"
similarity index 72%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227(2)- MQ\351\200\211\345\236\213.md"
rename to "\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211- MQ\351\200\211\345\236\213.md"
index 39bb631981..6b9b135afb 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227(2)- MQ\351\200\211\345\236\213.md"	
+++ "b/\346\225\260\346\215\256\345\255\230\345\202\250/\346\266\210\346\201\257\351\230\237\345\210\227/\346\266\210\346\201\257\351\230\237\345\210\227\351\235\242\350\257\225\350\247\243\346\236\220\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211- MQ\351\200\211\345\236\213.md"	
@@ -1,4 +1,4 @@
-# 1 选型标准
+# 1 MQ选型的标准
 ## 1.1 开源（白嫖）
 方便可以修改源代码，而非一味地等待软件提供商猴年马月发布的下个版本解决。在知识产权下，使用开源的才可商用。
 
@@ -13,14 +13,13 @@
 具备足够好的性能，能满足绝大多数场景的性能要求。
 
 看完标准，于是市面上主要就如下可供选择：
+
 # 2 RabbitMQ
 ## 2.1 优点
 Erlang语言编写，最早是为电信行业系统可靠通信设计，是支持AMQP协议的消息队列之一。相当轻量级的消息队列，非常容易部署和使用。号称世上使用最广泛的开源消息队列。
 
 ### 支持非常灵活的路由配置
-和其他消息队列不同，它在生产者（Producer）和队列（Queue）之间增加了一个Exchange模块。
-作用和交换机相似，根据配置的路由规则将生产者发出的消息分发到不同队列。
-路由规则也非常灵活，甚至你可以自己来实现路由规则。如果你正好需要该功能，RabbitMQ是个不错选择。
+和其他消息队列不同，它在生产者（Producer）和队列（Queue）之间增加了一个Exchange模块。作用和交换机也非常相似，根据配置的路由规则将生产者发出的消息分发到不同的队列中。路由的规则也非常灵活，甚至你可以自己来实现路由规则。如果你正好需要该功能，RabbitMQ是个不错选择。
 ### 支持的客户端语言
 所有消息队列中最多的。
 
@@ -30,7 +29,7 @@ Erlang语言编写，最早是为电信行业系统可靠通信设计，是支
 当大量消息积压的时候，RabbitMQ的性能急剧下降。
 
 ### 性能
-介绍的这几个消息队列中最差的，根据官方给出的测试数据综合我们日常使用的经验，依据硬件配置的不同，它大概可以处理几w~十几w条/s。也足够支撑绝大多数应用场景了，不过，如果你的应用对消息队列的性能要求非常高，那不要选RabbitMQ。
+介绍的这几个消息队列中最差的，根据官方给出的测试数据综合我们日常使用的经验，依据硬件配置的不同，它大概每秒钟可以处理几万到十几万条消息。其实，这个性能也足够支撑绝大多数的应用场景了，不过，如果你的应用对消息队列的性能要求非常高，那不要选RabbitMQ。
 
 ### Erlang
 小众语言，学习曲线非常陡峭。如果想做扩展和二次开发，慎重考虑维护问题。
@@ -45,30 +44,30 @@ RocketMQ有着不错的性能，稳定性和可靠性，具备一个现代的消
 大多数问题你都可以找到中文的答案，也许会成为你选择它的一个原因。
 
 - RocketMQ使用Java语言开发
-贡献者大多数都是中国人，源代码相对也比较容易读懂，很容易对RocketMQ进行扩展或者二次开发。
+贡献者大多数都是中国人，源代码相对也比较容易读懂，你很容易对RocketMQ进行扩展或者二次开发。
 
 - RocketMQ对在线业务的响应时延做了很多的优化
-大多数情况下可以做到ms级响应，如果你的应用场景很在意响应时延，那应该选择使用RocketMQ。
+大多数情况下可以做到毫秒级的响应，如果你的应用场景很在意响应时延，那应该选择使用RocketMQ。
 
-> RocketMQ是怎么做到低延时的？
+> rocketMQ是怎么做到低延时的？
 主要是设计上的选择问题，Kafka中到处都是“批量和异步”设计，它更关注的是整体的吞吐量，而RocketMQ的设计选择更多的是尽量及时处理请求。
 比如发消息，同样是用户调用了send()方法，RockMQ它会直接把这个消息发出去，而Kafka会把这个消息放到本地缓存里面，然后择机异步批量发送。
 所以，RocketMQ它的时延更小一些，而Kafka的吞吐量更高。
 
 - RocketMQ的性能比RabbitMQ要高一个数量级
-大概处理几十w条/s。
+每秒钟大概能处理几十万条消息。
 
 ## 3.2 缺点
-作为国产的消息队列，相比国外的比较流行的同类产品，在国际上还没有那么流行，与周边生态系统的集成和兼容程度要略逊一筹。
+RocketMQ的一个劣势是，作为国产的消息队列，相比国外的比较流行的同类产品，在国际上还没有那么流行，与周边生态系统的集成和兼容程度要略逊一筹。
 
 # 4 Kafka
 最早由LinkedIn开发，目前也是Apache顶级项目。最初的设计目的是用于处理海量的日志。
 
-在早期的版本中，为了获得极致性能，在设计方面做了很多的牺牲，比如：
+在早期的版本中，为了获得极致的性能，在设计方面做了很多的牺牲，比如：
 - 不保证消息的可靠性
 - 可能会丢失消息
 - 不支持集群
-- 功能上较简陋
+- 功能上也比较简陋
 
 这些牺牲对于处理海量日志这个特定的场景都是可以接受的。这个时期的Kafka甚至不能称之为一个合格的消息队列。
 
@@ -87,7 +86,6 @@ RocketMQ有着不错的性能，稳定性和可靠性，具备一个现代的消
 但Kafka这种异步批量的设计带来的问题是，它的同步收发消息的响应时延比较高，因为当客户端发送一条消息的时候，Kafka并不会立即发送出去，而是要等一会儿攒一批再发送，在它的Broker中，很多地方都会使用这种“先攒一波再一起处理”的设计。
 当你的业务场景中，每秒钟消息数量没有那么多的时候，Kafka的时延反而会比较高。所以，Kafka不太适合在线业务场景。
 
-# 	其它MQ
 - ActiveMQ
 最老牌的开源消息队列，十年前唯一可供选择的开源消息队列，目前已进入老年期，社区不活跃。无论是功能还是性能方面，ActiveMQ都与现代的消息队列存在明显的差距，它存在的意义仅限于兼容那些还在用的爷辈儿系统。
 - ZeroMQ
@@ -97,11 +95,8 @@ Pulsar是一个新兴的开源消息队列产品，最早是由Yahoo开发，目
 目前已经在使用Pulsar的公司已经不少了,国内的话有下面几家:
 涂鸦智能、腾讯计费系统、智联招聘、甜橙金融、EMQX。
 
-# kafka、activemq、rabbitmq、rocketmq对比
-![](https://img-blog.csdnimg.cn/20190516180246505.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
 # 5 选型总结
-- 最早大家都用ActiveMQ，但是现在用的不多了，没经过大规模吞吐量场景的验证，社区也不是很活跃，算了吧，不推荐
-- 后来大家开始用RabbitMQ，但erlang语言阻止了大量的java工程师去深入研究和掌控他，几乎处于不可控，但是开源的，比较稳定支持，活跃度也高。如果消息队列并不是你将要构建系统的主角之一，你对消息队列功能和性能都没有很高的要求，只需要一个开箱即用易于维护的产品，建议RabbitMQ。
-- 如果你的系统使用消息队列主要场景是处理在线业务，比如在交易系统中用消息队列传递订单，那RocketMQ的低延迟和金融级的稳定性是你需要的。
-- 如果需要处理海量的消息，像收集日志、监控信息或是前端的埋点这类数据，或是你的应用场景大量使用了大数据、流计算相关的开源产品，那Kafka是最适合。
\ No newline at end of file
+如果消息队列并不是你将要构建系统的主角之一，你对消息队列功能和性能都没有很高的要求，只需要一个开箱即用易于维护的产品，建议RabbitMQ。
+如果你的系统使用消息队列主要场景是处理在线业务，比如在交易系统中用消息队列传递订单，那RocketMQ的低延迟和金融级的稳定性是你需要的。
+
+如果需要处理海量的消息，像收集日志、监控信息或是前端的埋点这类数据，或是你的应用场景大量使用了大数据、流计算相关的开源产品，那Kafka是最适合。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/Guava Cache\347\274\223\345\255\230\350\256\276\350\256\241\345\216\237\347\220\206.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/Guava Cache\347\274\223\345\255\230\350\256\276\350\256\241\345\216\237\347\220\206.md"
deleted file mode 100644
index d82329c0ea..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/Guava Cache\347\274\223\345\255\230\350\256\276\350\256\241\345\216\237\347\220\206.md"	
+++ /dev/null
@@ -1,228 +0,0 @@
-Google开源的Java重用工具集库Guava里的一款缓存工具，实现的缓存功能：
-- 自动将entry节点加载进缓存结构
-- 当缓存的数据超过设置的最大值时，使用LRU移除
-- 具备根据entry节点上次被访问或者写入时间计算它的过期机制
-- 缓存的key被封装在WeakReference引用内
-- 缓存的Value被封装在WeakReference或SoftReference引用内
-- 统计缓存使用过程中命中率、异常率、未命中率等统计数据
-
-Guava Cache架构设计源于ConcurrentHashMap，简单场景下可自行编码通过HashMap做少量数据的缓存。
-但若结果可能随时间改变或希望存储的数据空间可控，最好自己实现这种数据结构。
-
-使用多个segments方式的细粒度锁，在保证线程安全的同时，支持高并发场景需求。
-Cache类似Map，是存储K.V对的集合，不过它还需处理evict、expire、dynamic load等算法逻辑，需要一些额外信息实现这些操作，需做方法与数据的关联封装。
-## Guava Cache数据结构
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMjVhY2JjYzk3NGJlYjk0MS5wbmc?x-oss-process=image/format,png)
-Cache由多个Segment组成，而每个Segment包含一个ReferenceEntry数组：
-
-```java
-volatile @MonotonicNonNull AtomicReferenceArray<ReferenceEntry<K, V>> table;
-```
-每个ReferenceEntry数组项都是一条ReferenceEntry链。
-![](https://img-blog.csdnimg.cn/8088871d635349d88e7bd52e5f795d08.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_Q1NETiBASmF2YUVkZ2Uu,size_16,color_FFFFFF,t_70,g_se,x_16)
-
-一个ReferenceEntry包含key、hash、value Reference、next字段，除了在ReferenceEntry数组项中组成的链。
-
-ReferenceEntry可以是强引用类型的key，也可以WeakReference类型的key，为了减少内存使用量，还可以根据是否配置了expireAfterWrite、expireAfterAccess、maximumSize来决定是否需要write链和access链确定要创建的具体Reference：StrongEntry、StrongWriteEntry、StrongAccessEntry、StrongWriteAccessEntry等。
-### ReferenceEntry接口
-![](https://img-blog.csdnimg.cn/a16a7a0d4a2a49b9ab33ca85c09e666d.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_Q1NETiBASmF2YUVkZ2Uu,size_16,color_FFFFFF,t_70,g_se,x_16)
-
-引用 map 中的节点。
-map 中的节点可处于如下状态：
-有效的：
-- Live：设置了有效的键/值
-- Loading：加载中
-
-无效的：
-- Expired：时间已过（键/值仍可设置）
-- Collected：键/值已部分收集，但尚未清理
-- Unset：标记为未设置，等待清理或重用
-
-### ValueReference
-Value的引用
-![](https://img-blog.csdnimg.cn/96d7ae6da2c34356848e23e801ccd85f.png)
-
-之所以用Reference命令，是因为Cache要支持：
-- WeakReference K
-- SoftReference、WeakReference V
-
-
-**ValueReference** 因为Cache支持强引用的Value、SoftReference Value以及WeakReference Value，因而它对应三个实现类：StrongValueReference、SoftValueReference、WeakValueReference。
-为了支持动态加载机制，它还有一个LoadingValueReference，在需要动态加载一个key的值时，先把该值封装在LoadingValueReference中，以表达该key对应的值已经在加载了，如果其他线程也要查询该key对应的值，就能得到该引用，并且等待改值加载完成，从而保证该值只被加载一次，在该值加载完成后，将LoadingValueReference替换成其他ValueReference类型。ValueReference对象中会保留对ReferenceEntry的引用，这是因为在Value因为WeakReference、SoftReference被回收时，需要使用其key将对应的项从Segment的table中移除。
-
-### Queue
-在一个Segment中，所有ReferenceEntry还组成：
-- access链（accessQueue）
-- write链（writeQueue）
-
-为了实现LRU，Guava Cache在Segment中添加的这两条链，都是双向链表，通过ReferenceEntry中的如下链接而成：
-- `previousInWriteQueue`
-- `nextInWriteQueue`
-- `previousInAccessQueue`
-- `nextInAccessQueue`
-#### WriteQueue
-![](https://img-blog.csdnimg.cn/e8080405487b4cab8a7898dce01d9486.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_Q1NETiBASmF2YUVkZ2Uu,size_13,color_FFFFFF,t_70,g_se,x_16)
-#### AccessQueue
-![](https://img-blog.csdnimg.cn/c8127cb7a26348da8490fab748749bf4.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_Q1NETiBASmF2YUVkZ2Uu,size_13,color_FFFFFF,t_70,g_se,x_16)
-
-WriteQueue和AccessQueue都自定义了offer、peek、remove、poll等操作：
-![](https://img-blog.csdnimg.cn/1cec317938ad4ca0ad54ee1e825eb391.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_Q1NETiBASmF2YUVkZ2Uu,size_10,color_FFFFFF,t_70,g_se,x_16)
-offer操作，若是：
-- 新增节点
-直接加到该链的结尾
-- 已存在的节点
-将该节点链接的链尾
-
-remove操作：
-直接从该链中移除该节点
-
-poll操作：
-将头节点的下一个节点移除，并返回。
-
-## 缓存相关操作
-## Segment的evict清除策略
-在每次调用操作的开始和结束时触发清理工作，这样比一般的缓存另起线程监控清理相比，可减少开销。
-但若长时间没有调用方法，会导致不能及时清理释放内存空间。
-
-evict主要处理四个Queue：
-- keyReferenceQueue
-- valueReferenceQueue
-- writeQueue
-- accessQueue
-
-前两个queue是因为WeakReference、SoftReference被垃圾回收时加入的，清理时只需遍历整个queue，将对应项从LocalCache移除即可：
-- keyReferenceQueue存放ReferenceEntry
-- valueReferenceQueue存放的是ValueReference
-
-要从Cache中移除需要有key，因而ValueReference需要有对ReferenceEntry的引用。
-
-后两个Queue，只需检查是否配置了相应的expire时间，然后从头开始查找已经expire的Entry，将它们移除。
-
-Segment中的put操作：put操作相对比较简单，首先它需要获得锁，然后尝试做一些清理工作，接下来的逻辑类似ConcurrentHashMap中的rehash，查找位置并注入数据。需要说明的是当找到一个已存在的Entry时，需要先判断当前的ValueRefernece中的值事实上已经被回收了，因为它们可以是WeakReference、SoftReference类型，如果已经被回收了，则将新值写入。并且在每次更新时注册当前操作引起的移除事件，指定相应的原因：COLLECTED、REPLACED等，这些注册的事件在退出的时候统一调用Cache注册的RemovalListener，由于事件处理可能会有很长时间，因而这里将事件处理的逻辑在退出锁以后才做。最后，在更新已存在的Entry结束后都尝试着将那些已经expire的Entry移除。另外put操作中还需要更新writeQueue和accessQueue的语义正确性。
-
-Segment带CacheLoader的get操作
-```java
-V get(K key, int hash, CacheLoader<? super K, V> loader) throws ExecutionException {
-      checkNotNull(key);
-      checkNotNull(loader);
-      try {
-        if (count != 0) { // read-volatile
-          // don't call getLiveEntry, which would ignore loading values
-          ReferenceEntry<K, V> e = getEntry(key, hash);
-          if (e != null) {
-            long now = map.ticker.read();
-            V value = getLiveValue(e, now);
-            if (value != null) {
-              recordRead(e, now);
-              statsCounter.recordHits(1);
-              return scheduleRefresh(e, key, hash, value, now, loader);
-            }
-            ValueReference<K, V> valueReference = e.getValueReference();
-            if (valueReference.isLoading()) {
-              return waitForLoadingValue(e, key, valueReference);
-            }
-          }
-        }
-
-        // at this point e is either null or expired;
-        return lockedGetOrLoad(key, hash, loader);
-      } catch (ExecutionException ee) {
-        Throwable cause = ee.getCause();
-        if (cause instanceof Error) {
-          throw new ExecutionError((Error) cause);
-        } else if (cause instanceof RuntimeException) {
-          throw new UncheckedExecutionException(cause);
-        }
-        throw ee;
-      } finally {
-        postReadCleanup();
-      }
-    }
-```
-1\. 先查找table中是否已存在没有被回收、也没有expire的entry，如果找到，并在CacheBuilder中配置了refreshAfterWrite，并且当前时间间隔已经操作这个事件，则重新加载值，否则，直接返回原有的值
-2\. 如果查找到的ValueReference是LoadingValueReference，则等待该LoadingValueReference加载结束，并返回加载的值
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTA3ZTRkNDY4NmQzMTJjMWIucG5n?x-oss-process=image/format,png)
-3\. 如果没有找到entry，或者找到的entry的值为null，则加锁后，继续在table中查找已存在key对应的entry，如果找到并且对应的entry.isLoading()为true，则表示有另一个线程正在加载，因而等待那个线程加载完成，如果找到一个非null值，返回该值，否则创建一个LoadingValueReference
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTRlNDgyZjczMjRiMjdkZGEucG5n?x-oss-process=image/format,png)
-并调用loadSync加载相应的值
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQwOGM0ODUzYmExYmE1MGQucG5n?x-oss-process=image/format,png)
-在加载完成后，将新加载的值更新到table中，即大部分情况下替换原来的LoadingValueReference
-# CacheBuilder
-提供Builder模式的CacheBuilder生成器来创建缓存。
-各个缓存参数的配置设置，类似函数式编程，可自行设置各类参数选型。
-
-```java
-Cache<String,String> cache = CacheBuilder.newBuilder() 		
-	.maximumSize(1024)
-	.expireAfterWrite(60,TimeUnit.SECONDS)
-	.weakValues() .build(); 
-
-cache.put("word","Hello Guava Cache"); 
-System.out.println(cache.getIfPresent("word"));
-```
-
-它提供三种方式加载到缓存：
-1.  在构建缓存的时候，使用build方法内部调用CacheLoader方法加载数据；
-2.  callable 、callback方式加载数据；
-3. 直接Cache.put 加载数据，但自动加载是首选的，因为它更容易推断所有缓存内容的一致性
-
-build生成器的两种方式都实现了一种逻辑：
-`从缓存中取key的值，如果该值已经缓存过了则返回缓存中的值，如果没有缓存过可以通过某个方法来获取这个值`。
-
-### CacheLoader
-针对整个cache定义的，可认为是统一的根据K值load V的方法：
-```java
-/**
- * CacheLoader
- */
-public void loadingCache() {
-    LoadingCache<String, String> graphs = CacheBuilder.newBuilder()
-            .maximumSize(1000)
-            .build(new CacheLoader<String, String>() {
-                @Override
-                public String load(String key) {
-                    if ("key".equals(key)) {
-                        return "key return result";
-                    } else {
-                        return "get-if-absent-compute";
-                    }
-                }
-            });
-    String resultVal = null;
-    try {
-        resultVal = graphs.get("key");
-    } catch (ExecutionException e) {
-        e.printStackTrace();
-    }
-}
-```
-### callable
-较为灵活，允许你在get的时候指定load方法
-
-```java
-/**
- *
- * Callable
-*/
-public void callablex() throws ExecutionException
- {
-   Cache<String, String> cache = CacheBuilder.newBuilder()
-     .maximumSize(1000).build();
-   String result = cache.get("key", new Callable<String>()
-    {
-      public String call()
-      {
-       return "result";
-      }
-    });
-  System.out.println(result);
- }
-```
-# 总结
-Guava Cache基于ConcurrentHashMap的设计，在高并发场景支持和线程安全上都有相应改进策略，使用Reference引用命令，提升高并发下的数据访问速度并保持了GC的可回收，有效节省空间。
-
-write链和access链的设计，能更灵活、高效的实现多种类型的缓存清理策略，包括基于容量的清理、基于时间的清理、基于引用的清理等。
-
-编程式的build生成器管理，让使用者有更多的自由度，能够根据不同场景设置合适的模式。
-
-还可以显式清除、统计信息、移除事件的监听器、自动加载等功能。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/Redis\347\274\223\345\255\230\350\256\276\350\256\241\345\216\237\347\220\206.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/Redis\347\274\223\345\255\230\350\256\276\350\256\241\345\216\237\347\220\206.md"
deleted file mode 100644
index dc27356a65..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/Redis\347\274\223\345\255\230\350\256\276\350\256\241\345\216\237\347\220\206.md"
+++ /dev/null
@@ -1,95 +0,0 @@
-### 3.2.2 Redis缓存（最佳）
-Redis是一个远程内存数据库（非关系型数据库），性能强劲，具有复制特性以及解决问题而生的独一无二的数据模型
-
-- Redis数据模型图
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNjA5YjVlMGRiMjJiYjk5NC5wbmc?x-oss-process=image/format,png)
-Redis内部使用一个redisObject对象来标识所有的key和value数据，redisObject最主要的信息:
-- type代表一个value对象具体是何种数据类型
-- encoding是不同数据类型在Redis内部的存储方式
-比如——type=string代表value存储的是一个普通字符串，那么对应的encoding可以是raw或是int，如果是int则代表Redis内部是按数值类型存储和表示这个字符串。
-
-raw列为对象的编码方式
-- 字符串可以被编码为raw（一般字符串）或Rint（为了节约内存，Redis会将字符串表示的64位有符号整数编码为整数来进行储存）
-- 列表可以被编码为ziplist或linkedlist，ziplist是为节约大小较小的列表空间而作的特殊表示
-- 集合可以被编码为intset或者hashtable，intset是只储存数字的小集合的特殊表示
-- hash表可以编码为zipmap或者hashtable，zipmap是小hash表的特殊表示
-- 有序集合可以被编码为ziplist或者skiplist格式
-  - ziplist用于表示小的有序集合
-  - skiplist则用于表示任何大小的有序集合
-
-从网络I/O模型上看，Redis使用单线程的I/O复用模型，自己封装了一个简单的AeEvent事件处理框架，主要实现了epoll、kqueue和select。对于单纯只有I/O操作来说，单线程可以将速度优势发挥到最大，但是Redis也提供了一些简单的计算功能，比如排序、聚合等，对于这些操作，单线程模型实际会严重影响整体吞吐量，CPU计算过程中，整个I/O调度都是被阻塞住的，在这些特殊场景的使用中，需要额外的考虑
-相较于memcached的预分配内存管理，Redis使用`现场申请内存`的方式来存储数据，并且很少使用free-list等方式来优化内存分配，会在`一定程度上存在内存碎片`
-Redis跟据存储命令参数，会把带过期时间的数据单独存放在一起，并把它们称为临时数据，非临时数据是永远不会被剔除的，即便物理内存不够，导致swap也不会剔除任何非临时数据（但会尝试剔除部分临时数据）
-
-Redis一共支持四种持久化方式，主要使用的两种：
-1.  **定时快照方式(snapshot)**
-该持久化方式实际是在Redis内部一个定时器事件，每隔固定时间去检查当前数据发生的改变次数与时间是否满足配置的持久化触发的条件，如果满足则通过操作系统fork调用来创建出一个子进程，这个子进程默认会与父进程共享相同的地址空间，这时就可以通过子进程来遍历整个内存来进行存储操作，而主进程则仍然可以提供服务，当有写入时由操作系统按照内存页（page）为单位来进行copy-on-write保证父子进程之间不会互相影响。它的缺点是快照只是代表一段时间内的内存映像，所以系统重启会丢失上次快照与重启之间所有的数据。
-2.  **基于语句追加文件的方式(aof)**
-aof方式实际类似MySQl的基于语句的binlog方式，即每条会使Redis内存数据发生改变的命令都会追加到一个log文件中，也就是说这个log文件就是Redis的持久化数据。
-
-aof的方式的主要缺点是追加log文件可能导致体积过大，当系统重启恢复数据时如果是aof的方式则加载数据会非常慢，几十G的数据可能需要几小时才能加载完，当然这个耗时并不是因为磁盘文件读取速度慢，而是由于读取的所有命令都要在内存中执行一遍。另外由于每条命令都要写log，所以使用aof的方式，Redis的读写性能也会有所下降。
-
-Redis的持久化使用了Buffer I/O，对持久化文件的写入和读取操作都会使用物理内存的Page Cache，而大多数数据库系统会使用Direct I/O来绕过这层Page Cache并自行维护一个数据的Cache
-而当Redis的持久化文件过大，并对其进行读写时，磁盘文件中的数据都会被加载到物理内存中作为操作系统对该文件的一层Cache，而这层Cache的数据与Redis内存中管理的数据实际是重复存储的
-虽然内核在物理内存紧张时会做Page Cache的剔除工作，但内核很可能认为某块Page Cache更重要，而让你的进程开始Swap，这时你的系统就会开始出现不稳定或者崩溃了，因此在持久化配置后，针对内存使用需要实时监控观察
-
-与memcached客户端支持分布式方案不同，Redis更倾向于在服务端构建分布式存储
-- Redis分布式集群图1
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNzY4ZGQ1YTE1NjRjNGVkZi5wbmc?x-oss-process=image/format,png)
-- Redis分布式集群
-![](https://img-blog.csdnimg.cn/20210204121340713.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-Redis Cluster是一个实现了分布式且允许单点故障的Redis高级版本，它没有中心节点，具有线性可伸缩的功能。其中节点与节点之间通过二进制协议进行通信，节点与客户端之间通过ascii协议进行通信。
-在数据的放置策略上，Redis Cluster将整个key的数值域分成4096个hash槽，每个节点上可以存储一个或多个hash槽，也就是说当前Redis Cluster支持的最大节点数就是4096
-Redis Cluster使用的分布式算法也很简单：crc16( key ) % HASH_SLOTS_NUMBER
-整体设计可总结为：
-*   数据hash分布在不同的Redis节点实例上
-*   M/S的切换采用Sentinel
-*   写：只会写master Instance，从sentinel获取当前的master Instance
-*   读：从Redis Node中基于权重选取一个Redis Instance读取，失败/超时则轮询其他Instance；Redis本身就很好的支持读写分离，在单进程的I/O场景下，可以有效的避免主库的阻塞风险
-*   通过RPC服务访问，RPC server端封装了Redis客户端，客户端基于Jedis开发
-
-在数据一致性问题上，`  Redis没有提供CAS操作命令来保障高并发场景下的数据一致性问题，不过它却提供了事务的功能  `
-Redis的Transactions提供的并不是严格的ACID的事务（比如一串用EXEC提交执行的命令，在执行中服务器宕机，那么会有一部分命令执行了，剩下的没执行）。
-但是这个Transactions还是提供了基本的命令打包执行的功能（在服务器不出问题的情况下，可以保证一连串的命令是顺序在一起执行的，中间有会有其它客户端命令插进来执行）
-Redis还提供了一个Watch功能，你可以对一个key进行Watch，然后再执行Transactions，在这过程中，如果这个Watched的值进行了修改，那么这个Transactions会发现并拒绝执行
-
-在失效策略上，Redis支持多达6种的数据淘汰策略
-1.  volatile-lru：从已设置过期时间的数据集（server.db[i].expires）中挑选最近最少使用的数据淘汰
-2.  volatile-ttl：从已设置过期时间的数据集（server.db[i].expires）中挑选将要过期的数据淘汰
-3.  volatile-random：从已设置过期时间的数据集（server.db[i].expires）中任意选择数据淘汰 
-4.  allkeys-lru：从数据集（server.db[i].dict）中挑选最近最少使用的数据淘汰；
-5.  allkeys-random：从数据集（server.db[i].dict）中任意选择数据淘汰；
-6.  no-enviction（驱逐）：禁止驱逐数据。
-
-**以下多种Web应用场景，可以充分的利用Redis的特性，大大提高效率**
-- 在主页中显示最新的项目列表
-Redis使用的是常驻内存的缓存，速度非常快
-    - LPUSH用来插入一个内容ID，作为关键字存储在列表头部
-    - LTRIM用来限制列表中的项目数最多为5000
-如果用户需要的检索的数据量超越这个缓存容量，这时才需要把请求发送到数据库
-- 删除和过滤
-如果一篇文章被删除，可以使用LREM从缓存中彻底清除掉
-- 排行榜及相关问题
-排行榜（leader board）按照得分进行排序
-    - ZADD命令可以直接实现这个功能
-    - ZREVRANGE命令可以用来按照得分来获取前100名的用户
-    - ZRANK可以用来获取用户排名，非常直接而且操作容易
-
-- 按照用户投票和时间排序
-排行榜，得分会随着时间变化。
-LPUSH和LTRIM命令结合运用，把文章添加到一个列表中
-一项后台任务用来获取列表，并重新计算列表的排序，ZADD命令用来按照新的顺序填充生成列表。列表可以实现非常快速的检索，即使是负载很重的站点。
-- 过期处理
-使用Unix时间作为关键字，用来保持列表能够按时间排序。对current_time和time_to_live进行检索，完成查找过期项目的艰巨任务。另一项后台任务使用ZRANGE…WITHSCORES进行查询，删除过期的条目。
-- 计数
-进行各种数据统计的用途是非常广泛的，比如想知道什么时候封锁一个IP地址
-INCRBY命令让这些变得很容易，通过原子递增保持计数
-GETSET用来重置计数器
-过期属性用来确认一个关键字什么时候应该删除
-- 特定时间内的特定项目
-这是特定访问者的问题，可以通过给每次页面浏览使用SADD命令来解决
-SADD不会将已经存在的成员添加到一个集合。
-- Pub/Sub
-在更新中保持用户对数据的映射是系统中的一个普遍任务。Redis的pub/sub功能使用了SUBSCRIBE、UNSUBSCRIBE和PUBLISH命令，让这个变得更加容易。
--  队列
-在当前的编程中队列随处可见。除了push和pop类型的命令之外，Redis还有阻塞队列的命令，能够让一个程序在执行时被另一个程序添加到队列。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/Spring\346\263\250\350\247\243\347\274\223\345\255\230\350\256\276\350\256\241\345\216\237\347\220\206\345\217\212\345\256\236\346\210\230.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/Spring\346\263\250\350\247\243\347\274\223\345\255\230\350\256\276\350\256\241\345\216\237\347\220\206\345\217\212\345\256\236\346\210\230.md"
deleted file mode 100644
index f6b96f7270..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/Spring\346\263\250\350\247\243\347\274\223\345\255\230\350\256\276\350\256\241\345\216\237\347\220\206\345\217\212\345\256\236\346\210\230.md"
+++ /dev/null
@@ -1,70 +0,0 @@
-Spring 3.1后引入注解缓存，其本质不是一个具体的缓存实现，而是一个对缓存使用的抽象。
-通过在既有代码中添加少量自定义注解，即可够达到使用缓存对象和缓存方法的返回对象的效果。
-Spring的缓存技术具备相当的灵活性，不仅能够使用SpEL来定义缓存的key和各种condition，还提供开箱即用的缓存临时存储方案，也支持和主流的专业缓存集成。
-
-# 特点
-- 基于注解和AOP，使用方便
-- 开箱即用，不用安装和部署额外的第三方组件即可使用缓存
-- 可以配置Condition和SPEL，能使用对象的任何属性或者方法来定义缓存的key和使用规则条件
-- 支持自定义key和自定义缓存管理者，具有相当的灵活性和可扩展性
-- 绕过Spring的话，注解无效
-
-Spring Cache的关键原理就是Spring AOP，通过Spring AOP实现了在方法调用前、调用后获取方法的入参和返回值，进而实现了缓存的逻辑。而Spring Cache利用了Spring AOP的动态代理技术，即当客户端尝试调用pojo的foo()方法的时候，给它的不是pojo自身的引用，而是一个动态生成的代理类
-- Spring动态代理调用图
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZDNlNjRlYjVjOTNlYmMwYy5wbmc?x-oss-process=image/format,png)
-如上图所示，实际客户端获取的是一个代理的引用，在调用foo()方法的时候，会首先调用proxy的foo()方法，这个时候proxy可以整体控制实际的pojo.foo()方法的入参和返回值，比如缓存结果，比如直接略过执行实际的foo()方法等，都是可以轻松做到的。
-
-Spring Cache主要使用如下注解
-- @Cacheable
-- @CachePut
-- @CacheEvict
-
-主要针对方法上注解使用，部分场景也可类上注解。当在类上使用时，该类所有方法都将受影响。
-
-作用和配置方法表：
-
-| 标签类型 | 作用 | 主要配置参数说明 |
-| --- | --- | --- |
-| @Cacheable | 主要针对方法配置，能够根据方法的请求参数对其结果进行缓存 | **value**：缓存的名称，在 Spring 配置文件中定义，必须指定至少一个<br></br> **key**：缓存的 key，可为null，若指定要按 SpEL 表达式编写，若不指定，则默认按方法的所有参数进行组合 <br></br> **condition**：缓存的条件，可为null，使用 SpEL 编写，返回 true 或者 false，为 true 时才进行缓存 |
-| @CachePut | 主要针对方法配置，能够根据方法的请求参数对其结果进行缓存，和 @Cacheable 不同的是，它每次都会触发真实方法的调用 | 同上 |
-| @CacheEvict | 主要针对方法配置，能够根据一定的条件对缓存进行清空 | 同上。 <br></br>**allEntries**：是否清空所有缓存内容，默认为 false，如果指定为 true，则方法调用后将立即清空所有缓存； <br></br>**beforeInvocation**：是否在方法执行前就清空，默认为 false，如果指定为 true，则在方法还没有执行的时候就清空缓存，默认情况下，如果方法执行抛出异常，则不会清空缓存 |
-
-### 可扩展
-Spring注解能满足一般应用对缓存的需求，但随着应用服务的复杂化，大并发高可用性能要求下，需要进行一定的扩展，这时对其自身集成的缓存方案可能不太适用，该怎么办？
-
-
-
-这能满足一般应用的缓存需求，但当用户量上去或性能跟不上，总需要进行扩展，这时你或许对其提供的内存缓存不满意了，因为其不支持高可用，也不持久化。这时就需要自定义你缓存方案了，Spring也想到了这点。
-
-先不考虑如何持久化缓存，毕竟这种三方实现很多，要考虑的是，怎么利用Spring提供的扩展点实现我们自己的缓存，且在不改原来已有代码的情况下进行扩展，是否在方法执行前就清空，默认为false，如果指定为true，则在方法还没有执行的时候就清空缓存，默认情况下，如果方法执行抛出异常，则不会清空缓存。
-三步：
-- 提供一个CacheManager接口的实现（继承AbstractCacheManager），管理自身的cache实例
-- 实现自己的cache实例MyCache(继承至Cache)，在这里面引入我们需要的第三方cache或自定义cache
-- 对配置项进行声明，将MyCache实例注入CacheManager进行统一管理。
-
-# 自定义注解缓存
-注解缓存的使用，可有效增强代码可读性，同时统一管理缓存，提供较好的可扩展性。
-为此，酒店商家端在Spring注解缓存基础上，自定义了适合自身业务特性的注解缓存。
-主要使用两个标签，即**@HotelCacheable**、**@HotelCacheEvict**，其作用和配置方法见下表：
-| 标签类型 | 作用 | 主要配置参数说明 |
-| --- | --- | --- |
-| @HotelCacheable | 主要针对方法配置，能够根据方法的请求参数对其结果进行缓存 | **domain**作用域，针对集合场景，解决批量更新问题； **domainKey**作用域对应的缓存key； **key**缓存对象key 前缀； **fieldKey**缓存对象key，与前缀合并生成对象key； **condition**缓存获取前置条件，支持spel语法； **cacheCondition**缓存刷入前置条件，支持spel语法； **expireTime**超时时间设置 |
-| @HotelCacheEvict | 主要针对方法配置，能够根据一定的条件对缓存进行清空 | **同上** |
-
-增加作用域的概念，解决商家信息变更下，多重重要信息实时更新的问题。
-- 域缓存处理图
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMDBkZWVmMzBkNWRjNTY5OS5wbmc?x-oss-process=image/format,png)
-如上图，按旧方案，当cache0发送变化时，为保持信息的实时更新，需手动删除cache1、cache2、cache3的缓存。
-`增加域缓存概念`，cache0、cache1、cache2、cache3是以账号ID为基础，相互存在影响约束的集合体，我们作为一个域集合，增加域缓存处理，当cache0发送变化时，整体的账号ID domain域已发生更新，自动影响cache1、cache2、cache3等处的缓存数据。将相关联逻辑缓存统一化，有效提升代码可读性，同时更好服务业务，账号重点信息能够实时变更刷新，相关服务响应速度提升。
-
-增加cacheCondition缓存刷入前置判断，有效解决商家业务多重外部依赖场景下，业务降级有损服务下，业务数据一致性保证，不因为缓存的增加影响业务的准确性；自定义CacheManager缓存管理器，可以有效兼容公共基础组件Medis、Cellar相关服务，在对应用程序不做改动的情况下，有效切换缓存方式；同时，统一的缓存服务AOP入口，结合接入Mtconfig统一配置管理，对应用内缓存做好降级准备，一键关闭缓存。
-
-Spring Cache的原理是基于动态生成的proxy代理机制来进行切面处理，关键点是对象的引用问题，如果对象的方法是类里面的内部调用（this引用）而不是外部引用的场景下，会导致proxy失败，那么我们所做的缓存切面处理也就失效了。因此，应避免已注解缓存的方法在类里面的内部调用。
-
-使用的key约束，缓存的key应尽量使用简单的可区别的元素，如ID、名称等，不能使用list等容器的值，或者使用整体model对象的值。非public方法无法使用注解缓存实现。
-
-# 总结
-注解驱动的Spring Cache能够极大的减少我们编写常见缓存的代码量，通过少量的注释标签和配置文件，即可达到使代码具备缓存的能力，且具备很好的灵活性和扩展性。但是我们也应该看到，Spring Cache由于基于Spring AOP技术，尤其是动态的proxy技术，导致其不能很好的支持方法的内部调用或者非public方法的缓存设置，当然这些都是可以解决的问题。
-
-参考
-- https://developer.ibm.com/zh/articles/os-cn-spring-cache/
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230\350\256\276\350\256\241\346\250\241\345\274\217.md"
deleted file mode 100644
index 5aca6258d0..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230\350\256\276\350\256\241\346\250\241\345\274\217.md"
+++ /dev/null
@@ -1,70 +0,0 @@
-很多同学更新缓存时，**先删除缓存，再更新数据库**，而后续操作会把数据再装载到缓存。
-
-**这个逻辑是错误的**。最简单的两个并发操作：更新&查询。
-更新操作删除缓存后，查询操作没有命中缓存，先把老数据读出来后放到缓存，然后更新操作更新了数据库。于是，缓存中的数据还是老数据，导致缓存中的数据是脏的，而且还一直这样脏下去。所以特此总结一下几个缓存更新的Design Pattern。
-
-先不讨论更新缓存和更新数据这两个事是一个事务的事，或是会有失败的可能，先假设更新数据库和更新缓存都可以成功的情况。
-
-# 1  Cache Aside（旁路缓存）
-最常用的模式：
-- 失效
-应用先从cache取数据，没有得到，则从DB取数据，成功后，放入cache
-- 命中
-应用程序从cache中取数据，取到后返回
-- 更新
-先把数据存到DB，成功后，再让缓存失效
-![Cache-Aside-Design-Pattern-Flow-Diagram](https://img-blog.csdnimg.cn/img_convert/a583a805ba22360ab0e5dd257ef37b1c.png)
-![Updating Data using the Cache-Aside Pattern - Flow Diagram](https://img-blog.csdnimg.cn/img_convert/897b76d9ef2bec14e800c75753653d3a.png)
-
-注意，是先更新DB，成功后，让缓存失效。
-
-一个查询操作，一个更新操作的并发
-首先，没有了删除cache数据的操作，而是先更新数据库中的数据，此时，缓存依然有效，所以，并发的查询操作拿的是没有更新的数据，但是，更新操作马上让缓存的失效了，后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题，后续的查询操作一直都在取老的数据。
-
-这是标准的design pattern，包括Facebook的论文《[Scaling Memcache at Facebook](https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf)》也使用了这个策略。为什么不是写DB后更新缓存？可以看一下Quora上的这个问答《[Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?](https://www.quora.com/Why-does-Facebook-use-delete-to-remove-the-key-value-pair-in-Memcached-instead-of-updating-the-Memcached-during-write-request-to-the-backend)》，主要是怕两个并发的写操作导致脏数据。
-
-那Cache Aside有并发问题吗？
-有。比如，一个是`读操作`，但是没有命中缓存，然后就到数据库中取数据，此时来了一个`写操作`，写完数据库后，让缓存失效，然后，之前的那个读操作再把老的数据放进去，所以，会造成脏数据。
-
-这个情形理论上会出现，不过，实际上出现的概率可能非常低，因为需要发生在读缓存时缓存失效，而且并发着有一个写操作。
-而实际上数据库的写操作会比读操作慢得多，而且还要锁表，而读操作必需在写操作前进入数据库操作，而又要晚于写操作更新缓存，所有的这些条件都具备的概率基本并不大
-
-这也就是Quora上的那个答案里说的，要么通过2PC或是Paxos协议保证一致性，要么就是拼命的降低并发时脏数据的概率，而Facebook使用了这个降低概率的玩法，因为2PC太慢，而Paxos太复杂。当然，最好还是为缓存设置上过期时间。
-# 2 Read/Write Through Pattern
-上面的Cache Aside，应用代码需要维护两个数据存储，一个是缓存，一个是数据库,应用程序比较啰嗦。
-而`Read/Write Through`是把更新数据库的操作由缓存自己代理，所以，对于应用层来说，就简单很多。
-可理解为，应用认为后端就是一个单一的存储，而存储自己维护自己的Cache。
-## 2.1  Read Through
-Read Through 就是在查询操作中更新缓存，也就是说，当缓存失效的时候（过期或LRU换出）
-- Cache Aside是由`调用方负责`把数据加载入缓存
-- Read Through则用`缓存服务`自己来加载，从而对应用方是透明的
-## 2.2  Write Through
-和Read Through相仿，不过是在更新数据时发生
-当有数据更新时
-- 如果没有命中缓存，直接更新数据库，然后返回
-- 如果命中了缓存，则更新缓存，然后再由Cache自己更新数据库（这是一个同步操作）
-
-下图中的Memory可以理解为就是我们例子里的数据库
-![A write-through cache with no-write allocation](https://img-blog.csdnimg.cn/img_convert/642013cf5ea3b5ec2859a4c0feabccd6.png)
-# 3 Write Behind（异步写回）
-又叫 Write Back。在更新数据时，只更新缓存，不更新DB，而我们的缓存会异步批量更新DB
-
-## 优点
-- 让数据的I/O操作飞快无比（因为直接操作内存嘛 ）
-- 因为异步，write back还可以合并对同一个数据的多次操作，所以性能的提高是相当可观
-
-## 缺点
-数据不是强一致性的，而且可能会丢失（我们知道Unix/Linux非正常关机会导致数据丢失，就是因为这个事）。
-
-另外，Write Back实现逻辑比较复杂，因为他需要track哪些数据是被更新的，需要刷到持久层。
-os的write back会在仅当这个cache需要失效时，才会被真正持久化，比如，内存不够了，或是进程退出了等情况，这又叫lazy write。
-
-比如在向磁盘中写数据时采用的也是这种策略。无论是：
-- os层面的 Page Cache
-- 日志的异步刷盘
-- 消息队列中消息的异步写入磁盘
-
-大多采用了这种策略。因为这个策略在性能优势明显，直接写内存，避免了直接写磁盘造成的随机写。
-
-- A write-back cache with write allocation
-![](https://img-blog.csdnimg.cn/img_convert/3210481d0a1230f6d75eb45058776f43.png)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\346\234\254\345\234\260\347\274\223\345\255\230\343\200\201\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\346\234\254\345\234\260\347\274\223\345\255\230\343\200\201\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md"
deleted file mode 100644
index 40d4e45e7f..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\346\234\254\345\234\260\347\274\223\345\255\230\343\200\201\345\210\206\345\270\203\345\274\217\347\274\223\345\255\230.md"
+++ /dev/null
@@ -1,233 +0,0 @@
-- 互联网应用常规流程
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZWM2NDgzYzdlYmM5OGNkNi5wbmc?x-oss-process=image/format,png)
-
-缓存可使用在1～4的各个环节中，每个环节的缓存方案与使用各有特点。
-# 1 缓存特征
-## 数据分类
-按使用频率和方式：
-- 静态数据
-一般不变，类似于字典表 
-- 准静态数据
-变化频率很低，部门结构设置，全国行政区划数据等 
-- 中间状态数据
-一些计算的可复用中间数据，变量副本，配置中心的本地副本 
-
-- 热数据
-使用频率高 
-- 读写比较大
-读的频率 >> 写的频率 
-
-这些数据适合于使用缓存的方式访问
-- 广义，为了加速数据处理，让业务更快访问的**临时存放**的**冗余数据**
-- 狭义，在分布式系统里把缓存到内存的数据，也就是 Redis
-缓存是一个数据模型对象，有它的一些特征
-## 1.1 命中率
-`命中率=返回正确结果数/请求缓存次数`
-命中率问题是缓存中的一个非常重要的问题，它是衡量缓存有效性的重要指标。命中率越高，表明缓存的使用率越高。
-## 1.2 最大元素（或最大空间）
-缓存中可以存放的最大元素的数量，一旦缓存中元素数量超过这个值（或者缓存数据所占空间超过其最大支持空间）
-那么将会触发`缓存启动清空策略`,根据不同的场景合理的设置最大元素值往往可以一定程度上提高缓存的命中率，从而更有效的使用缓存
-## 1.3 清除策略
-缓存的存储空间有限制，当缓存空间被用满时，如何保证在稳定服务的同时有效提升命中率？
-这就由缓存清空策略来处理
-常见的一般策略有：
-
-- FIFO(first in first out)
-最先进入缓存的数据在缓存空间不够的情况下（超出最大元素限制）会被优先被清除掉，以腾出新的空间接受新的数据
-策略算法主要比较缓存元素的创建时间。在`数据实效性要求场景下`可选择该类策略，优先保障最新数据可用
-
-- LFU(less frequently used)
-无论是否过期，根据元素的被使用次数判断，清除使用次数较少的元素释放空间
-策略算法主要比较元素的hitCount（命中次数）。在`保证高频数据有效性场景下`，可选择这类策略
-
-- LRU(least recently used)
-无论是否过期，根据元素最后一次被使用的时间戳，清除最远使用时间戳的元素释放空间
-策略算法主要比较元素最近一次被get使用时间。在`热点数据场景下较适用`，优先保证热点数据的有效性
-
-除此之外，还有一些简单策略比如：
-- 根据过期时间判断，清理过期时间最长的元素
-- 根据过期时间判断，清理最近要过期的元素
-- 随机清理
-- 根据关键字（或元素内容）长短清理等
-
-## 缓存使用不当导致的问题
-- 系统预热导致启动慢
-一个系统启动需要预热半个小时。 导致系统不能做到快速应对故障宕机等问题。 
-- 系统内存资源耗尽
-只加入数据，不能清理旧数据。 旧数据处理不及时，或者不能有效识别无用数据。
-
-# 2 缓存概述
-## 2.1 缓存本质
-系统各级处理速度不匹配，导致利用空间换时间。缓存是提升系统性能的一个简单有效的办法。
-
-## 2.2 缓存加载时机
-### 启动全量加载
-全局有效，使用简单，但导致启动慢。
-### 懒加载
-#### 同步使用加载
-- 先看缓存是否有数据，没有则从数据库读取
-- 读取的数据，先放入
-
-#### 延迟异步加载
-从缓存获取数据，不管是否为空直接返回，有如下策略：
-- 异步
-如果为空，则发起一个异步加载的线程，负责加载数据 
-- 解耦
-异步线程负责维护缓存的数据，定期或根据条件触发更新
-
-## 2.3 缓存的有效性
-- 变动频、一致性要求高的数据，不适合用缓存
-变化大，意味着内存缓存数据和原始数据库数据之间一直有差异；
-一致性要求高，意味着只有使用原始数据，甚至加了事务，才稳妥。
-
-### 缓存的有效性度量
-- 读写比
-对数据的写操作导致数据变动，意味着维护成本。**N :1**。
-- 命中率
-命中缓存意味着缓存数据被使用，意味着有价值。**90%+**
-
-> 计算机科学只存在两个难题：缓存失效和命名。
-> - Phil Karlton
-
-所以必须综合衡量数据一致性，性能，成本来决定是否引入缓存。
-## 2 缓存介质
-从硬件介质上来看，内存和硬盘
-从技术上，可以分成内存、硬盘文件、数据库
-- 内存
-将缓存存储于内存中是最快的选择，无需额外的I/O开销，但是内存的缺点是没有持久化落地物理磁盘，一旦应用异常break down而重新启动，数据很难或者无法复原
-- 硬盘
-一般来说，很多缓存框架会结合使用内存和硬盘，在内存分配空间满了或是在异常的情况下，可以被动或主动的将内存空间数据持久化到硬盘中，达到释放空间或备份数据的目的。
-- 数据库
-增加缓存的策略的目的之一就是为了减少数据库的I/O压力。现在使用数据库做缓存介质是不是又回到了老问题上了？其实，数据库也有很多种类型，像那些不支持SQL，只是简单的key-value存储结构的特殊数据库（如BerkeleyDB和Redis），响应速度和吞吐量都远远高于我们常用的关系型数据库等。
-# 3 缓存分类和应用场景
-根据缓存与应用的藕合度，分为local cache（本地缓存）和remote cache（分布式缓存）
-- 本地缓存
-在应用中的缓存组件，最大的优点是应用和cache是在同一个进程内部，请求缓存非常快速，没有过多的网络开销等，在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适；同时，它的缺点也是应为缓存跟应用程序耦合，多个应用程序无法直接的共享缓存，各应用或集群的各节点都需要维护自己的单独缓存，对内存是一种浪费。
-- 分布式缓存
-与应用分离的缓存组件或服务，其最大的优点是自身就是一个独立的应用，与本地应用隔离，多个应用可直接的共享缓存
-
-## 3.1 本地缓存
-### 3.1.1  编程直接实现缓存
-有的场景只需简单的缓存数据的功能，无需关注更多存取、清空策略等深入特性，这时直接编程实现缓存最为简单高效。
-
-#### 成员变量或局部变量实现
-```java
-    public void UseLocalCache(){
-     //一个本地的缓存变量
-     Map<String, Object> localCacheStoreMap = new HashMap<String, Object>();
-
-    List<Object> infosList = this.getInfoList();
-    for(Object item:infosList){
-        if(localCacheStoreMap.containsKey(item)){ //缓存命中 使用缓存数据
-            // todo
-        } else { // 缓存未命中  I/O获取数据，结果存入缓存
-            Object valueObject = this.getInfoFromDB();
-            localCacheStoreMap.put(valueObject.toString(), valueObject);
-
-        }
-    }
-}
-//示例
-private List<Object> getInfoList(){
-    return new ArrayList<Object>();
-}
-//示例数据库I/O获取
-private Object getInfoFromDB(){
-    return new Object();
-}
-
-```
-以局部变量map结构缓存部分业务数据，减少频繁的重复数据库I/O操作。缺点仅限于类的自身作用域内，类间无法共享缓存。
-#### 静态变量实现
-最常用的单例实现静态资源缓存
-```java
-   public class CityUtils {
-    private static final HttpClient httpClient = ServerHolder.createClientWithPool();
-    private static Map<Integer, String> cityIdNameMap = new HashMap<Integer, String>();
-    private static Map<Integer, String> districtIdNameMap = new HashMap<Integer, String>();
-
-    static {
-        HttpGet get = new HttpGet("http://gis-in.sankuai.com/api/location/city/all");
-        BaseAuthorizationUtils.generateAuthAndDateHeader(get,
-                BaseAuthorizationUtils.CLIENT_TO_REQUEST_MDC,
-                BaseAuthorizationUtils.SECRET_TO_REQUEST_MDC);
-        try {
-            String resultStr = httpClient.execute(get, new BasicResponseHandler());
-            JSONObject resultJo = new JSONObject(resultStr);
-            JSONArray dataJa = resultJo.getJSONArray("data");
-            for (int i = 0; i < dataJa.length(); i++) {
-                JSONObject itemJo = dataJa.getJSONObject(i);
-                cityIdNameMap.put(itemJo.getInt("id"), itemJo.getString("name"));
-            }
-        } catch (Exception e) {
-            throw new RuntimeException("Init City List Error!", e);
-        }
-    }
-    
-    static {
-        HttpGet get = new HttpGet("http://gis-in.sankuai.com/api/location/district/all");
-        BaseAuthorizationUtils.generateAuthAndDateHeader(get,
-                BaseAuthorizationUtils.CLIENT_TO_REQUEST_MDC,
-                BaseAuthorizationUtils.SECRET_TO_REQUEST_MDC);
-        try {
-            String resultStr = httpClient.execute(get, new BasicResponseHandler());
-            JSONObject resultJo = new JSONObject(resultStr);
-            JSONArray dataJa = resultJo.getJSONArray("data");
-            for (int i = 0; i < dataJa.length(); i++) {
-                JSONObject itemJo = dataJa.getJSONObject(i);
-                districtIdNameMap.put(itemJo.getInt("id"), itemJo.getString("name"));
-            }
-        } catch (Exception e) {
-            throw new RuntimeException("Init District List Error!", e);
-        }
-    }
-
-    public static String getCityName(int cityId) {
-        String name = cityIdNameMap.get(cityId);
-        if (name == null) {
-            name = "未知";
-        }
-        return name;
-    }
-
-    public static String getDistrictName(int districtId) {
-        String name = districtIdNameMap.get(districtId);
-        if (name == null) {
-            name = "未知";
-        }
-        return name;
-    }
-}
-```
-O2O业务中常用的城市基础基本信息判断，通过`静态变量一次获取缓存内存中，减少频繁的I/O读取`。
-`静态变量实现类间可共享，进程内可共享，缓存的实时性稍差`。
-
-缺点：
-- 并发
-- 容量
-- 过期策略
-
-为了解决本地缓存数据的实时性问题，目前大量使用的是结合ZooKeeper的自动发现机制，实时变更本地静态变量缓存：
-
-美团的基础配置组件MtConfig，采用的就是类似原理，使用静态变量缓存，结合ZooKeeper的统一管理，做到自动动态更新缓存。
-
-- Mtconfig实现图
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWFjY2E5ZTE3ZDIzYzZlODEucG5n?x-oss-process=image/format,png)
-
-这类缓存，优点是能直接在heap区内读写，最快也最方便。
-缺点同样是受heap区域影响，缓存的数据量非常有限，同时缓存时间受GC影响。
-主要满足单机场景下的小数据量缓存需求，同时对缓存数据的变更无需太敏感，如一般配置管理、基础静态数据等场景。
-### 3.1.2 Ehcache（使用度较低）
-### 3.1.3 Guava Cache（常用）
-### 3.1.4 Spring Cache（常用）
-### 3.1.5 本地缓存的缺陷
-- 无法在多个集群环境同步。当集群规模增大，缓存的读写放大
-- 在JVM中长期占用内存，如果是堆内存，会影响GC
-- 缓存数据的调度处理，影响执行业务的线程，抢资源。 
-
-所以使用集中处理缓存，单独部署管理缓存。
-这也有缺点：网络请求层数增加；如果故障，缓存全部延迟或失效。不过这仍是主流的缓存方案。
-## 3.2 分布式缓存
-### Memcached缓存
-### Redis 缓存
-实际工程中，对于缓存的应用可以有多种的实战方式，包括侵入式硬编码，抽象服务化应用，以及轻量的注解式使用等。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\347\274\223\345\255\230\344\270\216\346\225\260\346\215\256\345\272\223\344\270\200\350\207\264\346\200\247\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\347\274\223\345\255\230\344\270\216\346\225\260\346\215\256\345\272\223\344\270\200\350\207\264\346\200\247\350\247\243\345\206\263\346\226\271\346\241\210.md"
deleted file mode 100644
index ce4d8e563a..0000000000
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/\347\274\223\345\255\230/\347\274\223\345\255\230\344\270\216\346\225\260\346\215\256\345\272\223\344\270\200\350\207\264\346\200\247\350\247\243\345\206\263\346\226\271\346\241\210.md"
+++ /dev/null
@@ -1,245 +0,0 @@
-只要使用Redis做缓存，就必然存在缓存和DB数据一致性问题。若数据不一致，则业务应用从缓存读取的数据就不是最新数据，可能导致严重错误。比如将商品的库存缓存在Redis，若库存数量不对，则下单时就可能出错，这是不能接受的。
-# 1 什么是缓存和DB的数据一致性
-`一致性`包含如下情况：
-- 缓存有数据
-缓存的数据值需和DB相同
-- 缓存无数据
-DB必须是最新值
-
-不符合这两种情况的，都属于**缓存和DB数据不一致**。
-
-# 2 缓存的读写模式
-根据是否接收写请求，可将缓存分成读写缓存和只读缓存。
-## 2.1 读写缓存
-若要对数据进行**增删改，需要在Cache进行**。
-同时根据采取的写回策略，决定是否同步写回DB：
-### 2.1.1 同步直写
-写缓存时，也同步写数据库，缓存和数据库中的数据一致。
-### 2.1.2 异步写回
-写缓存时不同步写DB，等到数据从缓存中淘汰时，再写回DB。使用这种策略时，若数据还没有写回DB，缓存就发生故障，则此时，DB就没有最新数据了。
-
-所以，对于读写缓存，要想保证缓存和DB数据一致，就要采用`同步直写`。若采用这种策略，就需同时更新缓存和DB。所以，要在业务代码中使用事务，保证缓存和DB更新的原子性，即两者：
-- 要么一起更新
-- 要么都不更新，返回错误信息，进行重试
-
-否则，我们无法实现同步直写。
-
-有些场景下，我们对数据一致性要求不高，比如缓存的是电商商品的非关键属性或短视频的创建或修改时间等，则可以使用`异步写回`。
-## 2.2 只读缓存
-- 新增数据
-直接写DB
-- 删改数据
-删改DB，删除只读缓存中的数据
-
-这样应用后续再访问这些增删改的数据时，由于Cache无数据 =》缓存缺失。
-此时，再从DB把数据读入Cache，这样后续再访问数据时，直接读Cache。
-
-下面我们针对只读缓存，看看具体会遇到哪些问题，又该如何解决。
-
-# 3 新增数据
-数据直接写到DB，不操作Cache。此时，Cache本身无新增数据，而DB是最新值，所以，此时缓存和DB数据一致。
-
-# 4 删改数据
-此时应用既要更新DB，也要删除Cache。这俩操作若无法保证原子性，就可能出现数据不一致。
-
-## 4.1 先删Cache，再更新DB
-![](https://img-blog.csdnimg.cn/20210708153307119.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-## 4.2 先更新DB，再删除Cache
-![](https://img-blog.csdnimg.cn/20210708153236452.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-
-综上，在更新DB和删除Cache时，无论这俩操作谁先执行，只要有一个操作失败了，就会导致客户端读到旧值。
-
-那怎么办？好像怎么都会导致数据不一致？
-# 5 数据不一致的解决方案
-## 5.1 无并发
-### 重试
-将：
-- 要删除的Cache值
-- 或要更新的DB值
-
-暂存到MQ。
-
-当应用删除Cache或更新DB：
-- 成功
-把这些值从MQ去除，避免重复操作，这时即可保证DB、Cache数据一致性。
-- 失败
-重试。从MQ重新读取这些值，然后再次进行删除或更新。若重试超过一定次数，还没成功，就向业务层发送报错信息。
-
-在更新数据库和删除缓存值的过程中，其中一个操作失败了：
-#### 先更新DB，再删除缓存
-- 若删除缓存失败，再次重试后删除成功
-![](https://img-blog.csdnimg.cn/20210708155139659.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-其它情况不再赘述。
-
-即使这两个操作第一次执行时都没有失败，当有大量并发请求时，应用还是有可能读到不一致的数据。
-按不同的删除和更新顺序，分成两种情况来看
-
-## 5.2 高并发
-### 5.2.1 先删除Cache，再更新DB
-假设现有时刻t1< t2 < t3，线程 T1、T2：
-||T1|T2|问题|
-|--|--|--|--|
-|t1|删除缓存 X 的缓存值||
-|t2|| 1. 读取数据，缓存缺失，<br> 从 DB 读 X，读到旧值 <br> 2.把数据 X 写入缓存 |  1.T1 尚未更新 DB，导致 T2 读到旧值 <br> 2.T2 把旧值写入缓存，导致其它线程可能读到旧值
-|t3|更新DB 中的 X | |缓存中是旧值，DB 是新值，二者不一致
-
-此时，该怎么办呢？
-#### 解决方案
-T1更新完DB后，让它sleep一段时间，再删除缓存。
-
-> 为什么要sleep一段时间呢？
-
-为了让T2能够先从DB读数据，再把缺失数据写入缓存，然后，T1再进行删除。
-所以，T1 sleep的时间，就需要大于T2读取数据再写入缓存的时间。
-
-> 这个时间怎么确定？
-
-在业务程序运行时，统计下线程读数据和写缓存的操作时间，以此为基础来进行估算。
-
-这样，当其它线程读数据时，会发现缓存缺失，所以会从DB读最新值。因为这个方案会在第一次删除缓存值后，延迟一段时间再次进行删除，所以称为“延迟双删”。
-```bash
-redis.delKey(X)
-db.update(X)
-Thread.sleep(N)
-redis.delKey(X)
-```
-### 5.2.2 先更新DB，再删除Cache
-||T1|T2|问题|
-|--|--|--|--|
-|t1|删除 DB 的数据 X||
-|t2|| 读数据X，Cache命中，<br> 从Cache读X，读到旧值 |T1 尚未删除 Cache<br>导致 T2 读到 Cache 旧值
-|t3| 删除 Cache的数据 X | |
-
-这种情况下，如果其他线程并发读缓存的请求不多，那么，就不会有很多请求读取到旧值。
-而且，线程A一般也会很快删除缓存值，这样一来，其他线程再次读取时，就会发生缓存缺失，进而从数据库中读取最新值。所以，这种情况对业务的影响较小。
-
-至此，Cache和DB数据不一致的原因也都有了对应解决方案。
-- 删除Cache或更新DB失败而导致数据不一致
-重试，确保删除或更新成功
-- 在删除Cache、更新DB这两步操作中，有其他线程的并发读操作，导致其他线程读取到旧值
-延迟双删
-
-绝大多数场景都会将Redis作为只读缓存：
-- 既可以先删除缓存值再更新数据库
-- 也可以先更新数据库再删除缓存
-
-推荐优先使用先更新数据库再删除缓存：
-- 先删除缓存值再更新数据库，有可能导致请求因缓存缺失而访问数据库，给数据库带来压力
-- 如果业务应用中读取数据库和写缓存的时间不好估算，那么，延迟双删中的等待时间就不好设置
-
-不过，当使用先更新数据库再删除缓存时，也有个地方需要注意，如果业务层要求必须读取一致的数据，那么，我们就需要在更新数据库时，先在Redis缓存客户端暂存并发读请求，等数据库更新完、缓存值删除后，再读取数据，从而保证数据一致性。
-
-# 6 直接更新 Cache
-在只读缓存中进行数据的删改操作时，需要在缓存中删除相应的缓存值。若此过程不是删除缓存，而是直接更新缓存，效果如何？
-
-这种情况相当于把Redis当做**读写缓存**使用，删改操作同时操作DB、Cache。
-
-## 6.1 无并发
-### 先更新数据库，再更新缓存
-若更新DB成功，但Cache更新失败，此时DB最新值，但缓存旧值，后续读请求会直接命中缓存，得到旧值。
-
-### 先更新缓存，再更新数据库
-如果更新缓存成功，但数据库更新失败：
-- 缓存中是最新值
-- 数据库中是旧值
-
-后续读请求会直接命中缓存，但得到的是最新值，短期对业务影响不大。但一旦缓存过期或满容后被淘汰，读请求就会从数据库中重新加载旧值到缓存中，之后的读请求会从缓存中得到旧值，对业务产生影响。
-
-针对这种其中一个操作可能失败的情况，类似只读缓存方案，也可使用重试。把第二步操作放入到MQ中，消费者从MQ取出消息，再更新缓存或数据库，成功后把消息从消息队列删除，否则进行重试，以此达到数据库和缓存的最终一致。
-
-## 6.2 并发读写
-也会产生不一致，分为以下4种双写场景。
-
-> 双写模式下，更新DB有返回值，更新Redis的操作可放到更新DB返回后进行，通过数据库的行锁机制，可以避免更新DB是线程A，B，但更新Redis是线程B，A的情况。
-
-### 先更新数据库，再更新缓存
-写+读并发。
-线程A先更新数据库，之后线程B读取数据，此时线程B会命中缓存，读取到旧值，之后线程A更新缓存成功，后续的读请求会命中缓存得到最新值。
-
-这时，线程A未更新完缓存之前，在这期间的读请求会短暂读到旧值，对业务短暂影响。
-
-### 先更新缓存，再更新数据库
-写+读并发。
-线程A先更新缓存成功，之后线程B读取数据，此时线程B命中缓存，读取到最新值后返回，之后线程A更新数据库成功。这种场景下，虽然线程A还未更新完数据库，数据库会与缓存存在短暂不一致，但在这之前进来的读请求都能直接命中缓存，获取到最新值，所以对业务没影响。
-
-### 先更新数据库，再更新缓存
-写+写并发。
-线程A和线程B同时更新同一条数据，更新数据库的顺序是先A后B，但更新缓存时顺序是先B后A，这会导致数据库和缓存的不一致。
-
-### 先更新缓存，再更新数据库
-写+写并发。
-与场景3类似，线程A和线程B同时更新同一条数据，更新缓存的顺序是先A后B，但是更新数据库的顺序是先B后A，这也会导致数据库和缓存的不一致。
-
-场景1和2对业务影响较小，场景3和4会造成数据库和缓存不一致，影响较大。即读写缓存下，写+读并发对业务的影响较小，而写+写并发时，会造成数据库和缓存的不一致。
-
-针对场景3、4解决方案：对于写请求，配合分布式锁。写请求进来时，针对同一资源的修改操作，先加分布式锁，这样同一时间只允许一个线程去更新DB和Cache，没有拿到锁的线程把操作放入到MQ，延时处理。
-这样保证多个线程操作同一资源的顺序性，以此保证一致性。
-
-综上，使用读写缓存同时操作数据库和缓存时，因为其中一个操作失败导致不一致的问题，同样可以通过MQ重试解决。
-而在并发的场景下，读+写并发对业务没有影响或者影响较小，而写+写并发时需要配合分布式锁的使用，才能保证缓存和数据库的一致性。
-
-另外，读写缓存模式由于会同时更新数据库和缓存：
-- 优点
-缓存一直会有数据。若更新后立即访问，可直接命中缓存，能降低读请求对DB的压力（没有只读缓存的删除缓存导致缓存缺失和再加载的过程）
-- 缺点
-若更新后的数据，之后很少再被访问到，会导致缓存中保留的不是最热数据，缓存利用率不高（只读缓存中保留的都是热数据）
-
-所以读写缓存比较适合用于读写相当的业务场景。
-
-
-
-
-# 总结
-![](https://img-blog.csdnimg.cn/20210708164600442.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-# 延时双删策略
-写DB前后都执行`redis.del(key)`，并设定合理超时时间。
-
-## 执行流程
-1. 先删除缓存
-2. 再写数据库
-3. 休眠xx毫秒（根据具体业务时间）
-4. 再次删除缓存
-
-xx毫秒怎么确定？
-
-需要评估项目读数据业务逻辑耗时,以确保读请求结束，写请求可删除读请求造成的缓存脏数据。
-
-该策略还要考虑 redis 和数据库主从同步的耗时。最后的写数据的休眠时间：则在读数据业务逻辑的耗时的基础上，加上几百ms即可。比如：休眠1秒。
-
-# 设置缓存过期时间
-理论上，设置缓存过期时间，是保证最终一致性的解决方案。
-所有的写操作以DB为准，只要到达缓存过期时间，则后面的读请求自然会从DB读取新值，然后回填缓存。
-
-结合双删策略+缓存超时设置，这样最差的情况就是在超时时间内数据存在不一致，而且又增加写请求耗时。
-
-# 写完数据库后，再次删除缓存成功保证
-
-上述的方案有一个缺点，那就是操作完数据库后，由于种种原因删除缓存失败，这时，可能就会出现数据不一致的情况。
-需提供保障重试方案。
-
-## 方案一
-### 具体流程
-
-1. 更新数据库数据
-2. 缓存因为种种问题删除失败
-3. 将需要删除的key发送至消息队列
-4. 自己消费消息，获得需要删除的key
-5. 继续重试删除操作，直到成功
-
-然而，该方案有一个缺点，对业务线代码造成大量的侵入。于是有了方案二。
-在方案二中，启动一个订阅程序去订阅数据库的binlog，获得需要操作的数据。在应用程序中，另起一段程序，获得这个订阅程序传来的信息，进行删除缓存操作。
-
-## 方案二
-### 具体流程
-
-1. 更新数据库数据
-2. 数据库会将操作信息写入binlog日志当中
-3. 订阅程序提取出所需要的数据以及key
-4. 另起一段非业务代码，获得该信息
-5. 尝试删除缓存操作，发现删除失败
-6. 将这些信息发送至消息队列
-7. 重新从消息队列中获得该数据，重试操作。
-
-以上方案都是在业务中经常会碰到的场景，可以依据业务场景的复杂和对数据一致性的要求来选择具体的方案。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\205\253\345\244\247\346\216\222\345\272\217\347\232\204Java\345\256\236\347\216\260.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\205\253\345\244\247\346\216\222\345\272\217\347\232\204Java\345\256\236\347\216\260.md"
deleted file mode 100644
index 5d736bb71c..0000000000
--- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\205\253\345\244\247\346\216\222\345\272\217\347\232\204Java\345\256\236\347\216\260.md"
+++ /dev/null
@@ -1,1398 +0,0 @@
-# 概述
-排序有内部排序和外部排序
-- 内部排序是数据记录在内存中进行排序
-- 外部排序是因排序的数据很大，一次不能容纳全部的排序记录，在排序过程中需要访问外存
-
-`时间复杂度为最差情况下的复杂度`
-
-八大排序就是内部排序
-![](https://img-blog.csdnimg.cn/img_convert/028a59e7bc0ba4845ba41ac27cd1b341.png)
-
-当n较大，则应采用时间复杂度为O(nlog2n)的排序方法：快排,堆排,归排
-
-`快速排序：是目前基于比较的内部排序中被认为是最好的方法，当待排序的关键字是随机分布时，快速排序的平均时间最短 `
-
-# `稳定性:在值相等情况下,相对次序保持不变`
-
-#1. 插入排序—直接插入排序(Straight Insertion Sort) 
-- **基本思想:**
-将一个记录插入到已排序好的有序表中，从而得到一个新且记录数增1的有序表。即：先将序列的第1个记录看成是一个有序的子序列，然后从第2个记录逐个进行插入，直至整个序列有序为止。
-
-- 要点
-设立哨兵，作为临时存储和判断数组边界之用。
-
-- 直接插入排序示例
-![](https://img-blog.csdnimg.cn/img_convert/f58238c2f0c5072ed468f974e20ec5d4.png)
-
-
-`元素相等的，把欲插入的元素放在相等元素的后面`
-所以，相等元素的前后顺序没有改变，从原无序序列出去的顺序就是排好序后的顺序
-**所以`插入排序稳定`**
-
-- **实现：** 
-```java
-public class InsertionSort {
-
-	public static void insertionSort(int[] arr) {
-		if (arr == null || arr.length < 2) {
-			return;
-		}
-		for (int i = 1; i < arr.length; i++) {
-			for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
-				swap(arr, j, j + 1);
-			}
-		}
-	}
-
-	public static void swap(int[] arr, int i, int j) {
-		arr[i] = arr[i] ^ arr[j];
-		arr[j] = arr[i] ^ arr[j];
-		arr[i] = arr[i] ^ arr[j];
-	}
-
-	// for test
-	public static void comparator(int[] arr) {
-		Arrays.sort(arr);
-	}
-
-	// for test
-	public static int[] generateRandomArray(int maxSize, int maxValue) {
-		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
-		for (int i = 0; i < arr.length; i++) {
-			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
-		}
-		return arr;
-	}
-
-	// for test
-	public static int[] copyArray(int[] arr) {
-		if (arr == null) {
-			return null;
-		}
-		int[] res = new int[arr.length];
-		for (int i = 0; i < arr.length; i++) {
-			res[i] = arr[i];
-		}
-		return res;
-	}
-
-	// for test
-	public static boolean isEqual(int[] arr1, int[] arr2) {
-		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
-			return false;
-		}
-		if (arr1 == null && arr2 == null) {
-			return true;
-		}
-		if (arr1.length != arr2.length) {
-			return false;
-		}
-		for (int i = 0; i < arr1.length; i++) {
-			if (arr1[i] != arr2[i]) {
-				return false;
-			}
-		}
-		return true;
-	}
-
-	// for test
-	public static void printArray(int[] arr) {
-		if (arr == null) {
-			return;
-		}
-		for (int i = 0; i < arr.length; i++) {
-			System.out.print(arr[i] + " ");
-		}
-		System.out.println();
-	}
-
-	// for test
-	public static void main(String[] args) {
-		int testTime = 500000;
-		int maxSize = 100;
-		int maxValue = 100;
-		boolean succeed = true;
-		for (int i = 0; i < testTime; i++) {
-			int[] arr1 = generateRandomArray(maxSize, maxValue);
-			int[] arr2 = copyArray(arr1);
-			insertionSort(arr1);
-			comparator(arr2);
-			if (!isEqual(arr1, arr2)) {
-				succeed = false;
-				break;
-			}
-		}
-		System.out.println(succeed ? "Nice!" : "Fucking fucked!");
-
-		int[] arr = generateRandomArray(maxSize, maxValue);
-		printArray(arr);
-		insertionSort(arr);
-		printArray(arr);
-	}
-
-}
-
-```
-c++版本
-```
-void InsertSort(int a[], int n)  
-{  
-    for(int i= 1; i<n; i++){  
-        if(a[i] < a[i-1]){               //若第i个元素大于i-1元素，直接插入。小于的话，移动有序表后插入  
-            int j= i-1;   
-            int x = a[i];        //复制为哨兵，即存储待排序元素  
-            a[i] = a[i-1];           //先后移一个元素  
-            while(x < a[j]){  //查找在有序表的插入位置  
-                a[j+1] = a[j];  
-                j--;         //元素后移  
-            }  
-            a[j+1] = x;      //插入到正确位置  
-        }  
-        print(a,n,i);           //打印每趟排序的结果  
-    }  
-      
-}  
-```
-- **效率：**
-时间复杂度：`O（n^2）`
-
-其他的插入排序有二分插入排序，2-路插入排序。
-常数项极低,所以样本容量很小时`(数组长度小于6)最快!`,Collections.sort即是如此设计
-# 2 插入排序—希尔排序（Shell`s Sort） 
-1959 年由D.L.Shell 提出，相对直接排序有较大的改进
-又叫**缩小增量排序**
-- **思想**
-先将整个待排序的记录序列`分割`成为若干子序列分别进行直接插入排序
-待整个序列中的记录“`基本有序`”时
-再`对全体`记录进行依次直接插入排序。
-![](https://img-blog.csdnimg.cn/img_convert/e086671deb051143872707bdbf1a5d9d.png)
-##**实现**
-简单处理增量序列
-`d = {n/2 ,n/4, n/8 .....1}`, n为要排序数的个数
-
-- 先将要排序的一组记录按某个增量d（n/2,n为要排序数的个数）分成若干组子序列
-- 每组中记录的`下标相差d`,对每组中元素进行`直接插入排序`
-- 再用一个较小的增量（d/2）对它进行分组
-- 在每组中再进行`直接插入排序`
-- 继续不断`缩小增量`直至为1，最后使用`直接插入排序`完成排序
-```
-/** 
- * 直接插入排序的一般形式 
- * 
- * @param int dk 缩小增量，如果是直接插入排序，dk=1 
- * 
- */  
-  
-void ShellInsertSort(int a[], int n, int dk)  
-{  
-    for(int i= dk; i<n; ++i){  
-        if(a[i] < a[i-dk]){          //若第i个元素大于i-1元素，直接插入。小于的话，移动有序表后插入  
-            int j = i-dk;     
-            int x = a[i];           //复制为哨兵，即存储待排序元素  
-            a[i] = a[i-dk];         //首先后移一个元素  
-            while(x < a[j]){     //查找在有序表的插入位置  
-                a[j+dk] = a[j];  
-                j -= dk;             //元素后移  
-            }  
-            a[j+dk] = x;            //插入到正确位置  
-        }  
-        print(a, n,i );  
-    }  
-      
-}  
-  
-/** 
- * 先按增量d（n/2,n为要排序数的个数进行希尔排序 
- * 
- */  
-void shellSort(int a[], int n){  
-  
-    int dk = n/2;  
-    while( dk >= 1  ){  
-        ShellInsertSort(a, n, dk);  
-        dk = dk/2;  
-    }  
-}  
-
-```
-希尔排序时效分析很难，关键码的比较次数与记录移动次数依赖于增量因子序列d的选取，特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。
-增量因子序列可以有各种取法，有取奇数的，也有取质数的，但需要注意：增量因子中除1 外没有公因子，且最后一个增量因子必须为1。希尔排序方法是一个不稳定的排序方法。
-# 3 选择排序—简单选择排序（Simple Selection Sort）
-- **思想**
-在要排序的一组数中，选出最小/大的一个数与第1个位置的数交换
-然后在剩下的数当中再找最小/大的与第2个位置的数交换，依次类推，直到第n-1个元素（倒数第二个数）和第n个元素（最后一个数）比较为止
-![](https://img-blog.csdnimg.cn/img_convert/2c32984284709c35d943d672b6b66758.png)
-- **操作**
-第一趟，从n 个记录中找出关键码最小的记录与第一个记录交换；
-第二趟，从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换；
-以此类推..... 
-第i 趟，则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换，
-直到整个序列按关键码有序。
-- **实现**
-```java
-/** 
- * 数组的最小值 
- * 
- * @return int 数组的键值 
- */  
-int SelectMinKey(int a[], int n, int i)  
-{  
-    int k = i;  
-    for(int j=i+1 ;j< n; ++j) {  
-        if(a[k] > a[j]) k = j;  
-    }  
-    return k;  
-}  
-  
-/** 
- * 选择排序 
- * 
- */  
-void selectSort(int a[], int n){  
-    int key, tmp;  
-    for(int i = 0; i< n; ++i) {  
-        key = SelectMinKey(a, n,i);           //选择最小的元素  
-        if(key != i){  
-            tmp = a[i];  a[i] = a[key]; a[key] = tmp; //最小元素与第i位置元素互换  
-        }  
-        print(a,  n , i);  
-    }  
-}  
-```
-简单选择排序的改进——二元选择排序
-
-简单选择排序，每趟循环只能确定一个元素排序后的定位
-我们可以考虑改进为每趟循环确定两个元素（当前趟最大和最小记录）的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序，最多只需进行[n/2]趟循环即可。具体实现如下：
-
-无稳定性!!!
-# 4. 选择排序—堆排序（Heap Sort） 
-树形选择排序，是对直接选择排序的有效改进
-- **思想**
-堆的定义：具有n个元素的序列（k1,k2,...,kn),当且仅当满足
-![](https://img-blog.csdnimg.cn/img_convert/fd2c8099faae93a858c1f868d13f4e2d.png)
-可看出，**堆顶元素**（即第一个元素）必为最小项(小顶堆)
-
-若以一维数组存储一个堆，则堆对应一棵完全二叉树，且所有非叶结点的值均不大于(或不小于)其孩子的值，根结点（堆顶元素）的值是最小(或最大)的
-
-如：
-(a)大顶堆序列：（96, 83,27,38,11,09)
-(b)小顶堆序列：（12，36，24，85，47，30，53，91）
-![](https://img-blog.csdnimg.cn/img_convert/1c703395376bd66f6af3c97efc377d2c.png)
-- 初始时把要排序的n个数的序列看作是一棵**顺序存储的二叉树（一维数组存储二叉树）**
-调整它们的存储序，使之成为一个堆，将堆顶元素输出，得到n 个元素中最小(或最大)的元素，这时堆的根节点的数最小（或者最大）
-- 然后对前面(n-1)个元素重新调整使之成为堆，输出堆顶元素，得到n 个元素中次小(或次大)的元素
-- 依此类推，直到只有两个节点的堆，并对它们作交换，最后得到有n个节点的有序序列。称这个过程为**堆排序**。
-
-因此，实现堆排序需解决`两个问题`：
-1\. 如何将n 个待排序的数`建堆`
-2\. 输出堆顶元素后，怎样调整剩余n-1 个元素，使其成为一个`新堆`
-
-首先讨论第二个问题：输出堆顶元素后，对剩余n-1元素重新建成堆的调整过程
-`调整小顶堆`
-1. 设有m 个元素的堆，输出堆顶元素后，剩下m-1 个元素
-将堆底元素送入堆顶（（最后一个元素与堆顶进行交换），堆被破坏(根结点不满足堆的性质)
-2. 将根结点与左、右子树中较小元素的进行交换
-3. 若与左子树交换：如果左子树堆被破坏，即左子树的根结点不满足堆的性质，则重复方法2.
-4. 若与右子树交换，如果右子树堆被破坏，即右子树的根结点不满足堆的性质。则重复方法2.
-5. 继续对不满足堆性质的子树进行上述交换操作，直到叶结点，堆被建成
-
-称这个自根结点到叶子结点的调整过程为`筛选`
-![](https://img-blog.csdnimg.cn/img_convert/bb0eaa0573011a198456227cc02f601d.png)
-
-再讨论对n 个元素初始`建堆`的过程
-建堆方法：对初始序列建堆的过程，就是一个反复进行筛选的过程。
-
-1）n 个结点的完全二叉树，则最后一个结点是第`n / 2`个结点的子树
-2）筛选从第`n / 2`个结点为根的子树开始，该子树成为堆。
-3）之后向前依次对各结点为根的子树进行筛选，使之成为堆，直到根结点。
-
-如图建堆初始过程：无序序列：（49，38，65，97，76，13，27，49）
- ![](https://img-blog.csdnimg.cn/img_convert/55ad03deed681eed8d45f08476ee0b32.png)
-![](https://img-blog.csdnimg.cn/img_convert/33a500463204bbf6ce550ffd4afecd55.png)
-
-- 算法的实现 
-```java
-package com.sss;
-
-import java.util.Arrays;
-
-/**
- * @author Shusheng Shi
- */
-public class HeapSort {
-
-	public static void heapSort(int[] arr) {
-		if (arr == null || arr.length < 2) {
-			return;
-		}
-		for (int i = 0; i < arr.length; i++) {
-			heapInsert(arr, i);
-		}
-		int size = arr.length;
-		swap(arr, 0, --size);
-		while (size > 0) {
-			heapify(arr, 0, size);
-			swap(arr, 0, --size);
-		}
-	}
-
-	public static void heapInsert(int[] arr, int index) {
-		while (arr[index] > arr[(index - 1) / 2]) {
-			swap(arr, index, (index - 1) / 2);
-			index = (index - 1) / 2;
-		}
-	}
-
-	public static void heapify(int[] arr, int index, int size) {
-		int left = index * 2 + 1;
-		while (left < size) {
-			int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
-			largest = arr[largest] > arr[index] ? largest : index;
-			if (largest == index) {
-				break;
-			}
-			swap(arr, largest, index);
-			index = largest;
-			left = index * 2 + 1;
-		}
-	}
-
-	public static void swap(int[] arr, int i, int j) {
-		int tmp = arr[i];
-		arr[i] = arr[j];
-		arr[j] = tmp;
-	}
-
-	// for test
-	public static void comparator(int[] arr) {
-		Arrays.sort(arr);
-	}
-
-	// for test
-	public static int[] generateRandomArray(int maxSize, int maxValue) {
-		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
-		for (int i = 0; i < arr.length; i++) {
-			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
-		}
-		return arr;
-	}
-
-	// for test
-	public static int[] copyArray(int[] arr) {
-		if (arr == null) {
-			return null;
-		}
-		int[] res = new int[arr.length];
-		for (int i = 0; i < arr.length; i++) {
-			res[i] = arr[i];
-		}
-		return res;
-	}
-
-	// for test
-	public static boolean isEqual(int[] arr1, int[] arr2) {
-		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
-			return false;
-		}
-		if (arr1 == null && arr2 == null) {
-			return true;
-		}
-		if (arr1.length != arr2.length) {
-			return false;
-		}
-		for (int i = 0; i < arr1.length; i++) {
-			if (arr1[i] != arr2[i]) {
-				return false;
-			}
-		}
-		return true;
-	}
-
-	// for test
-	public static void printArray(int[] arr) {
-		if (arr == null) {
-			return;
-		}
-		for (int i = 0; i < arr.length; i++) {
-			System.out.print(arr[i] + " ");
-		}
-		System.out.println();
-	}
-
-	// for test
-	public static void main(String[] args) {
-		int testTime = 500000;
-		int maxSize = 100;
-		int maxValue = 100;
-		boolean succeed = true;
-		for (int i = 0; i < testTime; i++) {
-			int[] arr1 = generateRandomArray(maxSize, maxValue);
-			int[] arr2 = copyArray(arr1);
-			heapSort(arr1);
-			comparator(arr2);
-			if (!isEqual(arr1, arr2)) {
-				succeed = false;
-				break;
-			}
-		}
-		System.out.println(succeed ? "Nice!" : "Fucking fucked!");
-
-		int[] arr = generateRandomArray(maxSize, maxValue);
-		printArray(arr);
-		heapSort(arr);
-		printArray(arr);
-	}
-
-}
-```
-从算法描述来看，堆排序需要两个过程，一是建立堆，二是堆顶与堆的最后一个元素交换位置
-所以堆排序有两个函数组成
-- 一是建堆的渗透函数
-- 二是反复调用渗透函数实现排序的函数
-
-```cpp
-void print(int a[], int n){  
-    for(int j= 0; j<n; j++){  
-        cout<<a[j] <<"  ";  
-    }  
-    cout<<endl;  
-}  
-  
-  
-  
-/** 
- * 已知H[s…m]除了H[s] 外均满足堆的定义 
- * 调整H[s],使其成为大顶堆.即将对第s个结点为根的子树筛选,  
- * 
- * @param H是待调整的堆数组 
- * @param s是待调整的数组元素的位置 
- * @param length是数组的长度 
- * 
- */  
-void HeapAdjust(int H[],int s, int length)  
-{  
-    int tmp  = H[s];  
-    int child = 2*s+1; //左孩子结点的位置。(i+1 为当前调整结点的右孩子结点的位置)  
-    while (child < length) {  
-        if(child+1 <length && H[child]<H[child+1]) { // 如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)  
-            ++child ;  
-        }  
-        if(H[s]<H[child]) {  // 如果较大的子结点大于父结点  
-            H[s] = H[child]; // 那么把较大的子结点往上移动，替换它的父结点  
-            s = child;       // 重新设置s ,即待调整的下一个结点的位置  
-            child = 2*s+1;  
-        }  else {            // 如果当前待调整结点大于它的左右孩子，则不需要调整，直接退出  
-             break;  
-        }  
-        H[s] = tmp;         // 当前待调整的结点放到比其大的孩子结点位置上  
-    }  
-    print(H,length);  
-}  
-  
-  
-/** 
- * 初始堆进行调整 
- * 将H[0..length-1]建成堆 
- * 调整完之后第一个元素是序列的最小的元素 
- */  
-void BuildingHeap(int H[], int length)  
-{   
-    //最后一个有孩子的节点的位置 i=  (length -1) / 2  
-    for (int i = (length -1) / 2 ; i >= 0; --i)  
-        HeapAdjust(H,i,length);  
-}  
-/** 
- * 堆排序算法 
- */  
-void HeapSort(int H[],int length)  
-{  
-    //初始堆  
-    BuildingHeap(H, length);  
-    //从最后一个元素开始对序列进行调整  
-    for (int i = length - 1; i > 0; --i)  
-    {  
-        //交换堆顶元素H[0]和堆中最后一个元素  
-        int temp = H[i]; H[i] = H[0]; H[0] = temp;  
-        //每次交换堆顶元素和堆中最后一个元素之后，都要对堆进行调整  
-        HeapAdjust(H,0,i);  
-  }  
-}   
-  
-int main(){  
-    int H[10] = {3,1,5,7,2,4,9,6,10,8};  
-    cout<<"初始值：";  
-    print(H,10);  
-    HeapSort(H,10);  
-    //selectSort(a, 8);  
-    cout<<"结果：";  
-    print(H,10);  
-  
-}  
-```
-
-- **分析:**
-设树深度为k
-![](https://img-blog.csdnimg.cn/img_convert/b62e3e9a6c236e558f5bfc7b6705dcdf.png)
-从根到叶的筛选，元素比较次数至多`2(k-1)`次，交换记录至多`k `次
-所以，在建好堆后，排序过程中的筛选次数不超过下式 
- ![](https://img-blog.csdnimg.cn/img_convert/d10c1afe853e8dd84312084a768b7fbd.png)
-建堆时的比较次数不超过4n 次，因此堆排序最坏情况下，时间复杂度也为：
-`O(nlogn )`
-# 5 交换排序—冒泡排序（Bubble Sort）
-- **思想**
-在要排序的一组数中，对当前还未排好序的范围内的全部数，自上而下对相邻的两个数依次进行比较和调整，让较大的数往下沉，较小的往上冒。即：每当两相邻的数比较后发现它们的排序与排序要求相反时，就将它们互换。
-![冒泡排序的示例](https://img-blog.csdnimg.cn/img_convert/060b6ef68fa171cb319f6e9bab07a413.png)
-##算法的实现
-```java
-package com.sss;
-
-import java.util.Arrays;
-
-/**
- * @author Shusheng Shi
- */
-public class BubbleSort {
-
-	public static void bubbleSort(int[] arr) {
-		if (arr == null || arr.length < 2) {
-			return;
-		}
-		for (int e = arr.length - 1; e > 0; e--) {
-			for (int i = 0; i < e; i++) {
-				if (arr[i] > arr[i + 1]) {
-					swap(arr, i, i + 1);
-				}
-			}
-		}
-	}
-
-	public static void swap(int[] arr, int i, int j) {
-		arr[i] = arr[i] ^ arr[j];
-		arr[j] = arr[i] ^ arr[j];
-		arr[i] = arr[i] ^ arr[j];
-	}
-
-	// for test
-	public static void comparator(int[] arr) {
-		Arrays.sort(arr);
-	}
-
-	// for test
-	public static int[] generateRandomArray(int maxSize, int maxValue) {
-		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
-		for (int i = 0; i < arr.length; i++) {
-			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
-		}
-		return arr;
-	}
-
-	// for test
-	public static int[] copyArray(int[] arr) {
-		if (arr == null) {
-			return null;
-		}
-		int[] res = new int[arr.length];
-		for (int i = 0; i < arr.length; i++) {
-			res[i] = arr[i];
-		}
-		return res;
-	}
-
-	// for test
-	public static boolean isEqual(int[] arr1, int[] arr2) {
-		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
-			return false;
-		}
-		if (arr1 == null && arr2 == null) {
-			return true;
-		}
-		if (arr1.length != arr2.length) {
-			return false;
-		}
-		for (int i = 0; i < arr1.length; i++) {
-			if (arr1[i] != arr2[i]) {
-				return false;
-			}
-		}
-		return true;
-	}
-
-	// for test
-	public static void printArray(int[] arr) {
-		if (arr == null) {
-			return;
-		}
-		for (int i = 0; i < arr.length; i++) {
-			System.out.print(arr[i] + " ");
-		}
-		System.out.println();
-	}
-
-	// for test
-	public static void main(String[] args) {
-		int testTime = 500000;
-		int maxSize = 100;
-		int maxValue = 100;
-		boolean succeed = true;
-		for (int i = 0; i < testTime; i++) {
-			int[] arr1 = generateRandomArray(maxSize, maxValue);
-			int[] arr2 = copyArray(arr1);
-			bubbleSort(arr1);
-			comparator(arr2);
-			if (!isEqual(arr1, arr2)) {
-				succeed = false;
-				break;
-			}
-		}
-		System.out.println(succeed ? "Nice!" : "Fucking fucked!");
-
-		int[] arr = generateRandomArray(maxSize, maxValue);
-		printArray(arr);
-		bubbleSort(arr);
-		printArray(arr);
-	}
-
-}
-
-```
-##**冒泡排序算法的改进**
-
-对冒泡排序常见的改进方法是加入一标志性变量exchange，用于标志某一趟排序过程中是否有数据交换，如果进行某一趟排序时并没有进行数据交换，则说明数据已经按要求排列好，可立即结束排序，避免不必要的比较过程。
-
-本文再提供以下两种改进算法：
-1．设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。
-```cpp
-void Bubble_1 ( int r[], int n) {  
-    int i= n -1;  //初始时,最后位置保持不变  
-    while ( i> 0) {   
-        int pos= 0; //每趟开始时,无记录交换  
-        for (int j= 0; j< i; j++)  
-            if (r[j]> r[j+1]) {  
-                pos= j; //记录交换的位置   
-                int tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;  
-            }   
-        i= pos; //为下一趟排序作准备  
-     }   
-}    
-```
-2．传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。
-
-改进后的算法实现为:
-
-```cpp
-void Bubble_2 ( int r[], int n){  
-    int low = 0;   
-    int high= n -1; //设置变量的初始值  
-    int tmp,j;  
-    while (low < high) {  
-        for (j= low; j< high; ++j) //正向冒泡,找到最大者  
-            if (r[j]> r[j+1]) {  
-                tmp = r[j]; r[j]=r[j+1];r[j+1]=tmp;  
-            }   
-        --high;                 //修改high值, 前移一位  
-        for ( j=high; j>low; --j) //反向冒泡,找到最小者  
-            if (r[j]<r[j-1]) {  
-                tmp = r[j]; r[j]=r[j-1];r[j-1]=tmp;  
-            }  
-        ++low;                  //修改low值,后移一位  
-    }   
-}  
-```
-相邻值相同时不交换情况下,可以保持稳定性!!!
-## 6\. 交换排序—快速排序（Quick Sort）
-
-**基本思想：**
-
-1）选择一个基准元素,通常选择第一个元素或者最后一个元素,
-
-2）通过一趟排序讲待排序的记录分割成独立的两部分，其中一部分记录的元素值均比基准元素值小。另一部分记录的 元素值比基准值大。
-
-3）此时基准元素在其排好序后的正确位置
-
-4）然后分别对这两部分记录用同样的方法继续进行排序，直到整个序列有序。
-
-快速排序的示例：
-
-（a）一趟排序的过程：
-
-![image.png](https://img-blog.csdnimg.cn/img_convert/ab87f8fc8a72b3183372356717b5d1ce.png)
-
-
-（b）排序的全过程
-
-![image.png](https://img-blog.csdnimg.cn/img_convert/38776c797af7c0a01dfe41594cb9387e.png)
-
-
-算法的实现：
-
- 递归实现：
-```cpp
-void print(int a[], int n){  
-    for(int j= 0; j<n; j++){  
-        cout<<a[j] <<"  ";  
-    }  
-    cout<<endl;  
-}  
-  
-void swap(int *a, int *b)  
-{  
-    int tmp = *a;  
-    *a = *b;  
-    *b = tmp;  
-}  
-  
-int partition(int a[], int low, int high)  
-{  
-    int privotKey = a[low];                             //基准元素  
-    while(low < high){                                   //从表的两端交替地向中间扫描  
-        while(low < high  && a[high] >= privotKey) --high;  //从high 所指位置向前搜索，至多到low+1 位置。将比基准元素小的交换到低端  
-        swap(&a[low], &a[high]);  
-        while(low < high  && a[low] <= privotKey ) ++low;  
-        swap(&a[low], &a[high]);  
-    }  
-    print(a,10);  
-    return low;  
-}  
-  
-  
-void quickSort(int a[], int low, int high){  
-    if(low < high){  
-        int privotLoc = partition(a,  low,  high);  //将表一分为二  
-        quickSort(a,  low,  privotLoc -1);          //递归对低子表递归排序  
-        quickSort(a,   privotLoc + 1, high);        //递归对高子表递归排序  
-    }  
-}  
-  
-int main(){  
-    int a[10] = {3,1,5,7,2,4,9,6,10,8};  
-    cout<<"初始值：";  
-    print(a,10);  
-    quickSort(a,0,9);  
-    cout<<"结果：";  
-    print(a,10);  
-  
-}  
-```
-
-**分析：**
-
-快速排序是通常被认为在同数量级（O(nlog2n)）的排序方法中平均性能最好的
-但若初始序列按关键码有序或基本有序时，快排序反而蜕化为冒泡排序。
-为改进之，通常以“三者取中法”来选取基准记录，即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。
-
-**快速排序的改进**
-
-在本改进算法中,只对长度大于k的子序列递归调用快速排序,让原序列基本有序，然后再对整个基本有序序列用插入排序算法排序。实践证明，改进后的算法时间复杂度有所降低，且当k取值为 8 左右时,改进算法的性能最佳。算法思想如下：
-
-```cpp
-void print(int a[], int n){  
-    for(int j= 0; j<n; j++){  
-        cout<<a[j] <<"  ";  
-    }  
-    cout<<endl;  
-}  
-  
-void swap(int *a, int *b)  
-{  
-    int tmp = *a;  
-    *a = *b;  
-    *b = tmp;  
-}  
-  
-int partition(int a[], int low, int high)  
-{  
-    int privotKey = a[low];                 //基准元素  
-    while(low < high){                   //从表的两端交替地向中间扫描  
-        while(low < high  && a[high] >= privotKey) --high; //从high 所指位置向前搜索，至多到low+1 位置。将比基准元素小的交换到低端  
-        swap(&a[low], &a[high]);  
-        while(low < high  && a[low] <= privotKey ) ++low;  
-        swap(&a[low], &a[high]);  
-    }  
-    print(a,10);  
-    return low;  
-}  
-  
-  
-void qsort_improve(int r[ ],int low,int high, int k){  
-    if( high -low > k ) { //长度大于k时递归, k为指定的数  
-        int pivot = partition(r, low, high); // 调用的Partition算法保持不变  
-        qsort_improve(r, low, pivot - 1,k);  
-        qsort_improve(r, pivot + 1, high,k);  
-    }   
-}   
-void quickSort(int r[], int n, int k){  
-    qsort_improve(r,0,n,k);//先调用改进算法Qsort使之基本有序  
-  
-    //再用插入排序对基本有序序列排序  
-    for(int i=1; i<=n;i ++){  
-        int tmp = r[i];   
-        int j=i-1;  
-        while(tmp < r[j]){  
-            r[j+1]=r[j]; j=j-1;   
-        }  
-        r[j+1] = tmp;  
-    }   
-  
-}   
-  
-  
-  
-int main(){  
-    int a[10] = {3,1,5,7,2,4,9,6,10,8};  
-    cout<<"初始值：";  
-    print(a,10);  
-    quickSort(a,9,4);  
-    cout<<"结果：";  
-    print(a,10);  
-  
-}  
-```
-# 7. 归并排序(Merge Sort) 
-## 思想
-将两个（或以上）有序表合并成一个新的有序表
-即
-- 把待排序列分为若干个子序列，每个子序列是有序的
-- 然后再把有序子序列合并为整体有序序列
-![示例](https://img-blog.csdnimg.cn/img_convert/a64edc8c83a75af751b74594edd6ca2e.png)
-##**合并方法**
-设r[i…n]由两个有序子表r[i…m]和r[m+1…n]组成，两个子表长度分别为n-i +1、n-m。
-1.  j=m+1；k=i；i=i; //置两个子表的起始下标及辅助数组的起始下标
-2.  若i>m 或j>n，转⑷ //其中一个子表已合并完，比较选取结束
-3.  //选取r[i]和r[j]较小的存入辅助数组rf
-    如果r[i]<r[j]，rf[k]=r[i]； i++； k++； 转⑵
-    否则，rf[k]=r[j]； j++； k++； 转⑵
-4.  //将尚未处理完的子表中元素存入rf
-    如果i<=m，将r[i…m]存入rf[k…n] //前一子表非空
-    如果j<=n ,  将r[j…n] 存入rf[k…n] //后一子表非空
-5.  合并结束。
-
-```java
-package com.sss;
-
-import java.util.Arrays;
-
-public class MergeSort {
-
-	public static void mergeSort(int[] arr) {
-		if (arr == null || arr.length < 2) {
-			return;
-		}
-		mergeSort(arr, 0, arr.length - 1);
-	}
-
-	public static void mergeSort(int[] arr, int l, int r) {
-		if (l == r) {
-			return;
-		}
-		int mid = l + ((r - l) >> 1);
-		mergeSort(arr, l, mid);
-		mergeSort(arr, mid + 1, r);
-		merge(arr, l, mid, r);
-	}
-
-	public static void merge(int[] arr, int l, int m, int r) {
-		int[] help = new int[r - l + 1];
-		int i = 0;
-		int p1 = l;
-		int p2 = m + 1;
-		while (p1 <= m && p2 <= r) {
-			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
-		}
-		while (p1 <= m) {
-			help[i++] = arr[p1++];
-		}
-		while (p2 <= r) {
-			help[i++] = arr[p2++];
-		}
-		for (i = 0; i < help.length; i++) {
-			arr[l + i] = help[i];
-		}
-	}
-
-    /**
-     *
-     * @param arr
-     */
-	public static void comparator(int[] arr) {
-		Arrays.sort(arr);
-	}
-
-    /**
-     * for test
-     * @param maxSize
-     * @param maxValue
-     * @return
-     */
-	public static int[] generateRandomArray(int maxSize, int maxValue) {
-		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
-		for (int i = 0; i < arr.length; i++) {
-			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
-		}
-		return arr;
-	}
-
-    /**]
-     *
-     *  for test
-     * @param arr
-     * @return
-     */
-	public static int[] copyArray(int[] arr) {
-		if (arr == null) {
-			return null;
-		}
-		int[] res = new int[arr.length];
-		for (int i = 0; i < arr.length; i++) {
-			res[i] = arr[i];
-		}
-		return res;
-	}
-
-    /**
-     * for test
-     * @param arr1
-     * @param arr2
-     * @return
-     */
-	public static boolean isEqual(int[] arr1, int[] arr2) {
-		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
-			return false;
-		}
-		if (arr1 == null && arr2 == null) {
-			return true;
-		}
-		if (arr1.length != arr2.length) {
-			return false;
-		}
-		for (int i = 0; i < arr1.length; i++) {
-			if (arr1[i] != arr2[i]) {
-				return false;
-			}
-		}
-		return true;
-	}
-
-    /**
-     * for test
-     * @param arr
-     */
-	public static void printArray(int[] arr) {
-		if (arr == null) {
-			return;
-		}
-		for (int i = 0; i < arr.length; i++) {
-			System.out.print(arr[i] + " ");
-		}
-		System.out.println();
-	}
-
-    /**
-     * for test
-     * @param args
-     */
-	public static void main(String[] args) {
-		int testTime = 500000;
-		int maxSize = 100;
-		int maxValue = 100;
-		boolean succeed = true;
-		for (int i = 0; i < testTime; i++) {
-			int[] arr1 = generateRandomArray(maxSize, maxValue);
-			int[] arr2 = copyArray(arr1);
-			mergeSort(arr1);
-			comparator(arr2);
-			if (!isEqual(arr1, arr2)) {
-				succeed = false;
-				printArray(arr1);
-				printArray(arr2);
-				break;
-			}
-		}
-		System.out.println(succeed ? "Nice!" : "Fucking fucked!");
-
-		int[] arr = generateRandomArray(maxSize, maxValue);
-		printArray(arr);
-		mergeSort(arr);
-		printArray(arr);
-
-	}
-
-}
-
-```
-cpp版本
-```
-//将r[i…m]和r[m +1 …n]归并到辅助数组rf[i…n]  
-void Merge(ElemType *r,ElemType *rf, int i, int m, int n)  
-{  
-    int j,k;  
-    for(j=m+1,k=i; i<=m && j <=n ; ++k){  
-        if(r[j] < r[i]) rf[k] = r[j++];  
-        else rf[k] = r[i++];  
-    }  
-    while(i <= m)  rf[k++] = r[i++];  
-    while(j <= n)  rf[k++] = r[j++];  
-}  
-```
-
-**归并的迭代算法**
-
-1 个元素的表总是有序的。所以对n 个元素的待排序列，每个元素可看成1 个有序子表。对子表两两合并生成n/2个子表，所得子表除最后一个子表长度可能为1 外，其余子表长度均为2。再进行两两合并，直到生成n 个元素按关键码有序的表。
-```cpp
-void print(int a[], int n){  
-    for(int j= 0; j<n; j++){  
-        cout<<a[j] <<"  ";  
-    }  
-    cout<<endl;  
-}  
-  
-//将r[i…m]和r[m +1 …n]归并到辅助数组rf[i…n]  
-void Merge(ElemType *r,ElemType *rf, int i, int m, int n)  
-{  
-    int j,k;  
-    for(j=m+1,k=i; i<=m && j <=n ; ++k){  
-        if(r[j] < r[i]) rf[k] = r[j++];  
-        else rf[k] = r[i++];  
-    }  
-    while(i <= m)  rf[k++] = r[i++];  
-    while(j <= n)  rf[k++] = r[j++];  
-    print(rf,n+1);  
-}  
-  
-void MergeSort(ElemType *r, ElemType *rf, int lenght)  
-{   
-    int len = 1;  
-    ElemType *q = r ;  
-    ElemType *tmp ;  
-    while(len < lenght) {  
-        int s = len;  
-        len = 2 * s ;  
-        int i = 0;  
-        while(i+ len <lenght){  
-            Merge(q, rf,  i, i+ s-1, i+ len-1 ); //对等长的两个子表合并  
-            i = i+ len;  
-        }  
-        if(i + s < lenght){  
-            Merge(q, rf,  i, i+ s -1, lenght -1); //对不等长的两个子表合并  
-        }  
-        tmp = q; q = rf; rf = tmp; //交换q,rf，以保证下一趟归并时，仍从q 归并到rf  
-    }  
-}  
-  
-  
-int main(){  
-    int a[10] = {3,1,5,7,2,4,9,6,10,8};  
-    int b[10];  
-    MergeSort(a, b, 10);  
-    print(b,10);  
-    cout<<"结果：";  
-    print(a,10);  
-  
-}  
-```
-
-**两路归并的递归算法**
-
-```cpp
-void MSort(ElemType *r, ElemType *rf,int s, int t)  
-{   
-    ElemType *rf2;  
-    if(s==t) r[s] = rf[s];  
-    else  
-    {   
-        int m=(s+t)/2;          /*平分*p 表*/  
-        MSort(r, rf2, s, m);        /*递归地将p[s…m]归并为有序的p2[s…m]*/  
-        MSort(r, rf2, m+1, t);      /*递归地将p[m+1…t]归并为有序的p2[m+1…t]*/  
-        Merge(rf2, rf, s, m+1,t);   /*将p2[s…m]和p2[m+1…t]归并到p1[s…t]*/  
-    }  
-}  
-void MergeSort_recursive(ElemType *r, ElemType *rf, int n)  
-{   /*对顺序表*p 作归并排序*/  
-    MSort(r, rf,0, n-1);  
-}  
-```
-# 8. 桶排序/基数排序(Radix Sort)及计数排序
-说基数排序前，我们先说桶排序，桶排序是稳定的。
-## **思想**
-将阵列分到有限数量的桶里，再对每个桶再个别排序（有可能再使用别的排序或以递回方式继续使用桶排序）。
-当要被排序的阵列内的数值是均匀分配的时候，桶排序使用线性时间O(N)。但桶排序并非比较排序，他不受 O(NlogN) 下限的影响。
-
-简单来说，就是把数据分组，放在一个个的桶中，然后对每个桶里面的再排序 。例如要对大小为[1..1000]范围内的n个整数A[1..n]排序：  
-- 把桶设为大小为10的范围
-设集合B[1]存储[1..10]的整数，集合B[2]存储   (10..20]的整数，……集合B[i]存储(   (i-1)*10,   i*10]的整数，i   =   1,2,..100
-总共有  100个桶  
-- 对A[1..n]从头到尾扫描一遍，把每个A[i]放入对应的桶B[j]中
-再对这100个桶中每个桶里的数字排序，这时可用冒泡，选择或快排
-- 依次输出每个桶里面的数字，且每个桶中的数字从小到大输出，这样就得到所有数字排好序的一个序列了。 
-
-假设有n个数字,m个桶,如果数字均匀分布,则每个桶里面均有n/m个数
-如果对每个桶中的数字采用快排,那么整个算法的复杂度是：
-`O(n   +   m   *   n/m*log(n/m))   =   O(n   +   nlogn   -   nlogm) `
-当m接近n时，桶排序复杂度接近O(n)  。
-
-以上复杂度的计算基于输入的n个数字是均匀分布。该假设条件是很强的，实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中，那就退化成一般的排序 。
-
-前面说的几大排序算法 ，大部分时间复杂度都是O（n^2）,也有部分排序算法O(nlogn)
-而桶式排序却能实现O（n）时间复杂度
-但桶排序的缺点是
-- 空间复杂度较高,额外开销大
-排序有两个数组的空间开销，一个存放待排序数组，一个就是所谓的桶，比如待排序值是从0到m-1，那就需要m个桶，这个桶数组就要至少m个空间
-- 待排序的元素都要在一定的范围内等等
-桶式排序是一种分配排序。分配排序的特定是不需要进行关键码的比较，但前提是要知道待排序列的一些具体情况
-
-**分配排序思想：**说白了就是进行多次的桶式排序。****
-
-基数排序过程无须比较关键字，而是通过“分配”和“收集”过程来实现排序
-它们的时间复杂度可达到线性阶：O(n) 
-**实例**
-扑克牌中52 张牌
-![](https://img-blog.csdnimg.cn/img_convert/9e27fe54a8da319120107d1a4f02ce10.png)
-**2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J < Q < K < A**
-对扑克牌按花色、面值进行升排
-![](https://img-blog.csdnimg.cn/img_convert/dcc84dbcd28b5106848b207653bff143.png)
-![](https://img-blog.csdnimg.cn/img_convert/aa850455513eacf02d70d342fa805dd8.png)
-即两张牌，若花色不同，不论面值怎样，花色低的那张牌小于花色高的，只有在同花色情况下，大小关系才由面值的大小确定。这就是多关键码排序。
-
-为得到排序结果，我们讨论两种排序方法。
-- **方法1**：先对花色排序，将其分为4 个组,再对每组分别按面值排序,最后，4 组连接
-- **方法2**
-先给出13 个编号组(2 号，3 号，...，A 号)，将牌按面值依次放入对应的编号组，分成13 堆
-再按花色给出4 个编号组(梅花、方块、红心、黑心)，将2号组中牌取出分别放入对应花色组，再将3 号组中牌取出分别放入对应花色组，……，这样，4 个花色组中均按面值有序，然后，将4 个花色组依次连接起来即可
-
-设n 个元素的待排序列包含d 个关键码{k1，k2，…，kd}，则称序列对关键码{k1，k2，…，kd}有序是指：对于序列中任两个记录r[i]和r[j](1≤i≤j≤n)都满足下列有序关系：![](https://img-blog.csdnimg.cn/img_convert/abc83629039450dbe53f62518ff3e612.png)
-
-
-其中k1 称为最主位关键码，kd 称为最次位关键码     。
-
-**两种多关键码排序方法：**
-
-多关键码排序按照从最主位关键码到最次位关键码或从最次位到最主位关键码的顺序逐次排序，分两种方法：
-
-最高位优先(Most Significant Digit first)法，简称MSD 法：
-
-1）先按k1 排序分组，将序列分成若干子序列，同一组序列的记录中，关键码k1 相等。
-
-2）再对各组按k2 排序分成子组，之后，对后面的关键码继续这样的排序分组，直到按最次位关键码kd 对各子组排序后。
-
-3）再将各组连接起来，便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法一即是MSD 法。
-
-最低位优先(Least Significant Digit first)法，简称LSD 法：
-
-1) 先从kd 开始排序，再对kd-1进行排序，依次重复，直到按k1排序分组分成最小的子序列后。
-
-2) 最后将各个子序列连接起来，便可得到一个有序的序列, 扑克牌按花色、面值排序中介绍的方法二即是LSD 法。
-
-**基于LSD方法的链式基数排序的基本思想**
-
-　　“多关键字排序”的思想实现“单关键字排序”。对数字型或字符型的单关键字，可以看作由多个数位或多个字符构成的多关键字，此时可以采用“分配-收集”的方法进行排序，这一过程称作基数排序法，其中每个数字或字符可能的取值个数称为基数。比如，扑克牌的花色基数为4，面值基数为13。在整理扑克牌时，既可以先按花色整理，也可以先按面值整理。按花色整理时，先按红、黑、方、花的顺序分成4摞（分配），再按此顺序再叠放在一起（收集），然后按面值的顺序分成13摞（分配），再按此顺序叠放在一起（收集），如此进行二次分配和收集即可将扑克牌排列有序。   
-
-## 基数排序
-低位先排序，然后收集；
-再按高位排序，然后再收集；
-依次类推，直到最高位。
-
-有时候有些属性是有优先级顺序的，先按低优先级排序，再按高优先级排序。最后的次序就是高优先级高的在前，高优先级相同的低优先级高的在前
-基数排序基于分别排序，分别收集，所以是稳定的。
-
-**算法实现：**
-```java
-package com.sss;
-
-import java.util.Arrays;
-
-/**
- * @author Shusheng Shi
- */
-public class BucketSort {
-
-	/**
-	 * only for 0~200 value
-	 * @param arr
-	 */
-	public static void bucketSort(int[] arr) {
-		if (arr == null || arr.length < 2) {
-			return;
-		}
-		int max = Integer.MIN_VALUE;
-		for (int i = 0; i < arr.length; i++) {
-			max = Math.max(max, arr[i]);
-		}
-		int[] bucket = new int[max + 1];
-		for (int i = 0; i < arr.length; i++) {
-			bucket[arr[i]]++;
-		}
-		int i = 0;
-		for (int j = 0; j < bucket.length; j++) {
-			while (bucket[j]-- > 0) {
-				arr[i++] = j;
-			}
-		}
-	}
-
-	// for test
-	public static void comparator(int[] arr) {
-		Arrays.sort(arr);
-	}
-
-	// for test
-	public static int[] generateRandomArray(int maxSize, int maxValue) {
-		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
-		for (int i = 0; i < arr.length; i++) {
-			arr[i] = (int) ((maxValue + 1) * Math.random());
-		}
-		return arr;
-	}
-
-	// for test
-	public static int[] copyArray(int[] arr) {
-		if (arr == null) {
-			return null;
-		}
-		int[] res = new int[arr.length];
-		for (int i = 0; i < arr.length; i++) {
-			res[i] = arr[i];
-		}
-		return res;
-	}
-
-	// for test
-	public static boolean isEqual(int[] arr1, int[] arr2) {
-		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
-			return false;
-		}
-		if (arr1 == null && arr2 == null) {
-			return true;
-		}
-		if (arr1.length != arr2.length) {
-			return false;
-		}
-		for (int i = 0; i < arr1.length; i++) {
-			if (arr1[i] != arr2[i]) {
-				return false;
-			}
-		}
-		return true;
-	}
-
-	// for test
-	public static void printArray(int[] arr) {
-		if (arr == null) {
-			return;
-		}
-		for (int i = 0; i < arr.length; i++) {
-			System.out.print(arr[i] + " ");
-		}
-		System.out.println();
-	}
-
-	// for test
-	public static void main(String[] args) {
-		int testTime = 500000;
-		int maxSize = 100;
-		int maxValue = 150;
-		boolean succeed = true;
-		for (int i = 0; i < testTime; i++) {
-			int[] arr1 = generateRandomArray(maxSize, maxValue);
-			int[] arr2 = copyArray(arr1);
-			bucketSort(arr1);
-			comparator(arr2);
-			if (!isEqual(arr1, arr2)) {
-				succeed = false;
-				printArray(arr1);
-				printArray(arr2);
-				break;
-			}
-		}
-		System.out.println(succeed ? "Nice!" : "Fucking fucked!");
-
-		int[] arr = generateRandomArray(maxSize, maxValue);
-		printArray(arr);
-		bucketSort(arr);
-		printArray(arr);
-
-	}
-
-}
-
-```
-
-
-## 总结 
-
-**各种排序的稳定性，时间复杂度和空间复杂度总结：**
-
-![image.png](https://img-blog.csdnimg.cn/img_convert/077617aaa756cefba6a3dcb449b38673.png)
-
-
- 我们比较时间复杂度函数的情况：
-
-![image.png](https://img-blog.csdnimg.cn/img_convert/b1802c50e3f7b8166f885093a1c5a950.png)
-
-
-                             时间复杂度函数O(n)的增长情况
-
-![](https://img-blog.csdnimg.cn/img_convert/c2ffd3f3b0d382fd8d97b3a7705fed65.png)
-
-
-所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。
-
-**时间复杂度来说：**
-
-(1)平方阶(O(n<sup>2</sup>))排序 各类简单排序:直接插入、直接选择和冒泡排序；  (2)线性对数阶(O(nlog2n))排序 　　快速排序、堆排序和归并排序；  (3)O(n<sup>1+§</sup>))排序,§是介于0和1之间的常数。
-
-希尔排序 (4)线性阶(O(n))排序 基数排序，此外还有桶、箱排序。
-
-说明：
-
-当原表有序或基本有序时，直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数，时间复杂度可降至O（n）；
-
-而快速排序则相反，当原表基本有序时，将蜕化为冒泡排序，时间复杂度提高为O（n2）；
-
-原表是否有序，对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。
-
-**稳定性：**
-
-排序算法的稳定性:若待排序的序列中，存在多个具有相同关键字的记录，经过排序， 这些记录的相对次序保持不变，则称该算法是稳定的；若经排序后，记录的相对 次序发生了改变，则称该算法是不稳定的。 
-     稳定性的好处：排序算法如果是稳定的，那么从一个键上排序，然后再从另一个键上排序，第一个键排序的结果可以为第二个键排序所用。基数排序就是这样，先按低位排序，逐次按高位排序，低位相同的元素其顺序再高位也相同时是不会改变的。另外，如果排序算法稳定，可以避免多余的比较； 
-
-稳定的排序算法：冒泡排序、插入排序、归并排序和基数排序
-
-不是稳定的排序算法：选择排序、快速排序、希尔排序、堆排序
-
-**选择排序算法准则：**
-
-每种排序算法都各有优缺点。因此，在实用时需根据不同情况适当选用，甚至可以将多种方法结合起来使用。
-
-选择排序算法的依据
-
-影响排序的因素有很多，平均时间复杂度低的算法并不一定就是最优的。相反，有时平均时间复杂度高的算法可能更适合某些特殊情况。同时，选择算法时还得考虑它的可读性，以利于软件的维护。一般而言，需要考虑的因素有以下四点：
-
-1．待排序的记录数目n的大小；
-
-2．记录本身数据量的大小，也就是记录中除关键字外的其他信息量的大小；
-
-3．关键字的结构及其分布情况；
-
-4．对排序稳定性的要求。
-
-设待排序元素的个数为n.
-
-**1）**当n较大，则应采用时间复杂度为O(nlog2n)的排序方法：快速排序、堆排序或归并排序序。
-
-   快速排序：是目前基于比较的内部排序中被认为是最好的方法，当待排序的关键字是随机分布时，快速排序的平均时间最短；
-       堆排序 ：  如果内存空间允许且要求稳定性的，
-
-       归并排序：它有一定数量的数据移动，所以我们可能过与插入排序组合，先获得一定长度的序列，然后再合并，在效率上将有所提高。
-
-**2）**  当n较大，内存空间允许，且要求稳定性 =》归并排序
-
-**3）**当n较小，可采用直接插入或直接选择排序。
-
-    直接插入排序：当元素分布有序，直接插入排序将大大减少比较次数和移动记录的次数。
-
-    直接选择排序 ：元素分布有序，如果不要求稳定性，选择直接选择排序
-
-**5）**一般不使用或不直接使用传统的冒泡排序。
-
-**6）**基数排序  它是一种稳定的排序算法，但有一定的局限性：
-　　1、关键字可分解。 2、记录的关键字位数较少，如果密集更好 　　3、如果是数字时，最好是无符号的，否则将增加相应的映射复杂度，可先将其正负分开排序。
-![](https://img-blog.csdnimg.cn/img_convert/2ae71019f171ed05bef01202a9876139.png)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\233\236\346\272\257\347\256\227\346\263\225.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\233\236\346\272\257\347\256\227\346\263\225.md"
deleted file mode 100644
index c001787db6..0000000000
--- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\345\233\236\346\272\257\347\256\227\346\263\225.md"
+++ /dev/null
@@ -1,68 +0,0 @@
-DFS利用的就是是回溯算法思想，但其实回溯还可用在如正则表达式匹配、编译原理中的语法分析等。
-
-数学问题也可，如数独、八皇后、0-1背包、图的着色、旅行商问题、全排列等。
-# 理解“回溯算法”
-> 若人生可重来，如何才能在岔路口做出最正确选择，让自己的人生“最优”？
-
-贪心算法，在每次面对岔路口的时候，都做出看起来最优的选择，期望这一组选择可以使得我们的人生达到“最优”。但不一定能得到的是最优解。
-
-> 如何确保得到最优解？
-
-回溯算法很多时候都应用在“搜索”问题：在一组可能解中，搜索期望解。
-处理思想，类似枚举搜索：枚举所有解，找到满足期望的解。
-
-为规律枚举所有可能解，避免遗漏、重复，将问题求解过程分为多个阶段。
-每个阶段，都要面对一个岔路口，先随意选一条路走，当发现这条路走不通（不符合期望的解），就回退到上一个岔路口，另选一种走法继续走。
-# 八皇后
-8x8的棋盘，往里放8个棋子（皇后），每个棋子所在的行、列、对角线都不能有另一个棋子。
-![](https://img-blog.csdnimg.cn/0532b7d902884ce29a148695e4872ca0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_11,color_FFFFFF,t_70,g_se,x_16)
-
-把这个问题划分成8个阶段，依次将8个棋子放到第一行、第二行、第三行……第八行。
-放置过程中，不停地检查当前方法，是否满足要求
-- 满足
-跳到下一行继续放置棋子
-- 不满足
-换种方法尝试
-
-适合递归实现：
-![](https://img-blog.csdnimg.cn/4e7b69d48d3a4d04a3faafba4e91f90b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-# 0-1背包
-经典解法是动态规划，但还有简单但没那么高效的回溯解法。
-
-- 有一背包，背包总承载重量Wkg
-- 有n个物品，每个物品重量不等且不可分割
-- 期望选择几件物品，装载到背包中。在不超过背包所能装载重量的前提下，求背包中物品的总重量max？
-
-这个背包问题，物品不可分割，要么装要么不装，所以叫0-1背包，就无法通过贪心解决了。
-
-- 对于每个物品来说，都有两种选择，装 or 不装
-- 对于n个物品，就有 $2^n$ 种装法，去掉总重量超过Wkg的，从剩下的装法中选择总重量最接近Wkg的
-- 但如何才能不重复地穷举出这 $2^n$ 种装法？
-
-这就能回溯，把物品依次排列，整个问题分解为n个阶段，每个阶段对应一个物品怎么选择;
-- 先对第一个物品进行处理，选择装进去 or 不装进去
-- 再递归处理剩下物品
-
-搜索剪枝的技巧：当发现 已选择物品重量 ＞ Wkg，就停止探测剩下物品。
-![](https://img-blog.csdnimg.cn/e47c8d0cd7ae4203a3c8b109b62e4f01.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-## 正则表达式
-假设正表达式中只包含`*`、`?`通配符且现在规定：
-- `*` 匹配任意多个（大于等于0个）任意字符
-- `?` 匹配0或1个任意字符
-
-如何用回溯算法，判断某给定文本，是否匹配给定的正则表达式？
-依次考察正则表达式中的每个字符，当是非通配符时，就直接跟文本的字符进行匹配：
-- 相同
-继续往下处理
-- 不同
-回溯
-
-遇到特殊字符时，就有多种处理方式，如`*`有多种匹配方案，可匹配任意个文本串中的字符，先随意选择一种匹配方案，然后继续考察剩下字符：
-- 若中途发现无法继续匹配，就回到岔路口，重新选择一种匹配方案，再继续匹配剩下字符。
-![](https://img-blog.csdnimg.cn/31c5e631f10743328322e91c5387fa70.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-# 总结
-回溯算法思想很简单，大部分都是用来解决广义搜索问题：从一组可能解中，选出一个满足要求的解。
-
-回溯非常适合用递归实现，剪枝是提高回溯效率的一种技巧，无需穷举搜索所有情况。
-
-回溯算法可解决很多问题，如我们开头提到的深度优先搜索、八皇后、0-1背包问题、图的着色、旅行商问题、数独、全排列、正则表达式匹配等。
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204/\344\270\272\344\275\225Redis\344\275\277\347\224\250\350\267\263\350\241\250\350\200\214\351\235\236\347\272\242\351\273\221\346\240\221\345\256\236\347\216\260SortedSet\357\274\237.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204/\344\270\272\344\275\225Redis\344\275\277\347\224\250\350\267\263\350\241\250\350\200\214\351\235\236\347\272\242\351\273\221\346\240\221\345\256\236\347\216\260SortedSet\357\274\237.md"
deleted file mode 100644
index c812467965..0000000000
--- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204/\344\270\272\344\275\225Redis\344\275\277\347\224\250\350\267\263\350\241\250\350\200\214\351\235\236\347\272\242\351\273\221\346\240\221\345\256\236\347\216\260SortedSet\357\274\237.md"
+++ /dev/null
@@ -1,391 +0,0 @@
-知道跳表（Skip List）是在看关于Redis的书的时候，Redis中的有序集合使用了跳表数据结构。接着就查了一些博客，来学习一下跳表。后面会使用Java代码来简单实现跳表。
-# 什么是跳表
-跳表由William Pugh发明，他在论文《Skip lists: a probabilistic alternative to balanced trees》中详细介绍了跳表的数据结构和插入删除等操作，论文是这么介绍跳表的：
-
-> Skip lists are a data structure that can be used in place of balanced trees.Skip lists use probabilistic balancing rather than strictly enforced balancing and as a result the algorithms for insertion and deletion in skip lists are much simpler and significantly faster than equivalent algorithms for balanced trees.
-
-也就是说，跳表可以用来替代红黑树，使用概率均衡技术，使得插入、删除操作更简单、更快。先来看论文里的一张图：
-![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/4671dd3346d9d2d1cef44fe4ea9e1f93.png)
-观察上图
-
- - a：已排好序的链表，查找一个结点最多需要比较N个结点。
- - b：每隔2个结点增加一个指针，指向该结点间距为2的后续结点，那么查找一个结点最多需要比较ceil(N/2)+1个结点。
- - c，每隔4个结点增加一个指针，指向该结点间距为4的后续结点，那么查找一个结点最多需要比较ceil(N/4)+1个结点。
- - 若每第`2^i` 个结点都有一个指向间距为 `2^i`的后续结点的指针，这样不断增加指针，比较次数会降为log(N)。这样的话，搜索会很快，但插入和删除会很困难。
- 
-一个拥有k个指针的结点称为一个k层结点（level k node）。按照上面的逻辑，50%的结点为1层，25%的结点为2层，12.5%的结点为3层...如果每个结点的层数随机选取，但仍服从这样的分布呢（上图e，对比上图d）？
-
-使一个**k层结点的第i个指针**指向**第i层的下一个结点**，而不是它后面的第2^(i-1)个结点，那么结点的插入和删除只需要原地修改操作；一个结点的层数，是在它被插入的时候随机选取的，并且永不改变。因为这样的数据结构是基于链表的，并且额外的指针会跳过中间结点，所以作者称之为跳表（Skip Lists）。
-
-二分查找底层依赖数组随机访问的特性，所以只能用数组实现。若数据存储在链表，就没法用二分搜索了？
-
-其实只需稍微改造下链表，就能支持类似“二分”的搜索算法，即跳表（Skip list），支持快速的新增、删除、搜索操作。
-
-Redis中的有序集合（Sorted Set）就是用跳表实现的。我们知道红黑树也能实现快速的插入、删除和查找操作。那Redis 为何不选择红黑树来实现呢？
-![](https://img-blog.csdnimg.cn/c41cd7c5d6c34ea7bd75bc83d2f932c1.png)
-# 跳表的意义究竟在于何处？
-单链表即使存储的数据有序，若搜索某数据，也只能从头到尾遍历，搜索效率很低，平均时间复杂度是O(n)。
-
-追求极致的程序员就开始想了，那这该如何提高链表结构的搜索效率呢？
-若如下图，对链表建立一级“索引”，每两个结点提取一个结点到上一级，把抽出来的那级叫作索引或索引层。图中的down表示down指针，指向下一级结点。
-![](https://img-blog.csdnimg.cn/c8c2a8196d4f4346933b9f717a548182.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-比如要搜索16：
-- 先遍历索引层，当遍历到索引层的13时，发现下一个结点是17，说明目标结点位于这俩结点中间
-- 然后通过down指针，下降到原始链表层，继续遍历
-此时只需再遍历2个结点，即可找到16！
-
-原先单链表结构需遍历10个结点，现在只需遍历7个结点即可。可见，加一层索引，所需遍历的结点个数就减少了，搜索效率提升。
-![](https://img-blog.csdnimg.cn/862cdc1cf3234e0f937a2e65a52a585a.png)
-
-若再加层索引，搜索效率是不是更高？于是每两个结点再抽出一个结点到第二级索引。现在搜索16，只需遍历6个结点了！
-![](https://img-blog.csdnimg.cn/b9eb35ea32904818b53747416d2b18f7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-这里数据量不大，可能你也没感觉到搜索效率ROI高吗。
-
-那数据量就变大一点，现有一64结点链表，给它建立五级的索引。
-![](https://img-blog.csdnimg.cn/a7fd9946b27642fca7bbfddeda2546fb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-原来没有索引时，单链表搜索62需遍历62个结点！
-现在呢？只需遍历11个！所以你现在能体会到了，当链表长度n很大时，建立索引后，搜索性能显著提升。
-
-这种有多级索引的，可以提高查询效率的链表就是最近火遍面试圈的跳表。
-作为严谨的程序员，我们又开始好奇了
-# 跳表的搜索时间复杂度
-我们都知道单链表搜索时间复杂度O(n)，那如此快的跳表呢？
-
-若链表有n个结点，会有多少级索引呢？假设每两个结点抽出一个结点作为上级索引，则：
-- 第一级索引结点个数是n/2
-- 第二级n/4
-- 第三级n/8
-- ...
-- 第k级就是`n/(2^k)`
-
-假设索引有h级，最高级索引有2个结点，可得：
-
-
-```markup
-n/(2h) = 2
-```
-所以：
-
-```markup
-h = log2n-1
-```
-若包含原始链表这一层，整个跳表的高度就是log2 n。我们在跳表中查询某个数据的时候，如果每一层都要遍历m个结点，那在跳表中查询一个数据的时间复杂度就是O(m*logn)。
-
-那这个m的值是多少呢？按照前面这种索引结构，我们每一级索引都最多只需要遍历3个结点，也就是说m=3，为什么是3呢？我来解释一下。
-
-假设我们要查找的数据是x，在第k级索引中，我们遍历到y结点之后，发现x大于y，小于后面的结点z，所以我们通过y的down指针，从第k级索引下降到第k-1级索引。在第k-1级索引中，y和z之间只有3个结点（包含y和z），所以，我们在K-1级索引中最多只需要遍历3个结点，依次类推，每一级索引都最多只需要遍历3个结点。
-
-
-
-通过上面的分析，我们得到m=3，所以在跳表中查询任意数据的时间复杂度就是O(logn)。这个查找的时间复杂度跟二分查找是一样的。换句话说，我们其实是基于单链表实现了二分查找，是不是很神奇？不过，天下没有免费的午餐，这种查询效率的提升，前提是建立了很多级索引，也就是我们在第6节讲过的空间换时间的设计思路。
-
-# 跳表是不是很费内存？
-由于跳表要存储多级索引，势必比单链表消耗更多存储空间。那到底是多少呢？
-若原始链表大小为n：
-- 第一级索引大约有n/2个结点
-- 第二级索引大约有n/4个结点
-- ...
-- 最后一级2个结点
-
-多级结点数的总和就是：
-```java
-n/2+n/4+n/8…+8+4+2=n-2
-```
-所以空间复杂度是O(n)。这个量还是挺大的，能否再稍微降低索引占用的内存空间呢？
-若每三五个结点才抽取一个到上级索引呢？
-![](https://img-blog.csdnimg.cn/048531dae1394c048529edf6279dcfeb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-- 第一级索引需要大约n/3个结点
-- 第二级索引需要大约n/9个结点
-- 每往上一级，索引结点个数都除以3
-
-假设最高级索引结点个数为1，总索引结点数：
-
-```java
-n/3+n/9+n/27+…+9+3+1=n/2
-```
-尽管空间复杂度还是O(n)，但比上面的每两个结点抽一个结点的索引构建方法，要减少了一半的索引结点存储空间。
-
-我们大可不必过分在意索引占用的额外空间，实际开发中，原始链表中存储的有可能是很大的对象，而索引结点只需存储关键值和几个指针，无需存储对象，所以当对象比索引结点大很多时，那索引占用的额外空间可忽略。
-
-# 插入和删除的时间复杂度
-## 插入
-在跳表中插入一个数据，只需O(logn)时间复杂度。
-单链表中，一旦定位好要插入的位置，插入的时间复杂度是O(1)。但这里为了保证原始链表中数据的有序性，要先找到插入位置，所以这个过程中的查找操作比较耗时。
-
-单纯的单链表，需遍历每个结点以找到插入的位置。但跳表搜索某结点的的时间复杂度是O(logn)，所以搜索某数据应插入的位置的时间复杂度也是O(logn)。
-![](https://img-blog.csdnimg.cn/f6f355ffaa644532b72f6f7e1f5e4400.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-## 删除
-如果这个结点在索引中也有出现，除了要删除原始链表的结点，还要删除索引中的。
-因为单链表删除操作需拿到要删除结点的前驱结点，然后通过指针完成删除。所以查找要删除结点时，一定要获取前驱结点。若是双向链表，就没这个问题了。
-
-# 跳表索引动态更新
-当不停往跳表插入数据时，若不更新索引，就可能出现某2个索引结点之间数据非常多。极端情况下，跳表还会退化成单链表。
-![](https://img-blog.csdnimg.cn/c95c956e246c4e05889a2903b8490c15.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-作为一种动态数据结构，我们需要某种手段来维护索引与原始链表大小之间的平衡，也就是说，如果链表中结点多了，索引结点就相应地增加一些，避免复杂度退化，以及查找、插入、删除操作性能下降。
-
-像红黑树、AVL树这样的平衡二叉树通过左右旋保持左右子树的大小平衡，而跳表是通过随机函数维护前面提到的“平衡性”。
-
-往跳表插入数据时，可以选择同时将这个数据插入到部分索引层中。
-
-> 那如何选择加入哪些索引层呢？
-
-通过一个随机函数决定将这个结点插入到哪几级索引中，比如随机函数生成了值K，那就把这个结点添加到第一级到第K级这K级索引中。
-![](https://img-blog.csdnimg.cn/58a68ae89e384c71b0d9c9852efce0a8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-> 为何Redis要用跳表来实现有序集合，而不是红黑树？
-
-Redis中的有序集合支持的核心操作主要支持：
-- 插入一个数据
-- 删除一个数据
-- 查找一个数据
-- 迭代输出有序序列
-以上操作，红黑树也能完成，时间复杂度跟跳表一样。
-- **按照区间查找数据**
-红黑树的效率低于跳表。跳表可以做到`O(logn)`定位区间的起点，然后在原始链表顺序往后遍历即可。
-
-除了性能，还有其它原因：
-- 代码实现比红黑树好懂、好写多了，因为简单就代表可读性好，不易出错
-- 跳表更灵活，可通过改变索引构建策略，有效平衡执行效率和内存消耗
-
-因为红黑树比跳表诞生更早，很多编程语言中的Map类型（比如JDK 的 HashMap）都是通过红黑树实现的。业务开发时，直接从JDK拿来用，但跳表没有一个现成的实现，只能自己实现。
-
-# 跳表的代码实现（Java 版）
-## 数据结构定义
-表中的元素使用结点来表示，结点的层数在它被插入时随机计算决定（与表中已有结点数目无关）。
-一个i层的结点有i个前向指针（java中使用结点对象数组forward来表示），索引为从1到i。用MaxLevel来记录跳表的最大层数。
-跳表的层数为当前所有结点中的最大层数（如果list为空，则层数为1）。
-列表头header拥有从1到MaxLevel的前向指针：
-```java
-public class SkipList<T> {
-
-    // 最高层数
-    private final int MAX_LEVEL;
-    // 当前层数
-    private int listLevel;
-    // 表头
-    private SkipListNode<T> listHead;
-    // 表尾
-    private SkipListNode<T> NIL;
-    // 生成randomLevel用到的概率值
-    private final double P;
-    // 论文里给出的最佳概率值
-    private static final double OPTIMAL_P = 0.25;
-    
-    public SkipList() {
-        // 0.25, 15
-        this(OPTIMAL_P, (int)Math.ceil(Math.log(Integer.MAX_VALUE) / Math.log(1 / OPTIMAL_P)) - 1);
-    }
-
-    public SkipList(double probability, int maxLevel) {
-        P = probability;
-        MAX_LEVEL = maxLevel;
-
-        listLevel = 1;
-        listHead = new SkipListNode<T>(Integer.MIN_VALUE, null, maxLevel);
-        NIL = new SkipListNode<T>(Integer.MAX_VALUE, null, maxLevel);
-        for (int i = listHead.forward.length - 1; i >= 0; i--) {
-            listHead.forward[i] = NIL;
-        }
-    }
-
-    // 内部类
-    class SkipListNode<T> {
-        int key;
-        T value;
-        SkipListNode[] forward;
-        
-        public SkipListNode(int key, T value, int level) {
-            this.key = key;
-            this.value = value;
-            this.forward = new SkipListNode[level];
-        }
-    }
-}
-```
-## 搜索算法
-按key搜索，找到返回该key对应的value，未找到则返回null。
-
-通过遍历forward数组来需找特定的searchKey。假设skip list的key按照从小到大的顺序排列，那么从跳表的当前最高层listLevel开始寻找searchKey。在某一层找到一个非小于searchKey的结点后，跳到下一层继续找，直到最底层为止。那么根据最后搜索停止位置的下一个结点，就可以判断searchKey在不在跳表中。
-- 在跳表中找8的过程：
-![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/b7897fff74c4bf4e65f6c7ececdfe3ad.png)
-
-## 插入和删除算法
-都是通过查找与连接（search and splice）：
-![这里写图片描述](https://img-blog.csdnimg.cn/img_convert/29c8004c9ff02ce97b7987474eae5522.png)
-维护一个update数组，在搜索结束之后，update[i]保存的是待插入/删除结点在第i层的左侧结点。
-
-### 插入
-
-若key不存在，则插入该key与对应的value；若key存在，则更新value。
-
-如果待插入的结点的层数高于跳表的当前层数listLevel，则更新listLevel。
-
-选择待插入结点的层数randomLevel：
-
-randomLevel只依赖于跳表的最高层数和概率值p。
-
-另一种实现方法为，如果生成的randomLevel大于当前跳表的层数listLevel，那么将randomLevel设置为listLevel+1，这样方便以后的查找，在工程上是可以接受的，但同时也破坏了算法的随机性。
-### 删除
-删除特定的key与对应的value。如果待删除的结点为跳表中层数最高的结点，那么删除之后，要更新listLevel。
-```java
-public class SkipList<T> {
-
-    // 最高层数
-    private final int MAX_LEVEL;
-    // 当前层数
-    private int listLevel;
-    // 表头
-    private SkipListNode<T> listHead;
-    // 表尾
-    private SkipListNode<T> NIL;
-    // 生成randomLevel用到的概率值
-    private final double P;
-    // 论文里给出的最佳概率值
-    private static final double OPTIMAL_P = 0.25;
-
-    public SkipList() {
-        // 0.25, 15
-        this(OPTIMAL_P, (int)Math.ceil(Math.log(Integer.MAX_VALUE) / Math.log(1 / OPTIMAL_P)) - 1);
-    }
-
-    public SkipList(double probability, int maxLevel) {
-        P = probability;
-        MAX_LEVEL = maxLevel;
-
-        listLevel = 1;
-        listHead = new SkipListNode<T>(Integer.MIN_VALUE, null, maxLevel);
-        NIL = new SkipListNode<T>(Integer.MAX_VALUE, null, maxLevel);
-        for (int i = listHead.forward.length - 1; i >= 0; i--) {
-            listHead.forward[i] = NIL;
-        }
-    }
-
-    // 内部类
-    class SkipListNode<T> {
-        int key;
-        T value;
-        SkipListNode[] forward;
-        
-        public SkipListNode(int key, T value, int level) {
-            this.key = key;
-            this.value = value;
-            this.forward = new SkipListNode[level];
-        }
-    }
-
-    public T search(int searchKey) {
-        SkipListNode<T> curNode = listHead;
-
-        for (int i = listLevel; i > 0; i--) {
-            while (curNode.forward[i].key < searchKey) {
-                curNode = curNode.forward[i];
-            }
-        }
-
-        if (curNode.key == searchKey) {
-            return curNode.value;
-        } else {
-            return null;
-        }
-    }
-
-    public void insert(int searchKey, T newValue) {
-        SkipListNode<T>[] update = new SkipListNode[MAX_LEVEL];
-        SkipListNode<T> curNode = listHead;
-
-        for (int i = listLevel - 1; i >= 0; i--) {
-            while (curNode.forward[i].key < searchKey) {
-                curNode = curNode.forward[i];
-            }
-            // curNode.key < searchKey <= curNode.forward[i].key
-            update[i] = curNode;
-        }
-
-        curNode = curNode.forward[0];
-
-        if (curNode.key == searchKey) {
-            curNode.value = newValue;
-        } else {
-            int lvl = randomLevel();
-
-            if (listLevel < lvl) {
-                for (int i = listLevel; i < lvl; i++) {
-                    update[i] = listHead;
-                }
-                listLevel = lvl;
-            }
-
-            SkipListNode<T> newNode = new SkipListNode<T>(searchKey, newValue, lvl);
-
-            for (int i = 0; i < lvl; i++) {
-                newNode.forward[i] = update[i].forward[i];
-                update[i].forward[i] = newNode;
-            }
-        }
-    }
-
-    public void delete(int searchKey) {
-        SkipListNode<T>[] update = new SkipListNode[MAX_LEVEL];
-        SkipListNode<T> curNode = listHead;
-
-        for (int i = listLevel - 1; i >= 0; i--) {
-            while (curNode.forward[i].key < searchKey) {
-                curNode = curNode.forward[i];
-            }
-            // curNode.key < searchKey <= curNode.forward[i].key
-            update[i] = curNode;
-        }
-
-        curNode = curNode.forward[0];
-
-        if (curNode.key == searchKey) {
-            for (int i = 0; i < listLevel; i++) {
-                if (update[i].forward[i] != curNode) {
-                    break;
-                }
-                update[i].forward[i] = curNode.forward[i];
-            }
-
-            while (listLevel > 0 && listHead.forward[listLevel - 1] == NIL) {
-                listLevel--;
-            }
-        }
-    }
-
-    private int randomLevel() {
-        int lvl = 1;
-        while (lvl < MAX_LEVEL && Math.random() < P) {
-            lvl++;
-        }
-        return lvl;
-    }
-
-    public void print() {
-    for (int i = listLevel - 1; i >= 0; i--) {
-            SkipListNode<T> curNode = listHead.forward[i];
-            while (curNode != NIL) {
-                System.out.print(curNode.key + "->");
-                curNode = curNode.forward[i];
-            }
-            System.out.println("NIL");
-        }
-    }
-
-    public static void main(String[] args) {
-        SkipList<Integer> sl = new SkipList<Integer>();
-        sl.insert(20, 20);
-        sl.insert(5, 5);
-        sl.insert(10, 10);
-        sl.insert(1, 1);
-        sl.insert(100, 100);
-        sl.insert(80, 80);
-        sl.insert(60, 60);
-        sl.insert(30, 30);
-        sl.print();
-        System.out.println("---");
-        sl.delete(20);
-        sl.delete(100);
-        sl.print();
-    }
-}
-```
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204/\345\246\202\344\275\225\350\256\276\350\256\241\345\255\230\345\202\250\347\244\276\344\272\244\345\271\263\345\217\260\345\245\275\345\217\213\345\205\263\347\263\273\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204/\345\246\202\344\275\225\350\256\276\350\256\241\345\255\230\345\202\250\347\244\276\344\272\244\345\271\263\345\217\260\345\245\275\345\217\213\345\205\263\347\263\273\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204.md"
deleted file mode 100644
index 7546256101..0000000000
--- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\346\225\260\346\215\256\347\273\223\346\236\204/\345\246\202\344\275\225\350\256\276\350\256\241\345\255\230\345\202\250\347\244\276\344\272\244\345\271\263\345\217\260\345\245\275\345\217\213\345\205\263\347\263\273\347\232\204\346\225\260\346\215\256\347\273\223\346\236\204.md"
+++ /dev/null
@@ -1,118 +0,0 @@
-x博中，两个人可以互相关注，互加好友，那如何存储这些社交网络的好友关系呢？
-
-这就要用到：图。
-# 什么是“图”？（Graph）
-和树比起来，这是一种更加复杂的非线性表结构。
-
-树的元素称为节点，图中元素叫作顶点（vertex）。图中的一个顶点可以与任意其他顶点建立连接关系，这种建立的关系叫作边（edge）。
-![](https://img-blog.csdnimg.cn/9e8d606b57b24f239a46a36734d4249c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-社交网络就是典型的图结构。
-
-把每个用户看作一个顶点。如果两个用户之间互加好友，就在两者之间建立一条边。
-所以，整个微信的好友关系就可用一张图表示。
-每个用户有多少个好友，对应到图中就叫作顶点的度（degree），即跟顶点相连接的边的条数。
-
-不过微博的社交关系跟微信还有点不同，更复杂一点。微博允许单向关注，即用户A关注用户B，但B可不关注A。
-
-> 如何用图表示这种单向社交关系呢？
-
-这就引入边的“方向”。
-
-A关注B，就在图中画一条从A到B的带箭头的边，表示边的方向。A、B互关，就画一条从A指向B的边，再画一条从B指向A的边，这种边有方向的图叫作“有向图”。边没有方向的图也就叫“无向图”。
-![](https://img-blog.csdnimg.cn/f4ef3aa2589649838a55c13b5d559459.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-无向图中有“度”：一个顶点有多少条边。
-有向图中，把度分为：
-- 入度（In-degree）
-有多少条边指向这个顶点，即有多少粉丝
-- 出度（Out-degree）
-有多少条边是以这个顶点为起点指向其他顶点。对应到微博的例子，即关注了多少人
-
-QQ社交关系更复杂，不仅记录用户之间的好友关系，还记录了两个用户之间的亲密度，如何在图中记录这种好友关系亲密度呢？
-这就要用到带权图（weighted graph），每条边都有个权重（weight），可以通过这个权重来表示QQ好友间的亲密度。
-![](https://img-blog.csdnimg.cn/3807928549ef4d99af2744c1350d8630.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-# 存储
-## 邻接矩阵存储方法
-最直观的一种存储方法，邻接矩阵（Adjacency Matrix）。
-
-依赖一个二维数组：
-- 无向图
-如果顶点i与顶点j之间有边，就将A[i][j]和A[j][i]标记为1
-- 有向图
-如果顶点i到顶点j之间，有一条箭头从顶点i指向顶点j的边，那我们就将A[i][j]标记为1
-如果有一条箭头从顶点j指向顶点i的边，我们就将A[j][i]标记为1
-- 带权图，数组中就存储相应的权重
-![](https://img-blog.csdnimg.cn/43973239a1424f979308c26e8c1781ba.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-> 简单、直观，但比较浪费存储空间！
-
-无向图，若A[i][j]==1，则A[j][i]==1。实际上，只需存储一个即可。即无向图的二维数组，如果将其用对角线划分为上下两部分，则只需利用上或下面这样一半空间就够了，另外一半其实完全浪费。
-如果存储的是稀疏图（Sparse Matrix），即顶点很多，但每个顶点的边并不多，则更浪费空间。
-如微信有好几亿用户，对应到图就是好几亿顶点。但每个用户好友并不很多，一般也就三五百个而已。如果我们用邻接矩阵来存储，那绝大部分的存储空间都被浪费了。
-
-但这也并不是说，邻接矩阵的存储方法就完全没有优点。首先，邻接矩阵的存储方式简单、直接，因为基于数组，所以在获取两个顶点的关系时，就非常高效。其次，用邻接矩阵存储图的另外一个好处是方便计算。这是因为，用邻接矩阵的方式存储图，可以将很多图的运算转换成矩阵之间的运算。比如求解最短路径问题时会提到一个Floyd-Warshall算法，就是利用矩阵循环相乘若干次得到结果。
-## 邻接表存储方法
-针对上面邻接矩阵比较浪费内存空间，另外一种图存储，邻接表（Adjacency List）。
-
-有点像散列表？每个顶点对应一条链表，链表中存储的是与这个顶点相连接的其他顶点。图中画的是一个有向图的邻接表存储方式，每个顶点对应的链表里面，存储的是指向的顶点。对于无向图来说，也是类似的，不过，每个顶点的链表中存储的，是跟这个顶点有边相连的顶点，你可以自己画下。
-![](https://img-blog.csdnimg.cn/ee3d7c7dc6f146c6adb34bcafe3b19d5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-- 邻接矩阵存储较浪费空间，但更省时
-- 邻接表较节省存储空间，但较耗时
-
-如上图示例，若要确定是否存在一条从顶点2到顶点4的边，就要遍历顶点2的链表，看其中是否存在顶点4，而链表存储对缓存不友好。所以邻接表查询两个顶点之间的关系较为低效。
-
-基于链表法解决冲突的散列表中，若链过长，为提高查找效率，可将链表换成其他更高效数据结构，如平衡二叉查找树。
-邻接表长得很像散列。所以，也可将邻接表同散列表一样进行“优化”。
-
-可将邻接表中的链表改成平衡二叉查找树。实际可选用红黑树。即可更快速查找两个顶点之间是否存在边。
-这里的二叉查找树也可换成其他动态数据结构，如跳表、散列表。
-还可将链表改成有序动态数组，通过二分查找快速定位两个顶点之间是否存在边。
-# 如何存储微博、微信等社交网络中的好友关系？
-虽然微博有向图，微信是无向图，但对该问题，二者思路类似，以微博为例。
-
-数据结构服务于算法，选择哪种存储方法和需支持的操作有关。
-对于微博用户关系，需支持如下操作：
-- 判断用户A是否关注了用户B
-- 判断用户A是否是用户B的粉丝
-- 用户A关注用户B
-- 用户A取消关注用户B
-- 根据用户名称的首字母排序，分页获取用户的粉丝列表
-- 根据用户名称的首字母排序，分页获取用户的关注列表
-
-因为社交网络是一张稀疏图，使用邻接矩阵存储比较浪费存储空间。所以，这里采用邻接表。
-
-但一个邻接表存储这种有向图也是不够的。查找某用户关注了哪些用户很容易，但若想知道某用户都被哪些用户关注了，即粉丝列表就没法了。
-
-因此，还需一个逆邻接表，存储用户的被关注关系：
-- 邻接表，每个顶点的链表中，存储的就是该顶点指向的顶点
-查找某个用户关注了哪些用户
-- 逆邻接表，每个顶点的链表中，存储的是指向该顶点的顶点
-查找某个用户被哪些用户关注
-![](https://img-blog.csdnimg.cn/69ba70ef961d441c9753c31258af84a8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-基础的邻接表不适合快速判断两个用户是否为关注与被关注关系，所以进行优化，将邻接表的链表改为支持快速查找的动态数据结构。
-
-> 那是红黑树、跳表、有序动态数组还是散列表呢？
-
-因需按照用户名称首字母排序，分页获取用户的粉丝列表或关注列表，跳表最合适：插入、删除、查找都非常高效，时间复杂度$O(logn)$，空间复杂度稍高，是$O(n)$。
-跳表存储数据先天有序，分页获取粉丝列表或关注列表，非常高效。
-
-对小规模数据，如社交网络中只有几万、几十万个用户，可将整个社交关系存储在内存，该解决方案没问题。
-
-> 但像微博上亿用户，数据量太大，无法全部存储在内存，何解？
-
-可通过哈希算法等数据分片方案，将邻接表存储在不同机器：
-- 机器1存储顶点1，2，3的邻接表
-- 机器2存储顶点4，5的邻接表
-逆邻接表的处理方式同理。
-
-当要查询顶点与顶点关系时，利用同样的哈希算法，先定位顶点所在机器，然后再在相应机器上查找。
-![](https://img-blog.csdnimg.cn/295354cd7c904fa793e2784269c58241.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-还能借助外部存储（比如硬盘），因为外部存储的存储空间比内存多很多：
-如用下表存储这样一个图。为高效支持前面定义的操作，可建多个索引，比如第一列、第二列，给这两列都建立索引。
-![](https://img-blog.csdnimg.cn/b95b76c1fcce4f4a9df754bd80616f04.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-> 参考
-> - https://chowdera.com/2021/03/20210326155939001z.html
-> - https://www.zhihu.com/question/20216864
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\350\216\267\345\217\226Top 10\345\276\256\345\215\232\347\203\255\346\220\234\345\205\263\351\224\256\350\257\215\357\274\237.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\350\216\267\345\217\226Top 10\345\276\256\345\215\232\347\203\255\346\220\234\345\205\263\351\224\256\350\257\215\357\274\237.md"
deleted file mode 100644
index 0c21f06f0a..0000000000
--- "a/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225/\350\216\267\345\217\226Top 10\345\276\256\345\215\232\347\203\255\346\220\234\345\205\263\351\224\256\350\257\215\357\274\237.md"	
+++ /dev/null
@@ -1,178 +0,0 @@
-搜索引擎每天会接收大量的用户搜索请求，它会把这些用户输入的搜索关键词记录下来，然后再离线统计分析，得到最热门TopN搜索关键词。
-
-现在有一包含10亿个搜索关键词的日志文件，如何能快速获取到热门榜Top 10搜索关键词？
-可用堆解决，堆的几个应用：优先级队列、求Top K和求中位数。
-# 优先级队列
-首先应该是一个队列。队列最大的特性FIFO。 但优先级队列中，数据出队顺序是按优先级来，优先级最高的，最先出队。
-
-方法很多，但堆实现最直接、高效。因为堆和优先级队列很相似。一个堆即可看作一个优先级队列。很多时候，它们只是概念上的区分。
-- 往优先级队列中插入一个元素，就相当于往堆中插入一个元素
-- 从优先级队列中取出优先级最高的元素，就相当于取出堆顶元素
-
-优先级队列应用场景非常多：赫夫曼编码、图的最短路径、最小生成树算法等，Java的PriorityQueue。
-## 合并有序小文件
-- 有100个小文件
-- 每个文件100M
-- 每个文件存储有序字符串
-
-将这100个小文件合并成一个有序大文件，就用到优先级队列。
-像归排的合并函数。从这100个文件中，各取第一个字符串，放入数组，然后比较大小，把最小的那个字符串放入合并后的大文件中，并从数组中删除。
-
-假设，这最小字符串来自13.txt这个小文件，就再从该小文件取下一个字符串并放入数组，重新比较大小，并且选择最小的放入合并后的大文件，并且将它从数组中删除。依次类推，直到所有的文件中的数据都放入到大文件为止。
-
-用数组存储从小文件中取出的字符串。每次从数组取最小字符串，都需循环遍历整个数组，不高效，如何更高效呢？
-就要用到优先级队列，即堆：将从小文件中取出的字符串放入小顶堆，则堆顶元素就是优先级队列队首，即最小字符串。
-将这个字符串放入大文件，并将其从堆中删除。
-再从小文件中取出下一个字符串，放入到堆
-循环该 过程，即可将100个小文件中的数据依次放入大文件。
-
-删除堆顶数据、往堆插数据时间复杂度都是$O(logn)$，该案例$n=100$。
-这不比原来数组存储高效多了？
-# 2 高性能定时器
-有一定时器，维护了很多定时任务，每个任务都设定了一个执行时间点。
-定时器每过一个单位时间（如1s），就扫描一遍任务，看是否有任务到达设定执行时间。若到达，则执行。
-![](https://img-blog.csdnimg.cn/a8005a51f4ed46ada66d75c0f198c5f0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-显然这样每过1s就扫描一遍任务列表很低效：
-- 任务约定执行时间离当前时间可能还很久，这样很多次扫描其实都无意义
-- 每次都要扫描整个任务列表，若任务列表很大，就很耗时
-
-这时就该优先级队列上场了。按任务设定的执行时间，将这些任务存储在优先级队列，队首（即小顶堆的堆顶）存储最先执行的任务。
-
-这样，定时器就无需每隔1s就扫描一遍任务列表了。
-
-> $队首任务执行时间点 - 当前时间点相减  = 时间间隔T$
-
-T就是，从当前时间开始，需等待多久，才会有第一个任务要被执行。
-定时器就能设定在T秒后，再来执行任务。
-当前时间点 ~ $（T-1）s$ 时间段，定时器无需做任何事情。
-
-当Ts时间过去后，定时器取优先级队列中队首任务执行
-再计算新的队首任务执行时间点与当前时间点差值，将该值作为定时器执行下一个任务需等待时间。
-
-如此设计，定时器既不用间隔1s就轮询一次，也无需遍历整个任务列表，性能大大提高。
-# 利用堆求Top K
-求Top K的问题抽象成两类：
-## 静态数据集合
-数据集合事先确定，不会再变。
-
-可维护一个大小为K的小顶堆，顺序遍历数组，从数组中取数据与堆顶元素比较：
-- ＞堆顶
-删除堆顶，并将该元素插入堆
-- ＜堆顶
-do nothing，继续遍历数组
-
-等数组中的数据都遍历完，堆中数据就是Top K。
-
-遍历数组需要$O(n)$时间复杂度
-一次堆化操作需$O(logK)$时间复杂度
-所以最坏情况下，n个元素都入堆一次，所以时间复杂度就是$O(nlogK)$
-## 动态数据集合
-数据集合事先并不确定，有数据动态地加入到集合中，也就是求实时Top K。
-一个数据集合中有两个操作：
-- 添加数据
-- 询问当前TopK数据
-
-若每次询问Top K大数据，都基于当前数据重新计算，则时间复杂度$O(nlogK)$，n表示当前数据的大小。
-其实可一直都维护一个K大小的小顶堆，当有数据被添加到集合，就拿它与堆顶元素对比：
-- ＞堆顶
-就把堆顶元素删除，并且将这个元素插入到堆中
-- ＜堆顶
-do nothing。无论何时需查询当前的前K大数据，都可以里立刻返回给他
-# 利用堆求中位数
-求**动态数据**集合中的中位数：
-- 数据个数奇数
-把数据从小到大排列，第$\frac{n}{2}+1$个数据就是中位数
-- 数据个数是偶数
-处于中间位置的数据有两个，第$\frac{n}{2}$个、第$\frac{n}{2}+1$个数据，可随意取一个作为中位数，比如取两个数中靠前的那个，即第$\frac{n}{2}$个数据
-
-一组静态数据的中位数是固定的，可先排序，第$\frac{n}{2}$个数据就是中位数。
-每次询问中位数，直接返回该固定值。所以，尽管排序的代价比较大，但是边际成本会很小。但是，如果我们面对的是动态数据集合，中位数在不停地变动，如果再用先排序的方法，每次询问中位数的时候，都要先进行排序，那效率就不高了。
-
-借助堆，不用排序，即可高效地实现求中位数操作：
-需维护两个堆：
-- 大顶堆
-存储前半部分数据
-- 小顶堆
-存储后半部分数据 && 小顶堆数据都 ＞ 大顶堆数据
-
-即若有n（偶数）个数据，从小到大排序，则：
-- 前 $\frac{n}{2}$ 个数据存储在大顶堆
-- 后$\frac{n}{2}$个数据存储在小顶堆
-
-大顶堆中的堆顶元素就是我们要找的中位数。
-
-n是奇数也类似：
-- 大顶堆存储$\frac{n}{2}+1$个数据
-- 小顶堆中就存储$\frac{n}{2}$个数据
-
-数据动态变化，当新增一个数据时，如何调整两个堆，让大顶堆堆顶继续是中位数， 若：
-- 新加入的数据 ≤ 大顶堆堆顶，则将该新数据插到大顶堆
-- 新加入的数据大于等于小顶堆的堆顶元素，我们就将这个新数据插入到小顶堆。
-
-这时可能出现，两个堆中的数据个数不符合前面约定的情况，若：
-- n是偶数，两个堆中的数据个数都是 $\frac{n}{2}$
-- n是奇数，大顶堆有 $\frac{n}{2}+1$ 个数据，小顶堆有 $\frac{n}{2}$ 个数据
-
-即可从一个堆不停将堆顶数据移到另一个堆，以使得两个堆中的数据满足上面约定。
-
-插入数据涉及堆化，所以时间复杂度$O(logn)$，但求中位数只需返回大顶堆堆顶，所以时间复杂度$O(1)$。
-
-利用两个堆还可快速求其他百分位的数据，原理类似。
-“如何快速求接口的99%响应时间？
-
-中位数≥前50%数据，类比中位数，若将一组数据从小到大排列，这个99百分位数就是大于前面99%数据的那个数据。
-
-假设有100个数据：1，2，3，……，100，则99百分位数就是99，因为≤99的数占总个数99%。
-
-> 那99%响应时间是啥呢？
-
-若有100个接口访问请求，每个接口请求的响应时间都不同，如55ms、100ms、23ms等，把这100个接口的响应时间按照从小到大排列，排在第99的那个数据就是99%响应时间，即99百分位响应时间。
-
-即若有n个数据，将数据从小到大排列后，99百分位数大约就是第n*99%个数据。
-维护两个堆，一个大顶堆，一个小顶堆。假设当前总数据的个数是n，大顶堆中保存n*99%个数据，小顶堆中保存n*1%个数据。大顶堆堆顶的数据就是我们要找的99%响应时间。
-
-每插入一个数据时，要判断该数据跟大顶堆、小顶堆堆顶的大小关系，以决定插入哪个堆：
-- 新插入数据 ＜ 大顶堆的堆顶，插入大顶堆
-- 新插入的数据 ＞ 小顶堆的堆顶，插入小顶堆
-
-但为保持大顶堆中的数据占99%，小顶堆中的数据占1%，每次新插入数据后，都要重新计算，这时大顶堆和小顶堆中的数据个数，是否还符合99:1：
-- 不符合，则将一个堆中的数据移动到另一个堆，直到满足比例
-移动的方法类似前面求中位数的方法
-
-如此，每次插入数据，可能涉及几个数据的堆化操作，所以时间复杂度$O(logn)$。
-每次求99%响应时间时，直接返回大顶堆中的堆顶即可，时间复杂度$O(1)$。
-# 含10亿个搜索关键词的日志文件，快速获取Top 10
-很多人肯定说使用MapReduce，但若将场景限定为单机，可使用内存为1GB，你咋办？
-
-用户搜索的关键词很多是重复的，所以首先要统计每个搜索关键词出现的频率。
-可通过散列表、平衡二叉查找树或其他一些支持快速查找、插入的数据结构，记录关键词及其出现次数。
-
-假设散列表。
-顺序扫描这10亿个搜索关键词。当扫描到某关键词，去散列表中查询：
-- 存在，对应次数加一
-- 不存在，插入散列表，并记录次数1
-
-等遍历完这10亿个搜索关键词后，散列表就存储了不重复的搜索关键词及出现次数。
-
-再根据堆求Top K方案，建立一个大小为10小顶堆，遍历散列表，依次取出每个搜索关键词及对应出现次数，然后与堆顶搜索关键词对比：
-- 出现次数 ＞ 堆顶搜索关键词的次数
-删除堆顶关键词，将该出现次数更多的关键词入堆。
-
-以此类推，当遍历完整个散列表中的搜索关键词之后，堆中的搜索关键词就是出现次数最多的Top 10搜索关键词了。
-
-但其实有问题。10亿的关键词还是很多的。
-假设10亿条搜索关键词中不重复的有1亿条，如果每个搜索关键词的平均长度是50个字节，那存储1亿个关键词起码需要5G内存，而散列表因为要避免频繁冲突，不会选择太大的装载因子，所以消耗的内存空间就更多了。
-而机器只有1G可用内存，无法一次性将所有的搜索关键词加入内存。
-
-> 何解？
-
-因为相同数据经哈希算法后的哈希值相同，可将10亿条搜索关键词先通过哈希算法分片到10个文件：
-- 创建10个空文件：00~09
-- 遍历这10亿个关键词，并通过某哈希算法求哈希值
-- 哈希值同10取模，结果就是该搜索关键词应被分到的文件编号
-
-10亿关键词分片后，每个文件都只有1亿关键词，去掉重复的，可能就只剩1000万，每个关键词平均50个字节，总大小500M，1G内存足矣。
-
-针对每个包含1亿条搜索关键词的文件：
-- 利用散列表和堆，分别求Top 10
-- 10个Top 10放一起，取这100个关键词中，出现次数Top 10关键词，即得10亿数据的Top 10热搜关键词
\ No newline at end of file
diff --git "a/\346\235\203\351\231\220\347\256\241\347\220\206/Shiro\345\256\236\346\210\230(\345\205\255)---\346\235\203\351\231\220\347\274\223\345\255\230.md" "b/\346\235\203\351\231\220\347\256\241\347\220\206/Shiro\345\256\236\346\210\230(\345\205\255)---\346\235\203\351\231\220\347\274\223\345\255\230.md"
index 63e0c81b51..b7fb45f91c 100644
--- "a/\346\235\203\351\231\220\347\256\241\347\220\206/Shiro\345\256\236\346\210\230(\345\205\255)---\346\235\203\351\231\220\347\274\223\345\255\230.md"
+++ "b/\346\235\203\351\231\220\347\256\241\347\220\206/Shiro\345\256\236\346\210\230(\345\205\255)---\346\235\203\351\231\220\347\274\223\345\255\230.md"
@@ -70,7 +70,7 @@ this.manager = net.sf.ehcache.CacheManager.create(getCacheManagerConfigFileInput
 ```
 
 - 测试用例 
-```java
+```
 @Test
 public void testClearCachedAuthenticationInfo() {
     login(u1.getUsername(), password);
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\345\256\236\346\210\230(\344\270\200) - \345\255\220\345\237\237\343\200\201\346\240\270\345\277\203\345\237\237\343\200\201\351\200\232\347\224\250\345\237\237\345\222\214\346\224\257\346\222\221\345\237\237\347\255\211\346\240\270\345\277\203\346\246\202\345\277\265.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\345\256\236\346\210\230(\344\270\200) - \345\255\220\345\237\237\343\200\201\346\240\270\345\277\203\345\237\237\343\200\201\351\200\232\347\224\250\345\237\237\345\222\214\346\224\257\346\222\221\345\237\237\347\255\211\346\240\270\345\277\203\346\246\202\345\277\265.md"
new file mode 100644
index 0000000000..c2f883753c
--- /dev/null
+++ "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\345\256\236\346\210\230(\344\270\200) - \345\255\220\345\237\237\343\200\201\346\240\270\345\277\203\345\237\237\343\200\201\351\200\232\347\224\250\345\237\237\345\222\214\346\224\257\346\222\221\345\237\237\347\255\211\346\240\270\345\277\203\346\246\202\345\277\265.md"	
@@ -0,0 +1,62 @@
+# 领域
+用以确定边界。
+DDD按规则细分业务领域，细分到一定程度，DDD会将问题范围限定在特定边界，在该边界内建立领域模型，进而用代码实现该领域模型，解决相应业务问题。
+
+领域就是该边界内要解决的业务问题域。其越大，则业务范围越广。
+
+## 领域模型的特点
+对业务领域做了建模
+- 细粒度的类，易于扩展,容易复用
+- 可以应对复杂的业务逻辑
+- 需要经验才能掌握
+简单的领域模型
+- 几乎和数据库中的表 一一对应 
+- 复杂领域模型
+- 一使用了继承，组合，设计模式等各种手段
+# 子域
+领域可进步划分为子领域。划分出来的多个子领域称为子域，每个子域对应一个更小的问题域或业务范围。
+
+DDD是一种处理高度复杂领域的设计思想，它试图分离技术实现的复杂度。
+DDD的研究方法与自然科学类似。当人们在自然科学遇到复杂问题时，通常将问题步步细分，再针对细分出来的问题域，逐个深入研究，探索和建立所有子域的知识体系。当所有问题子域完成研究时，我们就建立了全部领域的完整知识体系了。
+
+每个细分的领域都有一个知识体系，即DDD的领域模型。在所有子域研究完后，就建立了全域的知识体系（领域模型）。
+
+以笔者所在的酒店行业为例。一开始的酒店核心系统是把所有功能放在一个系统，即单体架构。后来业务发展，开始转型中台，引入微服务架构。微服务架构就需划分业务领域边界，建立领域模型，并实现微服务落地。
+为实现酒店领域建模和微服务建设，可根据业务关联度及流程边界将酒店领域细分为：网上预定，入住，退房，客房服务，点餐等领域事件。
+- 网上预订，入住，退房。是酒店领域一定要有的功能，这就是核心子域。
+- 客房服务，点餐等不影响主要功能的就是支撑子领域。
+- 在预订这个限界上下文内可以建立预订的领域模型的领域模型最后映射到系统就是预订微服务。
+
+这就是一个酒店领域的细分和微服务的建设过程。
+
+不同行业的业务模型可能会不一样，但领域建模和微服务建设的过程和方法基本类似，其核心思想是将问题域逐步分解，降低业务理解和系统实现的复杂度。
+
+领域可细分为不同子域，子域可根据自身重要性和功能属性划分为三类子域：
+# 核心域
+
+决定产品和公司核心竞争力的子域是核心域，它是业务成功的主要因素和公司的核心竞争力。
+
+# 通用域
+没有太多个性化诉求，同时被多个子域使用的通用功能子域是通用域。比如认证、权限等，这类应用很容易买到，没有企业特点限制，无需太多定制化。
+
+# 支撑域
+既不包含决定产品和公司核心竞争力的功能，也不包含通用功能的子域，但又是必需的支撑域。支撑域具有企业特性，但不具通用性，例如数据代码类的数据字典等系统。
+
+#  划分核心域/通用域/支撑域的意义
+
+不同场景下，不同的人对桃树核心域的理解不同。
+
+核心域、支撑域和通用域的主要目标是：通过领域划分，区分不同子域在公司内的不同功能属性和重要性，从而公司可对不同子域采取不同的资源投入和建设策略，其关注度也会不一样。
+由于预算和资源有限，对不同类型子域应有不同关注度和资源投入策略。商业模式和战略方向决定公司在划分核心域、通用域和支撑域时的结果。
+
+都是电商平台的淘宝、天猫、京东和苏宁易购，商业模式却不同：
+- 淘宝是C2C
+- 京东和苏宁易购则是B2C
+	- 苏宁易购是传统线下卖场转型成为电商
+	- 京东是直营加部分平台模式
+
+导致其核心域划分结果自然也不同。在公司领域细分、建立领域模型和系统建设时，就要结合公司战略重点和商业模式，找到核心域，且重点关注核心域。
+
+# 总结
+领域的核心思想是将问题域逐级细分，降低业务理解和系统实现的复杂度。
+通过领域细分，逐步缩小微服务需要解决的问题域，构建合适的领域模型，而领域模型映射成系统就是微服务。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\345\256\236\346\210\230(\344\272\214) - \351\231\220\347\225\214\344\270\212\344\270\213\346\226\207\357\274\210bounded context\357\274\211.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\345\256\236\346\210\230(\344\272\214) - \351\231\220\347\225\214\344\270\212\344\270\213\346\226\207\357\274\210bounded context\357\274\211.md"
new file mode 100644
index 0000000000..31e8ac218d
--- /dev/null
+++ "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\345\256\236\346\210\230(\344\272\214) - \351\231\220\347\225\214\344\270\212\344\270\213\346\226\207\357\274\210bounded context\357\274\211.md"	
@@ -0,0 +1,59 @@
+限界上下文定义领域边界，以确保每个上下文含义在它特定的边界内都具有唯一的含义，领域模型则存在于这个边界之内。
+
+# 通用语言
+在事件风暴过程中，通过团队交流达成共识的，能够简单、清晰、准确描述业务涵义和规则的语言。限界上下文中的通用语言向我们提供了设计领域模型的概念术语。
+
+通用语言不是平白产生的，它必须通过与领域专家详细讨论之后才能得到的统一语言，不管你在团队中承担什么角色，在同一领域的软件生命周期里都使用统一的语言进行交流。通用语言定义上下文含义，它可解决交流障碍，使领域专家和开发人员能够协作，从而确保业务需求的正确表达。
+
+通用语言包含术语和用例场景，能够直接反映在代码。通用语言中的
+- 名词，用于给领域对象等概念命名，如商品、订单，对应实体对象
+- 形容词，用于描述这些概念
+- 动词，表示可以完成的操作，比如一个动作或事件，如商品已下单、订单已付款，对应领域事件或者命令
+
+通用语言贯穿DDD。基于它，代码可读性更好，将业务需求准确转化为代码设计。
+
+- 从事件风暴建立通用语言到领域对象设计和代码落地过程
+![](https://img-blog.csdnimg.cn/2020092919365579.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+
+1. 在事件风暴的过程中，领域专家会和设计、开发人员一起建立领域模型，在领域建模的过程中会形成通用的业务术语和用户故事。事件风暴也是一个项目团队统一语言的过程
+2. 通过用户故事分析会形成一个个的领域对象，这些领域对象对应领域模型的业务对象，每一个业务对象和领域对象都有通用的名词术语，并且一一映射
+3. 微服务代码模型来源于领域模型，每个代码模型的代码对象跟领域对象一一对应。
+
+设计过程可以用一些表格，来记录事件风暴和微服务设计过程中产生的领域对象及其属性。比如，领域对象在DDD分层架构中的位置、属性、依赖关系以及与代码模型对象的映射关系等。
+
+下面是一个微服务设计实例的部分数据，表格中的这些名词术语就是项目团队在事件风暴过程中达成一致、可用于团队内部交流的通用语言。
+![](https://img-blog.csdnimg.cn/20200929234859468.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+DDD分析过程中所有的领域对象及它们的属性都被记录，除了领域对象，还记录了在微服务设计过程中领域对象所对应的代码对象，并一一映射。
+
+
+DDD分析和设计过程中的每一个环节都需要保证限界上下文内术语的统一，在代码模型设计的时侯就要建立领域对象和代码对象的一一映射，从而**保证业务模型和代码模型的一致，实现业务语言与代码语言的统一**。
+
+做到这点，就建立了领域对象和代码对象的映射关系，即可指导开发准确无误按设计文档完成微服务开发。即使是不熟悉代码的业务人员，也可很快找到代码位置。
+
+# 限界上下文
+语言都有语义环境，通用语言亦是。为避免同样概念或语义在不同上下文环境中歧义，DDD在提出“限界上下文”，确定语义所在的领域边界。就可在统一的领域边界内用统一的语言进行交流。
+
+就是封装通用语言和领域对象，提供上下文环境，保证在领域之内的一些术语、业务相关对象等（通用语言）有一个确切的含义，没有二义性。
+这个边界定义了模型的适用范围，使团队所有成员能够明确地知道什么应该在模型中实现，不应该在模型中实现。
+
+## 案例
+都说汉语博大精深，不同的情境下，一个词会有不同涵义。比如小孩问妈妈：“今天应该穿几件衣服呀？”妈妈说：“能穿多少就穿多少！”那到底是穿多还是穿少？没有具体语境，还真不好判断。但若限定语境，比如寒冬或炎夏，就很清晰了。所以语言离不开它的语义环境。
+
+业务的通用语言也有它的业务边界。限界上下文就是用来细分领域，从而定义通用语言所在的边界。
+
+如电商领域的商品，商品在不同阶段有不同术语，在销售阶段是商品，而在运输阶段
+则变成货物。同一个东西，由于业务领域不同，赋予了这些术语不同涵义和职责边界，这个边界就可能会成为未来微服务设计的边界。领域边界就是通过限界上下文来定义的。
+
+# 限界上下文和微服务
+车险承保的流程包含了投保、缴费、出单等几个主要流程。如果出险了还会有报案、查勘、定损、理算等理赔流程。
+保险领域被拆分为：投保、支付、保单管理和理赔四个子域。子域还可根据需要进一步拆分为子子域，比如，支付子域可继续拆分为收款和付款子子域。拆到一定程度后，有些子子域的领域边界就可能变成限界上下文的边界了。子域可能会包含多个限界上下文，如理赔子域就包括报案、查勘和定损等多个限界上下文（限界上下文与理赔的子子域领域边界重合）。也有可能子域本身的边界就是限界上下文边界，如投保子域。
+每个领域模型都有它对应的限界上下文，团队在限界上下文内用通用语言交流。领域内所有限界上下文的领域模型构成整个领域的领域模型。理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务，就完成了从问题域到软件的解决方案。
+限界上下文是微服务设计和拆分的主要依据。在领域模型中，如果不考虑技术异构、团队沟通等其它外部因素，一个限界上下文理论上就可以设计为一个微服务。
+不过微服务的拆分还是有很多限制因素的，在设计中不宜过度拆分
+
+# 总结
+通用语言确定了项目团队内部交流的统一语言，而这个语言所在的语义环境则是由限界上下文来限定的，以确保语义的唯一性。
+领域专家、架构师和开发人员的主要工作就是通过事件风暴来划分限界上下文。限界上下文确定了微服务的设计和拆分方向，是微服务设计和拆分的主要依据。如果不考虑技术异构、团队沟通等其它外部因素，一个限界上下文理论上就可以设计为一个微服务。
+
+参考
+- 限界上下文：定义领域边界的利器
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230 - \347\220\206\350\247\243\350\201\232\345\220\210\357\274\210Aggregate\357\274\211\345\222\214\350\201\232\345\220\210\346\240\271\357\274\210AggregateRoot\357\274\211.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230 - \347\220\206\350\247\243\350\201\232\345\220\210\357\274\210Aggregate\357\274\211\345\222\214\350\201\232\345\220\210\346\240\271\357\274\210AggregateRoot\357\274\211.md"
index 9fcb39d01b..094fd9a292 100644
--- "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230 - \347\220\206\350\247\243\350\201\232\345\220\210\357\274\210Aggregate\357\274\211\345\222\214\350\201\232\345\220\210\346\240\271\357\274\210AggregateRoot\357\274\211.md"	
+++ "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230 - \347\220\206\350\247\243\350\201\232\345\220\210\357\274\210Aggregate\357\274\211\345\222\214\350\201\232\345\220\210\346\240\271\357\274\210AggregateRoot\357\274\211.md"	
@@ -1,3 +1,7 @@
+> `文章收录在我的 GitHub 仓库，欢迎Star/fork：`
+> [Java-Interview-Tutorial](https://github.com/Wasabi1234/Java-Interview-Tutorial)
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
 将实体（Entity）和值对象（ValueObject）组成聚合(Aggregate)，再根据业务语义将多个聚合划定到同一个限界上下文（Bounded Context）中，并在限界上下文内完成领域建模。
 
 注意思考：
@@ -130,4 +134,7 @@ AggregateTypel (
 
 参考
 - 《实现领域驱动设计》
--  聚合和聚合根：怎样设计聚合？
\ No newline at end of file
+-  聚合和聚合根：怎样设计聚合？
+
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(1)-\345\255\220\345\237\237\343\200\201\346\240\270\345\277\203\345\237\237\343\200\201\351\200\232\347\224\250\345\237\237\345\222\214\346\224\257\346\222\221\345\237\237\347\255\211\345\237\272\346\234\254\346\246\202\345\277\265.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(1)-\345\255\220\345\237\237\343\200\201\346\240\270\345\277\203\345\237\237\343\200\201\351\200\232\347\224\250\345\237\237\345\222\214\346\224\257\346\222\221\345\237\237\347\255\211\345\237\272\346\234\254\346\246\202\345\277\265.md"
deleted file mode 100644
index 928d29571f..0000000000
--- "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(1)-\345\255\220\345\237\237\343\200\201\346\240\270\345\277\203\345\237\237\343\200\201\351\200\232\347\224\250\345\237\237\345\222\214\346\224\257\346\222\221\345\237\237\347\255\211\345\237\272\346\234\254\346\246\202\345\277\265.md"
+++ /dev/null
@@ -1,47 +0,0 @@
-# 1 领域
-用以确定边界。
-
-DDD按规则细分业务领域，细分到一定程度，DDD会将问题范围限定在特定边界，在该边界内建立领域模型，进而用代码实现该领域模型，解决相应业务问题。
-
-领域就是该边界内要解决的**业务问题域**。其越大，则业务范围越广。
-
-## 领域模型的特点
-对业务领域建模：
-- 细粒度的类，易扩展，易复用
-- 可应对复杂业务逻辑
-- 需要经验
-
-简单的领域模型：
-- 几乎和DB中的表一一对应 
-- 复杂领域模型
-- 使用了继承，组合，设计模式等各种手段
-# 2 子域
-领域可再划分为多个子领域，即子域。
-每个子域对应一个更小的问题域或业务范围。
-
-DDD是处理复杂领域的设计思想，它试图分离技术实现的复杂度。每个细分的领域都有一个知识体系，即DDD的领域模型。在所有子域研究完后，就建立了领域模型。
-
-比如酒店行业，一开始的酒店核心系统是单体架构，后来业务发展，开始转型中台，引入微服务。微服务架构就需划分业务领域边界，建立领域模型，并实现微服务落地。
-可根据业务关联度及流程边界将酒店领域细分为：预订，入住，退房，客房服务，点餐等领域事件。
-- 网上预订，入住，退房。是酒店领域一定要有的功能，这就是核心子域。
-- 客房服务，点餐等不影响主要功能的就是支撑子领域。
-- 在预订这个限界上下文内可以建立预订的领域模型的领域模型最后映射到系统就是预订微服务。
-
-不同行业的业务模型可能不同，但领域建模过程类似，核心思想都是将问题域逐步分解，降低业务理解和系统实现的复杂度。
-
-领域可细分为不同子域，子域可根据自身重要性和功能属性划分为三类子域：
-## 2.1  核心域
-决定产品和公司核心竞争力的子域，是业务成功的主要因素和公司的核心竞争力。
-
-## 2.2  通用域
-没有太多个性化需求，同时被多个子域使用的通用功能子域。比如认证、权限等，无企业特点限制，无需太多定制化。
-
-## 2.3  支撑域
-既不包含决定产品和公司核心竞争力的功能，也不包含通用功能的子域，但又是必需的支撑域。支撑域具有企业特性，但不具通用性，例如数据代码类的数据字典等系统。
-
-## 2.4 划分核心域/通用域/支撑域的意义
-划分是为了区分不同子域在公司内的不同功能属性和重要性，从而公司可对不同子域采取不同的资源投入和建设策略，其关注度和资源投入策略不同。找到核心域，且重点关注核心域。
-
-# 3 总结
-领域的核心思想是将问题域逐级细分，降低业务理解和系统实现的复杂度。
-通过领域细分，逐步缩小微服务需要解决的问题域，构建合适的领域模型，映射成系统就是微服务。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(2)-\351\231\220\347\225\214\344\270\212\344\270\213\346\226\207(bounded context).md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(2)-\351\231\220\347\225\214\344\270\212\344\270\213\346\226\207(bounded context).md"
deleted file mode 100644
index d79cc79eab..0000000000
--- "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(2)-\351\231\220\347\225\214\344\270\212\344\270\213\346\226\207(bounded context).md"	
+++ /dev/null
@@ -1,55 +0,0 @@
-限界上下文定义领域边界，以确保每个上下文含义在它特定的边界内都具有唯一含义，领域模型则存于该边界内。
-
-# 通用语言
-## 定义
-事件风暴过程中，通过团队交流达成共识的，能够简单、清晰、准确描述业务涵义和规则的语言。限界上下文中的通用语言提供了设计领域模型的概念术语。
-
-通用语言是必须通过与领域专家详细讨论后才得到的统一语言，不管你在团队承担什么角色，在同一领域的软件生命周期里都使用统一语言交流。通用语言定义上下文含义，可解决交流障碍，使领域专家和开发人员能够协作，从而确保业务需求的正确表达。
-## 组成
-通用语言包含术语和用例场景，能够直接反映在代码。通用语言中的
-- 名词
-给领域对象等概念命名，如商品、订单，对应**实体**对象
-- 形容词
-描述这些概念
-- 动词
-表示可完成的操作，比如一个动作或事件，如商品已下单、订单已付款，对应**领域事件**或**命令**
-
-通用语言贯穿DDD。基于它，代码可读性更好，最后能将业务需求准确转化为代码设计。
-![](https://img-blog.csdnimg.cn/2020092919365579.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-
-1. 事件风暴时，领域专家会和设计、开发人员一起建立领域模型，在领域建模过程中形成通用的业务术语和用户故事，这也是一个项目团队统一语言的过程
-2. 通过用户故事分析会形成一个个领域对象，这些领域对象对应领域模型的业务对象，每一个业务对象和领域对象都有通用的名词术语，并且一一映射
-3. 微服务代码模型源于领域模型，每个代码模型的代码对象跟领域对象一一对应
-
-可以表格记录事件风暴和微服务设计过程中产生的领域对象及属性。比如，领域对象在DDD分层架构中的位置、属性、依赖关系以及与代码模型对象的映射关系等。
-
-DDD分析和设计过程中的每一个环节都需要保证限界上下文内术语的统一，在代码模型设计的时侯就要建立领域对象和代码对象的一一映射，从而**保证业务模型和代码模型的一致，实现业务语言与代码语言的统一**。
-
-做到这点，就建立了领域对象和代码对象的映射关系，即可指导开发准确无误按设计文档完成微服务开发。即使是不熟悉代码的业务人员，也可很快找到代码位置。
-
-# 限界上下文
-限界上下文就是用以确定语义所在的领域边界。就可在统一的领域边界内用统一的语言进行交流。
-
-封装通用语言和领域对象，提供上下文环境，保证在领域之内的一些术语、业务相关对象等（通用语言）无二义性。
-这个边界定义了模型的适用范围，使团队所有成员能够明确地知道什么应该在模型中实现，不应该在模型中实现。
-
-## 案例
-业务的通用语言也有它的业务边界。限界上下文就是用来细分领域，从而定义通用语言所在的边界。
-
-如电商领域的商品，商品在不同阶段有不同术语：
-- 销售阶段是商品
-- 运输阶段则变成货物
-
-同一个东西，由于业务领域不同，赋予了这些术语不同涵义和职责边界，这个边界就可能会成为未来微服务设计的边界。领域边界就是通过限界上下文来定义的。
-
-# 限界上下文和微服务
-子域还可根据需要进一步拆分为子子域。如支付子域，继续拆为收款、付款子子域。拆到一定程度后，有些子子域的领域边界可能变成限界上下文的边界了。
-
-子域可能会包含多个限界上下文。
-如理赔子域就包括报案、查勘和定损等多个限界上下文（限界上下文与理赔的子子域领域边界重合）。也有可能子域本身的边界就是限界上下文边界，如投保子域。
-
-每个领域模型都有它对应的限界上下文。领域内所有限界上下文的领域模型构成整个领域的领域模型。
-理论上限界上下文就是微服务的边界。我们将限界上下文内的领域模型映射到微服务，就完成了从问题域到软件的解决方案。
-
-**限界上下文是微服务设计和拆分的主要依据**。
-在领域模型中，如果不考虑技术异构、团队沟通等因素，一个限界上下文理论上就可以设计为一个微服务。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(3)-\347\220\206\350\247\243\345\256\236\344\275\223.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(3)-\347\220\206\350\247\243\345\256\236\344\275\223.md"
deleted file mode 100644
index 7e46779baf..0000000000
--- "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(3)-\347\220\206\350\247\243\345\256\236\344\275\223.md"
+++ /dev/null
@@ -1,169 +0,0 @@
-# 1 前言
-**实体是领域模型中的领域对象。**
-
-MVC开发人员总将关注点放在数据，而非领域。因为在软件开发中，DB占据主导地位。首先考虑的是数据的属性(即数据库的列)和关联关系(外键关联)，而不是富有行为的领域概念。
-导致将数据模型直接反映在对象模型，那些表示领域模型的实体(Entity)被包含了大量getter/setter。虽然在实体模型中加入getter/setter并非大错， 但这不是DDD做法。
-
-> 过于强调实体的作用却忽视了值对象。受到DB和持久化框架影响，实体被滥用，于是开始讨论如何避免大范围使用实体...
-
-# 2 为什么使用实体
-当我们需要考虑一个对象的个性特征或需要区分不同对象时，就引入实体这个领域概念。
-
-一个实体是一个**唯一**的东西，可在一段时间内**持续变化**。
-这些对象重要的不是属性，而是其延续性和标识，会跨越甚至超出软件生命周期。
-
-也正是 **唯一身份标识和可变性(mutability)** 特征将实体对象区别于值对象。
-
-实体建模并非总是完美。很多时候，一个领域概念应该建模成值对象，而非实体。这意味着DDD开发CRUD软件系统时可能更适用。但由于只从数据出发，CRUD系统是不能创建出好的业务模型的。
-
-在可以使用DDD时，我们会将数据模型转变为实体模型。
-
-**通过标识区分对象，而非属性**：
-此时应将标识作为主要的模型定义。同时保持简单类定义，关注对象在生命周期中的连续性和唯一标识性。不应该通过对象的状态形式和历史来区分不同实体……对于什么是相同的东西，模型应该给出定义。
-
-如何正确使用和设计实体呢？
-# 3 唯一标识
-在实体设计早期，关注能体现实体身份唯一性的主要属性和行为及如何查询实体，忽略次要的属性和行为。
-
-> 设计实体时，首先考虑实体的本质特征，特别是实体的唯一标识和对实体的查找，而不是一开始便关注实体的属性和行为。只有在对实体的本质特征有用的情况下，才加入相应的属性和行为。
-
-找到多种能够实现唯一标识性的方式，同时考虑如何在实体生命周期内维持唯一性。
-实体的唯一标识不见得一定有助对实体的查找和匹配。将唯一标识用于实体匹配通常取决于标识的可读性。
-比如：
-- 若系统提供根据人名查找功能，但此时一个Person实体的唯一标识可能不是人名，因为重名情况很多
-- 若某系统提供根据公司税号的查找功能，税号便可作为Company实体的唯一标识
-
-值对象可用于存放实体的唯一标识。值对象是不变(immutable)的，这就保证了实体身份的稳定性，并且与身份标识相关的行为也可得到集中处理。便可避免将身份标识相关的行为泄漏到模型的其他部分或客户端中去。
-
-## 3.1 创建实体身份标识的策略
-每种技术方案都存在副作用。比如将关系型DB用于对象持久化时，这样的副作用将泄漏到领域模型。创建前需考虑标识生成的时间、关系型数据的引用标识和ORM在标识创建过程中的作用等，还会考虑如何保证唯一标识的稳定性。
-
-- 参见
-[DDD领域驱动设计实战 - 创建实体身份标识的常用策略](https://javaedge.blog.csdn.net/article/details/108899385)
-
-## 3.2 标识稳定性
-绝大多数场景不应修改实体的唯一标识，可在实体的整个生命周期中保持标识的稳定性。
-
-可通过一些简单措施确保实体标识不被修改。可将标识的setter方法向用户隐藏。也可在setter方法种添加逻辑以确保标识在已存在时不再更新，比如可使用一些断言：
-- `username`属性是`User`实体的领域标识，该属性只能进行一次修改，并且只能在User对象内修改。setter方法`setUsername`实现了自封装性且对客户端不可见。当实体的public方法自委派给该setter方法时，该方法将检查`username`属性，看是否已被赋值。若是，表明该User对象的领域标识已经存在，程序将抛异常。
-![](https://img-blog.csdnimg.cn/20201002034914322.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFF00,t_70#pic_center)
-这个setter方法并不会阻碍Hibernate重建对象，因对象在创建时，它的属性都是使用默认值，且采用无参构造器，因此`username`属性的初始值为null。然后，Hibernate将调用setter方法，由于username属性此时为null，该 setter方法得以正确地执行，username属性也将被赋予正确的标识值。
-
-# 4 实体的形态
-## 4.1 业务形态
-战略设计时，实体是领域模型的一个重要对象。领域模型中的实体是多个属性、操作或行为的载体。
-
-事件风暴中，可根据命令、操作或者事件，找出产生这些行为的业务实体对象，进而按业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类，形成聚合。
-
-**实体和值对象是组成领域模型的基础单元。**
-
-## 4.2 代码形态
-即实体类，包含实体的属性和方法，通过这些方法实现实体自身的业务逻辑。
-采用充血模型：
-- 该实体相关的所有业务逻辑都在实体类的方法中实现
-- 跨多个实体的领域逻辑则在领域服务中实现
-## 4.3 运行形态
-实体以DO（领域对象）形式存在，每个实体对象都有唯一ID。可对实体做多次修改，所以一个实体对象可能和它之前状态存在较大差异。但它们**拥有相同身份标识(identity)**，所以始终是同一实体。
-
-比如商品是商品上下文的一个实体，通过唯一的商品ID标识，不管这商品的数据（比如价格）如何变，商品ID不会变，始终是同一商品。
-
-## 4.4 数据库形态
-DDD先构建领域模型，针对业务场景构建实体对象和行为，再将实体对象映射到数据持久化对象。
-
-在领域模型映射到数据模型时，一个实体可能对应0、1或多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。在某些场景中，有些实体只是暂驻静态内存的一个运行态实体，无需持久化。比如，基于多个价格配置数据计算后生成的折扣实体。
-
-有些复杂场景，实体与持久化对象可能是一对多或多对一：
-- 一对多
-用户user与角色role两个持久化对象可生成权限实体，一个实体对应两个持久化对象
-- 多对一
-有时为避免DB的联表查询，会将客户信息customer和账户信息account两类数据保存至同一张数据库表，客户和账户两个实体可根据需要从一个持久化对象中生成
-
-## 实体本质的探索
-一开始在Java代码中建模大量实体关系。将太多关注点放在数据库、表、列和对象映射上。导致所创建的模型实际上只是含有大量getter/setter的贫血领域模型。
-我们应该在DDD 上有更多的思考。
-如果我们认为对象就是一组命名的类和在类上定义的操作，除此之外并不包含其他内容，那就错了。
-如果一些特定的领域场景会在今后继续使用，这时可以用一个轻量的文档将它们记录下来。简单形式的通用语言可以是一组术语和一些简单的用例场景。 但是，如果我们就此认为通用语言只包含术语和用例场景，那么我们又错了。在最后，通用语言应该直接反映在代码中，而要保持设计文档的实时更新是非常困难的，甚至是不可能的。
-
-# 5 创建实体
-新建一个实体时，我们总期望通过构造器就能初始化足够多的实体状态，因为这样更容易通过各种条件查找到该实体。
-
-在使用及早生成唯一标识的策略时，构造器至少需接受唯一标识参数。若还有可能通过其他方式查找实体，比如名字或描述信息，那应该将这些参数一并传给构造器。
-
-有时一个实体维护一或多个不变条件(Invariant，在整个实体生命周期中都必须保持事务一致性的一种状态) 。
-> 不变条件主要是聚合所关注的，但聚合根也是实体。
-
-如果实体的不变条件要求该实体所包含的对象都不能为null或必须由其他状态计算所得，那么这些状态也需作为参数传递给构造器。
-
-```java
-public class User extends Entity {
-	...
-	// 每一个User对象都必须包含tenantld、username, password和person属性。
-	// 即在User对象得到正确实例化后，这些属性不能为null
-	// 由User对象的构造器和实例变量对应的setter方法保证这点
-	protected User (Tenantld aTenantld
-					String aUsername,
-					String aPassword,
-					Person aPerson) (
-		this();
-		this.setPassword(aPassword);
-		this.setPerson(aPerson);
-		this.setTenantld(aTenantld);
-		this.setUsername(aUsername);
-		this.initialize();
-	}
-	...
-	protected void setPassword(String aPassword) { 
-		if (aPassword == null) {
-			throw new 11legalArgumentException(
-				"The password may not be set to null.");
-		)
-		this.password = aPassword;
-	)
-	
-	protected void setPerson(Person aPerson) (
-		if (aPerson == null) ( 
-			throw new IllegalArgumentException("The person may not be set to null.");
-		}
-		this.person = aPerson;
-	}
-	
-	protected void setTenantld(Tenantld aTenantld) ( 
-		if (aTenantld == null) {
-			throw new IllegalArgumentException("The tenantld may not be set to null."); 
-		}
-		this.tenantld = aTenantld;
-	}
-	protected void setUsername(String aUsername) (
-		if (this.username != null) {
-			throw new IIlegalStateException("The username may not be changed.n);
-		}
-		if (aUsername == null) {
-			throw new IllegalArgumentException("The username may not be set to null."); 
-		}
-		this.username = aUsername;
-	}	
-```
-User对象展示了一种自封装性。在构造器对实例变量赋值时，把操作委派给实例变量对应的setter方法，便保证了实例变量的自封装性。实例变量的自封装性使用setter方法来决定何时给实例变量赋值。
-
-每个setter方法都“代表着实体”对所传进的参数做非null检查，这里的断言称为守卫(Guard)。setter方法的自封装性技术可能会变得非常复杂。所以对于复杂的创建实体场景，可使用工厂。
-
-User对象的构造函数被声明为 `protected`。 Tenant实体即为User实体的工厂也是同一个模块中唯一能够访问User 构造器的类。这样一来，只有Tenant能够创建User实例。
-
-```java
-public class Tenant extends Entity {
-	// 该工厂简化对User的创建，同时保证了Tenantld在User和Person对象中的正确性
-	// 该工厂能够反映通用语言。
-	public User registerUser(String aUsername,
-							 String aPassword,
-							 Person aPerson) {
-		aPerson.setTenantld(this.tenantld());
-		User user = new User(this.tenantld(), aUsername, aPassword, aPerson);
-		return user;
-}
-```
-
-> 参考
-> - https://tech.meituan.com/2017/12/22/ddd-in-practice.html
-> - 《实现领域驱动设计》
-> - 实体和值对象：从领域模型的基础单元看系统设计
-> - https://blog.csdn.net/Taobaojishu/article/details/106152641
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(4)-\347\220\206\350\247\243\345\200\274\345\257\271\350\261\241.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(4)-\347\220\206\350\247\243\345\200\274\345\257\271\350\261\241.md"
deleted file mode 100644
index 0bea955f38..0000000000
--- "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230(4)-\347\220\206\350\247\243\345\200\274\345\257\271\350\261\241.md"
+++ /dev/null
@@ -1,132 +0,0 @@
-**值对象也是领域模型中的领域对象。**
-
-应尽量使用值对象建模而非实体。即便一个领域概念必须建模成实体，在设计阶段也应更偏向于将其作为值对象。因为它可以非常容易进行创建、测试、使用、优化和维护。
-# 1 为什么使用值对象？
-> 曾经我们都滥用实体建模。在用户和权限等概念进入协作领域前，实体建模并没有带来什么坏处。在项目启动时，釆用了常用的建模方式：将领域模型中所有属性映射到对应的数据库表。并且为所有属性创建setter/getter。由于每个对象都有一个数据库主键，各个实体被组织在了一个庞大且复杂的对象网。这种建模方式是一种数据建模方式，很大程度受关系型DB影响，认为所有都需范式化，并通过外键关联引用。但其实全然面向实体的思维方法不仅没必要，而且还浪费开发时间。
-
-**在将领域概念建模成值对象时，应将通用语言考虑在内，这是建模值对象的首要原则。**
-
-那如何确定一个领域概念是否应该建模成一个值对象呢？就需要理解值对象的特征。
-# 2 值对象的特征
-考虑一个领域概念是否有如下特征：
-- 度量或描述了领域中的一件东西
-- 可作为不变量
-- 将不同的相关的属性组合成一个概念整体(Conceptual Whole)
-- 当度量和描述改变时，可以用另一个值对象予以替换
-- 可以和其他值对象进行相等性比较
-- 不会对协作对象造成副作用
-
-当你只关心某个对象的属性时，该对象便可作为一个值对象。为其添加有意义的属性，并赋予它相应的行为。需要将值对象看成不变对象，**不要给它任何身份标识**， 还应尽量避免像实体对象一样的复杂性。
-
-在设计得当时，我们可创建和传递值对象实例，甚至在用完后直接扔了。不用担心客户端对值对象的修改。一个值对象的生命周期可长可短，就像个无害的红细胞在系统中来往。
-> 从该角度来看待值对象是个很大转变，就像从没有GC的语言转变到有GC语言。
-
-
-《实现领域驱动设计》对值对象的定义：通过对象属性值来识别的对象，它将多个相关属性组合为一个概念整体。DDD中描述领域的特定方面，并且是一个没有标识符的对象。
-
-**值对象本质上就是一个集**。该集合有若干用于
-- 描述目的
-- 具有整体概念
-- 不可修改
-
-的属性。该集合存在的意义是在领域建模的过程中，值对象可保证属性归类的清晰和概念的完整性，避免属性零碎。
-
-# 3 案例
-人员实体包括：姓名、年龄、性别及所在省、市、县和街道等属性。这样显示地址相关属性就很零碎。
-可将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”，该**集合就是值对象**。
-
-# 4 值对象的形态
-## 4.1 业务形态
-值对象是DDD领域模型中的一个基础对象，跟实体一样源于事件风暴所构建的领域模型，都包含若干属性，与实体一起构成聚合。
-
-实体是业务对象，具有业务属性、业务行为和业务逻辑。
-值对象只是若干个属性的集合，只有
-- 数据初始化操作
-- 有限的不涉及修改数据的行为
-- 基本不包含业务逻辑
-
-值对象的属性集虽然在物理上独立，但在逻辑上仍是实体属性的一部分，以描述实体的特征。
-
-也有部分共享的标准类型的值对象，它们有自己的限界上下文及持久化对象，可建立共享的数据类微服务，比如数据字典。
-
-## 4.2 代码形态
-如果值对象是
-- 单一属性，直接定义为实体类的属性
-- 属性集，设计为类，包含具有整体概念的多个属性，这样的值对象无ID，会被实体整体引用
-
-比如电商系统中的Person用户实体：
-- 有单一属性的值对象，比如id、name
-- 也包含多个属性的值对象，比如address
-
-## 4.3 运行形态
-除数据初始化和整体替换的行为，少有更多业务行为。
-
-比如一个用户实体可有多个收货地址，多地址序列化后可嵌入人员的地址属性。**值对象创建后不允许修改，只能用另外一个值对象来整体替换**。
-若将值对象嵌入到实体，有如下方式：
-### 4.3.1  属性嵌入
-当引用如下之一：
-- 单一属性的值对象
-- 只有一条记录的多属性值对象的实体
-### 4.3.2 序列化大对象
-当引用一或多条记录的多属性值对象的实体时。
-
-- 以序列化大对象方式形成的人员实体对象，收货地址值对象被序列化成大对象JSON串后，嵌入人员实体
-
-## 4.4 DB形态
-设计值对象是期望转“数据建模为中心”为“领域建模为中心”，减少 DB 表的复杂度。
-
-# 5  值对象简化DB的最佳实践
-传统数据建模大多根据数据库范式设计，每个数据库表对应一个实体，每个实体的属性值用单列存储，一个实体主表会对应N个实体从表。
-而值对象简化了DB设计，多采用反范式，**值对象的属性值和实体对象的属性值保存在同一DB实体表**。
-
-比如人员和地址，要设计实体和数据模型，有如下解决方案：
-1. 把地址值对象的所有属性放入人员实体表，创建人员实体、人员数据表
-会破坏地址的业务含义和概念完整性
-2. 创建人员和地址两个实体，同时创建人员和地址两张表
-增加了不必要的实体和表，需要处理多个实体和表的关系，导致数据库复杂性剧增
-
-有没有一种设计可使得**业务含义清晰，又不让数据库变复杂**？综合以上方案优势，扬长避短：
-- 领域建模时，把地址作为值对象，人员作为实体，即可保留地址的业务含义和概念完整性
-- 数据建模时，将地址的属性值嵌入人员实体数据库表，只创建人员数据库表。这既可兼顾业务含义和表达，又不会复杂化DB
-
-值对象就是通过该方式，简化DB设计：
-1. **领域建模时，将部分对象设计为值对象，保留对象的业务含义，同时又减少了实体数量**
-2. **数据建模时，我们可以将值对象嵌入实体，减少实体表的数量，简化DB设计**
-
-要发挥对象的威力，就需优先领域建模，弱化DB作用，只把DB作为一个保存数据的仓库。即使违反DB设计原则，也不必大惊小怪，只要业务能顺利运行，无伤大雅。
-
-## 分析
-虽然优势是可简化DB复杂度。但若使用不当，优势就会成劣势。所以必须理解值对象的适用场景。
-
-值对象采用序列化大对象的方式简化DB设计，减少实体表的数量，可简单、清晰表达业务概念。该方式虽然降低DB设计复杂度，却无法满足基于值对象的快速查询，导致搜索值对象的属性值变难。
-
-值对象采用属性嵌入的方式提升了DB性能，但若实体引用的值对象过多，则会导致实体堆积一堆缺乏概念完整性的属性，这样值对象就会失去业务含义，操作也不方便。
-
-所以对照优劣势并结合实际业务场景，才能发挥值对象的最大作用。
-
-# 6 实体 V.S 值对象
-主要区别如下：
-- 实体有唯一性，值对象没有。比如用户具有唯一性，一旦某用户被系统管理，它就被赋予了在事件、流程和操作中被唯一识别的能力
-- 实体着重唯一性和延续性，不在意属性的变化，属性全变了，它还是自己；值对象着重描述性，对属性变化敏感，属性变了，它就不是自己了
-- 实体和值对象也可能随着系统业务关注点的不同而更换位置。比如，如果另一个限界上下文更关注地址，而不关注与这个地址产生联系的人员，那就把地址设计成实体，人员设计成值对象
-
-比如多人的单位地址是一样的，怎么处理：
-- 许多人可能属同一地址
-- 许多地址也可能属同一人
-
-所以人和地址既可分别作为实体而把对方作为值对象，也可共同作为实体描述业务，这正是业务设计的意义，而不是非黑即白。
-
-DDD提倡从**领域模型设计**出发，而非先设计数据模型。
-传统数据模型设计通常一个表对应一个实体，一个主表关联多个从表，当实体表太多，就很容易陷入复杂DB设计，领域模型就很容易被数据模型绑架。
-在领域模型中人员是实体，地址是值对象，地址值对象被人员实体引用。
-设计数据模型时
-- 地址值对象可作为一个属性集整体嵌入人员实体
-- 也可以序列化大对象的形式加入人员的地址属性
-
-同样一个对象在不同场景，可能设计不同：
-- 地址会被某一实体引用，只描述实体，并且其值只能整体替换，这时就可将地址设计为值对象，比如收货地址
-- 地址会被经常修改，地址作为一个独立对象存在，这时应设计为实体，比如行政区划中的地址信息
-
-> 参考
-> - 实体和值对象：从领域模型的基础单元看系统设计
-> - 《实现领域驱动设计》
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230-\345\210\206\345\261\202\346\236\266\346\236\204.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230-\345\210\206\345\261\202\346\236\266\346\236\204.md"
index a103c41e89..3f2782f10c 100644
--- "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230-\345\210\206\345\261\202\346\236\266\346\236\204.md"
+++ "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230-\345\210\206\345\261\202\346\236\266\346\236\204.md"
@@ -1,73 +1,72 @@
+整洁架构、CQRS、六边形架构等微服务架构都旨在“高内聚低耦合”。那DDD分层架构又如何？
+
 # 1 DDD分层架构
 ## 1.1 分层架构的基本原则
-**每层只与位于其下方的层发生耦合。**
+**每层只能与位于其下方的层发生耦合。**
 
 ## 1.2 分层架构的分类
-- 严格分层架构(Strict Layers Architecture)
-某层只能与其直接下层耦合，即我的奴隶的奴隶，不是我的奴隶。
--  松散分层架构(Relaxed Layers Architecture)
-允许任意上层与任意下层耦合。由于用户接口层和应用服务通常需要与基础设施打交道，许多系统都是该架构。
+1. 严格分层架构(Strict Layers Architecture)
+某层只能与其直接下方的层耦合
+2. 松散分层架构(Relaxed Layers Architecture)
+允许任意上方层与任意下方层耦合。由于用户接口层和应用服务通常需要与基础设施打交道，许多系统都是该架构
 
 >  较低层有时也可与较高层耦合，但只限于采用观察者 (Observer)模式或者调停者(Mediator)模式场景。
-> 较低层**绝不能直接访问**较高层。例如，在使用调停者模式时，较高层可能实现了较低层的接口，然后将实现对象作为参数传递到较低层。当较低层调用该实现时， 它并不知道实现出自何处。
+> 较低层**绝对不能直接访问**较高层。例如，在使用调停者模式时，较高层可能实现了较低层的接口，然后将实现对象作为参数传递到较低层。当较低层调用该实现时， 它并不知道实现出自何处。
 
 ## 1.3 分层架构演进
 ### 1.3.1 传统四层架构
 
-![](https://img-blog.csdnimg.cn/20201010103034893.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFF00,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20201010103034893.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFF00,t_70#pic_center)
 
 将领域模型和业务逻辑分离出来，并减少对基础设施、用户界面甚至应用层逻辑的依赖，因为它们不属业务逻辑。将一个夏杂的系统分为不同的层，每层都应该具有良好的内聚性，并且只依赖于比其自身更低的层。
 
-传统分层架构的**基础设施层**位于底层，持久化和消息机制便位于该层。
-这里的消息包含
-- MQ消息
-- SMTP
-- 文本消息(SMS)
 
-可将基础设施层中所有组件看作应用程序的低层服务，较高层与该层发生耦合以复用技术基础设施。即便如此，依然应`避免核心的领域模型对象与基础设施层直接耦合`。
+传统分层架构的**基础设施层**位于底层，持久化和消息机制便位于该层。
+这里的消息包含MQ消息、SMTP或文本消息(SMS)。可将基础设施层中所有组件看作应用程序的低层服务，较高层与该层发生耦合以复用技术基础设施。即便如此，依然应避免核心的领域模型对象与基础设施层直接耦合。
 
 ### 1.3.2 改良版四层架构
 #### 传统架构的缺陷
-DDD初创开发团队发现，将基础设施层放在最底层存在缺点，比如此时领域层中的一些技术实现就很困难：
+DDD初创开发团队发现，将基础设施层放在最底层存在缺点。
+比如此时领域层中的一些技术实现令人头疼：
 - 违背分层架构的基本原则
 - 难以编写测试用例
 
+
 何解？
-使用**依赖反转设计原则**：低层服务（如基础设施层）应依赖高层组件（比如用户界面层、应用层和领域层）所提供的接口。
+
+使用**依赖反转设计原则**：低层服务（如基础设施层）应依赖高层组件（比如用户界面层、应用层和领域层）所提供接口。
 
 #### 应用依赖反转原则
 - 依赖反转原则后的分层方式：基础设施层在最上方，可实现所有其他层中定义的接口
-![](https://img-blog.csdnimg.cn/20201010221054540.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20201010221054540.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
+#### 依赖反转原则真的可以支持所有层吗？
+有人认为依赖反转原则中只存在两层：最上方和最下方，上层实现下层定义的抽象接口。因此上图的基础设施层将位于最上方，而用户接口层、应用层和领域层应作**同层且都位于下方**。对此大家可保留自己意见。在六边形［Cockburn］或端口和适配器架构中对此做详细讲解。
 
-> 依赖反转原则真的可以支持所有层吗？
-有人认为依赖反转原则中只存在两层：最上方和最下方，上层实现下层定义的抽象接口。因此上图的基础设施层将位于最上方，而用户接口层、应用层和领域层应作**同层且都位于下方**。对此大家可保留自己意见。
 
 # 2 各层职责
 ## 2.1 用户接口层
 一般包括用户接口、Web 服务等。
 
 只处理用户显示和用户请求，不应包含领域或业务逻辑。 
-有人认为，既然用户接口需验证用户输入，就无可避免应该包含业务逻辑。事实上，用户接口所进行的验证和对领域模型的验证不同：对那些粗制滥造且只面向领域模型的验证行为，应该予以限制。
+有人可能认为，既然用户接口需验证用户输入，那就应该包含业务逻辑。事实上，用户接口所进行的验证和对领域模型的验证不同：对那些粗制滥造且只面向领域模型的验证行为，应该予以限制。
 
-如果用户接口使用了领域模型中的对象，那么此时**领域对象仅限于数据渲染展现**。在采用这种方式时，可使用展现模型对用户接口与领域对象进行解耦。
+如果用户接口使用了领域模型中的对象，那么此时领域对象仅限于数据渲染展现。在采用这种方式时，可使用展现模型对用户接口与领域对象进行解耦。
 由于用户可能是人，也可能是其他系统，有时用户接口层将采用开放主机服务的方式向外提供API。
-**用户接口层是应用层的直接用户。**
-用户接口层在于前后端调用的适配。若你的微服务要提供服务给很多外部应用，而对每个外部应用的入参出参都不同，你不可能开发一堆一对一的应用服务，这时Facade接口就起到了很好的作用，包括DO和DTO对象的组装和转换。
+用户接口层是应用层的直接用户。
+
+用户接口层很重要，在于前后端调用的适配。若你的微服务要面向很多应用或渠道提供服务，而每个渠道的入参出参都不一样，你不太可能开发出太多应用服务，这样Facade接口就起很好的作用了，包括DO和DTO对象的组装和转换等。
 
 ## 2.2 应用层
-主要包含应用服务，理论上**不应有业务规则或逻辑**，而主要是面向用例和流程相关的操作。
+主要包含应用服务，理论上不应有业务规则或逻辑，而主要是面向用例和流程相关的操作。
 
 - 应用层位于领域层之上，因为领域层包含多个聚合，所以它可协调**多个聚合服务和领域对象完成服务编排和组合**，协作完成业务。
 - 应用层也是微服务间的交互通道，它可调用其它微服务，完成**微服务间的服务组合和编排**。
 
-开发设计时，不要将本该放在领域层的业务逻辑放到应用层。因为**庞大的应用层会使领域模型失焦，时间一长，微服务就会退化为MVC架构，导致业务逻辑混乱**。
+开发设计时，不要将本该放在领域层的业务逻辑放到应用层。因为庞大的应用层会使领域模型失焦，时间一长微服务就会演化为传统MVC三层架构，导致业务逻辑混乱。
 
-应用服务是在应用层，负责
-- 服务的组合、编排、转发、转换和传递，处理业务用例的执行顺序以及结果的拼装，以粗粒度服务通过API网关发布到前端
-- 安全认证
-- 权限校验
-- 事务控制
-- 发送或订阅领域事件
+应用服务是在应用层，负责服务的组合、编排和转发，处理业务用例的执行顺序以及结果的拼装，以粗粒度服务通过API网关发布到前端。
+还可进行安全认证、权限校验、事务控制、发送或订阅领域事件等。
 
 ## 2.3 领域层
 主要包含聚合根、实体、值对象、领域服务等领域模型中的领域对象。
@@ -80,19 +79,11 @@ DDD初创开发团队发现，将基础设施层放在最底层存在缺点，
 实体和领域服务在实现业务逻辑上不是同级，当领域中的某些功能，单一实体或值对象无法实现，就会用到领域服务，它可组合聚合内的多个实体或值对象，实现复杂业务逻辑。
 
 ## 2.4 基础层
-为其它各层提供通用技术基础服务：
-- 三方工具
-- 驱动
-- MQ
-- API网关
-- 文件
-- 缓存
-- DB
-最常用的
+为其它各层提供通用的技术基础服务，包含三方工具、驱动、MQ、API网关、文件、缓存、DB、基础服务等。最常用的还是提供DB持久化。
 
 基础层包含基础服务，它采用依赖反转，封装基础资源服务，实现应用层、领域层与基础层解耦。
 
-MVC架构由于上层应用对DB强耦合，很多公司在架构演进最怕换DB，一旦更换，可能需重写一堆代码。
+传统架构由于上层应用对DB强耦合，很多公司在架构演进最怕换DB，一旦更换，可能需重写一堆代码。
 但采用依赖反转，应用层即可通过解耦保持独立核心业务逻辑。当DB变更，只需更换DB基础服务。
 
 # 3 微服务架构演进
@@ -118,48 +109,44 @@ MVC架构由于上层应用对DB强耦合，很多公司在架构演进最怕换
 
 在微服务内部，实体的方法被领域服务组合和封装，领域服务又被应用服务组合和封装。在服务逐层组合和封装的过程中，你会发现这样一个有趣的现象。
 ![](https://img-blog.csdnimg.cn/20201011030454710.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-服务设计时，你并不一定能完整预测有哪些下层服务会被多少个上层服务组装，因此领域层通常只提供一些原子服务，比如领域服务a、b、c。但随系统功能增强和外部接入越来越多，应用服务不断丰富。终有一日，你会发现领域服务b和c同时多次被多个应用服务调用了，执行顺序也基本一致。这时你可以考虑将b和c合并，再将应用服务中b、c的功能下沉到领域层，演进为新的领域服务（b+c）。这样既减少了服务的数量，也减轻了上层服务组合和编排的复杂度。
 
-这就是服务演进，领域模型会越来越能适应需求快速变化。
 
-# 从MVC跨越到DDD
-由于层间松耦合，可专注本层设计，而不必关心其它层，也不必担心自己的设计会影响其它层。即DDD成功降低层与层之间的依赖。
 
-分层架构使得程序结构更清晰，升级和维护更容易。修改某层代码时，只要本层接口参数不变，其它层不必修改。即使本层接口发生变化，也只影响相邻的上层，修改工作量小且可控。
+我们看下上面这张图。在服务设计时，你并不一定能完整预测有哪些下层服务会被多少个上层服务组装，因此领域层通常只提供一些原子服务，比如领域服务a、b、c。但随着系统功能增强和外部接入越来越多，应用服务会不断丰富。有一天你会发现领域服务b和c同时多次被多个应用服务调用了，执行顺序也基本一致。这时你可以考虑将b和c合并，再将应用服务中b、c的功能下沉到领域层，演进为新的领域服务（b+c）。这样既减少了服务的数量，也减轻了上层服务组合和编排的复杂度。
+
+你看，这就是服务演进的过程，它是随着你的系统发展的，最后你会发现你的领域模型会越来越精炼，越来越能适应需求的快速变化。
+
+# 三层架构如何演进到DDD分层架构？
+由于层间松耦合，我们可以专注于本层的设计，而不必关心其它层，也不必担心自己的设计会影响其它层。可以说，DDD成功地降低了层与层之间的依赖。
+
+其次，分层架构使得程序结构变得清晰，升级和维护更加容易。我们修改某层代码时，只要本层的接口参数不变，其它层可以不必修改。即使本层的接口发生变化，也只影响相邻的上层，修改工作量小且错误可以控制，不会带来意外的风险。
+
+那我们该怎样转向DDD分层架构呢？不妨看看下面这个过程。
 
 传统企业应用大多是单体架构，而单体架构则大多是三层架构。三层架构解决了程序内代码间调用复杂、代码职责不清的问题，但这种分层是逻辑概念，在物理上它是中心化的集中式架构，并不适合分布式微服务架构。
 
-DDD分层要类似三层架构，只是在DDD中，这些要素被重新划分了层，确定了层与层之间的交互规则和职责边界。
+DDD分层架构中的要素其实和三层架构类似，只是在DDD分层架构中，这些要素被重新归类，重新划分了层，确定了层与层之间的交互规则和职责边界。
+
 
 ![](https://img-blog.csdnimg.cn/20201011030552460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
-DDD分层架构相比MVC（只有API）在用户接口层新增了DTO，给前端提供了更多的可使用数据和更高的展示灵活性。
 
-DDD分层架构对三层架构的业务逻辑层进行了更清晰的划分，改善了三层架构核心业务逻辑混乱，代码改动相互影响大的情况。
+三层架构向DDD分层架构演进，主要发生在业务逻辑层和数据访问层。
+
+DDD分层架构在用户接口层引入了DTO，给前端提供了更多的可使用数据和更高的展示灵活性。
 
-MVC架构向DDD分层架构演进，主要发生在业务逻辑层和数据访问层。
-DDD分层架构将业务逻辑层的服务拆分到了应用层和领域层：
-- 应用层快速响应前端的变化
-- 领域层实现领域模型的能力
+DDD分层架构对三层架构的业务逻辑层进行了更清晰的划分，改善了三层架构核心业务逻辑混乱，代码改动相互影响大的情况。DDD分层架构将业务逻辑层的服务拆分到了应用层和领域层。应用层快速响应前端的变化，领域层实现领域模型的能力。
 
-数据访问层和基础层之间：
-- 三层架构数据访问采用DAO方式
-- DDD分层架构的数据库等基础资源访问，采用了仓储（Repository）设计模式，通过依赖倒置实现各层对基础资源的解耦。
+另外一个重要的变化发生在数据访问层和基础层之间。三层架构数据访问采用DAO方式；DDD分层架构的数据库等基础资源访问，采用了仓储（Repository）设计模式，通过依赖倒置实现各层对基础资源的解耦。
 
-> 仓储本身属基础层，但考虑到一个聚合对应一个仓储，为了以后聚合代码整体迁移方便，在微服务代码目录设计时，在聚合目录下增加一个Repository的仓储目录，跟仓储相关的代码都在这个目录下。
+> 关于仓储。仓储本身属于基础层，但考虑到一个聚合对应一个仓储，为了以后聚合代码整体迁移方便，在微服务代码目录设计时，在聚合目录下增加一个Repository的仓储目录，跟仓储相关的代码都在这个目录下。
 这个目录下的代码与聚合的其它业务代码是分开的。如果未来换数据库，只需将Repository目录下的代码替换。而如果聚合需要整体迁移到其它微服务中去，仓储的代码也会一并迁移。
 
 仓储又分为两部分：仓储接口和仓储实现。仓储接口放在领域层中，仓储实现放在基础层。原来三层架构通用的第三方工具包、驱动、Common、Utility、Config等通用的公共的资源类统一放到了基础层。
 
-MVC 到 DDD 具体操作如下：
-### 抽象数据存储层
-一般将Data Access层做抽象，降低系统对DB的直接依赖。
-举个例子：
-- 新建Account实体对象：一个实体（Entity）是拥有ID的域对象，除了拥有数据之外，同时拥有行为。Entity和数据库储存格式无关。
 
-对象储存接口类AccountRepository：Repository只负责Entity对象的存储和读取，而Repository的实现类完成数据库存储的细节。通过加入Repository接口，**底层数据库连接可以通过不同的实现类而替换**。
+# 总结
+DDD分层架构包含用户接口层、应用层、领域层和基础层。通过这些层次划分，我们可以明确微服务各层的职能，划定各领域对象的边界，确定各领域对象的协作方式。
 
-> 参考
-> - 《实现领域驱动设计》
-> - DDD分层架构：有效降低层与层之间的依赖
-> - https://zhuanlan.zhihu.com/p/343388831
-> - https://zhuanlan.zhihu.com/p/342826364
\ No newline at end of file
+参考
+- 《实现领域驱动设计》
+- DDD分层架构：有效降低层与层之间的依赖
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230\357\274\210\344\270\211\357\274\211- \347\220\206\350\247\243\345\256\236\344\275\223.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230\357\274\210\344\270\211\357\274\211- \347\220\206\350\247\243\345\256\236\344\275\223.md"
new file mode 100644
index 0000000000..4fee9afc50
--- /dev/null
+++ "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230\357\274\210\344\270\211\357\274\211- \347\220\206\350\247\243\345\256\236\344\275\223.md"	
@@ -0,0 +1,164 @@
+# 1 前言
+**实体是领域模型中的领域对象。**
+
+传统开发人员总将关注点放在数据，而不是领域。因为在软件开发中，DB占据主导地位。首先考虑的是数据的属性(即数据库的列)和关联关系(外键关联)，而不是富有行为的领域概念。导致将数据模型直接反映在对象模型，那些表示领域模型的实体(Entity)被包含了大量getter/setter。虽然在实体模型中加入getter/setter并非大错， 但这不是DDD的做法。
+
+由于团队成员起初过于强调实体的作用却忽视了值对象。受到DB和持久化框架影响，实体被该团队滥用，于是他们开始讨论如何避免大范围使用实体...
+
+# 2 为什么使用实体
+当我们需要考虑一个对象的个性特征，或需要区分不同对象时，就引入实体这个领域概念。
+
+一个实体是一个**唯一**的东西，可以在一段时间内持续变化。
+这些对象重要的不是属性，而是其延续性和标识，会跨越甚至超出软件生命周期。
+
+也正是 **唯一身份标识和可变性(mutability)** 特征将实体对象区别于值对象。
+
+实体建模并非总是完美方案。很多时候，一个领域概念应该建模成值对象，而非实体对象。这意味着DDD并不总能满足业务需求，开发CRUD软件系统时可能更适用。
+若将CRUD应用在错误的系统——那些更复杂的，需采用DDD的系统一一就有我们后悔的了。由于只从数据出发，CRUD系统是不能创建出好的业务模型的。
+
+在可以使用DDD时，我们会将数据模型转变为实体模型。
+> 通过标识区分对象，而非属性，此时应将标识作为主要的模型定义。同时保持简单类定义，关注对象在生命周期中的连续性和唯一标识性。不应该通过对象的状态形式和历史来区分不同的实体对象……对于什么是相同的东西，模型应该给出定义。
+
+那么如何正确地使用和设计实体？
+
+# 3 唯一标识
+在实体设计早期，关注能体现实体身份唯一性的主要属性和行为及如何查询实体，忽略次要的属性和行为。
+> 设计实体时，首先考虑实体的本质特征，特别是实体的唯一标识和对实体的查找，而不是一开始便关注实体的属性和行为。只有在对实体的本质特征有用的情况下，才加入相应的属性和行为［Evans, p.93］。
+
+找到多种能够实现唯一标识性的方式，同时考虑如何在实体生命周期内维持唯一性。
+实体的唯一标识不见得一定有助对实体的查找和匹配。将唯一标识用于实体匹配通常取决于标识的可读性。
+比如，若系统提供根据人名查找功能，但此时一个Person实体的唯一标识可能不是人名，因为重名情况很多。若某系统提供根据公司税号的查找功能，税号便可作为Company实体的唯一标识。
+
+值对象可用于存放实体的唯一标识。值对象是不变(immutable)的，这就保证了实体身份的稳定性，并且与身份标识相关的行为也可得到集中处理。便可避免将身份标识相关的行为泄漏到模型的其他部分或客户端中去。
+
+## 3.1 创建实体身份标识的策略
+通常来说，每种技术方案都存在副作用。比如将关系型DB用于对象持久化时，这样的副作用将泄漏到领域模型。创建前需考虑标识生成的时间、关系型数据的引用标识和ORM在标识创建过程中的作用等，还会考虑如何保证唯一标识的稳定性。
+
+- 详情参见
+[DDD领域驱动设计实战 - 创建实体身份标识的常用策略](https://javaedge.blog.csdn.net/article/details/108899385)
+
+## 3.2 标识稳定性
+绝大多数场景不应修改实体的唯一标识，可在实体的整个生命周期中保持标识的稳定性。
+
+可通过一些简单措施确保实体标识不被修改。可将标识的setter方法向用户隐藏。也可在setter方法种添加逻辑以确保标识在已经存在的情况下不会再被更新，比如可使用一些断言：
+- `username`属性是`User`实体的领域标识，该属性只能进行一次修改，并且只能在User对象内修改。setter方法`setUsername`实现了自封装性， 且对客户端不可见。当实体的public方法自委派给该setter方法时，该方法将检查`username`属性，看是否已被赋值。若是，表明该User对象的领域标识已经存在，程序将抛异常。
+![](https://img-blog.csdnimg.cn/20201002034914322.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_25,color_FFFF00,t_70#pic_center)
+这个setter方法并不会阻碍Hibernate重建对象，因对象在创建时，它的属性都是使用默认值，且采用无参构造器，因此`username`属性的初始值为null。然后，Hibernate将调用setter方法，由于username属性此时为null，该 setter方法得以正确地执行，username属性也将被赋予正确的标识值。
+
+
+
+# 4 各种状态的实体
+
+DDD的不同设计过程，实体的形态也不同。
+
+## 4.1 业务形态
+
+在战略设计时，实体是领域模型的一个重要对象。领域模型中的实体是多个属性、操作或行为的载体。
+事件风暴中，可以根据命令、操作或者事件，找出产生这些行为的业务实体对象，进而按业务规则将依存度高和业务关联紧密的多个实体对象和值对象进行聚类，形成聚合。
+**实体和值对象是组成领域模型的基础单元。**
+
+## 4.2 代码形态
+实体的表现形式是实体类，该类包含了实体的属性和方法，通过这些方法实现实体自身的业务逻辑。在DDD里，这些实体类通常采用充血模型，与该实体相关的所有业务逻辑都在实体类的方法中实现，跨多个实体的领域逻辑则在领域服务中实现。
+
+## 4.3 运行形态
+实体以DO（领域对象）形式存在，每个实体对象都有唯一ID。可以对实体做多次修改，所以一个实体对象可能和它之前状态存在较大差异。但它们**拥有相同的身份标识(identity)**，所以始终是同一实体。
+
+比如商品是商品上下文的一个实体，通过唯一的商品ID标识，不管这商品的数据（比如价格）如何变，商品ID不会变，始终是同一商品。
+
+## 4.4 数据库形态
+DDD是先构建领域模型，针对实际业务场景构建实体对象和行为，再将实体对象映射到数据持久化对象。
+
+在领域模型映射到数据模型时，一个实体可能对应0个、1个或者多个数据库持久化对象。大多数情况下实体与持久化对象是一对一。在某些场景中，有些实体只是暂驻静态内存的一个运行态实体，它不需要持久化。比如，基于多个价格配置数据计算后生成的折扣实体。
+
+有些复杂场景，实体与持久化对象可能是一对多或多对一：
+- 一对多：用户user与角色role两个持久化对象可生成权限实体，一个实体对应两个持久化对象
+- 多对一：有时为避免DB的联表查询，会将客户信息customer和账户信息account两类数据保存至同一张数据库表，客户和账户两个实体可根据需要从一个持久化对象中生成
+
+
+> 探索实体的本质
+一开始团队便遇到陷阱，在Java代码中建模大量实体-关系。将太多关注点放在数据库、表、列和对象映射上。导致所创建 的模型实际上只是含有大量getter/setter的贫血领域模型。他们应该在DDD 上有更多的思考。那时正值他们将安全处理机制从核心域中分离之际，他们学到了如何使用通用语言来更好地辅助建模。
+但如果我们认为对象就是一组命名的类和在类上定义的操作，除此之外并不包含其他内容，那就错了。在领域模型中还可包含很多其他内容。团队讨论和规范文档可以帮助我们创建更有意义的通用语言。到最后，团队可以直接使用通用语言来进行对话,而此时的模型也能够非常准确地反映通用语言。
+如果一些特定的领域场景会在今后继续使用，这时可以用一个轻量的文档将它们记录下来。简单形式的通用语言可以是一组术语和一些简单的用例场景。 但是，如果我们就此认为通用语言只包含术语和用例场景，那么我们又错了。在最后，通用语言应该直接反映在代码中，而要保持设计文档的实时更新是非常困难的，甚至是不可能的。
+
+# 5 创建实体
+新建一个实体时，我们总是期望通过构造器就能初始化足够多的实体状态，因为这有助于表明该实体的身份，也可帮助客户端更容易查找该实体。
+
+在使用及早生成唯一标识的策略时，构造器至少需接受一个唯一标识参数。若还有可能通过其他方式查找实体，比如名字或描述信息，那应该将这些参数也一并传给构造器。
+
+有时一个实体维护一或多个不变条件(Invariant，在整个实体生命周期中都必须保持事务一致性的一种状态) 。
+> 不变条件主要是聚合所关注的，但由于聚合根通常也是实体，故这里我们也稍作提及。
+
+如果实体的不变条件要求该实体所包含的对象都不能为null状态，或者由其他状态计算所得，那么这些状态需要作为参数传递给构造器。
+
+```java
+public class User extends Entity {
+	...
+	// 每一个User对象都必须包含tenantld、username, password和person属性。
+	// 即在User对象得到正确实例化后，这些属性不能为null
+	// 由User对象的构造器和实例变量对应的setter方法保证这点
+	protected User (Tenantld aTenantld, String aUsername,
+		String aPassword, Person aPerson) (
+		this();
+		this.setPassword(aPassword);
+		this.setPerson(aPerson);
+		this.setTenantld(aTenantld);
+		this.setUsername(aUsername);
+		this.initialize();
+	}
+	...
+	protected void setPassword(String aPassword) { 
+		if (aPassword == null) {
+			throw new 11legalArgumentException(
+				"The password may not be set to null.");
+		)
+		this.password = aPassword;
+	)
+	
+	protected void setPerson(Person aPerson) (
+		if (aPerson == null) ( 
+			throw new IllegalArgumentException(
+			"The person may not be set to null.");
+		}
+		this.person = aPerson;
+	}
+	
+	protected void setTenantld(Tenantld aTenantld) ( 
+		if (aTenantld == null)(
+			throw new IllegalArgumentException(
+				"The tenantld may not be set to null."); }
+		this.tenantld = aTenantld;
+	}
+	protected void setUsername(String aUsername) (
+		if (this.username != null) (
+			throw new IIlegalStateException(
+				"The username may not be changed.n);
+		}
+		if (aUsername == null) ( 
+			throw new IllegalArgumentException(
+				"The username may not be set to null."); 
+		}
+		this.username = aUsername;
+	}	
+```
+User对象展示了一种自封装性。在构造器对实例变量赋值时，它把操作委派给了实例变量所对应的setter方法，这样便保证了实例变量的自封装性。实例变量的自封装性使用setter方法来决定何时给实例变量赋值。
+每个setter方法都“代表着实体”对所传进的参数做非null检查，这里的断言称为守卫(Guard)。setter方法的自封装性技术可能会变得非常复杂。对于那些非常复杂的创建实体的情况，我们可以使用工厂。
+在上面的例子中，你是否注意到User对象的构造函数被声明为 `protected`? Tenant实体即为User实体的工厂也是同一个模块中唯一能够访问User 构造函数的类。这样一来，只有Tenant能够创建User实例。
+
+```java
+public class Tenant extends Entity {
+	// 该工厂简化对User的创建，同时保证了Tenantld在User和Person对象中的正确性
+	// 该工厂能够反映通用语言。
+	public User registerUser(
+		String aUsername,
+		String aPassword,
+		Person aPerson) {
+		aPerson.setTenantld(this.tenantld());
+		
+		User user = new User(this.tenantld(), aUsername, aPassword, aPerson);
+		return user;
+}
+```
+参考
+- https://tech.meituan.com/2017/12/22/ddd-in-practice.html
+- 《实现领域驱动设计》
+- 实体和值对象：从领域模型的基础单元看系统设计
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230\357\274\210\345\233\233\357\274\211- \347\220\206\350\247\243\345\200\274\345\257\271\350\261\241.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230\357\274\210\345\233\233\357\274\211- \347\220\206\350\247\243\345\200\274\345\257\271\350\261\241.md"
new file mode 100644
index 0000000000..6e7ef992a3
--- /dev/null
+++ "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\345\256\236\346\210\230\357\274\210\345\233\233\357\274\211- \347\220\206\350\247\243\345\200\274\345\257\271\350\261\241.md"	
@@ -0,0 +1,130 @@
+**值对象也是领域模型中的领域对象。**
+
+应该尽量使用值对象建模而非实体对象。即便一个领域概念必须建模成实体，在设计时也应更偏向于将其作为值对象容器，而非子实体容器。因为可以非常容易对值对象进行创建、测试、使用、优化和维护。
+
+阅读本文注意思考：应该建模成实体or值对象？如何实现值对象？
+
+# 1 为什么使用值对象？
+> 曾经，SaaSOvation公司团队滥用实体建模。在用户和权限等概念进入协作领域前，实体建模并没有给他们带来什么坏处。在项目启动时，他们釆用了常用的建模方式，即将领域模型中所有的属性都映射到对应的数据库表。并且为所有属性创建setter/getter。由于每个对象都有一个数据库主键，各个实体被组织在了一个庞大且复杂的对象网。这种建模方式是一种数据建模方式，很大程度受关系型DB影响，认为所有都需范式化，并通过外键关联引用。后来SaaSOvation团队才知道，全然面向实体的思维方法不仅没必要，而且还浪费开发时间。
+
+**在将领域概念建模成值对象时，应将通用语言考虑在内，这是建模值对象的首要原则。**
+
+
+如何确定一个领域概念应该建模成一个值对象呢？注意值对象的特征。
+# 2 值对象的特征
+当你决定一个领域概念是否是一个值对象时，需考虑它是否拥有以下特征：
+- 度量或者描述了领域中的一件东西
+- 可以作为不变量
+- 将不同的相关的属性组合成一个概念整体(Conceptual Whole)
+- 当度量和描述改变时，可以用另一个值对象予以替换
+- 可以和其他值对象进行相等性比较
+- 不会对协作对象造成副作用
+
+当你只关心某个对象的属性时，该对象便可作为一个值对象。为其添加有意义的属性，并赋予它相应的行为。需要将值对象看成不变对象，不要给它任何身份标识， 还应尽量避免像实体对象一样的复杂性。
+
+在使用这种方法分析模型时，会发现很多领域概念都可设计成值对象，而非实体对象。
+
+
+在设计得当时，我们可创建和传递值对象实例，甚至在用完后直接扔了。不用担心客户端对值对象的修改。一个值对象的生命周期可长可短，就像个无害的过客在系统中来往。
+从该角度来看待值对象是个很大转变，就像从没有GC的语言转变到有GC语言。
+
+
+虽然创建一个值对象类型非常简单，但是有时甚至连有经验的DDD开发者都面临难题。
+《实现领域驱动设计》对值对象的定义：通过对象属性值来识别的对象，它将多个相关属性组合为一个概念整体。DDD中描述领域的特定方面，并且是一个没有标识符的对象。
+
+**值对象本质上就是一个集**。该集合有若干用于描述目的、具有整体概念和不可修改的属性。该集合存在的意义是在领域建模的过程中，值对象可保证属性归类的清晰和概念的完整性，避免属性零碎。
+
+# 3 案例
+![](https://img-blog.csdnimg.cn/20200930014956996.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_25,color_000000,t_70#pic_center)
+
+实体人员，原包括：姓名、年龄、性别及所在省、市、县和街道等属性。这样显示地址相关属性就很零碎。
+就可将“省、市、县和街道等属性”拿出来构成一个“地址属性集合”，该集合就是值对象。
+
+# 4 不同状态的值对象
+## 4.1 业务形态
+值对象是DDD领域模型中的一个基础对象，跟实体一样源于事件风暴所构建的领域模型，都包含若干属性，与实体一起构成聚合。
+
+- 实体是看得到、摸得着的实实在在的业务对象，实体具有业务属性、业务行为和业务逻辑。
+- 值对象只是若干个属性集合，只有数据初始化操作和有限的不涉及修改数据的行为，基本不包含业务逻辑。值对象的属性集虽然在物理上独立，但在逻辑上仍是实体属性的一部分，用于描述实体的特征。
+
+值对象中也有部分共享的标准类型的值对象，它们有自己的限界上下文及持久化对象，可建立共享的数据类微服务，比如数据字典。
+
+## 4.2 代码形态
+代码中有两种形态。如果值对象是
+1. 单一属性，直接定义为实体类的属性
+2. 属性集合，设计为Class类，Class将具有整体概念的多个属性归集到属性集合，这样的值对象没有ID，会被实体整体引用
+
+- Person实体有若干单一属性的值对象，比如id、name
+![](https://img-blog.csdnimg.cn/20201003032717168.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFF00,t_70#pic_center)
+
+也包含多个属性的值对象，比如address![](https://img-blog.csdnimg.cn/2020100303300138.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFF00,t_70#pic_center)
+
+
+## 4.3 运行形态
+实体实例化后的DO对象的业务属性/行为都非常丰富，但值对象实例化的对象相对简单。除了值对象数据初始化和整体替换的行为外，很少有其它业务行为。
+
+举个例子，一人员实体可有多通讯地址，多地址序列化后可嵌入人员的地址属性。**值对象创建后不允许修改，只能用另外一个值对象来整体替换**。
+若将值对象嵌入到实体，即有如下方式：
+
+### 4.3.1  属性嵌入
+当引用单一属性的值对象或只有一条记录的多属性值对象的实体时
+
+- 属性嵌入形成的人员实体对象，地址值对象直接以属性值嵌入人员实体
+![](https://img-blog.csdnimg.cn/20200930214757898.png#pic_center)
+### 4.3.2 序列化大对象
+当引用一条或多条记录的多属性值对象的实体时
+
+- 以序列化大对象方式形成的人员实体对象，地址值对象被序列化成大对象JSON串后，嵌入人员实体
+![](https://img-blog.csdnimg.cn/20200930015649197.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+## 4.4 DB形态
+设计值对象是期望转“数据建模为中心”为“领域建模为中心”，以减少数据库表的数量和表与表间复杂依赖，尽可能简化DB设计，提升DB性能。
+
+# 5  值对象简化DB的最佳实践
+传统数据建模大多根据数据库范式设计，每个数据库表对应一个实体，每个实体的属性值用单独列存储，一个实体主表会对应N个实体从表。
+而值对象简化了DB的持久化设计，多采用反范式，**值对象的属性值和实体对象的属性值保存在同一DB实体表**。
+
+还是人员和地址案例，要设计实体和数据模型，通常有如下解决方案：
+1. 把地址值对象的所有属性放入人员实体表，创建人员实体、人员数据表
+会破坏地址的业务含义和概念完整性
+2. 创建人员和地址两个实体，同时创建人员和地址两张表
+增加了不必要的实体和表，需要处理多个实体和表的关系，导致数据库复杂性剧增
+
+那有没有设计可使得**业务含义清晰，又不会让数据库变复杂**？综合这俩方案优势，扬长避短即可：
+- 领域建模时，把地址作为值对象，人员作为实体，这就可保留地址的业务含义和概念完整性
+- 数据建模时，将地址的属性值嵌入人员实体数据库表，只创建人员数据库表。这既可兼顾业务含义和表达，又不会复杂化DB
+
+值对象就是通过该方式，简化DB设计：
+1. **领域建模时，将部分对象设计为值对象，保留对象的业务含义，同时又减少了实体数量**
+2. **数据建模时，我们可以将值对象嵌入实体，减少实体表的数量，简化DB设计**
+
+也有DDD专家认为，要发挥对象的威力，就需优先领域建模，弱化DB作用，只把DB作为一个保存数据的仓库即可。即使违反DB设计原则，也不必大惊小怪，只要业务能顺利运行，无伤大雅。
+
+# 6 值对象的优劣分析
+虽然优势是可简化DB设计，提升DB性能。但若使用不当，其优势很快会成劣势。必须理解值对象的适用场景。
+
+值对象采用序列化大对象的方式简化了DB设计，减少了实体表的数量，可简单、清晰表达业务概念。该方式虽然降低DB设计复杂度，但却无法满足基于值对象的快速查询，会导致搜索值对象的属性值变难。
+
+值对象采用属性嵌入的方式提升了DB性能，但若实体引用的值对象过多，则会导致实体堆积一堆缺乏概念完整性的属性，这样值对象就会失去业务含义，操作也不方便。
+
+所以对照优劣势并结合实际业务场景，才能发挥值对象的最大作用。
+
+# 7 和实体的联系
+实体和值对象都是微服务底层最基础的对象，一起实现实体最基本的核心领域逻辑。
+
+二者在某些场景可互换，因为很难判断到底将领域对象设计成实体or值对象。值对象在某些场景下有高价值，但并非适合所有场景。需根据团队设计开发习惯及优劣分析，才能选择最适合的方案。
+
+DDD提倡从领域模型设计出发，而非先设计数据模型。
+传统数据模型设计通常一个表对应一个实体，一个主表关联多个从表，当实体表太多，就很容易陷入复杂DB设计，领域模型就很容易被数据模型绑架。所以值对象和实体是相辅相成。
+
+还是那个案例，在领域模型中人员是实体，地址是值对象，地址值对象被人员实体引用。
+设计数据模型时
+- 地址值对象可作为一个属性集整体嵌入人员实体
+- 也可以序列化大对象的形式加入人员的地址属性
+
+该案例也可看出，同样一个对象在不同场景，可能设计不同：
+- 有些场景，地址会被某一实体引用，只描述实体，并且其值只能整体替换，这时就可将地址设计为值对象，比如收货地址
+- 某些场景，地址会被经常修改，地址作为一个独立对象存在，这时应设计为实体，比如行政区划中的地址信息维护
+
+参考
+- 实体和值对象：从领域模型的基础单元看系统设计
+- 《实现领域驱动设计》
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\347\232\204\346\210\230\347\225\245\350\256\276\350\256\241.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\347\232\204\346\210\230\347\225\245\350\256\276\350\256\241.md"
deleted file mode 100644
index 22d890033e..0000000000
--- "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241\347\232\204\346\210\230\347\225\245\350\256\276\350\256\241.md"
+++ /dev/null
@@ -1,78 +0,0 @@
-模型设计，DDD 分两阶段，战略设计和战术设计。
-# 战略设计
-战略设计的子域、限界上下文和上下文映射图等概念大致分为：
-- 业务划分
-以区分不同业务，即划分识别出来的业务概念
-- 落地成解决方案
-将划分出来的业务落地
-
-# 业务概念的划分
-首先需要明确：
-## 问题是什么
-我们要解决的问题就是领域问题，相关概念如，子域、核心域、支撑域、通用域等。其实都是：如何分解问题。
-## 如何解决
-领域（Domain）是一号位，对应待解决的问题。解决问题通常思路是分而治之，DDD就把一个大领域分解成若干小领域（子域（Subdomain））。
-
-DDD首先要建立起一套通用语言，拥有一致的业务词汇表，它们对应模型。
-接着，要分类词汇，即把它们划分到不同子域。**关键就是分离关注点。**
-
-比如，做个项目管理软件，就要有用户、项目、团队，不同人还要扮演不同角色。
-第一步，至少先分开身份管理、项目管理，因为关注点不同：
-- 身份管理，关注用户身份信息，如用户名、密码
-- 项目管理，关注项目和团队等
-
-这就有了俩子域：身份管理，项目管理。
-
-若直接给结果，你可能会觉得很好理解。但划分出不同子域还是容易出问题的，因为有些概念不易区分。
-比如，用户应该怎么划分？放在身份管理是合适的，但项目管理也要用到呀！
-
-根据单一职责原则，它给了我们一个重要思考维度，**变化从何而来**？
-不同角色的人关注不同变化，所以，虽然我们用的词都是“用户”，但想表达的含义其实不同，最好分开这些不同的含义，即分开不同角色：
-- 身份管理中，它是“用户”
-- 项目管理中，就成了“项目成员”
-# 业务概念的落地
-DDD问题层面的概念已经阐述完毕。接下来，就是解决方案层面。
-
-切分出的子域，怎样落实到代码？
-首先要解决的就是这些子域如何组织？
-- 写一个程序把所有子域都放里面
-- 每个子域单独做个应用
-- 有一些在一起，有一些分开
-
-这就引出DDD的
-# 限界上下文（Bounded Context）
-限定了通用语言自由使用的边界，一旦出界，含义便无法保证。
-比如，同样是“订单”，不加限制，很难区分它在哪种场景。一旦定义了限界上下文，那交易上下文的“订单”和物流上下文的“订单”肯定不同。就是因为订单这个说法，在不同边界内，含义不同。
-
-注意，子域和限界上下文不一定是一一对应的，可能在一个限界上下文中包含了多个子域，也可能在一个子域横跨了多个限界上下文。
-
-限界上下文是在解决方案层面，所以，就可以把限界上下文看作一个独立系统。限界上下文与微服务的理念契合，每个限界上下文都可成为一个独立服务。
-
-限界上下文是完全独立的，不会为了完成一个业务需求要跑到其他服务中去做很多事，这恰是很多微服务出问题的点，比如一个业务功能要调用很多其他系统功能。
-
-有限界上下文，就可以把整个业务分解到不同的限界上下文中，但是，尽管我们拆分了系统，它们终究还是一个系统，免不了交互。
-比如：
-- 一个用户下了订单，这是在订单上下文中完成的
-- 用户要去支付，这是在支付上下文中完成的
-
-我们要通过某种途径让订单上下文的一些信息发送到支付上下文。所以，要有一种描述方式，描述不同限界上下文之间交互的方式-上下文映射图（Context Map）。
-DDD 给我们提供了一些描述这种交互的方式，比如：
-- 合作关系（Partnership）
-- 共享内核（Shared Kernel）
-- 客户-供应商（Customer-Supplier）
-- 跟随者（Conformist）
-- 防腐层（Anticorruption Layer）
-防腐层是最具防御性的一种关系，就是在外部模型和内部模型之间建立起一个翻译层，将外部模型转化为内部模型。但凡有可能，就要建立防腐层，将外部模型完全隔离开。
-- 开放主机服务（Open Host Service）
-- 发布语言（Published Language）
-- 各行其道（Separate Ways）
-- 大泥球（Big Ball of Mud）
-要规避
-
-这么多交互方式，主要是为让你在头脑中仔细辨认，看看限界上下文之间到底在以怎样的方式交互。
-
-知道不同限界上下文之间交互方式后，不同交互方式就可落地为不同协议。
-常用协议如：REST API、RPC 或是 MQ， 按需选型即可。
-
-在我们定义好不同的限界上下文，将它们之间的交互呈现出来之后，就得到了一张上下文映射图。
-上下文映射图是可以帮助我们理解系统的各个部分之间，是怎样进行交互的，建立全局性认知。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/\345\205\205\350\241\200\346\250\241\345\236\213\343\200\201\350\264\253\350\241\200\351\242\206\345\237\237\346\250\241\345\236\213.md" "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/\345\205\205\350\241\200\346\250\241\345\236\213\343\200\201\350\264\253\350\241\200\351\242\206\345\237\237\346\250\241\345\236\213.md"
index 351028cad7..4bda8731aa 100644
--- "a/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/\345\205\205\350\241\200\346\250\241\345\236\213\343\200\201\350\264\253\350\241\200\351\242\206\345\237\237\346\250\241\345\236\213.md"
+++ "b/\346\236\266\346\236\204/DDD\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/\345\205\205\350\241\200\346\250\241\345\236\213\343\200\201\350\264\253\350\241\200\351\242\206\345\237\237\346\250\241\345\236\213.md"
@@ -10,16 +10,15 @@
 - 只有状态的对象就是所谓的“贫血对象”（常称为VO——Value Object）
 - 只有行为的对象就是我们常见的N层结构中的Logic/Service/Manager层（对应到EJB2中的Stateless Session Bean）。（曾经Spring的作者Rod Johnson也承认，Spring不过是在沿袭EJB2时代的“事务脚本”，也就是面向过程编程）
 
-贫血领域模型是一个存在已久的反模式，它不是个好东西。
+贫血领域模型是一个存在已久的反模式，目前仍有许多拥趸者。Martin Fowler曾经和Eric Evans聊天谈到它时，都觉得这个模型似乎越来越流行了。作为领域模型的推广者，他们觉得这不是一件好事。
 
-- 贫血领域模型的基本特征
-它第一眼看起来还真像这么回事儿。项目中有许多对象，它们的命名都是根据领域来的。对象之间有着丰富的连接方式，和真正的领域模型非常相似。但当你检视这些对象的行为时，会发现它们基本上没有任何行为，仅仅是一堆getter/setter。
+贫血领域模型的基本特征是：它第一眼看起来还真像这么回事儿。项目中有许多对象，它们的命名都是根据领域来的。对象之间有着丰富的连接方式，和真正的领域模型非常相似。但当你检视这些对象的行为时，会发现它们基本上没有任何行为，仅仅是一堆getter/setter。
 其实这些对象在设计之初就被定义为只能包含数据，不能加入领域逻辑。这些逻辑要全部写入一组叫Service的对象中。这些Service构建在领域模型之上，使用这些模型来传递数据。
 
-## 反模式的恐怖
-它完全和面向对象设计背道而驰。面向对象设计主张将数据和行为绑定在一起，而贫血领域模型则更像是一种面向过程设计。
+这种反模式的恐怖之处在于，它完全和面向对象设计背道而驰。
+面向对象设计主张将数据和行为绑定在一起，而贫血领域模型则更像是一种面向过程设计，Martin Fowler和Eric在Smalltalk时就极力反对这种做法。更糟糕的时，很多人认为这些贫血领域对象是真正的对象，从而彻底误解了面向对象设计的涵义。
 
-贫血领域模型的根本问题在于，它**引入了领域模型设计的所有成本，却没有带来任何好处**。
+如今，面向对象的概念已经传播得很广泛了，而要反对这种贫血领域模型的做法，还需要更多论据。贫血领域模型的根本问题在于，它**引入了领域模型设计的所有成本，却没有带来任何好处**。
 最主要的成本是将对象映射到数据库中，从而产生了一个O/R（对象关系）映射层。只有当你充分使用了面向对象设计来组织复杂的业务逻辑后，这一成本才能够被抵消。如果将所有行为都写入到Service对象，那最终你会得到一组事务处理脚本，从而错过了领域模型带来的好处。正如martin在企业应用架构模式一书中说到的，领域模型并不一定是最好的工具。
 
 将行为放入领域模型，这点和分层设计（领域层、持久化层、展现层等）并不冲突。因为领域模型中放入的是和领域相关的逻辑——验证、计算、业务规则等。如果你要讨论能否将数据源或展现逻辑放入到领域模型中，这就不在本文论述范围之内了。
@@ -36,7 +35,7 @@ Eric Evans的Domain Driven Design一书中提到：
 服务层很薄——所有重要的业务逻辑都写在领域层。他在服务模式中复述了这一观点：
 如今人们常犯的错误是不愿花时间将业务逻辑放到合适的领域模型中，从而逐渐形成面向过程的程序设计。
 
-为什么这种反模式会那么常见呢。我怀疑是因为大多数人并没有使用过一个设计良好的领域模型，特别是那些以数据为中心的开发人员。此外，有些技术也会推动这种反模式，比如J2EE的Entity Bean，这会让我更倾向于使用POJO领域模型。
+我不清楚为什么这种反模式会那么常见。我怀疑是因为大多数人并没有使用过一个设计良好的领域模型，特别是那些以数据为中心的开发人员。此外，有些技术也会推动这种反模式，比如J2EE的Entity Bean，这会让我更倾向于使用POJO领域模型。
 
 总之，如果你将大部分行为都放置在服务层，那么你就会失去领域模型带来的好处。如果你将所有行为都放在服务层，那你就无可救药了。
 
@@ -69,7 +68,7 @@ Eric Evans的Domain Driven Design一书中提到：
 
 如果一个对象包含其他对象，那就将职责继续委托下去，由具体的 POJO 执行业务逻辑，将策略模式更加细粒度，而不是写 ifelse。
 
-> 参考
-> - 《领域驱动设计》
-> - https://www.zhihu.com/question/20360521/answer/14891150
-> - https://www.martinfowler.com/bliki/AnemicDomainModel.html
\ No newline at end of file
+参考
+- 《领域驱动设计》
+- https://www.zhihu.com/question/20360521/answer/14891150
+- https://www.martinfowler.com/bliki/AnemicDomainModel.html
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\346\224\257\346\214\201\347\232\204\351\200\232\344\277\241\343\200\201\345\272\217\345\210\227\345\214\226\345\215\217\350\256\256\350\257\246\350\247\243.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\346\224\257\346\214\201\347\232\204\351\200\232\344\277\241\343\200\201\345\272\217\345\210\227\345\214\226\345\215\217\350\256\256\350\257\246\350\247\243.md"
deleted file mode 100644
index acfcef624e..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\346\224\257\346\214\201\347\232\204\351\200\232\344\277\241\343\200\201\345\272\217\345\210\227\345\214\226\345\215\217\350\256\256\350\257\246\350\247\243.md"
+++ /dev/null
@@ -1,34 +0,0 @@
-# dubbo支持不同的通信协议
-![](https://img-blog.csdnimg.cn/20190516115342103.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## dubbo协议
-
-```sql
-dubbo://192.168.0.1:20188
-```
-默认就是走dubbo协议的，单一长连接，NIO异步通信，基于hessian作为序列化协议。
-
-适用的场景就是：传输数据量很小（每次请求在100kb以内），但是并发量很高
-
-为了要支持高并发场景，一般是服务提供者就几台机器，但是服务消费者有上百台，可能每天调用量达到上亿次！此时用长连接是最合适的，就是跟每个服务消费者维持一个长连接就可以，可能总共就100个连接。然后后面直接基于长连接NIO异步通信，可以支撑高并发请求。
-
-否则如果上亿次请求每次都是短连接的话，服务提供者会扛不住。
-
-而且因为走的是单一长连接，所以传输数据量太大，会导致并发能力降低。推荐传输数据量很小，这样能支撑高并发访问。
-
-### rmi协议
-
-走java二进制序列化，多个短连接，适合消费者和提供者数量差不多，适用于文件的传输，一般较少用
-
-### hessian协议
-hessian序列化协议，多个短连接，适用于提供者数量比消费者数量还多，适用于文件的传输，一般较少用。
-
-### http协议
-json序列化
-
-### webservice
-SOAP文本序列化
-
-# dubbo支持的序列化协议
-
-所以dubbo实际基于不同的通信协议，支持hessian、java二进制序列化、json、SOAP文本序列化多种序列化协议。但是hessian是其默认的序列化协议。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/RPC\345\237\272\347\241\200.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/RPC\345\237\272\347\241\200.md"
deleted file mode 100644
index a859b96023..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/RPC\345\237\272\347\241\200.md"
+++ /dev/null
@@ -1,103 +0,0 @@
-# 分布式服务化与 SOA/ESB 区别
-服务汇聚到ESB：
-- 暴露和调用
-- 增强和中介
-- 统计和监控
-
-分布式服务化作为SOA的另一种选择，以不同方式把ESB的一些功能重做了一遍。
-SOA/ESB：代理调用，直接增强。
-# 1 RPC是什么
-RPC，Remote Procedure Call，远程`过程`调用。
-`过程` 就是业务处理、计算任务，也就是程序，像调用本地方法一样调用远程方法。
-
-RPC的概念与技术早在1981年由Nelson提出。1984年，Birrell和Nelson把其用于支持异构型分布式系统间的通讯。Birrell的RPC 模型引入存根进程( stub) 作为远程的本地代理，调用RPC运行时库来传输网络中的调用。
-Stub和RPC runtime屏蔽了网络调用所涉及的许多细节，特别是，参数的编码/译码及网络通讯是由stub和RPC runtime完成的，因此这一模式被各类RPC所采用。
-
-这到底是如何能做到本地方法调用时转换成远程呢？
-RPC采用Client-Server结构，通过request-response消息模式实现。
-
-- RPC VS RMI
-RMI，remote method invocation，远程方法调用是OOP领域中RPC的一种具体实现。
-
-- webservice、restfull接口调用是RPC吗?
-都是RPC，仅消息的组织方式及消息协议不同。
-
-远程过程调用较本地调用区别：
-- 速度相对慢
-- 可靠性减弱
-
-# RPC原理
-```java
-// 本地
-UserService service = new UserService();
-User user = service.findById(1);
-
-// RPC
-UserService service = Rpcfx.create(UserService.class, url);
-User user = service.findById(1);
-```
-![](https://img-blog.csdnimg.cn/20191114011312532.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-1. 客户端处理过程中`调用`Client stub (就像调用本地方法一样)，传递参数
-2. Client stub将参数`编组`为消息，然后通过系统调用向服务端发送消息
-3. 客户端本地操作系统将消息从客户端机器`发送`到服务端机器
-4. 服务端操作系统将接收到的数据包`传递`给Server stub
-5. Server stub `解组`消息为参数
-6. Sever stub`再调用`服务端的过程，过程执行结果以反方向的相同步骤响应给客户端
-
-核心是代理机制：
-1. 本地代理存根Stub，通过动态代理或 AOP 拦截请求
-2. 本地序列化反序列化
-3. 网络通信
-4. 远程序列化反序列化
-5. 远程服务存根Skeleton
-6. 调用实际业务服务
-7. 原路返回服务结果
-8. 返回给本地调用方
-![](https://img-blog.csdnimg.cn/20210217185336743.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-流程需要解决什么问题呢?
-# 3 RPC协议
-RPC调用过程中需要将参数编组为消息进行发送，接收方需要解组消息为参数，过程处理结果同样需要经编组、解组。
-
-消息由哪些部分构成及消息的表示形式就构成了消息协议。
-`RPC调用过程中采用的消息协议称为RPC协议。`
-
-RPC协议规定请求、响应消息的格式在TCP (网络传输控制协议)上可选用或自定义消息协议来完成RPC消息交互
-
-我们可以选用通用的标准协议(如: http、 https) ，也可根据自身的需要定义自己的消息协议!
-
-- 常见的RPC协议
-![](https://img-blog.csdnimg.cn/20191114130245795.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# 4 RPC框架(Java领域)
-封装好参数编组、消息解组、底层网络通信的RPC程序开发框架，带来的便捷是可以直接在其基础上只需专注于过程代码编写。
-
-➢ 传统的webservice框架
-- Apache CXF
-![](https://img-blog.csdnimg.cn/20191114130600432.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- Apache Axis2
-![](https://img-blog.csdnimg.cn/20191114130702279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- Java 自带的JAX-WS
-![](https://img-blog.csdnimg.cn/20191114130758130.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-webService框架大多基于标准的SOAP协议。
-
-➢ 新兴的微服务框架:
- 
-- Dubbo
-![](https://img-blog.csdnimg.cn/20191114130841739.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- spring cloud alibaba
-![](https://img-blog.csdnimg.cn/20191114131014282.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- Apache Thrift
-![](https://img-blog.csdnimg.cn/20191114131042186.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## 为何使用RPC
-- 服务化
-- 可重用
-- 系统间交互调用
-# 5 RPC相关术语
-- Client、 Server、 calls、 replies、service、programs、procedures、version、marshalling(编组)、unmarshalling(解组)
-- 一个网络服务由一个或多个远程程序集构成
-- 一个远程程序实现一个或多个远程过程
-- 过程、过程的参数、结果在程序协议说明书中定义说明
-- 为兼容程序协议变更、一个服务端可能支持多个版本的远程程序
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/RESTful\346\236\266\346\236\204.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/RESTful\346\236\266\346\236\204.md"
deleted file mode 100644
index 441aeb87ac..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/RESTful\346\236\266\346\236\204.md"
+++ /dev/null
@@ -1,151 +0,0 @@
-# 1 导读
-## 1.1 来源
-REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。
-
-他参与设计了HTTP协议，也是Apache Web Server项目（可惜现在已经是 Nginx 的天下）的co-founder。
-论文地址：[Architectural Styles and the Design of Network-based Software Architectures](http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)
-REST章节：[Fielding Dissertation: CHAPTER 5: Representational State Transfer (REST)](http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm)
-
-## 1.2 名称
-Fielding将他对互联网软件的架构原则,定名为REST ,即Representational State Transfer的缩写，资源在网络中以某种表现形式进行状态转移。
-
-如果一个架构符合REST原则,就称它为RESTful架构。
-
-Resource `Re`presentational `S`tate `T`ransfer，通过URI+动作来操作一个资源。
-##  1.3 拆文解字
-### Resource
-资源，即数据（网络的核心）。将网络上的信息实体看作是资源，比如可以是：
-- 文本
-- 图片
-- 服务
-- 音频
-...
-
-
-#### 标识
-资源用URI统一标识，URI只使用名词来指定资源，原则上不使用动词，因为它们是资源的标识。
-最佳实践：
-URL root:
-```bash
-[https://example.org/api/v1/](https://example.org/api/v1/)
-[https://api.example.com/v1/](https://api.example.com/v1/)
-```
-API versioning:
-可以放在URL里，也可以用HTTP的header：
-```bash
-/api/v1/
-```
-
-URI使用名词而非动词，推荐复数：
-BAD
-*   /getProducts
-*   /listOrders
-*   /retrieveClientByOrder?orderId=1
-
-GOOD
-*   GET /products
-return the list of all products
-*   POST /products
-add a product to the collection
-*   GET /products/4
-retrieve product
-*   PATCH/PUT /products/4
-update product
-
-###  Representational 表现层
-Representational：某种表现形式，比如用JSON，XML，JPEG。
-
-- 文本
-txt、html、 xml、json、二进制
-- 图片
-jpg、png
-- http协议的 content-type 和 accept
-
-Case：
-book是一个资源，获取不同的格式
-
-### State Transfer
-State Transfer：状态转化。通过HTTP动词实现，用以操作这些资源。
-
-- 幂等性
-每次HTTP请求相同的参数，相同的URI，产生的结果是相同的
-- GET-获取资源
-http://www.book.com/book/001
-- POST-创建资源，不具有幂等性
-http://www.book.com/book/
-- PUT-创建(更新)资源
-http://www.book.com/book/001
-- DELETE-删除资源
-http://www.book.com/book/001
-
-REST描述的是在网络中client和server的一种交互形式。REST本身不实用，实用的是如何设计 RESTful API（REST风格的网络接口）。
-
-Server提供的RESTful API中，URL中只使用名词来指定资源，原则上不使用动词。
-
-“资源”是REST架构或者说整个网络处理的核心。比如：
-
-```bash
-[http://api/v1/newsfeed](http://api.qc.com/v1/newsfeed)
-获取某人的新鲜事
-[http://api/v1/friends](http://api.qc.com/v1/friends)
-获取某人的好友列表
-[http://api/v1/profile](http://api.qc.com/v1/profile)
-获取某人的详细信息
-```
-
-用HTTP协议里的动词来实现资源的添加，修改，删除等操作，即通过HTTP动词实现资源的状态扭转：
-
-```bash
-DELETE [http://api.qc.com/v1/](http://api.qc.com/v1/friends)friends
-删除某人的好友 （在http parameter指定好友id）
-POST [http://api.qc.com/v1/](http://api.qc.com/v1/friends)friends
-添加好友
-UPDATE [http://api.qc.com/v1/profile](http://api.qc.com/v1/profile)
-更新个人资料
-```
-禁止使用： 
-
-```bash
-GET [http://api.qc.com/v1/deleteFriend](http://api.qc.com/v1/deleteFriend) 
-```
-
-Server和Client之间传递某资源的一个表现形式，比如用JSON，XML传输文本，或者用JPG，WebP传输图片等。当然还可以压缩HTTP传输时的数据（on-wire data compression）。
-
-用 HTTP Status Code传递Server的状态信息。比如最常用的 200 表示成功，500 表示Server内部错误等。
-
-Web端不再用之前典型的JSP架构，而是改为前段渲染和附带处理简单的商务逻辑（比如AngularJS或者BackBone的一些样例）。Web端和Server只使用上述定义的API来传递数据和改变数据状态。格式一般是JSON。iOS和Android同理可得。由此可见，Web，iOS，Android和第三方开发者变为平等的角色通过一套API来共同消费Server提供的服务。
-
-# 为什么要用RESTful架构
-以前网页是前端后端融在一起的，比如JSP等。在桌面时代问题不大，但近年移动互联网的发展，各种类型的Client层出不穷，RESTful可以通过一套统一的接口为 Web，iOS和Android提供服务。另外对于广大平台来说，比如Facebook platform，微博开放平台，微信公共平台等，它们不需要有显式的前端，只需要一套提供服务的接口，于是RESTful更是它们最好的选择。
-
-RESTful架构下：
-![](https://img-blog.csdnimg.cn/img_convert/c92c2a8400cac46ca1693e0cdbaedb2e.png)
-### Server API的RESTful设计原则
-保证 HEAD 和 GET 方法是安全的，不会对资源状态有所改变（污染）。比如严格杜绝如下情况：
-
-```bash
-GET /deleteProduct?id=1
-```
-
-资源的地址推荐用嵌套结构。比如：
-
-```bash
-GET /friends/10375923/profile
-UPDATE /profile/primaryAddress/city
-```
-
-警惕返回结果的大小。如果过大，及时进行分页（pagination）或者加入限制（limit）。HTTP协议支持分页（Pagination）操作，在Header中使用 Link 即可。
-
-使用正确的HTTP Status Code表示访问状态：[HTTP/1.1: Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
-
-在返回结果用明确易懂的文本（注意返回的错误是要给人看的，避免用 1001 这种错误信息），而且适当加入注释。
-
-关于安全：
-自己的接口就用https，加上一个key做一次hash放在最后即可。考虑到国情，HTTPS在无线网络里不稳定，可以使用应用层的加密手段把整个HTTP的payload加密。
-
-RESTful 是无状态的。
-
-> 参考
-> - [Getting Started · Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/)
-> - [授权机制说明](http://open.weibo.com/wiki/%E6%8E%88%E6%9D%83%E6%9C%BA%E5%88%B6%E8%AF%B4%E6%98%8E)
-> https://www.zhihu.com/question/28557115/answer/48094438
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/Hystrix\347\272\277\347\250\213\346\261\240\346\234\272\345\210\266\347\232\204\350\265\204\346\272\220\351\232\224\347\246\273\345\234\250\344\270\232\345\212\241\344\270\255\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/Hystrix\347\272\277\347\250\213\346\261\240\346\234\272\345\210\266\347\232\204\350\265\204\346\272\220\351\232\224\347\246\273\345\234\250\344\270\232\345\212\241\344\270\255\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
deleted file mode 100644
index 4b2f61221d..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/Hystrix\347\272\277\347\250\213\346\261\240\346\234\272\345\210\266\347\232\204\350\265\204\346\272\220\351\232\224\347\246\273\345\234\250\344\270\232\345\212\241\344\270\255\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"	
+++ /dev/null
@@ -1,139 +0,0 @@
-# 1 pom.xml
-```xml
-<dependency>
-    <groupId>com.netflix.hystrix</groupId>
-    <artifactId>hystrix-core</artifactId>
-    <version>1.5.12</version>
-</dependency>
-```
-# 2 将商品服务接口调用的逻辑进行封装
-hystrix资源隔离，其实是提供了一个抽象，叫做command。若把对某个依赖服务的所有调用请求，全部隔离在同一份资源池内。
-
-- 资源隔离
-对这个依赖服务的所有调用请求，全部走这个资源池内的资源，不会去用其他的资源。
-
-hystrix最基本的资源隔离的技术 --- 线程池隔离技术
-
-对某个依赖服务，商品服务所有的调用请求，全部隔离到一个线程池内，对商品服务的每次调用请求都封装在一个command。
-
-每个command（服务调用请求）都是使用线程池内的一个线程去执行。
-即使商品服务接口故障了，最多只有10个线程会hang死在调用商品服务接口的路上。缓存服务的tomcat内其他的线程还是可以用来调用其他的服务，做其他的事情
-```java
-public class CommandHelloWorld extends HystrixCommand<String> {
-
-    private final String name;
-
-    public CommandHelloWorld(String name) {
-        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
-        this.name = name;
-    }
-
-    @Override
-    protected String run() {
-        return "Hello " + name + "!";
-    }
-
-}
-```
-不让超出这个量的请求去执行了，保护说，不要因为某一个依赖服务的故障，导致耗尽了缓存服务中的所有的线程资源去执行。
-
-# 3 开发一个支持批量商品变更的接口
-- HystrixCommand 
-获取一条数据
-- HystrixObservableCommand
-获取多条数据
-
-```java
-public class ObservableCommandHelloWorld extends HystrixObservableCommand<String> {
-
-    private final String name;
-
-    public ObservableCommandHelloWorld(String name) {
-        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
-        this.name = name;
-    }
-
-    @Override
-    protected Observable<String> construct() {
-        return Observable.create(new Observable.OnSubscribe<String>() {
-            @Override
-            public void call(Subscriber<? super String> observer) {
-                try {
-                    if (!observer.isUnsubscribed()) {
-                        observer.onNext("Hello " + name + "!");
-                        observer.onNext("Hi " + name + "!");
-                        observer.onCompleted();
-                    }
-                } catch (Exception e) {
-                    observer.onError(e);
-                }
-            }
-         } ).subscribeOn(Schedulers.io());
-    }
-}
-```
-
-# 4 command的调用方式
-## 4.1 同步
-```java
-new CommandHelloWorld("World").execute()，
-new ObservableCommandHelloWorld("World").toBlocking().toFuture().get()
-```
-如果你认为observable command只会返回一条数据，那么可以调用上面的模式，去同步执行，返回一条数据
-
-## 4.2 异步
-```java
-new CommandHelloWorld("World").queue()，
-new ObservableCommandHelloWorld("World").toBlocking().toFuture()
-```
-对command调用queue()，仅仅将command放入线程池的一个等待队列，就立即返回，拿到一个Future对象，后面可以做一些其他的事情，然后过一段时间对future调用get()方法获取数据
-```java
-// observe()：hot，已经执行过了
-// toObservable(): cold，还没执行过
-
-Observable<String> fWorld = new CommandHelloWorld("World").observe();
-
-assertEquals("Hello World!", fWorld.toBlocking().single());
-
-fWorld.subscribe(new Observer<String>() {
-
-    @Override
-    public void onCompleted() {
-
-    }
-
-    @Override
-    public void onError(Throwable e) {
-        e.printStackTrace();
-    }
-
-    @Override
-    public void onNext(String v) {
-        System.out.println("onNext: " + v);
-    }
-
-});
-
-Observable<String> fWorld = new ObservableCommandHelloWorld("World").toObservable();
-
-assertEquals("Hello World!", fWorld.toBlocking().single());
-
-fWorld.subscribe(new Observer<String>() {
-
-    @Override
-    public void onCompleted() {
-
-    }
-
-    @Override
-    public void onError(Throwable e) {
-        e.printStackTrace();
-    }
-
-    @Override
-    public void onNext(String v) {
-        System.out.println("onNext: " + v);
-    }
-
-});
-```
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(12) - Nacos\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(12) - Nacos\351\205\215\347\275\256\347\256\241\347\220\206.md"
deleted file mode 100644
index 3fc8af8853..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(12) - Nacos\351\205\215\347\275\256\347\256\241\347\220\206.md"	
+++ /dev/null
@@ -1,92 +0,0 @@
-该技术类似Spring Cloud Config。
-
-# 1 配置管理的意义
-## 现实的痛点
-- 不同环境，不同配置
-- 配置属性动态刷新
-
-为解决痛点，常用方案是引入配置服务器，架构如下
-![](https://img-blog.csdnimg.cn/20191215221554158.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 引入依赖
-![](https://img-blog.csdnimg.cn/20191215221821994.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 写配置(约定大于配置)，要谨遵如下映射关系图哦
-![](https://img-blog.csdnimg.cn/20191215222031784.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 创建新的bootstrap.yml配置文件
-![](https://img-blog.csdnimg.cn/20191215222158954.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 在NACOS操作面板配置
-![](https://img-blog.csdnimg.cn/20191215222601454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/2019121522261920.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- 启动内容中心
-![](https://img-blog.csdnimg.cn/20191215222843437.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# 2 配置的动态刷新
-修改配置后，应用可动态刷新配置，而无需重启应用
-- 添加 **@RefreshScope** 注解即可
-![](https://img-blog.csdnimg.cn/20191215223346781.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## 回滚Bug
-不要回滚到初始状态! Nacos 至今未解决。
-- https://github.com/alibaba/nacos/issues/186
-![](https://img-blog.csdnimg.cn/2019121522510516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- https://qithub.com/alibaba/nacos/issues/434
-![](https://img-blog.csdnimg.cn/20191215225151357.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-既然迟迟不修复，我们只能尽量避免使用历史版本，而直接修改配置。
-
-# 3  共享配置
-## 3.1 相同应用内
-
-```bash
-所指定的 > 通用的
-```
-
-## 3.2 不同应用间
-- 比如这段配置在用户/内容中心都有，可以将其交给nacos管理共享配置
-![](https://img-blog.csdnimg.cn/201912152258123.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-下面看NACOS配置共享的两种方案：
-### shared-detaids
-![](https://img-blog.csdnimg.cn/20191215230042683.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### ext-config
-![](https://img-blog.csdnimg.cn/20191215230201384.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-### 优先级
-```bash
-shared-dataids < ext-config < 自动
-```
-
-# 4 引导上下文
-## 4.1 用来连接配置服务器,读取外部配置
-bootstrap.yml就是引导上下文的配置文件。对于应用，就是来连接Nacos以读取Nacos中的配置。
-## 4.2  Application Context的父上下文
-远程配置(Nacos) & 本地配置优先级
-默认情况下,远程配置优先级较高哦
-- 如下的配置文件必须放在远程配置中才生效
-![](https://img-blog.csdnimg.cn/20191215230727734.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# 5 数据持久化
-## 5.1 作为服务发现组件
-其数据都是存在本地路径:
-
-```bash
-~/nacos/naming/public
-```
-![](https://img-blog.csdnimg.cn/2019121523504728.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20191215235208474.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-## 5.2 作为配置服务器
-数据分为如下两部分
-### NACOS web ui中添加的配置数据: 
-```bash
-$NACOS_ HOME/data/derby-data
-```
-- 如果想查看其具体内容，必须停止nacos，然后连接Derby这个Apache开发的内嵌数据库，通过IDEA的数据源连接
-![](https://img-blog.csdnimg.cn/20191215235547255.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20191215235915619.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- 配置属性的快照
-~/nacos/config
-
-# 6 Nacos配置管理最佳实践
-- 能放本地,不放远程
-- 尽量规避优先级
-- 定规范，例如所有配置属性都要加上注释
-- 配置管理人员尽量少(官方正在开发权限控制)
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\350\264\237\350\275\275\345\235\207\350\241\241\347\256\227\346\263\225.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\350\264\237\350\275\275\345\235\207\350\241\241\347\256\227\346\263\225.md"
deleted file mode 100644
index fcac063200..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\350\264\237\350\275\275\345\235\207\350\241\241\347\256\227\346\263\225.md"
+++ /dev/null
@@ -1,88 +0,0 @@
-#  1 负载均衡的产生
-假设你订阅了一个别人的服务，从注册中心查询得到了这个服务的可用节点列表，而这个列表里包含了几十个节点，这个时候你该选择哪个节点发起调用呢？这就是客户端负载均衡算法的问题。
-
-# 2 负载均衡算法的意义
-- 考虑调用的均匀性，也就是要让每个节点都接收到调用，发挥所有节点的作用
-- 考虑调用的性能，也就是哪个节点响应最快，优先调用哪个节点
-
-不同负载均衡算法也就是在这两个方面的考虑不同。
-# 3 常见的负载均衡算法
-## random（随机）
-dubbo默认。
-![](https://img-blog.csdnimg.cn/20210321001351820.png)
-从可用的服务节点中，随机挑选一个节点来访问。
-实现时，随机算法通常通过生成一个随机数来实现，比如服务有10个节点，那么就每一次生成一个1～10之间的随机数，假设生成的是2，那么就访问编号为2的节点。
-采用随机算法，在节点数量足够多，并且访问量比较大的情况下，各个节点被访问的概率基本相同。
-### 适用场景
-实现简单，在请求量远超可用服务节点数量的情况下，各个服务节点被访问的概率基本相同，主要应用在各个服务节点的性能差异不大时。
-
-## roundrobin（轮询）
-均匀地将流量打到各个机器上去，但如果各个机器的性能不一样，容易导致性能差的机器负载过高。所以此时需要调整权重，让性能差的机器承载权重小一些，流量少一些。
-
-按固定顺序，把可用的服务节点，挨个访问一次。
-### 适用场景
-类似随机算法，各个服务节点被访问的概率也基本相同，也主要应用在各个服务节点性能差异不大。
-### 实现
-通常把所有可用节点放到一个数组，挨个访问。比如服务有10个节点，放到数组里就是一个大小为10的数组，这样的话就可以从序号为0的节点开始访问，访问后序号自动加1，下一次就会访问序号为1的节点，以此类推。
-
-轮询算法能保证所有节点被访问到的概率相同。一个轮询算法的代码实现，可以参考这个示例。
-## 加权轮询
-轮询能够保证所有节点被访问的概率相同，而加权轮询算法是在此基础上，给每个节点赋予一个权重，从而使每个节点被访问到的概率不同，权重大的节点被访问的概率就高，权重小的节点被访问的概率就小。
-
-### 实现
-加权轮询算法是生成一个节点序列，该序列里有n个节点，n是所有节点的权重之和。在这个序列中，每个节点出现的次数，就是它的权重值。比如有三个节点：a、b、c，权重分别是3、2、1，那么生成的序列就是{a、a、b、c、b、a}，这样的话按照这个序列访问，前6次请求就会分别访问节点a三次，节点b两次，节点c一次。从第7个请求开始，又重新按照这个序列的顺序来访问节点。
-
-要尽可能保证生产的序列的均匀，如果生成的不均匀会造成节点访问失衡，比如刚才的例子，如果生成的序列是{a、a、a、b、b、c}，就会导致前3次访问的节点都是a。
-
-### 适用场景
-主要用在服务节点性能差异比较大的情况。比如新的服务节点的性能往往要高于旧的节点，这个时候可以给新的节点设置更高的权重，让它承担更多的请求，充分发挥新节点的性能优势。
-## leastactive（最少活跃连接）
-自动感知一下，如果某个机器性能越差，那么接收的请求越少，越不活跃，此时就会给不活跃的性能差的机器更少的请求
-
-每次访问都选择连接数最少的节点。因为不同节点处理请求的速度不同，使得同一个服务消费者同每一个节点的连接数都不相同。连接数大的节点，可以认为是处理请求慢，而连接数小的节点，可以认为是处理请求快。所以在挑选节点时，可以以连接数为依据，选择连接数最少的节点访问。
-
-在实现时，需要记录跟每一个节点的连接数，这样在选择节点时，才能比较出连接数最小的节点。
-
-### 适用场景
-客户端同服务端节点的连接数是在时刻变化的，理论上连接数越少代表此时服务端节点越空闲，选择最空闲的节点发起请求，能获取更快的响应速度。
-特别是当：
-- 服务端节点性能差异较大
-- 不好做到预先定义权重
-
-采用该算法很不错。
-
-## consistanthash（一致性 hash）
-一致性Hash算法，相同参数的请求一定分发到一个provider上去，provider挂掉的时候，会基于虚拟节点均匀分配剩余的流量，抖动不会太大。
-
-通过某个hash函数，把同一个来源的请求都映射到同一个节点上。
-同一个来源的请求，只会映射到同一个节点上，可以说是具有记忆功能，不会有分布式会话的困扰。只有当该节点不可用时，请求才会被分配到相邻的可用节点上。
-### 适用场景
-如果你需要的不是随机负载均衡，是要一类请求都到一个节点，那就走这个一致性hash策略。
-
-因为它能够保证同一个客户端的请求始终访问同一个服务节点，所以适合服务端节点处理不同客户端请求差异较大的场景。比如服务端缓存里保存着客户端的请求结果，如果同一客户端一直访问一个服务节点，那么就可以一直从缓存中获取数据。
-
-这五种负载均衡算法是业界最常用的，不光在RPC调用中被广泛采用，在一些负载均衡组件比如Nginx中也有应用，所以说是一种通用的负载均衡算法，但是不是所有的业务场景都能很好解决呢？
-
-如果：
-- 服务节点数量众多，且性能差异比较大
-- 服务节点列表经常发生变化，增加节点或者减少节点时有发生
-- 客户端和服务节点之间的网络情况比较复杂，有些在一个数据中心，有些不在一个数据中心需要跨网访问，而且网络经常延迟或者抖动
-
-这时随机、轮询，第一个情况就不满足
-加权需要预先配置服务节点的权重，在节点列表经常变化的情况下不好维护，所以也不适合
-最少活跃连接算法是从客户端自身维度去判断的，在实际应用时，并不能直接反映出服务节点的请求量大小，尤其是在网络情况比较复杂的情况下，并不能做到动态的把请求发送给最合适的服务节点
-一致性hash显然也不适合这种场景
-
-
-针对上面这种场景，有一种算法更加适合，这种算法就是
-# 自适应最优选择算法
-在客户端本地维护一份同每一个服务节点的性能统计快照，每隔一段时间更新该快照。
-发起请求时，根据“二八原则”，把服务节点分成两部分，找出20%响应最慢的节点，降低权重。这样客户端就能够实时的根据自身访问每个节点性能的快慢，动态调整访问最慢的那些节点的权重，来减少访问量，从而可以优化长尾请求。
-
-自适应最优选择算法是对加权轮询算法的改良，可以看作是一种动态加权轮询算法:
-- 每隔一段时间获取客户端同每个服务节点之间调用的平均性能统计
-需要在内存中开辟一块空间记录客户端同每一个服务节点之间调用的平均性能，并每隔一段固定时间去更新。这个更新的时间间隔不能太短，太短的话很容易受瞬时的性能抖动影响，导致统计变化太快，没有参考性；同时也不能太长，太长的话时效性就会大打折扣，效果不佳。根据我的经验，1分钟的更新时间间隔是个比较合适的值。
-- 按这个性能统计对服务节点进行排序，对排在性能倒数20%的那部分节点赋予一个较低的权重，其余的节点赋予正常的权重
-关键是设定权重值，即使服务节点之间的性能差异较大，也不适合把权重设置得差异太大，可能导致性能较好的节点与性能较差的节点之间调用量相差太大，这样也不是一种合理的状态。在实际设定时，可以设置20%性能较差的节点权重为3，其余节点权重为5。
-
-这些都属于软件层面的负载均衡算法。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\246\202\344\275\225\344\277\235\350\257\201\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\350\257\267\346\261\202\347\232\204\351\241\272\345\272\217.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\246\202\344\275\225\344\277\235\350\257\201\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\350\257\267\346\261\202\347\232\204\351\241\272\345\272\217.md"
deleted file mode 100644
index 18037f9e96..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\246\202\344\275\225\344\277\235\350\257\201\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\350\257\267\346\261\202\347\232\204\351\241\272\345\272\217.md"
+++ /dev/null
@@ -1,20 +0,0 @@
-分布式系统接口的调用顺序一般无需保证的，但有时候确实需要**严格的顺序保证**。
-
-# 案例
-服务 A 调用服务 B，先插入再删除。
-俩请求过去了，落在不同机器，可能插入请求因某些原因执行慢了一些，导致删除请求先执行了，此时因为没数据所以啥效果也没。
-结果这时候插入请求过来了，数据插进去了！
-
-本该 “先插入 => 再删除”，这条数据应该不存在，但现在 “先删除 => 再插入”，数据还存在！
-
-所以这都是分布式系统一些很常见的问题。
-
-# 解决方案
-推荐从业务逻辑上设计的系统最好是不需要这种顺序性保证，因为一旦引入顺序性保障，那就要使用**分布式锁**，导致**系统复杂度上升**，**效率低下**，**热点数据压力过大**。
-
-简单来说，首先你得用 Dubbo 的一致性 hash 负载均衡策略，将比如某一个订单 id 对应的请求都给分发到某机器，接着就是在那个机器上因为可能还是多线程并发执行，你可能得立即将某个订单 id 对应的请求扔一个**队列**，强制排队确保他们的顺序性。
-
-但这样引发的后续问题就很多!
-比如说要是某个订单对应的请求特别多，造成某台机器成**热点**怎么办？解决这些问题又要开启后续一连串的复杂技术方案......曾经这类问题弄的我们头疼不已，所以，还是建议什么呢？
-
-最好是比如说刚才那种，一个订单的插入和删除操作，是否能合并成一个操作，比如就是一个删除，避免这种问题产生。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\246\202\344\275\225\345\201\232\346\234\215\345\212\241\350\267\257\347\224\261\357\274\237.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\246\202\344\275\225\345\201\232\346\234\215\345\212\241\350\267\257\347\224\261\357\274\237.md"
deleted file mode 100644
index 7fd85f120c..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\246\202\344\275\225\345\201\232\346\234\215\345\212\241\350\267\257\347\224\261\357\274\237.md"
+++ /dev/null
@@ -1,140 +0,0 @@
-负载均衡算法是为了解决服务消费者如何从众多可用服务节点中选取一个最合适的节点发起调用。
-
-但在业务中经常还会遇到这样的场景，比如服务A部署在北京、上海、广州三个数据中心，所有的服务节点按照所在的数据中心被分成了三组，那么服务A的消费者在发起调用时，该如何选择呢？
-
-这就是服务路由。
-
-# 什么是服务路由
-服务消费者在发起服务调用时，必须根据特定规则选择服务节点，从而满足某些特定需求。
-
-## 应用场景
-
-### 分组调用
-为了保证服务高可用，实现异地多活，一个服务往往不止部署在一个数据中心，而且出于节省成本等考虑，有些业务可能不仅在私有机房部署，还会采用公有云部署，甚至采用多家公有云部署。服务节点也会按照不同的数据中心分成不同的分组，这时对于服务消费者来说，选择哪一个分组调用，就必须有相应的路由规则。
-
-### 灰度发布
-在服务上线发布的过程中，一般需要先在一小部分规模的服务节点上先发布服务，然后验证功能是否正常。如果正常的话就继续扩大发布范围；如果不正常的话，就需要排查问题，解决问题后继续发布。
-
-### 流量切换
-在业务线上运行过程中，经常会遇到一些不可抗因素导致业务故障，比如
-- 某机房光缆被挖断
-- 着火等事故
-
-导致整个机房的服务都不可用。这时就需要按照某个指令，能够把原来调用这个机房服务的流量切换到其他正常的机房。
-
-### 读写分离
-大多业务来说都是读多写少，服务部署时，可以把读写分开部署，所有写接口可以部署在一起，而读接口部署在另外节点。
-
-## 服务路由规则
-### 条件路由
-基于条件表达式的路由规则，以下面的条件路由为例，我来给你详细讲解下它的用法。
-
-```bash
-condition://0.0.0.0/dubbo.test.interfaces.TestService?
-	category=routers&dynamic=true&priority=2&enabled=true&rule=
-	" + URL.encode(" host = 10.20.153.10=> host = 10.20.153.11")
-```
-`condition://` 代表这是一段用条件表达式编写的路由规则，具体的规则是
-
-```bash
-host = 10.20.153.10 => host = 10.20.153.11
-```
-`=>`前是**服务消费者的匹配条件**，后面是**服务提供者的过滤条件**：
-当**服务消费者**节点满足匹配条件，就对该**服务消费者**执行后面的过滤规则。
-这段表达式表达的意义就是IP为“10.20.153.10”的服务消费者都调用IP为“10.20.153.11”的服务提供者节点。
-
-若服务消费者的匹配条件为空，就表示对所有的服务消费者应用，就像下面的表达式一样。
-```bash
-=> host ！= 10.20.153.11
-```
-若服务提供者的过滤条件为空，就表示禁止服务消费者访问：
-```bash
-host = 10.20.153.10=>
-```
-#### 具体应用场景
-##### 排除某服务节点
-```bash
-=> host != 172.22.3.91
-```
-一旦这条路由规则被应用到线上，所有的服务消费者都不会访问IP为172.22.3.91的服务节点，这种路由规则一般应用在线上流量排除预发布机以及摘除某个故障节点。
-
-##### 白名单
-```bash
-host != 10.20.153.10,10.20.153.11 =>
-```
-除了IP为10.20.153.10和10.20.153.11的服务消费者可以发起服务调用以外，其他服务消费者都不可以。
-比如某个后台服务只允许特定的几台机器才可以访问，这样的话可以机器控制访问权限。
-
-##### 黑名单
-```bash
-host = 10.20.153.10,10.20.153.11 =>
-```
-除了IP为`10.20.153.10`和`10.20.153.11`的服务消费者不能发起服务调用以外，其他服务消费者都可以。
-比如线上经常会遇到某些调用方不管是出于有意还是无意的不合理调用，影响了服务的稳定性，这时候可以通过黑名单功能暂时予以封杀。
-
-##### 机房隔离
-
-```bash
-host = 172.22.3.* => host = 172.22.3.*
-```
-IP网段为`172.22.3.*`的服务消费者，才可以访问同网段的服务节点。
-一般应用于服务部署在多个IDC，理论上同一个IDC内的调用性能要比跨IDC调用性能要好，应用这个规则是为了实现同IDC就近访问。
-
-##### 读写分离
-```bash
-method = find*,list*,get*,is* => host =172.22.3.94,172.22.3.95
-method != find*,list*,get*,is* => host = 172.22.3.97,172.22.3.98
-```
-find*、get*、is*等读方法调用IP为172.22.3.94和172.22.3.95的节点，除此以外的写方法调用IP为172.22.3.97和172.22.3.98的节点。
-大部分业务读请求远大于写请求，而写请求的重要性往往要远远高于读请求，所以需要把读写请求进行分离，以避免读请求异常影响到写请求。
-
-### 脚本路由
-基于脚本语言的路由规则，常用的脚本语言比如JavaScript、Groovy、JRuby等。
-比如：
-
-```bash
-"script://0.0.0.0/com.foo.BarService?
-	category=routers&dynamic=false&rule=" + 
-	URL.encode("（function route(invokers) { ... } (invokers)）")
-```
-
-`script://` 代表了这是一段脚本语言编写的路由规则，具体规则定义在脚本语言的route方法实现。
-比如下面这段用JavaScript编写的route()方法：
-只有IP为10.20.153.10的服务消费者可以发起服务调用。
-```js
-function route(invokers){
-  var result = new java.util.ArrayList(invokers.size());
-  for(i =0; i < invokers.size(); i ++){
-    if("10.20.153.10".equals(invokers.get(i).getUrl().getHost())){ 
-       result.add(invokers.get(i));
-    } 
-  }
-  return result; 
- } (invokers)）;
-```
-
-服务消费者该如何获取路由规则呢？
-
-# 获取服务路由
-
-## 本地配置
-路由规则存储在服务消费者本地上。服务消费者发起调用时，从本地固定位置读取路由规则，然后按照路由规则选取一个服务节点发起调用。
-
-不排除某些服务消费者有特定的需求，需要定制自己的路由规则，这个时候就适合通过**本地配置**来定制。
-
-## 配置中心管理
-所有的服务消费者都从配置中心获取路由规则，由配置中心来统一管理。
-
-服务路由最好是存储在配置中心中，由配置中心来统一管理。这样所有服务消费者就不需要在本地管理服务路由，因为大部分的服务消费者并不关心服务路由的问题，或者说也不需要去了解其中的细节。通过**配置中心**，统一给各个服务消费者下发统一的服务路由，节省了沟通和管理成本。
-
-## 动态下发
-一般是运维人员或者开发人员，通过服务治理平台修改路由规则，服务治理平台调用配置中心接口，把修改后的路由规则持久化到配置中心。因为服务消费者订阅了路由规则的变更，于是就会从配置中心获取最新的路由规则，按照最新的路由规则来执行。
-
-动态下发可以理解为一种高级功能，它能够动态地修改路由规则，在某些业务场景下十分有用。比如某个数据中心存在问题，需要把调用这个数据中心的服务消费者都切换到其他数据中心，这时就可以通过动态下发的方式，向配置中心下发一条路由规则，将所有调用这个数据中心的请求都迁移到别的地方。
-
-当然，这三种方式也可以一起使用，这个时候服务消费者的判断优先级是本地配置>动态下发>配置中心管理。
-
-# 总结
-服务路由简单说就是为了实现某些调用的特殊需求，比如分组调用、灰度发布、流量切换、读写分离等。在业务规模比较小的时候，可能所有的服务节点都部署在一起，也就不需要服务路由。但随着业务规模的扩大、服务节点增多，尤其是涉及多数据中心部署的情况，把服务节点按照数据中心进行分组，或者按照业务的核心程度进行分组，对提高服务的可用性是十分有用的。以微博业务为例，有的服务不仅进行了核心服务和非核心服务分组，还针对私有云和公有云所处的不同数据中心也进行了分组，这样的话就可以将服务之间的调用尽量都限定在同一个数据中心内部，最大限度避免跨数据中心的网络延迟、抖动等影响。
-
-而服务路由具体是在本地配置，还是在配置中心统一管理，也是视具体业务需求而定的。如果没有定制化的需求，建议把路由规则都放到配置中心中统一存储管理。而动态下发路由规则对于服务治理十分有帮助，当数据中心出现故障的时候，可以实现动态切换流量，还可以摘除一些有故障的服务节点。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\347\232\204\346\234\215\345\212\241\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\347\232\204\346\234\215\345\212\241\351\205\215\347\275\256\347\256\241\347\220\206.md"
deleted file mode 100644
index e0bf0086ec..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\347\232\204\346\234\215\345\212\241\351\205\215\347\275\256\347\256\241\347\220\206.md"
+++ /dev/null
@@ -1,140 +0,0 @@
-单体应用只需管理一套配置，而微服务架构后，每个系统都有自己的配置，并且各不相同，有些配置还需要能够动态改变，以动态降级、切流量、扩缩容，那微服务架构下，到底服务配置该如何管理。
-
-> 本地配置
-
-
-> 最简单的，在代码里写配置。
-
-比如下面这段代码用到Hystrix，并且在代码里定义了几个配置：
-- 线程的超时时间是3000ms
-- 熔断器触发的错误比率是60%
-
-```bash
-@HystrixCommand(fallbackMethod = "getDefaultProductInventoryByCode",
-    commandProperties = {
-       @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
-       @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value="60")
-    }
-)
-
-public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
-{
-    ....
-}
-```
-
-> 还有一种方式，就是抽取到配置文件
-
-使配置与代码分离，比如下面这段代码。
-
-```java
-@HystrixCommand(commandKey = "inventory-by-productcode", fallbackMethod = "getDefaultProductInventoryByCode")
-public Optional<ProductInventoryResponse> getProductInventoryByCode(String productCode)
-{
-    ...
-}
-```
-
-相应的配置可以抽离到配置文件中，配置文件的内容如下：
-
-```bash
-hystrix.command.inventory-by-productcode.execution.isolation.thread.timeoutInMilliseconds=2000
-hystrix.command.inventory-by-productcode.circuitBreaker.errorThresholdPercentage=60
-```
-
-以上方案都相当于把配置存在应用程序的本地。这样如果需要修改配置，就要重新走一遍代码或配置的发布流程。这往往需要一次新的上线发布过程，且权限更繁琐。
-![](https://img-blog.csdnimg.cn/20210516202135664.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-倘若能有一个集中管理配置的地方，若要修改配置，只需在这个地方修改一下，线上服务就自动从这个地方同步过去，不需要走代码或配置的发布流程，不就简单多了？
-
-> 这就是配置中心
-
-设计思想就是把服务的各种配置，如
-- 代码里配置的各种参数
-- 服务降级的开关
-- 依赖的资源等
-
-都集中统一管理。服务启动时，可自动从配置中心中拉取所需配置，且若有配置变更，同样可自动从配置中心拉取最新配置信息，服务无需重新发布。
-
-配置中心一般包含如下功能：
-- 配置注册
-- 配置反注册
-- 配置查看
-- 配置变更订阅
-
-> 那配置中心的这些功能是如何实现的呢？
-
-
-> 配置存储结构
-
-一般配置中心存储配置按Group存的，同类配置放在一个Group下，以K, V键值对存储
-
-> 配置注册
-
-配置中心对外提供接口`/config/service?action=register`完成配置的注册功能，需要传递的参数包括配置对应的分组Group，以及对应的Key、Value。
-
-比如调用下面接口请求就会向配置项`global.property`中添加Key为`reload.locations`、Value为`/data1/confs/system/reload.properties`的配置。
-
-```bash
-curl "http://ip:port/config/service?action=register" 
--d "group=global.property&key=reload.locations&value=/data1/confs/system/reload.properties"
-```
-
-> 配置反注册
-
-配置中心对外提供接口`config/service?action=unregister`，传递参数包括配置对象的分组Group、对应的Key。
-比如调用下面的接口请求就会从配置项global.property中把Key为reload.locations的配置删除。
-
-```bash
-curl "http://ip:port/config/service?action=unregister" -d
-"group=global.property&key=reload.locations"
-```
-
-> 配置查看
-
-配置中心对外提供接口`config/service?action=lookup`，需传参配置对象的分组Group、对应的Key。
-比如调用下面的接口请求就会返回配置项global.property中Key为reload.locations的配置值。
-
-```bash
-curl "http://ip:port/config/service?	
-	action=lookup&group=global.property&key=reload.locations"
-```
-
-> 配置变更订阅
-
-配置中心对外提供接口`config/service?action=getSign`来完成配置变更订阅接口，客户端本地会保存一个配置对象的分组Group的sign值，同时每隔一段时间去配置中心拉取该Group的sign值，与本地保存的sign值做对比。一旦配置中心中的sign值与本地的sign值不同，客户端就会从配置中心拉取最新的配置信息。比如调用下面的接口请求就会返回配置项global.property中Key为reload.locations的配置值。
-
-```bash
-curl "http://ip:port/config/service?
-	action=getSign&group=global.property"
-```
-
-配置中心便于我们管理服务的配置信息，并且如果要修改配置信息，只需要同配置中心交互，应用程序会通过订阅配置中心的配置，自动完成配置更新。
-
-> 哪些场景适合使用配置中心呢？
-
-> 资源服务化
-
-应用规模不大时，所依赖的资源如Redis缓存或者RabbitMQ消息队列的数量不多，对应IP可直接写在配置。
-
-但随公司赚到钱后，所依赖资源数量也开始骤增，比如上千个。这时就经常会遇到个别节点不可用，若还采用本地配置，就需要去更改本地配置，把不可用的IP改成可用IP，然后发布新的配置，太麻烦了！
-
-可采用资源服务化，把对应缓存统统归为一类配置，然后若有个别机器不可用，只需在配置中心把对应IP换成可用的IP，应用程序会自动同步到本机，也无须发布。
-
-> 业务动态降级
-
-微服务架构下，拆分的服务越多，出现故障的概率就越大，因此需要有对应的服务治理手段，比如要具备动态降级能力，在依赖的服务出现故障的情况下，可以快速降级对这个服务的调用，从而保证不受影响。为此，服务消费者可以通过订阅依赖服务是否降级的配置，当依赖服务出现故障的时候，通过向配置中心下达指令，修改服务的配置为降级状态，这样服务消费者就可以订阅到配置的变更，从而降级对该服务的调用。
-
-> 分组流量切换
-
-为保证异地多活以及本地机房调用，一般服务提供者的部署会按照IDC维度进行部署，每个IDC划分为一个分组。此时，若一个IDC出现故障，可以把故障IDC机房的调用切换到其他正常IDC。为此，服务消费者可以通过订阅依赖服务的分组配置，当依赖服务的分组配置发生变更时，服务消费者就对应的把调用切换到新的分组，从而实现分组流量切换。
-
-> 开源配置中心选型
-
-- Spring Cloud Config
-Spring Cloud中使用的配置中心组件，只支持Java语言，配置存储在git中，变更配置也需要通过git操作，如果配置中心有配置变更，需要手动刷新。
-Spring Cloud Config作为配置中心的功能比较弱，只能通过git命令操作，而且变更配置的话还需要手动刷新，如果不是采用Spring Cloud框架，不建议选择。
-
-- Apollo
-携程开源的分布式配置中心，支持Java和.Net语言，客户端和配置中心通过HTTP长连接实现实时推送，并且有统一的管理界面来实现配置管理。
-Apollo功能强大，在国内许多互联网公司内部都有大量应用，其中Apollo对Spring Boot的支持比较好，如果应用本身采用的是Spring Boot开发的话，集成Apollo更容易。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\350\212\202\347\202\271\345\210\244\346\264\273.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\350\212\202\347\202\271\345\210\244\346\264\273.md"
deleted file mode 100644
index a04df9d074..0000000000
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\350\212\202\347\202\271\345\210\244\346\264\273.md"
+++ /dev/null
@@ -1,64 +0,0 @@
-> 那你首先，了解注册中心摘除机制吗？
-
-就是【服务Consumer】以【注册中心】中的数据为准，当服务端节点有变更时，【注册中心】会把变更通知给【服务Consumer】，【服务Consumer】就调用【注册中心】拉取最新的节点信息。
-
-是的，其实这种机制一般也够用了，但当网络频繁抖动时，【服务Provider】向【注册中心】汇报心跳信息可能失败。若在规定时间内，【注册中心】都未收到【服务Provider】的心跳信息，就会把该节点从可用节点列表中移除。
-
-> 更糟的是，在服务池拥有上百个节点时，每个节点都可能会被移除，导致【注册中心】可用节点的状态一直在变化，这该如何解决？
-
-可以考虑**心跳开关保护机制**。
-
-在网络频繁抖动时，【注册中心】的可用节点不断变化，这时【服务Consumer】会频繁收【服务Provider】节点变更信息，于是不断请求【注册中心】。
-当很多【服务Consumer】同时请求【注册中心】获取最新【服务Provider】节点信息时，很容易把【注册中心】带宽打满。
-
-所以需要一种措施，保证即使在网络频繁抖动时，【服务Consumer】也不会 **同时** 请求【注册中心】。
-
-这就是给【注册中心】设置一个开关。
-当开关打开时，即使网络频繁抖动，【注册中心】也不会通知所有【服务Consumer】，比如只通知10%的【服务Consumer】，这就能降低注册中心需要处理的请求量压力。
-
-> 那这种方案有什么问题吗？
-
-技术都是有利有弊的，开关的代价就是会导致【服务Consumer】感知最新服务节点信息延迟。所以应该作为一个紧急措施，在网络频繁抖动时，才打开开关。
-
-综上，该机制是为避免【服务Provider】节点频繁变更，而导致【服务Consumer】同时请求【注册中心】。
-
-> 那还有别的方案吗？
-
-是的，还有**服务节点摘除保护机制**。
-【服务Provider】在进程启动时，会注册服务到【注册中心】，并每隔一段时间，汇报心跳给【注册中心】，通知自己的存活状态。
-若隔了一段固定时间后，【服务Provider】仍没有汇报心跳给【注册中心】，【注册中心】就会认为该节点已死，将其从可用节点中移除。
-
-若遇到网络问题，大批【服务Provider】节点汇报给【注册中心】的心跳信息都可能会传达失败，【注册中心】就会把它们都从可用节点列表中移除，造成剩下可用节点难以承受所有调用，引起“雪崩”。但这种情况下，可能大部分服务提供者节点是可用的，仅因为网络原因无法汇报心跳给注册中心就被“无情”摘除。
-
-这就需要根据实际业务，设定一个阈值比例，即使网络抖动，【注册中心】也不能摘除超过这个阈值比例的节点。通常设定在20%。因为绝大部分时间，节点变化也不会变化频繁，只有在如下情况才可能发生：
-- 业务明确要下线大批节点
-这种情况又是可预知的，所以可以关闭阈值保护。
-- 网络抖动
-一般正常时，应打开阈值保护，以防网络抖动时，大批量可用服务节点被摘除。
-
-综上，该机制是为避免【服务Provider】节点被大量摘除，而导致【服务Consumer】可调用节点不足。
-
-所以这两种机制，都是因为【注册中心】的节点信息瞬息万变，所以也经常把【注册中心】称为【动态注册中心】。
-
-> 我开始疑惑了，那是否能另辟蹊径，【服务Consumer】并不严格以【注册中心】中的服务节点信息为准，而是更多的以【服务Consumer】实际调用信息来判断【服务Provider】是否可用呢？
-
-有的，这就是**静态注册中心**。
-心跳机制能保证在【服务Provider】出现异常时，【注册中心】可以及时把不可用的【服务Provider】从可用节点列表中移除，这很好。
-但仔细思考，为何不把这种心跳机制直接用在【服务Consumer】？
-
-因为【服务Provider】是向【服务Consumer】提供服务，是否可用【服务Consumer】应该比【注册中心】更清楚，因此可直接在【服务消费者】端根据调用【服务Provider】是否成功，以判定服务Provider】是否可用。
-若【服务Consumer】调用某一【服务Provider】连续失败超过一定次数，可在本地内存将该节点标记为不可用。
-每隔一段固定时间，【服务Consumer】向标记为不可用的节点发起**保活探测**，若探测成功，就将标记为不可用的节点再恢复为可用态，重新发起调用。
-
-这样【服务Provider】无需再向【注册中心】心跳汇报，【注册中心】中的服务节点信息也不会动态变化，这就是【静态注册中心】。
-
-很多公司也是一开始采用动态注册中心，后来考虑到网络的复杂性，心跳机制不一定可靠，而后开始改为采用服务消费者端的保活机制，事实证明这种机制足以应对网络频繁抖动等复杂场景。
-
-【静态注册中心】中的服务节点信息并不是一直不变，当在
-- 业务上线
-要把正在部署的服务节点从注册中心中移除，等到服务部署完毕，完全可用的时候，再加入到注册中心
-- 运维人工增加或删除服务节点
-需要调用注册中心提供的接口，添加节点信息或者删除节点
-
-这些预先感知场景，还是有必要修改【注册中心】中的服务节点信息的。
-看起来【静态注册中心】好像退化成了配置中心，只是这时配置中心存储的不是某一项简单的配置，而是某服务的可用节点信息。
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217Session\346\236\266\346\236\204\346\274\224\347\244\272\345\217\262.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217Session\346\236\266\346\236\204\346\274\224\347\244\272\345\217\262.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217Session\346\236\266\346\236\204\346\274\224\347\244\272\345\217\262.md"
rename to "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217Session\346\236\266\346\236\204\346\274\224\347\244\272\345\217\262.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\347\232\204\345\237\272\346\234\254\346\246\202\345\277\265.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\347\232\204\345\237\272\346\234\254\346\246\202\345\277\265.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\347\232\204\345\237\272\346\234\254\346\246\202\345\277\265.md"
rename to "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\347\232\204\345\237\272\346\234\254\346\246\202\345\277\265.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\215\225\347\202\271\347\231\273\345\275\225.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204/\345\215\225\347\202\271\347\231\273\345\275\225.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\215\225\347\202\271\347\231\273\345\275\225.md"
rename to "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204/\345\215\225\347\202\271\347\231\273\345\275\225.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\272\224\347\224\250\346\234\215\345\212\241\345\231\250\351\233\206\347\276\244\347\232\204Session\347\256\241\347\220\206.md" "b/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204/\345\272\224\347\224\250\346\234\215\345\212\241\345\231\250\351\233\206\347\276\244\347\232\204Session\347\256\241\347\220\206.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\272\224\347\224\250\346\234\215\345\212\241\345\231\250\351\233\206\347\276\244\347\232\204Session\347\256\241\347\220\206.md"
rename to "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204/\345\272\224\347\224\250\346\234\215\345\212\241\345\231\250\351\233\206\347\276\244\347\232\204Session\347\256\241\347\220\206.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo Remoting\346\250\241\345\235\227\350\257\246\350\247\243.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/Dubbo Remoting\346\250\241\345\235\227\350\257\246\350\247\243.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo Remoting\346\250\241\345\235\227\350\257\246\350\247\243.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/Dubbo Remoting\346\250\241\345\235\227\350\257\246\350\247\243.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\345\256\236\346\210\230(\344\270\200) - \345\237\272\347\241\200.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/Dubbo\345\256\236\346\210\230(\344\270\200) - \345\237\272\347\241\200.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\345\256\236\346\210\230(\344\270\200) - \345\237\272\347\241\200.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/Dubbo\345\256\236\346\210\230(\344\270\200) - \345\237\272\347\241\200.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\344\270\232\345\212\241\345\274\200\345\217\221.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/Dubbo\345\256\236\346\210\230(\345\233\233) -\344\270\232\345\212\241\347\216\257\345\242\203\346\220\255\345\273\272.md"
similarity index 96%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\344\270\232\345\212\241\345\274\200\345\217\221.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/Dubbo\345\256\236\346\210\230(\345\233\233) -\344\270\232\345\212\241\347\216\257\345\242\203\346\220\255\345\273\272.md"
index f6d9de7046..5c246e382b 100644
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\344\270\232\345\212\241\345\274\200\345\217\221.md"
+++ "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/Dubbo\345\256\236\346\210\230(\345\233\233) -\344\270\232\345\212\241\347\216\257\345\242\203\346\220\255\345\273\272.md"	
@@ -1,3 +1,6 @@
+基于Springboot和Dubbo的结合，进行用户模块业务开发，注册发现、负载均衡、路由策略等多项Dubbo核心特性
+进一步了解API网关与业务模块的结合和开发。
+
 # 0 概要
 - API网关权限验证和其他服务交互
 - 开发Springboot的自定义配置
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/\345\246\202\344\275\225\350\256\276\350\256\241\344\270\200\344\270\252RPC.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/RPC\346\234\215\345\212\241\346\262\273\347\220\206\346\241\206\346\236\266\345\256\236\346\210\230(\344\270\200) - \346\211\213\345\206\231RPC.md"
similarity index 50%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/\345\246\202\344\275\225\350\256\276\350\256\241\344\270\200\344\270\252RPC.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/RPC\346\234\215\345\212\241\346\262\273\347\220\206\346\241\206\346\236\266\345\256\236\346\210\230(\344\270\200) - \346\211\213\345\206\231RPC.md"
index 24258325e4..ccb28b7975 100644
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/\345\246\202\344\275\225\350\256\276\350\256\241\344\270\200\344\270\252RPC.md"
+++ "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/RPC\346\234\215\345\212\241\346\262\273\347\220\206\346\241\206\346\236\266\345\256\236\346\210\230(\344\270\200) - \346\211\213\345\206\231RPC.md"	
@@ -1,61 +1,63 @@
-很多语言都内置了RPC技术。
-Java RMI
-.NET Remoting
-远古时期，就有很多尝试：
-- Corba（Common ObjectRequest Broker Architecture）公共对象请求代理体系结构，OMG组织在1991年提出的公用对象请求代理程序结构的技术规范。底层结构是基于面向对象模型的，由OMG接口描述语言(OMG Interface Definition Language，OMG IDL)、对象请求代理(Objec tRequest Broker，ORB)和IIOP标准协议（Internet Inter ORB Protocol，也称网络ORB交换协议）3个关键模块组成。
-- COM（Component Object Model，组件对象模型）是微软公司于1993年提出的一种组件技术，它是一种平台无关、语言中立、位置透明、支持网络的中间件技术。很多老一辈程序员心目中的神书《COM本质论》。
-
 # 1 从使用者考虑
-定义过程接口
-![](https://img-blog.csdnimg.cn/20191114132816733.png)
 
-客户端使用生成的stub代理对象
-![](https://img-blog.csdnimg.cn/20191114132906197.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+用，户使用RPC框架开发过程时需要做什么?
+
+- 定义过程接口
+![](https://img-blog.csdnimg.cn/20191114132816733.png)
+- 服务端实现过程
+- 客户端使用生成的stub代理对象
+![](https://img-blog.csdnimg.cn/20191114132906197.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
-# 2 客户端的设计
-客户端生成过程接口的代理对象。
+# 2 设计客户端
+客户端生成过程接口的代理对象
+设计客户端代理工厂，用JDK动态代理即可生成接口的代理对象。
+![](https://img-blog.csdnimg.cn/20191114132958985.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
-客户端代理工厂，用JDK动态代理（或者 AOP 实现）即可生成接口的代理对象。
-![](https://img-blog.csdnimg.cn/20191114132958985.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+## 思考
+1、在ClientStubInvocationHandler中 需完成哪些事情?
+![](https://img-blog.csdnimg.cn/2019111413331941.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
-- ClientStubInvocationHandler
-![](https://img-blog.csdnimg.cn/2019111413331941.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+2、将请求的内容编组为消息这件事由谁来做?
+![](https://img-blog.csdnimg.cn/20191114133337475.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
-- 消息协议是固定不变的吗?它与什么有关?
+3、消息协议是固定不变的吗?它与什么有关?
 看框架对协议的支持广度，如果支持多种协议，就是会灵活变化的，它与具体的服务相关，
 A服务提供者可能选用的是协议1，B服务提供者可能选用协议2。
 
-- 某服务是用的什么消息协议这个信息从哪来?
+
+4、某服务是用的什么消息协议这个信息从哪来?
 从获取的服务信息中来，因此需要一个服务信息发现者。
 
 `把发现者设计出来, 要求:可灵活支持多种发现机制`
 
-![](https://img-blog.csdnimg.cn/20191115003249371.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/2019111500530155.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-- 想要做到可以支持多种协议，类该如何设计?面向接口、策略模式、组合
-![](https://img-blog.csdnimg.cn/20191115005522357.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-问题:
+![](https://img-blog.csdnimg.cn/20191115003249371.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/2019111500530155.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+5、我们想要做到可以支持多种协议，我们的类该如何设计?
+面向接口、策略模式、组合
+![](https://img-blog.csdnimg.cn/20191115005522357.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- 问题:
 ➢ marshalling和unmarshalling方法该定义怎样的参数与返回值?
 ➢ 编组、解组的操作对象是请求、响应，请求、响应的内容是不同的。编组、解组两个方法是否满足?
 
 ## 设计客户端协议层
 
-定义框架标准的请求, 响应类
+6. 定义框架标准的请求, 响应类
 
-![](https://img-blog.csdnimg.cn/20191115005858703.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191115005858703.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
-7. 将协议层扩展为四个![](https://img-blog.csdnimg.cn/20191115010053143.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+7. 将协议层扩展为四个![](https://img-blog.csdnimg.cn/20191115010053143.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 消息协议独立为一层(客户端、服务端均需要)
 
 网络层
 发送请求,获得响应  
 要发起网络请求,则须知道服务地址
-![](https://img-blog.csdnimg.cn/20191115123239178.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191115123239178.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
 - 客户端完整类图
-![](https://img-blog.csdnimg.cn/20191115123823652.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191115123823652.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+实现客户端
+按照类图设计，填码!
+看ClientStubProxyFactory核心类的代码示例!
 
 在实现过程中，协议层涉及一个重要概念
 - 参数序列化、反序列
@@ -64,16 +66,16 @@ A服务提供者可能选用的是协议1，B服务提供者可能选用协议2
 #  3 设计服务端
 ## 3.1 RPCServer
 客户端请求过来了，服务端首先需要通过RPCServer接收请求。
-![](https://img-blog.csdnimg.cn/20191115235722470.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191115235722470.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
-![](https://img-blog.csdnimg.cn/20191115235736165.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191115235736165.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
 - RPCServer
-![](https://img-blog.csdnimg.cn/20191116001129440.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191116001129440.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
 ## 3.2 思考
 RPCServer接收到客户端请求后，还需要做哪些工作?
-![](https://img-blog.csdnimg.cn/20191115235943756.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191115235943756.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
 
 网络层在RPCServer中提供多线程来处理请求，消息协议层复用客户端设计的。
 (设计一个`请求处理类`，来完成网络层以上的事情。)
@@ -81,7 +83,7 @@ RPCServer接收到客户端请求后，还需要做哪些工作?
 ## 3.3 RequestHandler
 RPCServer接收到请求后，将请求交给RequestHandler来处理
 RequestHandler调用协议层来解组请求消息为Request对象，然后调用过程!
-![](https://img-blog.csdnimg.cn/20191116000813865.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191116000813865.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
 ### 人性的拷问
 ➢ RequestHandler如何得到过程对象?
@@ -90,12 +92,12 @@ RequestHandler调用协议层来解组请求消息为Request对象，然后调
 ➢ 是否需要一个过程注册模块?
 
 看看之后的设计
-![](https://img-blog.csdnimg.cn/20191116002808402.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191116002808402.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
 ➢ `过程注册模块`:让用户将他们的过程注册到RPC框架
 ➢ `过程暴露模块`:想对外发布(暴露)服务注册、暴露可以由同一个类实现
 
-![](https://img-blog.csdnimg.cn/20191116003017951.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191116003017951.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
 1. RPCServer 中实现网络层: Netty, 使用RequestHandler
 2. ServiceRegister 模块实现服务注册、发布。
@@ -103,9 +105,7 @@ RequestHandler调用协议层来解组请求消息为Request对象，然后调
 
 ## 代码实现
 - 首先,用户需要设置你的端口和协议哦
-![](https://img-blog.csdnimg.cn/20191116003532999.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
+![](https://img-blog.csdnimg.cn/20191116003532999.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
-![](https://img-blog.csdnimg.cn/20191116003822920.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 
-> - [相关源码](https://github.com/Wasabi1234/rpc-framework)
\ No newline at end of file
+![](https://img-blog.csdnimg.cn/20191116003822920.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/rpc.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/rpc.md"
new file mode 100644
index 0000000000..95dde59886
--- /dev/null
+++ "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/rpc.md"
@@ -0,0 +1,76 @@
+# 1 RPC是什么
+![](https://img-blog.csdnimg.cn/20191114010847975.png)
+remote procedure call (RPC) :远程`过程`调用
+
+`过程` 就是业务处理、计算任务，更直白理解，就是程序。(像调用本地方法一样调用远程的过程。)
+
+RPC采用Client-Server结构，通过request-response消息模式实现。
+
+## 1.1 RPC VS RMI
+RMI(remote method invocation)远程方法调用是oop领域中RPC的一种具体实现。
+
+## 1.2 webservice、restfull接 口调用是RPC吗?
+都是RPC,仅消息的组织方式及消息协议不同。
+
+## 1.3 远程过程调用较本地调用有何不同?
+- 速度相对慢
+- 可靠性减弱
+
+# 2 RPC的流程环节
+![](https://img-blog.csdnimg.cn/20191114011312532.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+1. 客户端处理过程中`调用`Client stub (就像调用本地方法一样)，传递参数
+2. Client stub将参数`编组`为消息，然后通过系统调用向服务端发送消息
+3. 客户端本地操作系统将消息从客户端机器`发送`到服务端机器
+4. 服务端操作系统将接收到的数据包`传递`给Server stub
+5. Server stub `解组`消息为参数
+6. Sever stub`再调用`服务端的过程，过程执行结果以反方向的相同步骤响应给客户端
+
+流程需要解决什么问题呢?
+# 3 RPC协议
+RPC调用过程中需要将参数编组为消息进行发送，接收方需要解组消息为参数，过程处理结果同样需要经编组、解组。
+
+消息由哪些部分构成及消息的表示形式就构成了消息协议。
+`RPC调用过程中采用的消息协议称为RPC协议。`
+
+RPC协议规定请求、响应消息的格式在TCP (网络传输控制协议)上可选用或自定义消息协议来完成RPC消息交互
+
+我们可以选用通用的标准协议(如: http、 https) ，也可根据自身的需要定义自己的消息协议!
+
+- 常见的RPC协议
+![](https://img-blog.csdnimg.cn/20191114130245795.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+# 4 RPC框架(Java领域)
+封装好参数编组、消息解组、底层网络通信的RPC程序开发框架，带来的便捷是可以直接在其基础上只需专注于过程代码编写。
+
+➢ 传统的webservice框架
+- Apache CXF
+![](https://img-blog.csdnimg.cn/20191114130600432.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- Apache Axis2
+![](https://img-blog.csdnimg.cn/20191114130702279.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- Java 自带的JAX-WS
+![](https://img-blog.csdnimg.cn/20191114130758130.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+webService框架大多基于标准的SOAP协议。
+
+➢ 新兴的微服务框架:
+ 
+- Dubbo
+![](https://img-blog.csdnimg.cn/20191114130841739.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- spring cloud alibaba
+![](https://img-blog.csdnimg.cn/20191114131014282.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- Apache Thrift
+![](https://img-blog.csdnimg.cn/20191114131042186.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+## 为何使用RPC
+- 服务化
+- 可重用
+- 系统间交互调用
+# 5 RPC相关术语
+➢ Client、 Server、 calls、 replies、 service,  programs, procedures, version, marshalling(编组), unmarshalling(解组)
+➢ 一个网络服务由一个或多个远程程序集构成
+➢ 一个远程程序实现一个或多个远程过程
+➢ 过程、过程的参数、结果在程序协议说明书中定义说明
+➢ 为兼容程序协议变更、一个服务端可能支持多个版本的远程程序
+
+
+- 欢迎扫码关注,掌握更多核心技术
+ ![](https://img-blog.csdnimg.cn/20191114131953666.jpg)
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/\346\263\250\345\206\214\344\270\255\345\277\203.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/\346\263\250\345\206\214\344\270\255\345\277\203.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/\346\263\250\345\206\214\344\270\255\345\277\203.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/\346\263\250\345\206\214\344\270\255\345\277\203.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230/Dubbo\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230 - \350\264\237\350\275\275\345\235\207\350\241\241\347\256\227\346\263\225LoadBalance.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230/Dubbo\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230 - \350\264\237\350\275\275\345\235\207\350\241\241\347\256\227\346\263\225LoadBalance.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230/Dubbo\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230 - \350\264\237\350\275\275\345\235\207\350\241\241\347\256\227\346\263\225LoadBalance.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo/\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230/Dubbo\346\272\220\347\240\201\350\247\243\346\236\220\345\256\236\346\210\230 - \350\264\237\350\275\275\345\235\207\350\241\241\347\256\227\346\263\225LoadBalance.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo\350\277\230\346\230\257SpringCloud?.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo\350\277\230\346\230\257SpringCloud?.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo\350\277\230\346\230\257SpringCloud?.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Dubbo\350\277\230\346\230\257SpringCloud?.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/Eureka\346\272\220\347\240\201\345\210\206\346\236\220/\346\263\250\345\206\214\343\200\201\345\277\203\350\267\263\343\200\201\345\220\214\346\255\245\343\200\201\345\211\224\351\231\244\346\272\220\347\240\201.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/Eureka\346\272\220\347\240\201\345\210\206\346\236\220/\346\263\250\345\206\214\343\200\201\345\277\203\350\267\263\343\200\201\345\220\214\346\255\245\343\200\201\345\211\224\351\231\244\346\272\220\347\240\201.md"
similarity index 98%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/Eureka\346\272\220\347\240\201\345\210\206\346\236\220/\346\263\250\345\206\214\343\200\201\345\277\203\350\267\263\343\200\201\345\220\214\346\255\245\343\200\201\345\211\224\351\231\244\346\272\220\347\240\201.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/Eureka\346\272\220\347\240\201\345\210\206\346\236\220/\346\263\250\345\206\214\343\200\201\345\277\203\350\267\263\343\200\201\345\220\214\346\255\245\343\200\201\345\211\224\351\231\244\346\272\220\347\240\201.md"
index bb62028e20..55909ca80c 100644
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/Eureka\346\272\220\347\240\201\345\210\206\346\236\220/\346\263\250\345\206\214\343\200\201\345\277\203\350\267\263\343\200\201\345\220\214\346\255\245\343\200\201\345\211\224\351\231\244\346\272\220\347\240\201.md"	
+++ "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/Eureka\346\272\220\347\240\201\345\210\206\346\236\220/\346\263\250\345\206\214\343\200\201\345\277\203\350\267\263\343\200\201\345\220\214\346\255\245\343\200\201\345\211\224\351\231\244\346\272\220\347\240\201.md"	
@@ -1,3 +1,8 @@
+> 全是干货的 Java 技术仓库：
+> 
+> - https://github.com/Wasabi1234/Java-Interview-Tutorial
+
+
 ![](https://img-blog.csdnimg.cn/20200831170845801.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 
 - 当你在启动类上添加了启动 Eureka 服务注册中心注解时，到底发生了什么呢？![](https://img-blog.csdnimg.cn/20200509203159493.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
@@ -429,4 +434,7 @@ eureka 短时间内大量微服务被删除，会打开自我保护机制，避
 ![](https://img-blog.csdnimg.cn/20200510042959309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 这里最终还是调用如下：
 只不过此时关键在于是否为副本节点
-![](https://img-blog.csdnimg.cn/20200510043440551.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
\ No newline at end of file
+![](https://img-blog.csdnimg.cn/20200510043440551.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(1)-\347\256\200\344\273\213.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(1)-\347\256\200\344\273\213.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(1)-\347\256\200\344\273\213.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(1)-\347\256\200\344\273\213.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(10)-Hystrix.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(10)-Hystrix.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(10)-Hystrix.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(10)-Hystrix.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(11)-\345\276\256\346\234\215\345\212\241\347\275\221\345\205\263\346\241\206\346\236\266Zuul\345\217\212\345\205\266\345\256\236\347\216\260\345\216\237\347\220\206.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(11)-\345\276\256\346\234\215\345\212\241\347\275\221\345\205\263\346\241\206\346\236\266Zuul\345\217\212\345\205\266\345\256\236\347\216\260\345\216\237\347\220\206.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(11)-\345\276\256\346\234\215\345\212\241\347\275\221\345\205\263\346\241\206\346\236\266Zuul\345\217\212\345\205\266\345\256\236\347\216\260\345\216\237\347\220\206.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(11)-\345\276\256\346\234\215\345\212\241\347\275\221\345\205\263\346\241\206\346\236\266Zuul\345\217\212\345\205\266\345\256\236\347\216\260\345\216\237\347\220\206.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(3)-\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(3)-\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(3)-\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(3)-\346\234\215\345\212\241\346\263\250\345\206\214\344\270\216\345\217\221\347\216\260.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(4)-\345\276\256\346\234\215\345\212\241\344\270\255\347\232\204\346\234\215\345\212\241\346\213\206\345\210\206.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(4)-\345\276\256\346\234\215\345\212\241\344\270\255\347\232\204\346\234\215\345\212\241\346\213\206\345\210\206.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(4)-\345\276\256\346\234\215\345\212\241\344\270\255\347\232\204\346\234\215\345\212\241\346\213\206\345\210\206.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(4)-\345\276\256\346\234\215\345\212\241\344\270\255\347\232\204\346\234\215\345\212\241\346\213\206\345\210\206.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(5)-\345\272\224\347\224\250\351\200\232\344\277\241.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(5)-\345\272\224\347\224\250\351\200\232\344\277\241.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(5)-\345\272\224\347\224\250\351\200\232\344\277\241.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(5)-\345\272\224\347\224\250\351\200\232\344\277\241.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(6)-\347\273\237\344\270\200\351\205\215\347\275\256\344\270\255\345\277\203.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(6)-\347\273\237\344\270\200\351\205\215\347\275\256\344\270\255\345\277\203.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(6)-\347\273\237\344\270\200\351\205\215\347\275\256\344\270\255\345\277\203.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/SpringCloud\345\276\256\346\234\215\345\212\241\345\256\236\346\210\230(6)-\347\273\237\344\270\200\351\205\215\347\275\256\344\270\255\345\277\203.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/\345\271\277\345\221\212\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/\345\271\277\345\221\212\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/\345\271\277\345\221\212\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/\345\271\277\345\221\212\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/\346\212\225\346\224\276\347\263\273\347\273\237\350\256\276\350\256\241.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/\346\212\225\346\224\276\347\263\273\347\273\237\350\256\276\350\256\241.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/\346\212\225\346\224\276\347\263\273\347\273\237\350\256\276\350\256\241.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/\346\212\225\346\224\276\347\263\273\347\273\237\350\256\276\350\256\241.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/\346\243\200\347\264\242\347\263\273\347\273\237.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/\346\243\200\347\264\242\347\263\273\347\273\237.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/\346\243\200\347\264\242\347\263\273\347\273\237.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/\346\243\200\347\264\242\347\263\273\347\273\237.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/\350\204\232\346\211\213\346\236\266.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/\350\204\232\346\211\213\346\236\266.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Spring Cloud Netflix(old)/\350\204\232\346\211\213\346\236\266.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/Spring Cloud Netflix(old)/\350\204\232\346\211\213\346\236\266.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel(\344\270\200)-Sentinel\347\232\204\347\224\261\346\235\245.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel(\344\270\200)-Sentinel\347\232\204\347\224\261\346\235\245.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel(\344\270\200)-Sentinel\347\232\204\347\224\261\346\235\245.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel(\344\270\200)-Sentinel\347\232\204\347\224\261\346\235\245.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel(\344\272\214)-Sentinel\346\216\247\345\210\266\345\217\260\347\232\204\344\270\213\350\275\275\344\270\216\345\220\257\345\212\250.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel(\344\272\214)-Sentinel\346\216\247\345\210\266\345\217\260\347\232\204\344\270\213\350\275\275\344\270\216\345\220\257\345\212\250.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel(\344\272\214)-Sentinel\346\216\247\345\210\266\345\217\260\347\232\204\344\270\213\350\275\275\344\270\216\345\220\257\345\212\250.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel(\344\272\214)-Sentinel\346\216\247\345\210\266\345\217\260\347\232\204\344\270\213\350\275\275\344\270\216\345\220\257\345\212\250.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel-\350\247\204\345\210\231\347\256\241\347\220\206\345\217\212\346\216\250\351\200\201\346\250\241\345\274\217\357\274\210pull&push\357\274\211.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel-\350\247\204\345\210\231\347\256\241\347\220\206\345\217\212\346\216\250\351\200\201\346\250\241\345\274\217\357\274\210pull&push\357\274\211.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel-\350\247\204\345\210\231\347\256\241\347\220\206\345\217\212\346\216\250\351\200\201\346\250\241\345\274\217\357\274\210pull&push\357\274\211.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Sentinel/SpringCloudAlibaba\344\271\213Sentinel-\350\247\204\345\210\231\347\256\241\347\220\206\345\217\212\346\216\250\351\200\201\346\250\241\345\274\217\357\274\210pull&push\357\274\211.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Sentinel/\351\230\277\351\207\214Sentinel\346\240\270\345\277\203\346\272\220\347\240\201\350\247\243\346\236\220-\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217\346\234\200\344\275\263\345\256\236\350\267\265\357\274\210\344\270\213\357\274\211.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Sentinel/\351\230\277\351\207\214Sentinel\346\240\270\345\277\203\346\272\220\347\240\201\350\247\243\346\236\220-\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217\346\234\200\344\275\263\345\256\236\350\267\265\357\274\210\344\270\213\357\274\211.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Sentinel/\351\230\277\351\207\214Sentinel\346\240\270\345\277\203\346\272\220\347\240\201\350\247\243\346\236\220-\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217\346\234\200\344\275\263\345\256\236\350\267\265\357\274\210\344\270\213\357\274\211.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Sentinel/\351\230\277\351\207\214Sentinel\346\240\270\345\277\203\346\272\220\347\240\201\350\247\243\346\236\220-\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217\346\234\200\344\275\263\345\256\236\350\267\265\357\274\210\344\270\213\357\274\211.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(1) - \346\246\202\350\277\260.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(1) - \346\246\202\350\277\260.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(1) - \346\246\202\350\277\260.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(1) - \346\246\202\350\277\260.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(10) - Spring Cloud GateWay.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(10) - Spring Cloud GateWay.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(10) - Spring Cloud GateWay.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(10) - Spring Cloud GateWay.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(11) - Spring Cloud\350\256\244\350\257\201\346\216\210\346\235\203.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(11) - Spring Cloud\350\256\244\350\257\201\346\216\210\346\235\203.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(11) - Spring Cloud\350\256\244\350\257\201\346\216\210\346\235\203.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(11) - Spring Cloud\350\256\244\350\257\201\346\216\210\346\235\203.md"
diff --git "a/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(12) - Nacos\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(12) - Nacos\351\205\215\347\275\256\347\256\241\347\220\206.md"
new file mode 100644
index 0000000000..1414d1cc5e
--- /dev/null
+++ "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(12) - Nacos\351\205\215\347\275\256\347\256\241\347\220\206.md"	
@@ -0,0 +1,92 @@
+该技术类似于Spring Cloud Config
+
+# 1 配置管理的意义
+## 项目痛点
+> 不同环境 ,不同配置
+> 配置属性动态刷新
+
+为了解决痛点,常用方案是引入配置服务器,架构如下:
+![](https://img-blog.csdnimg.cn/20191215221554158.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- 引入依赖
+![](https://img-blog.csdnimg.cn/20191215221821994.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- 写配置(约定大于配置),要谨遵如下映射关系图哦
+![](https://img-blog.csdnimg.cn/20191215222031784.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- 创建新的bs.yml配置文件
+![](https://img-blog.csdnimg.cn/20191215222158954.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- 在NACOS操作面板配置
+![](https://img-blog.csdnimg.cn/20191215222601454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/2019121522261920.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- 启动内容中心
+![](https://img-blog.csdnimg.cn/20191215222843437.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+# 2 配置的动态刷新
+修改配置后,应用可以动态刷新配置,而不需要重启应用
+- 添加@RefreshScope注解即可
+![](https://img-blog.csdnimg.cn/20191215223346781.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+## 回滚的Bug(不要回滚到初始状态! Nacos 1.2会解决)
+- https://github.com/alibaba/nacos/issues/186
+![](https://img-blog.csdnimg.cn/2019121522510516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+- https://qithub.com/alibaba/nacos/issues/434
+![](https://img-blog.csdnimg.cn/20191215225151357.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+> 可以尽量避免使用历史版本,而直接修改配置
+
+# 3  共享配置
+## 3.1 相同应用内
+所指定的大于通用的
+## 3.2 不同应用间
+- 比如这段配置在用户/内容中心都有,可以将其交给nacos管理共享配置
+![](https://img-blog.csdnimg.cn/201912152258123.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+下面看NACOS配置共享的两种方案:
+### shared-detaids
+![](https://img-blog.csdnimg.cn/20191215230042683.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+### ext-config
+![](https://img-blog.csdnimg.cn/20191215230201384.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+### 优先级
+```bash
+shared-dataids < ext-config < 自动
+```
+
+# 4 引导上下文
+## 4.1 用来连接配置服务器,读取外部配置
+我们的bootstrap.yml就是引导上下文的配置文件
+对于我们的应用来说,就是来连接NACOS,读取NACOS中的配置的
+## 4.2  Application Context的父上下文
+远程配置(NACOS) & 本地配置优先级
+默认情况下,远程配置优先级较高哦
+- 如下的配置文件必须放在远程配置中才生效
+![](https://img-blog.csdnimg.cn/20191215230727734.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+# 5 数据持久化
+## 5.1 作为服务发现组件
+其数据都是存在本地路径:
+
+```bash
+~/nacos/naming/public
+```
+![](https://img-blog.csdnimg.cn/2019121523504728.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191215235208474.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+## 5.2 作为配置服务器
+数据分为如下两部分
+### NACOS web ui中添加的配置数据: 
+
+```bash
+$NACOS_ HOME/data/derby-data
+```
+如果想查看其具体内容,必须停止nacos,然后连接Derby这个Apache开发的内嵌数据库,通过IDEA的数据源连接
+![](https://img-blog.csdnimg.cn/20191215235547255.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20191215235915619.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+
+### 配置属性的快照
+~/nacos/config
+
+# 6 搭建生产可用的Nacos集群
+一次搭建即可,也不一定就你哦!
+- 推荐阅读
+[搭建生产可用的Nacos集群](https://www.imooc.com/article/288153)
+# 7 NACOS配置管理最佳实践
+- 能放本地,不放远程
+- 尽量规避优先级
+- 定规范,例如所有配置属性都要加上注释
+- 配置管理人员尽量少(官方正在开发权限控制)
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(13) -  Sleuth\350\260\203\347\224\250\351\223\276\347\233\221\346\216\247.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(13) -  Sleuth\350\260\203\347\224\250\351\223\276\347\233\221\346\216\247.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(13) -  Sleuth\350\260\203\347\224\250\351\223\276\347\233\221\346\216\247.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(13) -  Sleuth\350\260\203\347\224\250\351\223\276\347\233\221\346\216\247.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(2) - \345\205\263\344\272\216Spring Boot\344\275\240\344\270\215\345\217\257\344\270\215\347\237\245\351\201\223\347\232\204\345\256\236\346\203\205.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(2) - \345\205\263\344\272\216Spring Boot\344\275\240\344\270\215\345\217\257\344\270\215\347\237\245\351\201\223\347\232\204\345\256\236\346\203\205.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(2) - \345\205\263\344\272\216Spring Boot\344\275\240\344\270\215\345\217\257\344\270\215\347\237\245\351\201\223\347\232\204\345\256\236\346\203\205.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(2) - \345\205\263\344\272\216Spring Boot\344\275\240\344\270\215\345\217\257\344\270\215\347\237\245\351\201\223\347\232\204\345\256\236\346\203\205.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(3) - \345\276\256\346\234\215\345\212\241\346\213\206\345\210\206\344\270\216\347\274\226\345\206\231.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(3) - \345\276\256\346\234\215\345\212\241\346\213\206\345\210\206\344\270\216\347\274\226\345\206\231.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(3) - \345\276\256\346\234\215\345\212\241\346\213\206\345\210\206\344\270\216\347\274\226\345\206\231.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(3) - \345\276\256\346\234\215\345\212\241\346\213\206\345\210\206\344\270\216\347\274\226\345\206\231.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(4) - NACOS \346\234\215\345\212\241\345\217\221\347\216\260\344\270\216\346\263\250\345\206\214\344\270\255\345\277\203.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(4) - NACOS \346\234\215\345\212\241\345\217\221\347\216\260\344\270\216\346\263\250\345\206\214\344\270\255\345\277\203.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(4) - NACOS \346\234\215\345\212\241\345\217\221\347\216\260\344\270\216\346\263\250\345\206\214\344\270\255\345\277\203.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(4) - NACOS \346\234\215\345\212\241\345\217\221\347\216\260\344\270\216\346\263\250\345\206\214\344\270\255\345\277\203.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(5) - \350\264\237\350\275\275\345\235\207\350\241\241.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(5) - \350\264\237\350\275\275\345\235\207\350\241\241.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(5) - \350\264\237\350\275\275\345\235\207\350\241\241.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(5) - \350\264\237\350\275\275\345\235\207\350\241\241.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(6) - \345\243\260\346\230\216\345\274\217HTTP\345\256\242\346\210\267\347\253\257-Feign.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(6) - \345\243\260\346\230\216\345\274\217HTTP\345\256\242\346\210\267\347\253\257-Feign.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(6) - \345\243\260\346\230\216\345\274\217HTTP\345\256\242\346\210\267\347\253\257-Feign.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(6) - \345\243\260\346\230\216\345\274\217HTTP\345\256\242\346\210\267\347\253\257-Feign.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(7) - \346\234\215\345\212\241\345\256\271\351\224\231-Sentinel.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(7) - \346\234\215\345\212\241\345\256\271\351\224\231-Sentinel.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(7) - \346\234\215\345\212\241\345\256\271\351\224\231-Sentinel.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(7) - \346\234\215\345\212\241\345\256\271\351\224\231-Sentinel.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(8)-\345\256\241\346\240\270\344\270\232\345\212\241\347\232\204\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\345\244\204\347\220\206\345\256\236\347\216\260.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(8)-\345\256\241\346\240\270\344\270\232\345\212\241\347\232\204\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\345\244\204\347\220\206\345\256\236\347\216\260.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(8)-\345\256\241\346\240\270\344\270\232\345\212\241\347\232\204\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\345\244\204\347\220\206\345\256\236\347\216\260.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(8)-\345\256\241\346\240\270\344\270\232\345\212\241\347\232\204\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\345\244\204\347\220\206\345\256\236\347\216\260.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(9) - Spring Cloud Stream.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(9) - Spring Cloud Stream.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(9) - Spring Cloud Stream.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/SpringCloudAlibaba(new)/Spring Cloud Alibaba \345\256\236\346\210\230(9) - Spring Cloud Stream.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\346\225\205\351\232\234\345\244\204\347\220\206\346\226\271\346\241\210.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\225\205\351\232\234\345\244\204\347\220\206\346\226\271\346\241\210.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\346\225\205\351\232\234\345\244\204\347\220\206\346\226\271\346\241\210.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\225\205\351\232\234\345\244\204\347\220\206\346\226\271\346\241\210.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\344\270\213\350\257\267\346\261\202\350\260\203\347\224\250\345\244\261\350\264\245\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\344\270\213\350\257\267\346\261\202\350\260\203\347\224\250\345\244\261\350\264\245\350\247\243\345\206\263\346\226\271\346\241\210.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\344\270\213\350\257\267\346\261\202\350\260\203\347\224\250\345\244\261\350\264\245\350\247\243\345\206\263\346\226\271\346\241\210.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\344\270\213\350\257\267\346\261\202\350\260\203\347\224\250\345\244\261\350\264\245\350\247\243\345\206\263\346\226\271\346\241\210.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\347\224\265\345\225\206\344\272\222\350\201\224\347\275\221\345\246\202\344\275\225\345\201\232\345\276\256\346\234\215\345\212\241\346\262\273\347\220\206(SOA governance)\357\274\237.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/\347\224\265\345\225\206\344\272\222\350\201\224\347\275\221\345\246\202\344\275\225\345\201\232\345\276\256\346\234\215\345\212\241\346\262\273\347\220\206(SOA governance)\357\274\237.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\347\224\265\345\225\206\344\272\222\350\201\224\347\275\221\345\246\202\344\275\225\345\201\232\345\276\256\346\234\215\345\212\241\346\262\273\347\220\206(SOA governance)\357\274\237.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/\347\224\265\345\225\206\344\272\222\350\201\224\347\275\221\345\246\202\344\275\225\345\201\232\345\276\256\346\234\215\345\212\241\346\262\273\347\220\206(SOA governance)\357\274\237.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\347\275\221\345\205\263V.S\345\217\215\345\220\221\344\273\243\347\220\206.md" "b/\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/\347\275\221\345\205\263V.S\345\217\215\345\220\221\344\273\243\347\220\206.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\347\275\221\345\205\263V.S\345\217\215\345\220\221\344\273\243\347\220\206.md"
rename to "\346\236\266\346\236\204/\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204/\347\275\221\345\205\263V.S\345\217\215\345\220\221\344\273\243\347\220\206.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/1.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/1.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/1.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/1.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/13_\345\274\200\345\217\221\345\223\201\347\211\214\345\220\215\347\247\260\350\216\267\345\217\226\346\216\245\345\217\243\347\232\204\345\237\272\344\272\216\346\234\254\345\234\260\347\274\223\345\255\230\347\232\204fallback\351\231\215\347\272\247\346\234\272\345\210\266.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/13_\345\274\200\345\217\221\345\223\201\347\211\214\345\220\215\347\247\260\350\216\267\345\217\226\346\216\245\345\217\243\347\232\204\345\237\272\344\272\216\346\234\254\345\234\260\347\274\223\345\255\230\347\232\204fallback\351\231\215\347\272\247\346\234\272\345\210\266.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/13_\345\274\200\345\217\221\345\223\201\347\211\214\345\220\215\347\247\260\350\216\267\345\217\226\346\216\245\345\217\243\347\232\204\345\237\272\344\272\216\346\234\254\345\234\260\347\274\223\345\255\230\347\232\204fallback\351\231\215\347\272\247\346\234\272\345\210\266.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/13_\345\274\200\345\217\221\345\223\201\347\211\214\345\220\215\347\247\260\350\216\267\345\217\226\346\216\245\345\217\243\347\232\204\345\237\272\344\272\216\346\234\254\345\234\260\347\274\223\345\255\230\347\232\204fallback\351\231\215\347\272\247\346\234\272\345\210\266.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\347\252\201\347\240\264Java\351\235\242\350\257\225-hystrix\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\345\217\257\347\224\250\346\200\247\345\217\212\350\256\276\350\256\241\345\216\237\345\210\231.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\347\252\201\347\240\264Java\351\235\242\350\257\225-hystrix\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\345\217\257\347\224\250\346\200\247\345\217\212\350\256\276\350\256\241\345\216\237\345\210\231.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\347\252\201\347\240\264Java\351\235\242\350\257\225-hystrix\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\345\217\257\347\224\250\346\200\247\345\217\212\350\256\276\350\256\241\345\216\237\345\210\231.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\347\252\201\347\240\264Java\351\235\242\350\257\225-hystrix\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\345\217\257\347\224\250\346\200\247\345\217\212\350\256\276\350\256\241\345\216\237\345\210\231.md"
diff --git "a/\346\236\266\346\236\204/\350\275\257\344\273\266\346\236\266\346\236\204\345\210\206\345\261\202\346\226\271\346\263\225\350\256\272.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\350\275\257\344\273\266\346\236\266\346\236\204\345\210\206\345\261\202\346\226\271\346\263\225\350\256\272.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\350\275\257\344\273\266\346\236\266\346\236\204\345\210\206\345\261\202\346\226\271\346\263\225\350\256\272.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\350\275\257\344\273\266\346\236\266\346\236\204\345\210\206\345\261\202\346\226\271\346\263\225\350\256\272.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221/\351\253\230\345\217\257\346\211\251\345\261\225\346\200\247\347\263\273\347\273\237\347\232\204\350\256\276\350\256\241.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\346\211\251\345\261\225\346\200\247\347\263\273\347\273\237\347\232\204\350\256\276\350\256\241.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221/\351\253\230\345\217\257\346\211\251\345\261\225\346\200\247\347\263\273\347\273\237\347\232\204\350\256\276\350\256\241.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\346\211\251\345\261\225\346\200\247\347\263\273\347\273\237\347\232\204\350\256\276\350\256\241.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(10)-Hystrix\347\232\204\347\272\277\347\250\213\346\261\240+\346\234\215\345\212\241+\346\216\245\345\217\243\345\210\222\345\210\206\344\273\245\345\217\212\350\265\204\346\272\220\346\261\240\347\232\204\345\256\271\351\207\217\345\244\247\345\260\217\346\216\247\345\210\266.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(10)-Hystrix\347\232\204\347\272\277\347\250\213\346\261\240+\346\234\215\345\212\241+\346\216\245\345\217\243\345\210\222\345\210\206\344\273\245\345\217\212\350\265\204\346\272\220\346\261\240\347\232\204\345\256\271\351\207\217\345\244\247\345\260\217\346\216\247\345\210\266.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(10)-Hystrix\347\232\204\347\272\277\347\250\213\346\261\240+\346\234\215\345\212\241+\346\216\245\345\217\243\345\210\222\345\210\206\344\273\245\345\217\212\350\265\204\346\272\220\346\261\240\347\232\204\345\256\271\351\207\217\345\244\247\345\260\217\346\216\247\345\210\266.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(10)-Hystrix\347\232\204\347\272\277\347\250\213\346\261\240+\346\234\215\345\212\241+\346\216\245\345\217\243\345\210\222\345\210\206\344\273\245\345\217\212\350\265\204\346\272\220\346\261\240\347\232\204\345\256\271\351\207\217\345\244\247\345\260\217\346\216\247\345\210\266.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(12) - \345\237\272\344\272\216request cache\350\257\267\346\261\202\347\274\223\345\255\230\346\212\200\346\234\257\344\274\230\345\214\226\346\211\271\351\207\217\345\225\206\345\223\201\346\225\260\346\215\256\346\237\245\350\257\242\346\216\245\345\217\243.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(12) - \345\237\272\344\272\216request cache\350\257\267\346\261\202\347\274\223\345\255\230\346\212\200\346\234\257\344\274\230\345\214\226\346\211\271\351\207\217\345\225\206\345\223\201\346\225\260\346\215\256\346\237\245\350\257\242\346\216\245\345\217\243.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(12) - \345\237\272\344\272\216request cache\350\257\267\346\261\202\347\274\223\345\255\230\346\212\200\346\234\257\344\274\230\345\214\226\346\211\271\351\207\217\345\225\206\345\223\201\346\225\260\346\215\256\346\237\245\350\257\242\346\216\245\345\217\243.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(12) - \345\237\272\344\272\216request cache\350\257\267\346\261\202\347\274\223\345\255\230\346\212\200\346\234\257\344\274\230\345\214\226\346\211\271\351\207\217\345\225\206\345\223\201\346\225\260\346\215\256\346\237\245\350\257\242\346\216\245\345\217\243.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(16) - \345\237\272\344\272\216timeout\346\234\272\345\210\266\346\235\245\344\270\272\345\225\206\345\223\201\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\350\260\203\347\224\250\350\266\205\346\227\266\346\217\220\344\276\233\345\256\211\345\205\250\344\277\235\346\212\244.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(16) - \345\237\272\344\272\216timeout\346\234\272\345\210\266\346\235\245\344\270\272\345\225\206\345\223\201\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\350\260\203\347\224\250\350\266\205\346\227\266\346\217\220\344\276\233\345\256\211\345\205\250\344\277\235\346\212\244.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(16) - \345\237\272\344\272\216timeout\346\234\272\345\210\266\346\235\245\344\270\272\345\225\206\345\223\201\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\350\260\203\347\224\250\350\266\205\346\227\266\346\217\220\344\276\233\345\256\211\345\205\250\344\277\235\346\212\244.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(16) - \345\237\272\344\272\216timeout\346\234\272\345\210\266\346\235\245\344\270\272\345\225\206\345\223\201\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\350\260\203\347\224\250\350\266\205\346\227\266\346\217\220\344\276\233\345\256\211\345\205\250\344\277\235\346\212\244.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(17) - \345\237\272\344\272\216Hystrix\347\232\204\351\253\230\345\217\257\347\224\250\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241\347\232\204\346\200\273\347\273\223.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(17) - \345\237\272\344\272\216Hystrix\347\232\204\351\253\230\345\217\257\347\224\250\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241\347\232\204\346\200\273\347\273\223.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(17) - \345\237\272\344\272\216Hystrix\347\232\204\351\253\230\345\217\257\347\224\250\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241\347\232\204\346\200\273\347\273\223.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(17) - \345\237\272\344\272\216Hystrix\347\232\204\351\253\230\345\217\257\347\224\250\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\346\236\266\346\236\204\350\256\276\350\256\241\347\232\204\346\200\273\347\273\223.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(9) - \345\237\272\344\272\216Hystrix\347\232\204\344\277\241\345\217\267\351\207\217\346\212\200\346\234\257\345\257\271\345\234\260\347\220\206\344\275\215\347\275\256\350\216\267\345\217\226\351\200\273\350\276\221\350\277\233\350\241\214\350\265\204\346\272\220\351\232\224\347\246\273\344\270\216\351\231\220\346\265\201.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(9) - \345\237\272\344\272\216Hystrix\347\232\204\344\277\241\345\217\267\351\207\217\346\212\200\346\234\257\345\257\271\345\234\260\347\220\206\344\275\215\347\275\256\350\216\267\345\217\226\351\200\273\350\276\221\350\277\233\350\241\214\350\265\204\346\272\220\351\232\224\347\246\273\344\270\216\351\231\220\346\265\201.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(9) - \345\237\272\344\272\216Hystrix\347\232\204\344\277\241\345\217\267\351\207\217\346\212\200\346\234\257\345\257\271\345\234\260\347\220\206\344\275\215\347\275\256\350\216\267\345\217\226\351\200\273\350\276\221\350\277\233\350\241\214\350\265\204\346\272\220\351\232\224\347\246\273\344\270\216\351\231\220\346\265\201.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241(9) - \345\237\272\344\272\216Hystrix\347\232\204\344\277\241\345\217\267\351\207\217\346\212\200\346\234\257\345\257\271\345\234\260\347\220\206\344\275\215\347\275\256\350\216\267\345\217\226\351\200\273\350\276\221\350\277\233\350\241\214\350\265\204\346\272\220\351\232\224\347\246\273\344\270\216\351\231\220\346\265\201.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(2) - Hystrix.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(2) - hystrix\350\246\201\350\247\243\345\206\263\347\232\204\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\345\217\257\347\224\250\346\200\247\351\227\256\351\242\230\344\273\245\345\217\212\345\205\266\350\256\276\350\256\241\345\216\237\345\210\231.md"
similarity index 64%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(2) - Hystrix.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(2) - hystrix\350\246\201\350\247\243\345\206\263\347\232\204\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\345\217\257\347\224\250\346\200\247\351\227\256\351\242\230\344\273\245\345\217\212\345\205\266\350\256\276\350\256\241\345\216\237\345\210\231.md"
index 9b76e32fe0..6222b4b47e 100644
--- "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(2) - Hystrix.md"	
+++ "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(2) - hystrix\350\246\201\350\247\243\345\206\263\347\232\204\345\210\206\345\270\203\345\274\217\347\263\273\347\273\237\345\217\257\347\224\250\346\200\247\351\227\256\351\242\230\344\273\245\345\217\212\345\205\266\350\256\276\350\256\241\345\216\237\345\210\231.md"	
@@ -1,4 +1,17 @@
-# Hystrix是什么
+# 1 导读
+
+高可用性这个topic，然后咱们会用几讲的时间来讲解一下如何用hystrix，来构建高可用的服务的架构
+
+咱们会用一个真实的项目背景，作为业务场景，来带出来在这个特定的业务场景下，可能会产生哪些各种各样的可用性的一些问题
+
+针对这些问题，我们用hystrix的解决方案和原理是什么
+
+带着大家，纯手工将所有的服务的高可用架构的代码，全部纯手工自己敲出来
+
+形成高可用服务架构的项目实战的一个教程
+
+# 2 Hystrix是什么
+
 在分布式系统中，每个服务都可能会调用很多其他服务，被调用的那些服务就是依赖服务，有的时候某些依赖服务出现故障也是很常见的。
 
 Hystrix可以让我们在分布式系统中对服务间的调用进行控制，加入一些调用延迟或者依赖故障的容错机制。
@@ -6,14 +19,26 @@ Hystrix可以让我们在分布式系统中对服务间的调用进行控制，
 Hystrix通过将依赖服务进行**资源隔离**，进而避免某个依赖服务出现故障的时候，在整个系统所有的依赖服务调用中蔓延，同时Hystrix还提供故障时的fallback降级机制
 
 总而言之，Hystrix通过这些方法帮助我们提升分布式系统的可用性和稳定性
-# Hystrix的历史
-hystrix，一种高可用保障的框架，Netflix API团队从2011年开始做一些提升系统可用性和稳定性的工作，Hystrix就是从那时候开始发展出来的。
+
+- 什么是分布式系统以及其中的故障和hystrix
+![](https://ask.qcloudimg.com/http-save/1752328/d1razflqky.png)
+
+# 3 Hystrix的历史
+
+hystrix，一种高可用保障的框架，类似于spring（ioc，mvc），mybatis，activiti，lucene，框架，预先封装好的为了解决某个特定领域的特定问题的一套代码库
+
+框架，用了框架之后，来解决这个领域的特定的问题，就可以大大减少我们的工作量，提升我们的工作质量和工作效率，框架,hystrix，就是高可用性保障的一个框架
+
+Netflix（可以认为是国外的优酷或者爱奇艺之类的视频网站），API团队从2011年开始做一些提升系统可用性和稳定性的工作，Hystrix就是从那时候开始发展出来的。
 
 在2012年的时候，Hystrix就变得比较成熟和稳定了，Netflix中，除了API团队以外，很多其他的团队都开始使用Hystrix。
 
+时至今日，Netflix中每天都有数十亿次的服务间调用，通过Hystrix框架在进行，而Hystrix也帮助Netflix网站提升了整体的可用性和稳定性
+
 2018 年 11 月，Hystrix 在其 [Github 主页](https://github.com/Netflix/Hystrix/blob/master/README.md#hystrix-status)宣布，不再开放新功能，推荐开发者使用其他仍然活跃的开源项目。维护模式的转变绝不意味着 Hystrix 不再有价值。相反，Hystrix 激发了很多伟大的想法和项目，其思想仍值得我们深入学习!
 
-# Hystrix的设计原则
+# 4 Hystrix的设计原则
+
 - 对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护
 - 在复杂的分布式系统中，阻止某一个依赖服务的故障在整个系统中蔓延，服务A->服务B->服务C，服务C故障了，服务B也故障了，服务A故障了，整套分布式系统全部故障，整体宕机
 - 提供fail-fast（快速失败）和快速恢复的支持
@@ -21,18 +46,27 @@ hystrix，一种高可用保障的框架，Netflix API团队从2011年开始做
 - 支持近实时的监控、报警以及运维操作5 Hystrix要解决的问题在复杂的分布式系统架构中，每个服务都有很多的依赖服务，而每个依赖服务都可能会故障
 如果服务没有和自己的依赖服务进行隔离，那么可能某一个依赖服务的故障就会拖垮当前这个服务
 
-# 举例
+举例来说
+
 某个服务有30个依赖服务，每个依赖服务的可用性非常高，已经达到了99.99%的高可用性
 
 那么该服务的可用性就是99.99%的30次方，也就是99.7%的可用性
 
-99.7%的可用性就意味着3%的请求可能会失败，因为3%的时间内系统可能出现了故障不可用了。
+99.7%的可用性就意味着3%的请求可能会失败，因为3%的时间内系统可能出现了故障不可用了
+
+对于1亿次访问来说，3%的请求失败，也就意味着300万次请求会失败，也意味着每个月有2个小时的时间系统是不可用的
+
+在真实生产环境中，可能更加糟糕
+
+上面也就是说，即使你每个依赖服务都是99.99%高可用性，但是一旦你有几十个依赖服务，还是会导致你每个月都有几个小时是不可用的
+
+画图分析说，当某一个依赖服务出现了调用延迟或者调用失败时，为什么会拖垮当前这个服务？以及在分布式系统中，故障是如何快速蔓延的？
 
-对于1亿次访问来说，3%的请求失败，也就意味着300万次请求会失败，也意味着每个月有2个小时的时间系统是不可用的。在真实生产环境中，可能更加糟糕。
+- 依赖服务的故障导致服务被拖垮以及故障的蔓延示意图
+![](https://ask.qcloudimg.com/http-save/1752328/twu9s9m6xx.png)
+
+# 6 Hystrix的更加细节的设计原则
 
-上面也就是说，即使你每个依赖服务都是99.99%高可用性，但是一旦你有几十个依赖服务，还是会导致你每个月都有几个小时是不可用的。
-![](https://img-blog.csdnimg.cn/83a446c51c3e4b27beb3e42560458ca0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-#  Hystrix的更加细节的设计原则
 - 阻止任何一个依赖服务耗尽所有的资源，比如tomcat中的所有线程资源
 - 避免请求排队和积压，采用限流和fail fast来控制故障
 - 提供fallback降级机制来应对故障
@@ -43,7 +77,8 @@ hystrix，一种高可用保障的框架，Netflix API团队从2011年开始做
 
 调用这个依赖服务的时候，client调用包有bug，阻塞，等等，依赖服务的各种各样的调用的故障，都可以处理
 
-# Hystrix如何实现它的目标
+# 7 Hystrix如何实现它的目标
+
 - 通过HystrixCommand或者HystrixObservableCommand来封装对外部依赖的访问请求，这个访问请求一般会运行在独立的线程中，资源隔离
 - 对于超出我们设定阈值的服务调用，直接进行超时，不允许其耗费过长时间阻塞住。这个超时时间默认是99.5%的访问时间，但是一般我们可以自己设置一下
 - 为每一个依赖服务维护一个独立的线程池，或者是semaphore，当线程池已满时，直接拒绝对这个服务的调用
@@ -54,5 +89,15 @@ hystrix，一种高可用保障的框架，Netflix API团队从2011年开始做
 
 画图分析，对依赖进行资源隔离后，如何避免依赖服务调用延迟或失败导致当前服务的故障
 
-- 资源隔离如何保护依赖服务的故障不要拖垮整个系统
-![](https://img-blog.csdnimg.cn/b7b99687fdbb4352974f7006f5f5c989.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
+- 资源隔离如何保护依赖服务的故障不要拖垮整个系统![](https://ask.qcloudimg.com/http-save/1752328/0tx22cj2bs.png)
+
+# 参考
+
+- 《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(3) -\347\224\265\345\225\206\345\225\206\345\223\201\350\257\246\346\203\205\351\241\265\347\274\223\345\255\230\350\203\214\346\231\257\345\217\212\346\241\206\346\236\266\350\257\264\346\230\216.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(3) -\347\224\265\345\225\206\345\225\206\345\223\201\350\257\246\346\203\205\351\241\265\347\274\223\345\255\230\350\203\214\346\231\257\345\217\212\346\241\206\346\236\266\350\257\264\346\230\216.md"
new file mode 100644
index 0000000000..5a927b4afb
--- /dev/null
+++ "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\346\236\266\346\236\204\350\256\276\350\256\241(3) -\347\224\265\345\225\206\345\225\206\345\223\201\350\257\246\346\203\205\351\241\265\347\274\223\345\255\230\350\203\214\346\231\257\345\217\212\346\241\206\346\236\266\350\257\264\346\230\216.md"	
@@ -0,0 +1,130 @@
+# [Github](https://github.com/Wasabi1234)
+
+# 0 导读
+
+我们这个教程，基于hystrix，如何来构建高可用的分布式系统的架构，项目实战
+
+模拟真实业务的这么一个小型的项目，来全程贯穿，用这个项目中的业务场景去一个一个的讲解hystrix高可用的每个技术
+
+纯讲hystrix，脱离实际的业务背景，听起来有点枯燥，大家学完了hystrix以后，可能没法完全感受到技术是如何融入我们的项目中的
+
+大背景：电商网站，首页，商品详情页，搜索结果页，广告页，促销活动，购物车，订单系统，库存系统，物流系统
+
+小背景：商品详情页，如何用最快的结果将商品数据填充到一个页面中，然后将页面显示出来
+
+分布式系统：商品详情页，缓存服务，+底层源数据服务，商品信息服务，店铺信息服务，广告信息服务，推荐信息服务，综合起来组成一个分布式的系统
+
+# 1 电商网站的商品详情页系统架构
+
+## 1.1 小型电商网站的商品详情页系统架构(非重点)
+
+![](https://ask.qcloudimg.com/http-save/1752328/g8fb1n804c.png)
+
+## 1.2 大型电商网站的商品详情页系统架构
+
+大型电商网站商品详情页的系统设计中，当商品数据发生变更时，会将变更消息压入消息队列中。
+
+缓存服务从消息队列中消费这条消息时，感知到有数据发生变更，便通过调用数据服务接口，获取变更后的数据，然后将整合好的数据推送至 redis 中。
+
+Nginx 本地缓存的数据是有一定的时间期限的，比如说 10 分钟，当数据过期之后，它就会从 redis 获取到最新的缓存数据，并且缓存到自己本地。
+
+用户浏览网页时，动态将 Nginx 本地数据渲染到本地 html 模板并返回给用户。
+
+虽然没有直接返回 html 页面那么快，但是因为数据在本地缓存，所以也很快，其实耗费的也就是动态渲染一个 html 页面的性能。如果 html 模板发生了变更，不需要将所有的页面重新静态化，也不需要发送请求，没有网络请求的开销，直接将数据渲染进最新的 html 页面模板后响应即可。
+
+在这种架构下，我们需要保证系统的高可用性。
+
+如果系统访问量很高，Nginx 本地缓存过期失效了，redis 中的缓存也被 LRU 算法给清理掉了，那么会有较高的访问量，从缓存服务调用商品服务。但如果此时商品服务的接口发生故障，调用出现了延时，缓存服务全部的线程都被这个调用商品服务接口给耗尽了，每个线程去调用商品服务接口的时候，都会卡住很长时间，后面大量的请求过来都会卡在那儿，此时缓存服务没有足够的线程去调用其它一些服务的接口，从而导致整个大量的商品详情页无法正常显示。
+
+这其实就是一个商品接口服务故障导致缓存服务资源耗尽的现象。
+
+![](https://ask.qcloudimg.com/http-save/1752328/37fnr6h5uu.png)
+
+## 1.3 页面模板
+
+举个例子
+
+将数据动态填充/渲染到一个html模板中，是什么意思呢？
+
+```
+<html>
+	<title>#{name}的页面</title>
+	<body>
+		商品的价格是：#{price}
+		商品的介绍：#{description}
+	</body>
+</html>
+```
+
+上面这个就可以认为是一个页面模板，里面的很多内容是不确定的，#{name}，#{price}，#{description}，这都是一些模板脚本，不确定里面的值是什么？
+
+将数据填充/渲染到html模板中，是什么意思呢？
+
+```
+{
+	"name": "iphone7 plus（玫瑰金+32G）",
+	"price": 5599.50
+	"description": "这个手机特别好用。。。。。。"
+}
+
+<html>
+	<title>iphone7 plus（玫瑰金+32G）的页面</title>
+	<body>
+		商品的价格是：5599.50
+		商品的介绍：这个手机特别好用。。。。。。
+	</body>
+</html>
+```
+
+上面这个就是一份填充好数据的一个html页面
+
+# 2 缓存服务
+
+缓存服务，订阅一个MQ的消息变更，如果有消息变更的话，那么就会发送一个网络请求，调用一个底层的对应的源数据服务的接口，去获取变更后的数据
+
+将获取到的变更后的数据填充到分布式的redis缓存中去
+
+高可用这一块儿，最可能出现说可用性不高的情况，是什么呢？
+
+就是说，在接收到消息之后，可能在调用各种底层依赖服务的接口时，会遇到各种不稳定的情况
+
+比如底层服务的接口调用超时，200ms，2s都没有返回; 底层服务的接口调用失败，比如说卡了500ms之后，返回一个报错
+
+在分布式系统中，对于这种大量的底层依赖服务的调用，就可能会出现各种可用性的问题，一旦没有处理好的话
+
+可能就会导致缓存服务自己本身会挂掉，或者故障掉，就会导致什么呢？
+
+不可以对外提供服务，严重情况下，甚至会导致说整个商品详情页显示不出来
+
+缓存服务接收到变更消息后，去调用各个底层依赖服务时的高可用架构的实现
+
+我们刚才讲解的整套大型电商网站的商品详情页的缓存架构，完整的那个流程，《亿级流量电商详情页系统的大型高并发与高可用缓存架构实战》
+
+# 3 框架结构
+
+围绕着缓存服务去拉取各种底层的源数据服务的数据，调用其接口时，可能出现的系统不可用的问题
+
+从简
+
+spring boot，微服务的非常快速，非常好用的技术框架，脱胎于spring，具体的东西就不讲解，直接带着大家上手搭建一个spring boot的框架
+
+2个服务，缓存服务，商品服务，缓存服务依赖于商品服务
+
+模拟各种商品服务可能接口调用时出现的各种问题，导致系统不可用的场景，然后用hystrix完整的各种技术点全部贯穿在里面
+
+解决了一大堆设计业务背景下的系统不可用问题，hystrix整个技术体系，知识体系，也就讲解完了
+
+消息队列，redis，咱们都不搞了
+
+spring boot + http client + hystrix
+
+# 参考
+
+- 《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\347\232\204\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241-\350\265\204\346\272\220\351\232\224\347\246\273\343\200\201\351\231\220\346\265\201\343\200\201\347\206\224\346\226\255\343\200\201\351\231\215\347\272\247\343\200\201\347\233\221\346\216\247.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\347\232\204\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241-\350\265\204\346\272\220\351\232\224\347\246\273\343\200\201\351\231\220\346\265\201\343\200\201\347\206\224\346\226\255\343\200\201\351\231\215\347\272\247\343\200\201\347\233\221\346\216\247.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\217\257\347\224\250/\351\253\230\345\217\257\347\224\250\347\232\204\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241-\350\265\204\346\272\220\351\232\224\347\246\273\343\200\201\351\231\220\346\265\201\343\200\201\347\206\224\346\226\255\343\200\201\351\231\215\347\272\247\343\200\201\347\233\221\346\216\247.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\217\257\347\224\250\347\232\204\345\276\256\346\234\215\345\212\241\346\236\266\346\236\204\350\256\276\350\256\241-\350\265\204\346\272\220\351\232\224\347\246\273\343\200\201\351\231\220\346\265\201\343\200\201\347\206\224\346\226\255\343\200\201\351\231\215\347\272\247\343\200\201\347\233\221\346\216\247.md"
diff --git "a/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241\344\271\213\351\201\223\357\274\210\344\270\200\357\274\211- \346\226\271\346\263\225\350\256\272.md" "b/\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241\344\271\213\351\201\223\357\274\210\344\270\200\357\274\211- \346\226\271\346\263\225\350\256\272.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241\344\271\213\351\201\223\357\274\210\344\270\200\357\274\211- \346\226\271\346\263\225\350\256\272.md"
rename to "\346\236\266\346\236\204/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241/\351\253\230\345\271\266\345\217\221\347\263\273\347\273\237\350\256\276\350\256\241\344\271\213\351\201\223\357\274\210\344\270\200\357\274\211- \346\226\271\346\263\225\350\256\272.md"
diff --git "a/\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/Java\347\224\265\345\225\206\346\224\257\344\273\230\347\263\273\347\273\237\345\256\236\346\210\230\357\274\210\344\270\200\357\274\211- \347\256\200\344\273\213.md" "b/\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/Java\347\224\265\345\225\206\346\224\257\344\273\230\347\263\273\347\273\237\345\256\236\346\210\230\357\274\210\344\270\200\357\274\211- \347\256\200\344\273\213.md"
new file mode 100644
index 0000000000..5b7b31ee43
--- /dev/null
+++ "b/\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/Java\347\224\265\345\225\206\346\224\257\344\273\230\347\263\273\347\273\237\345\256\236\346\210\230\357\274\210\344\270\200\357\274\211- \347\256\200\344\273\213.md"	
@@ -0,0 +1,30 @@
+现如今，支付成为热点
+![](https://img-blog.csdnimg.cn/20200115013714632.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+对于电商业务，这都是不可或缺的
+![](https://img-blog.csdnimg.cn/20200115013839528.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+
+核心功能剖析 下单-》支付
+![](https://img-blog.csdnimg.cn/20200115014127135.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+nginx 将用户请求反向代理到我们编写的电商系统 = 下单
+之后，点击支付跳转到支付系统，最后对接
+通过跳转将业务和支付解耦，不管电商系统，还是新增的活动系统都可调支付系统
+
+# 支付结果通知
+![](https://img-blog.csdnimg.cn/20200115014415367.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+# 本教程侧重
+- 电商经典案例
+- 真实支付
+- 最新互联网技术栈
+- 项目实战手把手，日臻完善
+
+# 我们的追求
+- 熟练对接支付宝、微信
+- 独立完成小电商
+- 具备设计架构的初步能力
+
+# 适宜人口
+- 四有青年
+- 对支付功能情有独钟
+
+# 你需要掌握
+![](https://img-blog.csdnimg.cn/20200115014739830.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MyBatis or JPA.md" "b/\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/Java\347\224\265\345\225\206\346\224\257\344\273\230\347\263\273\347\273\237\346\211\213\346\212\212\346\211\213\345\256\236\347\216\260\357\274\210\344\270\211\357\274\211 -  SpringBoot \345\272\224\347\224\250\347\250\213\345\272\217\346\220\255\345\273\272.md"
similarity index 51%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MyBatis or JPA.md"
rename to "\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/Java\347\224\265\345\225\206\346\224\257\344\273\230\347\263\273\347\273\237\346\211\213\346\212\212\346\211\213\345\256\236\347\216\260\357\274\210\344\270\211\357\274\211 -  SpringBoot \345\272\224\347\224\250\347\250\213\345\272\217\346\220\255\345\273\272.md"
index f6547b86bd..7822417396 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MyBatis or JPA.md"	
+++ "b/\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/Java\347\224\265\345\225\206\346\224\257\344\273\230\347\263\273\347\273\237\346\211\213\346\212\212\346\211\213\345\256\236\347\216\260\357\274\210\344\270\211\357\274\211 -  SpringBoot \345\272\224\347\224\250\347\250\213\345\272\217\346\220\255\345\273\272.md"	
@@ -1,4 +1,29 @@
-# 
+# 1 软件版本要对齐
+- java
+![](https://img-blog.csdnimg.cn/20200124215221610.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- Intellij IDEA Ultimate
+![](https://img-blog.csdnimg.cn/20200124220604492.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- Maven
+![](https://img-blog.csdnimg.cn/2020012421553824.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- SpringBoot
+2.1.7（强制必须）
+
+#  2 新建应用
+![](https://img-blog.csdnimg.cn/20200124220650921.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- 确保你的科学上网方式正常
+![](https://img-blog.csdnimg.cn/20200124220423417.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- 只修改标志范围内的![](https://img-blog.csdnimg.cn/20200124220800448.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- 注意可能 sb 的版本号找不到 2.1.7，先默认，之后再改，目前只勾选 Spring Web
+![](https://img-blog.csdnimg.cn/20200124221046928.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20200124221222210.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- 配置为本地 maven 路径
+![](https://img-blog.csdnimg.cn/20200124221704605.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- 修改为 2.1.7 版本
+![](https://img-blog.csdnimg.cn/20200124222222883.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+
+- IDEA(开发是过程)
+- Java命令( 部署是最终结果&调试)
+# 3 MyBatis V.S JPA
 - JPA是趋势
 
 - Mybatis是国内现状
@@ -11,7 +36,8 @@
 ![](https://img-blog.csdnimg.cn/20200125112706794.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 - 接着，自然是配置数据库了
 ![](https://img-blog.csdnimg.cn/20200125113333551.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
-
+- 新建 pojo 包
+![](https://img-blog.csdnimg.cn/20200125132420461.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 - 新建 dao 层
 
 - 单元测试不可忘
@@ -32,4 +58,10 @@
 
 ## xml 语句
 - 注意指定mapper文件路径
-![](https://img-blog.csdnimg.cn/20200125181626705.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
\ No newline at end of file
+![](https://img-blog.csdnimg.cn/20200125181626705.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+
+
+# 参考
+- 高性能 MySQL 第三版
+- 实战支付+电商双系统
+
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/\346\225\260\346\215\256\345\272\223\350\241\250\350\256\276\350\256\241\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/Java\347\224\265\345\225\206\346\224\257\344\273\230\347\263\273\347\273\237\346\211\213\346\212\212\346\211\213\345\256\236\347\216\260\357\274\210\344\272\214\357\274\211 - \346\225\260\346\215\256\345\272\223\350\241\250\350\256\276\350\256\241\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
similarity index 92%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/\346\225\260\346\215\256\345\272\223\350\241\250\350\256\276\350\256\241\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
rename to "\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/Java\347\224\265\345\225\206\346\224\257\344\273\230\347\263\273\347\273\237\346\211\213\346\212\212\346\211\213\345\256\236\347\216\260\357\274\210\344\272\214\357\274\211 - \346\225\260\346\215\256\345\272\223\350\241\250\350\256\276\350\256\241\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
index 791f226d25..e687dd1e50 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/\346\225\260\346\215\256\345\272\223\350\241\250\350\256\276\350\256\241\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"
+++ "b/\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/Java\347\224\265\345\225\206\346\224\257\344\273\230\347\263\273\347\273\237\346\211\213\346\212\212\346\211\213\345\256\236\347\216\260\357\274\210\344\272\214\357\274\211 - \346\225\260\346\215\256\345\272\223\350\241\250\350\256\276\350\256\241\347\232\204\346\234\200\344\275\263\345\256\236\350\267\265.md"	
@@ -44,4 +44,15 @@ id=0为根节点，分类其实是树状结构
 - create_ time: 创建时间
 - update_ _time: 更新时间
 
-因此，每个表里基本都有该两个字段哦！
\ No newline at end of file
+因此，每个表里基本都有该两个字段哦！
+
+# 4 总结
+在数据库设计时，注意如下几点
+- 表关系
+- 单索引及组合索引
+- 表结构
+- 时间戳
+- 唯一索引
+# 参考
+- 高性能 MySQL 第三版
+- Java支付电商平台实战
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\345\271\202\347\255\211\346\200\247.md" "b/\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\345\271\202\347\255\211\346\200\247.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\345\271\202\347\255\211\346\200\247.md"
rename to "\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\347\232\204\345\271\202\347\255\211\346\200\247.md"
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\272\223\345\210\206\350\241\250\345\220\216\345\205\250\345\261\200id\347\224\237\346\210\220\346\226\271\346\241\210.md" "b/\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/\345\210\206\345\272\223\345\210\206\350\241\250\345\220\216\345\205\250\345\261\200id\347\224\237\346\210\220\346\226\271\346\241\210.md"
similarity index 100%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\272\223\345\210\206\350\241\250\345\220\216\345\205\250\345\261\200id\347\224\237\346\210\220\346\226\271\346\241\210.md"
rename to "\347\224\265\345\225\206\345\270\270\350\247\201\344\270\232\345\212\241\351\227\256\351\242\230\350\247\243\345\206\263\346\226\271\346\241\210/\345\210\206\345\272\223\345\210\206\350\241\250\345\220\216\345\205\250\345\261\200id\347\224\237\346\210\220\346\226\271\346\241\210.md"
diff --git "a/\347\224\265\345\225\206\350\257\246\346\203\205\351\241\265\347\263\273\347\273\237\345\256\236\346\210\230/\347\224\265\345\225\206\350\257\246\346\203\205\351\241\265\347\263\273\347\273\237\345\256\236\346\210\230(2) -\345\260\217\345\236\213\347\224\265\345\225\206\347\275\221\347\253\231\345\225\206\345\223\201\350\257\246\346\203\205\351\241\265\347\232\204\351\241\265\351\235\242\351\235\231\346\200\201\345\214\226\346\236\266\346\236\204\345\217\212\347\274\272\351\231\267.md" "b/\347\224\265\345\225\206\350\257\246\346\203\205\351\241\265\347\263\273\347\273\237\345\256\236\346\210\230/\347\224\265\345\225\206\350\257\246\346\203\205\351\241\265\347\263\273\347\273\237\345\256\236\346\210\230(2) -\345\260\217\345\236\213\347\224\265\345\225\206\347\275\221\347\253\231\345\225\206\345\223\201\350\257\246\346\203\205\351\241\265\347\232\204\351\241\265\351\235\242\351\235\231\346\200\201\345\214\226\346\236\266\346\236\204\345\217\212\347\274\272\351\231\267.md"
new file mode 100644
index 0000000000..13f6f8897f
--- /dev/null
+++ "b/\347\224\265\345\225\206\350\257\246\346\203\205\351\241\265\347\263\273\347\273\237\345\256\236\346\210\230/\347\224\265\345\225\206\350\257\246\346\203\205\351\241\265\347\263\273\347\273\237\345\256\236\346\210\230(2) -\345\260\217\345\236\213\347\224\265\345\225\206\347\275\221\347\253\231\345\225\206\345\223\201\350\257\246\346\203\205\351\241\265\347\232\204\351\241\265\351\235\242\351\235\231\346\200\201\345\214\226\346\236\266\346\236\204\345\217\212\347\274\272\351\231\267.md"	
@@ -0,0 +1,83 @@
+
+> 商品详情页的系统架构 =>  缓存架构 => 高并发 => 高可用
+
+电商网站里，大概可以说分成两种
+- 小型电商
+简单的一种架构方案，页面静态化的方案
+- 大型电商
+复杂的一套架构，大电商，国内排名前几的电商，用得应该咱们这里讲解的这套大型的详情页架构
+
+页面静态化，全量的页面静态化
+```
+<html>
+	<title></title>
+	<body>
+		商品名称：#{productName}
+		商品价格：#{productPrice}
+		商品描述：#{productDesc}
+	</body>
+</html>
+```
+=>
+```
+<html>
+	<title>
+		<style css>
+		<javascript>
+	</title>
+	<body>
+		商品名称：#{productName}
+		商品价格：#{productPrice}
+		商品描述：#{productDesc}
+	</body>
+</html>
+```
+product1.html
+
+
+假设是放在一个数据库里的
+
+product表
+```
+product_name		product_price		product_desc
+
+iphon7 plus			5599.50				这是最好的手机
+```
+=>
+```
+iphon7 plus（玫瑰）	5299.50				这是最好的手机，大降价了
+```
+......1万行数据，1万个页面
+
+模板的渲染
+
+```
+<html>
+	<title>
+		<style css>
+		<javascript>
+	</title>
+	<body>
+		商品名称：iphon7 plus（玫瑰）
+		商品价格：5299.50	
+		商品描述：这是最好的手机，大降价了
+	</body>
+</html>
+```
+iphone7_plus.html
+
+
+如果模板改变了，那么这个模板对应的所有数据，1万个数据，全部重新渲染一遍，填充到模板中，生成最终的静态化html页面
+
+
+对于小网站，页面很少，很实用，非常简单，模板引擎，velocity，freemarker，页面数据管理的cms系统，内容管理系统
+
+点击一个按钮，做成系统自动化，重新全量渲染
+
+html --> 几百个页面，推送到nginx服务器上面，直接走html
+
+
+页面太多，上亿，一个模板修改了，重新渲染一亿的商品，靠谱，几天
+
+# 参考
+[（第二版）亿级流量电商详情页系统实战-缓存架构+高可用服务架构+微服务架构](https://www.roncoo.com/view/35)
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\345\276\256\346\234\215\345\212\241\351\241\271\347\233\256\345\246\202\344\275\225\346\240\241\351\252\214\345\217\202\346\225\260.md" "b/\347\274\226\347\250\213\350\247\204\350\214\203/EffectiveJava/\345\276\256\346\234\215\345\212\241\351\241\271\347\233\256\345\246\202\344\275\225\346\240\241\351\252\214\345\217\202\346\225\260.md"
similarity index 100%
rename from "\351\207\215\346\236\204/\345\276\256\346\234\215\345\212\241\351\241\271\347\233\256\345\246\202\344\275\225\346\240\241\351\252\214\345\217\202\346\225\260.md"
rename to "\347\274\226\347\250\213\350\247\204\350\214\203/EffectiveJava/\345\276\256\346\234\215\345\212\241\351\241\271\347\233\256\345\246\202\344\275\225\346\240\241\351\252\214\345\217\202\346\225\260.md"
diff --git "a/\347\274\226\347\250\213\350\247\204\350\214\203/\351\230\277\351\207\214 Java \345\274\200\345\217\221\346\211\213\345\206\214\350\257\246\350\247\243/ \343\200\212\351\230\277\351\207\214\345\267\264\345\267\264Java\345\274\200\345\217\221\346\211\213\345\206\214\343\200\213\347\240\201\345\207\272\351\253\230\346\225\210\350\257\246\350\247\243\357\274\210\345\233\233\357\274\211- \345\274\202\345\270\270\345\244\204\347\220\206\345\237\272\346\223\215.md" "b/\347\274\226\347\250\213\350\247\204\350\214\203/\351\230\277\351\207\214 Java \345\274\200\345\217\221\346\211\213\345\206\214\350\257\246\350\247\243/ \343\200\212\351\230\277\351\207\214\345\267\264\345\267\264Java\345\274\200\345\217\221\346\211\213\345\206\214\343\200\213\347\240\201\345\207\272\351\253\230\346\225\210\350\257\246\350\247\243\357\274\210\345\233\233\357\274\211- \345\274\202\345\270\270\345\244\204\347\220\206\345\237\272\346\223\215.md"
new file mode 100644
index 0000000000..35b2a0d29c
--- /dev/null
+++ "b/\347\274\226\347\250\213\350\247\204\350\214\203/\351\230\277\351\207\214 Java \345\274\200\345\217\221\346\211\213\345\206\214\350\257\246\350\247\243/ \343\200\212\351\230\277\351\207\214\345\267\264\345\267\264Java\345\274\200\345\217\221\346\211\213\345\206\214\343\200\213\347\240\201\345\207\272\351\253\230\346\225\210\350\257\246\350\247\243\357\274\210\345\233\233\357\274\211- \345\274\202\345\270\270\345\244\204\347\220\206\345\237\272\346\223\215.md"	
@@ -0,0 +1,236 @@
+# 0 前言
+全是干货的技术殿堂
+
+> `文章收录在我的 GitHub 仓库，欢迎Star/fork：`
+> 
+> [Java-Interview-Tutorial](https://github.com/Wasabi1234/Java-Interview-Tutorial)
+> 
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
+#  1 引导语
+《手册》的异常处理规约对异常的处理方式提出了一些指导规范1，如：
+
+![](https://img-blog.csdnimg.cn/20200206000837876.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20200206000908110.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+
+# 2 "吞掉"异常？
+## 2.1 简介
+即，处理后不再将异常传给上层。其中包括 catch 到异常并处理（打印日志、发通知等）后不再扔给上层；捕捉到异常后给上层返回 null 值等行为。
+
+前一小节的强制 5就属于该种措施。
+
+## 2.2 为什么要手动回滚
+先看事务的执行入口：
+
+- TransactionInterceptor#invoke
+![](https://img-blog.csdnimg.cn/20200206002732147.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- TransactionAspectSupport#invokeWithinTransaction ：
+
+```java
+	@Nullable
+	protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
+			final InvocationCallback invocation) throws Throwable {
+
+		// If the transaction attribute is null, the method is non-transactional.
+		TransactionAttributeSource tas = getTransactionAttributeSource();
+		final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
+		final PlatformTransactionManager tm = determineTransactionManager(txAttr);
+		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
+
+		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
+			// Standard transaction demarcation with getTransaction and commit/rollback calls.
+			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
+
+			Object retVal;
+			try {
+				// This is an around advice: Invoke the next interceptor in the chain.
+				// This will normally result in a target object being invoked.
+				retVal = invocation.proceedWithInvocation();
+			}
+			catch (Throwable ex) {
+				// target invocation exception
+				completeTransactionAfterThrowing(txInfo, ex);
+				throw ex;
+			}
+			finally {
+				cleanupTransactionInfo(txInfo);
+			}
+			commitTransactionAfterReturning(txInfo);
+			return retVal;
+		}
+		...
+```
+带 @Transaction 注解的事务函数中捕获到异常后，执行
+- TransactionAspectSupport#completeTransactionAfterThrowing
+
+```java
+	protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
+		if (txInfo != null && txInfo.getTransactionStatus() != null) {
+			if (logger.isTraceEnabled()) {
+				logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
+						"] after exception: " + ex);
+			}
+			if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
+				try {
+					txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
+				}
+				catch (TransactionSystemException ex2) {
+					logger.error("Application exception overridden by rollback exception", ex);
+					ex2.initApplicationException(ex);
+					throw ex2;
+				}
+				catch (RuntimeException | Error ex2) {
+					logger.error("Application exception overridden by rollback exception", ex);
+					throw ex2;
+				}
+			}
+			else {
+				// We don't roll back on this exception.
+				// Will still roll back if TransactionStatus.isRollbackOnly() is true.
+				try {
+					txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
+				}
+				catch (TransactionSystemException ex2) {
+					logger.error("Application exception overridden by commit exception", ex);
+					ex2.initApplicationException(ex);
+					throw ex2;
+				}
+				catch (RuntimeException | Error ex2) {
+					logger.error("Application exception overridden by commit exception", ex);
+					throw ex2;
+				}
+			}
+		}
+	}
+```
+
+可以看到，如果设置了事务属性且当前异常满足 rollbackOn 指定的异常（默认为 RuntimeException 类型及其子类以及Error 及其子类），则会将当前事务回滚，否则提交。
+
+因此如果 catch 异常后没有再次将异常抛出或者不手动回滚，将会导致事务提交。
+
+在封装二方接口很多人也会选择 “吞掉” 异常，示意代码如下：
+
+```java
+@Component
+public class DemoClient {
+
+    @Resource
+    private XXServcie xxServcie;
+
+    public XXInfo someMethod(Long id) {
+        try {
+            return xxServcie.getXXInfo(id);
+        } catch (Exception e) {
+            log.warn("调用xx服务异常，参数:{}", id, e);
+            return null;
+        }
+    }
+}
+```
+当调用异常时打印异常信息后直接返回 null。
+
+此时如果调用方直接拿到返回值对象而未做判空处理直接使用其属性，易 NPE。
+
+另外由于此处 “吞掉” 了二方接口的异常，有些业务异常中包含的错误原因（如包含xxx敏感词汇、标题不能超过20个字等）无法传给上层再封装给前端，可能会造成出错后用户懵逼，增加很多用户咨询。
+
+比如用户输入了某个敏感词汇，调用二方接口时 “吞掉” 了敏感词汇的业务异常提示（输入中包含 xx敏感词），用户通过技术支持咨询，开发人员要查日志才能知道具体的错误原因（如果此处没打印日志，可能连日志都没得查），非常低效。
+
+开发中要根据具体业务场景慎重确定是否要“吞掉” 异常，一个 “不经意” 的写法可能会造成很多线上咨询甚至线上 BUG。
+
+# 3 循环中的异常处理问题
+`特别注意循环的代码异常处理的对程序的影响。`
+
+## 案例1
+
+![](https://img-blog.csdnimg.cn/20200206004731574.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+在写代码时这种场景非常常见，如果不对循环代码进行捕捉，如果循环中出现异常，后续代码则无法执行。
+
+但是如果在 for 循环外部捕捉异常，虽然for循环后如果有代码依然可以执行，但是列表中的非最后一个元素作为参数调用 doSomeRemoteInvoke 出现异常，后续数据无法继续执行。
+```java
+try {
+    for (String str : data) {
+        // 远程方法调用（可能出现异常）
+        String result = doSomeRemoteInvoke(str);
+        System.out.println(result);
+    }
+} catch (Exception e) {
+    log.error("程序出错，参数data:{},错误详情", JSON.toJSONString(data), e);
+}
+```
+
+因此需要对 for 循环代码内对可能出现的异常进行捕捉：
+
+```java
+for (String str : data) {
+    try {
+        // 远程方法调用（可能出现异常）
+        String result = doSomeRemoteInvoke(str);
+        System.out.println(result);
+    } catch (Exception e) {
+        log.error("程序出错，参数data:{},错误详情", JSON.toJSONString(data), e);
+    }
+}
+```
+
+## 案例 2 
+思考两个问题：
+- 分别调用两个函数 pirntList1 和 printList2 输出的结果有何不同
+- 哪个不需要捕捉异常也不会造成中间有一个出错后续处理中断
+
+代码如下：
+![](https://img-blog.csdnimg.cn/20200206014222563.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- 在函数 pirntList1
+![](https://img-blog.csdnimg.cn/20200206014457752.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+和上面的代码非常相似，for 循环在线程池代码外部，每次循环调用线程池去执行判断和打印语句。
+
+此时依次传入 a、ab、abc、abcd 四个字符串；当执行到 ab 时会抛出 IllegalArgumentException，此时线程池中的唯一的线程销毁；当执行到 abc 字符串时，再次在线程池中执行，线程池创建新的线程来执行，依然可以正常执行。
+![](https://img-blog.csdnimg.cn/20200206015117943.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+- 而在函数 pirntList2
+![](https://img-blog.csdnimg.cn/20200206014639452.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+中 for 循环在 线程池 execute 参数的lambda表达式内，所有的循环执行都在同一个线程内。当执行到 ab 字符串时，抛出了异常，导致整个线程销毁，无法继续执行。
+![](https://img-blog.csdnimg.cn/20200206020252356.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+
+因此为了不让一个数据出错导致后续的代码都无法执行，如果采用第二种方式来执行可以对代码做出如下修改：
+![](https://img-blog.csdnimg.cn/20200206020409740.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+在实际业务开发过程中，这种问题比较隐蔽，尤其是在异步线程中执行时，如果不加留意，很容易出现上面所描述的问题。
+
+# 4 tips
+## 慎重思考是否“吞掉” 异常
+在二方服务封装时，如捕捉异常，应打印出查询参数和异常详情。
+实际开发中，一般都不会吞掉异常，遇到 “吞掉” 异常的场景要慎重思考是否合理。
+另外，正如第二部分给出的范例所示，如果调用二方接口出现异常没有打印日志，将对排查问题造成很大的困难。
+
+## 要理解好受检异常和非受检异常的区别
+Java 中的异常主要分为两类：受检异常和非受检异常。
+
+根据 [JLS 异常部分的描述](https://docs.oracle.com/javase/specs/jls/se9/html/jls-11.html#jls-11.1)
+- 受检异常主要指编译时强制检查的异常，包括非受检异常之外的其他 Throwable 的子类
+- 非受检异常主要指编译器免检异常，通常包括运行时异常类和 Error相关类。
+
+![](https://img-blog.csdnimg.cn/20200206021343267.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+Error 和 Exception 都是 Throwable的子类。 RuntimeException 和其子类都属于运行时异常。Error 类和其子类都属于错误类。RuntimeException 及其子类 和 Error类及其子类 属于非受检异常，除此之外的 其他 Throwable 子类属于受检异常。
+通常开发中自定义的业务异常（BusinessException）属于非受检异常，会定义为 RuntimeException 的子类。
+
+有些朋友可能会将业务异常定义为受检异常，导致底层抛出后上层调用每层都要被迫处理它。
+
+## 努力使失败保持原子性。
+《Effective Java》第 3 版 第 76条 努力使失败保持原子性[^2] 所提到的那样。
+
+我们可以在函数核心代码执行前对参数进行检查，对不满足的条件抛出适当的异常。
+
+实际开发中通常可以使用 com.google.common.base.Preconditions 或者 org.apache.commons.lang3.Validate 第三方库提供的参数检查工具类来实现。
+
+## 如果忽略异常，请给出理由
+如果 catch 住异常却没有进行编写任何处理代码，请在注释中给出充分的理由，避免其他人产生困惑，避免留坑。
+
+大家可以参考 org.springframework.context.support.AbstractApplicationContext#close 源码：
+![](https://img-blog.csdnimg.cn/20200206021818756.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
+
+上面的源码捕捉到 IllegalStateException 异常以后没有处理，给出了处理方式和原因: 忽略此异常，因为虚拟机已经正在关闭。
+
+# 5.总结
+本节主要讲异常的一些处理建议，包括是否要 “吞掉” 异常，循环中的异常处理，以及一些补充建议。希望大家可以重视异常，少趟坑。
+
+# 参考
+- 阿里巴巴Java 开发手册 华山版
+- 《Effective Java 中文版 (原书第 3 版)》
\ No newline at end of file
diff --git "a/\347\274\226\347\250\213\350\247\204\350\214\203/\351\230\277\351\207\214 Java \345\274\200\345\217\221\346\211\213\345\206\214\350\257\246\350\247\243/\343\200\212\351\230\277\351\207\214\345\267\264\345\267\264Java\345\274\200\345\217\221\346\211\213\345\206\214\343\200\213\347\240\201\345\207\272\351\253\230\346\225\210\350\257\246\350\247\243\357\274\210\344\270\200\357\274\211- \344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\351\230\277\351\207\214\347\274\226\347\240\201\346\211\213\345\206\214.md" "b/\347\274\226\347\250\213\350\247\204\350\214\203/\351\230\277\351\207\214 Java \345\274\200\345\217\221\346\211\213\345\206\214\350\257\246\350\247\243/\343\200\212\351\230\277\351\207\214\345\267\264\345\267\264Java\345\274\200\345\217\221\346\211\213\345\206\214\343\200\213\347\240\201\345\207\272\351\253\230\346\225\210\350\257\246\350\247\243\357\274\210\344\270\200\357\274\211- \344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\351\230\277\351\207\214\347\274\226\347\240\201\346\211\213\345\206\214.md"
new file mode 100644
index 0000000000..6f02dbdaab
--- /dev/null
+++ "b/\347\274\226\347\250\213\350\247\204\350\214\203/\351\230\277\351\207\214 Java \345\274\200\345\217\221\346\211\213\345\206\214\350\257\246\350\247\243/\343\200\212\351\230\277\351\207\214\345\267\264\345\267\264Java\345\274\200\345\217\221\346\211\213\345\206\214\343\200\213\347\240\201\345\207\272\351\253\230\346\225\210\350\257\246\350\247\243\357\274\210\344\270\200\357\274\211- \344\270\272\344\273\200\344\271\210\350\246\201\345\255\246\344\271\240\351\230\277\351\207\214\347\274\226\347\240\201\346\211\213\345\206\214.md"	
@@ -0,0 +1,52 @@
+#  1 引导语
+阿里手册是每个 Java 工程师必备的一本案头书。该手册包括 编程规约、异常日志、单元测试、安全规约、MySQL 数据库、工程结构、设计规约 7 个部分 ，基本涵盖了 Java 开发的常见必用知识点。
+
+认真实践该《手册》能够帮助 Java 开发者养成好的编程习惯，帮助企业的开发团队在 Java 开发上更加高效、提高容错性、团队协作更好，并有助于提高代码的质量、降低项目维护的难度。
+
+然而！！！
+很多人会看了就忘，记住了却不理解，工作时根本不会用。
+
+# 2 现实的尴尬
+- 看《手册》等 Java 技术图书时觉得啥都懂，一到实战时就忘了；
+- 很多知识点，知其然而不知其所以然，面试时多问你几个为什么就 “靓仔语塞”；
+- 想通过读源码来进阶，但是容易迷失在细节中，总是半途而废；
+- 不重视需求分析，导致开发完成才意识到设计和需求有偏差；
+- 遇到问题时如果无法简单地定位原因，会优先通过百度、请教别人来解决问题；
+- 开发中遇到问题排查耗时很久，方法很原始；
+- 自己开发的项目，每次上线几乎必出 BUG，而有些同事的项目质量则很高，自己却不知道如何才能尽可能地避免。
+
+# 3 灵魂的拷问
+- 知道很容易，懂很难，很多人把知道当做懂。自认为掌握了就不愿意再深入学习，恰恰错过了彻底掌握该知识的最佳机会；
+- 专业基础不够扎实。 很多人急于求成，只重视解决眼前问题，不能够未雨绸缪，巩固好专业基础，最终导致很多问题” 知其然而不知其所以然 “，排查问题时靠猜、靠问，而不是靠扎实的专业基础之上的推测和验证；
+- 很多人不愿意改变学习方法，学习和培养好的编程习惯，不敢走出舒适区。比如很多人学了很多技术，却从来没有认真仔细阅读过官方文档；比如读源码毫无章法，随心所欲，常常半途而废；
+- 态度决定一切。很多人嘴上说想学好，但是对自己代码要求很低，总是为自己找各种理由不去学更好的方法，不去努力写更优雅的代码；
+- 在学习技术过程中，很多人把脑力劳动当成了体力劳动，把需要思考的问题当做了纯记忆的问题，学习和工作过程中缺乏思考。比如很多人是 “记忆” 经典图书的知识点，而不是理解知识点，导致容易遗忘，不能灵活运用。在学习很多知识点时缺乏思考，没有去搞懂是什么、不明白为什么、不知道如何去做；
+- 没有养成好的解决问题的习惯，排查问题靠猜，而不是思考和验证。也没有主动掌握常见的排查问题的步骤和工具等。
+
+很多人缺乏的不只是好的资料，而是学习的方法。学一样的技术，使用不同的方法，最终学习的效果截然不同。而技术是学不完的，如果没有科学的方法，无法很好地应对层出不穷的新技术。每个人的成长速度是不同的，有的人工作多年，却只有一年的技术经验；而有的人工作一年，却有超越一年的技术经验。造成这种差异的主要原因在于学习能力。
+
+从 Java 新手到高手的进阶过程是一个漫长的爬坑过程。 很多同学遇到 BUG 时由于基础不扎实也没有系统地排查方法，为了解决一个小问题浪费了大把的时间。而且写出的 BUG 太多将直接或间接影响绩效，影响同事、领导对你的印象。
+
+阻碍初学者进步的往往是一成不变的学习方法，是一叶障目不见泰山的盲目自信，而破解尴尬的核心就在于提高自身的学习能力和排错能力。
+
+#  4 解决方案
+
+- 从学习方法主要切入点，结合源码，Java 语言规范 2 和 Java 虚拟机规范 3 等对《手册》的讲解和补充
+- 设计者角度思考问题，很多知识点将从设计者视角去思考分析问题，更容易理解问题的根源
+- 通过对开发中常用的思维导图、流程图和常见 UML 图的讲解，让大家可以 “大战需求分析”，前期明确需求，后期少返工
+- 通过单元测试、Code Review 等相关知识的学习和运用，促进代码质量的提升
+- 通过独特的学习源码视角，来从正确的角度和方法来学习源码的精髓，反向促进日常的开发
+- 结合实际的开发经验，给出相关知识点掌握不牢容易造成的坑，给出一些避坑建议。
+
+# 5 版本
+Java 源码均默认为 JDK 8 版本（特殊标注除外）。
+
+技术是学不完的，学习能力和态度才是进阶的关键。作为一个技术人员，只有保持 “Stay Hungry， Stay Foolish” 的心态，才能够保持进取心；只有真正知道哪些才是更有价值的东西，才能真正少走弯路。
+
+希望大家能够通本教程，从 “学习具体内容” 为主，转变到学习 “学习的方法” 为主；从技术的学习者变为技术的思考者。
+
+# 参考
+- 《 Java 开发手册 1.5.0》华山版. 2019
+- 《Java Language Specification: Java SE 8 Edition》
+- 《The Java Virtual Machine Specification Java SE 8 Edition》
+- 码出规范：《阿里巴巴Java开发手册》详解
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\347\251\272\346\214\207\351\222\210\351\227\256\351\242\230\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/\347\274\226\347\250\213\350\247\204\350\214\203/\351\230\277\351\207\214 Java \345\274\200\345\217\221\346\211\213\345\206\214\350\257\246\350\247\243/\343\200\212\351\230\277\351\207\214\345\267\264\345\267\264Java\345\274\200\345\217\221\346\211\213\345\206\214\343\200\213\347\240\201\345\207\272\351\253\230\346\225\210\350\257\246\350\247\243\357\274\210\344\270\211\357\274\211- \347\243\250\344\272\272\347\232\204\347\251\272\346\214\207\351\222\210\351\227\256\351\242\230\350\247\243\346\236\220.md"
similarity index 97%
rename from "\351\207\215\346\236\204/\347\251\272\346\214\207\351\222\210\351\227\256\351\242\230\346\234\200\344\275\263\345\256\236\350\267\265.md"
rename to "\347\274\226\347\250\213\350\247\204\350\214\203/\351\230\277\351\207\214 Java \345\274\200\345\217\221\346\211\213\345\206\214\350\257\246\350\247\243/\343\200\212\351\230\277\351\207\214\345\267\264\345\267\264Java\345\274\200\345\217\221\346\211\213\345\206\214\343\200\213\347\240\201\345\207\272\351\253\230\346\225\210\350\257\246\350\247\243\357\274\210\344\270\211\357\274\211- \347\243\250\344\272\272\347\232\204\347\251\272\346\214\207\351\222\210\351\227\256\351\242\230\350\247\243\346\236\220.md"
index ae0cf56122..6a6c21e973 100644
--- "a/\351\207\215\346\236\204/\347\251\272\346\214\207\351\222\210\351\227\256\351\242\230\346\234\200\344\275\263\345\256\236\350\267\265.md"
+++ "b/\347\274\226\347\250\213\350\247\204\350\214\203/\351\230\277\351\207\214 Java \345\274\200\345\217\221\346\211\213\345\206\214\350\257\246\350\247\243/\343\200\212\351\230\277\351\207\214\345\267\264\345\267\264Java\345\274\200\345\217\221\346\211\213\345\206\214\343\200\213\347\240\201\345\207\272\351\253\230\346\225\210\350\257\246\350\247\243\357\274\210\344\270\211\357\274\211- \347\243\250\344\272\272\347\232\204\347\251\272\346\214\207\351\222\210\351\227\256\351\242\230\350\247\243\346\236\220.md"	
@@ -251,7 +251,11 @@ public void doSomeOperation(Operation operation) {
     }
 }
 ```
-可构造一个 NullXXX 类拓展自某个接口，这样该接口需要为 null 时，直接返回该对象即可：
+
+《设计模式之禅》（第二版）554 页在拓展篇讲述了 “空对象模式”。
+
+可以构造一个 NullXXX 类拓展自某个接口， 这样这个接口需要为 null 时，直接返回该对象即可：
+
 ```java
 public class NullOperation implements Operation {
 
@@ -450,6 +454,10 @@ public static void doSomething(@NotNull String param) {
     proccess(param);
 }
 ```
+
+# 5. 总结
+本节主要讲述空指针的含义，空指针常见的中枪姿势，以及如何避免空指针异常。下一节将为你揭秘 当 switch 遇到空指针，又会发生什么奇妙的事情。
+
 # 参考
 - 《 阿里巴巴Java 开发手册 1.5.0：华山版》
 -  《Java Language Specification: Java SE 8 Edition》
diff --git "a/\350\201\214\344\270\232\345\217\221\345\261\225/Java\347\263\273\347\273\237\347\272\277\344\270\212\347\224\237\344\272\247\351\227\256\351\242\230\346\216\222\346\237\245\344\270\200\346\212\212\346\242\255.md" "b/\350\201\214\344\270\232\345\217\221\345\261\225/Java\347\263\273\347\273\237\347\272\277\344\270\212\347\224\237\344\272\247\351\227\256\351\242\230\346\216\222\346\237\245\344\270\200\346\212\212\346\242\255.md"
deleted file mode 100644
index 601e23ba5b..0000000000
--- "a/\350\201\214\344\270\232\345\217\221\345\261\225/Java\347\263\273\347\273\237\347\272\277\344\270\212\347\224\237\344\272\247\351\227\256\351\242\230\346\216\222\346\237\245\344\270\200\346\212\212\346\242\255.md"
+++ /dev/null
@@ -1,178 +0,0 @@
-# 1 环境
-## 1.1 Dev
-可以随意使用任何熟悉的工具排查。只要问题能重现，排查就不会太难，最多就是把程序调试到各种框架源码，所以这也是为何面试都会问源码，不求都看过，但要有思路知道如何去看能解决问题。
-
-## 1.2 Test
-比开发环境少了debug，不过也可使用jvisualvm或Arthas，附加到远程JVM进程。
-
-还有测试环境是允许造数据来模拟我们需要的场景的哦，因此这时遇到问题记得主动沟通测试人员造数据让bug更容易复现。
-
-##  1.3 Prd
-该环境下开发人员的权限最低，所以排查问题时障碍很大：
-- 无法使用调试工具从远程附加进程
-- 快速恢复为先，即使在结婚，也得赶紧修复线上问题。而且生产环境流量大、网络权限严格、调用链路复杂，因此更容易出问题，也是出问题最多的环境。
-
-# 2 监控
-生产环境出现问题时，因为要尽快恢复应用，就不可能保留完整现场用于排查和测试。因此，是否有充足的信息（日志、监控和快照）可以了解历史、还原bug 场景。
-最常用的就是 ELK 的日志了，注意：
-- 确保错误、异常信息可被完整记录到文件日志
-- 确保生产上程序的日志级别是INFO以上
-记录日志要使用合理的日志优先级，DEBUG用于开发调试、INFO用于重要流程信息、WARN用于需要关注的问题、ERROR用于阻断流程的错误
-
-生产环境需开发配合运维才能做好完备监控：
-## 主机维度
-对CPU、内存、磁盘、网络等资源做监控。如果应用部署在虚拟机或k8s集群，那么除了对物理机做基础资源监控外，同样还要对虚拟机或Pod监控。监控层数取决于应用的部署方案，有一层OS就要做一层监控。
-## 网络维度
-监控专线带宽、交换机基本情况、网络延迟
-
-## 所有的中间件和存储都要做好监控
-不仅仅是监控进程对CPU、内存、磁盘IO、网络使用的基本指标，更重要的是监控组件内部的一些重要指标。比如最常用的Prometheus，就提供了大量exporter对接各种中间件和存储系统
-## 应用层面
-需监控JVM进程的类加载、内存、GC、线程等常见指标（比如使用Micrometer来做应用监控），此外还要确保能够收集、保存应用日志、GC日志
-
-我们再来看看快照。这里的“快照”是指，应用进程在某一时刻的快照。通常情况下，我们会为生产环境的Java应用设置-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath=…这2个JVM参数，用于在出现OOM时保留堆快照。这个课程中，我们也多次使用MAT工具来分析堆快照。
-
-# 分析定位问题的最佳实践
-定位问题，首先要定位问题出在哪个层次：Java应用程序自身问题还是外部因素导致。
-- 可以先查看程序是否有异常，异常信息一般比较具体，可以马上定位到大概的问题方向
-- 如果是一些资源消耗型的问题可能不会有异常，我们可以通过指标监控配合显性问题点来定位。
-
-一般问题原因可归类如下：
-## 程序发布后 Bug
-回滚，再慢慢通过版本差异分析根因。
-## 外部因素
-比如主机、中间件或DB问题。
-这种按主机层面问题、中间件或存储（统称组件）的问题分为：
-### 主机层
-可使用工具排查：
-#### CPU相关
-使用top、vmstat、pidstat、ps
-#### 内存相关
-使用free、top、ps、vmstat、cachestat、sar
-#### IO相关
-使用lsof、iostat、pidstat、sar、iotop、df、du
-#### 网络相关
-使用ifconfig、ip、nslookup、dig、ping、tcpdump、iptables
-
-#### 组件
-从如下方面排查：
-- 组件所在主机是否有问题
-- 组件进程基本情况，观察各种监控指标
-- 组件的日志输出，特别是错误日志
-- 进入组件控制台，使用一些命令查看其运作情况。
-
-## 系统资源不够造成系统假死
-> 通常先通过重启和扩容解决问题，之后再分析，最好能留个快照。
-
-系统资源不够，一般可能：
-### CPU使用高
-若现场还在，具体分析流程：
-- 在服务器执行`top -Hp pid`
-查看进程中哪个线程CPU使用高
-- 输入大写的P将线程按照 CPU 使用率排序，并把明显占用CPU的线程ID转换为16进制
-- 在jstack命令输出的线程栈中搜索这个线程ID，定位出问题的线程当时的调用栈
-
-若无法直接在服务器执行top，可采样定位：间隔固定时间运行一次jstack，采样几次后，对比采样得出哪些线程始终处于运行状态，找出问题线程。
-
-若现场没了，可排除法分析。CPU使用高，一般是由下面的因素引起的：
-- 突发压力
-可通过应用之前的负载均衡的流量或日志量确认，诸如Nginx等反向代理都会记录URL，可依靠代理的Access Log进行细化定位，也可通过监控观察JVM线程数的情况。压力问题导致CPU使用高的情况下，如果程序的各资源使用没有明显不正常，之后可以通过压测+Profiler（jvisualvm就有这个功能）进一步定位热点方法；如果资源使用不正常，比如产生了几千个线程，就需要考虑调参
-
-- GC
-可通过JVM监控GC相关指标、GC Log确认。如果确认是GC压力，那么内存使用也很可能会不正常，需要按照内存问题分析流程做进步分析。
-- 死循环或不正常处理流程
-可以结合应用日志分析。一般情况下，应用执行过程中都会产生一些日志，可以重点关注日志量异常部分。
-### 内存泄露或OOM
-最简单的就是堆转储后使用MAT分析。堆转储，包含了堆现场全貌和线程栈信息，一般观察支配树图、直方图就可以马上看到占用大量内存的对象，可以快速定位到内存相关问题
-Java进程对内存的使用不仅仅是堆区，还包括线程使用的内存（线程个数*每一个线程的线程栈）和元数据区。每一个内存区都可能产生OOM，可以结合监控观察线程数、已加载类数量等指标分析
-注意看JVM参数的设置是否有明显不合理的，限制了资源。
-
-### IO问题
-除非是代码问题引起的资源不释放等问题，否则通常都不是由Java进程内部因素引发的。
-
-### 网络
-一般也是由外部因素引起。对于连通性问题，结合异常信息通常比较容易定位；对于性能或瞬断问题，可以先尝试使用ping等工具简单判断，如果不行再使用tcpdump或Wireshark。
-
-# 迷茫时的最佳实践
-偶尔可能分析和定位难题，会迷失自我。如果你也这样，可参考如下经验
-## cause or result？
-比如业务执行的很慢，而且线程数增多，那就可能是：
-- 代码逻辑有问题、依赖的外部服务慢
-使得自己的业务逻辑执行缓慢，在访问量不变情况下，就需要更多线程处理。比如，10 TPS的并发原先一次请求1s即可完成，10个线程可支撑；现在执行完成需要10s，就需100个线程
-- 请求量增大
-使得线程数增多，应用本身CPU不足，上下文切换问题导致处理变慢
-
-这时就需要多结合监控指标和各服务的入口流量，分析慢是cause or result。
-
-## 探求规律
-如果没头绪，那就试试总结规律吧！
-比如
-- 有一堆服务器做负载均衡，出问题时可分析监控和日志看请求是否是均匀分布的，可能问题都集中在某个机器节点上
-- 应用日志一般会记录线程名称，出问题时可分析日志是否集中在某类线程
-- 若发现应用开启大量TCP连接，通过netstat可分析出主要集中连接到哪个服务
-
-探求到了规律，就很容易突破了。
-
-## 调用拓扑
-比如看到Nginx返回502，一般可认为是下游服务的问题导致网关无法完成请求转发。
-对于下游服务，不能想当然就认为是我们的Java程序，比如在拓扑上可能Nginx代理的是Kubernetes的Traefik Ingress，链路是Nginx->Traefik->应用，如果一味排查Java程序的健康，则始终找不到根因。
-
-有时虽然使用了Feign进行服务调用，出现连接超时也不一定就是服务端问题，有可能是客户端通过URL调用服务端，并非通过Eureka的服务发现实现的客户端负载均衡。即客户端连接的是Nginx代理而非直接连接应用，客户端连接服务出现的超时，其实是Nginx代理宕机所致。
-
-## 资源限制
-观察各种监控指标，如果发现曲线慢慢上升然后稳定在一个水平线，一般就是资源达到瓶颈。
-
-观察网络带宽曲线时，如果带宽上升到120MB左右不动了，很可能就是打满了1GB的网卡或传输带宽
-观察到数据库活跃连接数上升到10个不动了，很可能是连接池打满了
-
-观察监控一旦看到任何这样曲线，都要引起重视。
-
-## 连锁反应
-CPU、内存、IO和网络相辅相成，一个资源出现瓶颈，很可能同时引起其他资源连锁反应。
-
-内存泄露后对象无法回收会造成大量Full GC，CPU会大量消耗在GC从而引起CPU使用增加
-
-经常会把数据缓存在内存队列进行异步IO，网络或磁盘出现问题时，就很可能会引起内存暴涨。
-
-所以出问题时，要综合考虑避免误判
-## 客户端or服务端or传输问题？
-比如MySQL访问慢了，可能：
-- 客户端原因，连接池不够导致连接获取慢、GC停顿、CPU占满
-- 传输过程问题
-包括光纤可能被挖断了呀、防火墙、路由表等设置有问题
-- 真的服务端背锅了
-
-这都需要逐一排查区分。
-
-服务端慢一般可以看到MySQL出慢日志，传输慢一般可以通过ping来简单定位，排除了这两个可能，并且仅仅是部分客户端出现访问慢的情况，就需要怀疑是客户端本身的问题。对于第三方系统、服务或存储访问出现慢的情况，不能完全假设是服务端的问题。
-
-第七，快照类工具和趋势类工具需要结合使用。比如，jstat、top、各种监控曲线是趋势类工具，可以让我们观察各个指标的变化情况，定位大概的问题点；而jstack和分析堆快照的MAT是快照类工具，用于详细分析某一时刻应用程序某一个点的细节。
-
-一般情况下，我们会先使用趋势类工具来总结规律，再使用快照类工具来分析问题。如果反过来可能就会误判，因为快照类工具反映的只是一个瞬间程序的情况，不能仅仅通过分析单一快照得出结论，如果缺少趋势类工具的帮助，那至少也要提取多个快照来对比。
-
-第八，不要轻易怀疑监控。我曾看过一个空难事故的分析，飞行员在空中发现仪表显示飞机所有油箱都处于缺油的状态，他第一时间的怀疑是油表出现故障了，始终不愿意相信是真的缺油，结果飞行不久后引擎就断油熄火了。同样地，在应用出现问题时，我们会查看各种监控系统，但有些时候我们宁愿相信自己的经验，也不相信监控图表的显示。这可能会导致我们完全朝着错误的方向来排查问题。
-
-如果你真的怀疑是监控系统有问题，可以看一下这套监控系统对于不出问题的应用显示是否正常，如果正常那就应该相信监控而不是自己的经验。
-
-第九，如果因为监控缺失等原因无法定位到根因的话，相同问题就有再出现的风险，需要做好三项工作：
-
-做好日志、监控和快照补漏工作，下次遇到问题时可以定位根因；
-针对问题的症状做好实时报警，确保出现问题后可以第一时间发现；
-考虑做一套热备的方案，出现问题后可以第一时间切换到热备系统快速解决问题，同时又可以保留老系统的现场。
-
-# 总结
-## 分析问题必须讲理
-靠猜是猜不出来的，需要提前做好基础监控的建设。监控的话，需要在基础运维层、应用层、业务层等多个层次进行。定位问题的时候，我们同样需要参照多个监控层的指标表现综合分析。
-
-## 定位问题要先对原因进行大致分类
-比如是内部问题还是外部问题、CPU相关问题还是内存相关问题、仅仅是A接口的问题还是整个应用的问题，然后再去进一步细化探索，一定是从大到小来思考问题；在追查问题遇到瓶颈的时候，我们可以先退出细节，再从大的方面捋一下涉及的点，再重新来看问题。
-
-## 经验很重要
-遇到重大问题的时候，往往也需要根据直觉来第一时间找到最有可能的点，这里甚至有运气成分。我还和你分享了我的九条经验，建议你在平时解决问题的时候多思考、多总结，提炼出更多自己分析问题的套路和拿手工具。
-
-定位到问题原因后，要做好复盘回溯。每次故障的解决都是宝贵经验，复盘不止是记录问题，更是为了架构优化。
-复盘可重点关注如下：
-- 记录完整的时间线、处理措施、上报流程等信息
-- 分析问题的根本原因
-- 给出短、中、长期改进方案，包括但不限于代码改动、SOP、流程，并记录跟踪每一个方案进行闭环
-- 定期组织团队回顾过去的故障
\ No newline at end of file
diff --git "a/\350\201\214\344\270\232\345\217\221\345\261\225/\345\277\205\350\257\273\344\271\246\347\261\215.md" "b/\350\201\214\344\270\232\345\217\221\345\261\225/\345\277\205\350\257\273\344\271\246\347\261\215.md"
deleted file mode 100644
index efc142ed09..0000000000
--- "a/\350\201\214\344\270\232\345\217\221\345\261\225/\345\277\205\350\257\273\344\271\246\347\261\215.md"
+++ /dev/null
@@ -1,357 +0,0 @@
-以下皆出自本人亲自翻阅过的书籍，体验良好，豆瓣大众也以为然，遂列举，以供后浪规划学习。
-
-* * *
-
-1 JavaSE
-========
-
-1.1 基础
-------
-
-### 《Java 核心技术：卷1 》
-
-适合转行及大一的CS专业新生们
-
-![](https://img-blog.csdnimg.cn/20200124010655430.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-  
-
-1.2 进阶
-------
-
-### Java 编程思想 中文第四版
-
-即使是最晦涩的概念，在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形。从Java的基础语法到最高级特性（深入的面向对象概念、多线程、自动项目构建、单元测试和调试等），本书都能逐步指导你轻松掌握。
-
-作者拥有多年教学经验，对C、C++以及Java语言都有独到、深入的见解，以通俗易懂及小而直接的示例解释了一个个晦涩抽象的概念。包含了Java语言基础语法以及高级特性，适合各个层次的Java程序员阅读。
-
-![](https://img-blog.csdnimg.cn/2020041022402693.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-  
-
-###  On Java 8 (Java 编程思想 英文第五版)
-
-![](https://img-blog.csdnimg.cn/20200410224536738.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-  
-
-### Effective Java中文版（第3版）
-
-90个条目，每个条目讨论Java程序设计中的一条规则。这些规则反映了最有经验的优秀程序员在实践中常用的一些有益的做法。
-
-每一章都涉及软件设计的一个主要方面，并不一定需要按部就班地从头读到尾，每个条目都有一定程度的独立性。相互之间经常交叉引用，因此可以很容易地在书中找到自己需要的内容。
-
-本书的目标是帮助读者更加有效地使用Java编程语言及其基本类库：java.lang、java.util和java.io，以及子包，如java.util.concurrent和java.util.function。
-
-![](https://img-blog.csdnimg.cn/20200124010747616.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-并发
---
-
-### 《Java并发编程实战》
-
-并发领域圣经，适合进阶选手的阅读，由 JDK 并发包作者亲自执笔，科学权威地讲解了并发的设计原理。
-
-![](https://img-blog.csdnimg.cn/20200124012004430.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-2 JVM
-=====
-
-2.1 基础
-------
-
-### 深入理解Java虚拟机（第3版）
-
-这是一部从工作原理和工程实践两个维度深入剖析JVM的著作，是计算机领域公认的经典，繁体版在台湾也颇受欢迎。
-
-第3版在第2版的基础上做了重大修订，内容更丰富、实战性更强：根据新版JDK对内容进行了全方位的修订和升级，围绕新技术和生产实践新增逾10万字，包含近50%的全新内容，并对第2版中含糊、瑕疵和错误内容进行了修正。
-
-![](https://img-blog.csdnimg.cn/20200124010454852.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-Inside the Java 2 Virtual Machine
-=================================
-
-3 操作系统（Linux）
-=============
-
- 3.1 基础
--------
-
-鳥哥的Linux私房菜（第四版）
-----------------
-
-![](https://img-blog.csdnimg.cn/20200124011312222.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-深入理解计算机系统（原书第3版）
-----------------
-
-和第2版相比，本版内容上最大变化是，从以IA32和x86-64为基础转变为完全以x86-64为基础。主要更新如下：
-
-基于x86-64，大量地重写代码，首次介绍对处理浮点数据的程序的机器级支持。
-
-处理器体系结构修改为支持64位字和操作的设计。
-
-引入更多的功能单元和更复杂的控制逻辑，使基于程序数据流表示的程序性能模型预测更加可靠。
-
-扩充关于用GOT和PLT创建与位置无关代码的讨论，描述了更加强大的链接技术（比如库打桩）。
-
-增加了对信号处理程序更细致的描述，包括异步信号安全的函数等。
-
-采用新函数，更新了与协议无关和线程安全的网络编程。
-
-![](https://img-blog.csdnimg.cn/8af0446b4d08458991f91cb69286303d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-  
-
-  
-
-《**UNIX环境高级编程**》第三版
--------------------
-
-被誉为UNIX编程“圣经”的Advanced Programming in the UNIX Environment一书的第3版。在本书第2版出版后的8年中，UNIX行业发生了巨大的变化，特别是影响UNIX编程接口的有关标准变化很大。本书在保持前一版风格的基础上，根据最新的标准对内容进行了修订和增补，反映了最新的技术发展。书中除了介绍UNIX文件和目录、标准I/O库、系统数据文件和信息、进程环境、进程控制、进程关系、信号、线程、线程控制、守护进程、各种I/O、进程间通信、网络IPC、伪终端等方面的内容，还在此基础上介绍了众多应用实例，包括如何创建数据库函数库以及如何与网络打印机通信等
-
-![](https://img-blog.csdnimg.cn/20200124012046460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-4 Spring 框架
-===========
-
-4.1 基础
-------
-
-### Spring实战（第4版）
-
-入门经典书籍。第5版最新但是设计不适合初学者，所以推荐四版。适合刚开始学习Spring 框架的Java 开发人员快速上手。
-
-![](https://img-blog.csdnimg.cn/20200926042752695.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-===================================================================================================================================================================================================
-
-5 数据库（MySQL）
-============
-
-5.1 基础
-------
-
-### 《SQL 必知必会》
-
-本书是深受世界各地读者欢迎的SQL经典畅销书，内容丰富，文字简洁明快，针对Oracle、SQL Server、MySQL、DB2、PostgreSQL、SQLite等各种主流数据库提供了大量简明的实例。与其他同类图书不同，它没有过多阐述数据库基础理论，而是专门针对一线软件开发人员，直接从SQL SELECT开始，讲述实际工作环境中最常用和最必需的SQL知识，实用性极强。通过本书，读者能够从没有多少SQL经验的新手，迅速编写出世界级的SQL！
-
-![](https://img-blog.csdnimg.cn/20200124011613817.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-### 《高性能 MySQL》第三版
-
-![](https://img-blog.csdnimg.cn/20200124011712860.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-  
-
-6 Redis
-=======
-
-6.1 基础
-------
-
-### Redis设计与实现
-
-![](https://img-blog.csdnimg.cn/20200124011831993.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
-
-### Redis开发与运维
-
-本书全面讲解Redis基本功能及其应用，并结合线上开发与运维监控中的实际使用案例，深入分析并总结了实际开发运维中遇到的“陷阱”，以及背后的原因， 包含大规模集群开发与管理的场景、应用案例与开发技巧，为高效开发运维提供了大量实际经验和建议。本书不要求读者有任何Redis使用经验,对入门与进阶DevOps的开发者提供有价值的帮助。主要内容包括：Redis的安装配置、API、各种高效功能、客户端、持久化、复制、高可用、内存、哨兵、集群、缓存设计等，Redis高可用集群解决方案，Redis设计和使用中的问题，最后提供了一个开源工具：Redis监控运维云平台CacheCloud。
-
-![](https://img-blog.csdnimg.cn/20200630094214256.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-### Redis 深度历险：核心原理与应用实践
-
-《Redis 深度历险：核心原理与应用实践》分为基础和应用篇、原理篇、集群篇、拓展篇、源码篇共 5 大块内容。基础和应用篇讲解对读者来说最有价值的内容，可以直接应用到实际工作中；原理篇、集群篇让开发者透过简单的技术表面看到精致的底层世界；拓展篇帮助读者拓展技术视野和夯实基础，便于进阶学习；源码篇让高阶的读者能够读懂源码，掌握核心技术实力。
-
-适合人群：有 Redis 基础，渴望深度掌握 Redis 技术原理的中高级后端开发者；渴望成功进入大型互联网企业研发部的中高级后端开发者；需要支撑公司 Redis 中间件运维工作的初中级运维工程师；对 Redis 中间件技术好奇的中高级前端技术研究者。
-
-![](https://img-blog.csdnimg.cn/20200630094508786.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-6.2 进阶
-------
-
-### Redis5设计与源码分析
-
-本书系统讲解Redis 5设计、数据结构、底层命令实现，以及持久化、主从复制、集群的实现。
-
-![](https://img-blog.csdnimg.cn/20200630094640802.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-7 软件设计
-======
-
- 7.1 基础
--------
-
-### 《Head First设计模式》
-
-![](https://img-blog.csdnimg.cn/20200630094740534.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-### 大话设计模式
-
-![](https://img-blog.csdnimg.cn/20200630094820433.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-### 设计模式之禅（第2版）
-
-本书是设计模式领域公认的3本经典著作之一，“极具趣味，容易理解，但讲解又极为严谨和透彻”是本书的写作风格和方法的最大特点。深刻解读6大设计原则和28种设计模式的准确定义、应用方法和最佳实践，全方位比较各种同类模式之间的异同，详细讲解将不同的模式组合使用的方法。
-
-![](https://img-blog.csdnimg.cn/20201011051617541.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-### 代码整洁之道
-
-本书提出：代码质量与其整洁度成正比。干净的代码，既在质量上较为可靠，也为后期维护、升级奠定了良好基础。本书给出一系列行之有效的整洁代码操作实践，并辅以来自现实项目的正、反两面的范例。
-
-遵循这些规则，就能编写出干净的代码，有效提升代码质量。涵盖从命名到重构的多个编程方面。
-
-![](https://img-blog.csdnimg.cn/20211006015429607.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-### UNIX编程艺术
-
-本书主要介绍了Unix系统领域中的设计和开发哲学、思想文化体系、原则与经验，由公认的Unix编程大师、开源运动领袖人物之一Eric S. Raymond倾力多年写作而成。包括Unix设计者在内的多位领域专家也为本书贡献了宝贵的内容。本书内容涉及社群文化、软件开发设计与实现，覆盖面广、内容深邃，完全展现了作者极其深厚的经验积累和领域智慧。
-
-![](https://img-blog.csdnimg.cn/20211010013840628.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-  
-
-8 架构
-====
-
-8.1 基础
-------
-
-### 《大型网站技术架构：核心原理与案例分析》\- 面试架构知识点核心书籍
-
-通过梳理大型网站技术发展历程，剖析大型网站技术架构模式，深入讲述大型互联网架构设计的核心原理，并通过一组典型网站技术架构设计案例，为读者呈现一幅包括技术选型、架构设计、性能优化、Web 安全、系统发布、运维监控等在内的大型网站开发全景视图。了解大型网站的解决方案和开发理念。
-
-![](https://img-blog.csdnimg.cn/20200926043702445.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-8.2 进阶
-------
-
-### 亿级流量网站架构核心技术
-
-京东架构师经验之谈，总结并梳理了亿级流量网站高可用和高并发原则，通过实例详细介绍了如何落地这些原则。
-
-分为四部分：概述、高可用原则、高并发原则、案例实战。从负载均衡、限流、降级、隔离、超时与重试、回滚机制、压测与预案、缓存、池化、异步化、扩容、队列等多方面详细介绍了亿级流量网站的架构核心技术，让读者看后能快速运用到实践项目中。 
-
-![](https://img-blog.csdnimg.cn/20201130145340753.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-### 企业应用架构模式
-
-本书作者是当今面向对象软件开发的权威，他在一组专家级合作者的帮助下，将40多种经常出现的解决方案转化成模式，最终写成这本能够应用于任何一种企业应用平台的、关于解决方案的、不可或缺的手册。本书获得了2003年度美国软件开发杂志图书类的生产效率奖和读者选择奖。本书分为两大部分。第一部分是关于如何开发企业应用的简单介绍。第二部分是本书的主体，是关于模式的详细参考手册，每个模式都给出使用方法和实现信息
-
-![](https://img-blog.csdnimg.cn/20201002040637862.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-### 架构整洁之道
-
-创造“Clean神话”的Bob大叔在架构领域的登峰之作，围绕“架构整洁”这一重要导向，系统地剖析其缘起、内涵及应用场景，涵盖软件研发完整过程及所有核心架构模式。
-
-![](https://img-blog.csdnimg.cn/20211005030603809.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-=========================================================================================================================================================================================
-
-### 数据密集型应用系统设计
-
-全书分为三大部分：
-
-第一部分，主要讨论有关增强数据密集型应用系统所需的若干基本原则。首先开篇第1章即瞄准目标：可靠性、可扩展性与可维护性，如何认识这些问题以及如何达成目标。第2章我们比较了多种不同的数据模型和查询语言，讨论各自的适用场景。接下来第3章主要针对存储引擎，即数据库是如何安排磁盘结构从而提高检索效率。第4章转向数据编码（序列化）方面，包括常见模式的演化历程。
-
-第二部分，我们将从单机的数据存储转向跨机器的分布式系统，这是扩展性的重要一步，但随之而来的是各种挑战。所以将依次讨论数据远程复制（第5章）、数据分区（第6章）以及事务（第7章）。接下来的第8章包括分布式系统的更多细节，以及分布式环境如何达成一致性与共识（第9章）。
-
-第三部分，主要针对产生派生数据的系统，所谓派生数据主要指在异构系统中，如果无法用一个数据源来解决所有问题，那么一种自然的方式就是集成多个不同的数据库、缓存模块以及索引模块等。首先第10章以批处理开始来处理派生数据，紧接着第11章采用流式处理。第12章总结之前介绍的多种技术，并分析讨论未来构建可靠、可扩展和可维护应用系统可能的新方向或方法。
-
-![](https://img-blog.csdnimg.cn/20211006191349733.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-  
-
-9 DDD
-=====
-
- 9.1 基础
--------
-
-### 领域驱动设计模式、原理与实践
-
-![](https://img-blog.csdnimg.cn/20200926052219965.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-### 实现领域驱动设计
-
-领域驱动设计（DDD）教我们如何做好软件的，同时也是教我们如何更好地使用面向对象技术的。它为我们提供了设计软件的全新视角，同时也给开发者留下了一大难题：如何将领域驱动设计付诸实践？Vaughn Vernon 的这本《实现领域驱动设计》为我们给出了全面的解答。
-
-《实现领域驱动设计》分别从战略和战术层面详尽地讨论了如何实现DDD，其中包含了大量的最佳实践、设计准则和对一些问题的折中性讨论。《实现领域驱动设计》共分为14 章，在DDD 战略部分，《实现领域驱动设计》向我们讲解了领域、限界上下文、上下文映射图和架构等内容，战术部分包括实体、值对象、领域服务、领域事件、聚合和资源库等内容。一个虚构的案例研究贯穿全书，这对于实例讲解DDD 实现来说非常有用。
-
-《实现领域驱动设计》在DDD 的思想和实现之间建立起了一座桥梁，架构师和程序员均可阅读，同时也可以作为一本DDD 参考书。
-
-![](https://img-blog.csdnimg.cn/20200926044228475.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-9.2 进阶
-------
-
-### 领域驱动设计
-
-领域驱动设计方面的经典之作。全书围绕设计和开发实践，结合项目案例，向读者阐述如何在真实的软件开发中应用领域驱动设计。给出了领域驱动设计的系统化方法，并将人们普遍接受的一些实践综合到一起，融入了作者的见解和经验，展现了一些可扩展的设计新实践、已验证过的技术以及便于应对复杂领域的软件项目开发的基本原则。
-
-![](https://img-blog.csdnimg.cn/20200926044555766.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-  
-
-10 计算机网络
-========
-
-10.1 基础
--------
-
-### 《图解HTTP》
-
-本书对HTTP协议进行全面系统介绍。作者由HTTP协议的发展历史娓娓道来，严谨细致地剖析了HTTP协议的结构，列举诸多常见通信场景及实战案例，最后延伸到Web安全、最新技术动向等方面。本书的特色为在讲解的同时，辅以大量生动形象的通信图例，更好地帮助读者深刻理解HTTP通信过程中客户端与服务器之间的交互情况。读者可通过本书快速了解并掌握HTTP协议的基础，前端工程师分析抓包数据，后端工程师实现REST API、实现自己的HTTP服务器等过程中所需的HTTP相关知识点本书均有介绍。
-
-![](https://img-blog.csdnimg.cn/202009260447331.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-11 数据结构与算法
-==========
-
-11.1 基础
--------
-
-### 算法（第4版）
-
-Sedgewick畅销著作的最新版，反映了经过几十年演化而成的算法核心知识体系，全面论述排序、搜索、图处理和字符串处理的算法和数据结构，涵盖每位程序员应知应会的50种算法，全新的Java实现代码，采用模块化的编程风格，所有代码均可供读者使用。
-
-![](https://img-blog.csdnimg.cn/20200703165148393.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-### 大话数据结构
-
-本书为超级畅销书《大话设计模式》作者程杰潜心三年推出的扛鼎之作！以一个计算机教师教学为场景，讲解数据结构和相关算法的知识。通篇以一种趣味方式来叙述，大量引用了各种各样的生活知识来类比，并充分运用图形语言来体现抽象内容，对数据结构所涉及到的一些经典算法做到逐行分析、多算法比较。与市场上的同类数据结构图书相比，本书内容趣味易读，算法讲解细致深刻，是一本非常适合自学的读物。
-
-本书以一个计算机教师教学为场景，讲解数据结构和相关算法的知识。通篇以一种趣味方式来叙述，大量引用了各种各样的生活知识来类比，并充分运用图形语言来体现抽象内容，对数据结构所涉及到的一些经典算法做到逐行分析、多算法比较。与市场上的同类数据结构图书相比，本书内容趣味易读，算法讲解细致深刻，是一本非常适合自学的读物。
-
-![](https://img-blog.csdnimg.cn/20200703165428511.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-  
-
-11.2 进阶
--------
-
-### 程序员代码面试指南（第2版）
-
-程序员代码面试"神书”！书中对IT名企代码面试各类题目的最优解进行了总结，并提供了相关代码实现。针对当前程序员面试缺乏权威题目汇总这一痛点，本书选取将近300道真实出现过的经典代码面试题，帮助广大程序员的面试准备做到接近万无一失。"刷”完本书后，你就是"题王”！《程序员代码面试指南：IT名企算法与数据结构题目最优解（第2版）》采用题目解答的方式组织内容，并把面试题类型相近或者解法相近的题目尽量放在一起，读者在学习本书时很容易看出面试题解法之间的联系，使知识的学习避免碎片化。本书所收录的所有面试题都给出了最优解讲解和代码实现，并且提供了一些普通解法和最优解法的运行时间对比，让读者真切地感受到最优解的魅力！书中收录了大量新题和最优解分析，这些内容源自笔者多年来"死磕自己”的深入思考。提升算法和数据结构等方面能力。
-
-![](https://img-blog.csdnimg.cn/2020070316532482.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-12 网络编程
-=======
-
-12.1 基础
--------
-
-### Netty 实战
-
-本书中文版基于Netty4.1.9做了修订。Netty之父”Trustin Lee作序推荐。无论是构建高性能的Web、游戏服务器、推送系统、RPC框架、消息中间件还是分布式大数据处理引擎，都离不开Netty，在整个行业中，Netty广泛而成功的应用，使其成为了Java高性能网络编程的卓绝框架。无论是想要学习Spring 5 、Spark、Cassandra等这样的系统，还是通过学习Netty来构建自己的基于Java的高性能网络框架，或者是更加具体的高性能Web或者游戏服务器等，本书都将是你的超强拍档。
-
-![](https://img-blog.csdnimg.cn/20201030002004281.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-13 消息队列
-=======
-
-RabbitMQ 实战
------------
-
-![](https://img-blog.csdnimg.cn/20210703182055114.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
\ No newline at end of file
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\345\274\202\345\270\270\345\222\214\344\270\255\346\226\255.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206 (\345\215\201\344\272\214) - \345\274\202\345\270\270\345\222\214\344\270\255\346\226\255.md"
similarity index 100%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\345\274\202\345\270\270\345\222\214\344\270\255\346\226\255.md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206 (\345\215\201\344\272\214) - \345\274\202\345\270\270\345\222\214\344\270\255\346\226\255.md"
diff --git "a/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\200\357\274\211- \345\206\257\302\267\350\257\272\344\276\235\346\233\274\344\275\223\347\263\273\347\273\223\346\236\204.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\200\357\274\211- \345\206\257\302\267\350\257\272\344\276\235\346\233\274\344\275\223\347\263\273\347\273\223\346\236\204.md"
new file mode 100644
index 0000000000..af4dee4786
--- /dev/null
+++ "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\200\357\274\211- \345\206\257\302\267\350\257\272\344\276\235\346\233\274\344\275\223\347\263\273\347\273\223\346\236\204.md"	
@@ -0,0 +1,211 @@
+![](https://ask.qcloudimg.com/http-save/1752328/g6cdrb45jg.png)
+
+# 1 计算机的基本硬件组成
+
+早期,DIY一台计算机，要先有三大件
+
+- CPU
+- 内存
+- 主板
+
+## 1.1 CPU
+
+计算机最重要的核心配件，中央处理器（Central Processing Unit）。
+
+计算机的所有“计算”都是由CPU来进行的。
+
+- CPU是一个超级精细的印刷电路版
+![](https://ask.qcloudimg.com/http-save/1752328/1ya6ng3q8l.png)
+
+## 1.2 内存（Memory）
+
+你撰写的程序、打开的浏览器、运行的游戏，都要加载到内存里才能运行。
+
+程序读取的数据、计算得到的结果，也都要放在内存里。内存越大，能加载的东西自然也就越多。
+
+内存通常直接可以插在主板上，存放在内存里的程序和数据，需要被CPU读取，CPU计算完之后，还要把数据写回到内存。然而CPU不能直接插到内存上，反之亦然。于是，就带来了最后一个大件——主板（Motherboard）。
+
+- 内存通常直接可以插在主板上
+![](https://ask.qcloudimg.com/http-save/1752328/fzz62il1s0.png)
+
+## 1.3 主板
+
+主板是一个有着各种各样，有时候多达数十乃至上百个插槽的配件。
+
+我们的CPU要插在主板上，内存也要插在主板上。
+
+主板的芯片组（Chipset）和总线（Bus）解决了CPU和内存之间如何通信的问题。
+
+- 芯片组控制了数据传输的流转，也就是数据从哪里到哪里的问题
+- 总线则是实际数据传输的高速公路。总线速度（Bus Speed）决定了数据能传输得多快。
+- 计算机主板上通常有着各种各样的插槽![](https://ask.qcloudimg.com/http-save/1752328/lyress98vj.png)
+
+有了三大件，只要配上**电源**供电，计算机差不多就可以跑起来了。
+
+但是现在还缺少各类输入（Input）/输出（Output）设备，也就是我们常说的**I/O设备**。
+
+如果你用的是自己的个人电脑，那显示器肯定必不可少，只有有了显示器我们才能看到计算机输出的各种图像、文字，这也就是所谓的**输出设备**。
+
+同样的，鼠标和键盘也都是必不可少的配件。这样我才能输入文本，写下这篇文章。它们也就是所谓的**输入设备**。
+
+最后，你自己配的个人计算机，还要配上一个硬盘。这样各种数据才能持久地保存下来。
+
+绝大部分人都会给自己的机器装上一个机箱，配上风扇，解决灰尘和散热的问题。
+
+不过机箱和风扇，算不上是计算机的必备硬件，我们拿个纸板或者外面放个电风扇，也一样能用。
+
+显示器、鼠标、键盘和硬盘这些东西并不是一台计算机必须的部分。
+
+其实只需要有I/O设备，能让我们从计算机里输入和输出信息就可以了。
+
+很多网吧的计算机就没有硬盘，而是直接通过局域网，读写远程网络硬盘里面的数据。
+
+各类云服务器，只要让计算机能通过网络，SSH远程登陆访问就好了，因此也没必要配显示器、鼠标、键盘这些东西。
+
+这样不仅能够节约成本，还更方便维护。
+
+还有一个很特殊的设备，就是**显卡**（Graphics Card）。
+
+现在，使用图形界面操作系统的计算机，无论是Windows、Mac OS还是Linux，显卡都是必不可少的。
+
+有人可能要说了，我装机的时候没有买显卡，计算机一样可以正常跑起来啊！那是因为，现在的主板都带了内置的显卡。
+
+如果你用计算机玩游戏，做图形渲染或者跑深度学习应用，你多半就需要买一张单独的显卡，插在主板上。
+
+显卡之所以特殊，是因为显卡里有除了CPU之外的另一个“处理器”，也就是GPU（Graphics Processing Unit，图形处理器），GPU一样可以做各种“计算”的工作。
+
+鼠标、键盘以及硬盘都是插在主板上的。作为外部I/O设备，它们是通过主板上的**南桥**（SouthBridge）芯片组，来控制和CPU之间的通信的。
+
+“南桥”芯片的名字很直观
+
+- 它在主板上的位置，通常在主板的“南面”
+- 它的作用就是作为“桥”，来连接鼠标、键盘以及硬盘这些外部设备和CPU之间的通信。
+
+有了南桥，自然对应着也有“北桥”。
+
+是的，以前的主板上通常也有“北桥”芯片，用来作为“桥”，连接CPU和内存、显卡之间的通信。
+
+不过，随着时间的变迁，现在的主板上的“北桥”芯片的工作，已经被移到了CPU的内部，所以你在主板上，已经看不到北桥芯片了。
+
+# 2 冯·诺依曼体系结构
+
+刚才我们讲了一台计算机的硬件组成，这说的是我们平时用的个人电脑或者服务器。那我们平时最常用的智能手机的组成，也是这样吗？
+
+我们手机里只有SD卡（Secure Digital Memory Card）类似硬盘功能的存储卡插槽，并没有内存插槽、CPU插槽这些东西。
+
+没错，因为手机尺寸的原因，手机制造商们选择把
+
+CPU、内存、网络通信，乃至摄像头芯片，都封装到一个芯片，然后再嵌入到手机主板上。
+
+这种方式叫**SoC**，也就是System on a Chip（系统芯片）。
+
+看起来，个人电脑和智能手机的硬件组成方式不太一样。
+
+可是，我们写智能手机上的App，和写个人电脑的客户端应用似乎没有什么差别，都是通过“高级语言”这样的编程语言撰写、编译之后，一样是把代码和数据加载到内存里来执行。
+
+无论是个人电脑/服务器/智能手机，还是Raspberry Pi这样的微型卡片机，都遵循着同一个“计算机”的抽象概念。
+
+这是怎么样一个“计算机”呢？这其实就是，计算机鼻祖冯·诺依曼提出的**冯·诺依曼体系结构**（Von Neumann architecture），也叫**存储程序计算机**。
+
+什么是存储程序计算机呢？这里面其实暗含了两个概念
+
+- “可编程”计算机
+- “存储”计算机
+
+什么是“不可编程”？？？
+
+计算机是由各种门电路组合而成的，然后通过组装出一个固定的电路版，完成一个特定的计算程序。
+
+一旦需要修改功能，就要重新组装电路。这样的话，计算机就是“不可编程”的，因为程序在计算机硬件层面是“写死”的。
+
+最常见的就是老式计算器，电路板设好了加减乘除，做不了任何计算逻辑固定之外的事情。
+
+- 计算器的本质是一个不可编程的计算机
+![](https://ask.qcloudimg.com/http-save/1752328/efi2z2i6f1.png)
+
+我们再来看“存储”计算机。
+
+程序本身是存储在计算机的内存里，可以通过加载不同的程序来解决不同的问题。
+
+有“存储程序计算机”，自然也有不能存储程序的计算机。
+
+典型的就是早年的“Plugboard”这样的插线板式的计算机。整个计算机就是一个巨大的插线板，通过在板子上不同的插头或者接口的位置插入线路，来实现不同的功能。这样的计算机自然是“可编程”的，但是编写好的程序不能存储下来供下一次加载使用，不得不每次要用到和当前不同的“程序”的时候，重新插板子，重新“编程”。
+
+- 著名的Engima Machine就用到了Plugboard来进行“编程”
+![](https://ask.qcloudimg.com/http-save/1752328/tni9s3p8vf.png)
+可以看到，无论是“不可编程”还是“不可存储”，都会让使用计算机的效率大大下降。
+而这个对于效率的追求，也就是“存储程序计算机”的由来。
+
+冯，基于当时在秘密开发的EDVAC写了一篇报告First Draft of a Report on the EDVAC，描述了他心目中的一台计算机应该长什么样。这篇报告在历史上有个很特殊的简称，叫First Draft。这样，现代计算机的发展就从祖师爷写的一份草案开始了。
+
+#### First Draft里面说了一台计算机应该有哪些部分组成
+
+首先是一个包含
+
+- 算术逻辑单元（Arithmetic Logic Unit，ALU）
+- 处理器寄存器（Processor Register）
+
+的**处理器单元**（Processing Unit），用来完成各种算术和逻辑运算。
+
+因为它能够完成各种数据的处理或者计算工作，因此也有人把这个叫作数据通路（Datapath）或者运算器。
+
+然后是一个包含
+
+- 指令寄存器（Instruction Reigster）
+- 程序计数器（Program Counter）
+
+的**控制器单元**（Control Unit/CU），用来控制程序的流程，通常就是不同条件下的分支和跳转。
+
+在现在的计算机里，上面的算术逻辑单元和这里的控制器单元，共同组成了我们说的CPU。
+
+接着是用来存储数据（Data）和指令（Instruction）的**内存**。以及更大容量的**外部存储**，在过去，可能是磁带、磁鼓这样的设备，现在通常就是硬盘。
+
+最后就是各种**输入和输出设备**，以及对应的输入和输出机制。
+
+我们现在无论是使用什么样的计算机，其实都是和输入输出设备在打交道。
+
+- 个人电脑的鼠标键盘是输入设备，显示器是输出设备
+- 我们用的智能手机，触摸屏既是输入设备，又是输出设备
+- 跑在各种云上的服务器，则是通过网络来进行输入和输出。这个时候，网卡既是输入设备又是输出设备
+
+> 任何一台计算机的任何一个部件都可以归到运算器、控制器、存储器、输入设备和输出设备中，而所有的现代计算机也都是基于这个基础架构来设计开发的
+
+而所有的计算机程序，也都可以抽象为从**输入设备**读取输入信息，通过**运算器**和**控制器**来执行存储在**存储器**里的程序，最终把结果输出到**输出设备**中。而我们所有撰写的无论高级还是低级语言的程序，也都是基于这样一个抽象框架来进行运作的。
+
+- 冯·诺依曼体系结构示意图
+![](https://ask.qcloudimg.com/http-save/1752328/6u485dctzr.png)
+
+# 3 总结
+
+冯·诺依曼体系结构确立了我们现在每天使用的计算机硬件的基础架构。
+
+因此，学习计算机组成原理，其实就是学习和拆解冯·诺依曼体系结构。
+
+具体来说，其实就是
+
+- 学习控制器、运算器的工作原理，也就是CPU是怎么工作的，以及为何这样设计
+- 学习内存的工作原理，从最基本的电路，到上层抽象给到CPU乃至应用程序的接口是怎样的
+- 学习CPU是怎么和输入设备、输出设备打交道的。=
+
+学习组成原理，就是在理解从控制器、运算器、存储器、输入设备以及输出设备，从电路这样的硬件，到最终开放给软件的接口，是怎么运作的，为什么要设计成这样，以及在软件开发层面怎么尽可能用好它。
+
+# 4 推荐阅读
+
+- First Draft of a Report on the EDVAC
+对于工程师来说，直接读取英文论文的原文，既可以搞清楚、弄明白对应的设计及其背后的思路来源，还可以帮你破除对于论文或者核心技术的恐惧心理。
+
+# 5 思考
+
+计算机行业的两大祖师爷之一，除了冯·诺依曼机之外，还有一位就是著名的图灵（Alan Mathison Turing）。对应的，我们现在的计算机也叫图灵机（Turing Machine）。那么图灵机和冯·诺依曼机是两种不同的计算机么？图灵机是一种什么样的计算机抽象呢？
+
+欢迎留言分享你的思考和疑惑，也可以把本文分享给你的朋友，一起学习和进步！
+
+# 参考
+
+深入浅出计算机组成原理
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\203\357\274\211- \347\250\213\345\272\217\346\227\240\346\263\225\345\220\214\346\227\266\345\234\250Linux\345\222\214Windows\344\270\213\350\277\220\350\241\214.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\203\357\274\211- \347\250\213\345\272\217\346\227\240\346\263\225\345\220\214\346\227\266\345\234\250Linux\345\222\214Windows\344\270\213\350\277\220\350\241\214.md"
similarity index 100%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\203\357\274\211- \347\250\213\345\272\217\346\227\240\346\263\225\345\220\214\346\227\266\345\234\250Linux\345\222\214Windows\344\270\213\350\277\220\350\241\214.md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\203\357\274\211- \347\250\213\345\272\217\346\227\240\346\263\225\345\220\214\346\227\266\345\234\250Linux\345\222\214Windows\344\270\213\350\277\220\350\241\214.md"
diff --git "a/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\211\357\274\211- \350\277\233\345\207\273,\346\233\264\345\274\272\347\232\204\346\200\247\350\203\275!.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\211\357\274\211- \350\277\233\345\207\273,\346\233\264\345\274\272\347\232\204\346\200\247\350\203\275!.md"
new file mode 100644
index 0000000000..5f6c455733
--- /dev/null
+++ "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\270\211\357\274\211- \350\277\233\345\207\273,\346\233\264\345\274\272\347\232\204\346\200\247\350\203\275!.md"	
@@ -0,0 +1,273 @@
+![](https://ask.qcloudimg.com/http-save/1752328/uskvyzme4j.png)
+
+在上一篇中,我们谈到过
+
+```
+程序的CPU执行时间 = 指令数×CPI×Clock Cycle Time
+```
+
+要提升计算机的性能，可以从上面这三方面着手。
+
+通过指令数/CPI，好像都太难了。
+
+因此工程师们，就在CPU上多放晶体管，不断提升CPU的时钟频率，让CPU更快，程序的执行时间就会缩短。
+
+![](https://ask.qcloudimg.com/http-save/1752328/09hlntlacb.png)
+
+- 从1978年Intel发布的8086 CPU开始，计算机的主频从5MHz开始，不断攀升
+- 1980年代中期的80386能够跑到40MHz
+- 1989年的486能够跑到100MHz
+- 直到2000年的奔腾4处理器，主频已经到达了1.4GHz
+
+# 1 功耗：CPU的“人体极限”
+
+[奔腾4的CPU主频从来没有达到过10GHz，最终它的主频上限定格在3.8GHz](https://zh.wikipedia.org/wiki/%25E5%25A5%2594%25E8%2585%25BE4)
+
+而且奔腾4的主频虽然高，但是实际性能却配不上同样的主频
+
+想要用在笔记本上的奔腾4 2.4GHz处理器，其性能只和基于奔腾3架构的奔腾M 1.6GHz匹配
+
+于是不仅让Intel的对手AMD获得了喘息之机，更是代表着“主频时代”的终结。
+
+后面几代Intel CPU主频不但没有上升，反而下降了。
+
+到如今，2019年的最高配置[Intel i9 CPU](https://zh.wikipedia.org/wiki/Intel_Core_i9)，主频也不过是5GHz
+
+相较于1978年到2000年，这20年里300倍的主频提升，从2000年到现在的这19年，CPU的主频大概提高了3倍
+
+- CPU的主频变化，奔腾4时进入瓶颈期![](https://ask.qcloudimg.com/http-save/1752328/mxpvmi6gzl.png)
+
+奔腾4的主频为什么没能超3.8GHz？
+
+就因为功耗.
+
+一个3.8GHz的奔腾4处理器，满载功率是130瓦
+
+130瓦是什么概念呢？机场允许带上飞机的充电宝的容量上限是100瓦时
+
+如果我们把这个CPU安在手机里面，不考虑屏幕内存之类的耗电，这个CPU满载运行45分钟，充电宝里面就没电了
+
+而iPhone X使用ARM架构的CPU，功率则只有4.5瓦左右。
+
+CPU，也称作**超大规模集成电路（Very-Large-Scale Integration，VLSI** 
+
+由一个个晶体管组成
+
+CPU的计算过程，其实就是让晶体管里面的“开关”不断“打开”/“关闭”，组合完成各种运算和功能。
+
+要想算得快
+
+- 增加密度
+在CPU同样的面积，多放晶体管
+- 提升主频
+让晶体管“打开”/“关闭”得快点
+
+这两者，都会增加功耗，带来耗电和散热的问题!!!
+
+可以把CPU想象成一个工厂，有很多工人
+
+就如CPU上面的晶体管，互相之间协同工作。
+
+为了工作快点完成，在工厂里多塞一点人
+
+你可能会问，为什么不把工厂造得大点？
+
+这是因为，人和人之间如果离得远了，互相之间走过去需要花的时间就会变长，这也会导致性能下降!
+
+这就如如果CPU的面积大，晶体管之间的距离变大，电信号传输的时间就会变长，运算速度自然就慢了。
+
+除了多塞一点人，还希望每个人动作快点，同样时间就可多干活了
+
+这就相当于提升CPU主频，但是动作快，每个人就要出汗散热
+
+要是太热了，对工厂里面的人来说会休克，对CPU来说就会崩溃出错。
+
+我们会在CPU上面抹硅脂、装风扇，乃至用上水冷或者其他更好的散热设备
+
+就好像在工厂里面装风扇、空调，发冷饮一样
+
+但是同样的空间下，装上风扇空调能够带来的散热效果也是有极限的
+
+因此，在CPU里面，能够放下的晶体管数量和晶体管的“开关”频率也都是有限的。
+
+一个CPU的功率，可以用这样一个公式来表示：
+
+```
+功耗  ≈ 1/2 ×负载电容 × 电压的平方 × 开关频率 × 晶体管数量
+```
+
+为了提升性能，要不断地增加晶体管数量
+
+同样的面积下，想要多放一点晶体管，就要把晶体管造得小一点
+
+这个就是平时我们所说的提升“制程”
+
+从28nm到7nm，相当于晶体管本身变成了原来的1/4大小
+
+这个就相当于我们在工厂里，同样的活儿，我们要找瘦小一点的工人，这样一个工厂里面就可以多一些人
+
+我们还要提升主频，让开关的频率变快，也就是要找手脚更快的工人
+
+![](https://ask.qcloudimg.com/http-save/1752328/4r5uwxufnv.png)
+
+但功耗增加过多，CPU散热就跟不上
+
+这时就需要降低电压
+
+这里有一点非常关键，在整个功耗的公式里面，功耗和电压的平方是成正比的
+
+这意味着电压下降到原来的1/5，整个的功耗会变成原来的1/25。
+
+事实上，从5MHz主频的8086到5GHz主频的Intel i9，CPU的电压已经从5V左右下降到了1V左右
+
+这也是为什么我们CPU的主频提升了1000倍，但是功耗只增长了40倍
+
+# 2 并行优化 - 阿姆达尔定律
+
+虽然制程的优化和电压的下降，在过去的20年里，让CPU性能有所提升
+
+但是从上世纪九十年代到本世纪初，软件工程师们所用的“面向摩尔定律编程”的套路越来越用不下去了
+
+“写程序不考虑性能，等明年CPU性能提升一倍，到时候性能自然就不成问题了”，这种想法已经不可行了。
+
+于是，从奔腾4开始，Intel意识到通过提升主频比较“难”去实现性能提升
+
+开始推出Core Duo这样的多核CPU，通过提升“吞吐率”而不是“响应时间”，来达到目的。
+
+提升响应时间，就好比提升你用的交通工具的速度
+
+原本你是开汽车，现在变成了高铁乃至飞机
+
+但是，在此之上，再想要提升速度就不太容易了
+
+CPU在奔腾4的年代，就好比已经到了飞机这个速度极限
+
+那你可能要问了，接下来该怎么办呢？
+
+相比于给飞机提速，工程师们又想到了新的办法，可以一次同时开2架、4架乃至8架飞机，这就好像我们现在用的2核、4核，乃至8核的CPU。
+
+虽然从上海到北京的时间没有变，但是一次飞8架飞机能够运的东西自然就变多了，也就是所谓的“吞吐率”变大了。所以，不管你有没有需要，现在CPU的性能就是提升了2倍乃至8倍、16倍。
+
+这也是一个最常见的提升性能的方式，**通过并行提高性能**。
+
+这个思想在很多地方都可以使用
+
+举个例子，我们做机器学习程序的时候，需要计算向量的点积，比如向量
+
+```
+$W = [W_0, W_1, W_2, …, W_{15}]$
+```
+
+和向量
+
+```
+$X = [X_0, X_1, X_2, …, X_{15}]$
+```
+
+```
+$W·X = W_0 * X_0 + W_1 * X_1 +$
+```
+
+```
+$W_2 * X_2 + … + W_{15} * X_{15}$
+```
+
+这些式子由16个乘法和1个连加组成。如果你自己一个人用笔来算的话，需要一步一步算16次乘法和15次加法。
+
+如果这个时候我们把这个人物分配给4个人，同时去算$W_0～W\_3$, $W\_4～W\_7$, $W\_8～W_{11}$, $W_{12}～W_{15}$这样四个部分的结果，再由一个人进行汇总，需要的时间就会缩短。
+
+![](https://ask.qcloudimg.com/http-save/1752328/z6nx9lsihq.png)
+
+但并不是所有问题，都可以通过并行提高性能来解决
+
+要使用这种思想，需要满足以下条件:
+
+- 需要进行的计算，本身可以分解成几个可以并行的任务
+好比上面的乘法和加法计算，几个人可以同时进行，不会影响最后的结果。
+- 需要能够分解好问题，并确保几个人的结果能够汇总到一起
+- 在“汇总”这个阶段，是没有办法并行进行的，还是得顺序执行，一步一步来。
+
+这就引出了性能优化中一个经验定律
+
+- 阿姆达尔定律（Amdahl’s Law）
+对于一个程序进行优化之后，处理器并行运算之后效率提升的情况
+
+具体可以用这样一个公式来表示：
+
+```
+优化后的执行时间 = 受优化影响的执行时间/加速倍数+不受影响的执行时间
+```
+
+在刚刚的向量点积例子里，4个人同时计算向量的一小段点积，就是通过并行提高了这部分的计算性能
+
+但是，这4个人的计算结果，最终还是要在一个人那里进行汇总相加
+
+这部分汇总相加的时间，是不能通过并行来优化的，也就是上面的公式里面**不受影响的执行时间**部分
+
+比如上面的各个向量的一小段
+
+- 点积，需要100ns
+- 加法需要20ns
+
+总共需要120ns。这里通过并行4个CPU有了4倍的加速度。那么最终优化后，就有了100/4+20=45ns
+
+即使我们增加更多的并行度来提供加速倍数，比如有100个CPU，整个时间也需要100/100+20=21ns。
+
+![](https://ask.qcloudimg.com/http-save/1752328/m00nhlporg.png)
+
+# 3 总结
+
+无论是简单地通过提升主频，还是增加更多的CPU核心数量，通过并行提升性能，都会遇到相应的瓶颈
+
+仅靠简单地通过“堆硬件”的方式，在今天已经不能很好地满足我们对于程序性能的期望了。
+
+于是，工程师们需要从其他方面开始下功夫了。
+
+在“摩尔定律”和“并行计算”之外，在整个计算机组成层面，还有这样几个原则性的性能提升方法。
+
+## 3.1 加速大概率事件
+
+深度学习，整个计算过程中，99%都是向量和矩阵计算
+
+于是，工程师们通过用GPU替代CPU，大幅度提升了深度学习的模型训练过程
+
+本来一个CPU需要跑几小时甚至几天的程序，GPU只需要几分钟就好了
+
+Google更是不满足于GPU的性能，进一步地推出了TPU
+
+> 通常我们使用 O 表示一个算法的好坏，我们优化一个算法也是基于 big-O
+> 但是 big-O 其实是一个近似值，就好比一个算法时间复杂度是 O(n^2) + O(n)
+> 这里的 O(n^2) 是占大比重的，特别是当 n 很大的时候，通常我们会忽略掉 O(n)，着手优化 O(n^2) 的部分
+
+## 3.2 通过流水线提高性能
+
+现代的工厂里的生产线叫“流水线”。
+
+我们可以把装配iPhone这样的任务拆分成一个个细分的任务，让每个人都只需要处理一道工序，最大化整个工厂的生产效率。
+
+我们的CPU其实就是一个“运算工厂”
+
+我们把CPU指令执行的过程进行拆分，细化运行，也是现代CPU在主频没有办法提升那么多的情况下，性能仍然可以得到提升的重要原因之一
+
+## 3.3 通过预测提高性能
+
+预测下一步该干什么，而不是等上一步运行结果，提前进行运算，也是让程序跑得更快一点的办法
+
+在一个循环访问数组的时候，凭经验，你也会猜到下一步我们会访问数组的下一项
+
+后面要讲的“分支和冒险”、“局部性原理”这些CPU和存储系统设计方法，其实都是在利用我们对于未来的“预测”，提前进行相应的操作，来提升我们的程序性能。
+
+> 深度优先搜索算法里面的 “剪枝策略”，防止没有必要的分支搜索，这会大幅度提升算法效率
+
+- 整个组成乃至体系结构，都是基于冯·诺依曼架构组成的软硬件一体的解决方案
+- 这里面的方方面面的设计和考虑，除了体系结构层面的抽象和通用性之外，核心需要考虑的是“性能”问题
+
+# 参考
+
+深入浅出计算机组成原理
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\271\235\357\274\211- \345\212\250\346\200\201\351\223\276\346\216\245.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\271\235\357\274\211- \345\212\250\346\200\201\351\223\276\346\216\245.md"
similarity index 100%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\271\235\357\274\211- \345\212\250\346\200\201\351\223\276\346\216\245.md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\271\235\357\274\211- \345\212\250\346\200\201\351\223\276\346\216\245.md"
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\345\255\246\344\271\240\350\267\257\347\272\277.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\272\214\357\274\211- \345\210\266\345\256\232\345\255\246\344\271\240\350\267\257\347\272\277\357\274\214\346\224\200\347\231\273\342\200\234\346\200\247\350\203\275\342\200\235\344\271\213\345\267\205.md"
similarity index 81%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\345\255\246\344\271\240\350\267\257\347\272\277.md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\272\214\357\274\211- \345\210\266\345\256\232\345\255\246\344\271\240\350\267\257\347\272\277\357\274\214\346\224\200\347\231\273\342\200\234\346\200\247\350\203\275\342\200\235\344\271\213\345\267\205.md"
index 0e977b1fb7..c01aaacb32 100644
--- "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\345\255\246\344\271\240\350\267\257\347\272\277.md"
+++ "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\272\214\357\274\211- \345\210\266\345\256\232\345\255\246\344\271\240\350\267\257\347\272\277\357\274\214\346\224\200\347\231\273\342\200\234\346\200\247\350\203\275\342\200\235\344\271\213\345\267\205.md"	
@@ -1,8 +1,16 @@
-计算机组成原理，Computer Organization。Organization 意"组织机构"。
+# 0 学习路线的知识点概括
+
+![](https://ask.qcloudimg.com/http-save/1752328/z6jdpea3cv.png)
+
+**学习计算机组成原理，就是学习计算机是如何协调运行的**
+
+- 计算机组成原理的英文叫Computer Organization![](https://ask.qcloudimg.com/http-save/1752328/o6fo6b97eb.png)
+
+Organization 意"组织机构"。
 
 该组织机构能够进行各种计算、控制、读取输入，进行输出，达成各种强大的功能。
 
-计算机组成原理知识点大致可分为：
+把整个计算机组成原理的知识点拆分成了四大部分
 
 - 计算机的基本组成
 - 计算机的指令和计算
@@ -51,6 +59,16 @@ CPU时钟可以用来构造寄存器和内存的锁存器和触发器，因此
 
 对于存储器，我们不仅需要它们能够正常工作，还要确保里面的数据不能丢失。于是你要掌握我们是如何通过RAID、Erasure Code、ECC以及分布式HDFS，这些不同的技术，来确保数据的完整性和访问性能。
 
+## 计算机组成原理的学习办法
+
+相较于整个计算机科学中的其他科目，计算机组成原理更像是整个计算机学科里的“纲要”。这门课里任何一个知识点深入挖下去，都可以变成计算机科学里的一门核心课程。
+
+- 程序怎样从高级代码变成指令在计算机里面运行，对应着“编译原理”和“操作系统”这两门课程
+- 计算实现背后则是“数字电路”
+- 如果要深入CPU和存储器系统的优化，必然要深入了解“计算机体系结构”。
+
+为了更快更好地学计算机组成，总结了三个学习方法
+
 ### 学会提问自己来串联知识点
 
 学完一个知识点之后，你可以从下面两个方面，问一下自己。
@@ -60,8 +78,21 @@ CPU时钟可以用来构造寄存器和内存的锁存器和触发器，因此
 
 无论是程序的编译、链接、装载和执行，以及计算时需要用到的逻辑电路、ALU，乃至CPU自发为你做的流水线、指令级并行和分支预测，还有对应访问到的硬盘、内存，以及加载到高速缓存中的数据，这些都对应着我们学习中的一个个知识点。建议你自己脑子里过一遍，最好时口头表述一遍或者写下来，这样对你彻底掌握这些知识点都会非常有帮助。
 
+### 写一些示例程序来验证知识点
+
+计算机科学是一门实践的学科。计算机组成中的大量原理和设计，都对应着“性能”这个词。因此，通过把对应的知识点，变成一个个性能对比的示例代码程序记录下来，是把这些知识点融汇贯通的好方法。因为，相比于强记硬背知识点，一个有着明确性能对比的示例程序，会在你脑海里留下更深刻的印象。当你想要回顾这些知识点的时候，一个程序也更容易提示你把它从脑海深处里面找出来。
+
+### 通过和计算机硬件发展的历史做对照
+
+计算机的发展并不是一蹴而就的。从第一台电子计算机ENIAC（Electronic Numerical Integrator And Computer，电子数值积分计算机）的发明到现在，已经有70多年了。现代计算机用的各个技术，都是跟随实际应用中遇到的挑战，一个个发明、打磨，最后保留下来的。这当中不仅仅有学术层面的碰撞，更有大量商业层面的交锋。通过了解充满戏剧性和故事性的计算机硬件发展史，让你更容易理解计算机组成中各种原理的由来。
+
+比如说，奔腾4和SPARC的失败，以及ARM的成功，能让我们记住CPU指令集的繁与简、权衡性能和功耗的重要性，而现今高速发展的机器学习和边缘计算，又给计算机硬件设计带来了新的挑战。
 
-# 书籍
+## 学习资料推荐
+
+最有效的办法还是“读书百遍，其义自见”。对于不够明白的知识点，多搜索，多看不同来源的资料，多和朋友、同事、老师一起交流，一定能够帮你掌握好想要学习的知识点。
+
+### 入门书籍
 
 - [《计算机是怎样跑起来的》](https://book.douban.com/subject/26397183/)
 ![](https://ask.qcloudimg.com/http-save/1752328/67jwgpbwqj.png)
@@ -96,6 +127,13 @@ CPU时钟可以用来构造寄存器和内存的锁存器和触发器，因此
 理解计算机硬件和操作系统层面代码执行的优秀阅读材料
 ![](https://ask.qcloudimg.com/http-save/1752328/jpv8cnyhw9.png)
 
+## 路线学习小结
+
+学习不是死记硬背，学习材料也不是越多越好。
+
+最有效的办法，不是短时间冲刺，而是有节奏地坚持，多在留言区和其他朋友一起交流，就更容易能够“积小步而至千里”，在程序员这个职业上有更长足的发展。
+
+# 欢迎重学计算机组成原理 ! ! !
 
 ![](https://ask.qcloudimg.com/http-save/1752328/j0mroi5yoh.png)
 
@@ -321,7 +359,24 @@ CPI相当于你在写程序的时候，熟悉各种快捷键，越是打同样
 
 如果三者皆能实现，你自然可以很快地写出一个优秀的程序，“性能”从外面来看就是好的。
 
-参考
+# 3 总结
+
+学完本文，对“性能”这个名词，你应该有了更清晰的认识。
+
+主要对于“响应时间”这个性能指标进行抽丝剥茧，拆解成了计算机时钟周期、CPI以及指令数这三个独立的指标的乘积，并且为指明了优化计算机性能的三条康庄大道。
+
+也就是，提升计算机主频，优化CPU设计使得在单个时钟周期内能够执行更多指令，以及通过编译器来减少需要的指令数。
+
+后面会讲解，具体怎么在电路硬件、CPU设计，乃至指令设计层面，提升计算机的性能。
+
+# 参考
+
 - 豆瓣读书 - 计算机专题
 - 深入浅出计算机组成原理
-- [What do 'real', 'user' and 'sys' mean in the output of time(1)?](https://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1)
\ No newline at end of file
+- [What do 'real', 'user' and 'sys' mean in the output of time(1)?](https://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1)
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\272\224\357\274\211- \"\346\227\213\350\275\254\350\267\263\350\267\203\"\347\232\204\346\214\207\344\273\244\345\256\236\347\216\260.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\272\224\357\274\211- \"\346\227\213\350\275\254\350\267\263\350\267\203\"\347\232\204\346\214\207\344\273\244\345\256\236\347\216\260.md"
similarity index 100%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\272\224\357\274\211- \"\346\227\213\350\275\254\350\267\263\350\267\203\"\347\232\204\346\214\207\344\273\244\345\256\236\347\216\260.md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\344\272\224\357\274\211- \"\346\227\213\350\275\254\350\267\263\350\267\203\"\347\232\204\346\214\207\344\273\244\345\256\236\347\216\260.md"
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\205\253\357\274\211- \347\250\213\345\272\217\347\232\204\350\243\205\350\275\275.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\205\253\357\274\211- \347\250\213\345\272\217\347\232\204\350\243\205\350\275\275.md"
similarity index 100%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\205\253\357\274\211- \347\250\213\345\272\217\347\232\204\350\243\205\350\275\275.md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\205\253\357\274\211- \347\250\213\345\272\217\347\232\204\350\243\205\350\275\275.md"
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\205\255\357\274\211- \345\207\275\346\225\260\350\260\203\347\224\250\346\200\216\344\271\210\347\252\201\347\204\266Stack Overflow\344\272\206!.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\205\255\357\274\211- \345\207\275\346\225\260\350\260\203\347\224\250\346\200\216\344\271\210\347\252\201\347\204\266Stack Overflow\344\272\206!.md"
similarity index 100%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\205\255\357\274\211- \345\207\275\346\225\260\350\260\203\347\224\250\346\200\216\344\271\210\347\252\201\347\204\266Stack Overflow\344\272\206!.md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\205\255\357\274\211- \345\207\275\346\225\260\350\260\203\347\224\250\346\200\216\344\271\210\347\252\201\347\204\266Stack Overflow\344\272\206!.md"
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\215\201\344\270\200\357\274\211- \351\227\250\347\224\265\350\267\257\347\232\204\"\345\215\203\351\207\214\344\274\240\351\237\263\".md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\215\201\344\270\200\357\274\211- \351\227\250\347\224\265\350\267\257\347\232\204\"\345\215\203\351\207\214\344\274\240\351\237\263\".md"
similarity index 100%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\215\201\344\270\200\357\274\211- \351\227\250\347\224\265\350\267\257\347\232\204\"\345\215\203\351\207\214\344\274\240\351\237\263\".md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\215\201\344\270\200\357\274\211- \351\227\250\347\224\265\350\267\257\347\232\204\"\345\215\203\351\207\214\344\274\240\351\237\263\".md"
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\215\201\357\274\211- \"\347\203\253\347\203\253\347\203\253\"\344\271\261\347\240\201\347\232\204\347\224\261\346\235\245.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\215\201\357\274\211- \"\347\203\253\347\203\253\347\203\253\"\344\271\261\347\240\201\347\232\204\347\224\261\346\235\245.md"
similarity index 100%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\215\201\357\274\211- \"\347\203\253\347\203\253\347\203\253\"\344\271\261\347\240\201\347\232\204\347\224\261\346\235\245.md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\215\201\357\274\211- \"\347\203\253\347\203\253\347\203\253\"\344\271\261\347\240\201\347\232\204\347\224\261\346\235\245.md"
diff --git "a/\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\233\233\357\274\211- \347\216\251\347\216\251\347\272\270\345\270\246\347\274\226\347\250\213.md" "b/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\233\233\357\274\211- \347\216\251\347\216\251\347\272\270\345\270\246\347\274\226\347\250\213.md"
similarity index 100%
rename from "\346\223\215\344\275\234\347\263\273\347\273\237/\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\233\233\357\274\211- \347\216\251\347\216\251\347\272\270\345\270\246\347\274\226\347\250\213.md"
rename to "\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\273\204\346\210\220\345\216\237\347\220\206\357\274\210\345\233\233\357\274\211- \347\216\251\347\216\251\347\272\270\345\270\246\347\274\226\347\250\213.md"
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Cookie\343\200\201Session\346\234\272\345\210\266.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Cookie\343\200\201Session\346\234\272\345\210\266.md"
deleted file mode 100644
index 6a3d4592cb..0000000000
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Cookie\343\200\201Session\346\234\272\345\210\266.md"
+++ /dev/null
@@ -1,324 +0,0 @@
-会话（Session）跟踪是Web程序中常用的技术，用来**跟踪用户的整个会话**
-常用的会话跟踪技术是Cookie与Session。
-**Cookie通过在客户端记录信息确定用户身份**
-**Session通过在服务器端记录信息确定用户身份**
-
-本文将系统地讲述Cookie与Session机制，并比较说明什么时候不能用Cookie，什么时候不能用Session。
-
-# **1 Cookie机制**
-理论上，**一个用户的所有请求操作都应该属于同一个会话**，而另一个用户的所有请求操作则应该属于另一个会话，二者不能混淆
-例如，用户A在超市购买的任何商品都应该放在A的购物车内，不论是用户A什么时间购买的，这都是属于同一个会话的，不能放入用户B或用户C的购物车内，这不属于同一个会话。
-
-而Web应用程序是使用HTTP协议传输数据的
-**HTTP协议是无状态的协议。一旦数据交换完毕，客户端与服务器端的连接就会关闭，再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话**
-即用户A购买了一件商品放入购物车内，当再次购买商品时服务器已经无法判断该购买行为是属于用户A的会话还是用户B的会话了。`要跟踪该会话，必须引入一种机制。`
-
-Cookie就是这样的一种机制。它可以弥补HTTP协议无状态的不足。在Session出现之前，基本上所有的网站都采用Cookie来跟踪会话。
-
-## **1.1. 什么是Cookie**
-由于HTTP是一种无状态的协议，服务器单从网络连接上无从知道客户身份。怎么办呢？
-**给客户端们颁发一个通行证吧，每人一个，无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理**
-
-Cookie实际上是一小段的文本信息。客户端请求服务器，如果服务器需要记录该用户状态，就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时，浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie，以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
-
-
-查看某个网站颁发的Cookie很简单。在浏览器地址栏输入`javascript:alert (document. cookie)`就可以了（需要有网才能查看）。JavaScript脚本会弹出一个对话框显示本网站颁发的所有Cookie的内容
-![](https://img-blog.csdnimg.cn/img_convert/c9bdbef1b99c7e7e41354fbfc9aa3e86.png)
-其中第一行`BAIDUID`记录的就是笔者的身份，只是Baidu使用特殊的方法将Cookie信息加密了
-
-如果浏览器不支持Cookie（如大部分手机中的浏览器）或者把Cookie禁用了，Cookie功能就会失效。
-
-不同的浏览器采用不同的方式保存Cookie
-IE浏览器会在“C:\Documents and Settings\你的用户名\Cookies”文件夹下以文本文件形式保存，一个文本文件保存一个Cookie
-## **1.2  记录用户访问次数**
-Java中把Cookie封装成了`javax.servlet.http.Cookie`类
-![](https://img-blog.csdnimg.cn/img_convert/8c2990a324dd323f20348303c1c4d8c2.png)
-每个Cookie都是该Cookie类的对象。服务器通过操作Cookie类对象对客户端Cookie进行操作。
-通过`request.getCookies()`
-获取客户端提交的所有Cookie（以Cookie[]数组形式返回）
-![](https://img-blog.csdnimg.cn/img_convert/52acee6c638b06fd564b482f7a625460.png)
-通过`response.addCookie(Cookiecookie)`
-向客户端设置Cookie
-
-Cookie对象使用key-value属性对的形式保存用户状态，一个Cookie对象保存一个属性对，一个request/response同时使用多个Cookie。因为Cookie类位于包javax.servlet.http.<sub>*</sub>下面，所以JSP中不需要import该类。
-
-### 1.1.3  Cookie的不可跨域名性
-- 很多网站都会使用Cookie，Google会向客户端颁发Cookie，Baidu也会向客户端颁发Cookie。那浏览器访问Google会不会也带上Baidu颁发的Cookie？
-不会！
-
-**Cookie具有不可跨域名性**。根据Cookie规范，浏览器访问Google只会携带Google的Cookie，而不会携带Baidu的Cookie。Google也只能操作Google的Cookie，而不能操作Baidu的Cookie。
-
-Cookie在客户端由浏览器管理。浏览器会保证Google只会操作Google的Cookie而不会操作Baidu的Cookie，从而保证用户隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google与Baidu的域名不一样，因此Google不能操作Baidu的Cookie。
-
-虽然网站images.google.com与网站www.google.com同属Google，但域名不同，二者同样不能互相操作彼此Cookie。
-
-用户登录网站www.google.com之后会发现访问images.google.com时登录信息仍然有效，而普通的Cookie是做不到的。这是因为Google做了特殊处理！
-
-**1.1.4  Unicode编码：保存中文**
-
-中文与英文字符不同，**中文属于Unicode字符，在内存中占4个字符，而英文属于ASCII字符，内存中只占2个字节**。Cookie中使用Unicode字符时需要对Unicode字符进行编码，否则会乱码。
-
-提示：Cookie中保存中文只能编码。一般使用UTF-8编码即可。不推荐使用GBK等中文编码，因为浏览器不一定支持，而且JavaScript也不支持GBK编码。
-
-**1.1.5  BASE64编码：保存二进制图片**
-
-Cookie不仅可以使用ASCII字符与Unicode字符，还可以使用二进制数据。例如在Cookie中使用数字证书，提供安全度。使用二进制数据时也需要进行编码。
-
-注意：由于浏览器每次请求服务器都会携带Cookie，因此Cookie内容不宜过多，否则影响速度。Cookie的内容应该少而精。
-
-**1.1.6  设置Cookie的所有属性**
-
-除了name与value之外，Cookie还具有其他几个常用的属性。每个属性对应一个getter方法与一个setter方法。Cookie类的所有属性如表1.1所示。
-
-![image.png](https://img-blog.csdnimg.cn/img_convert/bb36276019a1961db27dfbf4b49f9c32.png)
-
-
-**1.1.7  Cookie的有效期**
-Cookie的maxAge决定着Cookie的有效期，单位为秒（Second）。Cookie中通过getMaxAge()方法与setMaxAge(int maxAge)方法来读写maxAge属性。
-
-如果maxAge属性为正数，则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化，即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑，只要还在maxAge秒之前，登录网站时该Cookie仍然有效。下面代码中的Cookie信息将永远有效。
-```java
-Cookie cookie = new Cookie("username","helloweenvsfei"); // 新建Cookie
-
-cookie.setMaxAge(Integer.MAX_VALUE); // 设置生命周期为MAX_VALUE
-
-response.addCookie(cookie); // 输出到客户端
-```
-如果maxAge为负数，则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效，关闭窗口后该Cookie即失效。
-maxAge为负数的Cookie，为临时性Cookie，不会被持久化，不会被写到Cookie文件中。Cookie信息保存在浏览器内存中，因此关闭浏览器该Cookie就消失了。Cookie默认的maxAge值为–1。
-
-如果maxAge为0，则表示删除该Cookie。Cookie机制没有提供删除Cookie的方法，因此通过设置该Cookie即时失效实现删除Cookie的效果。失效的Cookie会被浏览器从Cookie文件或者内存中删除，
-
-例如：
-
-```java
-Cookie cookie = new Cookie("username","helloweenvsfei"); // 新建Cookie
-
-cookie.setMaxAge(0); // 设置生命周期为0，不能为负数
-
-response.addCookie(cookie); // 必须执行这一句
-```
-
-response对象提供的Cookie操作方法只有一个添加操作add(Cookie cookie)。
-
-要想修改Cookie只能使用一个同名的Cookie来覆盖原来的Cookie，达到修改的目的。删除时只需要把maxAge修改为0即可。
-
-注意：从客户端读取Cookie时，包括maxAge在内的其他属性都是不可读的，也不会被提交。浏览器提交Cookie时只会提交name与value属性。maxAge属性只被浏览器用来判断Cookie是否过期。
-
-**1.1.8  Cookie的修改、删除**
-- Cookie并不提供修改、删除操作。如果要修改某个Cookie，只需要新建一个同名的Cookie，添加到response中覆盖原来的Cookie。
-- 如果要删除某个Cookie，只需要新建一个同名的Cookie，并将maxAge设置为0，并添加到response中覆盖原来的Cookie。注意是0而不是负数。负数代表其他的意义。读者可以通过上例的程序进行验证，设置不同的属性。
-- 注意：修改、删除Cookie时，新建的Cookie除value、maxAge之外的所有属性，例如name、path、domain等，都要与原Cookie完全一样。否则，浏览器将视为两个不同的Cookie不予覆盖，导致修改、删除失败。
-
-### 1.1.9  Cookie的域名
-Cookie是不可跨域名的。域名www.google.com颁发的Cookie不会被提交到域名www.baidu.com去。这是由Cookie的隐私安全机制（能够禁止网站非法获取其他网站的Cookie）决定的。
-
-正常情况下，同一个一级域名下的两个二级域名如：
-- www.java.com
-- images.java.com
-
-也不能交互使用Cookie，因为二者的域名并不严格相同。
-
-跨域z则是我们访问两个不同的域名或路径时，希望带上同一个cookie，跨域的具体实现方式有很多。如果想所有java.com名下的二级域名都可以使用该Cookie，可以设置Cookie的domain参数，表示浏览器访问这个域名时才带上这个cookie。
-```java
-Cookie cookie = new Cookie("time","20080808");
-cookie.setDomain(".java.com");
-cookie.setPath("/");
-cookie.setMaxAge(Integer.MAX_VALUE);
-response.addCookie(cookie);
-```
-读者可以修改本机hosts文件配置多个临时域名，然后使用程序设置跨域名Cookie验证domain属性。
-
-domain参数必须以点(".")开始。另外，name相同但domain不同的两个Cookie是两个不同的Cookie。如果想要两个域名完全不同的网站共有Cookie，可以生成两个Cookie，domain属性分别为两个域名，输出到客户端。
-
-### 1.1.10  Cookie的路径
-- domain属性
-决定运行访问Cookie的域名
-- path属性
-表示访问的URL是这个path或者子路径时才带上这个cookie，决定允许访问Cookie的路径（ContextPath）
-
-例如，如果只允许/session/下的程序使用Cookie，可以这么写：
-```java
-Cookie cookie = new Cookie("time","20080808");
-cookie.setPath("/session/");
-response.addCookie(cookie);
-```
-设置为“/”时允许所有路径使用Cookie。path属性需要使用符号“/”结尾。name相同但domain同同的两个Cookie也是不同的
-
-注意：页面只能获取它属于的Path的Cookie。例如/session/test/a.jsp不能获取到路径为/session/abc/的Cookie。使用时一定要注意。
-
-**1.1.11  Cookie的安全属性**
-HTTP协议不仅是无状态的，而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播，有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。
-如果不希望Cookie在HTTP等非安全协议中传输，可以设置Cookie的secure属性为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。下面的代码设置secure属性为true：
-```java
-Cookie cookie = new Cookie("time", "20080808");
-cookie.setSecure(true);
-response.addCookie(cookie);
-```
-提示：secure属性并不能对Cookie内容加密，不能保证绝对的安全性。
-如果需要高安全性，**需要在程序中对Cookie内容加密
-
-**1.1.12  JavaScript操作Cookie**
-Cookie是保存在浏览器端的，因此浏览器具有操作Cookie的先决条件。
-浏览器可以使用脚本程序如JS或者VBScript等操作Cookie。
-这里以JavaScript为例介绍常用的Cookie操作。
-例如下面的代码会输出本页面所有的Cookie。
-`<script>document.write(document.cookie);</script>`
-由于JavaScript能够任意地读写Cookie，有些好事者便想使用JavaScript程序去窥探用户在其他网站的Cookie。不过这是徒劳的，W3C组织早就意识到JavaScript对Cookie的读写所带来的安全隐患并加以防备了，W3C标准的浏览器会阻止JavaScript读写任何不属于自己网站的Cookie。换句话说，A网站的JavaScript程序读写B网站的Cookie不会有任何结果。
-
-**1.1.13  案例：永久登录**
-如果用户是在自己家的电脑上网，登录时就可以记住他的登录信息，下次访问时不需要再次登录，直接访问即可。
-实现方法是**把登录信息如账号、密码等保存在Cookie中，并控制Cookie的有效期，下次访问时再验证Cookie中的登录信息即可。**
-
-保存登录信息有多种方案
-- 最直接的是把用户名与密码都保持到Cookie中，下次访问时检查Cookie中的用户名与密码，与数据库比较。这是**一种比较危险的选择，一般不把密码等重要信息保存到Cookie中**。
-
-- 还有**一种方案是把密码加密后保存到Cookie中，下次访问时解密并与数据库比较**。这种方案略微安全一些。如果不希望保存密码，还可以把登录的时间戳保存到Cookie与数据库中，到时只验证用户名与登录时间戳就可以了。
-这几种方案验证账号时都要查询数据库。
-
-- 本例将采用另一种方案，只在登录时查询一次数据库，以后访问验证登录信息时不再查询数据库。实现方式是
-**把账号按照一定的规则加密后，连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可**。
-本例把账号保存到名为account的Cookie中，把账号连同密钥用MD5算法加密后保存到名为ssid的Cookie中。验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。
-
-登录时可以选择登录信息的有效期：关闭浏览器即失效、30天内有效与永久有效。通过设置Cookie的age属性来实现，注意观察代码
-
-提示：该加密机制中最重要的部分为算法与密钥。由于MD5算法的不可逆性，即使用户知道了账号与加密后的字符串，也不可能解密得到密钥。因此，只要保管好密钥与算法，该机制就是安全的。
-
-**1.2  Session机制**
-Web应用程序中还经常使用Session来记录客户端状态。**Session是服务器端使用的一种记录客户端状态的机制**，使用上比Cookie简单一些，相应的也**增加了服务器的存储压力**。
-
-**1.2.1  什么是Session**
-Session是另一种记录客户状态的机制，不同的是Cookie保存在客户端浏览器中，而Session保存在服务器上。
-客户端浏览器访问服务器的时候，服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
-
-如果说**Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话，那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案，客户来访的时候只需要查询客户档案表就可以了**
-
-**1.2.2  实现用户登录**
-Session对应的类为javax.servlet.http.HttpSession类。
-每个来访者对应一个Session对象，所有该客户的状态信息都保存在这个Session对象里。
-**Session对象是在客户端第一次请求服务器的时候创建的**
-Session也是一种key-value的属性对，通过getAttribute(Stringkey)和setAttribute(String key，Objectvalue)方法读写客户状态信息
-Servlet里通过request.getSession()方法获取该客户的Session
-
-request还可以使用getSession(boolean create)来获取Session。区别是如果该客户的Session不存在，request.getSession()方法会返回null，而getSession(true)会先创建Session再将Session返回。
-
-Servlet中必须使用request来编程式获取HttpSession对象，而JSP中内置了Session隐藏对象，可以直接使用。如果使用声明了<%@page session="false" %>，则Session隐藏对象不可用。
-
-
-当多个客户端执行程序时，服务器会保存多个客户端的Session。获取Session的时候也不需要声明获取谁的Session。**Session机制决定了当前客户只会获取到自己的Session，而不会获取到别人的Session。各客户的Session也彼此独立，互不可见**。
-
-提示**：Session的使用比Cookie方便，但是过多的Session存储在服务器内存中，会对服务器造成压力。**
-
-**1.2.3  Session的生命周期**
-Session保存在服务器端。**为了获得更高的存取速度，服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂，当大量客户访问服务器时可能会导致内存溢出。因此，Session里的信息应该尽量精简**
-
-**Session在用户第一次访问服务器的时候自动创建**。需要注意只有访问JSP、Servlet等程序时才会创建Session，只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session，也可以使用request.getSession(true)强制生成Session。
-
-**Session生成后，只要用户继续访问，服务器就会更新Session的最后访问时间，并维护该Session**。用户每访问服务器一次，无论是否读写Session，服务器都认为该用户的Session“活跃（active）”了一次。
-
-**1.2.4  Session的有效期**
-由于会有越来越多的用户访问服务器，因此Session也会越来越多。**为防止内存溢出，服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器，Session就自动失效了。**
-
-Session的超时时间为maxInactiveInterval属性，可以通过对应的getMaxInactiveInterval()获取，通过setMaxInactiveInterval(longinterval)修改。
-
-Session的超时时间也可以在web.xml中修改。另外，通过调用Session的invalidate()方法可以使Session失效。
-
-**1.2.5  Session的常用方法**
-
-Session中包括各种方法，使用起来要比Cookie方便得多。Session的常用方法如表1.2所示。
-
-![image.png](https://img-blog.csdnimg.cn/img_convert/b85d5e07803eade931b06a5b40d54f32.png)
-
-Tomcat中Session的默认超时时间为20分钟。通过setMaxInactiveInterval(int seconds)修改超时时间。可以修改web.xml改变Session的默认超时时间。例如修改为60分钟：
-
-<session-config>
-
-   <session-timeout>60</session-timeout>      <!-- 单位：分钟 -->
-
-</session-config>
-
- 
-
-注意：<session-timeout>参数的单位为分钟，而setMaxInactiveInterval(int s)单位为秒。
-**1.2.6  Session对浏览器的要求**
-
-虽然Session保存在服务器，对客户端是透明的，它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的，Session不能依据HTTP连接来判断是否为同一客户，因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie，它的值为该Session的id（也就是HttpSession.getId()的返回值）。Session依据该Cookie来识别是否为同一用户。
-
-该Cookie为服务器自动生成的，它的maxAge属性一般为–1，表示仅当前浏览器内有效，并且各浏览器窗口间不共享，关闭浏览器就会失效。
-
-因此同一机器的两个浏览器窗口访问服务器时，会生成两个不同的Session。但是由浏览器窗口内的链接、脚本等打开的新窗口（也就是说不是双击桌面浏览器图标等打开的窗口）除外。这类子窗口会共享父窗口的Cookie，因此会共享一个Session。
-
-注意：新开的浏览器窗口会生成新的Session，但子窗口除外。子窗口会共用父窗口的Session。例如，在链接上右击，在弹出的快捷菜单中选择“在新窗口中打开”时，子窗口便可以访问父窗口的Session。
-
-如果客户端浏览器将Cookie功能禁用，或者不支持Cookie怎么办？例如，绝大多数的手机浏览器都不支持Cookie。Java Web提供了另一种解决方案：URL地址重写。
-
-**1.2.7  URL地址重写**
-URL地址重写是对客户端不支持Cookie的解决方案。
-URL地址重写的原理是将该用户Session的id信息重写到URL地址中。
-服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie，也可以使用Session来记录用户状态。HttpServletResponse类提供了encodeURL(Stringurl)实现URL地址重写，例如：
-```java
-<td>
-        <a href="<%=response.encodeURL("index.jsp?c=1&wd=Java") %>"> 
-        Homepage</a>
-</td>
-```
-该方法会自动判断客户端是否支持Cookie。如果客户端支持Cookie，会将URL原封不动地输出来。如果客户端不支持Cookie，则会将用户Session的id重写到URL中。重写后的输出可能是这样的：
-```
-<td>
-    <ahref="index.jsp;jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E?c=
-    1&wd=Java">Homepage</a>
-</td>
-```
-即在文件名的后面，在URL参数的前面添加了字符串“;jsessionid=XXX”。其中XXX为Session的id。
-分析一下可以知道，增添的jsessionid字符串既不会影响请求的文件名，也不会影响提交的地址栏参数。用户单击这个链接的时候会把Session的id通过URL提交到服务器上，服务器通过解析URL地址获得Session的id。
-
-如果是页面重定向（Redirection），URL地址重写可以这样写：
-```jsp
-<%
-    if(“administrator”.equals(userName))
-
-    {
-
-       response.sendRedirect(response.encodeRedirectURL(“administrator.jsp”));
-
-        return;
-
-    }
-%>
-```
-效果跟response.encodeURL(String url)是一样的：如果客户端支持Cookie，生成原URL地址，如果不支持Cookie，传回重写后的带有jsessionid字符串的地址。
-
-对于WAP程序，由于大部分的手机浏览器都不支持Cookie，WAP程序都会采用URL地址重写来跟踪用户会话。比如用友集团的移动商街等。
-
-注意：TOMCAT判断客户端浏览器是否支持Cookie的依据是请求中是否含有Cookie。尽管客户端可能会支持Cookie，但是由于第一次请求时不会携带任何Cookie（因为并无任何Cookie可以携带），URL地址重写后的地址中仍然会带有jsessionid。当第二次访问时服务器已经在浏览器中写入Cookie了，因此URL地址重写后的地址中就不会带有jsessionid了。
-
-**1.2.8  Session中禁止使用Cookie**
-Java Web规范支持通过配置的方式禁用Cookie。下面举例说一下怎样通过配置禁止使用Cookie。
-
-打开项目sessionWeb的WebRoot目录下的META-INF文件夹（跟WEB-INF文件夹同级，如果没有则创建），打开context.xml（如果没有则创建），编辑内容如下：
-
-代码1.11 /META-INF/context.xml
-
-```xml
-<?xml version='1.0' encoding='UTF-8'?>
-
-<Context path="/sessionWeb"cookies="false">
-
-</Context>
-```
-
-或者修改Tomcat全局的conf/context.xml，修改内容如下：
-context.xml
-```xml
-<!-- The contents of this file will be loaded for eachweb application -->
-
-<Context cookies="false">
-
-    <!-- ... 中间代码略 -->
-
-</Context>
-```
-
-部署后TOMCAT便不会自动生成名JSESSIONID的Cookie，Session也不会以Cookie为识别标志，而仅仅以重写后的URL地址为识别标志了。
-
-注意：该配置只是禁止Session使用Cookie作为识别标志，并不能阻止其他的Cookie读写。也就是说服务器不会自动维护名为JSESSIONID的Cookie了，但是程序中仍然可以读写其他的Cookie。
\ No newline at end of file
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/GET \345\222\214 POST \347\232\204\345\214\272\345\210\253.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/GET \345\222\214 POST \347\232\204\345\214\272\345\210\253.md"
deleted file mode 100644
index a09757b3e7..0000000000
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/GET \345\222\214 POST \347\232\204\345\214\272\345\210\253.md"	
+++ /dev/null
@@ -1,43 +0,0 @@
-"标准"答案
-
->  GET使用URL或Cookie传参，POST则将数据放在body中
->  GET的URL会有长度上的限制，POST的数据可以非常大
->  POST比GET安全，因为数据在地址栏上不可见
->  
-
-这都是一些经典面试材料抄袭的"经典"的答案，没有一点权威意义,不一提，今天我们就从官方RFC文档一探究竟
-
-GET 和 POST 是由 HTTP 协议定义的
- 在HTTP协议中，Methods
-
-![](https://img-blog.csdnimg.cn/20210222095323729.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-和Header
-
-![](https://img-blog.csdnimg.cn/20210222095331905.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-
- 是不相干的两个概念，使用哪个Method与应用层的数据如何传输是没有关系的
- 
-![](https://img-blog.csdnimg.cn/20210222095341530.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-Methods 定义
-
->  译文 ： 请求方法token是请求语义的主要来源;
->  它表示客户端发出此请求的目的以及客户端对成功结果的期望。
->  如果这些附加语义与方法不冲突，请求方法的语义可能会进一步由某些头字段的
->  语义进一步专门化，如果存在于请求中（第5节）
->  method = token
->  
-
-HTTP 协议也没有要求，如果Method是POST数据就要放在BODY中
-
-![](https://img-blog.csdnimg.cn/20210222095353673.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
->  译文 ： POST 方法请求目标资源 根据资源自身的特定语义 处理请求中包含的表示
->  例如，POST 用于以下功能：
->  1.供数据块，例如输入HTML 表格的字段的数据处理过程;
->  2.在公告栏，新闻组，邮件列表，博客或类似的文章组中发布消息;
->  3.创建一个尚未被原服务器识别的新资源;
->  4.将数据附加到资源的现有表示中
\ No newline at end of file
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP\345\215\217\350\256\256\347\232\204\345\211\215\344\270\226\344\273\212\347\224\237.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP\345\215\217\350\256\256\347\232\204\345\211\215\344\270\226\344\273\212\347\224\237.md"
deleted file mode 100644
index 200e7ca617..0000000000
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP/HTTP\345\215\217\350\256\256\347\232\204\345\211\215\344\270\226\344\273\212\347\224\237.md"
+++ /dev/null
@@ -1,173 +0,0 @@
-Tomcat本身是一个“HTTP服务器 + Servlet容器”，想深入理解Tomcat工作原理，理解HTTP协议的工作原理是基础。
-
-# HTTP的本质
-HTTP协议是浏览器与服务器之间的数据传送协议。作为应用层协议，HTTP是基于TCP/IP协议来传递数据的（HTML文件、图片、查询结果等），HTTP协议不涉及数据包（Packet）传输，主要规定了客户端和服务器之间的通信格式。
-
-假如浏览器需要从远程HTTP服务器获取一个HTML文本，在这个过程中，浏览器实际上要做两件事情。
-- 与服务器建立Socket连接
-浏览器从地址栏获取用户输入的网址和端口，去连接远端的服务器，这样就能通信了。
-- 生成**请求数据**并通过Socket发送出去
-这个请求数据长什么样？请求什么内容？浏览器需告诉服务端什么？
-
-首先，你要让服务端知道你是想获取内容 or 提交内容
-然后，你需要告诉服务端你想要哪个内容。
-
-要把这些信息以何格式放入请求？这就是HTTP协议要解决的问题。
-即**HTTP协议本质是一种浏览器与服务器之间约定好的通信格式**。
-浏览器与服务器之间具体是怎么工作的？
-
-# HTTP工作原理
-- 一次HTTP请求过程
-![](https://img-blog.csdnimg.cn/20210714225935750.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-1. 用户通过浏览器操作，比如输入www.google.cn，并回车，于是浏览器获取该事件
-2. 浏览器向服务端发出TCP连接请求
-3. 服务程序接受浏览器的连接请求，并经过TCP三次握手建立连接
-4. 浏览器将请求数据打包成一个HTTP协议格式的数据包
-5. 浏览器将该数据包推入网络，数据包经过网络传输，最终达到端服务程序
-6. 服务端程序拿到这个数据包后，同样以HTTP协议格式解包，获取到客户端的意图
-7. 得知客户端意图后进行处理，比如提供静态文件或者调用服务端程序获得动态结果
-8. 服务器将响应结果（可能是HTML或者图片等）按照HTTP协议格式打包
-9. 服务器将响应数据包推入网络，数据包经过网络传输最终达到到浏览器
-10. 浏览器拿到数据包后，以HTTP协议的格式解包，然后解析数据，假设这里的数据是HTML
-11. 浏览器将HTML文件展示在页面上
-
-Tomcat作为一个HTTP服务器，在这个过程中主要
-- 接受连接
-- 解析请求数据
-- 处理请求
-- 发送响应
-
-# HTTP格式
-## 请求数据
-你有没有注意到，在浏览器和HTTP服务器之间通信的过程中，首先要将数据打包成HTTP协议的格式，那HTTP协议的数据包具体长什么样呢？
-### 组成
-HTTP请求数据由三部分组成，分别是请求行、请求报头、请求正文。
-比如一个简单的登录请求，浏览器会发如下HTTP请求：
-#### 请求行
-```bash
-GET /login?callBack=xxx HTTP/1.1
-```
-
-#### 请求报头
-```bash
-Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
-Accept-Encoding: gzip, deflate, br
-Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
-Cache-Control: max-age=0
-Connection: keep-alive
-Host: www.nowcoder.com
-User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
-```
-#### 请求正文
-```bash
-{"country":86, "password":"22"}
-```
-
-当这个HTTP请求数据到达Tomcat后，Tomcat会把HTTP请求数据字节流解析成一个Request对象，这个Request对象封装了HTTP所有的请求信息。
-接着Tomcat把这个Request对象交给Web应用去处理，处理完后得到一个Response对象，Tomcat会把这个Response对象转成HTTP格式的响应数据并发送给浏览器。
-
-## HTTP响应
-HTTP的响应也是由三部分组成：状态行、响应报头、报文主体。
-#### 状态行
-```bash
-HTTP/1.1 200 OK
-```
-#### 响应报头
-```bash
-Connection: keep-alive
-Content-Length: 0
-Content-Type: text/html; charset=UTF-8
-Date: Thu, 15 Jul 2021 02:37:13 GMT
-Server: openresty
-X-Backend-Response: 0.002
-```
-#### 报文主体
-```bash
-```
-
-# Cookie和Session
-## 无状态
-HTTP协议本身是无状态的，不同请求间协议内容无相关性，即本次请求与上次请求没有内容的依赖关系，本次响应也只针对本次请求的数据，至于服务器应用程序为用户保存的状态是属于应用层，与HTTP协议本身无关！
-
-无状态，就是为完成某一操作，请求里包含了所有信息，服务端无需保存请求的状态，即无需保存session，无session的好处是带来了服务端良好的可伸缩性，方便failover，请求被LB转到不同server实例也没有区别。这样看，正是REST架构风格，才有了HTTP的无状态特性。REST和HTTP1.1其实都出自同一人之手。
-
-http最初设计成无状态的是因为只是用来浏览静态文件的，无状态协议已经足够，也没什么负担。
-但随着web发展，它需要变得有状态，但是不是就要修改HTTP协议使之有状态？
-不需要：
-- 因为我们经常长时间逗留在某一个网页，然后才进入到另一个网页，如果在这两个页面之间维持状态，代价很高
-- 其次，历史让http无状态，但是现在对http提出了新的要求，按照软件领域的通常做法是，保留历史经验，在http协议上再加上一层实现我们的目的。所以引入cookie、session等机制来实现这种有状态的连接。
-
-在分布式场景下，为减少Session不一致，一般将Session存储到Redis，而非直接存储在server实例。
-
-现在的Web容器都支持将session存储在第三方中间件（如Redis）中，为什么大多都绕过容器，直接在应用中将会话数据存入中间件中？
-因为用Web容器的Session方案需要侵入特定的Web容器，用Spring Session可能比较简单，它使得程序员甚至感觉不到Servlet容器的存在，可以专心开发Web应用。它是通过Servlet规范中的Filter机制拦截了所有Servlet请求，偷梁换柱，将标准Servlet请求对象包装，换成自己的Request包装类对象，当程序员通过包装后的Request对象的getSession方法拿Session时，是通过Spring拿Session，和Web容器无关。
-
-
-有了 session 就够了吗？这还有一个问题：Web应用不知道你是谁。
-比如你登录淘宝，在购物车添加了三件商品，刷新一下网页，这时系统提示你仍然处未登录状态，购物车也空了！
-因此HTTP协议需要一种技术让请求与请求之间建立联系，服务器需要知道这个请求来自谁，于是出现了Cookie。
-
-## Cookie
-Cookie是HTTP报文的一个请求头，Web应用可以将用户的标识信息或者其他一些信息（用户名等）存储在Cookie。用户经过验证后，每次HTTP请求报文中都包含Cookie，这样服务器读取这个Cookie请求头就知道用户是谁了。
-Cookie本质上就是一份存储在用户本地的文件，里面包含了每次请求中都需要传递的信息。
-
-## Session
-由于Cookie以明文的方式存储在本地，而Cookie中往往带有用户信息，这样就造成了非常大的安全隐患，于是产生了Session。
-
-### 如何理解Session
-可理解为服务器端开辟的存储空间，里面保存了用户的状态，用户信息以Session的形式存储在服务端。当用户请求到来时，服务端可以把用户的请求和用户的Session对应起来。
-
-### Session如何对应请求
-通过Cookie，浏览器在Cookie中填个了类似sessionid的字段标识请求。
-
-### 工作过程
-- 服务端创建Session同时，为该Session生成唯一的sessionid
-- 通过set-cookie放在HTTP的响应头
-- 浏览器将sessionid写到cookie里
-- 当浏览器再次发送请求时，自动携带该sessionid
-- 服务器接受到请求后，根据sessionid找到相应Session
-- 找到Session后，即可在Session中获取或添加内容
-- 这些内容只会保存在服务器，发到客户端的只有sessionid，这样相对安全，也节省网络流量，无需在Cookie中存储大量用户信息
-
-### Session创建与存储
-在服务器端程序运行的过程中创建的，不同语言实现的应用程序有不同的创建Session的方法。
-
-在Java中，是Web应用程序在调用HttpServletRequest的getSession方法时，由Web容器（比如Tomcat）创建的。
-
-Tomcat的Session管理器提供了多种持久化方案来存储Session，通常会采用高性能的存储方式，比如Redis，并且通过集群部署的方式，防止单点故障，从而提升高可用。同时，Session有过期时间，因此Tomcat会开启后台线程定期的轮询，如果Session过期了就将Session失效。
-
-引入session是因为cookie存在客户端，有安全隐患；但是session id也是通过cookie由客户端发送到服务端，虽然敏感的用户信息没有在网络上传输了，但是攻击者拿到sessionid也可以冒充受害者发送请求，这就是为什么我们需要https，加密后攻击者就拿不到sessionid了，另外CSRF也是一种防止session劫持的方式。
-token比如jwt token，本质也就是个加密的cookie。
-
-- Cookie本质上就是一份存储在用户本地的文件，包含每次请求中都需要传递的信息
-
-# HTTP长连接
-HTTP协议和其他应用层协议一样，本质上是一种通信格式。类比现实生活：
-- HTTP是通信的方式
-HTTP是信封
-- HTML是通信的目的
-信封里面的信（HTML）才是内容，但没有信封，信也没办法寄出去。
-
-像Cookie这些信息就像在信封表面的那些发信人相关个人信息。
-TCP连接就是送信员，负责真正的传输数据信息。无状态表示每次寄信都是新的信封。
-在服务端看来这些信之间是没有关系的，并且服务端通过阅读这封信就得到它要的全部信息，无需从其它地方（比如Session）获取这封信的更多上下文信息，服务端就知道怎么处理和回信。
-
-HTTP协议就是浏览器与服务器之间的沟通语言，具体交互过程是请求、处理和响应。
-
-在HTTP/1.0时期，每次HTTP请求都会创建一个新的TCP连接，请求完成后之后这个TCP连接就会被关闭。
-这种通信模式的效率不高，所以在HTTP/1.1中，引入了HTTP长连接的概念。
-
-使用长连接的HTTP协议，会在响应头加入`Connection:keep-alive`。
-这样当浏览器完成一次请求后，浏览器和服务器之间的TCP连接不会关闭，再次访问这个服务器上的网页时，浏览器会继续使用这一条已经建立的连接，也就是说两个请求可能共用一个TCP连接。
-
-**keep-alive**表示TCP的连接可复用，指的是利用已有传输通道进行HTTP协议内容的传输，省去创建、关闭连接的开销以达到提升性能效果（类似线程池的复用线程）。
-Connection:keep-alive只是建立TCP层的状态，省去了下一次的TCP三次握手，而HTTP本身还是继续保持无状态。
-应用程序其实一般不关心这次HTTP请求的TCP传输细节，只关心HTTP协议的内容，因此只要复用TCP连接时做好必要的数据重置，是不算有状态的。
-
-HTTP的无状态性与共用TCP连接发送多个请求之间没有冲突，这些请求之间相对独立，唯一的关系可能只有发送的先后顺序关系。
-HTTP/1.1中的长连接没有解决 head of line blocking，请求是按顺序排队处理的，前面的HTTP请求处理会阻塞后面的HTTP请求，虽然HTTP pipelining对连接请求做了改善，但是复杂度太大，并没有普及，以至于后面的连接必须等待前面的返回了才能够发送。这个问题直到HTTP/2.0采取二进制分帧编码方式才彻底解决。
-
-- HTTP 1.0
-买一个信封只能传送一个来回的信。
-- HTPP 1.1 **keep–alive**
-买一个信封可重复使用，但前提是得等到服务端把这个信封里的信处理完，并送回来！
\ No newline at end of file
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP\350\266\205\346\227\266\345\244\204\347\220\206.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP\350\266\205\346\227\266\345\244\204\347\220\206.md"
deleted file mode 100644
index a944497e0d..0000000000
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP\350\266\205\346\227\266\345\244\204\347\220\206.md"
+++ /dev/null
@@ -1,190 +0,0 @@
-# 1 超时，网络终究是靠不住的
-HTTP调用既然是网络请求，就可能超时，超时错误分两种，connect timeout和read timeout，前者可能是网络问题，或者服务端连接池不够用了。后者是连接已经建立了，但是服务端太忙了，不能及时处理完你的请求。
-
-因此在实际开发中都需要注意考虑这些超时的处理措施。
-
-框架设置的默认超时时间是否合理？
-- 过短，请求还未处理完成，你有些急不可耐了呀！
-- 过长
-请求早已超出正常响应时间而挂了！
-
-网络不稳定性，超时后可以通过定时任务请求重试
-这时，就要注意考虑服务端接口幂等性设计，即是否允许重试？
-
-框架是否会像浏览器那样限制并发连接数，以免在高并发下，HTTP调用的并发数成为瓶颈！
-
-## 1.1 HTTP调用框架技术选型
-- Spring Cloud全家桶或Dubbo
-使用Feign进行声明式服务调用或 Dubbo自己的一套服务调用流程。
-- 只使用Spring Boot
-HTTP客户端，如Apache HttpClient
-
-## 1.2 连接超时配置 && 读取超时参数
-虽然应用层是HTTP协议，但网络层始终是TCP/IP协议。TCP/IP是面向连接的协议，在传输数据之前需先建立连接。
-网络框架都会提供如下超时相关参数：
-![](https://img-blog.csdnimg.cn/20201111193236911.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-- 连接超时参数ConnectTimeout
-可自定义配置的建立连接最长等待时间
-- 读取超时参数ReadTimeout
-控制从Socket上读取数据的最长等待时间。
-
-## 1.3 常见的写 bug 姿势
-### 连接超时配置过长
-比如60s。TCP三次握手正常建立连接所需时间很短，在ms级最多到s级，不可能需要十几、几十秒，多半是网络或防火墙配置问题。这时如果几秒还连不上，那么可能永远也连不上。所以设置特别长的连接超时无意义，1~5秒即可。
-如果是纯内网调用，还可以设更短，在下游服务无法连接时，快速失败
-### 无脑排查连接超时问题
-服务一般会有多个节点，若别的客户端通过负载均衡连接服务端，那么客户端和服务端会直接建立连接，此时出现连接超时大概率是服务端问题
-而若服务端通过Nginx反向代理来负载均衡，客户端连接的其实是Nginx，而非服务端，此时出现连接超时应排查Nginx
-
-
-### 读取超时参数和读取超时“坑点”
-
-#### 只要读取超时，服务端程序的正常执行就一定中断了？
-##### 案例
-client接口内部通过`HttpClient`调用服务端接口server，客户端读取超时2秒，服务端接口执行耗时5秒。
-![](https://img-blog.csdnimg.cn/20201111204103629.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-调用client接口后，查看日志：
-- 客户端2s后出现`SocketTimeoutException`，即读取超时![](https://img-blog.csdnimg.cn/2020111120432842.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-- 服务端却泰然地在3s后执行完成![](https://img-blog.csdnimg.cn/2020111120451912.png#pic_center)
-
-Tomcat Web服务器是把服务端请求提交到线程池处理，只要服务端收到请求，网络层面的超时和断开便不会影响服务端的执行。因此，出现读取超时不能随意假设服务端的处理情况，需要根据业务状态考虑如何进行后续处理。
-
-#### 读取超时只是Socket网络层面概念，是数据传输的最长耗时，故将其配置很短
-比如100ms。
-
-发生读取超时，网络层面无法区分如下原因：
-- 服务端没有把数据返回给客户端
-- 数据在网络上耗时较久或丢包
-
-但TCP是连接建立完成后才传输数据，对于网络情况不是特差的服务调用，可认为：
-- **连接超时**
-网络问题或服务不在线
-- **读取超时**
-服务处理超时。读取超时意味着向Socket写入数据后，我们等到Socket返回数据的超时时间，其中包含的时间或者说绝大部分时间，是服务端处理业务逻辑的时间
-
-#### 超时时间越长，任务接口成功率越高，便将读取超时参数配置过长
-HTTP请求一般需要获得结果，属**同步调用**。
-若超时时间很长，在等待 Server 返回数据同时，Client 线程（通常为 Tomcat 线程）也在等待，当下游服务出现大量超时，程序可能也会受到拖累创建大量线程，最终崩溃。
-- 对**定时任务**或**异步任务**，读取超时配置较长问题不大
-- 但面向用户响应的请求或是微服务平台的同步接口调用，并发量一般较大，应该设置一个较短的读取超时时间，以防止被下游服务拖慢，通常不会设置读取超时超过30s。
-
-评论可能会有人问了，若把读取超时设为2s，而服务端接口需3s，不就永远拿不到执行结果？
-的确，因此**设置读取超时要结合实际情况**：
-- 过长可能会让下游抖动影响到自己
-- 过短又可能影响成功率。甚至，有些时候我们还要根据下游服务的SLA，为不同的服务端接口设置不同的客户端读取超时。
-
-## 1.4  最佳实践
-连接超时代表建立TCP连接的时间，读取超时代表了等待远端返回数据的时间，也包括远端程序处理的时间。在解决连接超时问题时，我们要搞清楚连的是谁；在遇到读取超时问题的时候，我们要综合考虑下游服务的服务标准和自己的服务标准，设置合适的读取超时时间。此外，在使用诸如Spring Cloud Feign等框架时务必确认，连接和读取超时参数的配置是否正确生效。
-# 2 Feign&&Ribbon
-## 2.1 如何配置超时
-为Feign配置超时参数的难点在于，Feign自身有两个超时参数，它使用的负载均衡组件Ribbon本身还有相关配置。这些配置的优先级是啥呢？
-
-## 2.2 案例
-- 测试服务端超时，假设服务端接口，只休眠10min
-![](https://img-blog.csdnimg.cn/20201111211514444.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-- Feign调用该接口：
-![](https://img-blog.csdnimg.cn/2020111121200699.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-- 通过Feign Client进行接口调用
-![](https://img-blog.csdnimg.cn/20201111213831460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-在配置文件仅指定服务端地址的情况下：
-
-```bash
-clientsdk.ribbon.listOfServers=localhost:45678
-```
-
-得到如下输出：
-
-```bash
-[21:46:24.222] [http-nio-45678-exec-4] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController:26  ] - 
-	执行耗时：222ms 错误：Connect to localhost:45679 [localhost/127.0.0.1, localhost/0:0:0:0:0:0:0:1] 
-		failed: Connection refused (Connection refused) executing 
-			POST http://clientsdk/feignandribbon/server
-```
-Feign默认读取超时是1秒，如此短的读取超时算是“坑”。
-- 分析源码
-![](https://img-blog.csdnimg.cn/20201111215719405.png#pic_center)![](https://img-blog.csdnimg.cn/20201111215731406.png#pic_center)![](https://img-blog.csdnimg.cn/20201111215630631.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-## 自定义配置Feign客户端的两个全局超时时间
-可以设置如下参数：
-```bash
-feign.client.config.default.readTimeout=3000
-feign.client.config.default.connectTimeout=3000
-```
-修改配置后重试，得到如下日志：
-
-```bash
-[http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] - 执行耗时：3006ms 错误：Read timed out executing POST http://clientsdk/feignandribbon/server
-```
-3秒读取超时生效。
-注意：这里有一个大坑，如果希望只修改读取超时，可能会只配置这么一行：
-
-```bash
-feign.client.config.default.readTimeout=3000
-```
-测试会发现，这样配置无法生效。
-
-## 要配置Feign读取超时，必须同时配置连接超时
-查看`FeignClientFactoryBean`源码
-- 只有同时设置`ConnectTimeout`、`ReadTimeout`，Request.Options才会被覆盖
-![](https://img-blog.csdnimg.cn/20201111221444938.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-想针对单独的Feign Client设置超时时间，可以把default替换为Client的name：
-```bash
-feign.client.config.default.readTimeout=3000
-feign.client.config.default.connectTimeout=3000
-feign.client.config.clientsdk.readTimeout=2000
-feign.client.config.clientsdk.connectTimeout=2000
-```
-
-## 单独的超时可覆盖全局超时
-```bash
- [http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] - 
- 执行耗时：2006ms 错误：Read timed out executing 
- POST http://clientsdk/feignandribbon/server
-```
-
-## 除了可以配置Feign，也可配置Ribbon组件的参数以修改两个超时时间
-参数首字母要大写，和Feign的配置不同。
-```bash
-ribbon.ReadTimeout=4000
-ribbon.ConnectTimeout=4000
-```
-
-可以通过日志证明参数生效：
-
-```bash
-[http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] - 
-执行耗时：4003ms 错误：Read timed out executing 
-POST http://clientsdk/feignandribbon/server
-```
-
-## 同时配置Feign和Ribbon的参数
-谁会生效？
-```bash
-clientsdk.ribbon.listOfServers=localhost:45678
-feign.client.config.default.readTimeout=3000
-feign.client.config.default.connectTimeout=3000
-ribbon.ReadTimeout=4000
-ribbon.ConnectTimeout=4000
-```
-最终生效的是Feign的超时：
-
-```bash
-[http-nio-45678-exec-3] [WARN ] [o.g.t.c.h.f.FeignAndRibbonController    :26  ] - 
-执行耗时：3006ms 错误：Read timed out executing 
-POST http://clientsdk/feignandribbon/server
-```
-
-## 同时配置Feign和Ribbon的超时，以Feign为准
-在`LoadBalancerFeignClient`源码
-如果`Request.Options`不是默认值，就会创建一个`FeignOptionsClientConfig`代替原来Ribbon的`DefaultClientConfigImpl`，导致**Ribbon的配置被Feign覆盖**：
-![](https://img-blog.csdnimg.cn/20201111222305978.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-但若这么配置，最终生效的还是Ribbon的超时（4秒），难点Ribbon又反覆盖了Feign？不，这还是因为坑点二，**单独配置Feign的读取超时无法生效**：
-```bash
-clientsdk.ribbon.listOfServers=localhost:45678
-feign.client.config.default.readTimeout=3000
-feign.client.config.clientsdk.readTimeout=2000
-ribbon.ReadTimeout=4000
-```
\ No newline at end of file
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP\351\207\215\345\244\215\350\257\267\346\261\202.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP\351\207\215\345\244\215\350\257\267\346\261\202.md"
deleted file mode 100644
index c1356ccb48..0000000000
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP\351\207\215\345\244\215\350\257\267\346\261\202.md"
+++ /dev/null
@@ -1,99 +0,0 @@
-# Ribbon自动重试请求
-一些HTTP客户端往往会内置一些重试策略，其初衷是好的，毕竟因为网络问题导致丢包虽然频繁但持续时间短，往往重试就能成功，
-但要留心这是否符合我们期望。
-
-## 案例
-短信重复发送的问题，但短信服务的调用方用户服务，反复确认代码里没有重试逻辑。
-那问题究竟出在哪里？
-
-- Get请求的发送短信接口，休眠2s以模拟耗时：
-![](https://img-blog.csdnimg.cn/20201111225415763.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-配置一个Feign供客户端调用：
-![](https://img-blog.csdnimg.cn/20201111225647955.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-Feign内部有一个Ribbon组件负责客户端负载均衡，通过配置文件设置其调用的服务端为两个节点：
-
-```bash
-SmsClient.ribbon.listOfServers=localhost:45679,localhost:45678
-```
-
-- 客户端接口，通过Feign调用服务端
-![](https://img-blog.csdnimg.cn/20201111230748895.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-在45678和45679两个端口上分别启动服务端，然后访问45678的客户端接口进行测试。因为客户端和服务端控制器在一个应用中，所以45678同时扮演了客户端和服务端的角色。
-
-在45678日志中可以看到，29秒时客户端收到请求开始调用服务端接口发短信，同时服务端收到了请求，2秒后（注意对比第一条日志和第三条日志）客户端输出了读取超时的错误信息：
-
-```bash
-[http-nio-45678-exec-4] [INFO ] [c.d.RibbonRetryIssueClientController:23  ] - client is called
-[http-nio-45678-exec-5] [INFO ] [c.d.RibbonRetryIssueServerController:16  ] - http://localhost:45678/ribbonretryissueserver/sms is called, 13600000000=>a2aa1b32-a044-40e9-8950-7f0189582418
-[http-nio-45678-exec-4] [ERROR] [c.d.RibbonRetryIssueClientController:27  ] - send sms failed : Read timed out executing GET http://SmsClient/ribbonretryissueserver/sms?mobile=13600000000&message=a2aa1b32-a044-40e9-8950-7f0189582418
-```
-而在另一个服务端45679的日志中还可以看到一条请求，客户端接口调用后的1秒：
-```bash
-[http-nio-45679-exec-2] [INFO ] [c.d.RibbonRetryIssueServerController:16  ] - http://localhost:45679/ribbonretryissueserver/sms is called, 13600000000=>a2aa1b32-a044-40e9-8950-7f0189582418
-```
-客户端接口被调用的日志只输出了一次，而服务端的日志输出了两次。虽然Feign的默认读取超时时间是1秒，但客户端2秒后才出现超时错误。
-说明**客户端自作主张进行了一次重试**，导致短信重复发送。
-## 源码揭秘
-查看Ribbon源码，MaxAutoRetriesNextServer参数默认为1，也就是Get请求在某个服务端节点出现问题（比如读取超时）时，Ribbon会自动重试一次：
-![](https://img-blog.csdnimg.cn/20201111232018867.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-![](https://img-blog.csdnimg.cn/20201111232254555.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-### 解决方案
-1. 把发短信接口从Get改为Post
-API设计规范：有状态的API接口不应定义为Get。根据HTTP协议规范，Get请求适用于数据查询，Post才是把数据提交到服务端用于修改或新增。选择Get还是Post的依据，应该是API行为，而非参数大小。
-> 常见误区：Get请求的参数包含在Url QueryString中，会受浏览器长度限制，所以一些开发会选择使用JSON以Post提交大参数，使用Get提交小参数。
-
-2. 将`MaxAutoRetriesNextServer`参数配为0，禁用服务调用失败后在下一个服务端节点的自动重试。在配置文件中添加一行即可：
-
-```bash
-ribbon.MaxAutoRetriesNextServer=0
-```
-### 问责
-至此，问题出在用户服务还是短信服务？
-也许双方都有问题吧。
-- Get请求应该是无状态或者幂等的，短信接口可以设计为支持幂等调用
-- 用户服务的开发同学，如果对Ribbon的重试机制有所了解的话，或许就能在排查问题上少走弯路
-
-## 最佳实践
-对于重试，因为HTTP协议认为Get请求是数据查询操作，是无状态的，又考虑到网络出现丢包是比较常见的事情，有些HTTP客户端或代理服务器会自动重试Get/Head请求。如果你的接口设计不支持幂等，需要关闭自动重试。但，更好的解决方案是，遵从HTTP协议的建议来使用合适的HTTP方法。
-
-# 并发限制爬虫抓取
-HTTP请求调用还有一个常见的问题：并发数的限制，导致程序处理性能无法提升。
-## 案例
-某爬虫项目，整体爬取数据效率很低，增加线程池数量也无谓，只能堆机器。
-现在模拟该场景，探究问题本质。
-
-假设要爬取的服务端是这样的一个简单实现，休眠1s返回数字1：
-![](https://img-blog.csdnimg.cn/20201111233015454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-爬虫需多次调用该接口抓取数据，为确保线程池不是并发瓶颈，使用了一个无线程上限的`newCachedThreadPool`，然后使用`HttpClient`执行HTTP请求，把请求任务循环提交到线程池处理，最后等待所有任务执行完成后输出执行耗时：
-![](https://img-blog.csdnimg.cn/20201111233347439.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-- 使用默认的`PoolingHttpClientConnectionManager`构造的`CloseableHttpClient`，测试一下爬取10次的耗时：
-![](https://img-blog.csdnimg.cn/20201111233512569.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-虽然一个请求需要1s执行完成，但线程池可扩张使用任意数量线程。
-按道理，10个请求并发处理的时间基本相当于1个请求的处理时间，即1s，但日志中显示实际耗时5秒：
-![](https://img-blog.csdnimg.cn/20201111233832252.png#pic_center)
-
-## 源码解析
-`PoolingHttpClientConnectionManager`源码有两个重要参数：
-![](https://img-blog.csdnimg.cn/20201111234051864.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-- defaultMaxPerRoute=2，即同一主机/域名的最大并发请求数为2。我们的爬虫需要10个并发，显然是**默认值太小限制了爬虫的效率**。
-- maxTotal=20，即所有主机整体最大并发为20，这也是HttpClient整体的并发度。**我们请求数是10最大并发是10，20不会成为瓶颈**。举一个例子，使用同一个HttpClient访问10个域名，defaultMaxPerRoute设置为10，为确保每一个域名都能达到10并发，需要把maxTotal设置为100。
-
-`HttpClient`是常用的HTTP客户端，那为什么默认值限制得这么小？
-很多早期的浏览器也限制了同一个域名两个并发请求。对于同一个域名并发连接的限制，其实是HTTP 1.1协议要求的，这里有这么一段话：
-
-> Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion.
-HTTP 1.1协议是20年前制定的，现在HTTP服务器的能力强很多了，所以有些新的浏览器没有完全遵从2并发这个限制，放开并发数到了8甚至更大。
-如果需要通过HTTP客户端发起大量并发请求，不管使用什么客户端，请务必确认客户端的实现默认的并发度是否满足需求。
-
-尝试声明一个新的HttpClient放开相关限制，设置maxPerRoute为50、maxTotal为100，然后修改一下刚才的wrong方法，使用新的客户端进行测试：
-![](https://img-blog.csdnimg.cn/20201111234405368.png#pic_center)
-输出如下，10次请求在1秒左右执行完成。可以看到，因为放开了一个Host 2个并发的默认限制，爬虫效率得到了大幅提升：
-![](https://img-blog.csdnimg.cn/20201111234518322.png#pic_center)
-## 最佳实践
-若你的客户端有比较大的请求调用并发，比如做爬虫，或是扮演类似代理的角色，又或者是程序本身并发较高，如此小的默认值很容易成为吞吐量的瓶颈，需要及时调整。
\ No newline at end of file
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Unix\347\232\204IO\346\250\241\345\236\213\350\247\243\346\236\220.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Unix\347\232\204IO\346\250\241\345\236\213\350\247\243\346\236\220.md"
deleted file mode 100644
index e7f4cd5e0e..0000000000
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Unix\347\232\204IO\346\250\241\345\236\213\350\247\243\346\236\220.md"
+++ /dev/null
@@ -1,200 +0,0 @@
-IO 是主存和外部设备 ( 硬盘、各种移动终端及网络等 ) 拷贝数据的过程。IO 是操作系统的底层功能，通过 I/O 指令完成。网络编程领域的IO专指网络IO。
-
-# JDK 的 NIO
-NIO，即NEW IO，引入了多路选择器、Channel 和 Bytebuffer。
-os为了保护自身稳定，会将内存空间划分为内核、用户空间。当需通过 TCP 发送数据时，在应用程序中实际上执行了将数据从用户空间拷贝至内核空间，再由内核进行实际的发送动作；而从 TCP 读取数据时则反过来，等待内核将数据准备好，再从内核空间拷贝至用户空间，应用数据才能处理。针对在两个阶段上不同的操作，Unix 定义了 5 种 IO 模型
-# 1 阻塞式IO（Blocking IO）
-最流行的 IO 模型，在客户端上特别常见，因为其编写难度最低，也最好理解。
-
-在linux中，默认情况下所有的socket都是blocking，一个典型的读操作流程大概是这样：
-1. 通常涉及等待数据从网络中到达。当所有等待数据到达时，它被复制到内核中的某个缓冲区
-2. 把数据从内核缓冲区复制到应用程序缓冲区
-![](https://img-blog.csdnimg.cn/20201102000306356.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-用户进程调用`recvfrom（系统调用）`，kernel开始IO的第一个阶段：准备数据。
-对network io，很多时候数据在一开始还没有到达（比如，还没收到一个完整的UDP包），这时kernel就要等待足够数据。
-而用户进程整个被阻塞。当kernel一直等到数据准备好了，它就会将数据从kernel中拷贝到用户内存，然后kernel返回结果，用户进程才解除 block状态，重新运行。
-
-所以，blocking IO的特点就是在IO执行的两个阶段都被阻塞。调用返回成功或发生错误前，应用程序都在阻塞在方法的调用上。当方法调用成功返回后，应用程序才能开始处理数据。
-
-JDK1.4前，Java只支持BIO。
-示例代码：
-```java
-public static void main(String[] args) throws IOException {
-	// 创建一个客户端socket实例
-    Socket socket = new Socket();
-    // 尝试连接一个远端的服务器地址
-    socket.connect(InetSocketAddress.createUnresolved("localhost", 4591));
-    // 在连接成功后则获取输入流
-    InputStream inputStream = socket.getInputStream();
-    byte[] content = new byte[128];
-    // 并且尝试读取数据
-    int bytesOfRead = inputStream.read(content);
-}
-```
-
-在输入流上的read调用会阻塞，直到有数据被读取成功或连接发生异常。
-read的调用就会经历上述将程序阻塞，然后内核等待数据准备后，将数据从内核空间复制到用户空间，即入参传递进来的二进制数组中。
-实际读取的字节数可能小于数组的长度，方法的返回值正是实际读取的字节数。
-
-# 非阻塞式IO
-允许将一个套接字设置为非阻塞。当设置为非阻塞时，是在通知内核：如果一个操作需要将当前的调用线程阻塞住才能完成时，不采用阻塞的方式，而是返回一个错误信息。其模型如下
-
-
-
-可以看到，在内核没有数据时，尝试对数据的读取不会导致线程阻塞，而是快速的返回一个错误。直到内核中收到数据时，尝试读取，就会将数据从内核复制到用户空间，进行操作。
-
-可以看到，在非阻塞模式下，要感知是否有数据可以读取，需要不断的轮训，这么做往往会耗费大量的 CPU。所以这种模式不是很常见。
-
-JDK1.4提供新的IO包  - NIO，其中的SocketChannel提供了对非阻塞 IO 的支持。
-```java
-public static void main(String[] args) throws IOException
-    {
-        SocketChannel socketChannel = SocketChannel.open();
-        socketChannel.configureBlocking(false);
-        socketChannel.connect(InetSocketAddress.createUnresolved("192.168.31.80", 4591));
-        ByteBuffer buffer = ByteBuffer.allocate(128);
-        while (socketChannel.read(buffer) == 0)
-        {
-            ;
-        }
-    }
-```
-
-一个SocketChannel实例就类似从前的一个Socket对象。
-
-首先是通过`SocketChannel.open()`调用新建了一个SocketChannel实例，默认情况下，新建的socket实例都是阻塞模式，通过`java.nio.channels.spi.AbstractSelectableChannel#configureBlocking`调用将其设置为非阻塞模式，然后连接远程服务端。
-
-`java.nio.channels.SocketChannel`使用`java.nio.ByteBuffer`作为数据读写的容器，可简单将`ByteBuffer`看成是一个内部持有二进制数据的包装类。
-
-调用方法java.nio.channels.SocketChannel#read(java.nio.ByteBuffer)时会将内核中已经准备好的数据复制到ByteBuffer中。但是如果内核中此时并没有数据（或者说socket的读取缓冲区没有数据），则方法会立刻返回，并不会阻塞住。这也就对应了上图中，在内核等待数据的阶段（socket的读取缓冲区没有数据），读取调用时会立刻返回错误的。只不过在Java中，返回的错误在上层处理为返回一个读取为0的结果。
-
-# IO复用
-IO复用指的应用程序阻塞在系统提供的两个调用select或poll上。当应用程序关注的套接字存在可读情况（也就是内核收到数据了），select或poll的调用被返回。此时应用程序可以通过recvfrom调用完成数据从内核空间到用户空间的复制，进而进行处理。具体的模型如下
-
-
-
-可以看到，和 阻塞式IO 相比，都需要等待，并不存在优势。而且由于需要2次系统调用，其实还稍有劣势。但是IO复用的优点在于，其select调用，可以同时关注多个套接字，在规模上提升了处理能力。
-
-IO复用的模型支持一样也是在JDK1.4中的 NIO 包提供了支持。可以参看如下示例代码：
-
-```java
-public static void main(String[] args) throws IOException
-    {
-        /**创建2个Socket通道**/
-        SocketChannel socketChannel = SocketChannel.open();
-        socketChannel.configureBlocking(false);
-        socketChannel.connect(InetSocketAddress.createUnresolved("192.168.31.80", 4591));
-        SocketChannel socketChannel2 = SocketChannel.open();
-        socketChannel2.configureBlocking(false);
-        socketChannel2.connect(InetSocketAddress.createUnresolved("192.168.31.80", 4591));
-        /**创建2个Socket通道**/
-        /**创建一个选择器，并且两个通道在这个选择器上注册了读取关注**/
-        Selector selector = Selector.open();
-        socketChannel.register(selector, SelectionKey.OP_READ);
-        socketChannel2.register(selector, SelectionKey.OP_READ);
-        /**创建一个选择器，并且两个通道在这个选择器上注册了读取关注**/
-        ByteBuffer buffer = ByteBuffer.wrap(new byte[128]);
-        //选择器可以同时检查所有在其上注册的通道，一旦哪个通道有关注事件发生，select调用就会返回，否则一直阻塞
-        selector.select();
-        Set<SelectionKey>      selectionKeys = selector.selectedKeys();
-        Iterator<SelectionKey> iterator      = selectionKeys.iterator();
-        while (iterator.hasNext())
-        {
-            SelectionKey  selectionKey = iterator.next();
-            SocketChannel channel      = (SocketChannel) selectionKey.channel();
-            channel.read(buffer);
-            iterator.remove();
-        }
-    }
-```
-
-代码一开始，首先是新建了2个客户端通道，连接到服务端上。接着创建了一个选择器Selector。选择器就是 Java 中实现 IO 复用的关键。选择器允许通道将自身的关注事件注册到选择器上。完成注册后，应用程序调用java.nio.channels.Selector#select()方法，程序进入阻塞等待直到注册在选择器上的通道中发生其关注的事件，则select调用会即可返回。然后就可以从选择器中获取刚才被选中的键。从键中可以获取对应的通道对象，然后就可以在通道对象上执行读取动作了。
-
-结合IO复用模型，可以看到，select调用的阻塞阶段，就是内核在等待数据的阶段。一旦有了数据，内核等待结束，select调用也就返回了。
-
-# 信号驱动IO
-![](https://img-blog.csdnimg.cn/20201102011116492.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-与非阻塞IO类似，其在数据等待阶段并不阻塞，但是原理不同。信号驱动IO是在套接字上注册了一个信号调用方法。这个注册动作会将内核发出一个请求，在套接字的收到数据时内核会给进程发出一个sigio信号。该注册调用很快返回，因此应用程序可以转去处理别的任务。当内核准备好数据后，就给进程发出了信号。进程就可以通过recvfrom调用来读取数据。其模型如下
-
-
-
-这种模型的优点就是在数据包到达之前，进程不会被阻塞。而且采用通知的方式也避免了轮训带来的损耗。
-
-这种模型在Java中并没有对应的实现。
-
-# 异步IO
-异步IO的实现一般是通过系统调用，向内核注册了一个套接字的读取动作。这个调用一般包含了：缓存区指针，缓存区大小，偏移量、操作完成时的通知方式。该注册动作是即刻返回的，并且在整个IO的等待期间，进程都不会被阻塞。当内核收到数据，并且将数据从内核空间复制到用户空间完成后，依据注册时提供的通知方式去通知进程。其模型如下：
-
-
-
-与信号驱动 IO 相比，最大的不同在于信号驱动 IO 是内核通知应用程序可以读取数据了；而 异步IO 是内核通知应用程序数据已经读取完毕了。
-
-Java 在 1.7 版本引入对 异步IO 的支持，可以看如下的例子：
-
-```java
-public class MainDemo
-{
-    public static void main(String[] args) throws IOException, ExecutionException, InterruptedException
-    {
-        final AsynchronousSocketChannel asynchronousSocketChannel = AsynchronousSocketChannel.open();
-        Future<Void>                    connect                   = asynchronousSocketChannel.connect(InetSocketAddress.createUnresolved("192.168.31.80", 3456));
-        connect.get();
-        ByteBuffer buffer = ByteBuffer.wrap(new byte[128]);
-        asynchronousSocketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>()
-        {
-            @Override
-            public void completed(Integer result, ByteBuffer buffer)
-            {
-                //当读取到数据，流中止，或者读取超时到达时均会触发回调
-                if (result > 0)
-                {
-                    //result代表着本次读取的数据，代码执行到这里意味着数据已经被放入buffer了
-                    processWithBuffer(buffer);
-                }
-                else if (result == -1)
-                {
-                    //流中止，没有其他操作
-                }
-                else{
-                    asynchronousSocketChannel.read(buffer, buffer, this);
-                }
-            }
-
-            private void processWithBuffer(ByteBuffer buffer)
-            {
-            }
-
-            @Override
-            public void failed(Throwable exc, ByteBuffer attachment)
-            {
-            }
-        });
-    }
-}
-```
-
-代码看上去和IO复用时更简单了。
-
-首先是创建一个异步的 Socket 通道，注意，这里和 NIO 最大的区别就在于创建的是异步Socket通道，而 NIO 创建的属于同步通道。
-
-执行connect方法尝试连接远程，此时方法会返回一个future，这意味着该接口是非阻塞的。实际上connect动作也是可以传入回调方法，将连接结果在回调方法中进行传递的。这里为了简化例子，就直接使用future了。
-
-连接成功后开始在通道上进行读取动作。这里就是和 NIO 中最大的不同。读取的时候需要传入一个回调方法。当数据读取成功时回调方法会被调用，并且当回调方法被调用时读取的数据已经被填入了ByteBuffer。
-
-主线程在调用读取方法完成后不会被阻塞，可以去执行别的任务。可以看到在整个过程都不需要用户线程参与，内核完成了所有的工作。
-
-# 同步 V.S 异步
-根据 POSIX 的定义：
-- 同步：同步操作导致进程阻塞，直到 IO 操作完成
-- 异步：异步操作不导致进程阻塞
-
-来看下五种 IO 模型的对比，如下
-
-
-
-可以看到，根据定义，前 4 种模型，在数据的读取阶段，全部都是阻塞的，因此是同步IO。而异步IO模型在整个IO过程中都不阻塞，因此是异步IO。
-
-参考
- - http://www.tianshouzhi.com/api/tutorials/netty/221
\ No newline at end of file
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\345\272\224\347\224\250\345\261\202.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\345\272\224\347\224\250\345\261\202.md"
similarity index 100%
rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\345\272\224\347\224\250\345\261\202.md"
rename to "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\345\272\224\347\224\250\345\261\202.md"
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md"
similarity index 78%
rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md"
rename to "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md"
index 451ed93df9..bcbef17c31 100644
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md"
+++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\346\225\260\346\215\256\351\223\276\350\267\257\345\261\202.md"
@@ -1,23 +1,19 @@
-功能：保证数据正确的顺序，无错和完整
-# 1 链路层概述
-## 信道类型
+功能:保证数据正确的顺序,无错和完整
+#1 链路层概述
+数据链路层使用的信道主要有以下两种类型
 - 点对点信道
 一对一的点对点通信方式
 - 广播信道
 一对多的广播通信方式，过程比较复杂。广播信道上连接的主机很多，必须使用专用的共享信道协议来协调这些主机的数据发送
-
-## 数据链路层的简单模型
-![](https://img-blog.csdnimg.cn/img_convert/611100e3410a83cfb01855de69b74ea8.png)
-![](https://img-blog.csdnimg.cn/img_convert/56929dc9b045d1bf2db4719d6e8dd56e.png)
-
-- 链路层地址
-LAN地址，物理地址或称为MAC地址
-
-## 基本问题
-### 封装成帧(framing)
-一段数据的前后分别添加首部和尾部，构成一个帧。首部和尾部的一个重要作用就是进行帧定界
-![封帧图解](https://img-blog.csdnimg.cn/img_convert/e5a683d24def67dded3fd286e7a73c20.png)
-### 透明传输
+![数据链路层的简单模型](http://upload-images.jianshu.io/upload_images/4685968-b3725c5ecde43b95.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![数据链路层的简单模型( 续）](http://upload-images.jianshu.io/upload_images/4685968-0b7730d198a38697.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+链路层地址:LAN地址,物理地址或称为MAC地址
+## 1 链路层的三个基本问题
+- 封装成帧(framing)
+一段数据的前后分别添加首部和尾部，构成一个帧
+首部和尾部的一个重要作用就是进行帧定界
+![封帧图解](http://upload-images.jianshu.io/upload_images/4685968-fcd9088b56781893.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 透明传输
 "在数据链路层透明传送数据",表示无论什么样的比特组合的数据都能够通过这个数据链路层
 数据看不见数据链路层有什么妨碍数据传输的东西
 数据链路层对于这些数据来说是透明的
@@ -25,12 +21,12 @@ LAN地址，物理地址或称为MAC地址
 发送端的数据链路层在数据中出现控制字符“SOH”或“EOT”的前面插入一个转义字符“ESC”(其十六进制编码是 1B)。
 字节填充(byte stuffing)或字符填充(character stuffing)——接收端的数据链路层在将数据送往网络层之前删除插入的转义字符。
 如果转义字符也出现数据当中，那么应在转义字符前面插入一个转义字符。当接收端收到连续的两个转义字符时，就删除其中前面的一个。
-### 差错检测
+- 差错检测
 在传输过程中可能会产生比特差错：1 可能会变成0 ,而0也可能变成1
 在一段时间内,传输错误的比特占所传输比特总数的比率称为误码率 BER (Bit Error Rate)
 为了保证数据传输的可靠性,在计算机网络传输数据时,必须采用各种差错检测措施 
-# 2 PPP (Point-to-Point Protocol)
-点对点协议 ，三个组成部分 
+## 2 点对点协议 PPP (Point-to-Point Protocol)的组成
+三个组成部分 
 - 一个将 IP 数据报封装到串行链路的方法。
 - 链路控制协议 LCP (Link Control Protocol)。
 - 网络控制协议 NCP (Network Control Protocol)。  
@@ -51,9 +47,7 @@ RFC1662规定的填充方法：
 ### 冗余码的计算 
 - 用二进制的模 2 运算进行 2n 乘 M 的运算，这相当于在 M 后面添加 n 个 0。
 - 得到的 (k + n) 位的数除以双方事先选定好的长度为 (n + 1) 位的除数 P，得出商是 Q 而余数是 R，余数 R 比除数 P 少1 位，即 R 是 n 位。
-
-- 冗余码的计算举例
-![](https://img-blog.csdnimg.cn/img_convert/aa12f298f758f17f7389d55b41755eac.png)
+![冗余码的计算举例 ](http://upload-images.jianshu.io/upload_images/4685968-e443c8285025374b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 # 4 交换局域网
 ## 4.1 链路层寻址和ARP
@@ -64,37 +58,28 @@ RFC1662规定的填充方法：
 - 应用最广泛的LAN技术
 - 比令牌局域网和ATM等，简单、便宜
 - 满足网络速率需求：10 Mbps – 10 Gbps
-
-- Metcalfe的以太网草图
-![](https://img-blog.csdnimg.cn/img_convert/50546d79723f3b6e28f18590e4c92043.png)
+![Metcalfe的以太网草图](http://upload-images.jianshu.io/upload_images/4685968-a869964e0402bf10.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ### 4.2.2 以太网：物理拓扑
 总线(bus): 上世纪90年代中期前流行
 - 所有结点在同一冲突域(collision domain) (可能彼此冲突)
 
-星型(star)：目前主流网络拓扑
+星型(star): 目前主流网络拓扑
 - 中心交换机(switch)
 - 每个结点一个单独冲突域(结点间彼此不冲突)
-
-- 总线(bus): 同轴电缆&&星型(star)
-![](https://img-blog.csdnimg.cn/img_convert/d565d59037d23fc550a76955cfeeaac9.png)
+![总线(bus): 同轴电缆&&星型(star)](http://upload-images.jianshu.io/upload_images/4685968-75ee56e4a71c2996.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ### 4.2.3 以太网：不可靠、无连接服务
-无连接(connectionless)：发送帧的网卡与接收帧的网卡间没有“握手”过程。
-
-不可靠(unreliable)：接收网卡不向发送网卡进行确认
-- 差错帧直接丢弃，丢弃帧中的数据恢复依靠高层协议 (e.g., TCP)，否则，发生数据丢失。
-
-以太网的MAC协议采用二进制指数退避算法的CSMA/CD。
-### 4.2.4 CSMA/CD(Carrier Sense Multiple Access with Collision Detection)
-协议的工作过程，最短帧长与争用期的关系(重点)
-
-#### 概念
-载波监听多点接入/碰撞检测：
-- 多点接入
-许多计算机以多点接入的方式连接在一根总线上
-- 载波监听
-每一个站在发送数据之前先要检测一下总线上是否有其他计算机在发送数据，如果有，则暂时不要发送数据，以免发生碰撞
-- 碰撞检测
-计算机边发送数据边检测信道上的信号电压大小
+无连接(connectionless): 发送帧的网卡与接收帧的网卡间没有“握手”过程
+
+不可靠(unreliable): 接收网卡不向发送网卡进行确认
+- 差错帧直接丢弃，丢弃帧中的数据恢复依靠高层协议 (e.g., TCP)，否则，发生数据丢失
+
+以太网的MAC协议: 采用二进制指数退避算法的CSMA/CD
+### 4.2.4 CSMA/CD(Carrier Sense Multiple Access with Collision Detection)，协议的工作过程，最短帧长与争用期的关系(重点)
+  #### 概念
+载波监听多点接入/碰撞检测
+"多点接入":许多计算机以多点接入的方式连接在一根总线上
+"载波监听":每一个站在发送数据之前先要检测一下总线上是否有其他计算机在发送数据，如果有，则暂时不要发送数据，以免发生碰撞
+"碰撞检测":计算机边发送数据边检测信道上的信号电压大小
 #### 原理
 载波监听
 碰撞检测
@@ -131,7 +116,7 @@ RFC1662规定的填充方法：
   - 凡长度小于 64 字节的帧都是由于冲突而异常中止的无效帧
 ### 4.2.5 以太网帧结构
 发送端网卡将IP数据报(或其他网络层协议分组)封装到以太网帧中：
-![](https://img-blog.csdnimg.cn/img_convert/2055801dabce72426f050a79191496a8.png)
+![](http://upload-images.jianshu.io/upload_images/4685968-94a4d5ff50c03caa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 #### 各个字段意义
 - 前导码(Preamble)(8B):
   - 7个字节的10101010，第8字节为10101011
@@ -151,7 +136,7 @@ RFC1662规定的填充方法：
   - 相同的MAC协议和帧格式
   - 不同速率: 2 Mbps, 10 Mbps, 100 Mbps, 1Gbps, 10G bps
   - 不同物理介质: 光纤, 线缆
-![](https://img-blog.csdnimg.cn/img_convert/b1464502d7b2e2cb63b79b7fb309af23.png)
+![](http://upload-images.jianshu.io/upload_images/4685968-632cbed384702387.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 # 5 交换机(switch)
 ## 5.1 以太网交换机(switch)
@@ -170,7 +155,7 @@ RFC1662规定的填充方法：
 - 交换机在每段链路上利用CSMA/CD收发帧，但无冲突，且可以全双工
   - 每段链路一个独立的冲突域
 - 交换(switching): A-A’与B-B’的传输可以同时进行，没有冲突
-![6个接口交换机 (1,2,3,4,5,6)](https://img-blog.csdnimg.cn/img_convert/7964b54d110623c30a303183c09e9fc1.png)
+![6个接口交换机 (1,2,3,4,5,6)](http://upload-images.jianshu.io/upload_images/4685968-471ebdc766c91754.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 5.3 交换机转发表：交换表
 Q: 交换机怎么知道A’可以通过接口4到达，而B’可以通过接口5到达?
 - A: 每个交换机有一个交换表(switch table), 每个入口(entry):
@@ -182,7 +167,7 @@ Q: 交换机怎么知道A’可以通过接口4到达，而B’可以通过接
 交换机通过自学习，获知到达主机的接口信息
   - 当收到帧时，交换机“学习”到发送帧的主机（通过帧的源MAC地址），位于收到该帧的接口所连接的LAN网段
   - 将发送主机MAC地址/接口信息记录到交换表中
-![](https://img-blog.csdnimg.cn/img_convert/e6e22fe21967dcd0e42706edff3a1910.png)
+![](http://upload-images.jianshu.io/upload_images/4685968-fcbf2dc4f65cc128.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 5.5 交换机: 帧过滤/转发
 当交换机收到帧:
 1. 记录帧的源MAC地址与输入链路接口
@@ -196,6 +181,11 @@ else 将帧转发到该入口指向的接口
 else 泛洪(flood) /* 向除收到该帧的接口之外的所有接口转发 */
 
 
+
+
+
+
+
 ## 6 集线器、转发器、网桥、以太网交换机的异同
 ### 集线器的特点
 - 使用集线器的以太网在逻辑上仍是一个总线网，各工作站使用的还是 CSMA/CD 协议，并共享逻辑上的总线。 
@@ -221,15 +211,9 @@ else 泛洪(flood) /* 向除收到该帧的接口之外的所有接口转发 */
 - 网桥每收到一个帧，就记下其源地址和进入网桥的接口，作为转发表中的一个项目。
 - 在建立转发表时是把帧首部中的源地址写在“地址”这一栏的下面。
 - 在转发帧时，则是根据收到的帧首部中的目的地址来转发的。这时就把在“地址”栏下面已经记下的源地址当作目的地址，而把记下的进入接口当作转发接口
-#  8 网卡
-网卡，也叫网络适配器。处于数据链路层与物理层，兼顾两层功能。是个小型嵌入式系统，上面有自己的处理器以及存储器（包括ROM和RAM）。
-
-### 网卡的职责 
-计算机的CPU将网络层的IP数据报交给网卡后，就可以去做别的了，由网卡的处理器复杂将IP数据报处理，并将信号转为电信号发送出去，以及接收在网络上传输过来的分组，然后通过中断通知计算机的CPU来把数据读走。
-
-如果只是通过IP地址转发分组，那么如果中间需要很多路由器来转发，如何去识别我该将分组转发给哪个路由器呢？所以提出了物理地址作为设备的地址唯一标识，即MAC地址，每个网卡具有唯一MAC地址，这样根据MAC地址就可以找到如何转发了。MAC地址是一个48位的二进制数据。
+## 8 了解网络适配器的主要功能及其工作在哪一层
 网络接口板又称为通信适配器(adapter)或网络接口卡NIC(Network Interface Card),或“网卡”
 - 进行串行/并行转换；
 - 对数据进行缓存；
 - 在计算机的操作系统安装设备驱动程序；
-- 实现以太网协议
\ No newline at end of file
+- 实现以太网协议
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\211\251\347\220\206\345\261\202.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\211\251\347\220\206\345\261\202.md"
similarity index 100%
rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\211\251\347\220\206\345\261\202.md"
rename to "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\211\251\347\220\206\345\261\202.md"
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\275\221\347\273\234\345\261\202.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\275\221\347\273\234\345\261\202.md"
similarity index 51%
rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\275\221\347\273\234\345\261\202.md"
rename to "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\275\221\347\273\234\345\261\202.md"
index 4b636b21ee..6c4c9055a4 100644
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\275\221\347\273\234\345\261\202.md"
+++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\347\275\221\347\273\234\345\261\202.md"
@@ -1,72 +1,55 @@
 # 1 IP地址与MAC地址的关系
-- MAC是身份证号码，用来识别网络设备本身
-- IP地址是居住地
-
-# 2 ARP协议
-Address Resolution Protocol(地址解析协议)。ARP协议能实现任意网络地址到任意物理地址的转换，这里仅讨论IP地址到以太网地址（MAC地址）的转换。
-#### 工作原理
-主机向自己所在网络广播一个ARP请求(包含目标机器的网络地址)。此网络上的其他机器都将接收到这个请求，但只有被请求的目标机器会回应一个ARP应答(包含自己的物理地址)。
-## 2.1 作用
- 地址解析流程：
-![](https://img-blog.csdnimg.cn/img_convert/92ff97cec7e612ad5f805aba4d994499.png)
-将主机的IP地址解析为相应的链路层的MAC地址。
-- 不管网络层使用的是什么协议，在实际网络的链路上传送数据帧时，最终还是必须使用MAC地址
-- 每一个主机都设有一个ARP高速缓存，里面有所在局域网上的各主机和路由器的 IP 地址到MAC地址的映射表
-
-当主机 A 欲向本局域网上的主机 B 发送 IP 数据报时：
-1. 先在其ARP高速缓存中查看有无主机 B 的 IP 地址。若有，就可查出其对应MAC地址。
-2. 再将此MAC地址写入 MAC 帧，然后通过局域网将该 MAC 帧发往此MAC地址。
-![](https://img-blog.csdnimg.cn/img_convert/f4974e7bef31dff61df8c5fc9c4a5190.png)
-## ARP高速缓存的作用
-- 为了减少网络上的通信量，主机A在发送其 ARP 请求分组时，就将自己的 IP 地址到硬件地址的映射写入 ARP 请求分组
-- 当主机 B 收到 A 的 ARP 请求分组时，就将主机 A 的这一地址映射写入主机 B 自己的 ARP 高速缓存中。这样主机 B 以后向 A 发送数据报时就更方便了。 
-# 3 IP地址
+MAC是身份证号码，用来识别网络设备本身
+IP地址是居住地
+# 2 ARP协议的作用,地址解析的过程
+![ARP协议作用](http://upload-images.jianshu.io/upload_images/4685968-3d3fb943e04b6afc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+将主机的IP地址解析为相应的链路层的MAC地址
+- 不管网络层使用的是什么协议,在实际网络的链路上传送数据帧时,最终还是必须使用MAC地址 
+- 每一个主机都设有一个 ARP 高速缓存,里面有所在的局域网上的各主机和路由器的 IP 地址到MAC地址的映射表
+- 当主机 A 欲向本局域网上的某个主机 B 发送 IP 数据报时,就先在其 ARP 高速缓存中查看有无主机 B 的 IP 地址.如有,就可查出其对应的MAC地址,再将此MAC地址写入 MAC 帧,然后通过局域网将该 MAC 帧发往此MAC地址. 
+![](http://upload-images.jianshu.io/upload_images/4685968-3de8864320588c15.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## ARP 高速缓存的作用
+- 为了减少网络上的通信量,主机 A 在发送其 ARP 请求分组时,就将自己的 IP 地址到硬件地址的映射写入 ARP 请求分组
+- 当主机 B 收到 A 的 ARP 请求分组时，就将主机 A 的这一地址映射写入主机 B 自己的 ARP 高速缓存中。这对主机 B 以后向 A 发送数据报时就更方便了。 
+# 3 IP地址的分类,每类地址的范围,给定IP地址能够判断属于哪一类
 ## 3.1 分类
-每类地址都由两个固定长度的字段组成：
-- 网络号 net-id
-标志主机（或路由器）所连接到的网络。
-- 主机号 host-id
-标志该主机（或路由器）。
+每一类地址都由两个固定长度的字段组成
+- 其中一个字段是网络号 net-id，它标志主机（或路由器）所连接到的网络，
+- 而另一个字段则是主机号 host-id，它标志该主机（或路由器）
 
 两级的 IP 地址可以记为：
-```bash
 IP 地址 ::= { <网络号>, <主机号>}
-```
-- IP 地址中的网络号字段和主机号字段
-![ ](https://img-blog.csdnimg.cn/img_convert/e030632a15ca69c97095d112048e3113.png)
-- 点分十进制记法 
-![](https://img-blog.csdnimg.cn/img_convert/3f784bbf57446d87115c5f4f769f8677.png)
+![IP 地址中的网络号字段和主机号字段 ](http://upload-images.jianshu.io/upload_images/4685968-1af948240c9378f1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![点分十进制记法 ](http://upload-images.jianshu.io/upload_images/4685968-53eb6f4fd647c1d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 3.2 范围
-- 常用的三种类别的IP 地址的使用范围
-![](https://img-blog.csdnimg.cn/img_convert/66c6f034759517d76e30bb4286f578ec.png)
+![常用的三种类别的IP 地址的使用范围](http://upload-images.jianshu.io/upload_images/4685968-cb2b5b2f565ff006.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 # 4 IP数据报分片
 ## 4.1 数据报格式
-一个 IP 数据报由首部和数据两部分组成：
+- 一个 IP 数据报由首部和数据两部分组成
 - 首部的前一部分是固定长度，共 20 字节，是所有 IP 数据报必须具有的
 - 在首部固定部分的后面是可选字段，长度可变
-![](https://img-blog.csdnimg.cn/img_convert/48f09ce085e7b1f0b562d4e90621b479.png)
-![](https://img-blog.csdnimg.cn/img_convert/2ba2b4fdee451e465b5bbedfddf7eb69.png)
+![](http://upload-images.jianshu.io/upload_images/4685968-2851b069fd954f6c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+ 
+![](http://upload-images.jianshu.io/upload_images/4685968-bd13b576b0954fdc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 版本——占 4 位，指 IP 协议的版本,目前的 IP 协议版本号为 4 (即 IPv4)
 - 首部长度——占 4 位，可表示的最大数值,是 15 个单位(一个单位为 4 字节),因此 IP 的首部长度的最大值是 60 字节
 - 区分服务——占 8 位，用来获得更好的服务,在旧标准中叫做服务类型，但实际上一直未被使用过。1998 年这个字段改名为区分服务。只有在使用区分服务（DiffServ）时，这个字段才起作用。在一般的情况下都不使用这个字段
 -  总长度——占 16 位，指首部和数据之和的长度，单位为字节，因此数据报的最大长度为 65535 字节。总长度必须不超过最大传送单元 MTU
 
-- 最大传送单元 MTU
-![](https://img-blog.csdnimg.cn/img_convert/21d4d66f03d8fc4b09ed0859dcdd306a.png)
-![](https://img-blog.csdnimg.cn/20201210230136250.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20201210230221432.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+![最大传送单元 MTU](http://upload-images.jianshu.io/upload_images/4685968-0a8765b69d6c3f01.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
-- IP 数据报分片![](https://img-blog.csdnimg.cn/img_convert/fc39ceaa459bf4a81a903c5f92649c3d.png)
-- 生存时间(8 位)记为 TTL (Time To Live)数据报在网络中可通过的路由器数的最大值
-- 协议(8 位)字段指出此数据报携带的数据使用何种协议,以便目的主机的 IP 层将数据部分上交给哪个处理过程
 
 
-- 协议字段作用
-![](https://img-blog.csdnimg.cn/img_convert/809fcb267d048a223ba137663a6b98a9.png)
-- 首部检验和(16 位)字段只检验数据报的首部，不检验数据部分。这里不采用 CRC 检验码而采用简单的计算方法
-![image.png](https://img-blog.csdnimg.cn/img_convert/fefb1aed4396c6994455d6b0a2711374.png)
+
+![IP 数据报分片](http://upload-images.jianshu.io/upload_images/4685968-d7227146af98413b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 生存时间(8 位)记为 TTL (Time To Live)数据报在网络中可通过的路由器数的最大值
+- 协议(8 位)字段指出此数据报携带的数据使用何种协议,以便目的主机的 IP 层将数据部分上交给哪个处理过程
+![协议字段作用](http://upload-images.jianshu.io/upload_images/4685968-d399498530a54f84.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 首部检验和(16 位)字段只检验数据报的首部,不检验数据部分。这里不采用 CRC 检验码而采用简单的计算方法
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-553e1feb1fe63a65.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 源地址和目的地址都各占 4 字节
-## 4.2 IP分片与重组
+## 4.2 IP分片(重点)与重组
 ### 大IP分组向较小MTU链路转发时，可以被“分片” (fragmented)
 - 1个IP分组分为多片IP分组
 - IP分片到达目的主机后进行“重组” (reassembled)
@@ -77,7 +60,7 @@ IP协议利用一个计数器，每产生IP分组计数器加1，作为该IP分
 - 标志位(flag)字段占 3 位，目前只有前两位有意义
  MF (More Fragment)
 DF (Don't Fragment) 
-![](https://img-blog.csdnimg.cn/img_convert/ad57430ba0ecdde4460ef322ad8693ac.png)
+![](http://upload-images.jianshu.io/upload_images/4685968-01f12a5b9a85d60f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 片偏移(13 位)指出:一个IP分组分片封装原IP分组数据的相对偏移量
   - 片偏移字段以8字节为单位
 ## IP分片过程
@@ -87,12 +70,12 @@ DF (Don't Fragment)
 通常分片时，除最后一分片，其他分片均分为MTU允许的最大分片 
  - 一个最大分片可封装的数据应该是8的倍数，因此，一个
 最大分片可封装的数据为：
-![image.png](https://img-blog.csdnimg.cn/img_convert/1cf77f015fd8ba72d48f6ee2eab8d435.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-eb4784838e4c7f87.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 需要的总片数为：
-![image.png](https://img-blog.csdnimg.cn/img_convert/9fcb880a2591e7262e28207361493543.png)
-![image.png](https://img-blog.csdnimg.cn/img_convert/985f5d3311caf1fd3ee855c59e695ce5.png)
-![image.png](https://img-blog.csdnimg.cn/img_convert/5452d04f7bf735ab8a7c60bf327802d7.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-7895dddd686a3898.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-7d777cb631b06a17.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-0e5d3fafe9c5d6e6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 #  IP编址
 ## IP编址(addressing)
 - IP分组：
@@ -102,110 +85,111 @@ DF (Don't Fragment)
   - 实现网络层功能
   - 路由器通常有多个接口
   - 主机通常只有一个或两个接口 (e.g.，有线的以太网接口，无线的802.11接口)
-![](https://img-blog.csdnimg.cn/img_convert/575317236f15467203df1221ad6d319a.png)
+![](http://upload-images.jianshu.io/upload_images/4685968-87731ea03957edb4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - IP地址: 32比特(IPv4)编号标识主机、路由器的接口
 - IP地址与每个接口关联
 - 怎样为接口分配IP地址呢？
-![](https://img-blog.csdnimg.cn/img_convert/91214ec29ff554f78144f38ec855be0b.png)
+![](http://upload-images.jianshu.io/upload_images/4685968-f6949546f95bfd7b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## IP子网（Subnets）
 - IP地址:
   - 网络号(NetID) – 高位比特
   - 主机号(HostID) – 低位比特
-![image.png](https://img-blog.csdnimg.cn/img_convert/744191ae1d64f0fbddd744b3c4a6272f.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-be892ff8f6152782.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - IP子网：
   - IP地址具有相同网络号的设
 备接口
   - 不跨越路由器（第三及以上层网络设备）可以彼此物理联通的接口
-![image.png](https://img-blog.csdnimg.cn/img_convert/86781b4b7e92610460ab018bffd9e4a2.png)
-![image.png](https://img-blog.csdnimg.cn/img_convert/65dfa841d7ec64b4ed9e943312865bd4.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-3d9aaf5ce5d7be97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-c692ad750eca5933.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 # 有类IP地址
 ## IP地址(Addresses)
-![image.png](https://img-blog.csdnimg.cn/img_convert/e6e096fc6d66ffd37beae9fec638330c.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-5a02e1bdaac37e74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 特殊IP地址
-![image.png](https://img-blog.csdnimg.cn/img_convert/628d306da82f616aecc918f118a4afa3.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-f8b975706dbacd0b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 私有（Private）IP地址
-![image.png](https://img-blog.csdnimg.cn/img_convert/9a1f2e13ec78033ec66f049a3470b5ed.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-0528487a5306d421.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 
 # 5 IP子网划分与子网掩码(重点)
 ## 子网划分(Subnetting)？
 如何区分一个IP子网更小范围网络（子网）?
 - 子网划分
-![](https://img-blog.csdnimg.cn/img_convert/978609dec308f3473852180d18b56ba6.png)
+![](http://upload-images.jianshu.io/upload_images/4685968-ee6f79b9ca24e601.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - IP地址:
   - 网络号(NetID) – 高位比特
   - 子网号(SubID) – 原网络主机号部分比特
   - 主机号(HostID) – 低位比特
-![                                                                                                                                                                                                                                                                                                                                                                        ](https://img-blog.csdnimg.cn/img_convert/b648be2abd7382b25252f1a59836d69a.png)
+![                                                                                                                                                                                                                                                                                                                                                                        ](http://upload-images.jianshu.io/upload_images/4685968-859af5207bb36a60.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 子网掩码
-![image.png](https://img-blog.csdnimg.cn/img_convert/185412b0e82210aba6eca26c6a34a7c3.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-bc4e48fd7e946e03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 子网划分
-![image.png](https://img-blog.csdnimg.cn/img_convert/edd9f71872d9a70a77c1ae042ab3a057.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-41a6771c1cf27c54.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 子网掩码的应用
-![image.png](https://img-blog.csdnimg.cn/img_convert/721eba08d019cb17ee2bba8f464b89c8.png)
-![image.png](https://img-blog.csdnimg.cn/img_convert/b4b603dc9a35729897d91d5606402f45.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-144497f6ddcb5a6f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-77346ea770edc3ec.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
 ## 5.1 从两级 IP 地址到三级 IP 地址 
 在 ARPANET 的早期，IP 地址的设计确实不够合理。
 - IP 地址空间的利用率有时很低。 
 - 给每一个物理网络分配一个网络号会使路由表变得太大因而使网络性能变坏。 
 - 两级的 IP 地址不够灵活
 - 从 1985 年起在 IP 地址中又增加了一个“子网号字段”，使两级的 IP 地址变成为三级的 IP 地址。这种做法叫作划分子网(subnetting) 。划分子网已成为因特网的正式标准协议。
- 
-## 5.2 基本思路 
+ ## 5.2 基本思路 
 - 划分子网纯属一个单位内部的事情。单位对外仍然表现为没有划分子网的网络。
 - 从主机号借用若干个位作为子网号 subnet-id，而主机号 host-id 也就相应减少了若干个位。
 IP地址 ::= {<网络号>, <子网号>, <主机号>}       
 - 凡是从其他网络发送给本单位某个主机的 IP 数据报，仍然是根据 IP 数据报的目的网络号 net-id，先找到连接在本单位网络上的路由器。
 - 然后此路由器在收到 IP 数据报后，再按目的网络号 net-id 和子网号 subnet-id 找到目的子网。
 - 最后就将 IP 数据报直接交付目的主机。 
-![一个未划分子网的 B 类网络145.13.0.0](https://img-blog.csdnimg.cn/img_convert/662a9f8d44a4cb7a77528938489c67fb.png)
-![划分为三个子网后对外仍是一个网络 ](https://img-blog.csdnimg.cn/img_convert/7cd4c5c97625f6c1db9806e0e4205dff.png)
+![一个未划分子网的 B 类网络145.13.0.0](http://upload-images.jianshu.io/upload_images/4685968-904dfc760a53902c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![划分为三个子网后对外仍是一个网络 ](http://upload-images.jianshu.io/upload_images/4685968-1f00971471c7d18d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 5.3 划分子网后变成了三级结构 
 - 当没有划分子网时，IP 地址是两级结构。
 - 划分子网后 IP 地址就变成了三级结构。
 - 划分子网只是把 IP 地址的主机号 host-id 这部分进行再划分，而不改变 IP 地址原来的网络号 net-id
 ## 5.4 子网掩码(subnet mask)
 从一个 IP 数据报的首部并无法判断源/目的主机所连接的网络是否进行了子网划分,使用子网掩码可以找出 IP 地址中的子网部分
-![IP 地址的各字段和子网掩码](https://img-blog.csdnimg.cn/img_convert/76514e83930b648dad1b36b7c402c64a.png)
-![(IP 地址) AND (子网掩码) =网络地址](https://img-blog.csdnimg.cn/img_convert/f6eb4b8132fd1e9166c5bdb94f568320.png)
-![默认子网掩码](https://img-blog.csdnimg.cn/img_convert/d870f70f9c3fa3375d68d049245a163f.png)
+![IP 地址的各字段和子网掩码](http://upload-images.jianshu.io/upload_images/4685968-73d04eaee0f778c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![(IP 地址) AND (子网掩码) =网络地址](http://upload-images.jianshu.io/upload_images/4685968-4e6321abc5e2ecf1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![默认子网掩码](http://upload-images.jianshu.io/upload_images/4685968-4d7fb943816d1d7a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - 子网掩码是一个网络或一个子网的重要属性
 - 路由器在和相邻路由器交换路由信息时,必须把自己所在网络（或子网）的子网掩码告诉相邻路由器
 - 路由器的路由表中的每一个项目,除了要给出目的网络地址外,还必须同时给出该网络的子网掩码
 - 若一个路由器连接在两个子网上就拥有两个网络地址和两个子网掩码
-![IP 地址141.14.72.24，子网掩码255.255.192.0求网络地址](https://img-blog.csdnimg.cn/img_convert/ee2ad3ec5ef5bc64fad47733ef371fd9.png)
-![在上例中，子网掩码改为255.255.224.0求网络地址](https://img-blog.csdnimg.cn/img_convert/532d7a74857b7679d53027bda4c03838.png)
+![IP 地址141.14.72.24，子网掩码255.255.192.0求网络地址](http://upload-images.jianshu.io/upload_images/4685968-7048048d3f0850dc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![在上例中，子网掩码改为255.255.224.0求网络地址](http://upload-images.jianshu.io/upload_images/4685968-004cfca7702e22c2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 不同的子网掩码得出相同的网络地址。
 但不同的掩码的效果是不同的
 # 6 CIDR与路由聚合 
-![CIDR](https://img-blog.csdnimg.cn/img_convert/4958d40223bbd4f4c5864dd699673d0d.png)
-![CIDR与路由聚合](https://img-blog.csdnimg.cn/img_convert/666e39020c77019ae56e5f11b713a2fc.png)
-![路由聚合-上](https://img-blog.csdnimg.cn/img_convert/fdcb852e2714a41a07606f48cc7b1775.png)
-![路由聚合-下](https://img-blog.csdnimg.cn/img_convert/bf833d1d7058ecbcc5d535878e4fe0fc.png)
-![路由聚合](https://img-blog.csdnimg.cn/img_convert/283c9a822dc4ee8b11811e2f9dc2e8d1.png)
-![路由聚合](https://img-blog.csdnimg.cn/img_convert/f25cff4b0d32288a67896cb3f00386a0.png)
+![CIDR](http://upload-images.jianshu.io/upload_images/4685968-9cbce53f9f4d793e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![CIDR与路由聚合](http://upload-images.jianshu.io/upload_images/4685968-ee158d474830f478.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![路由聚合-上](http://upload-images.jianshu.io/upload_images/4685968-4b5d9dd825485a42.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![路由聚合-下](http://upload-images.jianshu.io/upload_images/4685968-1d542476d30f98a7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![路由聚合](http://upload-images.jianshu.io/upload_images/4685968-37b2e3203eef13df.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![路由聚合](http://upload-images.jianshu.io/upload_images/4685968-41c68ce5d209163d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 ## 地址聚合，给出一个地址，能够知道其所在地址块，并会计算地址块中地址的个数，最大最小地址；给出一个地址块，能够合理划分子网
 # DHCP协议
-![如何获得IP地址?-上](https://img-blog.csdnimg.cn/img_convert/0b4ea553ba85d61e1e4c3bdce9c44e94.png)
-![如何获得IP地址?-下](https://img-blog.csdnimg.cn/img_convert/e07b8b3c169d727de9dc05e2e0273bb4.png)
-![动态主机配置协议(DHCP)](https://img-blog.csdnimg.cn/img_convert/55605f1997cf8d6b7b699fdf0808766f.png)
-![DHCP工作过程示例-一](https://img-blog.csdnimg.cn/img_convert/d48c5f9dc388c8d9b5e73d6735c972ee.png)
-![DHCP工作过程示例-二](https://img-blog.csdnimg.cn/img_convert/2d1746013be04287fed6e93eb22b63f7.png)
-![DHCP工作过程示例-三](https://img-blog.csdnimg.cn/img_convert/92a0539a5eaa7be41e4e0a2e42356222.png)
+![如何获得IP地址?-上](http://upload-images.jianshu.io/upload_images/4685968-784b4affbf1557ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![如何获得IP地址?-下](http://upload-images.jianshu.io/upload_images/4685968-8b35496acadd25f5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![动态主机配置协议(DHCP)](http://upload-images.jianshu.io/upload_images/4685968-445d63f3f47a6b08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![DHCP工作过程示例-一](http://upload-images.jianshu.io/upload_images/4685968-f679bdb7dba5bdd0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![DHCP工作过程示例-二](http://upload-images.jianshu.io/upload_images/4685968-7c201e3d347aa831.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![DHCP工作过程示例-三](http://upload-images.jianshu.io/upload_images/4685968-f962c83a1d2a8973.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 # 网络地址转换(NAT)
-![网络地址转换](https://img-blog.csdnimg.cn/img_convert/6365105e5aed32797acc98d4759ee941.png)
-![image.png](https://img-blog.csdnimg.cn/img_convert/ad09ef0fd92e85add8d3e2833afec9d9.png)
-![image.png](https://img-blog.csdnimg.cn/img_convert/5a5700be14478e8e7d07b409ba42c9ba.png)
-![image.png](https://img-blog.csdnimg.cn/img_convert/7897a8133507ab9650d911f2068262f6.png)
-![image.png](https://img-blog.csdnimg.cn/img_convert/527b708ba89d8b58fea84d2cc26a30da.png)
-![NAT穿透问题](https://img-blog.csdnimg.cn/img_convert/44a91de01e878e0f50ab20800349e013.png)
-![NAT穿透问题](https://img-blog.csdnimg.cn/img_convert/51b9ab56433fffbf80029ec328e5ef0e.png)
-![NAT穿透问题](https://img-blog.csdnimg.cn/img_convert/16c8bea3f0f5148819b476966a8b3db4.png)
+![网络地址转换](http://upload-images.jianshu.io/upload_images/4685968-14e5fef049d84e5a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-b8706d31faf032b8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-1d695a379c6835a2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-f9c2917f0af50067.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-6449df8111ec1b0a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![NAT穿透问题](http://upload-images.jianshu.io/upload_images/4685968-f91c1d76ee317e38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![NAT穿透问题](http://upload-images.jianshu.io/upload_images/4685968-1fe1eb29657f6a71.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![NAT穿透问题](http://upload-images.jianshu.io/upload_images/4685968-6c43e9fcac91d05b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 # 路由算法
 
-# 7 理解RIP(Routing Information Protocol)及路由表更新过程
+# 7 理解RIP(Routing Information Protocol)及路由表更新过程(重点)
 ## 7.1 原理
 - 路由信息协议 RIP 是内部网关协议 IGP中最先得到广泛使用的协议。
 - RIP 是一种分布式的基于距离向量的路由选择协议，优点是简单。
@@ -215,7 +199,7 @@ IP地址 ::= {<网络号>, <子网号>, <主机号>}
 - 从一个路由器到非直接连接的网络的距离定义为所经过的路由器数加 1。
 - RIP 协议中的“距离”也称为“跳数”(hop count)，因为每经过一个路由器，跳数就加 1。
 - 这里的“距离”实际上指的是“最短距离”
-![image.png](https://img-blog.csdnimg.cn/img_convert/805b18b7d09fdd70191d6f9d1600dd79.png)
+![image.png](http://upload-images.jianshu.io/upload_images/4685968-796dd09b4957930c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 - RIP 认为一个好的路由就是它通过的路由器的数目少,即“距离短”
 - RIP 允许一条路径最多只能包含 15 个路由器。因此“距离”的最大值为16 时即相当于不可达。可见 RIP 只适用于小型互联网
 - RIP 不能在两个网络之间同时使用多条路由。RIP 选择一个具有最少路由器的路由（即最短路由），哪怕还存在另一条高速(低时延)但路由器较多的路由   
@@ -238,14 +222,14 @@ IP地址 ::= {<网络号>, <子网号>, <主机号>}
 else什么也不做
 - (3) 若 3 分钟还没有收到相邻路由器的更新路由表，则把此相邻路由器记为不可达路由器，即将距离置为16（16表示不可达）
 - (4) 返回
-![距离向量算法（例）](https://img-blog.csdnimg.cn/img_convert/ad6aba8e5c49c167e4c11bc6aefc8ff9.png)
+![距离向量算法（例）](http://upload-images.jianshu.io/upload_images/4685968-3830b36d4536cdbe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ## 7.6 路由器之间交换信息 
 - RIP协议让一个自治系统中的所有路由器都和自己的相邻路由器不断交换路由信息，并不断更新其路由表，使得从每一个路由器到每一个目的网络的路由都是最短的（即跳数最少）
 - 虽然所有的路由器最终都拥有了整个自治系统的全局路由信息，但由于每一个路由器的位置不同，它们的路由表当然也应当是不同的
 # 8 路由器的结构
 - 路由器是一种具有多个输入/出端口的专用计算机，其任务是转发分组。也就是说，将路由器某个输入端口收到的分组，按照分组要去的目的地（即目的网络），把该分组从路由器的某个合适的输出端口转发给下一跳路由器
 - 下一跳路由器也按照这种方法处理分组,直到该分组到达终点为止 
-![典型的路由器的结构 ](https://img-blog.csdnimg.cn/img_convert/409056b420404b12b386c9f6bca403d1.png)
+![典型的路由器的结构 ](http://upload-images.jianshu.io/upload_images/4685968-2e0b9a26b20157d8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 
 “转发”和“路由选择”的区别 
 - “转发”(forwarding)就是路由器根据转发表将收到的 IP 数据报从合适的端口转发出去。
@@ -256,4 +240,4 @@ else什么也不做
 - 网际组管理协议 IGMP (Internet Group Management Protocol) 
 为了使路由器知道多播组成员的信息
 - 多播路由选择协议 
-连接在局域网上的多播路由器还必须和因特网上的其他多播路由器协同工作，以便把多播数据报用最小代价传送给所有的组成员
\ No newline at end of file
+连接在局域网上的多播路由器还必须和因特网上的其他多播路由器协同工作，以便把多播数据报用最小代价传送给所有的组成员
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\350\277\220\350\276\223\345\261\202.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\350\277\220\350\276\223\345\261\202.md"
similarity index 72%
rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\350\277\220\350\276\223\345\261\202.md"
rename to "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\350\277\220\350\276\223\345\261\202.md"
index 942a83ad86..4ecd40cfe6 100644
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/OSI\345\210\206\345\261\202\346\250\241\345\236\213/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\350\277\220\350\276\223\345\261\202.md"
+++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\344\271\213\350\277\220\350\276\223\345\261\202.md"
@@ -2,96 +2,89 @@
 ## 1.1 进程间通信
 - 从通信和信息处理的角度看，运输层向应用层提供通信服务，它属于面向通信部分的最高层，同时也是用户功能中的最底层
 - 当网络的边缘部分中的两个主机使用网络的核心部分的功能进行端到端的通信时,只有位于网络边缘部分的主机的协议栈才有运输层,而网络核心部分中的路由器在转发分组时都只用到下三层的功能
-
-- 运输层为相互通信的应用进程提供了逻辑通信
-![](https://img-blog.csdnimg.cn/img_convert/c135f36866a86c4246a1421d3245bf1f.png)
+![运输层为相互通信的应用进程提供了逻辑通信](https://img-blog.csdnimg.cn/img_convert/c135f36866a86c4246a1421d3245bf1f.png)
 ## 1.2 应用进程间通信
 - 两个主机进行通信实际上是两个主机中的应用进程通信 
 - 应用进程间通信又称为端到端的通信 
 - 运输层的一个很重要的功能就是复用和分用
 应用层不同进程的报文通过不同的端口向下交到运输层，再往下就共用网络层提供的服务。
 - 运输层提供应用进程间的逻辑通信
-"逻辑通信"：运输层之间的通信好像是沿水平方向传送数据。但事实上这两个运输层之间并没有一条水平方向的物理连接。
-
-- 运输层协议和网络层协议的主要区别
-![](https://img-blog.csdnimg.cn/img_convert/847b3cf004a016651e7878ee336ec7cc.png)
+"逻辑通信"：运输层之间的通信好像是沿水平方向传送数据。但事实上这两个运输层之间并没有一条水平方向的物理连接
+![运输层协议和网络层协议的主要区别](https://img-blog.csdnimg.cn/img_convert/847b3cf004a016651e7878ee336ec7cc.png)
 ## 1.3 运输层的主要功能 
-- 为应用进程之间提供端到端的逻辑通信
+- 为应用进程之间提供端到端的逻辑通信（但网络层是为主机之间提供逻辑通信）
 - 对收到的报文进行差错检测
 - 需要有两种不同的运输协议，即面向连接的 TCP 和无连接的 UDP。
 ## 1.4 两种不同的运输协议
 - 运输层向高层用户屏蔽了下面网络核心的细节,它使应用进程看见的就是好像在两个运输层实体之间有一条端到端的逻辑通信信道
-- 当运输层采用面向连接的 TCP 协议时，尽管下层的网络都是不可靠的（只提供尽最大努力服务），但这种逻辑通信信道就相当于一条全双工的可靠信道
+- 当运输层采用面向连接的 TCP 协议时，尽管下面的网络是不可靠的（只提供尽最大努力服务），但这种逻辑通信信道就相当于一条全双工的可靠信道
 - 当运输层采用无连接的 UDP 协议时，这种逻辑通信信道是一条不可靠信道
 # 2 UDP与TCP异同
 ## 2.1 TCP 与 UDP 
 - 两个对等运输实体在通信时传送的数据单位叫作运输协议数据单元 TPDU (Transport Protocol Data Unit)。
 - TCP 传送的数据单位协议是 TCP 报文段(segment)
-- UDP（User Datagram Protocol） 传送的数据单位协议是 UDP 报文或用户数据报 
-
-- TCP/IP 体系中的运输层协议
-![](https://img-blog.csdnimg.cn/img_convert/bc69dec73738a8f0119c650387316f4b.png)
+- UDP 传送的数据单位协议是 UDP 报文或用户数据报 
+![TCP/IP 体系中的运输层协议 ](https://img-blog.csdnimg.cn/img_convert/bc69dec73738a8f0119c650387316f4b.png)
 - UDP 在传送数据之前不需要先建立连接。对方的运输层在收到 UDP 报文后，不需要给出任何确认。虽然 UDP 不提供可靠交付，但在某些情况下 UDP 是一种最有效的工作方式。
-- TCP 提供面向连接的服务，TCP 不提供广播或多播服务。由于 TCP 要提供**可靠的、面向连接**的运输服务，因此不可避免地增加了许多的开销。这不仅使协议数据单元的首部增大很多，还要占用许多的处理机资源
+- TCP 则提供面向连接的服务。TCP 不提供广播或多播服务。由于 TCP 要提供可靠的、面向连接的运输服务，因此不可避免地增加了许多的开销。这不仅使协议数据单元的首部增大很多，还要占用许多的处理机资源
 - 运输层的 UDP 用户数据报与网际层的IP数据报有很大区别。IP 数据报要经过互连网中许多路由器的存储转发，但 UDP 用户数据报是在运输层的端到端抽象的逻辑信道中传送的。
 - TCP 报文段是在运输层抽象的端到端逻辑信道中传送，这种信道是可靠的全双工信道。但这样的信道却不知道究竟经过了哪些路由器，而这些路由器也根本不知道上面的运输层是否建立了 TCP 连接。 
 
 ## 3.1 运输层的端口 
-运行在计算机中的进程是用进程标识符来标志。运行在应用层的各种应用进程却不应当让os指派它的进程标识符。这是因为在因特网上使用os种类很多，而不同os又使用不同格式的进程标识符。为使运行不同os的计算机的应用进程能够互相通信，就必须用统一的方法对 TCP/IP 体系的应用进程进行标志 
+- 运行在计算机中的进程是用进程标识符来标志的。
+- 运行在应用层的各种应用进程却不应当让计算机操作系统指派它的进程标识符。这是因为在因特网上使用的计算机的操作系统种类很多，而不同的操作系统又使用不同格式的进程标识符。
+-   为了使运行不同操作系统的计算机的应用进程能够互相通信，就必须用统一的方法对 TCP/IP 体系的应用进程进行标志。 
 ## 3.2 需要解决的问题 
-由于进程的创建、撤销都是动态的，发送方几乎无法识别其他机器上的进程。
-有时我们会改换接收报文的进程，但无需通知所有发送方。我们往往需要利用目的主机提供的功能来识别终点，而无需知道实现这个功能的进程。
-解决这个问题的方法就是在运输层使用协议端口号(protocol port number)，简称为端口(port)。
-虽然通信的终点是应用进程，但我们可以把端口想象是通信的终点，因为我们只需将要传送的报文交到目的主机的某个合适的目的端口，后续工作（即最后交付目的进程）就由 TCP 完成。
-
-### 软件端口与硬件端口
-- 在协议栈层间的抽象的协议端口是软件端口
-- 路由器或交换机上的端口是硬件端口
+- 由于进程的创建和撤销都是动态的，发送方几乎无法识别其他机器上的进程。
+- 有时我们会改换接收报文的进程，但并不需要通知所有发送方。
+- 我们往往需要利用目的主机提供的功能来识别终点，而不需要知道实现这个功能的进程。
+- 解决这个问题的方法就是在运输层使用协议端口号(protocol port number)，或通常简称为端口(port)。
+虽然通信的终点是应用进程，但我们可以把端口想象是通信的终点，因为我们只要把要传送的报文交到目的主机的某一个合适的目的端口，剩下的工作（即最后交付目的进程）就由 TCP 来完成。
+
+软件端口与硬件端口
+- 在协议栈层间的抽象的协议端口是软件端口。
+- 路由器或交换机上的端口是硬件端口。
 - 硬件端口是不同硬件设备进行交互的接口，而软件端口是应用层的各种协议进程与运输实体进行层间交互的一种地址
 ## 3.3 TCP 的端口 
-端口用一个 16 位端口号进行标志。端口号只具有本地意义，即端口号只是为了标志本计算机应用层中的各进程。在因特网中不同计算机的相同端口号无任何联系。
-## 3.4 端口的分类
-- 熟知端口，0~1023
-- 登记端口号，1024~49151，为没有熟知端口号的应用程序使用的。使用这个范围的端口号必须在 IANA 登记，以防止重复
-- 客户端口号或短暂端口号，49152~65535，留给客户进程选择暂时使用。当服务器进程收到客户进程的报文时，就知道了客户进程所使用的动态端口号。通信结束后，这个端口号可供其他客户进程以后使用
-# 4 TCP（Transmission Control Protocol）
-TCP是**传输控制协议**，一种**面向连接**的、**可靠的**、**基于字节流**的传输层通信协议,由IETF的RFC 793定义。
-
-## 4.1 特点 
-### 面向连接
-连接需要先创建再使用，创建连接的三次握手有一定开销。两个使用 TCP的应用在交换数据前必须先建立一个 TCP 连接，在一个 TCP 连接中，仅有两方进行彼此通信，广播和多播不能用于 TCP
-### 每条 TCP 连接只能有两个端点(endpoint)
-每条 TCP 连接只能是点对点（一对一） 
-### 可靠交付
-
-### 全双工通信
-
-### 面向字节流
-两个应用程序通过 TCP 连接，TCP 不在字节中插入记录标识符。TCP对字节流内容不做任何编解码解释，不知道传输的字节流数据是二进制数据还是 ASCII 字符或其它类型数据，对字节流的编解码由TCP连接双方的应用层协商。 
-字节是发送数据的最小单元，TCP协议本身无法区分哪几个字节是完整的消息体，也无法感知是否有多个客户端在使用同一个TCP连接，TCP只是一个读写数据的管道。
-- TCP 面向流的概念
-![](https://img-blog.csdnimg.cn/img_convert/d6261fff949753fd3854eec394121778.png)
-![](https://img-blog.csdnimg.cn/20210710223506585.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## 注意
- - TCP 连接是一条虚连接而不是一条真正的物理连接
- - TCP 对应用进程一次把多长的报文发送到TCP 的缓存中是不关心的
- - TCP 根据对方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少个字节（而UDP 发送的报文长度是应用进程给出的）
- - TCP 可把太长的数据块划分短一些再传送
+- 端口用一个 16 位端口号进行标志。
+- 端口号只具有本地意义，即端口号只是为了标志本计算机应用层中的各进程。在因特网中不同计算机的相同端口号是没有联系的。
+## 3.4 三类端口 
+- 熟知端口，数值一般为 0~1023。
+- 登记端口号，数值为1024~49151，为没有熟知端口号的应用程序使用的。使用这个范围的端口号必须在 IANA 登记，以防止重复。
+- 客户端口号或短暂端口号，数值为49152~65535，留给客户进程选择暂时使用。当服务器进程收到客户进程的报文时，就知道了客户进程所使用的动态端口号。通信结束后，这个端口号可供其他客户进程以后使用
+# 4 TCP
+## 4.1 TCP 最主要的特点 
+- 面向连接的运输层协议
+面向连接意味着两个使用 TCP的应用在交换数据前必须先建立一个 TCP 连接,在一个 TCP 连接中，仅有两方进行彼此通信，广播和多播不能用于 TCP
+- 每一条 TCP 连接只能有两个端点(endpoint)
+每一条 TCP 连接只能是点对点的（一对一） 
+- 提供可靠交付的服务
+- 提供全双工通信
+- 面向字节流
+字节流服务：两个应用程序通过 TCP 连接，TCP 不在字节中插入记录标识符 
+TCP对字节流的内容不做任何解释,不知道传输的字节流数据是二进制数据还是 ASCII 字符或其他类型数据,对字节流的解释由TCP连接双方的应用层 
+
+![TCP 面向流的概念](https://img-blog.csdnimg.cn/img_convert/d6261fff949753fd3854eec394121778.png)
+### 应当注意
+  - TCP 连接是一条虚连接而不是一条真正的物理连接
+  - TCP 对应用进程一次把多长的报文发送到TCP 的缓存中是不关心的
+  - TCP 根据对方给出的窗口值和当前网络拥塞的程度来决定一个报文段应包含多少个字节（而UDP 发送的报文长度是应用进程给出的）
+  - TCP 可把太长的数据块划分短一些再传送
 TCP 也可等待积累有足够多的字节后再构成报文段发送出去
 ## 4.2 TCP 的连接 
-TCP 把连接作为最基本的抽象，每一条 TCP 连接有两个端点。TCP 连接的端点不是主机、主机的IP 地址、应用进程、运输层的协议端口，TCP 连接的端点叫做套接字(socket)！端口号拼接到(contatenated with) IP 地址即构成了套接字。
+- TCP 把连接作为最基本的抽象
+- 每一条 TCP 连接有两个端点
+- TCP 连接的端点不是主机,不是主机的IP 地址,不是应用进程,也不是运输层的协议端口.TCP 连接的端点叫做套接字(socket)
+- 端口号拼接到(contatenated with) IP 地址即构成了套接字
 
 ### 4.2.1 套接字 (Socket)
-Socket = (IP地址: 端口号)    。每一条 TCP 连接唯一地被通信两端的两个端点（即两个套接字）所确定，即：
-```bash
+- 套接字 Socket = (IP地址: 端口号)    
+每一条 TCP 连接唯一地被通信两端的两个端点（即两个套接字）所确定
+即：
 TCP 连接 ::= {socket1, socket2} 
-          = {(IP1: port1), (IP2: port2)}  
-```
-所以一个 socket 就是一个网络通信资源组。
-
-#### Socket的多重含义
-- 应用编程接口 API 称为 socket API，简称为 socket
+             = {(IP1: port1), (IP2: port2)}  
+#### Socket的多层含义
+- 应用编程接口 API 称为 socket API, 简称为 socket
 - socket API 中使用的一个函数名也叫作 socket
 - 调用 socket 函数的端点称为 socket
 - 调用 socket 函数时其返回值称为 socket 描述符，可简称为 socket
@@ -99,7 +92,7 @@ TCP 连接 ::= {socket1, socket2}
  
 ### 4.2.2 三次握手
 - TCP建立连接的三次握手
-![](https://img-blog.csdnimg.cn/20201111201028208.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20201111201028208.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
 
 - 第一次握手
 建立连接时，客户端发送syn包(syn=j)到服务器，进入`SYN_SEND`态，待服务器确认
@@ -109,12 +102,11 @@ TCP 连接 ::= {socket1, socket2}
 - 第三次握手
 客户端收到服务器的SYN＋ACK包，向服务器发送确认包ACK(ack=k+1)，此包发送完毕，客户端和服务器进入` ESTABLISHED`状态，完成三次握手 
 
-完成三次握手，也就建立了客户端与服务端的连接。客户端与服务器开始传送数据。一个完整的三次握手也就是：请求---应答---再次确认。
-
-那说到现在，到底什么是连接？也就是经过 TCP 三次握手后，双方主机准备好数据传输的资源（比如各种数据结构）。
-
+完成三次握手，客户端与服务器开始传送数据。一个完整的三次握手也就是：
+请求---应答---再次确认
 ### 4.2.3 四次挥手
 由于TCP连接是全双工，因此每个方向都必须`单独进行关闭`。
+
 #### 原则
 当一方完成它的数据发送任务后，就能发送一个FIN来终止这个方向的连接。
 收到一个 FIN只代表这一方向上没有数据流动，一个TCP连接在收到一个FIN后仍能发送数据。
@@ -126,11 +118,6 @@ TCP 连接 ::= {socket1, socket2}
 - B关闭与A的连接，发FIN给A
 - A发ACK报文确认，并将确认序号设置为收到序号加1
 ![](https://img-blog.csdnimg.cn/img_convert/2f85b4f054b6c614e1a9a77464ad1c2a.png)
-
-如下图中上框就是三次挥手，下框内即是四次挥手
-![](https://img-blog.csdnimg.cn/20210322181905895.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
 ### 4.2.4 为什么建立连接是三次握手，而关闭连接却四次握手
 服务端的`listen`状态下的socket当收到syn报文的连接请求后，它可把ACK和SYN（ACK起应答作用，SYN起同步作用）放在同一个报文发送。
 
@@ -174,12 +161,6 @@ TCP 的可靠传输机制用字节的序号进行控制:所有的确认都是基
 TCP 两端的四个窗口经常处于动态变化中
 
 TCP 连接的往返时间 **RTT** 也不是固定不变的:需要使用特定的算法估算较为合理的重传时间
-
-## 应用场景
-聊天消息传输、推送，单人语音、视频聊天等。几乎UDP能做的都能做，但需要考虑复杂性、性能问题。
-
-## 限制
-无法进行广播、多播等操作
 # 5 TCP 报文段的首部格式 
 ![TCP 报文段结构](https://img-blog.csdnimg.cn/img_convert/089aee8bee7d09961d61151719407881.png)
 - 源/目的端口——各占 2 字节
@@ -265,9 +246,7 @@ TCP窗口的单位是字节
 - 流量控制
   - 在给定的发送端和接收端之间的点对点通信量的控制
   - 抑制发送端发数据的速率，以便使接收端来得及接收
-
-- 拥塞控制所起的作用
-![](https://img-blog.csdnimg.cn/img_convert/4403f050b0233630ea5034bdc3e98d67.png)
+![拥塞控制所起的作用 ](https://img-blog.csdnimg.cn/img_convert/4403f050b0233630ea5034bdc3e98d67.png)
 ## 7.2 拥塞控制的一般原理 
 拥塞控制是很难设计的，因为它是一个动态的问题
 
@@ -294,10 +273,8 @@ TCP窗口的单位是字节
 - 在每收到一个对新的报文段的确认后，将拥塞窗口加 1，即增加一个 MSS 的数值
 - 用这样的方法逐步增大发送端的拥塞窗口 cwnd，可以使分组注入到网络的速率更加合理 
 
-
-- 发送方每收到一个对新报文段的确认（重传的不算在内）就使 cwnd 加 1
-![](https://img-blog.csdnimg.cn/20210221135936183.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
+![发送方每收到一个对新报文段的确认
+（重传的不算在内）就使 cwnd 加 1](http://upload-images.jianshu.io/upload_images/4685968-22762ac522f6cdf9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ### 传输轮次(transmission round)
 - 使用慢开始算法后，每经过一个传输轮次，拥塞窗口 cwnd 就加倍 
 - 一个传输轮次所经历的时间其实就是往返时间 RTT
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(3)-DHCP & IP\347\232\204\"\345\255\275\347\274\230\".md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(\344\270\211) - DHCP & IP\347\232\204\"\345\255\275\347\274\230\".md"
similarity index 78%
rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(3)-DHCP & IP\347\232\204\"\345\255\275\347\274\230\".md"
rename to "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(\344\270\211) - DHCP & IP\347\232\204\"\345\255\275\347\274\230\".md"
index f65cce8f8a..b846004c1a 100644
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(3)-DHCP & IP\347\232\204\"\345\255\275\347\274\230\".md"	
+++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(\344\270\211) - DHCP & IP\347\232\204\"\345\255\275\347\274\230\".md"	
@@ -1,155 +1,211 @@
-和其他机器通讯，就需要一个通讯地址，要给网卡配置这样一个地址。
+![](https://ask.qcloudimg.com/http-save/1752328/c8ix1ovhb3.png)和其他机器通讯，就需要一个通讯地址，要给网卡配置这么一个地址。
 
 # 1 配置IP地址
-可使用：
-- ifconfig
-- ip addr
 
-设置后，用这俩命令，将网卡up一下，即可开始工作。
+可以使用ifconfig，也可以使用ip addr。设置好了以后，用这两个命令，将网卡up一下，就可以开始工作了。
 
 ## 1.1 net-tools
-```bash
+
+```
 $ sudo ifconfig eth1 10.0.0.1/24
 $ sudo ifconfig eth1 up
 ```
+
 ## 1.2 iproute2
-```bash
+
+```
 $ sudo ip addr add 10.0.0.1/24 dev eth1
 $ sudo ip link set up eth1
 ```
-若配置的是一个和谁都无关的地址呢？
-例如，旁边机器都是192.168.1.x，我就配置个16.158.23.6，会咋样？
-包发不出去！
-
-这是为啥？
-***192.168.1.6*** 就在你这台机器的旁边，甚至在同一交换机，而你把机器地址设为 ***16.158.23.6***。
-在这台机器，企图去ping ***192.168.1.6***，觉得只要将包发出去，同一交换机的另一台机器应该马上就能收到，是嘛？
-Linux不是这样的，它并不智能，你眼睛看到那台机器就在旁边，Linux则是根据自己的逻辑处理的：
-**`只要是在网络上跑的包，都是完整的，可以有下层没上层，绝对不可能有上层没下层`**。
-所以它：
-- 有自己的源IP地址 ***16.158.23.6***
-- 也有目标IP地址 ***192.168.1.6***
-
-但包发不出去，是因为MAC层还没填。自己的MAC地址自己肯定知道，但目标MAC填啥？
-是不是该填 ***192.168.1.6*** 机器的MAC地址？
-不是!
+
+如果配置的是一个和谁都不搭嘎的地址呢？
+
+例如，旁边的机器都是192.168.1.x，我非得配置一个16.158.23.6，会出现什么现象呢？
+
+不会出现任何现象，就是包发不出去呗
+
+为什么发不出去呢？
+
+_**192.168.1.6**_ 就在你这台机器的旁边，甚至在同一交换机，而你把机器的地址设为 _**16.158.23.6**_
+
+在这台机器上，你企图去ping _**192.168.1.6**_，觉得只要将包发出去，同一个交换机的另一台机器马上就能收到，是嘛？
+
+可Linux不是这样的，没你想得那么智能
+
+你用肉眼看到那台机器就在旁边，它则需要根据自己的逻辑处理
+
+**`只要是在网络上跑的包，都是完整的，可以有下层没上层，绝对不可能有上层没下层`**
+
+所以，它有自己的源IP地址 _**16.158.23.6**_，也有目标IP地址 _**192.168.1.6**_，但包发不出去，这是因为MAC层还没填
+
+自己的MAC地址自己知道,但目标MAC填啥呢？
+
+是不是填 _**192.168.1.6**_ 机器的MAC地址呢？
+
+当然不是!
+
 Linux会判断要去的这个地址和我是一个网段吗，或者和我的一个网卡是同一网段吗？
+
 只有是一个网段的，它才会发送ARP请求，获取MAC地址
+
 如果发现不是呢？
 
-Linux默认的逻辑，如果这是一个跨网段的调用，它不会直接将包发送到网络上，而是将包发送到网关。
+**`Linux默认的逻辑，如果这是一个跨网段的调用，它不会直接将包发送到网络上，而是将包发送到网关`**
 
 如果配置了网关，Linux会获取网关的MAC地址，然后将包发出去
-对于 ***192.168.1.6*** 机器，虽然路过家门的这个包，目标IP是它，但是无奈MAC地址不是它的，所以它的网卡是不会把包收进去的
+
+对于 _**192.168.1.6**_ 机器，虽然路过家门的这个包，目标IP是它，但是无奈MAC地址不是它的，所以它的网卡是不会把包收进去的
 
 **如果没有配置网关呢**？
+
 那包压根就发不出去
 
-如果将网关配置为 ***192.168.1.6*** 呢？
+如果将网关配置为 _**192.168.1.6**_ 呢？
+
 不可能，Linux不会让你配置成功
+
 因为 **`网关要和当前的网络至少一个网卡是同一个网段`** 
+
 怎能允你16.158.23.6的网关是192.168.1.6呢？
 
 所以，当你需要手动配置一台机器的网络IP时，一定要好好问问你的网管
+
 如果在机房里面，要去网管那申请，让他给你分配一段正确的IP地址
+
 当然，真正配置的时候，一定不是直接用命令配置的，而是放在一个配置文件里面
+
 **不同系统的配置文件格式不同，但是无非就是CIDR、子网掩码、广播地址和网关地址**
 
 # 2 DHCP - 动态主机配置协议
+
 配置IP后一般不能变，配置一个服务端的机器还可以，但如果是客户端的机器呢？
+
 我抱着一台笔记本电脑在公司里走来走去，或者白天来晚上走，每次使用都要配置IP地址，那可怎么办？还有人事、行政等非技术人员，如果公司所有的电脑都需要IT人员配置，肯定忙不过来啊。
 
 需要有一个自动配置的协议，即**动态主机配置协议（Dynamic Host Configuration Protocol），简称DHCP**
 
 那网管就轻松多了。只需要配置一段共享的IP地址
+
 每一台新接入的机器都通过DHCP协议，来这个共享的IP地址里申请，然后自动配置好就可
+
 等用完了，还回去，这样其他的机器也能用。
 
-**数据中心里面的服务器，IP一旦配置好，基本不会变
-这就相当于买房自己装修。DHCP的方式就相当于租房。你不用装修，都是帮你配置好的。你暂时用一下，用完退租就可以了。**
+\*\*数据中心里面的服务器，IP一旦配置好，基本不会变
+
+这就相当于买房自己装修。DHCP的方式就相当于租房。你不用装修，都是帮你配置好的。你暂时用一下，用完退租就可以了。\*\*
 
 # 3 DHCP的工作方式
+
 当一台机器新加入一个网络的时候，肯定啥情况都不知道，只知道自己的MAC地址
+
 怎么办？先吼一句，我来啦，有人吗？这时候的沟通基本靠“吼”
+
 这一步，我们称为DHCP Discover。
 
-新来的机器使用IP地址 ***0.0.0.0*** 发送了一个广播包，目的IP地址为 ***255.255.255.255***
+新来的机器使用IP地址 _**0.0.0.0**_ 发送了一个广播包，目的IP地址为 _**255.255.255.255**_
+
 广播包封装了UDP，UDP封装了BOOTP
+
 其实DHCP是BOOTP的增强版，但是如果去抓包的话，很可能看到的名称还是BOOTP协议
 
 在广播包里，新人大喊：我是新来的（Boot request），我的MAC地址是这个，我还没有IP，谁能给租给我个IP地址！
 
 - 格式就像
-![](https://img-blog.csdnimg.cn/20190820013104636.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
+![](https://ask.qcloudimg.com/http-save/1752328/7aajibaa43.png)
 
 如果一个网管在网络里面配置了**DHCP Server**，他就相当于这些IP的管理员
+
 立刻能知道来了一个“新人”
+
 这个时候，我们可以体会MAC地址唯一的重要性了
+
 当一台机器带着自己的MAC地址加入一个网络的时候，MAC是它唯一的身份，如果连这个都重复了，就没办法配置了。
 
 只有MAC唯一，IP管理员才能知道这是一个新人，需要租给它一个IP地址，这个过程我们称为**DHCP Offer**
+
 同时，DHCP Server为此客户保留为它提供的IP地址，从而不会为其他DHCP客户分配此IP地址。
 
 DHCP Offer的格式就像这样，里面有给新人分配的地址。
 
-![](https://img-blog.csdnimg.cn/20190820013258466.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
+![](https://ask.qcloudimg.com/http-save/1752328/3btvsn4ffv.png)
 
 DHCP Server 仍然使用广播地址作为目的地址
+
 因为，此时请求分配IP的新人还没有自己的IP
+
 DHCP Server回复说，我分配一个可用的IP给你，你看如何？
+
 除此之外，服务器还发送了子网掩码、网关和IP地址租用期等信息。
 
 新来的机器很开心，它的“吼”得到了回复，并且有人愿意租给它一个IP地址了，这意味着它可以在网络上立足了
+
 当然更令人开心的是，如果有多个DHCP Server，这台新机器会收到多个IP地址，简直受宠若惊!!!
 
 它会选择其中一个DHCP Offer，一般是最先到达的那个，并且会向网络发送一个DHCP Request广播数据包，包中包含客户端的MAC地址、接受的租约中的IP地址、提供此租约的DHCP服务器地址等，并告诉所有DHCP Server它将接受哪一台服务器提供的IP地址，告诉其他DHCP服务器，谢谢你们的接纳，并请求撤销它们提供的IP地址，以便提供给下一个IP租用请求者
-![](https://img-blog.csdnimg.cn/201908202336176.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+
+![](https://ask.qcloudimg.com/http-save/1752328/qsvfbm3y9b.png)
 
 还没得到DHCP Server的最后确认，客户端仍然使用0.0.0.0为源IP地址、255.255.255.255为目标地址进行广播
+
 在BOOTP里面，接受某个DHCP Server的分配的IP。
 
 当DHCP Server接收到客户机的DHCP request之后，会广播返回给客户机一个DHCP ACK消息包，表明已经接受客户机的选择，并将这一IP地址的合法租用信息和其他的配置信息都放入该广播包，发给客户机，欢迎它加入网络大家庭
-![](https://img-blog.csdnimg.cn/20190820233637478.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+
+![](https://ask.qcloudimg.com/http-save/1752328/2fgu26qeru.png)
 
 最终租约达成的时候，还是需要广播一下，让大家都知道。
 
 # IP地址的收回和续租
+
 既然是租房子，就是有租期的。租期到了，管理员就要将IP收回。
+
 如果不用的话，收回就收回了。就像你租房子一样，如果还要续租的话，不能到了时间再续租，而是要提前一段时间给房东说
+
 **DHCP也是这样**
 
 客户机会在租期过去50%的时候，直接向为其提供IP地址的DHCP Server发送DHCP request消息包
+
 客户机接收到该服务器回应的DHCP ACK消息包，会根据包中所提供的新的租期以及其他已经更新的TCP/IP参数，更新自己的配置
+
 这样，IP租用更新就完成了。
 
 网络管理员不仅能自动分配IP地址，还能帮你自动安装操作系统！
 
 # 预启动执行环境（PXE）
+
 数据中心里面的管理员可能一下子就拿到几百台空的机器，一个个安装操作系统，会累死的。
 
 所以管理员希望的不仅仅是自动分配IP地址，还要自动安装系统。装好系统之后自动分配IP地址，直接启动就能用了
 
 安装操作系统，应该有个光盘吧。数据中心里不能用光盘吧，想了一个办法就是，可以将光盘里面要安装的操作系统放在一个服务器上，让客户端去下载
+
 但是客户端放在哪里呢？它怎么知道去哪个服务器上下载呢？客户端总得安装在一个操作系统上呀，可是这个客户端本来就是用来安装操作系统的呀？
 
 其实，这个过程和操作系统启动的过程有点儿像。
+
 - 首先，启动BIOS,读取硬盘的MBR启动扇区，将GRUB启动起来
 - 然后将权力交给GRUB，GRUB加载内核、加载作为根文件系统的initramfs文件
 - 再将权力交给内核
 - 最后内核启动，初始化整个操作系统。
 
 安装操作系统的过程，只能插在BIOS启动之后了
+
 因为没安装系统之前，连启动扇区都没有。因而这个过程叫做预启动执行环境 **（Pre-boot Execution Environment），PXE**
 
 PXE协议分为客户端和服务器端，由于还没有操作系统，只能先把客户端放在BIOS里面
+
 当计算机启动时，BIOS把PXE客户端调入内存里面，就可以连接到服务端做一些操作了。
 
 首先，PXE客户端自己也需要有个IP地址
+
 因为PXE的客户端启动起来，就可以发送一个DHCP的请求，让DHCP Server给它分配一个地址。PXE客户端有了自己的地址，那它怎么知道PXE服务器在哪里呢？对于其他的协议，都好办，要么人告诉他
+
 例如，告诉浏览器要访问的IP地址，或者在配置中告诉它；例如，微服务之间的相互调用。
 
 但是PXE客户端启动的时候，啥都没有
+
 好在DHCP Server除了分配IP地址以外，还可以做一些其他的事情。这里有一个DHCP Server的一个样例配置：
+
 ```
 ddns-update-style interim;
 ignore client-updates;
@@ -167,31 +223,49 @@ subnet 192.168.1.0 netmask 255.255.255.0
 	next-server 192.168.1.180;
 }
 ```
+
 按照上面的原理，默认的DHCP Server是需要配置的，无非是我们配置IP的时候所需要的IP地址段、子网掩码、网关地址、租期等
+
 如果想使用PXE，则需要配置next-server，指向PXE服务器的地址，另外要配置初始启动文件filename
 
 这样PXE客户端启动之后，发送DHCP请求之后，除了能得到一个IP地址，还可以知道PXE服务器在哪里，也可以知道如何从PXE服务器上下载某个文件，去初始化操作系统。
 
 # 解析PXE的工作过程
+
 ## 启动PXE客户端
+
 通过DHCP协议告诉DHCP Server，我,穷b，打钱!
+
 DHCP Server便租给它一个IP地址，同时也给它PXE服务器的地址、启动文件pxelinux.0
 
 ## 初始化机器
+
 PXE客户端知道要去PXE服务器下载这个文件后，就可以初始化机器
+
 便开始下载(TFTP协议),还需要有一个TFTP服务器
+
 PXE客户端向TFTP服务器请求下载这个文件，TFTP服务器说好啊，于是就将这个文件传给它
 
 ## 执行文件
+
 然后，PXE客户端收到这个文件后，就开始执行这个文件
+
 这个文件会指示PXE客户端，向TFTP服务器请求计算机的配置信息pxelinux.cfg
+
 TFTP服务器会给PXE客户端一个配置文件，里面会说内核在哪里、initramfs在哪里。PXE客户端会请求这些文件。
 
 ## 启动Linux内核
+
 一旦启动了操作系统，以后就啥都好办了
-![](https://img-blog.csdnimg.cn/20190821003023985.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+
+![](https://ask.qcloudimg.com/http-save/1752328/cay61t0gaw.png)
 
 # 总结
+
 DHCP协议主要是用来给客户租用IP地址，和房产中介很像，要商谈、签约、续租，广播还不能“抢单”；
 
-DHCP协议能给客户推荐“装修队”PXE，能够安装操作系统，这个在云计算领域大有用处。
\ No newline at end of file
+DHCP协议能给客户推荐“装修队”PXE，能够安装操作系统，这个在云计算领域大有用处。
+
+# 参考
+
+- 趣谈网络协议
\ No newline at end of file
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(2)-\346\233\276\350\256\260\345\220\246,\346\237\245IP\345\234\260\345\235\200.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(\344\272\214) - \346\233\276\350\256\260\345\220\246,\346\237\245IP\345\234\260\345\235\200.md"
similarity index 56%
rename from "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(2)-\346\233\276\350\256\260\345\220\246,\346\237\245IP\345\234\260\345\235\200.md"
rename to "\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(\344\272\214) - \346\233\276\350\256\260\345\220\246,\346\237\245IP\345\234\260\345\235\200.md"
index a049f0b2ca..1cce1a4a7b 100644
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(2)-\346\233\276\350\256\260\345\220\246,\346\237\245IP\345\234\260\345\235\200.md"
+++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234(\344\272\214) - \346\233\276\350\256\260\345\220\246,\346\237\245IP\345\234\260\345\235\200.md"	
@@ -1,167 +1,219 @@
-- 1.1.1.1 不是测试用的，原来一直没分配，现在被用来做一个DNS了，宣传是比谷歌等公司的DNA服务更保护用户隐私
-- 255.255.255.255，代表有限广播，它的目标是网络中的所有主机
-- 0.0.0.0，通常代表未知的源主机。当主机采用DHCP动态获取IP地址而无法获得合法IP地址时，会用IP地址0.0.0.0来表示源主机IP地址未知
-- NID不能以数字127开头。NID 127被保留给内部回送函数，作为本机循环测试使用
-例如，使用命令ping 127.0.0.1测试TCP/IP协议栈是否正确安装。在路由器中，同样支持循环测试地址的使用。
-# 1 ip
-## 1.1 ifconfig V.S ip addr 
+![](https://ask.qcloudimg.com/http-save/1752328/cyt6u5rhid.png)
+
+先献上几个梗
+
+- 1.1.1.1 不是测试用的，原来一直没分配，现在被用来做一个DNS了，宣传是比谷歌等公司的dns服务
+更保护用户隐私。
+- IP地址255.255.255.255，代表有限广播，它的目标是网络中的所有主机。
+- IP地址0.0.0.0，通常代表未知的源主机。当主机采用DHCP动态获取IP地址而无法获得合法IP地址时，会用IP地址0.0.0.0来表示源主机IP地址未知。
+- NID不能以数字127开头。NID 127被保留给内部回送函数,作为本机循环测试使用。
+例如，使用命令ping 127.0.0.1测试TCP/IP协议栈是否正确安装。在路由器中，同样支持循环测试地址的使用。1 查ip
 - Windows - ipconfig
 - Linux上- ifconfig
 
 还有--- **ip addr**
 
->net-tools起源于BSD，自2001年起，Linux社区已经对其停止维护，而iproute2旨在取代net-tools，并提供了一些新功能。一些Linux发行版已经停止支持net-tools，只支持iproute2。
-net-tools通过procfs(/proc)和ioctl系统调用去访问和改变内核网络配置，而iproute2则通过netlink套接字接口与内核通讯。net-tools中工具的名字比较杂乱，而iproute2则相对整齐和直观，基本是ip命令加后面的子命令。
+ifconfig & ip addr的区别
+
+> net-tools起源于BSD，自2001年起，Linux社区已经对其停止维护，而iproute2旨在取代net-tools，并提供了一些新功能。一些Linux发行版已经停止支持net-tools，只支持iproute2。
+
+net-tools通过procfs(/proc)和ioctl系统调用去访问和改变内核网络配置，而iproute2则通过netlink套接字接口与内核通讯。
+
+net-tools中工具的名字比较杂乱，而iproute2则相对整齐和直观，基本是ip命令加后面的子命令。
+
 虽然取代意图很明显，但是这么多年过去了，net-tool依然还在被广泛使用，最好还是两套命令都掌握吧。
 
-- ip addr
-![](https://img-blog.csdnimg.cn/2019081823483347.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-显示这台机器上所有的网卡，大部分的网卡都有一个IP地址。
+想象你登录进入一个非常小的Linux系统，发现既没有_ifconfig_命令，也没有_ip addr_命令，
+
+是不是感觉这个系统没法用？
+
+可以自行安装net-tools和iproute2这两个工具
+
+- 运行ip addr。不出意外，应该会输出下面的内容
+![](https://ask.qcloudimg.com/http-save/1752328/82jzge91jg.png)
+
+该命令显示这台机器上所有的网卡
+
+大部分的网卡都有一个IP地址
+
+**IP地址是一个网卡在网络世界的通讯地址，相当于我们现实世界的门牌号码**
+
+既然是门牌号，不能大家都一样，不然就会冲突,快递就找不到地方了
+
+所以，有时候咱们的电脑弹出网络地址冲突，出现上不去网的情况，多半是IP地址冲突
+
+如上输出的结果，192.168.10.208就是一个IP地址
+
+地址被点分隔为四个部分，每个部分8bit，总共32位
+
+这样产生的IP地址的数量很快就不够用了
+
+于是就有了IPv6，也就是上面输出结果里面inet6 fe80::...
 
-**IP地址是一个网卡在网络世界的通讯地址，相当于门牌号码**
-既然是门牌号，不能大家都一样，不然就会冲突。所以，有时候咱们的电脑弹出网络地址冲突，出现上不去网的情况，多半是IP地址冲突。
+这个有128位，现在看来是够够的
 
-ip地址被点分隔为四个部分，每个部分8bit，总共32位。这样产生的IP地址的数量很快就不够用了。于是就有了IPv6，即inet6 fe80::...
-这个有128位，现在看来是够的。
-## 1.2 分类
-32位IP地址被分为5类：
-![](https://img-blog.csdnimg.cn/20190818235334304.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/2021071018153487.png)
-## 构成
-![](https://img-blog.csdnimg.cn/20210710183717791.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-A、B、 C类主要分为两部分：
-- 前部分 - 网络号
-- 后部分 - 主机号
+本来32位的IP地址就不够，还被分成了5类。现在想想，当时分配地址的时候，真是太奢侈了
 
-可以理解成大家都是1单元1号，但我是Java小区的，而你是PHP小区的。
+![](https://ask.qcloudimg.com/http-save/1752328/ahm1fa9bqx.png)
 
-C网广播地址一般为: 192.XXX.XXX.255 ( 比如：192.168.1.255 )
+在网络地址中，至少在当时设计的时候，对于A、B、 C类主要分两部分
 
-A、B、C三类地址所能包含的主机的数量：
-![](https://img-blog.csdnimg.cn/2019081823542586.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
-- C类地址能包含的最大主机数量实在太少了，只有254个。当年设计恐怕也没想到，现在估计一个网咖都不够用。
-- B类地址能包含的最大主机数量又太多。6万多台机器放在一个网络下面，一般的企业基本达不到这个规模，闲着的地址就是浪费。
+- 前面一部分是网络号
+- 后面一部分是主机号
+
+这很好理解，大家都是六单元1001号
+
+我是小区A的六单元1001号
+
+而你是小区B的六单元1001号。
+
+- 下面这个表格，详细地展示了A、B、C三类地址所能包含的主机的数量
+![](https://ask.qcloudimg.com/http-save/1752328/iu04jhmf47.png)
+这里面有个尴尬的事情，就是C类地址能包含的最大主机数量实在太少了，只有254个
+当时设计的时候恐怕没想到，现在估计一个网吧都不够用吧
+而B类地址能包含的最大主机数量又太多了。6万多台机器放在一个网络下面，一般的企业基本达不到这个规模，闲着的地址就是浪费。
 
 # 2 无类型域间选路（CIDR）
-这打破了原来设计的几类地址的做法，将32位的IP地址分为：网络号+主机号。
 
-10.100.122.2/24，这个IP地址中有一个斜杠，斜杠后面有个数字24，这种地址表示形式，就是**CIDR**。
-24表示32位中前24位是网络号，后8位是主机号。
+这打破了原来设计的几类地址的做法，将32位的IP地址一分为二
+
+- 前面是网络号
+- 后面是主机号
+
+10.100.122.2/24，这个IP地址中有一个斜杠，斜杠后面有个数字24
+
+这种地址表示形式，就是**CIDR**
+
+后面24的意32位中前24是网络号，后8位是主机号
 
 伴随着CIDR存在的
-- **广播地址**
-10.100.122.255 若发送这个地址，则所有10.100.122网络里面的机器都可以收到
-- **子网掩码**
+
+- 一个是**广播地址**
+10.100.122.255 如果发送这个地址，所有10.100.122网络里面的机器都可以收到
+- 另一个是**子网掩码**
 255.255.255.0
 
-将子网掩码和IP地址进行AND计算，得到**网络号**
+将子网掩码和IP地址进行AND计算,就可得到**网络号**
+
 - 前面三个255，转成二进制都是1
-1和任何数值取AND，都是原来数值，前三个数不变：10.100.122
+1和任何数值取AND，都是原来数值，因而前三个数不变，为10.100.122
 - 后面一个0，转换成二进制是0
 0和任何数值取AND，都是0，因而最后一个数变为0，合起来就是10.100.122.0
 
 # 3 公/私有IP地址
+
 日常工作，几乎不用划分A类、B类或者C类，很多人就忘记了这个分类，只记得CIDR
+
 但是有一点还是要注意的，就是**公有IP地址和私有IP地址**
-![](https://img-blog.csdnimg.cn/20190819233834172.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+![](https://ask.qcloudimg.com/http-save/1752328/rftycekj7l.png)
+
 上面的表格。表格最右列是私有IP地址段
+
 平时看到的数据中心里，办公室/家/学校的IP地址，一般都是私有IP地址段
+
 因为这些地址允许组织内部的IT人员自己管理、分配，而且可重复
+
 因此，你学校的某个私有IP地址段和我学校的可以是一样的。
 
 > 这就像每个小区有自己的楼编号和门牌号，你们小区可以叫6栋，我们小区也叫6栋，没有任何问题
-但是一旦出了小区，就需要使用公有IP地址。就像人民路888号，是国家统一分配的，不能两个小区都叫人民路888号。
+> 但是一旦出了小区，就需要使用公有IP地址。就像人民路888号，是国家统一分配的，不能两个小区都叫人民路888号。
 
 **公有IP地址有个组织统一分配，你需要去买**
+
 如果你搭建一个网站，给你学校的人使用，让你们学校的IT人员给你一个IP地址就行
+
 但是假如你要做一个类似网易163这样的网站，就需要有公有IP地址，这样全世界的人才能访问。
 
 表格中的**192.168.0.x**是最常用的私有IP地址
+
 你家里有Wi-Fi，对应就会有一个IP地址。一般你家里地上网设备不会超过256个，所以/24基本就够了
+
 有时候我们也能见到/16的CIDR，这两种是最常见的，也是最容易理解的。
 
 不需要将十进制转换为二进制32位，就能明显看出192.168.0是网络号，后面是主机号
+
 而整个网络里面的第一个地址192.168.0.1，往往就是你这个私有网络的出口地址
+
 例如，你家里的电脑连接Wi-Fi，Wi-Fi路由器的地址就是192.168.0.1
-![](https://img-blog.csdnimg.cn/20190819235121180.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)而192.168.0.255就是广播地址。一旦发送这个地址，整个192.168.0网络里面的所有机器都能收到。
+
+![](https://ask.qcloudimg.com/http-save/1752328/n3r1rqg895.png)而192.168.0.255就是广播地址。一旦发送这个地址，整个192.168.0网络里面的所有机器都能收到。
 
 但是也不总都是这样的情况。因此，其他情况往往就会很难理解，还容易出错。
 
-# 4 CIDR可不好算
-## 16.158.165.91/22
-这个CIDR，求该网络的第一个地址、子网掩码和广播地址。
-首先声明16.158.165.1是错的!
+# 4 一个容易“犯错”的CIDR
 
-/22不是8的整数倍，只能先变成二进制。
-- 16.158的部分不会动，占了前16位
-- 中间的165，二进制为‭10100101‬。除了前面的16位，还剩6位。所以，这8位中前6位是网络号，16.158.<101001>，而<01>.91是机器号。
+我们来看16.158.165.91/22这个CIDR
 
-第一个地址是16.158.<101001><00>.1，即16.158.164.1
-子网掩码是255.255.<111111><00>.0，即255.255.252.0
-广播地址为16.158.<101001><11>.255，即16.158.167.255。
+求一下这个网络的第一个地址、子网掩码和广播地址
 
-## IP : 192.168.124.7
-子网掩码：255.255.255.192
-网络地址：192.168.124.0
-广播地址：192.168.124.63
+你要是上来就写16.158.165.1，那就大错特错!!!
 
-```bash
-255.255.255.192=》11111111.11111111.11111111.11000000
-所以可划分网段：2^2=4个：
-0~63  192.168.124.7属于该网段
-64~127
-128~191
-192~255
-```
-所以广播地址为：192.168.124.63。所以广播地址最后一位不一定是 255。
+/22不是8的整数倍，不好办，只能先变成二进制来看
 
-## 广播通信问题
-主机一：192.168.124.7 子网掩码 : 255.255.255.192
-主机二: 192.168.124.100 子网掩码: 255.255.255.192
+- 16.158的部分不会动，它占了前16位
+- 中间的165，变为二进制为‭10100101‬。除了前面的16位，还剩6位。所以，这8位中前6位是网络号，16.158.<101001>，而<01>.91是机器号。
 
-主机一广播地址: 192.168.124.63
-主机二广播地址: 192.168.124.127
-所以无法互相通信。
+第一个地址是16.158.<101001><00>.1，即16.158.164.1
 
+子网掩码是255.255.<111111><00>.0，即255.255.252.0
+
+广播地址为16.158.<101001><11>.255，即16.158.167.255。
 
 D类是**组播地址**
+
 使用这一类地址，属于某个组的机器都能收到
+
 这有点类似在公司里面大家都加入了一个邮件组。发送邮件，加入这个组的都能收到
 
 在IP地址的后面有个scope
+
 - 对于eth0这张网卡来讲，是global，说明这张网卡是可以对外的，可以接收来自各个地方的包
 - 对于lo来讲，是host，说明仅可以供本机相互通信。
 lo全称是loopback，又称**环回接口**，往往会被分配到127.0.0.1这个地址
 这个地址用于本机通信，经过内核处理后直接返回，不会在任何网络中出现。
 
 # 5 MAC地址
+
 在IP地址的上一行是link/ether fa:16:3e:c7:79:75 brd ff:ff:ff:ff:ff:ff
+
 这个被称为**MAC地址**
+
 是一个网卡的物理地址，用十六进制，6个byte表示
 
 MAC地址号称全局唯一，不会有两个网卡有相同的MAC地址，而且网卡自生产出来，就带着这个地址
+
 很多人看到这里就会想，既然这样，整个互联网的通信，全部用MAC地址好了，只要知道了对方的MAC地址，就可以把信息传过去。
 
 这样当然是不行的
+
 **`一个网络包要从一个地方传到另一个地方，除了要有确定的地址，还需要有定位功能`**
+
 而有门牌号码属性的`IP地址，才是有远程定位功能`
 
 > 例如，你去XX市XX路XX号X楼X层找XX，你在路上问路，可能被问的人不知道X楼是哪个，但是可以给你指网商路怎么去
 > 但是如果你问一个人，你知道这个身份证号的人在哪里吗？可想而知，没有人知道。
 
 `MAC地址更像是身份证，是一个唯一的标识`
+
 它的唯一性设计是为了组网的时候，不同的网卡放在一个网络里面的时候，可以不用担心冲突
+
 从硬件角度，保证不同的网卡有不同的标识。
 
 MAC地址是有一定定位功能的，只不过范围非常有限
+
 > 你可以根据IP地址，找到XX市XX路XX号X楼X层，但是依然找不到我，你就可以靠吼了，大声喊身份证XXXX的是哪位？我听到了，我就会站起来说，是我啊
-但是如果你在上海，到处喊身份证XXXX的是哪位，我不在现场，当然不会回答，因为我在杭州不在上海。
+> 但是如果你在上海，到处喊身份证XXXX的是哪位，我不在现场，当然不会回答，因为我在杭州不在上海。
 
 所以，MAC地址的通信范围比较小，局限在一个子网里面
+
 例如，从192.168.0.2/24访问192.168.0.3/24是可以用MAC地址的
+
 一旦跨子网，即从192.168.0.2/24到192.168.1.2/24，MAC地址就不行了，需要IP地址起作用了
 
 # 6 网络设备的状态标识
-***`<BROADCAST,MULTICAST,UP,LOWER_UP>`***  叫**net_device flags，网络设备的状态标识**
+
+_**`<BROADCAST,MULTICAST,UP,LOWER_UP>`**_  叫**net\_device flags，网络设备的状态标识**
 
 - UP
 网卡处于启动的状态
@@ -169,26 +221,36 @@ MAC地址是有一定定位功能的，只不过范围非常有限
 网卡有广播地址，可以发送广播包
 - MULTICAST
 网卡可以发送多播包
-- LOWER_UP
+- LOWER\_UP
 L1是启动的，也即网线插着呢
 - MTU1500
 最大传输单元MTU为1500，这是以太网的默认值。
 
 网络包是层层封装的
+
 MTU是二层MAC层的概念。MAC层有MAC的头，以太网规定连MAC头带正文合起来，不允许超过1500个字节。正文里面有IP的头、TCP的头、HTTP的头。如果放不下，就需要分片来传输。
 
-- qdisc pfifo_fast (queueing discipline，排队规则)
+- qdisc pfifo\_fast (queueing discipline，排队规则)
 内核如果需要通过某个网络接口发送数据包，它都需要按照为这个接口配置的qdisc（排队规则）把数据包加入队列。
 
 最简单的qdisc是pfifo，它不对进入的数据包做任何的处理，数据包采用先入先出的方式通过队列
-pfifo_fast稍微复杂一些，它的队列包括三个波段（band）。在每个波段里面，使用先进先出规则。
+
+pfifo\_fast稍微复杂一些，它的队列包括三个波段（band）。在每个波段里面，使用先进先出规则。
 
 三个波段（band）的优先级也不相同。band 0的优先级最高，band 2的最低。如果band 0里面有数据包，系统就不会处理band 1里面的数据包，band 1和band 2之间也是一样。
 
 数据包是按照**服务类型（Type of Service，TOS）** 被分配到三个波段（band）里面的
+
 TOS是IP头里面的一个字段，代表了当前的包是高优先级的，还是低优先级的。
 
+队列是个好东西，后面我们讲云计算中的网络的时候，会有很多用户共享一个网络出口的情况，这个时候如何排队，每个队列有多粗，队列处理速度应该怎么提升，我都会详细为你讲解。
+
 # 7 总结
+
 - IP是地址，有定位功能；MAC是身份证，无定位功能
 - CIDR可以用来判断是不是本地人
-- IP分公有IP、私有IP
\ No newline at end of file
+- IP分公有的IP和私有的IP
+
+# 参考
+
+- 趣谈网络协议
\ No newline at end of file
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\343\200\220\345\233\276\350\247\243\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\221\347\211\251\347\220\206\345\261\202\345\222\214MAC\345\261\202\347\232\204\344\272\262\345\257\206\345\205\263\347\263\273.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\343\200\220\345\233\276\350\247\243\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\221\347\211\251\347\220\206\345\261\202\345\222\214MAC\345\261\202\347\232\204\344\272\262\345\257\206\345\205\263\347\263\273.md"
deleted file mode 100644
index aa0a5252be..0000000000
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\351\207\215\345\255\246\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/\343\200\220\345\233\276\350\247\243\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\343\200\221\347\211\251\347\220\206\345\261\202\345\222\214MAC\345\261\202\347\232\204\344\272\262\345\257\206\345\205\263\347\263\273.md"
+++ /dev/null
@@ -1,63 +0,0 @@
-机器有了IP，就能在网络和其他机器通信。
-#  物理层
-电脑连电脑时，需要配置这俩电脑的IP地址、子网掩码和默认网关。要想两台电脑能够通信，这三项必须配置成为一个网络，可以一个是192.168.0.1/24，另一个是192.168.0.2/24，否则不通。
-
-两台电脑间的网络包，包含MAC层。IP层要封装了MAC层才能将包放入物理层。
-
-至此两台电脑构成最小的局域网 - LAN。
-
-怎么把三台电脑连在一起？
-以前有Hub - 集线器。这种设备有多个口，可以连接多台电脑。不同于交换机，集线器没有大脑，完全工作在物理层。它会将自己收到的每一个字节，都复制到其他端口。这是物理层联通的方案。
-
-# 数据链路层
-Hub采取广播模式，若每一台电脑发出的包，局域网内每个电脑都能收到，那就麻烦了。必须解决如下问题（MAC层要解决的）：
-## 包发给谁？谁接收？
-这里用到一个物理地址 - **链路层地址**。但因该层主要解决媒体接入控制，所以常被称为**MAC地址**。
-
-解决这个问题牵扯该层的网络包格式。
-比如以太网，该层的最开始，就是目标MAC地址、源MAC地址。
-![](https://img-blog.csdnimg.cn/20210622182256653.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-大部分的类型是IP数据包，然后IP里面包含TCP、UDP，以及HTTP等。
-
-有了目标MAC地址，数据包在链路上广播，MAC的网卡才能发现，这个包是给它的。MAC的网卡接收这个包，然后打开IP包，发现IP地址也是自己的，再打开TCP包，发现端口是自己，也就是80，而nginx就是监听80。
-
-于是将请求提交给nginx，nginx返回一个网页。然后将网页需要发回请求的机器。然后层层封装，最后到MAC层。因为来时有源MAC地址，返回时，源MAC就变成目标MAC，返给请求的机器。
-## 有无发送顺序？
-MAC，Medium Access Control，媒体访问控制。就是控制在往媒体上发数据时，谁先发、谁后发。这个规则称为**多路访问**。比如如下方案：
-- 多车道
-每个车一个车道，你走你的，我走我的。这叫信道划分
-- 今天单号出行，明天双号出行
-这叫轮流协议
-- 不管啥事，有事儿先出门，发现特堵，就回去。错过高峰再出
-这叫随机接入协议，以太网就是这种。
-
-这就解决了媒体接入控制的问题。MAC层就是用来解决多路访问的堵车问题的。
-## 发送时出错，咋办？
-对于以太网，该层的最后是CRC，计算整个包是否在发送过程出错。
-
-当源机器知道目标机器，可将目标地址放入包，若不知道呢？
-一个广播的网络里面接入了N台机器，如何知道每个MAC地址是谁？即已知IP地址，求MAC地址的协议。
-![](https://img-blog.csdnimg.cn/20210622232702973.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-即：在一个局域网里，当知道了IP地址，不知道MAC咋办。
-![](https://img-blog.csdnimg.cn/20210622232924976.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-发送一个广播包，谁是这个IP谁来回答。具体询问和回答的报文就像下面这样：
-![](https://img-blog.csdnimg.cn/202106222330175.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-为避免每次都用ARP请求，机器本地也会进行ARP缓存。机器会不断上线下线，IP也可能会变，所以ARP的MAC地址缓存过一段时间会过期。
-
-# 交换机
-Hub是广播的，不管某个接口是否需要，所有Bit都会被发出去，然后让主机自行判断是否需要。
-这种方式，当路上车少时没问题，但车一多，产生冲突概率就高了。把不需要的包转发过去，也属于浪费。看来Hub这种一股脑转发的设备是不行的，需要更智能的。因为每个口都只连接一台电脑，这台电脑又不怎么变更IP和MAC地址，只需记住这台电脑的MAC地址，若目标MAC地址不是这台电脑，这口就不用转发了。
-
-所以需要知道目标MAC地址是否就是连接某个口的电脑的MAC地址。这就要一个能把MAC头拿下来，检查目标MAC地址，然后根据策略转发的设备 - 交换机。
-
-#### 交换机怎么知道每个口的电脑的MAC地址？
-交换机会学习。
-
-MAC1电脑将一个包发送给MAC2电脑，当这个包到达交换机，一开始交换机也不知道MAC2电脑在哪个口，它只能将包转发给除了来的那个口之外的其他所有的口。
-但这时，交换机会记住，MAC1是来自一个明确的口。以后有包的目的地址是MAC1的，直接发送到这个口。
-
-当交换机作为一个关卡一样，过了一段时间之后，就有了整个网络结构。这时，基本不用广播了，全部可准确转发。
-每个机器的IP地址会变，所在口也会变，所以交换机的学习结果，称为转发表，有过期时间。
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/JavaEE/\346\234\215\345\212\241\345\256\232\344\275\215\345\231\250\346\250\241\345\274\217\357\274\210Service Locator Pattern\357\274\211.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/JavaEE/\346\234\215\345\212\241\345\256\232\344\275\215\345\231\250\346\250\241\345\274\217\357\274\210Service Locator Pattern\357\274\211.md"
similarity index 100%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/JavaEE/\346\234\215\345\212\241\345\256\232\344\275\215\345\231\250\346\250\241\345\274\217\357\274\210Service Locator Pattern\357\274\211.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/JavaEE/\346\234\215\345\212\241\345\256\232\344\275\215\345\231\250\346\250\241\345\274\217\357\274\210Service Locator Pattern\357\274\211.md"
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\344\270\255\344\273\213\350\200\205\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\344\270\255\344\273\213\350\200\205\346\250\241\345\274\217.md"
similarity index 100%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\344\270\255\344\273\213\350\200\205\346\250\241\345\274\217.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\344\270\255\344\273\213\350\200\205\346\250\241\345\274\217.md"
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\216\237\345\236\213\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\216\237\345\236\213\346\250\241\345\274\217.md"
similarity index 96%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\216\237\345\236\213\346\250\241\345\274\217.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\216\237\345\236\213\346\250\241\345\274\217.md"
index 65a6552f52..d3c4514dc8 100644
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\216\237\345\236\213\346\250\241\345\274\217.md"
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\216\237\345\236\213\346\250\241\345\274\217.md"
@@ -90,4 +90,12 @@
 ![](https://upload-images.jianshu.io/upload_images/4685968-11755d686ccab05b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ![ArrayList](https://upload-images.jianshu.io/upload_images/4685968-e7eb3c8f98e5bc1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
 ![HashMap](https://upload-images.jianshu.io/upload_images/4685968-72510c2d11881a7f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
-![CacheKey in MyBatis](https://upload-images.jianshu.io/upload_images/4685968-08c9a9611ffac002.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
\ No newline at end of file
+![CacheKey in MyBatis](https://upload-images.jianshu.io/upload_images/4685968-08c9a9611ffac002.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\221\275\344\273\244\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\221\275\344\273\244\346\250\241\345\274\217.md"
similarity index 100%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\221\275\344\273\244\346\250\241\345\274\217.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\221\275\344\273\244\346\250\241\345\274\217.md"
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\244\207\345\277\230\345\275\225\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\244\207\345\277\230\345\275\225\346\250\241\345\274\217.md"
similarity index 100%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\244\207\345\277\230\345\275\225\346\250\241\345\274\217.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\244\207\345\277\230\345\275\225\346\250\241\345\274\217.md"
diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md"
new file mode 100644
index 0000000000..295400962c
--- /dev/null
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md"
@@ -0,0 +1,236 @@
+- 所牵涉源代码地址
+https://github.com/Wasabi1234/design-patterns
+# 0 简单工厂案例
+![](https://upload-images.jianshu.io/upload_images/4685968-7a2b52fa68ffee5e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-1814ce7ea3bed285.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-3353ca443c842e0c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-67b48971f4020fe8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-c9424893b63f6f45.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-da57e6568f523cd3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## JDK 应用实例
+### 日历类
+![](https://upload-images.jianshu.io/upload_images/4685968-6d0189ad2ca154b0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-50f2491fe7a2ac75.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-1cf0e5cd50176c08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 迭代器
+Collection 接口就相当于 VideoFactory
+![](https://upload-images.jianshu.io/upload_images/4685968-7f01c2df10f7b309.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+相当于各种具体的工厂,如 JavaVideoFactory
+![父类接口,子类实现](https://upload-images.jianshu.io/upload_images/4685968-c1969f53a9e8f9d2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+Itr 就是具体产品 JavaVideo
+![](https://upload-images.jianshu.io/upload_images/4685968-a737909bb84e4167.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 工厂应用
+#### 为解决 url 协议扩展使用
+![](https://upload-images.jianshu.io/upload_images/4685968-b924b06429f554ed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![Launcher#Factory静态类](https://upload-images.jianshu.io/upload_images/4685968-4e9beb0c810025a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+#### logback 应用
+![LoggerFactory#getLogger(String name)](https://upload-images.jianshu.io/upload_images/4685968-4033df2ab32a341d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-a5dfaa56b86b1b32.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-685e3cfb144801e9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## JDBC实例
+![直接注册 MySQL 驱动](https://upload-images.jianshu.io/upload_images/4685968-4a5d57ae6416a5af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-d311bc6304e17ef5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-def56f6f534fa472.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-d07bf98d6b57c227.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+返回值是一个抽象类,必有一子类实现其,看一下
+![](https://upload-images.jianshu.io/upload_images/4685968-b3580b7d4d7180b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-e36d91e68ff08352.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![通过间接继承此处理器](https://upload-images.jianshu.io/upload_images/4685968-4a27bb9c3b3ed64b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-d2a104c4bc566b56.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-3ec72ed07f9f2d4e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+这其中URLStreamHandler就相当于各种抽象产品,而其实现类即各种具体的产品
+URLStreamHandlerFactory就相当于 VideoFactory
+而如下 Factory 就相当于如 JavaVideoFactory/PythonVideoFactory
+![](https://upload-images.jianshu.io/upload_images/4685968-e0d2ec52f3edb023.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+## Logback实例
+![](https://upload-images.jianshu.io/upload_images/4685968-056b7e3e6eebb1c8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-888e10bb2d071322.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-fb5fcd3912e4f649.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 1 工厂方法模式案例
+## 1.1 简单工厂的升级
+![](https://upload-images.jianshu.io/upload_images/4685968-1e3130ff50160485.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![](https://upload-images.jianshu.io/upload_images/4685968-2edda92bd092d600.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-0751b26d4d587b5d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-2aa397d86f68fe93.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-aa45708a5fd3aa38.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+对造人过程进行分析，该过程涉及三个对象：女娲、八卦炉、三种不同肤色的人
+- 女娲可以使用场景类`Client`表示
+- 八卦炉类似于一个工厂，负责制造生产产品（即人类）
+- 三种不同肤色的人，他们都是同一个接口下的不同实现类,对于八卦炉来说都是它生产出的产品
+![女娲造人类图](https://upload-images.jianshu.io/upload_images/4685968-23b729aba2523fee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 接口Human是对人类的总称，每个人种都至少具有两个方法
+![](https://upload-images.jianshu.io/upload_images/4685968-d1b24b0cee720eb7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 黑色人种
+![](https://upload-images.jianshu.io/upload_images/4685968-5124fe0aaebb211f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 黄色人种
+![](https://upload-images.jianshu.io/upload_images/4685968-5dc7946d579ffb56.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 白色人种
+![](https://upload-images.jianshu.io/upload_images/4685968-8b2d955ebefc1471.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+所有人种定义完毕，下一步就是定义一个八卦炉，然后烧制人类
+
+最可能给八卦炉下达什么样的生产命令呢？
+应该是`给我生产出一个黄色人种（YellowHuman类）`
+而不会是`给我生产一个会走、会跑、会说话、皮肤是黄色的人种`
+因为这样的命令增加了交流的成本，作为一个生产的管理者，只要知道生产什么就可以了，而不需要事物的具体信息
+![](https://upload-images.jianshu.io/upload_images/4685968-bb006308572d3439.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+在这里采用了泛型，通过定义泛型对createHuman的输入参数产生两层限制
+● 必须是Class类型
+● 必须是Human的实现类
+其中的`T`表示，只要实现了Human接口的类都可以作为参数
+
+只有一个八卦炉，其实现生产人类的方法
+![](https://upload-images.jianshu.io/upload_images/4685968-eb7a7b7669772452.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+人种有了，八卦炉也有了，剩下的工作就是女娲采集黄土，然后命令八卦炉开始生产
+![](https://upload-images.jianshu.io/upload_images/4685968-1756e8409b6f5598.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+人种有了，八卦炉有了，负责生产的女娲也有了
+运行一下，结果如下所示
+![](https://upload-images.jianshu.io/upload_images/4685968-0e911383ba8416d9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+以上就是工厂方法模式
+# 2  定义
+![](https://upload-images.jianshu.io/upload_images/4685968-728bf0b4fe33b89c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 官方定义
+Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses
+定义一个用于创建对象的接口，让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类
+![](https://upload-images.jianshu.io/upload_images/4685968-132c8ab8cb8d1302.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![工厂方法模式的通用类图](http://upload-images.jianshu.io/upload_images/4685968-8e20457c9b86f71b.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+在工厂方法模式中，抽象产品类Product负责定义产品的共性，实现对事物最抽象的定义；Creator为抽象创建类，也就是抽象工厂，具体如何创建产品类是由具体的实现工厂ConcreteCreator完成的。工厂方法模式的变种较多，我们来看一个比较实用的通用源码。
+- 抽象产品类![](https://upload-images.jianshu.io/upload_images/4685968-e19c2599d115daaa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+具体的产品类可以有多个，都继承于抽象产品类
+- 具体产品类
+![](https://upload-images.jianshu.io/upload_images/4685968-be2036255104404a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-44979f2d21bfa26a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 抽象工厂类
+负责定义产品对象的产生
+![](https://upload-images.jianshu.io/upload_images/4685968-9b6549b0faae91c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 具体工厂类
+具体如何产生一个产品的对象，是由具体的工厂类实现的
+![](https://upload-images.jianshu.io/upload_images/4685968-b60b98650513095a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 场景类
+![](https://upload-images.jianshu.io/upload_images/4685968-b479b632eeeb1c02.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-607a2c562ed1a2d0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+该通用代码是一个比较实用、易扩展的框架，读者可以根据实际项目需要进行扩展
+# 3 应用
+## 3.1 优点
+![](https://upload-images.jianshu.io/upload_images/4685968-83c08c2a4f6d6724.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-279640d183d64bcd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 良好的封装性，代码结构清晰
+一个对象创建是有条件约束的，如一个调用者需要一个具体的产品对象，只要知道这个产品的类名（或约束字符串）就可以了，不用知道创建对象的艰辛过程，降低模块间的耦合
+- 工厂方法模式的扩展性非常优秀
+在增加产品类的情况下，只要适当地修改具体的工厂类或扩展一个工厂类，就可以完成“拥抱变化”
+例如在我们的例子中，需要增加一个棕色人种，则只需要增加一个BrownHuman类，工厂类不用任何修改就可完成系统扩展。
+- 屏蔽产品类
+这一特点非常重要，产品类的实现如何变化，调用者都不需要关心，它只需要关心产品的接口，只要接口保持不变，系统中的上层模块就不要发生变化
+因为产品类的实例化工作是由工厂类负责的，`一个产品对象具体由哪一个产品生成是由工厂类决定的`
+在数据库开发中，大家应该能够深刻体会到工厂方法模式的好处：如果使用JDBC连接数据库，数据库从MySQL切换到Oracle，需要改动的地方就是切换一下驱动名称（前提条件是SQL语句是标准语句），其他的都不需要修改，这是工厂方法模式灵活性的一个直接案例。
+- 典型的解耦框架
+高层模块值需要知道产品的抽象类，其他的实现类都不用关心
+符合迪米特法则，我不需要的就不要去交流
+也符合依赖倒置原则，只依赖产品类的抽象
+当然也符合里氏替换原则，使用产品子类替换产品父类，没问题！
+## 3.2 缺点
+![](https://upload-images.jianshu.io/upload_images/4685968-3562169226a921ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-6c486bc9daa9ab9c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## 3.3 适用场景
+![](https://upload-images.jianshu.io/upload_images/4685968-2892cbc52a9be8ca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-1201cd113bfaeb44.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+### 工厂方法模式是new一个对象的替代品
+在所有需要生成对象的地方都可以使用，但是需要慎重地考虑是否要增加一个工厂类进行管理，增加代码的复杂度
+### 需要灵活的、可扩展的框架时
+万物皆对象，那万物也就皆产品类
+例如需要设计一个连接邮件服务器的框架，有三种网络协议可供选择：POP3、IMAP、HTTP
+我们就可以把这三种连接方法作为产品类，定义一个接口如`IConnectMail`
+然后定义对邮件的操作方法
+用不同的方法实现三个具体的产品类（也就是连接方式）
+再定义一个工厂方法，按照不同的传入条件，选择不同的连接方式
+如此设计，可以做到完美的扩展，如某些邮件服务器提供了WebService接口，很好，我们只要增加一个产品类就可以了
+### 异构项目
+例如通过WebService与一个非Java的项目交互，虽然WebService号称是可以做到异构系统的同构化，但是在实际的开发中，还是会碰到很多问题，如类型问题、WSDL文件的支持问题，等等。从WSDL中产生的对象都认为是一个产品，然后由一个具体的工厂类进行管理，减少与外围系统的耦合。
+### 使用在测试驱动开发的框架下
+例如，测试一个类A，就需要把与类A有关联关系的类B也同时产生出来，我们可以使用工厂方法模式把类B虚拟出来，避免类A与类B的耦合。目前由于JMock和EasyMock的诞生，该使用场景已经弱化了，读者可以在遇到此种情况时直接考虑使用JMock或EasyMock
+# 4 扩展
+工厂方法模式有很多扩展，而且与其他模式结合使用威力更大，下面将介绍4种扩展。
+## 4.1 缩小为简单工厂模式
+我们这样考虑一个问题：一个模块仅需要一个工厂类，没有必要把它产生出来，使用静态的方法就可以了，根据这一要求，我们把上例中的`AbstarctHumanFactory`修改一下
+![简单工厂模式类图](http://upload-images.jianshu.io/upload_images/4685968-72f86b5e992c5237.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+我们在类图中去掉了`AbstractHumanFactory`抽象类，同时把`createHuman`方法设置为静态类型，简化了类的创建过程，变更的源码仅仅是HumanFactory和NvWa类
+
+- 简单工厂模式中的工厂类
+![待考证](https://upload-images.jianshu.io/upload_images/4685968-4c9740ec9baec87a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+HumanFactory类仅有两个地方发生变化
+- 去掉继承抽象类
+- 在`createHuman`前增加static关键字
+
+工厂类发生变化，也同时引起了调用者NvWa的变化
+ ![简单工厂模式中的场景类](https://upload-images.jianshu.io/upload_images/4685968-5765ddc36a0bdc84.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+运行结果没有发生变化，但是我们的类图变简单了，而且调用者也比较简单，该模式是工厂方法模式的弱化，因为简单，所以称为`简单工厂模式`（Simple Factory Pattern），也叫做`静态工厂模式`
+在实际项目中，采用该方法的案例还是比较多的
+- 其缺点
+工厂类的扩展比较困难，不符合开闭原则，但它仍然是一个非常实用的设计模式。
+## 4.2 升级为多个工厂类
+当我们在做一个比较复杂的项目时，经常会遇到初始化一个对象很耗费精力的情况，所有的产品类都放到一个工厂方法中进行初始化会使代码结构不清晰
+例如，一个产品类有5个具体实现，每个实现类的初始化（不仅仅是new，初始化包括new一个对象，并对对象设置一定的初始值）方法都不相同，如果写在一个工厂方法中，势必会导致该方法巨大无比，那该怎么办？
+
+考虑到需要结构清晰，我们就为每个产品定义一个创造者，然后由调用者自己去选择与哪个工厂方法关联
+我们还是以女娲造人为例，每个人种都有一个固定的八卦炉，分别造出黑色人种、白色人种、黄色人种
+![多个工厂类的类图](http://upload-images.jianshu.io/upload_images/4685968-79af47234aac2999.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 每个人种（具体的产品类）都对应了一个创建者，每个创建者都独立负责创建对应的产品对象，非常符合单一职责原则，看看代码变化
+![多工厂模式的抽象工厂类](https://upload-images.jianshu.io/upload_images/4685968-03006ceca2c8f242.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+抽象方法中已经不再需要传递相关参数了，因为每一个具体的工厂都已经非常明确自己的职责：创建自己负责的产品类对象。
+
+- 黑色人种的创建工厂实现
+![](https://upload-images.jianshu.io/upload_images/4685968-504d24acb4e0b24d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 黄色人种的创建类
+![](https://upload-images.jianshu.io/upload_images/4685968-e523b54e2cbae3a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 白色人种的创建类
+![](https://upload-images.jianshu.io/upload_images/4685968-ef6f65ec05c16cf7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+三个具体的创建工厂都非常简单，但是，如果一个系统比较复杂时工厂类也会相应地变复杂。
+- 场景类NvWa修改后的代码
+![](https://upload-images.jianshu.io/upload_images/4685968-ad52150fd79c52c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+运行结果还是相同
+每一个产品类都对应了一个创建类，好处就是创建类的职责清晰，而且结构简单，但是给可扩展性和可维护性带来了一定的影响。为什么这么说呢？如果要扩展一个产品类，就需要建立一个相应的工厂类，这样就增加了扩展的难度。因为工厂类和产品类的数量相同，维护时需要考虑两个对象之间的关系。
+
+当然，在复杂的应用中一般采用多工厂的方法，然后再增加一个协调类，避免调用者与各个子工厂交流，协调类的作用是封装子工厂类，对高层模块提供统一的访问接口。
+## 4.3 替代单例模式
+单例模式的核心要求就是`在内存中只有一个对象`，通过工厂方法模式也可以只在内存中生产一个对象
+![工厂方法模式替代单例模式类图](http://upload-images.jianshu.io/upload_images/4685968-6ee750bc612b6d6f.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+Singleton定义了一个private的无参构造函数，目的是不允许通过new的方式创建一个对象
+![单例类](https://upload-images.jianshu.io/upload_images/4685968-fcdbcb35b5e7805e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+Singleton保证不能通过正常的渠道建立一个对象
+
+- 那SingletonFactory如何建立一个单例对象呢？
+反射~
+![负责生成单例的工厂类](https://upload-images.jianshu.io/upload_images/4685968-2a0728c6b43e3cdf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+通过获得类构造器，然后设置private访问权限，生成一个对象，然后提供外部访问，保证内存中的对象唯一
+
+以上通过工厂方法模式创建了一个单例对象，该框架可以继续扩展，在一个项目中可以产生一个单例构造器，所有需要产生单例的类都遵循一定的规则（构造方法是private），然后通过扩展该框架，只要输入一个类型就可以获得唯一的一个实例。
+## 3.4 延迟初始化（Lazy initialization）
+一个对象被消费完毕后，并不立刻释放，工厂类保持其初始状态，等待再次被使用
+延迟初始化是工厂方法模式的一个扩展应用
+![延迟初始化的通用类图
+](http://upload-images.jianshu.io/upload_images/4685968-dde2820312878278.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+ProductFactory负责产品类对象的创建工作，并且通过prMap变量产生一个缓存，对需要再次被重用的对象保留
+![延迟加载的工厂类](https://upload-images.jianshu.io/upload_images/4685968-faeaf3df679a26c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+通过定义一个Map容器，容纳所有产生的对象，如果在Map容器中已经有的对象，则直接取出返回；如果没有，则根据需要的类型产生一个对象并放入到Map容器中，以方便下次调用。
+
+延迟加载框架是可以扩展的，例如限制某一个产品类的最大实例化数量，可以通过判断Map中已有的对象数量来实现，这样的处理是非常有意义的，例如JDBC连接数据库，都会要求设置一个MaxConnections最大连接数量，该数量就是内存中最大实例化的数量。
+
+延迟加载还可以用在对象初始化比较复杂的情况下，例如硬件访问，涉及多方面的交互，则可以通过延迟加载降低对象的产生和销毁带来的复杂性。
+# 4 最佳实践
+工厂方法模式在项目中使用得非常频繁，以至于很多代码中都包含工厂方法模式
+该模式几乎尽人皆知，但不是每个人都能用得好。熟能生巧，熟练掌握该模式，多思考工厂方法如何应用，而且工厂方法模式还可以与其他模式混合使用（例如模板方法模式、单例模式、原型模式等），变化出无穷的优秀设计，这也正是软件设计和开发的乐趣所在。
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.md"
similarity index 100%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\345\273\272\351\200\240\350\200\205\346\250\241\345\274\217.md"
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\346\212\275\350\261\241\345\267\245\345\216\202\346\250\241\345\274\217\357\274\210Abstract-Factory-Pattern\357\274\211.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\346\212\275\350\261\241\345\267\245\345\216\202\346\250\241\345\274\217\357\274\210Abstract-Factory-Pattern\357\274\211.md"
similarity index 100%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\346\212\275\350\261\241\345\267\245\345\216\202\346\250\241\345\274\217\357\274\210Abstract-Factory-Pattern\357\274\211.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\346\212\275\350\261\241\345\267\245\345\216\202\346\250\241\345\274\217\357\274\210Abstract-Factory-Pattern\357\274\211.md"
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\347\255\226\347\225\245\346\250\241\345\274\217\357\274\210Strategy-Pattern\357\274\211.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\347\255\226\347\225\245\346\250\241\345\274\217\357\274\210Strategy-Pattern\357\274\211.md"
similarity index 100%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\347\255\226\347\225\245\346\250\241\345\274\217\357\274\210Strategy-Pattern\357\274\211.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\347\255\226\347\225\245\346\250\241\345\274\217\357\274\210Strategy-Pattern\357\274\211.md"
diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md"
new file mode 100644
index 0000000000..88fae975df
--- /dev/null
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md"
@@ -0,0 +1,31 @@
+# 定义与类型
+![](https://upload-images.jianshu.io/upload_images/4685968-5a9576189c8e5e1a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 适用场景
+![](https://upload-images.jianshu.io/upload_images/4685968-223bb37e8742148d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 优点
+![](https://upload-images.jianshu.io/upload_images/4685968-26e681be14b48d5a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 缺点
+![](https://upload-images.jianshu.io/upload_images/4685968-4b77b933afb1074f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# coding
+![](https://upload-images.jianshu.io/upload_images/4685968-ed888f5d321b034a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-4b1046fb905f15e0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-fb6d0ff24fd80c73.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-765ddd9d7be6e8f1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-2378eaba412ee8c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ae51d4db6d247db7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-87dd65c810ed8420.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ea740980ef1ab583.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-793412716d22062c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-e31e3ccc7e5e86a7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+接下来,来到观察者- `Teacher`的代码区中
+![](https://upload-images.jianshu.io/upload_images/4685968-2705098e1cadd417.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 源码应用
+![JDK应用](https://upload-images.jianshu.io/upload_images/4685968-3b2ca4ec326660c4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## Guava
+![](https://upload-images.jianshu.io/upload_images/4685968-99bc1215f23ac110.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-95cdf8cad0660f10.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![注册,即添加观察者](https://upload-images.jianshu.io/upload_images/4685968-03e5008cacf284ab.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-a2a768f37c0748cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-c41574e7e3b2144d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![移除观察者](https://upload-images.jianshu.io/upload_images/4685968-235da3b8d7cef292.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-d95b863446e24048.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\247\243\351\207\212\345\231\250\346\250\241\345\274\217\357\274\210Interpreter-Pattern\357\274\211.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\247\243\351\207\212\345\231\250\346\250\241\345\274\217\357\274\210Interpreter-Pattern\357\274\211.md"
similarity index 100%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\247\243\351\207\212\345\231\250\346\250\241\345\274\217\357\274\210Interpreter-Pattern\357\274\211.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\247\243\351\207\212\345\231\250\346\250\241\345\274\217\357\274\210Interpreter-Pattern\357\274\211.md"
diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.md"
new file mode 100644
index 0000000000..ea724ca639
--- /dev/null
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.md"
@@ -0,0 +1,57 @@
+# 1 定义与类型
+## 1.1 定义
+- 维基
+它包含了一些命令对象和一系列的处理对象;
+每一个处理对象决定它能处理哪些命令对象,它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象.
+该模式还描述了往该处理链的末尾添加新的处理对象的方法.
+
+- 精简定义
+为请求创建一个接收此次请求对象的链.
+
+## 1.2 类型
+行为型
+
+# 2 适用场景
+一个请求的处理需要多个对象当中的一个或几个协作处理
+当然也包括需要全部的情况
+
+# 3 优点
+请求的发送者和接收者(请求的处理)解耦
+责任链可以动态组合
+
+
+# 4 缺点
+- 责任链太长或者处理时间过长,影响性能
+- 责任链有可能过多
+
+5 # 相关设计模式
+~和状态模式
+- 各个对象并不指定下一个所要处理的对象者是谁,只有在客户端类设置链顺序及元素,知道被某个责任链处理或者整条链结束.
+- 每个状态知道自己下一个所要处理的对象者是谁,即在编译时确定
+
+# 6 实战
+- 相关类
+![](https://upload-images.jianshu.io/upload_images/4685968-5f61ab5ed1db8c31.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-b4ac47bf8102362c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-3885c81df1ebbae4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-a34d00e17af71a0a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-b950249ca12d3c7e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- UML图
+![](https://upload-images.jianshu.io/upload_images/4685968-ab22fc917646f57a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 测试类
+![](https://upload-images.jianshu.io/upload_images/4685968-1a5b383f7f2bf188.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-535359f026338231.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 将博客注释掉
+![](https://upload-images.jianshu.io/upload_images/4685968-5fb4c20bf2895c6e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-536d32e80b910927.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 调试过程
+![](https://upload-images.jianshu.io/upload_images/4685968-fe1fce0b021fd204.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-f5f7e9ee7d87c866.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+开始发布
+![](https://upload-images.jianshu.io/upload_images/4685968-8cbf04eb02013d8e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 框架源码应用
+![](https://upload-images.jianshu.io/upload_images/4685968-0891c4661445243c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- doFilter相当于 deploy 方法
+![](https://upload-images.jianshu.io/upload_images/4685968-dbb26c1e36a5cb1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-6506748536cee1ad.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md"
similarity index 51%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md"
index 3a49860571..8b116eeab5 100644
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md"
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230---\351\200\202\351\205\215\345\231\250\346\250\241\345\274\217.md"
@@ -1,21 +1,19 @@
-# 1 导读
-一般客户端通过目标类的接口访问它所提供的服务。
-有时，现有类可以满足客户端类的需要，但所提供接口不一定是客户端所期望的，可能因为现有类中方法名与目标类中定义的方法名不一致。
+# 1 动机
+类似于电源适配器的设计和编码技巧。一般客户端通过目标类的接口访问它所提供的服务。
+有时，现有类可以满足客户类需要，但所提供接口不一定是客户类所期望的，可能因为现有类中方法名与目标类中定义的方法名不一致
 
-这时，**现有接口需要转化为客户端的期望接口，保证复用现有类**。若不进行这样转化，客户端就不能利用现有类所提供功能，适配器模式就可以完成这样的转化。
+这时，**现有接口需要转化为客户类期望的接口，保证复用现有类**
+如果不进行这样的转化，客户类就不能利用现有类所提供的功能，适配器模式可以完成这样的转化。
 
 在适配器模式中可以定义一个包装类，包装不兼容接口的对象
-- 包装类
-适配器(Adapter)
-- 所包装的对象
-适配者(Adaptee)，即被适配的类
+- 包装类指的就是适配器(Adapter)
+- 所包装的对象就是适配者(Adaptee)，即被适配的类
 
-适配器提供客户类需要的接口。
-适配器的实现就是把客户端的请求转化为对适配者的相应接口的调用。即当客户类调用适配器方法时，在适配器类的内部将调用适配者类的方法，而该过程对客户类透明，客户类并不直接访问适配者类。
-因此，适配器可以使由于接口不兼容而不能交互的类可以一起协作。
+适配器提供客户类需要的接口
+适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。即当客户类调用适配器的方法时，在适配器类的内部将调用适配者类的方法，而这个过程对客户类是透明的，客户类并不直接访问适配者类。因此，适配器可以使由于接口不兼容而不能交互的类可以一起工作
 
 # 2 定义
-将一个接口转换成客户端希望的另一个接口，使接口不兼容的那些类可以一起工作，其别名为包装器。
+将一个接口转换成客户希望的另一个接口，使接口不兼容的那些类可以一起工作，其别名为包装器
 既可以作为类结构型模式，也可以作为对象结构型模式。
 
 # 3 结构
@@ -25,13 +23,14 @@
 *   Client：客户类
 
 适配器模式有对象适配器和类适配器两种实现：
+
 ## 3.1 对象适配器
 ![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZGU0NWZjYzgyNjQwMjY4My5qcGc?x-oss-process=image/format,png)
 
 ## 3.2 类适配器
 ![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtMzFiNzdkZmVlZjcyNzdmNC5qcGc?x-oss-process=image/format,png)
 
-# 4 时序图
+#4 时序图
 ![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZDNkY2JhZDQ1YjBkMjE3Yi5qcGc?x-oss-process=image/format,png)
 
 # 5 代码分析
@@ -53,24 +52,148 @@
 *AudioPlayer* 使用适配器类 *MediaAdapter* 传递所需的音频类型，不需要知道能播放所需格式音频的实际类。*AdapterPatternDemo*，我们的演示类使用 *AudioPlayer* 类来播放各种格式。
 
 ![适配器模式的 UML 图](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtZjJmNzQ1ZWNmNzcyNmViYy5qcGc?x-oss-process=image/format,png)
+
 ### 步骤 1
-创建接口。
-![](https://img-blog.csdnimg.cn/20210718163101535.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)![](https://img-blog.csdnimg.cn/20210718163326636.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
 
+为媒体播放器和更高级的媒体播放器创建接口。
+
+*MediaPlayer.java*
+```
+public interface MediaPlayer {
+   public void play(String audioType, String fileName);
+}
+```
+
+*AdvancedMediaPlayer.java*
+```
+public interface AdvancedMediaPlayer {    
+   public void playVlc(String fileName);
+   public void playMp4(String fileName);
+}
+```
 ### 步骤 2
-![](https://img-blog.csdnimg.cn/20210718163812845.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210718163847504.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+创建实现了 *AdvancedMediaPlayer* 接口的实体类
+
+*VlcPlayer.java*
+
+```
+public class VlcPlayer implements AdvancedMediaPlayer{
+   @Override
+   public void playVlc(String fileName) {
+      System.out.println("Playing vlc file. Name: "+ fileName);        
+   }
+
+   @Override
+   public void playMp4(String fileName) {
+      //什么也不做
+   }
+}
+```
+
+*Mp4Player.java*
+```
+public class Mp4Player implements AdvancedMediaPlayer{
+
+   @Override
+   public void playVlc(String fileName) {
+      //什么也不做
+   }
+
+   @Override
+   public void playMp4(String fileName) {
+      System.out.println("Playing mp4 file. Name: "+ fileName);        
+   }
+}
+```
+
 
 ### 步骤 3
-![](https://img-blog.csdnimg.cn/20210718163721309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+
+创建实现了 *MediaPlayer* 接口的适配器类。
+
+*MediaAdapter.java*
+
+```
+public class MediaAdapter implements MediaPlayer {
+
+   AdvancedMediaPlayer advancedMusicPlayer;
+
+   public MediaAdapter(String audioType){
+      if(audioType.equalsIgnoreCase("vlc") ){
+         advancedMusicPlayer = new VlcPlayer();            
+      } else if (audioType.equalsIgnoreCase("mp4")){
+         advancedMusicPlayer = new Mp4Player();
+      }    
+   }
+
+   @Override
+   public void play(String audioType, String fileName) {
+      if(audioType.equalsIgnoreCase("vlc")){
+         advancedMusicPlayer.playVlc(fileName);
+      }else if(audioType.equalsIgnoreCase("mp4")){
+         advancedMusicPlayer.playMp4(fileName);
+      }
+   }
+}
+```
 ### 步骤 4
-![](https://img-blog.csdnimg.cn/20210718164931518.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
 
+创建实现了 *MediaPlayer* 接口的实体类。
+
+*AudioPlayer.java*
+```
+public class AudioPlayer implements MediaPlayer {
+   MediaAdapter mediaAdapter; 
+
+   @Override
+   public void play(String audioType, String fileName) {        
+
+      //播放 mp3 音乐文件的内置支持
+      if(audioType.equalsIgnoreCase("mp3")){
+         System.out.println("Playing mp3 file. Name: "+ fileName);            
+      } 
+      //mediaAdapter 提供了播放其他文件格式的支持
+      else if(audioType.equalsIgnoreCase("vlc") 
+         || audioType.equalsIgnoreCase("mp4")){
+         mediaAdapter = new MediaAdapter(audioType);
+         mediaAdapter.play(audioType, fileName);
+      }
+      else{
+         System.out.println("Invalid media. "+
+            audioType + " format not supported");
+      }
+   }   
+}
+```
 ### 步骤 5
-![](https://img-blog.csdnimg.cn/20210718165039929.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+
+使用 AudioPlayer 来播放不同类型的音频格式。
+
+*AdapterPatternDemo.java*
+
+```
+public class AdapterPatternDemo {
+   public static void main(String[] args) {
+      AudioPlayer audioPlayer = new AudioPlayer();
+
+      audioPlayer.play("mp3", "beyond the horizon.mp3");
+      audioPlayer.play("mp4", "alone.mp4");
+      audioPlayer.play("vlc", "far far away.vlc");
+      audioPlayer.play("avi", "mind me.avi");
+   }
+}
+```
+
 ### 步骤 6
-测试类输出：
-![](https://img-blog.csdnimg.cn/20210718180057697.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+
+验证输出。
+
+```
+Playing mp3 file. Name: beyond the horizon.mp3
+Playing mp4 file. Name: alone.mp4
+Playing vlc file. Name: far far away.vlc
+Invalid media. avi format not supported
+```
 
 # 案例
 ## 实现 `Iterable` 的 `Fibnoacci` 生成器
@@ -78,32 +201,37 @@
 - 不过你并不是总拥有源代码的控制权
 - 并且，除非必须这么做，否则，我们也不愿意重写一个类
 
-因此另一种选择，创建一个 适配器(Adapter) 来实现所需接口。有多种适配器的实现，例如继承：
-![](https://img-blog.csdnimg.cn/20200627210435744.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
+因此另一种选择，创建一个 *适配器* (Adapter) 来实现所需的接口
+
+有多种适配器的实现。例如继承：
+![](https://img-blog.csdnimg.cn/20200627210435744.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
 
 在 *for-in* 语句中使用 `IterableFibonacci`，必须在构造函数中提供一个边界值，这样 `hasNext()` 才知道何时返回 **false**，结束循环。
 # 8 优点
-- 解耦目标类和适配者类，通过引入一个适配器类来重用现有的适配者类，而无须修改原有代码
-- 增加了类的透明性和复用性，将具体的实现封装在适配者类中，对于客户端类来说是透明的，而且提高了适配者的复用性
-- 灵活性和扩展性都非常好，通过使用配置文件，可以很方便地更换适配器，也可以在不修改原有代码的基础上增加新的适配器类，完全符合“开闭原则”
+
+*   将目标类和适配者类解耦，通过引入一个适配器类来重用现有的适配者类，而无须修改原有代码。
+*   增加了类的透明性和复用性，将具体的实现封装在适配者类中，对于客户端类来说是透明的，而且提高了适配者的复用性。
+*   灵活性和扩展性都非常好，通过使用配置文件，可以很方便地更换适配器，也可以在不修改原有代码的基础上增加新的适配器类，完全符合“开闭原则”。
 
 类适配器模式还具有如下优点：
 由于适配器类是适配者类的子类，因此可以在适配器类中置换一些适配者的方法，使得适配器的灵活性更强。
-
 对象适配器模式还具有如下优点：
 一个对象适配器可以把多个不同的适配者适配到同一个目标，也就是说，同一个适配器可以把适配者类和它的子类都适配到目标接口。
 
 # 9 缺点
 ## 类适配器模式
-对于Java不支持多继承，一次最多只能适配一个适配者类，而且目标抽象类只能为抽象类，不能为具体类，其使用有一定的局限性，不能将一个适配者类和它的子类都适配到目标接口。
+对于Java、C#等不支持多重继承的语言，一次最多只能适配一个适配者类，而且目标抽象类只能为抽象类，不能为具体类，其使用有一定的局限性，不能将一个适配者类和它的子类都适配到目标接口。
 ## 对象适配器模式
 与类适配器模式相比，要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法，就只好先做一个适配者类的子类，将适配者类的方法置换掉，然后再把适配者类的子类当做真正的适配者进行适配，实现过程较为复杂。
 
+
 # 10 适用环境
 
 在以下情况下可以使用适配器模式：
-- 系统需要使用现有的类，而这些类的接口不符合系统的需要
-- 想要建立一个可以重复使用的类，用于与一些彼此之间没有太大关联的一些类，包括一些可能在将来引进的类一起工作
+
+*   系统需要使用现有的类，而这些类的接口不符合系统的需要。
+*   想要建立一个可以重复使用的类，用于与一些彼此之间没有太大关联的一些类，包括一些可能在将来引进的类一起工作。
+
 # 11 模式应用
 Sun公司在1996年公开了Java语言的数据库连接工具JDBC，JDBC使得Java语言程序能够与数据库连接，并使用SQL语言来查询和操作数据。JDBC给出一个客户端通用的抽象接口，每一个具体数据库引擎（如SQL Server、Oracle、MySQL等）的JDBC驱动软件都是一个介于JDBC接口和数据库引擎接口之间的适配器软件。抽象的JDBC接口和各个数据库引擎API之间都需要相应的适配器软件，这就是为各个不同数据库引擎准备的驱动程序。
 
@@ -117,4 +245,4 @@ Sun公司在1996年公开了Java语言的数据库连接工具JDBC，JDBC使得J
 *   适配器模式包含四个角色：目标抽象类定义客户要用的特定领域的接口；适配器类可以调用另一个接口，作为一个转换器，对适配者和抽象目标类进行适配，它是适配器模式的核心；适配者类是被适配的角色，它定义了一个已经存在的接口，这个接口需要适配；在客户类中针对目标抽象类进行编程，调用在目标抽象类中定义的业务方法。
 *   在类适配器模式中，适配器类实现了目标抽象类接口并继承了适配者类，并在目标抽象类的实现方法中调用所继承的适配者类的方法；在对象适配器模式中，适配器类继承了目标抽象类并定义了一个适配者类的对象实例，在所继承的目标抽象类方法中调用适配者类的相应业务方法。
 *   适配器模式的主要优点是将目标类和适配者类解耦，增加了类的透明性和复用性，同时系统的灵活性和扩展性都非常好，更换适配器或者增加新的适配器都非常方便，符合“开闭原则”；类适配器模式的缺点是适配器类在很多编程语言中不能同时适配多个适配者类，对象适配器模式的缺点是很难置换适配者类的方法。
-*   适配器模式适用情况包括：系统需要使用现有的类，而这些类的接口不符合系统的需要；想要建立一个可以重复使用的类，用于与一些彼此之间没有太大关联的一些类一起工作。
\ No newline at end of file
+*   适配器模式适用情况包括：系统需要使用现有的类，而这些类的接口不符合系统的需要；想要建立一个可以重复使用的类，用于与一些彼此之间没有太大关联的一些类一起工作。
diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md"
new file mode 100644
index 0000000000..eb4bcc9fd9
--- /dev/null
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md"
@@ -0,0 +1,436 @@
+# 1 定义与类型
+- 模板方法模式（Template Method Pattern）
+Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.（定义一个操作中的算法的框架，而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。）
+![定义](https://upload-images.jianshu.io/upload_images/4685968-e39f281f77196ca1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![定义补充](https://upload-images.jianshu.io/upload_images/4685968-c9664ee9d4a0e136.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![类型](https://upload-images.jianshu.io/upload_images/4685968-45cd5433a82e2df6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![模板方法模式的通用类图](https://upload-images.jianshu.io/upload_images/4685968-39e33a07bfb7e900.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+模板方法模式确实非常简单，仅仅使用了Java的继承机制，但它是一个应用非常广泛的模式。其中，AbstractClass叫做抽象模板，它的方法分为两类：
+● 基本方法
+基本操作，是由子类实现的方法，并且在模板方法被调用
+● 模板方法
+可以有一个或几个，一般是一个具体方法，也就是一个框架，实现对基本方法的调度，完成固定的逻辑
+为了防止恶意的操作，一般模板方法都加上final关键字，不允许被覆写。
+
+在类图中还有一个角色：具体模板。ConcreteClass1和ConcreteClass2属于具体模板，实现父类所定义的一个或多个抽象方法，也就是父类定义的基本方法在子类中得以实现。
+
+我们来看其通用代码，AbstractClass抽象模板类
+```
+public abstract class AbstractClass {
+     //基本方法
+     protected abstract void doSomething();
+     //基本方法
+     protected abstract void doAnything();
+     //模板方法
+     public void templateMethod(){
+             /*
+              * 调用基本方法，完成相关的逻辑
+              */
+             this.doAnything();
+             this.doSomething();
+     }
+}
+```
+- 具体模板类
+```
+public class ConcreteClass1 extends AbstractClass {
+     //实现基本方法
+     protected void doAnything() {
+             //业务逻辑处理
+     }
+     protected void doSomething() {
+             //业务逻辑处理
+     }
+}
+public class ConcreteClass2 extends AbstractClass {
+     //实现基本方法
+     protected void doAnything() {
+             //业务逻辑处理
+     }
+     protected void doSomething() {
+             //业务逻辑处理
+     }
+}
+```
+- 场景类
+```
+public class Client {
+     public static void main(String[] args) {
+             AbstractClass class1 = new ConcreteClass1();
+             AbstractClass class2 = new ConcreteClass2();               
+             //调用模板方法
+             class1.templateMethod();
+             class2.templateMethod();
+     }
+}
+```
+抽象模板中的基本方法尽量设计为protected类型，符合迪米特法则，不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要，尽量不要扩大父类中的访问权限。
+# 2 适用场景
+![场景](https://upload-images.jianshu.io/upload_images/4685968-2e2b6f6b2d30b1a6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+● 多个子类有公有的方法，并且逻辑基本相同时。
+● 重要、复杂的算法，可以把核心算法设计为模板方法，周边的相关细节功能则由各个子类实现。
+● 重构时，模板方法模式是一个经常使用的模式，把相同的代码抽取到父类中，然后通过钩子函数（见“模板方法模式的扩展”）约束其行为。
+# 3 优点
+![](https://upload-images.jianshu.io/upload_images/4685968-1cd27db040651115.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+● 封装不变部分，扩展可变部分
+把认为是不变部分的算法封装到父类实现，而可变部分的则可以通过继承来继续扩展
+● 提取公共部分代码，便于维护
+如果我们不抽取到父类中，任由这种散乱的代码发生，想想后果是什么样子？维护人员为了修正一个缺陷，需要到处查找类似的代码
+● 行为由父类控制，子类实现
+基本方法是由子类实现的，因此子类可以通过扩展的方式增加相应的功能，符合开闭原则
+# 4 缺点
+![](https://upload-images.jianshu.io/upload_images/4685968-255b37b0b71729b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+抽象类负责声明最抽象、最一般的事物属性和方法，实现类完成具体的事物属性和方法
+但是模板方法模式却颠倒了，抽象类定义了部分抽象方法，由子类实现，子类执行的结果影响了父类的结果，也就是子类对父类产生了影响，这在复杂的项目中，会带来代码阅读的难度，而且也会让新手产生不适感。
+
+# 相关设计模式
+![](https://upload-images.jianshu.io/upload_images/4685968-fc9260cd849805ed.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 示例 -  辉煌工程——制造悍马
+先不考虑扩展性，那好办，先按照最一般的经验设计类图
+![悍马车模型最一般的类图](https://upload-images.jianshu.io/upload_images/4685968-66f076c6cb35e384.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+- 非常简单的实现，悍马车有两个型号，H1和H2
+按照需求，只需要悍马模型，那好我就给你悍马模型，先写个抽象类，然后两个不同型号的模型实现类，通过简单的继承就可以实现业务要求
+
+我们先从抽象类开始编写
+
+抽象悍马模型
+```
+public abstract class HummerModel {
+     /*
+      * 首先，这个模型要能够被发动起来，别管是手摇发动，还是电力发动，反正
+      * 是要能够发动起来，那这个实现要在实现类里了
+      */
+     public abstract void start();
+     //能发动，还要能停下来，那才是真本事
+     public abstract void stop();       
+     //喇叭会出声音，是滴滴叫，还是哔哔叫
+     public abstract void alarm();
+     //引擎会轰隆隆地响，不响那是假的
+     public abstract void engineBoom();
+     //那模型应该会跑吧，别管是人推的，还是电力驱动的，总之要会跑
+     public abstract void run();
+}
+```
+定义了悍马模型都必须具有的特质：能够发动、停止，喇叭会响，引擎可以轰鸣，而且还可以停止
+但是每个型号的悍马实现是不同的
+
+- H1型号的悍马
+```
+public class HummerH1Model extends HummerModel {
+     //H1型号的悍马车鸣笛
+     public void alarm() {
+             System.out.println("悍马H1鸣笛...");
+     }
+     //引擎轰鸣声
+     public void engineBoom() {
+             System.out.println("悍马H1引擎声音是这样的...");
+     }
+     //汽车发动
+     public void start() {
+             System.out.println("悍马H1发动...");
+     }  
+     //停车
+     public void stop() {
+             System.out.println("悍马H1停车...");
+     }
+     //开动起来
+     public void run(){
+             //先发动汽车
+             this.start();
+             //引擎开始轰鸣
+             this.engineBoom();
+             //然后就开始跑了，跑的过程中遇到一条狗挡路，就按喇叭
+             this.alarm();
+             //到达目的地就停车
+             this.stop();
+     }
+}
+```
+注意run()方法，这是一个汇总的方法，一个模型生产成功，要拿给客户检测吧，怎么检测？
+“是骡子是马，拉出去溜溜”，这就是一种检验方法，让它跑起来！
+通过run()这样的方法，把模型的所有功能都测试到了。
+
+- H2型号悍马
+```
+public class HummerH2Model extends HummerModel {
+     //H2型号的悍马车鸣笛
+     public void alarm() {
+             System.out.println("悍马H2鸣笛...");
+     }
+     //引擎轰鸣声
+     public void engineBoom() {
+             System.out.println("悍马H2引擎声音是这样在...");
+     }
+     //汽车发动
+     public void start() {
+             System.out.println("悍马H2发动...");
+     }  
+     //停车
+     public void stop() {
+             System.out.println("悍马H2停车...");
+     }  
+     //开动起来
+     public void run(){
+             //先发动汽车
+             this.start();              
+             //引擎开始轰鸣
+             this.engineBoom();         
+             //然后就开始跑了，跑的过程中遇到一条狗挡路，就按喇叭
+             this.alarm();              
+             //到达目的地就停车
+             this.stop();
+     }
+}
+```
+
+程序编写到这里，已经发现问题`两个实现类的run()方法完全相同`
+那这个run()方法的实现应该出现在抽象类，不应该在实现类上，抽象是所有子类的共性封装
+
+问题发现,马上更改
+![ 修改后的悍马车模类图](http://upload-images.jianshu.io/upload_images/4685968-a69b29d5469b7e06.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+抽象类`HummerModel#run()`，由抽象方法变更为实现方法
+```
+public abstract class HummerModel {
+     /*
+      * 首先，这个模型要能发动起来，别管是手摇发动，还是电力发动，反正
+      * 是要能够发动起来，那这个实现要在实现类里了
+      */
+     public abstract void start();      
+     //能发动，还要能停下来，那才是真本事
+     public abstract void stop();       
+     //喇叭会出声音，是滴滴叫，还是哔哔叫
+     public abstract void alarm();      
+     //引擎会轰隆隆地响，不响那是假的
+     public abstract void engineBoom(); 
+     //那模型应该会跑吧，别管是人推的，还是电力驱动，总之要会跑
+     public void run(){
+             //先发动汽车
+             this.start();              
+             //引擎开始轰鸣
+             this.engineBoom();         
+             //然后就开始跑了，跑的过程中遇到一条狗挡路，就按喇叭
+             this.alarm();              
+             //到达目的地就停车
+             this.stop();
+     }
+}
+```
+在抽象的悍马模型上已经定义了run()方法的执行规则，先启动，然后引擎立刻轰鸣，中间还要按一下喇叭,然后停车
+它的两个具体实现类就不需要实现run()方法了，只要把相应代码上的run()方法删除即可
+
+场景类实现的任务就是把生产出的模型展现给客户
+```
+public class Client {
+     public static void main(String[] args) {
+             //XX公司要H1型号的悍马
+             HummerModel h1 = new HummerH1Model();              
+             //H1模型演示
+             h1.run();
+     }
+}
+```
+运行结果
+```
+悍马H1发动...
+
+悍马H1引擎声音是这样的...
+
+悍马H1鸣笛...
+
+悍马H1停车...
+```
+目前客户只要看H1型号的悍马车，没问题，生产出来，同时可以运行起来给他看看。非常简单，那如果我告诉你这就是模板方法模式你会不会很不屑呢？就这模式，太简单了，我一直在使用呀！是的，你经常在使用，但你不知道这是模板方法模式，那些所谓的高手就可以很牛地说：“用模板方法模式就可以实现”，你还要很崇拜地看着，哇，牛人，模板方法模式是什么呀？这就是模板方法模式。
+# 扩展
+ ![](https://upload-images.jianshu.io/upload_images/4685968-1bfcdc28387a7178.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+到目前为止，这两个模型都稳定地运行，突然有一天，老大急匆匆地找到了我：
+
+“看你怎么设计的，车子一启动，喇叭就狂响，吵死人了！客户提出H1型号的悍马喇叭想让它响就响，H2型号的喇叭不要有声音，赶快修改一下。”
+
+自己惹的祸，就要想办法解决它，稍稍思考一下，解决办法有了，先画出类图
+![扩展悍马车模类图](http://upload-images.jianshu.io/upload_images/4685968-45658d3f5688a489.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+类图改动似乎很小，在抽象类`HummerModel`中增加了一个实现方法`isAlarm`，确定各个型号的悍马是否需要声音，由各个实现类覆写该方法，同时其他的基本方法由于不需要对外提供访问，因此也设计为protected类型
+- 扩展后的抽象模板类
+```
+public abstract class HummerModel {
+     /*
+      * 首先，这个模型要能够被发动起来，别管是手摇发动，还是电力发动，反正
+      * 是要能够发动起来，那这个实现要在实现类里了
+      */
+     protected abstract void start();   
+     //能发动，还要能停下来，那才是真本事
+     protected abstract void stop();    
+     //喇叭会出声音，是滴滴叫，还是哔哔叫
+     protected abstract void alarm();   
+     //引擎会轰隆隆的响，不响那是假的
+     protected abstract void engineBoom();      
+     //那模型应该会跑吧，别管是人推的，还是电力驱动，总之要会跑
+     final public void run() {          
+             //先发动汽车
+             this.start();              
+             //引擎开始轰鸣
+             this.engineBoom();         
+             //要让它叫的就是就叫，喇嘛不想让它响就不响
+             if(this.isAlarm()){
+                      this.alarm();
+             }
+             //到达目的地就停车
+             this.stop();
+     }  
+     //钩子方法，默认喇叭是会响的
+     protected  boolean isAlarm(){
+             return true;
+     }
+}
+```
+- 在抽象类中，`isAlarm`是一个实现方法
+模板方法根据其返回值决定是否要响喇叭，子类可以覆写该返回值，由于H1型号的喇叭是想让它响就响，不想让它响就不响，由人控制
+
+- 扩展后的H1悍马
+```  
+public class HummerH1Model extends HummerModel {
+     private boolean alarmFlag = true;  //要响喇叭
+     protected void alarm() {
+             System.out.println("悍马H1鸣笛...");
+     }
+     protected void engineBoom() {
+             System.out.println("悍马H1引擎声音是这样的...");
+     }
+     protected void start() {
+             System.out.println("悍马H1发动...");
+     }
+     protected void stop() {
+             System.out.println("悍马H1停车...");
+     }
+     protected boolean isAlarm() {
+             return this.alarmFlag;
+     }
+     //要不要响喇叭，是由客户来决定的
+     public void setAlarm(boolean isAlarm){
+             this.alarmFlag = isAlarm;
+     }
+}
+```
+只要调用H1型号的悍马，默认是有喇叭响的，当然你可以不让喇叭响，通过isAlarm(false)就可以实现。H2型号的悍马是没有喇叭声响的
+
+- 扩展后的H2悍马
+```
+public class HummerH2Model extends HummerModel {
+     protected void alarm() {
+             System.out.println("悍马H2鸣笛...");
+     }
+     protected void engineBoom() {
+             System.out.println("悍马H2引擎声音是这样的...");
+     }
+     protected void start() {
+             System.out.println("悍马H2发动...");
+     }
+     protected void stop() {
+             System.out.println("悍马H2停车...");
+     }
+     //默认没有喇叭的
+     protected boolean isAlarm() {
+             return false;
+     }
+}
+```
+H2型号的悍马设置isAlarm()的返回值为false，也就是关闭了喇叭功能
+
+-  扩展后的场景类
+```
+public class Client {
+     public static void main(String[] args) throws IOException {
+             System.out.println("-------H1型号悍马--------");
+             System.out.println("H1型号的悍马是否需要喇叭声响？0-不需要   1-需要");
+             String type=(new BufferedReader(new InputStreamReader([System.in](http://system.in/)))).readLine();
+             HummerH1Model h1 = new HummerH1Model();
+             if(type.equals("0")){  
+                     h1.setAlarm(false);
+             }
+             h1.run();
+             System.out.println("\n-------H2型号悍马--------");
+             HummerH2Model h2 = new HummerH2Model();
+             h2.run();
+     }
+}
+```
+运行是需要交互的，首先，要求输入H1型号的悍马是否有声音，如下所示：
+```
+-------H1型号悍马--------
+
+H1型号的悍马是否需要喇叭声响？0-不需要 1-需要
+
+输入“0”后的运行结果如下所示：
+
+-------H1型号悍马--------
+
+H1型号的悍马是否需要喇叭声响？0-不需要 1-需要
+
+0
+
+悍马H1发动...
+
+悍马H1引擎声音是这样的...
+
+悍马H1停车...
+
+-------H2型号悍马--------
+
+悍马H2发动...
+
+悍马H2引擎声音是这样的...
+
+悍马H2停车...
+
+输入“1”后的运行结果如下所示：
+
+-------H1型号悍马--------
+
+H1型号的悍马是否需要喇叭声响？0-不需要 1-需要
+
+1
+
+悍马H1发动...
+
+悍马H1引擎声音是这样的...
+
+悍马H1鸣笛...
+
+悍马H1停车...
+
+-------H2型号悍马--------
+
+悍马H2发动...
+
+悍马H2引擎声音是这样的...
+
+悍马H2停车...
+```
+看到没，H1型号的悍马是由客户自己控制是否要响喇叭，也就是说外界条件改变，影响到模板方法的执行。
+在我们的抽象类中isAlarm的返回值就是影响了模板方法的执行结果，该方法就叫做钩子方法（Hook Method）。有了钩子方法模板方法模式才算完美，大家可以想想，由子类的一个方法返回值决定公共部分的执行结果，是不是很有吸引力呀！
+
+模板方法模式就是在模板方法中按照一定的规则和顺序调用基本方法，具体到前面那个例子，就是run()方法按照规定的顺序（先调用start()，然后再调用engineBoom()，再调用alarm()，最后调用stop()）调用本类的其他方法，并且由isAlarm()方法的返回值确定run()中的执行顺序变更。
+
+
+
+# Coding!!!
+![](https://upload-images.jianshu.io/upload_images/4685968-753ff691e6ee5b5c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-4a1e2f336d316ab9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ce273adc1d8907dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-d1d4ebd9bd2452d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-b0bec7541a33cc5c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 最佳实践
+初级程序员在写程序的时候经常会问高手“父类怎么调用子类的方法”。这个问题很有普遍性，反正我是被问过好几回，那么父类是否可以调用子类的方法呢？我的回答是能，但强烈地、极度地不建议这么做，那该怎么做呢?
+
+● 把子类传递到父类的有参构造中，然后调用
+● 使用反射的方式调用
+● 父类调用子类的静态方法
+
+这三种都是父类直接调用子类的方法，好用不？好用！解决问题了吗？解决了！项目中允许使用不？不允许！
+我就一直没有搞懂为什么要用父类调用子类的方法。如果一定要调用子类，那为什么要继承它呢？搞不懂。其实这个问题可以换个角度去理解，父类建立框架，子类在重写了父类部分的方法后，再调用从父类继承的方法，产生不同的结果（而这正是模板方法模式）。这是不是也可以理解为父类调用了子类的方法呢？你修改了子类，影响了父类行为的结果，曲线救国的方式实现了父类依赖子类的场景，模板方法模式就是这种效果。
+
+模板方法在一些开源框架中应用非常多，它提供了一个抽象类，然后开源框架写了一堆子类。在《××× In Action》中就说明了，如果你需要扩展功能，可以继承这个抽象类，然后覆写protected方法，再然后就是调用一个类似execute方法，就完成你的扩展开发，非常容易扩展的一种模式。
+# 应用
+## AbstractList
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\212\266\346\200\201\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\212\266\346\200\201\346\250\241\345\274\217.md"
similarity index 71%
rename from "\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\212\266\346\200\201\346\250\241\345\274\217.md"
rename to "\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\212\266\346\200\201\346\250\241\345\274\217.md"
index 59e6f8fc6c..16195426c6 100644
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\212\266\346\200\201\346\250\241\345\274\217.md"
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\212\266\346\200\201\346\250\241\345\274\217.md"
@@ -7,17 +7,21 @@
 该模式中，类的行为基于其状态改变。即允许一个对象在其内部状态改变时改变它的行为，对象看起来似乎修改了它的类。其别名为状态对象(Objects for States)，状态模式是一种对象行为型模式。
 
 # 3 架构
-## Context-环境类
-拥有状态的对象，环境类有时可以充当状态管理器(State Manager)，在环境类中对状态进行切换操作。
-## State-抽象状态类
-可以是抽象类，也可是接口，不同状态类就是继承这个父类的不同子类，状态类的产生是由于环境类存在多个状态，同时还满足：这些状态经常需要切换，在不同状态下对象行为不同。
-## ConcreteState-具体状态类
+- Context：环境类
+- State：抽象状态类
+- ConcreteState：具体状态类
+
+
 # 4 意义
 解决：对象的行为依赖于它的状态（属性），并且可以根据它的状态改变而改变它的相关行为。
 状态模式的关键是引入了一个抽象类来专门表示对象的状态 - 抽象状态类。而对象的每种具体状态类都继承该类，并在不同具体状态类中实现不同状态的行为，包括各种状态之间的转换。
 
-可以将不同对象下的行为单独提取出来封装在**具体的状态类**，使得**环境类**对象在其内部状态改变时可以改变它的行为，对象看起来似乎修改了它的类，而实际上是由于切换到不同的具体状态类实现的。
-由于**环境类**可以设置为任一具体状态类，因此它针对抽象状态类进行编程，在程序运行时可以将任一具体状态类的对象设置到环境类中，从而使得环境类可以改变内部状态，并且改变行为。
+在状态模式结构中需要理解环境类与抽象状态类的作用：
+- 环境类实际上就是拥有状态的对象，环境类有时候可以充当状态管理器(State Manager)，可在环境类中对状态进行切换操作。
+- 抽象状态类可以是抽象类，也可是接口，不同状态类就是继承这个父类的不同子类，状态类的产生是由于环境类存在多个状态，同时还满足：这些状态经常需要切换，在不同状态下对象行为不同。
+因此可以将不同对象下的行为单独提取出来封装在具体的状态类中，使得环境类对象在其内部状态改变时可以改变它的行为，对象看起来似乎修改了它的类，而实际上是由于切换到不同的具体状态类实现的。由于环境类可以设置为任一具体状态类，因此它针对抽象状态类进行编程，在程序运行时可以将任一具体状态类的对象设置到环境类中，从而使得环境类可以改变内部状态，并且改变行为。
+
+
 # 5 优点
 1. 封装了转换规则
 2. 枚举可能的状态，在枚举状态之前需要确定状态种类
@@ -55,6 +59,7 @@
 ![](https://img-blog.csdnimg.cn/20201004191242755.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_25,color_FFFF00,t_70#pic_center)
 
 1. State 接口
+![](https://img-blog.csdnimg.cn/20201004022025441.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_25,color_FFFF00,t_70#pic_center)
 2. 实现 State 接口的实体状态类
 ![](https://img-blog.csdnimg.cn/20201004184658967.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_25,color_FFFF00,t_70#pic_center)
 3. Context：带有某状态的类
@@ -66,23 +71,17 @@
 - 共享状态
 在有些情况下多个环境对象需共享同一状态，若期望在系统中实现多个环境对象实例共享一个或多个状态对象，那么需要将这些状态对象定义为环境的静态成员对象。
 
-##  简单状态模式
-状态都相互独立，状态之间无须进行转换的状态模式，这是最简单的一种状态模式。
-每个状态类都封装与状态相关的操作，无需关心状态切换，可在客户端直接实例化状态类，然后将状态对象设置到环境类。
-
-遵循“开闭原则”，在客户端可以针对抽象状态类进行编程，而将具体状态类写到配置文件中，同时增加新的状态类对原有系统也不造成任何影响。
-## 可切换状态的状态模式
+## 简单状态模式与可切换状态的状态模式
+- 简单状态模式
+状态都相互独立，状态之间无须进行转换的状态模式，这是最简单的一种状态模式。这种状态模式，每个状态类都封装与状态相关的操作，无需关心状态切换，可在客户端直接实例化状态类，然后将状态对象设置到环境类。它遵循“开闭原则”，在客户端可以针对抽象状态类进行编程，而将具体状态类写到配置文件中，同时增加新的状态类对原有系统也不造成任何影响。
+- 可切换状态的状态模式
 大多数的状态模式都是可切换状态的状态模式，在实现状态切换时，在具体状态类内部需要调用环境类Context的setState()方法进行状态的转换操作，在具体状态类中可以调用到环境类的方法，因此状态类与环境类之间通常还存在关联关系或者依赖关系。通过在状态类中引用环境类的对象来回调环境类的setState()方法实现状态的切换。在这种可以切换状态的状态模式中，增加新的状态类可能需要修改其他某些状态类甚至环境类的源代码，否则系统无法切换到新增状态。
 
 # 11 总结
 状态模式允许一个对象在其内部状态改变时改变它的行为，对象看起来似乎修改了它的类。其别名为状态对象，状态模式是一种对象行为型模式。
-
 状态模式包含三个角色：环境类又称为上下文类，它是拥有状态的对象，在环境类中维护一个抽象状态类State的实例，这个实例定义当前状态，在具体实现时，它是一个State子类的对象，可以定义初始状态；抽象状态类用于定义一个接口以封装与环境类的一个特定状态相关的行为；具体状态类是抽象状态类的子类，每一个子类实现一个与环境类的一个状态相关的行为，每一个具体状态类对应环境的一个具体状态，不同的具体状态类其行为有所不同。
-
 状态模式描述了对象状态的变化以及对象如何在每一种状态下表现出不同的行为。
+状态模式的主要优点在于封装了转换规则，并枚举可能的状态，它将所有与某个状态有关的行为放到一个类中，并且可以方便地增加新的状态，只需要改变对象状态即可改变对象的行为，还可以让多个环境对象共享一个状态对象，从而减少系统中对象的个数；其缺点在于使用状态模式会增加系统类和对象的个数，且状态模式的结构与实现都较为复杂，如果使用不当将导致程序结构和代码的混乱，对于可以切换状态的状态模式不满足“开闭原则”的要求。
+状态模式适用情况包括：对象的行为依赖于它的状态（属性）并且可以根据它的状态改变而改变它的相关行为；代码中包含大量与对象状态有关的条件语句，这些条件语句的出现，会导致代码的可维护性和灵活性变差，不能方便地增加和删除状态，使客户类与类库之间的耦合增强。
 
-状态模式的主要优点在于封装了转换规则，并枚举可能状态，它将所有与某个状态有关的行为放到一个类中，并且可以方便地增加新的状态，只需要改变对象状态即可改变对象的行为，还可以让多个环境对象共享一个状态对象，从而减少系统中对象的个数；其缺点在于使用状态模式会增加系统类和对象的个数，且状态模式的结构与实现都较为复杂，如果使用不当将导致程序结构和代码的混乱，对于可以切换状态的状态模式不满足“开闭原则”的要求。
-
-## 适用情况
-- 对象的行为依赖于它的状态（属性）并且可以根据它的状态改变而改变它的相关行为
-- 代码中包含大量与对象状态有关的条件语句，这些条件语句的出现，会导致代码的可维护性和灵活性变差，不能方便地增加和删除状态，使客户类与类库之间的耦合增强。
\ No newline at end of file
+<img src="../assets/公众号.png">
\ No newline at end of file
diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\255\226\347\225\245\346\250\241\345\274\217(Strategy-Pattern).md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\255\226\347\225\245\346\250\241\345\274\217(Strategy-Pattern).md"
new file mode 100644
index 0000000000..c29d0c0420
--- /dev/null
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\255\226\347\225\245\346\250\241\345\274\217(Strategy-Pattern).md"
@@ -0,0 +1,108 @@
+# 0 联系我
+![](http://upload-images.jianshu.io/upload_images/4685968-10e219418608a3d4?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "图片标题") 
+1.Q群【Java开发技术交流】：[https://jq.qq.com/?_wv=1027&k=5UB4P1T](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+2.完整博客链接:[www.shishusheng.com](https://blog.csdn.net/qq_33589510)
+3.知乎:[http://www.zhihu.com/people/shi-shu-sheng-](http://www.zhihu.com/people/shi-shu-sheng-)
+4.gayhub:[https://github.com/Wasabi1234](https://github.com/Wasabi1234)
+
+# 0.0 相关源码链接
+https://github.com/Wasabi1234/design-patterns
+
+# 1 定义
+![](https://upload-images.jianshu.io/upload_images/4685968-f3e6ce1684ece913.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+也叫做政策模式（Policy Pattern）
+- 维基百科
+对象有某个行为，但是在不同的场景中，该行为有不同的实现算法.
+比如每个人都要“交个人所得税”，但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法.
+- 定义
+Define a family of algorithms,encapsulate each one,and make them interchangeable.
+定义一组算法,将每个算法都封装起来,并且使它们之间可以互换.
+
+
+在`运行时`(非编译时)改变软件的算法行为
+- 主要思想
+定义一个通用的问题,使用不同的算法来实现,然后将这些算法都封装在一个统一接口的背后.
+
+![策略模式的通用类图](https://upload-images.jianshu.io/upload_images/4685968-ad1caf184324decf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+策略模式使用的就是面向对象的继承和多态机制
+
+策略模式的三个角色
+● Context 封装角色
+也叫做上下文角色，起承上启下封装作用;
+屏蔽高层模块对策略、算法的直接访问，封装可能存在的变化.
+
+● Strategy抽象策略角色
+策略、算法家族的抽象，通常为接口，定义每个策略或算法必须具有的方法和属性
+
+● ConcreteStrategy具体策略角色
+实现抽象策略中的操作，含有具体的算法
+
+### 通用源码
+- 抽象策略角色，它是一个非常普通的接口，在我们的项目中就是一个普通得不能再普通的接口了，定义一个或多个具体的算法
+
+
+# 2 适用场景
+针对一个对象，其行为有些是固定的不变的，有些是容易变化的，针对不同情况有不同的表现形式。那么对于这些容易变化的行为，我们不希望将其实现绑定在对象中，而是希望以动态的形式，针对不同情况产生不同的应对策略。那么这个时候就要用到策略模式了。简言之，策略模式就是为了应对对象中复杂多变的行为而产生的。
+
+- 系统有很多类,而他们的区别仅仅在于他们的行为不同
+- 一个系统需要动态地在几种算法中选择一种
+
+# 3 优点
+- 符合开闭原则
+- 避免使用多重条件转移语句
+比如省去大量的 if/else 和 switch 语句,降低代码的耦合
+- 提高算法的保密性和安全性
+只需知道策略的作用,而不关心内部实现
+
+# 4 缺点
+- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
+- 产生很多策略类
+
+# 5 相关设计模式的差异
+## 策略模式和工厂模式
+- 行为型
+接收已经创建好的对象,从而实现不同的行为
+- 创造型
+接收指令,创建出符合要求的具体对象
+
+## 策略模式和状态模式
+- 若系统中某个类的某个行为存在多种实现方式,客户端需要知道到底使用哪个策略
+- 若系统中某个对象存在多种状态,不同状态下的行为又具有差异性,状态之间会自动转换,客户端不需要关心具体状态
+
+# 6 实战
+![](https://upload-images.jianshu.io/upload_images/4685968-0ebc08f41e07cdca.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![](https://upload-images.jianshu.io/upload_images/4685968-98e2b70fe0d9a3f0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-ecbce7b0043a7490.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5dab16664b2d6639.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-57e3f0490d67cfb0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-8a75a258378f8a69.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![image.png](https://upload-images.jianshu.io/upload_images/4685968-844075f01a9e349b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+改造后的测试类
+![](https://upload-images.jianshu.io/upload_images/4685968-4991d2eaad9357c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+可见 if/else 语句过多,采取策略+工厂模式结合
+- 策略工厂
+![](https://upload-images.jianshu.io/upload_images/4685968-230088ca260db256.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 最新测试类
+![](https://upload-images.jianshu.io/upload_images/4685968-acf80da4fa5ea954.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 输出结果
+![](https://upload-images.jianshu.io/upload_images/4685968-7d26033a1b39bd6a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 7 源码应用解析
+## JDK中的比较器接口
+- 策略比较器
+![](https://upload-images.jianshu.io/upload_images/4685968-307666896c3d1800.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![具体策略](https://upload-images.jianshu.io/upload_images/4685968-d928dd16bea44a60.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+比如Arrays类中的 sort 方法通过传入不同比较接口器的实现达到不同排序策略
+![](https://upload-images.jianshu.io/upload_images/4685968-f92073712e30ce66.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## JDK中的TreeMap
+类似于促销活动中有促销策略对象,在T reeMap 中也有比较器对象
+![](https://upload-images.jianshu.io/upload_images/4685968-424f787da17d4876.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+compare 方法进步加工
+![](https://upload-images.jianshu.io/upload_images/4685968-32e02456542c1e48.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## Spring 中的Resource
+不同访问策略
+![](https://upload-images.jianshu.io/upload_images/4685968-66d6191177faaf2a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## Spring 中bean 的初始化ceInstantiationStrategy
+- 两种 bean 的初始化策略
+![](https://upload-images.jianshu.io/upload_images/4685968-8fa5e44e491aafdc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\277\255\344\273\243\345\231\250\346\250\241\345\274\217.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\277\255\344\273\243\345\231\250\346\250\241\345\274\217.md"
new file mode 100644
index 0000000000..1f3aa40ffd
--- /dev/null
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\277\255\344\273\243\345\231\250\346\250\241\345\274\217.md"
@@ -0,0 +1,387 @@
+# 1 概念
+![定义](https://upload-images.jianshu.io/upload_images/4685968-49588f1c3c13bc9d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![类型](https://upload-images.jianshu.io/upload_images/4685968-9766cbeda8e891bf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 迭代器模式（Iterator Pattern）
+目前已经是一个没落的模式，基本上没人会单独写一个迭代器，除非是产品性质的开发，其定义如下：
+>Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.（它提供一种方法访问一个容器对象中各个元素，而又不需暴露该对象的内部细节。）
+
+迭代器是为容器服务的，那什么是容器呢？ 能容纳对象的所有类型都可以称之为容器，例如Collection集合类型、Set类型等，迭代器模式就是为解决遍历这些容器中的元素而诞生的
+![迭代器模式的通用类图](https://upload-images.jianshu.io/upload_images/4685968-37e980ece5cad7fc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+迭代器模式提供了遍历容器的方便性，容器只要管理增减元素就可以了，需要遍历时交由迭代器进行
+看看迭代器模式中的各个角色：
+- Iterator抽象迭代器
+抽象迭代器负责定义访问和遍历元素的接口，而且基本上是有固定的3个方法：
+    - first()获得第一个元素
+    - next()访问下一个元素
+    - hasNext()是否已经访问到底部
+- ConcreteIterator具体迭代器
+具体迭代器角色要实现迭代器接口，完成容器元素的遍历。
+- Aggregate抽象容器
+容器角色负责提供创建具体迭代器角色的接口，必然提供一个类似createIterator()这样的方法，在Java中一般是iterator()方法。
+- Concrete Aggregate具体容器
+具体容器实现容器接口定义的方法，创建出容纳迭代器的对象。
+
+我们来看迭代器模式的通用源代码
+- 抽象迭代器
+```
+public interface Iterator<E> {
+
+    boolean hasNext();
+
+    E next();
+
+    default void remove() {
+        throw new UnsupportedOperationException("remove");
+    }
+
+    /**
+     * Performs the given action for each remaining element until all elements
+     * have been processed or the action throws an exception.  Actions are
+     * performed in the order of iteration, if that order is specified.
+     * Exceptions thrown by the action are relayed to the caller.
+     *
+     * @implSpec
+     * <p>The default implementation behaves as if:
+     * <pre>{@code
+     *     while (hasNext())
+     *         action.accept(next());
+     * }</pre>
+     *
+     * @param action The action to be performed for each element
+     * @throws NullPointerException if the specified action is null
+     * @since 1.8
+     */
+    default void forEachRemaining(Consumer<? super E> action) {
+        Objects.requireNonNull(action);
+        while (hasNext())
+            action.accept(next());
+    }
+}
+
+```
+- 具体迭代器
+```
+public class ConcreteIterator implements Iterator {
+     private Vector vector = new Vector();
+     //定义当前游标
+     public int cursor = 0;
+     @SuppressWarnings("unchecked")
+     public ConcreteIterator(Vector _vector){
+             this.vector = _vector;
+     }
+     //判断是否到达尾部
+     public boolean hasNext() {
+             if(this.cursor == this.vector.size()){
+                    return false;
+             }else{
+                    return true;
+             }
+     }
+     //返回下一个元素
+     public Object next() {
+             Object result = null;
+             if(this.hasNext()){
+                    result = this.vector.get(this.cursor++);
+             }else{
+                    result = null;
+             }
+             return result;
+     }
+     //删除当前元素
+     public boolean remove() {
+             this.vector.remove(this.cursor);
+             return true;
+     }
+}
+```
+开发系统时，迭代器的删除方法应该完成两个逻辑
+- 删除当前元素
+- 当前游标指向下一个元素
+
+- 抽象容器
+```
+public interface Aggregate {
+     //是容器必然有元素的增加
+     public void add(Object object);
+     //减少元素
+     public void remove(Object object);
+     //由迭代器来遍历所有的元素
+     public Iterator iterator();
+}
+```
+- 具体容器
+```
+public class ConcreteAggregate implements Aggregate {
+     //容纳对象的容器
+     private Vector vector = new Vector();
+     //增加一个元素
+     public void add(Object object) {
+             this.vector.add(object);
+     }
+     //返回迭代器对象
+     public Iterator iterator() {
+             return new ConcreteIterator(this.vector);
+     }
+     //删除一个元素
+     public void remove(Object object) {
+             this.remove(object);
+     }
+}
+```
+- 场景类
+```
+public class Client {
+     public static void main(String[] args) {
+             //声明出容器
+             Aggregate agg = new ConcreteAggregate();
+             //产生对象数据放进去
+             agg.add("abc");
+             agg.add("aaa");
+             agg.add("1234");     
+             //遍历一下
+             Iterator iterator = agg.iterator();
+             while(iterator.hasNext()){
+                     System.out.println(iterator.next());
+             }
+     }
+}
+```
+简单地说，迭代器就类似于一个数据库中的游标，可以在一个容器内上下翻滚，遍历所有它需要查看的元素。
+# 2 适用场景
+![](https://upload-images.jianshu.io/upload_images/4685968-8af37f548b02dbde.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+我们在例子中使用了迭代器模式后为什么使原本简单的应用变得复杂起来了呢？那是因为我们在简单的应用中使用了迭代器，在哪？
+注意这段话
+```
+for(IProject project:projectList)
+```
+它为什么能够运行起来？还不是因为ArrayList已经实现了iterator()方法，我们才能如此简单地应用。
+
+从JDK 1.2版本开始增加java.util.Iterator这个接口，并逐步把Iterator应用到各个聚集类（Collection）中，我们来看JDK 1.5的API帮助文件，你会看到有一个叫java.util.Iterable的接口
+看看有多少个接口继承了它：
+BeanContext,BeanContextServices,BlockingQueue,Collection,List,Queue,Set,SortedSet
+再看看有它多少个实现类：AbstractCollection,AbstractList,AbstractQueue,AbstractSequentialList,AbstractSet,ArrayBlockingQueue,ArrayList,AttributeList,BeanContextServicesSupport,BeanContextSupport,ConcurrentLinkedQueue,CopyOnWriteArrayList,CopyOnWriteArraySet,DelayQueue,EnumSet,HashSet,JobStateReasons,LinkedBlockingQueue,LinkedHashSet,LinkedList,PriorityBlockingQueue,PriorityQueue,RoleList,RoleUnresolvedList,Stack,SynchronousQueue,TreeSet,Vector
+基本上我们经常使用的类都在这个表中了，也正是因为Java把迭代器模式已经融入到基本API中了，我们才能如此轻松、便捷。
+
+我们再来看看Iterable接口。java.util.Iterable接口只有一个方法：iterator()，也就说，通过iterator()这个方法去遍历聚集类中的所有方法或属性，基本上现在所有的高级语言都有Iterator这个接口或者实现，Java已经把迭代器给我们准备好了，我们再去写迭代器，就有点多余了。所以呀，这个迭代器模式也有点没落了，基本上很少有项目再独立写迭代器了，直接使用Collection下的实现类就可以完美地解决问题。
+
+迭代器现在应用得越来越广泛了，甚至已经成为一个最基础的工具。一些大师级人物甚至建议把迭代器模式从23个模式中删除，为什么呢？就是因为现在它太普通了，已经融入到各个语言和工具中了，比如PHP中你能找到它的身影，Perl也有它的存在，甚至是前台的页面技术AJAX也可以有它的出现（如在Struts2中就可以直接使用iterator）。基本上，只要你不是在使用那些古董级（指版本号）的编程语言的话，都不用自己动手写迭代器。
+# 3 优点
+![](https://upload-images.jianshu.io/upload_images/4685968-d7e1562a2114c7a5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 4 缺点
+![](https://upload-images.jianshu.io/upload_images/4685968-bfc60b2582c63eb6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 相关模式
+![](https://upload-images.jianshu.io/upload_images/4685968-17e3688fbb4b6ae7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 实例
+现在还在开发或者维护的103个项目，项目信息很乱，很多是两年前的信息，你能不能先把这些项目最新情况重新打印一份给我，咱们好查查到底有什么问题
+![项目信息类图](https://upload-images.jianshu.io/upload_images/4685968-888b7ac85c6c420c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+简单得不能再简单的类图，是个程序员都能实现。我们来看看这个简单的东西
+- 项目信息接口
+```
+public interface IProject {
+     //从老板这里看到的就是项目信息
+     public String getProjectInfo();
+}
+```
+- 项目信息的实现
+```
+public class Project implements IProject {
+     //项目名称
+     private String name = "";
+     //项目成员数量
+     private int num = 0;
+     //项目费用
+     private int cost = 0;
+     //定义一个构造函数，把所有老板需要看到的信息存储起来
+     public Project(String name,int num,int cost){
+             //赋值到类的成员变量中
+             this.name = name;
+             this.num = num;
+             this.cost=cost;
+     }
+     //得到项目的信息
+     public String getProjectInfo() {
+             String info = "";
+             //获得项目的名称
+             info = info+ "项目名称是：" + [this.name](http://this.name/);
+             //获得项目人数
+             info = info + "\t项目人数: "+ this.num;
+             //项目费用
+             info = info+ "\t 项目费用："+ this.cost;
+             return info;
+     }
+}
+```
+通过构造函数把要显示的数据传递过来，然后放到getProjectInfo中显示
+- 报表的场景
+```
+public class Boss {
+             public static void main(String[] args) {
+                     //定义一个List，存放所有的项目对象
+                     ArrayList projectList = new ArrayList();
+                     //增加星球大战项目
+                     projectList.add(new Project("星球大战项目",10,100000));
+                     //增加扭转时空项目
+                     projectList.add(new Project("扭转时空项目",100,10000000));
+                     //增加超人改造项目
+                     projectList.add(new Project("超人改造项目",10000,1000000000));
+                     //这边100个项目
+                     for(int i=4;i<104;i++){
+                             projectList.add(new Project("第"+i+"个项目",i*5,i*1000000));
+                     }
+                     //遍历一下ArrayList，把所有的数据都取出
+                     for(IProject project:projectList){
+             System.out.println(project.getProjectInfo());
+                                        }
+             }
+}
+```
+然后看一下我们的运行结果，如下所示：
+![](https://upload-images.jianshu.io/upload_images/4685968-79d80105a9e74fa3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+又看了一遍程序,应该还有另外一种实现方式，因为是遍历嘛，让我想到的就是Java的迭代器接口java.util.iterator，它的作用就是遍历Collection集合下的元素，那我们的程序还可以有另外一种实现，通过实现iterator接口来实现遍历
+![增加迭代接口的类图](https://upload-images.jianshu.io/upload_images/4685968-a9322f7175906ebe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+看着是不是复杂了很多？是的，是有点复杂了，是不是我们把简单的事情复杂化了？
+我们先分析一下我们的类图java.util.Iterator接口中声明了三个方法，这是JDK定义的， ProjectIterator 实现该接口，并且聚合了Project对象，也就是把Project对象作为本对象的成员变量使用。看类图还不是很清晰，我们一起看一下代码，先看IProject接口的改变
+- 项目信息接口
+```
+public interface IProject {
+     //增加项目
+     public void add(String name,int num,int cost);
+     //从老板这里看到的就是项目信息
+     public String getProjectInfo();
+     //获得一个可以被遍历的对象
+     public IProjectIterator iterator();
+}
+```
+这里多了两个方法，一个是add方法，这个方法是增加项目，也就是说产生了一个对象后，直接使用add方法增加项目信息。我们再来看其实现类
+- 项目信息
+```
+public class Project implements IProject {
+     //定义一个项目列表，说有的项目都放在这里
+     private ArrayList projectList = new ArrayList();
+     //项目名称
+     private String name = "";
+     //项目成员数量
+     private int num = 0;
+     //项目费用
+     private int cost = 0;
+     public Project(){
+
+     }
+     //定义一个构造函数，把所有老板需要看到的信息存储起来
+     private Project(String name,int num,int cost){
+             //赋值到类的成员变量中
+             [this.name](http://this.name/) = name;
+             this.num = num;
+             this.cost=cost;
+     }
+     //增加项目
+     public void add(String name,int num,int cost){
+             this.projectList.add(new Project(name,num,cost));
+     }
+     //得到项目的信息
+     public String getProjectInfo() {
+             String info = "";
+             //获得项目的名称
+             info = info+ "项目名称是：" + [this.name](http://this.name/);
+             //获得项目人数
+             info = info + "\t项目人数: "+ this.num;
+             //项目费用
+             info = info+ "\t 项目费用："+ this.cost;
+             return info;
+     }
+     //产生一个遍历对象
+     public IProjectIterator iterator(){
+             return new ProjectIterator(this.projectList);
+     }
+}
+```
+通过构造函数，传递了一个项目所必需的信息，然后通过iterator()方法，把所有项目都返回到一个迭代器中。Iterator()方法看不懂不要紧，继续向下阅读。再看IProjectIterator接口
+- 项目迭代器接口
+```
+public interface IProjectIterator extends Iterator {
+}
+```
+大家可能对该接口感觉很奇怪，你定义的这个接口方法、变量都没有，有什么意义呢？有意义，所有的Java书上都会说要面向接口编程，你的接口是对一个事物的描述，也就是说我通过接口就知道这个事物有哪些方法，哪些属性，我们这里的IProjectIterator是要建立一个指向Project类的迭代器，目前暂时定义的就是一个通用的迭代器，可能以后会增加IProjectIterator的一些属性或者方法。当然了，你也可以在实现类上实现两个接口，一个是Iterator,一个是IProjectIterator（这时候，这个接口就不用继承Iterator），杀猪杀尾巴，各有各的杀法。
+`如果我要实现一个容器或者其他API提供接口时，我一般都自己先写一个接口继承，然后再继承自己写的接口，保证自己的实现类只用实现自己写的接口（接口传递，当然也要实现顶层的接口）`
+我们继续看迭代器的实现类
+- 项目迭代器
+```
+public class ProjectIterator implements IProjectIterator {
+     //所有的项目都放在ArrayList中
+     private ArrayList projectList = new ArrayList();
+     private int currentItem = 0; 
+     //构造函数传入projectList
+     public ProjectIterator(ArrayList projectList){
+             this.projectList = projectList;
+     }
+     //判断是否还有元素，必须实现
+     public boolean hasNext() {
+             //定义一个返回值
+             boolean b = true;
+             if(this.currentItem>=projectList.size()||this.projectList.get(this.currentItem)==null){
+                  b =false;
+          }
+             return b;
+     }
+     //取得下一个值
+     public IProject next() {
+             return (IProject)this.projectList.get(this.currentItem++);
+     }
+     //删除一个对象
+     public void remove() {
+             //暂时没有使用到
+     }
+}
+```
+细心的读者可能会从代码中发现一个问题，java.util.iterator接口中定义next()方法的返回值类型是E，而你在ProjectIterator中返回值却是IProject，E和IProject有什么关系？
+
+E是JDK 1.5中定义的新类型：元素（Element），是一个泛型符号，表示一个类型，具体什么类型是在实现或运行时决定，总之它代表的是一种类型，你在这个实现类中把它定义为ProjectIterator，在另外一个实现类可以把它定义为String，都没有问题。它与Object这个类可是不同的，Object是所有类的父类，随便一个类你都可以把它向上转型到Object类，也只是因为它是所有类的父类，它才是一个通用类，而E是一个符号，代表所有的类，当然也代表Object了。
+
+都写完毕了，看看我们的Boss类有多少改动
+- 老板看报表
+```
+public class Boss {
+             public static void main(String[] args) {
+                     //定义一个List，存放所有的项目对象
+                     IProject project = new Project();
+                     //增加星球大战项目
+                     project.add("星球大战项目ddddd",10,100000);
+                     //增加扭转时空项目
+                     project.add("扭转时空项目",100,10000000);
+                     //增加超人改造项目
+                     project.add("超人改造项目",10000,1000000000);
+                     //这边100个项目
+                     for(int i=4;i<104;i++){
+                             project.add("第"+i+"个项目",i*5,i*1000000);
+                     }
+                     //遍历一下ArrayList，把所有的数据都取出
+                     IProjectIterator projectIterator = project.iterator();
+                     while(projectIterator.hasNext()){
+                             IProject p = (IProject)projectIterator.next();
+                             System.out.println(p.getProjectInfo());
+                     }
+             }
+}
+```
+运行结果如下所示
+![](https://upload-images.jianshu.io/upload_images/4685968-0799f9ab5e0eb6ee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 5 Coding
+![](https://upload-images.jianshu.io/upload_images/4685968-f88001900fbb2f74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-4f20d23e8ab80e9e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-56c0007d897195f6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-42b53f913cb23433.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-9012d211ce1eeeb6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-937682a7a07e4127.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-05d12617f4159ade.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-1887cb9c29e392d7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 源码解析
+![](https://upload-images.jianshu.io/upload_images/4685968-090e96a077f2ef10.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-326f94443f8444d6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-038cb871c9367d4e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## mybatis 中的应用
+![](https://upload-images.jianshu.io/upload_images/4685968-e95c85cc70517d56.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-efe56902fdfe7d34.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+# 最佳实践
+如果你是做Java开发，尽量不要自己写迭代器模式！省省吧，使用Java提供的Iterator一般就能满足你的要求了
diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\344\270\203\345\244\247\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\344\270\203\345\244\247\345\216\237\345\210\231-\345\215\225\344\270\200\350\201\214\350\264\243\345\216\237\345\210\231.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\344\270\203\345\244\247\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\344\270\203\345\244\247\345\216\237\345\210\231-\345\215\225\344\270\200\350\201\214\350\264\243\345\216\237\345\210\231.md"
new file mode 100644
index 0000000000..690406d361
--- /dev/null
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\344\270\203\345\244\247\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\344\270\203\345\244\247\345\216\237\345\210\231-\345\215\225\344\270\200\350\201\214\350\264\243\345\216\237\345\210\231.md"
@@ -0,0 +1,170 @@
+> `文章收录在我的 GitHub 仓库，欢迎Star/fork：`
+> [Java-Interview-Tutorial](https://github.com/Wasabi1234/Java-Interview-Tutorial)
+> https://github.com/Wasabi1234/Java-Interview-Tutorial
+
+- 相关源码
+
+https://github.com/Wasabi1234/design-patterns
+
+# 1 简介
+## 1.1 定义
+不要存在多于一个导致类变更的原因。该原则备受争议，争议之处在于对职责的定义，什么是类的职责?怎么划分类的职责?
+
+
+## 1.2 特点
+一个类/接口/方法只负责一项职责。
+
+## 1.3 优点
+降低类的复杂度、提高类的可读性，提高系统的可维护性、降低变更引起的风险。
+
+# 2 代码实战
+## 2.1 鸟类案例
+- 最开始的 Bird 类
+![](https://img-blog.csdnimg.cn/20201011052517957.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+- 测试类
+![简单测试类](https://img-blog.csdnimg.cn/img_convert/7ea1da00d7a9e0615db6170063d5d468.png)
+
+显然鸵鸟用翅膀飞是错误的!
+- 修改类实现
+![](https://img-blog.csdnimg.cn/2020101105314531.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
+- 以上的设计依旧很差，总不能一味堆砌 ifelse 添加鸟类，结合该业务逻辑，考虑分别实现类职责，即根据单一原则创建两种鸟类即可。
+![](https://img-blog.csdnimg.cn/20201011053445284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20201011053528342.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20201011053706470.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
+
+## 2.2 课程案例
+- 最初的课程接口有两个职责，耦合过大
+![](https://img-blog.csdnimg.cn/20201011054054803.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
+- 按职责拆分
+![](https://img-blog.csdnimg.cn/20201011063546976.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+![](https://img-blog.csdnimg.cn/20201011063629925.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
+
+![](https://img-blog.csdnimg.cn/img_convert/82c766b21a88c40cdc3a1946c5cf51c4.png)
+
+# 3 终极实例
+
+## 3.1 单一!!!
+只要做过项目，肯定要接触到用户、机构、角色管理这些模块，基本上使用的都是RBAC模型（Role-Based Access Control，基于角色的访问控制，通过分配和取消角色来完成用户权限的授予和取消，使动作主体（用户）与资源的行为（权限）分离），确实是一个很好的解决办法。
+我们这里要讲的是用户管理、修改用户的信息、增加机构（一个人属于多个机构）、增加角色等，用户有这么多的信息和行为要维护，我们就把这些写到一个接口中，都是用户管理类
+
+- 用户信息维护类图
+![](https://img-blog.csdnimg.cn/img_convert/377b177c0732a18271498795b9720c91.png)
+
+得有问题，用户的属性和用户的行为没有分开，这是一个严重的错误！
+这个接口确实设计得一团糟
+- 应该把用户的信息抽取成一个BO（Business Object，业务对象）
+- 把行为抽取成一个Biz（Business Logic，业务逻辑）
+
+- 职责划分后的类图
+![](https://img-blog.csdnimg.cn/img_convert/bcfd4544da6e11c680c9533c3eb54900.png)
+
+重新拆封成两个接口
+- IUserBO负责用户的属性，简单地说，IUserBO的职责就是收集和反馈用户的属性信息
+- IUserBiz负责用户的行为，完成用户信息的维护和变更
+
+看一看分拆成两个接口怎么使用。我们现在是面向接口编程，所以产生了这个UserInfo对象之后，当然可以把它当IUserBO接口使用。也可以当IUserBiz接口使用，这要看你在什么地方使用了。
+- 要获得用户信息，就当是IUserBO的实现类
+- 要是希望维护用户的信息，就把它当作IUserBiz的实现类就成
+```java
+IUserInfo userInfo = new UserInfo();
+//我要赋值了，我就认为它是一个纯粹的BO
+IUserBO userBO = (IUserBO)userInfo;
+userBO.setPassword("abc");
+//我要执行动作了，我就认为是一个业务逻辑类
+IUserBiz userBiz = (IUserBiz)userInfo;
+userBiz.deleteUser();
+```
+确实可以如此，问题也解决了，但是我们来分析一下刚才的动作，为什么要把一个接口拆分成两个呢？
+其实，在实际的使用中，我们更倾向于使用两个不同的类或接口：一个是IUserBO，一个是IUserBiz
+![项目中经常采用的SRP类图](https://img-blog.csdnimg.cn/img_convert/ddf683f0892e2a6a08726c7642b9dd25.png)
+以上我们把一个接口拆分成两个接口的动作，就是依赖了单一职责原则，那什么是单一职责原则呢？单一职责原则的定义是：应该有且仅有一个原因引起类的变更。
+
+## 3.2  打破你的传统思维
+- SRP
+There should never be more than one reason for a class to change.
+
+再来看看下面这个例子是否好理解。电话通话的时候有4个过程发生：拨号、通话、回应、挂机
+那我们写一个接口
+![电话类图](https://img-blog.csdnimg.cn/img_convert/a7fab40bd3a15f919374e59ecb03b59a.png)
+- 电话过程
+```java
+public interface IPhone {
+     //拨通电话
+     public void dial(String phoneNumber);
+     //通话
+     public void chat(Object o);
+     //通话完毕，挂电话
+     public void hangup();
+}
+```
+实现类也比较简单，就不再写了，大家看看这个接口有没有问题？我相信大部分的读者都会说这个没有问题呀，以前我就是这么做的呀，某某书上也是这么写的呀，还有什么什么的源码也是这么写的！是的，这个接口接近于完美，看清楚了，是“接近”！
+单一职责原则要求一个接口或类只有一个原因引起变化，也就是一个接口或类只有一个职责，它就负责一件事情，看看上面的接口只负责一件事情吗？是只有一个原因引起变化吗？好像不是！
+
+IPhone这个接口可不是只有一个职责，它包含了两个职责：
+- 协议管理
+dial()和hangup()两个方法实现的是协议管理，分别负责拨号接通和挂机
+- 数据传送
+chat()实现的是数据的传送，把我们说的话转换成模拟信号或数字信号传递到对方，然后再把对方传递过来的信号还原成我们听得懂的语言。
+
+协议接通的变化会引起这个接口或实现类的变化吗？会的！那数据传送（想想看，电话不仅仅可以通话，还可以上网）的变化会引起这个接口或实现类的变化吗？会的！那就很简单了，这里有两个原因都引起了类的变化。这两个职责会相互影响吗？电话拨号，我只要能接通就成，甭管是电信的还是网通的协议；电话连接后还关心传递的是什么数据吗？
+通过这样的分析，我们发现类图上的IPhone接口包含了两个职责，而且这两个职责的变化不相互影响，那就考虑拆分成两个接口
+![职责分明的电话类图](https://img-blog.csdnimg.cn/img_convert/a32b12f5a750fccb83178a41a30131d5.png)
+
+
+这个类图看上去有点复杂了，完全满足了单一职责原则的要求，每个接口职责分明，结构清晰，但是我相信你在设计的时候肯定不会采用这种方式，一个手机类要把ConnectionManager和DataTransfer组合在一块才能使用。
+组合是一种强耦合关系，你和我都有共同的生命期，这样的强耦合关系还不如使用接口实现的方式呢，而且还增加了类的复杂性，多了两个类。
+经过这样的思考后，我们再修改一下类图
+![简洁清晰、职责分明的电话类图](https://img-blog.csdnimg.cn/img_convert/6440c34103fafd869b635156ddff80cf.png)
+这样的设计才是完美的，一个类实现了两个接口，把两个职责融合在一个类中。你会觉得这个Phone有两个原因引起变化了呀，是的，但是别忘记了我们是面向接口编程，我们对外公布的是接口而不是实现类。而且，如果真要实现类的单一职责，这个就必须使用上面的组合模式了，这会引起类间耦合过重、类的数量增加等问题，人为地增加了设计的复杂性。
+
+## 好处
+● 类的复杂性降低，实现什么职责都有清晰明确的定义
+● 可读性提高，复杂性降低，那当然可读性提高了
+● 可维护性提高，可读性提高，那当然更容易维护了
+● 变更引起的风险降低，变更是必不可少的，如果接口的单一职责做得好，一个接口修改只对相应的实现类有影响，对其他的接口无影响，这对系统的扩展性、维护性都有非常大的帮助。
+
+
+其实单一职责原则最难划分的就是职责。
+一个职责一个接口，但问题是“职责”没有一个量化的标准，一个类到底要负责那些职责？这些职责该怎么细化？细化后是否都要有一个接口或类？
+这些都需要从实际的项目去考虑，从功能上来说，定义一个IPhone接口也没有错，实现了电话的功能，而且设计还很简单，仅仅一个接口一个实现类，实际的项目我想大家都会这么设计。项目要考虑可变因素和不可变因素，以及相关的收益成本比率，因此设计一个IPhone接口也可能是没有错的。
+但是，如果纯从“学究”理论上分析就有问题了，有两个可以变化的原因放到了一个接口中，这就为以后的变化带来了风险。如果以后模拟电话升级到数字电话，我们提供的接口IPhone是不是要修改了？接口修改对其他的Invoker类是不是有很大影响？
+
+单一职责原则提出了一个编写程序的标准，用“职责”或“变化原因”来衡量接口或类设计得是否优良，但是“职责”和“变化原因”都是不可度量的，因项目而异，因环境而异。
+
+# 4 我单纯，所以我快乐
+对于接口，设计时一定要单一，但是对于实现类就需要多方面考虑。
+生搬硬套单一职责原则会引起类的剧增，给维护带来非常多的麻烦，而且过分细分类的职责也会人为地增加系统的复杂性。本来一个类可以实现的行为硬要拆成两个类，然后再使用聚合或组合的方式耦合在一起，人为制造了系统的复杂性。所以原则是死的，人是活的，这句话很有道理。
+
+单一职责原则很难在项目中得到体现，非常难，为什么？
+在国内，技术人员的地位和话语权都比较低，因此在项目中需要考虑环境，考虑工作量，考虑人员的技术水平，考虑硬件的资源情况，等等，最终妥协的结果是经常违背单一职责原则。而且，我们中华文明就有很多属于混合型的产物，比如筷子，我们可以把筷子当做刀来使用，分割食物；还可以当叉使用，把食物从盘子中移动到口中。而在西方的文化中，刀就是刀，叉就是叉，你去吃西餐的时候这两样肯定都是有的，刀就是切割食物，叉就是固定食物或者移动食物，分工很明晰。这种文化的差异很难一步改造过来，但是我相信随着技术的深入，单一职责原则必然会深入到项目的设计中，而且这个原则是那么的简单，简单得不需要我们更加深入地思考，单从字面上大家都应该知道是什么意思，单一职责嘛！
+
+单一职责适用于接口、类，同时也适用于方法。一个方法尽可能做一件事情，比如一个方法修改用户密码，不要把这个方法放到“修改用户信息”方法中，这个方法的颗粒度很粗
+
+- 一个方法承担多个职责
+![](https://img-blog.csdnimg.cn/img_convert/5cebbf619d200508262e3ddd37e4251e.png)
+
+在IUserManager中定义了一个方法changeUser，根据传递的类型不同，把可变长度参数changeOptions修改到userBO这个对象上，并调用持久层的方法保存到数据库中。
+在我的项目组中，如果有人写了这样一个方法，我不管他写了多少程序，花了多少工夫，一律重写！
+原因很简单：方法职责不清晰，不单一，不要让别人猜测这个方法可能是用来处理什么逻辑的。比较好的设计如下
+- 一个方法承担一个职责
+![](https://img-blog.csdnimg.cn/img_convert/21d3889d0f1a23a242e5a24cc464dea5.png)
+
+通过类图可知，如果要修改用户名称，就调用changeUserName方法
+要修改家庭地址，就调用changeHomeAddress方法
+要修改单位电话，就调用changeOfficeTel方法
+每个方法的职责非常清晰明确，不仅开发简单，而且日后的维护也非常容易，大家可以逐渐养成这样的习惯。
+
+
+ 
+# 5 最佳实践
+是的，类的单一职责确实受非常多因素的制约，纯理论地来讲，这个原则是非常优秀的，但是现实有现实的难处，你必须去考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等因素。
+对于单一职责原则，建议是接口一定要做到单一职责，类的设计尽量做到只有一个原因引起变化。
+
+参考
+- 《设计模式之蝉》
+
+
+![](https://img-blog.csdnimg.cn/20200825235213822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70#pic_center)
\ No newline at end of file
diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\344\270\203\345\244\247\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\344\270\203\345\244\247\345\216\237\345\210\231\345\256\236\346\210\230(\344\270\211)-\344\276\235\350\265\226\345\200\222\347\275\256\345\216\237\345\210\231.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\344\270\203\345\244\247\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\344\270\203\345\244\247\345\216\237\345\210\231\345\256\236\346\210\230(\344\270\211)-\344\276\235\350\265\226\345\200\222\347\275\256\345\216\237\345\210\231.md"
new file mode 100644
index 0000000000..751504d9e9
--- /dev/null
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\344\270\203\345\244\247\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\344\270\203\345\244\247\345\216\237\345\210\231\345\256\236\346\210\230(\344\270\211)-\344\276\235\350\265\226\345\200\222\347\275\256\345\216\237\345\210\231.md"
@@ -0,0 +1,264 @@
+# 1 定义
+- Dependence Inversion Principle,DIP
+High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
+![](https://upload-images.jianshu.io/upload_images/4685968-d532df9c099033f3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+高层模块和低层模块容易理解，每一个逻辑的实现都是由原子逻辑组成的，不可分割的原子逻辑就是低层模块，原子逻辑的再组装就是高层模块
+在Java语言中，抽象就是指接口或抽象类，两者都是不能直接被实例化的
+细节就是实现类，实现接口或继承抽象类而产生的类就是细节，其特点就是可以直接被实例化，也就是可以加上一个关键字new产生一个对象。
+依赖倒置原则在Java语言中的表现就是：
+● 模块间的依赖通过抽象发生，实现类之间不发生直接的依赖关系，其依赖关系是通过接口或抽象类产生的；
+● 接口或抽象类不依赖于实现类；
+● 实现类依赖接口或抽象类。
+
+更加精简的定义就是“面向接口编程”——OOD（Object-Oriented Design，面向对象设计）的精髓之一。
+![](https://upload-images.jianshu.io/upload_images/4685968-056e1d0bc5067d92.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 2 实战
+首先定义一个学习者类
+![](https://upload-images.jianshu.io/upload_images/4685968-ff2fa1d6a5d48ae5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![简单的测试类](https://upload-images.jianshu.io/upload_images/4685968-5cb8239865341a4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+假如现在又想学习 py, 那么面向实现编程就是直接在类添加方法
+![](https://upload-images.jianshu.io/upload_images/4685968-9708eba85a458afa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+此类需要经常改变,扩展性很差,Test 高层模块, Geely 类为低层模块,耦合度过高
+引入抽象
+![](https://upload-images.jianshu.io/upload_images/4685968-d611017d74407ff6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![实现类](https://upload-images.jianshu.io/upload_images/4685968-cff91fb808a6fba0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-92be431e0cca0043.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-5dc028f5b4752f06.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+于是就可以将原学习者类的方法都消除
+![](https://upload-images.jianshu.io/upload_images/4685968-72a8477045f3f88b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-176a09727508b4b1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+# 3  言而无信，你太需要契约
+采用依赖倒置原则可以减少类间的耦合性，提高系统的稳定性，降低并行开发引起的风险，提高代码的可读性和可维护性。
+
+证明一个定理是否正确，有两种常用的方法
+- 根据提出的论题，经过一番论证，推出和定理相同的结论，这是顺推证法
+- 首先假设提出的命题是伪命题，然后推导出一个荒谬、与已知条件互斥的结论，这是反证法
+
+我们今天就用反证法来证明依赖倒置原则是多么优秀和伟大！
+
+## 论题
+依赖倒置原则可以减少类间的耦合性，提高系统的稳定性，降低并行开发引起的风险，提高代码的可读性和可维护性。
+
+## 反论题
+不使用依赖倒置原则也可以减少类间的耦合性，提高系统的稳定性，降低并行开发引起的风险，提高代码的可读性和可维护性。
+
+我们通过一个例子来说明反论题是不成立的
+![图3-1 司机驾驶奔驰车类图](http://upload-images.jianshu.io/upload_images/4685968-1babd8680c8100cf.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+奔驰车可以提供一个方法run，代表车辆运行，实现过程如代码清单3-1所示。
+```
+public class Driver {      
+     //司机的主要职责就是驾驶汽车
+     public void drive(Benz benz){
+             benz.run();
+     }
+}
+```
+司机通过调用奔驰车的run方法开动奔驰车
+```
+public class Benz {
+     //汽车肯定会跑
+     public void run(){
+             System.out.println("奔驰汽车开始运行...");
+     }
+}
+```
+有车，有司机，在Client场景类产生相应的对象
+```
+public class Client {
+     public static void main(String[] args) {
+             Driver zhangSan = new Driver();
+             Benz benz = new Benz();
+             //张三开奔驰车
+             zhangSan.drive(benz);
+     }
+}
+```
+我们在一段貌似磐石的程序上加上一块小石头：张三司机不仅要开奔驰车，还要开宝马车，又该怎么实现呢？麻烦出来了，那好，我们走一步是一步，我们先把宝马车产生出来
+```
+public class BMW {
+     //宝马车当然也可以开动了
+     public void run(){
+             System.out.println("宝马汽车开始运行...");
+     }
+}
+```
+宝马车也产生了，但是我们却没有办法让张三开动起来，为什么？张三没有开动宝马车的方法呀！一个拿有C驾照的司机竟然只能开奔驰车而不能开宝马车，这也太不合理了！在现实世界都不允许存在这种情况，何况程序还是对现实世界的抽象
+
+我们的设计出现了问题：`司机类和奔驰车类之间是紧耦合的关系，其导致的结果就是系统的可维护性大大降低，可读性降低`
+两个相似的类需要阅读两个文件，你乐意吗？还有稳定性，什么是稳定性？固化的、健壮的才是稳定的，这里只是增加了一个车类就需要修改司机类，这不是稳定性，这是易变性。
+`被依赖者的变更竟然让依赖者来承担修改的成本，这样的依赖关系谁肯承担！`
+证明到这里，我们已经知道反论题已经部分不成立了。
+
+我们继续证明，`“减少并行开发引起的风险”`
+什么是并行开发的风险？并行开发最大的风险就是风险扩散，本来只是一段程序的错误或异常，逐步波及一个功能，一个模块，甚至到最后毁坏了整个项目。为什么并行开发就有这样的风险呢？一个团队，20个开发人员，各人负责不同的功能模块，甲负责汽车类的建造，乙负责司机类的建造，在甲没有完成的情况下，乙是不能完全地编写代码的，缺少汽车类，编译器根本就不会让你通过！在缺少Benz类的情况下，Driver类能编译吗？更不要说是单元测试了！在这种不使用依赖倒置原则的环境中，所有的开发工作都是“单线程”的，甲做完，乙再做，然后是丙继续……这在20世纪90年代“个人英雄主义”编程模式中还是比较适用的，一个人完成所有的代码工作。但在现在的大中型项目中已经是完全不能胜任了，一个项目是一个团队协作的结果，一个“英雄”再牛也不可能了解所有的业务和所有的技术，要协作就要并行开发，要并行开发就要解决模块之间的项目依赖关系，那然后呢？依赖倒置原则就隆重出场了！
+
+根据以上证明，如果不使用依赖倒置原则就会加重类间的耦合性，降低系统的稳定性，增加并行开发引起的风险，降低代码的可读性和可维护性。承接上面的例子，引入依赖倒置原则后的类图如图3-2所示。
+
+建立两个接口：IDriver和ICar，分别定义了司机和汽车的各个职能，司机就是驾驶汽车，必须实现drive()方法
+```
+public interface IDriver {
+     //是司机就应该会驾驶汽车
+     public void drive(ICar car);
+}
+```
+接口只是一个抽象化的概念，是对一类事物的最抽象描述，具体的实现代码由相应的实现类来完成
+```
+public class Driver implements IDriver{    
+     //司机的主要职责就是驾驶汽车
+     public void drive(ICar car){
+             car.run();
+     }
+}
+```
+在IDriver中，通过传入ICar接口实现了抽象之间的依赖关系，Driver实现类也传入了ICar接口，至于到底是哪个型号的Car，需要在高层模块中声明。
+
+ICar及其两个实现类的实现过程
+```
+public interface ICar {
+     //是汽车就应该能跑
+     public void run();
+}
+
+public class Benz implements ICar{
+     //汽车肯定会跑
+     public void run(){
+             System.out.println("奔驰汽车开始运行...");
+     }
+}
+
+public class BMW  implements ICar{      
+     //宝马车当然也可以开动了
+     public void run(){
+             System.out.println("宝马汽车开始运行...");
+     }
+}
+```
+在业务场景中，我们贯彻“抽象不应该依赖细节”，也就是我们认为抽象（ICar接口）不依赖BMW和Benz两个实现类（细节），因此在高层次的模块中应用都是抽象
+```
+public class Client {
+     public static void main(String[] args) {
+             IDriver zhangSan = new Driver();
+             ICar benz = new Benz();
+             //张三开奔驰车
+             zhangSan.drive(benz);
+     }
+}
+```
+Client属于高层业务逻辑，它对低层模块的依赖都建立在抽象上，zhangSan的表面类型是IDriver，Benz的表面类型是ICar，也许你要问，在这个高层模块中也调用到了低层模块，比如new Driver()和new Benz()等，如何解释？确实如此，zhangSan的表面类型是IDriver，是一个接口，是抽象的、非实体化的，在其后的所有操作中，zhangSan都是以IDriver类型进行操作，屏蔽了细节对抽象的影响。当然，张三如果要开宝马车，也很容易，我们只要修改业务场景类就可以
+```
+public class Client {
+     public static void main(String[] args) {
+             IDriver zhangSan = new Driver();
+             ICar bmw = new BMW();
+             //张三开奔驰车
+             zhangSan.drive(bmw);
+     }
+}
+```
+在新增加低层模块时，只修改了业务场景类，也就是高层模块，对其他低层模块如Driver类不需要做任何修改，业务就可以运行，把“变更”引起的风险扩散降到最低。
+
+注意　在Java中，只要定义变量就必然要有类型，一个变量可以有两种类型：表面类型和实际类型，表面类型是在定义的时候赋予的类型，实际类型是对象的类型，如zhangSan的表面类型是IDriver，实际类型是Driver。
+
+我们再来思考依赖倒置对并行开发的影响。两个类之间有依赖关系，只要制定出两者之间的接口（或抽象类）就可以独立开发了，而且项目之间的单元测试也可以独立地运行，而TDD（Test-Driven Development，测试驱动开发）开发模式就是依赖倒置原则的最高级应用。我们继续回顾上面司机驾驶汽车的例子，甲程序员负责IDriver的开发，乙程序员负责ICar的开发，两个开发人员只要制定好了接口就可以独立地开发了，甲开发进度比较快，完成了IDriver以及相关的实现类Driver的开发工作，而乙程序员滞后开发，那甲是否可以进行单元测试呢？答案是可以，我们引入一个JMock工具，其最基本的功能是根据抽象虚拟一个对象进行测试
+
+测试类
+```
+public class DriverTest extends TestCase{
+     Mockery context = new JUnit4Mockery();
+     @Test
+     public void testDriver() {
+             //根据接口虚拟一个对象
+             final ICar car = context.mock(ICar.class);
+             IDriver driver = new Driver();
+             //内部类
+             context.checking(new Expectations(){{
+                      oneOf (car).run();        
+             }});
+             driver.drive(car);
+     }
+}
+```
+注意粗体部分，我们只需要一个ICar的接口，就可以对Driver类进行单元测试。从这一点来看，两个相互依赖的对象可以分别进行开发，孤立地进行单元测试，进而保证并行开发的效率和质量，TDD开发的精髓不就在这里吗？测试驱动开发，先写好单元测试类，然后再写实现类，这对提高代码的质量有非常大的帮助，特别适合研发类项目或在项目成员整体水平比较低的情况下采用。
+
+抽象是对实现的约束，对依赖者而言，也是一种契约，不仅仅约束自己，还同时约束自己与外部的关系，其目的是保证所有的细节不脱离契约的范畴，确保约束双方按照既定的契约（抽象）共同发展，只要抽象这根基线在，细节就脱离不了这个圈圈，始终让你的对象做到“言必信，行必果”。
+
+# 4 依赖的三种写法
+依赖是可以传递的，A对象依赖B对象，B又依赖C，C又依赖D……生生不息，依赖不止
+`只要做到抽象依赖，即使是多层的依赖传递也无所畏惧！`
+
+对象的依赖关系有三种方式来传递
+# 4.1 构造函数传递依赖对象
+在类中通过构造函数声明依赖对象，`构造函数注入`
+```
+public interface IDriver {
+     //是司机就应该会驾驶汽车
+     public void drive();
+}
+```
+```
+public class Driver implements IDriver{
+     private ICar car;  
+     
+     //构造函数注入
+     public Driver(ICar _car){
+          this.car = _car;
+     }
+
+     //司机的主要职责就是驾驶汽车
+     public void drive(){
+             this.car.run();
+     }
+}
+```
+## 4.2 Setter方法传递依赖对象
+在抽象中设置Setter方法声明依赖关系,`Setter依赖注入`
+```
+public interface IDriver {
+     //车辆型号
+     public void setCar(ICar car);
+     //是司机就应该会驾驶汽车
+     public void drive();
+}
+```
+```
+public class Driver implements IDriver{
+     private ICar car;  
+     public void setCar(ICar car){
+             this.car = car;
+     }
+     //司机的主要职责就是驾驶汽车
+     public void drive(){
+             this.car.run();
+     }
+}
+```
+## 4.3 接口声明依赖对象
+在接口的方法中声明依赖对象
+# 5  最佳实践
+依赖倒置原则的本质就是`通过抽象（接口或抽象类）使各个类或模块的实现彼此独立，不互相影响，实现模块间的松耦合`
+我们怎么在项目中使用这个规则呢？只要遵循以下的几个规则就可以：
+- 每个类尽量都有接口或抽象类，或者抽象类和接口两者都具备
+这是依赖倒置的基本要求，接口和抽象类都是属于抽象的，有了抽象才可能依赖倒置
+
+- 变量的表面类型尽量是接口或者是抽象类
+很多书上说变量的类型一定要是接口或者是抽象类，这个有点绝对化了
+    - 比如一个工具类，xxxUtils一般是不需要接口或是抽象类的
+    - 如果你要使用类的clone方法，就必须使用实现类，这个是JDK提供的一个规范。
+
+- 任何类都不应该从具体类派生
+如果一个项目处于开发状态，确实不应该有从具体类派生出子类的情况，但这也不是绝对的，因为人都是会犯错误的，有时设计缺陷是在所难免的，因此只要不超过两层的继承都是可以忍受的
+
+- 尽量不要覆写基类的方法
+如果基类是一个抽象类，而且这个方法已经实现了，子类尽量不要覆写
+类间依赖的是抽象，覆写了抽象方法，对依赖的稳定性会产生一定的影响
+
+- 结合里氏替换原则使用
+父类出现的地方子类就能出现， 接口负责定义public属性和方法，并且声明与其他对象的依赖关系，抽象类负责公共构造部分的实现，实现类准确的实现业务逻辑，同时在适当的时候对父类进行细化。
+
+那到底什么是“倒置”呢？
+我们先说“正置”是什么意思，依赖正置就是类间的依赖是实实在在的实现类间的依赖，也就是面向实现编程，这也是正常人的思维方式，我要开奔驰车就依赖奔驰车，我要使用笔记本电脑就直接依赖笔记本电脑
+而编写程序需要的是对现实世界的事物进行抽象，抽象的结果就是有了抽象类和接口，然后我们根据系统设计的需要产生了抽象间的依赖，代替了人们传统思维中的事物间的依赖，“倒置”就是从这里产生的
+
+
+依赖倒置原则是设计原则中最难以实现的，它是实现开闭原则的重要途径，依赖倒置原则没有实现，就别想实现对扩展开放，对修改关闭。
+在项目中，大家只要记住是`面向接口编程`就基本上抓住了依赖倒置原则的核心。
diff --git "a/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\344\270\203\345\244\247\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\344\270\203\345\244\247\345\216\237\345\210\231\345\256\236\346\210\230(\344\272\214)-\345\274\200\351\227\255\345\216\237\345\210\231.md" "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\344\270\203\345\244\247\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\344\270\203\345\244\247\345\216\237\345\210\231\345\256\236\346\210\230(\344\272\214)-\345\274\200\351\227\255\345\216\237\345\210\231.md"
new file mode 100644
index 0000000000..a166a9eb61
--- /dev/null
+++ "b/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\344\270\203\345\244\247\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\344\270\203\345\244\247\345\216\237\345\210\231\345\256\236\346\210\230(\344\272\214)-\345\274\200\351\227\255\345\216\237\345\210\231.md"
@@ -0,0 +1,252 @@
+# 1  开闭原则的定义
+开闭原则是Java世界里最基础的设计原则，它指导我们如何建立一个稳定的、灵活的系统，先来看开闭原则的定义：
+`Software entities like classes,modules and functions should be open for extension but closed for modifications`
+![定义](https://upload-images.jianshu.io/upload_images/4685968-f44ce20cec946514.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+初看到这个定义，可能会很迷惑，对扩展开放？开放什么？对修改关闭，怎么关闭？没关系，我会一步一步带领大家解开这些疑惑。
+
+我们做一件事情，或者选择一个方向，一般需要经历三个步骤：What——是什么，Why——为什么，How——怎么做（简称3W原则，How取最后一个w）。对于开闭原则，我们也采用这三步来分析，即什么是开闭原则，为什么要使用开闭原则，怎么使用开闭原则。
+# 2 开闭原则的庐山真面目
+定义已经非常明确地告诉我们：软件实体应该对扩展开放，对修改关闭
+其含义是说`一个软件实体应该通过扩展来实现变化，而不是通过修改已有的代码来实现变化`
+![](https://upload-images.jianshu.io/upload_images/4685968-968f6b3c96577eef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+那什么又是软件实体呢？软件实体包括以下部分
+● 项目或软件产品中按照一定的逻辑规则划分的模块
+● 抽象和类
+● 方法
+
+一个软件产品只要在生命期内，都会发生变化，既然变化是一个既定的事实，我们就应该在设计时尽量适应这些变化，以提高项目的稳定性和灵活性，真正实现“拥抱变化”。
+开闭原则告诉我们应尽量通过扩展软件实体的行为来实现变化，而不是通过修改已有的代码来完成变化，它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。
+
+# 3 实例
+- 课程接口
+![](https://upload-images.jianshu.io/upload_images/4685968-2e4ec699689d2b01.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- Java 课程实现类
+![](https://upload-images.jianshu.io/upload_images/4685968-aa3b78b187f0ac56.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 测试类
+![](https://upload-images.jianshu.io/upload_images/4685968-96bb4615b438e943.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![原UML 图](https://upload-images.jianshu.io/upload_images/4685968-7b65534ac908e4ff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+现在想添加一个折扣优惠方法,如果直接修改放在接口,那么每个类都得重新添加方法实现,如下
+![](https://upload-images.jianshu.io/upload_images/4685968-b19bef256b640a15.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![](https://upload-images.jianshu.io/upload_images/4685968-04d9fd8ef0dd23af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+然而接口应该是稳定的,不该经常性的修改
+
+
+
+- Java 课程折扣类
+![](https://upload-images.jianshu.io/upload_images/4685968-771565f22322ebaa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+![添加折扣类后的UML 图](https://upload-images.jianshu.io/upload_images/4685968-ecbd999ef7868a4a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+![](https://upload-images.jianshu.io/upload_images/4685968-6c46320bd42b71c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+假如想通过添加方法获取原价
+![](https://upload-images.jianshu.io/upload_images/4685968-c486b05e5deb6675.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+debug发现其真正类型,由于是父类引用,无法直接获得原价方法,需强转
+![](https://upload-images.jianshu.io/upload_images/4685968-e2edad7ebdf04462.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+
+
+
+#  4 书店销售书籍例
+![书店售书类图](http://upload-images.jianshu.io/upload_images/4685968-206f8da2e62e1296.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- 小说类`NovelBook`是一个具体的实现类，是所有小说书籍的总称
+- `BookStore`指的是书店
+```
+public interface IBook {   
+     //书籍名称
+     public String getName();
+     //书籍售价
+     public int getPrice();
+     //书籍作者
+     public String getAuthor();
+}
+```
+- 目前书店只出售小说类书籍
+```
+public class NovelBook implements IBook {
+
+     private String name;       
+
+     private int price; 
+
+     private String author;             
+
+     public NovelBook(String _name,int _price,String _author){
+             this.name = _name;
+             this.price = _price;
+             this.author = _author;
+     }  
+
+     public String getAuthor() {
+             return this.author;
+     }
+
+     public String getName() {
+             return this.name;
+     }
+
+     public int getPrice() {
+             return this.price;
+     }
+}
+```
+把价格定义为int类型并不是错误，在非金融类项目中对货币处理时，一般取2位精度，通常的设计方法是在运算过程中扩大100倍，在需要展示时再缩小100倍，减少精度带来的误差。
+
+- 书店售书类
+```
+public class BookStore {
+     private  static final ArrayList bookList = new ArrayList();
+ 
+     static{
+             bookList.add(new NovelBook("天龙八部",3200,"金庸"));
+             bookList.add(new NovelBook("巴黎圣母院",5600,"雨果"));
+             bookList.add(new NovelBook("悲惨世界",3500,"雨果"));
+             bookList.add(new NovelBook("金瓶梅",4300,"兰陵笑笑生"));
+     }
+     //模拟买书
+     public static void main(String[] args) {
+             NumberFormat formatter = NumberFormat.getCurrencyInstance();
+             formatter.setMaximumFractionDigits(2);
+             System.out.println("-----------书店卖出去的书籍记录如下：-----------");
+             for(IBook book:bookList){
+                     System.out.println("书籍名称：" + book.getName()+"\t书籍作者：" +
+        book.getAuthor()+"\t书籍价格："+ formatter.format (book.getPrice()/
+        100.0)+"元");
+             }
+     }
+}
+```
+在BookStore中声明了一个静态模块，实现了数据的初始化
+![](https://upload-images.jianshu.io/upload_images/4685968-8abc57b9fb52bef3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+书店为了生存开始打折销售：所有40元以上的书籍9折销售，其他的8折销售
+对已经投产的项目来说，这就是一个变化，我们应该如何应对这样一个需求变化？
+有如下三种方法可以解决这个问题：
+
+● 修改接口
+在IBook上新增加一个方法getOffPrice()，专门用于进行打折处理，所有的实现类实现该方法
+但是这样修改，实现类NovelBook要修改，BookStore中的main方法也修改
+同时IBook作为`接口应该是稳定且可靠的，不应该经常发生变化`，否则接口作为契约的作用就失去了效能。因此，该方案否定。
+
+● 修改实现类
+修改NovelBook类中的方法，直接在getPrice()中实现打折处理，好办法，我相信大家在项目中经常使用的就是这样的办法，通过class文件替换的方式可以完成部分业务变化（或是缺陷修复）。
+该方法在项目有明确的章程（团队内约束）或优良的架构设计时，是一个非常优秀的方法，但是该方法还是有缺陷的。
+例如采购书籍人员也是要看价格的，由于该方法已经实现了打折处理价格，因此采购人员看到的也是打折后的价格，会`因信息不对称而出现决策失误`的情况。因此，该方案也不是一个最优的方案。
+
+● 通过扩展实现变化
+`增加一个子类`OffNovelBook，覆写getPrice方法，高层次的模块（static静态模块区）通过OffNovelBook类产生新的对象，完成业务变化对系统的最小化开发。好办法，修改也少，风险也小
+![扩展后的书店售书类图
+](http://upload-images.jianshu.io/upload_images/4685968-afe9f970bc620c07.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+- OffNovelBook类继承了NovelBook，并覆写了getPrice方法，不修改原有的代码
+```
+public class OffNovelBook extends NovelBook {
+     public OffNovelBook(String _name,int _price,String _author){
+             super(_name,_price,_author);
+     }
+     //覆写销售价格
+     @Override
+     public int getPrice(){
+             //原价
+             int selfPrice = super.getPrice();
+             int offPrice=0;
+             if(selfPrice>4000){  //原价大于40元，则打9折
+                     offPrice = selfPrice * 90 /100;
+             }else{
+                     offPrice = selfPrice * 80 /100;
+             }
+             return offPrice;
+     }
+}
+```
+很简单，仅仅覆写了getPrice方法，通过扩展完成了新增加的业务
+
+书店类BookStore需要依赖子类，代码稍作修改
+```
+public class BookStore {
+     private final static ArrayList bookList = new ArrayList();
+     //static静态模块初始化数据，实际项目中一般是由持久层完成
+     static{
+             bookList.add(new OffNovelBook("天龙八部",3200,"金庸"));
+             bookList.add(new OffNovelBook("巴黎圣母院",5600,"雨果"));
+             bookList.add(new OffNovelBook("悲惨世界",3500,"雨果"));
+             bookList.add(new OffNovelBook("金瓶梅",4300,"兰陵笑笑生"));
+     }  
+     //模拟书店买书
+     public static void main(String[] args) {
+             NumberFormat formatter = NumberFormat.getCurrencyInstance();
+             formatter.setMaximumFractionDigits(2);
+             System.out.println("-----------书店卖出去的书籍记录如下：-----------");
+             for(IBook book:bookList){
+                      System.out.println("书籍名称：" + book.getName()+"\t书籍作者：" + book.getAuthor()+ "\t书籍价格：" + formatter.format (book.getPrice()/100.0)+"元");
+             }
+     }
+}
+```
+我们只修改了粗体部分，其他的部分没有任何改动，运行结果如下所示。
+![](https://upload-images.jianshu.io/upload_images/4685968-7dfc29e013406fe2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+打折销售开发完成了。看到这里，各位可能有想法了：增加了一个OffNoveBook类后，你的业务逻辑还是修改了，你修改了static静态模块区域。
+这部分确实修改了，该部分属于高层次的模块，是由持久层产生的，在业务规则改变的情况下高层模块必须有部分改变以适应新业务，改变要尽量地少，防止变化风险的扩散。
+
+注意　开闭原则对扩展开放，对修改关闭，并不意味着不做任何修改，低层模块的变更，必然要有高层模块进行耦合，否则就是一个孤立无意义的代码片段。
+
+我们可以把变化归纳为以下三种类型：
+
+● 逻辑变化
+只变化一个逻辑，而不涉及其他模块，比如原有的一个算法是a*b+c，现在需要修改为a*b*c，可以通过修改原有类中的方法的方式来完成，前提条件是所有依赖或关联类都按照相同的逻辑处理。
+
+● 子模块变化
+一个模块变化，会对其他的模块产生影响，特别是一个低层次的模块变化必然引起高层模块的变化，因此在通过扩展完成变化时，高层次的模块修改是必然的，刚刚的书籍打折处理就是类似的处理模块，该部分的变化甚至会引起界面的变化。
+
+● 可见视图变化
+可见视图是提供给客户使用的界面，如JSP程序、Swing界面等，该部分的变化一般会引起连锁反应（特别是在国内做项目，做欧美的外包项目一般不会影响太大）。如果仅仅是界面上按钮、文字的重新排布倒是简单，最司空见惯的是业务耦合变化，什么意思呢？一个展示数据的列表，按照原有的需求是6列，突然有一天要增加1列，而且这一列要跨N张表，处理M个逻辑才能展现出来，这样的变化是比较恐怖的，但还是可以通过扩展来完成变化，这就要看我们原有的设计是否灵活。
+
+回顾一下书店销售书籍的程序，首先是我们有一个还算灵活的设计（不灵活是什么样子？BookStore中所有使用到IBook的地方全部修改为实现类，然后再扩展一个ComputerBook书籍，你就知道什么是不灵活了）
+然后有一个需求变化，我们通过扩展一个子类拥抱了变化
+最后把子类投入运行环境中，新逻辑正式投产。
+通过分析，我们发现并没有修改原有的模块代码，IBook接口没有改变，NovelBook类没有改变，这属于已有的业务代码，我们保持了历史的纯洁性。
+放弃修改历史的想法吧，一个项目的基本路径应该是这样的：项目开发、重构、测试、投产、运维，其中的重构可以对原有的设计和代码进行修改，运维尽量减少对原有代码的修改，保持历史代码的纯洁性，提高系统的稳定性。
+# 5  为什么要采用开闭原则
+依照Java语言的称谓，开闭原则是抽象类，其他五大原则是具体的实现类，开闭原则在面向对象设计领域中的地位就类似于牛顿第一定律在力学、勾股定律在几何学、质能方程在狭义相对论中的地位，其地位无人能及。
+## 5.1 开闭原则对测试的影响
+有变化提出时，我们就需要考虑一下，原有的健壮代码是否可以不修改，仅仅通过扩展实现变化呢？
+否则，就需要把原有的测试过程回笼一遍，需要进行单元测试、功能测试、集成测试甚至是验收测试
+
+以上面提到的书店售书为例，IBook接口写完了，实现类NovelBook也写好了，我们需要写一个测试类进行测试，测试类如代码
+```
+public class NovelBookTest extends TestCase {
+     private String name = "平凡的世界";
+     private int price = 6000;
+     private String author = "路遥";      
+     private IBook novelBook = new NovelBook(name,price,author);
+     //测试getPrice方法
+     public void testGetPrice() {
+             //原价销售，根据输入和输出的值是否相等进行断言
+             super.assertEquals(this.price, this.novelBook.getPrice());
+     }
+}
+```
+在单元测试中，有一句非常有名的话，叫做"Keep the bar green to keep the code clean"，即保持绿条有利于代码整洁，这是什么意思呢？绿条就是Junit运行的两种结果中的一种：要么是红条，单元测试失败；要么是绿条，单元测试通过。一个方法的测试方法一般不少于3种，为什么呢？首先是正常的业务逻辑要保证测试到，其次是边界条件要测试到，然后是异常要测试到，比较重要的方法的测试方法甚至有十多种，而且单元测试是对类的测试，类中的方法耦合是允许的，在这样的条件下，如果再想着通过修改一个方法或多个方法代码来完成变化，基本上就是痴人说梦，该类的所有测试方法都要重构，想象一下你在一堆你并不熟悉的代码中进行重构时的感觉吧！
+
+在书店售书的例子中，增加了一个打折销售的需求，如果我们直接修改getPrice方法来实现业务需求的变化，那就要修改单元测试类。想想看，我们举的这个例子是非常简单的，如果是一个复杂的逻辑，你的测试类就要修改得面目全非。还有，在实际的项目中，一个类一般只有一个测试类，其中可以有很多的测试方法，在一堆本来就很复杂的断言中进行大量修改，难免会出现测试遗漏情况，这是项目经理很难容忍的事情。
+
+所以，我们需要通过扩展来实现业务逻辑的变化，而不是修改。上面的例子中通过增加一个子类OffNovelBook来完成了业务需求的变化，这对测试有什么好处呢？我们重新生成一个测试文件OffNovelBookTest，然后对getPrice进行测试，单元测试是孤立测试，只要保证我提供的方法正确就成了，其他的我不管
+```
+public class OffNovelBookTest extends TestCase {   
+     private IBook below40NovelBook = new OffNovelBook("平凡的世界",3000,"路遥");
+     private IBook above40NovelBook = new OffNovelBook("平凡的世界",6000,"路遥");      
+     //测试低于40元的数据是否是打8折
+     public void testGetPriceBelow40() {
+             super.assertEquals(2400, this.below40NovelBook.getPrice());
+     }  
+     //测试大于40的书籍是否是打9折
+     public void testGetPriceAbove40(){
+             super.assertEquals(5400, this.above40NovelBook.getPrice());
+     }
+}
+```
+新增加的类，新增加的测试方法，只要保证新增加类是正确的就可以了。
+## 5.2 开闭原则可以提高复用性
+在面向对象的设计中，所有的逻辑都是从原子逻辑组合而来的，而不是在一个类中独立实现一个业务逻辑。只有这样代码才可以复用，粒度越小，被复用的可能性就越大。那为什么要复用呢？减少代码量，避免相同的逻辑分散在多个角落，避免日后的维护人员为了修改一个微小的缺陷或增加新功能而要在整个项目中到处查找相关的代码，然后发出对开发人员“极度失望”的感慨。那怎么才能提高复用率呢？缩小逻辑粒度，直到一个逻辑不可再拆分为止。
+## 5.3 开闭原则可以提高可维护性
+一款软件投产后，维护人员的工作不仅仅是对数据进行维护，还可能要对程序进行扩展，维护人员最乐意做的事情就是扩展一个类，而不是修改一个类，甭管原有的代码写得多么优秀还是多么糟糕，让维护人员读懂原有的代码，然后再修改，是一件很痛苦的事情，不要让他在原有的代码海洋里游弋完毕后再修改，那是对维护人员的一种折磨和摧残。
+
+## 5.4 面向对象开发的要求
+万物皆对象，我们需要把所有的事物都抽象成对象，然后针对对象进行操作，但是万物皆运动，有运动就有变化，有变化就要有策略去应对，怎么快速应对呢？这就需要在设计之初考虑到所有可能变化的因素，然后留下接口，等待“可能”转变为“现实”。
+![](https://upload-images.jianshu.io/upload_images/4685968-ffae6225d2fe16c1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
diff --git "a/\351\207\215\346\236\204/Java\351\200\211\346\211\213\344\270\272\344\275\225\350\246\201\346\214\201\347\273\255\346\200\247\345\255\246\344\271\240.md" "b/\351\207\215\346\236\204/Java\351\200\211\346\211\213\344\270\272\344\275\225\350\246\201\346\214\201\347\273\255\346\200\247\345\255\246\344\271\240.md"
deleted file mode 100644
index c6ae78de13..0000000000
--- "a/\351\207\215\346\236\204/Java\351\200\211\346\211\213\344\270\272\344\275\225\350\246\201\346\214\201\347\273\255\346\200\247\345\255\246\344\271\240.md"
+++ /dev/null
@@ -1,27 +0,0 @@
-# 痛点
-- 这辈子读了很多书和理论，但写代码时就忘了
-- 很多知识点，知其然而不知其所以然，浮于表面，止于面试
-- 以为读源码就能提升，殊不知嵌套太多，只是迷失自我
-- 不重视业务，开发过程才知道设计和业务需求的差距，延期说来就来
-- 遇到线上问题时不懂如何定位，胡乱百度问人
-- 和大佬差距从未缩小
-
-# 灵魂拷问
-- 概念简单但理解不深刻，把知道当做懂
-- 基础不稳
-每天只忙业务，不持续研究和学习，很多问题知其然而不知其所以然，排查问题靠猜、靠问，而非自己专业的推测和验证
-- 没有科学的学习方法
-- 态度
-- 把需要思考的问题当做了纯记忆的问题，学习和工作过程中缺乏思考。比如很多人是 “记忆” 经典图书的知识点，而不是理解知识点，导致容易遗忘，不能灵活运用。在学习很多知识点时缺乏思考，没有去搞懂是什么、不明白为什么、不知道如何去做；
-
-从 Java 新手到高手是个漫长过程。 很多同学遇到 BUG 时由于基础不扎实也没有系统地排查方法，为了解决一个小问题浪费了大把的时间。而且写出的 BUG 太多将直接或间接影响绩效，影响同事、领导对你的印象。
-
-阻碍初学者进步的往往是一成不变的学习方法，是一叶障目不见泰山的盲目自信，而破解尴尬的核心就在于提高自身的学习能力和排错能力。
-
-# 怎么学习?
-
-- 结合源码、Java 语言规范、Java 虚拟机规范
-- 站在设计者角度思考问题
-- 思维导图、流程图和常见 UML，精通业务梳理
-- 单元测试、Code Review 提升代码质量
-- 实际开发的最佳实践
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/OOP \344\270\211\345\244\247\347\211\271\345\276\201\344\271\213\345\244\232\346\200\201\357\274\210Polymorphism\357\274\211.md" "b/\351\207\215\346\236\204/OOP \344\270\211\345\244\247\347\211\271\345\276\201\344\271\213\345\244\232\346\200\201\357\274\210Polymorphism\357\274\211.md"
deleted file mode 100644
index f0ece83792..0000000000
--- "a/\351\207\215\346\236\204/OOP \344\270\211\345\244\247\347\211\271\345\276\201\344\271\213\345\244\232\346\200\201\357\274\210Polymorphism\357\274\211.md"	
+++ /dev/null
@@ -1,147 +0,0 @@
-OOP三大特性最重要的：多态。
-
-很多程序员虽然在用支持OOP的语言，但却从未用过多态。
-
-- 只使用封装、继承的编程方式，称为基于对象（Object Based）编程
-- 只有加入多态，才能称为OOP
-没写过多态，就是没写过OO代码。
-
-正是有了多态，软件设计才有更大弹性，更好拥抱变化。
-# 如何理解多态？
-多态，即一个接口，多种形态。
-
-一个draw方法，以正方形调用，则画正方形；以圆形调用，则画圆形：
-![](https://img-blog.csdnimg.cn/26163eccb42440899cbf633925961e08.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-继承的两种方式之一的实现继承，请尽可能用组合替代。而接口继承，主要是给多态用的。
-
-因为重点在于继承体系的使用者，主要考虑父类，而非子类。
-如下代码段，不必考虑具体形状是啥，仅需调用它的draw方法
-![](https://img-blog.csdnimg.cn/986e35f6e6134ec897c497265a6b23f5.png)
-优势在于，一旦有新变化，比如将正方形换成圆，除了变量初始化，其它代码不需要动。
-
-> 既然多态这么好，为什么很多人感觉无法在项目中自如地多态？
-
-多态需构建抽象。
-
-# 构建抽象
-找出不同事物的共同点，这是最具挑战的。令人懵逼的也往往是眼中的不同之处。在很多人眼里，鸡就是鸡，鸭就是鸭。
-
-寻找共同点，根基还是**分离关注点**。
-当你能看出鸡、鸭都有羽毛，都养在家里，你才可能识别“家禽”。
-
-> 构建出的抽象会以接口（此处接口不一定是个语法，而是一个类型的约束）体现。所以，本文讨论的多态范畴内，接口、抽象类、父类等概念等价，统一称为接口。
-
-## 接口的意义
-### 接口隔离了变化部分、不变部分
-- 不变部分
-接口的约定
-- 变化部分
-子类各自的实现
-
-最影响程序的就是各种变化。有时需求来了，你的代码就得跟着改，一个可能的原因就是各种代码混在了一起。
-比如，一个通信协议的调整，你要改业务逻辑，这明显不合理。
-所以识别出变化与不变，是区分程序员水平的一大标准。
-
-### 接口是边界
-清晰界定系统内不同模块的职责很关键，而模块间彼此通信最重要的就是通信协议，对应到代码中的接口。
-
-很多程序员在接口中添加方法很随意，因为他们眼里，不存在实现者和使用者的角色差异，导致没有清晰边界，后果就是模块定义随意，彼此之间互相耦合，最终玩死自己。
-
-所以，理解多态在于理解接口，理解接口在于谨慎选择接口中的方法。
-
-面向接口编程的价值就源于多态。
-
-这些原则你可能都听说过，但写代码时，就会忽略细节。
-比如：
-![](https://img-blog.csdnimg.cn/491f7d59c37849fb8790c5a3e42edf3d.png)
-这显然没有面向接口编程，推荐写法：
-![](https://img-blog.csdnimg.cn/855dad01a2bb4dc08f4a2cb9b7d6474b.png)
-差别就在于变量类型，是面向一个接口，还是面向一个具体实现类。
-
-多态对程序员的要求更高，需要你能感知未来变化！
-
-# 实现多态
-**OOP会限制使用函数指针，它是对程序控制权的间接转移施加了约束。**
-理解这句话，就要理解多态如何实现的。
-
-Linux文件系统用C实现了OOP，就是用了函数指针：
-![](https://img-blog.csdnimg.cn/140c9d5229104bd2b859041322e29283.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-即可这样赋值：
-![](https://img-blog.csdnimg.cn/d58e3218f6934aea98b1958a7abc2094.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-给该结构体赋不同值，就能实现不同文件系统。
-但这样非常不安全。既然是个结构体字段，就可能改写它：
-![](https://img-blog.csdnimg.cn/28209b781f58425a80d0dc49cd5e391b.png)
-本该在hellofs_read运行的代码，跑进了sillyfs_read，程序崩溃。对于C这种灵活语言，你无法禁止这种操作，只能靠人为规定和代码检查。
-
-到了OOP 语言，这种做法由一种编程结构变成一种语法。给函数指针赋值的操作下沉到了运行时去实现。运行时的实现，就是个查表过程：
-![](https://img-blog.csdnimg.cn/deb97483fcb544aa94513061a2f97ff7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-一个类在编译时，会给其中的函数在虚拟函数表中找个位置，把函数指针地址写进去，不同子类对应不同虚拟表。
-当用接口去调用对应函数时，实际上完成的就是在对应虚拟函数表的一个偏移，不管现在面对哪个子类，都可找到相应实现函数。
-
-C++这种注重运行时消耗的语言：
-- 只有virtual函数会出现在虚拟函数表
-- 普通函数就是直接的函数调用，以此减少消耗
-
-对于Java程序员，可通过给无需改写的方法添加final帮助运行时优化。
-
-当多态成为语法，就限制了函数指针的使用，犯错率大大降低！
-
-# 没有继承的多态
-封装，多态。至于继承，却不是必然选项。只要能够遵循相同接口，即可表现出多态，所以，多态并不一定要依赖继承。
-
-动态语言中一个常见说法 - Duck Typing，若走起来像鸭子，叫起来像鸭子，那它就是鸭子。
-两个类可不在同一继承体系下，但只要有相同接口，就是一种多态。
-
-如下代码段：Duck和FakeDuck不在一棵继承树上，但make_quack调用时，它们俩都可传进去。
-![](https://img-blog.csdnimg.cn/5ead17c395794be4a5123eba5095ff47.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_10,color_FFFFFF,t_70,g_se,x_16)
-
-很多软件都有插件能力，而插件结构本身就是多态。
-比如，著名的开源图形处理软件GIMP，它自身是用C开发的，为它编写插件就需要按照它规定的结构去编写代码：
-
-```c
-struct GimpPlugInInfo
-{
-  /* GIMP 应用初始启动时调用 */
-  GimpInitProc  init_proc;
-
-  /* GIMP 应用退出时调用 */
-  GimpQuitProc  quit_proc;
-
-  /* GIMP 查询插件能力时调用 */
-  GimpQueryProc query_proc;
-
-  /* 插件安装之后，开始运行时调用*/
-  GimpRunProc   run_proc;
-};
-```
-
-我们所需做的就是按照这个结构声明出PLUG_IN_INFO，这是隐藏的名字，将插件的能力注册给GIMP这个应用：
-
-```c
-GimpPlugInInfo PLUG_IN_INFO = {
-  init,
-  quit,
-  query,
-  run
-};
-```
-这里用的C语言，但依然能表现多态。
-
-多态依赖于继承，这只是某些程序设计语言自身的特点。在面向对象本身的体系中，封装和多态才是重中之重，而继承则很尴尬。
-
-**一定要跳出单一语言的局限，这样，才能对各种编程思想有更本质的认识。**
-
-OOP三大特点的地位：
-- 封装是面向对象的根基，软件就是靠各种封装好的对象逐步组合出来的
-- 继承给了继承体系内的所有对象一个约束，让它们有了统一的行为
-- 多态让整个体系能够更好地应对未来的变化。
-
-# FAQ
-某系统需要对普通用户增删改查，后来加了超级管理员用户也需要增删改查。把用户的操作抽象成接口方法，让普通用户和管理员用户实现接口方法…… 那么问题来了，这些接口方法的出入参没法完全共用，比如查询用户信息接口，普通用户和超级管理员用户的返回体信息字段不同。所以没法抽象，请问一下老师这种应不应该抽象呢？如果应该做成抽象需要怎么分离变的部分呢
-
-应该分，因为管理员和普通用户的关注点是不同的。管理员和普通用户可以分别提供接口，分别提供相应的内容。
-如果说非要二者共用，可以考虑在服务层共用，在接口层面分开，在接口层去适配不同的接口。
-# 总结
-多态是基于对象和面向对象的分水岭。多态就是接口一样，实现不同。
-**建立起恰当抽象，面向接口编程。**
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/OOP\344\270\211\345\244\247\347\211\271\346\200\247\344\271\213\345\260\201\350\243\205.md" "b/\351\207\215\346\236\204/OOP\344\270\211\345\244\247\347\211\271\346\200\247\344\271\213\345\260\201\350\243\205.md"
deleted file mode 100644
index 881de22e73..0000000000
--- "a/\351\207\215\346\236\204/OOP\344\270\211\345\244\247\347\211\271\346\200\247\344\271\213\345\260\201\350\243\205.md"
+++ /dev/null
@@ -1,118 +0,0 @@
-像C语言这种结构化编程帮助我们解决了很多问题，但随现代应用系统的代码量剧增，其局限也越发明显：各模块依赖关系太强，不能有效隔离变化。
-
-于是，OOP诞生。但对于大部分初学就是C语言的开发人员，习惯了结构化编程思维，认为：
-```java
-OO=数据+函数
-```
-不能说是错的，但层次太低。结构化编程思维就如管中窥豹，只能看到局部。想要用好OOP，则需更宏观的视野。
-
-# 如何理解封装
-OO是解决更大规模应用开发的一种尝试，它提升了程序员管理程序的尺度。
-
-封装，是OO的根基：
-- 它把紧密相关的信息放在一起，形成一个单元
-- 若该单元稳定，即可将该单元和其它单元继续组合，构成更大单元
-- 同理，继续构建更大单元，层层封装变大
-# OO的初心
-OO由Alan Kay提出，图灵奖获得者。其最初构想，对象就是细胞。将细胞组织起来，组成身体各器官，再组织起来，就构成人体。而当你去观察人时，就不用再去考虑每个细胞如何。所以，OO提供了更宏观思维。
-
-但这一切的前提：每个对象要构建好，即封装要做好。就像每个细胞都有细胞壁将其与外界隔离，形成一个完整个体。
-
-Kay强调**对象之间只能通过消息通信**。
-按如今程序设计语言通常做法，发消息就是方法调用，对象之间就是靠方法调用通信。
-
-> 但这方法调用并非简单地把对象内部的数据通过方法暴露。Kay的构想甚至想把数据去掉。
-
-因为封装的重点在于**对象提供了哪些行为，而非有哪些数据**。
-即便我们把对象理解成数据+函数，数据、函数也不是对等的:
-- 函数是接口
-接口是稳定的
-- 数据是内部的实现
-实现是易变的，应该隐藏
-
-很多人的开发习惯：写一个类，写其一堆字段，然后生成一堆getter、setter，暴露这些字段的访问。
-这种做法是错误的，它把数据当成设计核心，这一堆getter、setter，就等于暴露实现细节。
-
-**正确做法**：
-1. 设计一个类，先考虑对象应提供哪些**行为**
-2. 然后，根据这些行为提供对应方法
-3. 最后考虑实现这些方法要有哪些**字段**
-
-所以连接二者的是方法，其命名就是个大学问了，应体现你的意图，而非具体怎么做的。所以，getXXX和setXXX绝不是个好命名。
-比如，设计：用户修改密码。
-
-一些人上手就来：
-![](https://img-blog.csdnimg.cn/5ab9f7f30f824b32b24444885ef1c1ee.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_19,color_FFFFFF,t_70,g_se,x_16)
-
-但推荐写法是表达你的意图：
-![](https://img-blog.csdnimg.cn/3d818b23e99d43b7bdbabf50b007ddd3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-两段代码只是修改密码的方法名不同，但更重要的差异是：
-- 一个在说做什么
-- 一个在说怎么做
-
-**将意图与实现分离**，优秀设计须考虑的问题。
-
-实际项目中，有时确实需要暴露一些数据。
-所以，当确实需暴露时，再写getter也不迟，你一定要问自己为何要加getter？
-关于setter：
-- 大概率是你用错名字，应该用一个表达意图的名字
-- setter通常意味着修改，这是不推荐的
-
-可变的对象会带来很多的问题，后续再深入讨论。所以，设计中更好的做法是设计不变类。
-
-Lombok很好，少写很多代码，但必须限制它的使用，像Data和Setter都不该用。Java Bean本来也不是应该用在所有情况下的技术，导致很多人误用。
-# 减少接口的暴露
-之所以需要封装，就是要构建一个内聚单元。所以，要减少该单元对外的暴露：
-- 减少内部实现细节的暴露
-- 减少对外暴露的接口
-
-OOP语言都支持public、private，日常开发经常会轻率地给一个方法加public，不经意间暴露了一些本是内部实现的部分。
-
-比如，一个服务要停下来时，你可能要把一些任务都停下来：
-![](https://img-blog.csdnimg.cn/498578fc8ea2413dadd84961d0067381.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_14,color_FFFFFF,t_70,g_se,x_16)
-
-别人可能这样调用时：
-![](https://img-blog.csdnimg.cn/bd93854e9f3a4acb8ecf56f37b3a7122.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_13,color_FFFFFF,t_70,g_se,x_16)
-
-突然某天，你发现停止轮询任务必须在停止定时器任务之前，你就不得不要求别人改代码。而这一切就是因为我们很草率地给那两个方法加上public，让别人有机会看到这俩方法。
-
-设计角度，必须谨慎自省：这个方法有必要暴露吗？
-其实可仅暴露一个方法：
-![](https://img-blog.csdnimg.cn/18eaf9df12fe4f56b6a32cf226053673.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_14,color_FFFFFF,t_70,g_se,x_16)外部的调用代码也会简化：
-![](https://img-blog.csdnimg.cn/85533a09a5cf4e8180b361b02bed0b65.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_11,color_FFFFFF,t_70,g_se,x_16)
-尽可能减少接口暴露，该原则适于类的设计、系统设计。
-很多人都特别随意在系统里添加接口，让一个看似不复杂的系统，随便就有成百上千个接口。
-
-后续，当你想改造系统，去掉一些接口时，很可能造成线上事故，因为你根本不知道哪个团队在何时用到了它。
-所以，软件设计中，谨慎暴露接口！
-
-可总结出：最小化接口暴露，即每增加一个接口，都要找到一个充分的理由！
-#  总结
-封装，除了要减少内部实现细节的暴露，还要减少对外接口的暴露。每暴露一个公共API就增加一份职责，所以在每次暴露API时就要问自己，这个职责是自己必要的，还是有可能会增加不必要的负担。
-一个原则是最小化接口暴露。
-
-注意区分：
-- OO和 Java 语言
-- 传输数据和业务对象
-
-Java语言特点就是一切皆对象，Java中对象的概念跟OO中对象的概念不同：
-- 前者是语言特性
-- 后者是一种编程范式
-
-在具体编码中，哪些属于对象，哪些不属于对象，应该是程序员掌控。
-比如：
-- DDD中的领域实体，就是对象，需仔细设计其行为接口
-- 一些POJO，可看成数据载体，可直接加getter、setter的（没有这些默认getter、setter，很多第三方数据转化都很不方便，比如JSON，SQL）。使用时，不归结为对象即可
-
-**基于行为进行封装，不要暴露实现细节，最小化接口暴露。**
-
-Demeter 不是一个人，而是一个项目，项目主页 http://www.ccs.neu.edu/research/demeter/。最早提到迪米特法则的论文出版于 1989 年，Assuring good style for object-oriented programs。还有一本书，1996 年出版，Adaptive Object-Oriented Software: The Demeter Method with Propagation Patterns。没有看过。
-
-Demeter 是希腊神话中的大地和丰收女神，也叫做德墨忒尔。
-
-迪米特法则简单的说，分为两个部分：不该有直接依赖关系的类之间，不要有依赖；有依赖关系的类之间，尽量只依赖必要的接口。其实如果用另一个名字“最小知识原则”可能更容易理解一些，这个也算是程序员的“黑话”吧。
-
-虽然接触OOP已经很久了，不过写程序时，大多数人还是习惯“一个对象一张表”，也没有太多考虑封装。整个类里都是 getter、setter 的事情也做过，这就像是用“面向对象的语言写面向过程的代码”。
-
-> 参考
-> - https://www2.ccs.neu.edu/research/demeter/
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\344\273\200\344\271\210\346\230\257\345\210\206\347\246\273\345\205\263\346\263\250\347\202\271\357\274\237.md" "b/\351\207\215\346\236\204/\344\273\200\344\271\210\346\230\257\345\210\206\347\246\273\345\205\263\346\263\250\347\202\271\357\274\237.md"
deleted file mode 100644
index 084836a16a..0000000000
--- "a/\351\207\215\346\236\204/\344\273\200\344\271\210\346\230\257\345\210\206\347\246\273\345\205\263\346\263\250\347\202\271\357\274\237.md"
+++ /dev/null
@@ -1,182 +0,0 @@
-软件开发就是在解决问题。
-- 那问题一般如何解决？
-最常见的解决思路是分而治之。但如何分解、组合，就是软件设计中考虑的问题。
-
-然而，软件设计环节的大部分人都聚焦如何组合，忽略了第一步：分解。
-“分解？不就是把一个大系统拆成子系统，再把子系统拆成模块，一层层拆下去呗。”
-这种程度分解远远不够，因为粒度太大了，就会导致不同东西混淆一起。听不懂？那就看个案例吧。
-# 技术和业务耦合
-某清结算系统，一开始觉得是个业务规则比较多的系统，偶尔出点故障，也情有可原。
-但分析系统故障报告后，发现这个系统设计得极其复杂。
-有一处：上游系统以推送方式向这个系统发消息。在原本的实现中，开发人员发现这个过程可能会丢消息，于是设计了一个补偿机制：
-因为推送过来的数据是之前由这个系统发出去的，它本身有这些数据的初始信息，于是，开发人员就在DB增加一个状态，记录消息返回的情况。
-一旦发现丢消息，该系统就会访问上游接口，将丢失的数据请求回来。
-
-就是这补偿机制设计，带来了后续问题。比如，当系统业务量增加时，DB访问压力本身很大，但在这时，丢数据概率也增加了，用于补偿的线程也会频繁访问DB，因为它要找出丢失数据，还要把请求回来的数据写回DB。
-
-即一旦业务量上升，本来就吃力的系统，负担更重，系统卡顿在所难免。
-
-补偿机制的设计有问题，在于上游系统向下游推消息，应该是个通信层面问题。而在原有的设计中，因为那个状态的添加，这个问题被带到业务层面。
-典型的分解没有做好，分解粒度太大。开发只考虑业务功能，忽视其他维度。技术和业务混在一起。
-
-> 那到底怎么设计呢？
-
-既然是否丢消息是通信层面的事，争取在通信层解决。
-当时的解决方案是，选择吞吐量更大的MQ。在未来可见的业务量下，消息都不会丢。通信层面的问题在通信层面解决了，业务层面也就不会受到影响了。改造后，系统的稳定性果然得到大幅提升。
-
-- 这样上游系统的补偿接口，现在也不需要了，上游系统得到简化
-- 这个系统里那个表示状态的字段，其实还被用在了业务处理中，也引发过其他问题，现在它只用在业务处理中，角色单一了，相关问题也就少了
-
-有人见怪了，觉得补偿机制还是要的吧，就算换吞吐量大的消息队列，丢失消息还是有可能出现的，只是几率小很多。只是之前的补偿机制设计得不合理？
-若分析是不是丢消息，就要看它何时会丢消息。之前的业务丢消息是因为MQ处理不过来，而换了吞吐更好的MQ就不存在该问题了。
-其实，真正需要的是可靠的信息传送通道，至于是不是MQ不重要。若怕丢消息，可在生产端重试，在消费者端做幂等。补偿是一个能把场景弄复杂的做法，不推荐。
-
-常见的还有要区分技术异常和业务异常的。技术层面的异常信息不应该暴露给上层的业务人员。典型的例子就是大型网站的错误页面，而不是直接把后台的npe堆栈信息抛给用户。
-
-技术与业务的分割线太模糊。代码的重构优化会点，但是分离关注点就涉及到具体的业务了，具体业务的划分与分离就又迷茫了。
-
-简单区分：
-- 业务人员能理解的就是业务
-比如，订单
-- 业务人员不理解的就是技术
-比如，多线程
-
-软件设计都期望将粒度分解越小越好，但又嫌分解太小过于麻烦。就像很多人希望别人写好文档，自己却不写。
-
-业务代码和技术实现往往被混写在一起，都是因为分离不够！
-# 分离关注点
-看来分解粒度太大是不太好哦。那到底该如何考虑分解？
-
-传统上，我们习惯的分解问题的方式是树型。
-比如，按功能分解，可分为：功能1、2、3等，然后，每个功能再分成功能1.1、功能1.2、功能2.1、功能3.1等。
-
-只从业务上看，似乎没问题。但要实现一个真实的系统，不仅要考虑功能性需求，还要考虑非功能性需求。
-比如数据不能丢失、有的系统还要求处理速度快。
-
-这与业务不是同维度，设计时要能发现这些非功能性需求。
-分解问题时候，会有很多维度，每个都代表着一个关注点，这就是设计中一个常见的说法，“分离关注点（Separation of concerns）”。
-
-可以分离的关注点很多，最常见的就是把业务处理和技术实现两个关注点混杂。
-
-> 如果现在业务的处理性能跟不上，你有什么办法解决吗？
-
-多线程！的确是一种解决方案。但若不限制地去修改这段代码成多线程，则会引入多线程相关问题，比如，各种资源竞争、数据同步，稍有不慎，更多 bug。
-
-**写好业务规则**和**正确地处理多线程**，这是两个不同关注点，应该分离业务代码和多线程代码。
-
-业务程序员基本都不该写多线程程序，应由专门程序员把并发处理封装成框架，提供给大家使用，写业务代码即可。
-
-> Kent Beck 曾曰：我不准备在这本书里讲高并发问题，我的做法是把高并发问题从我的程序里移出去
-
-把业务处理和技术实现混在一起的问题还有很多。
-比如经常问怎么处理分布式事务，怎么做分库分表等。更该问的是，业务需要分布式事务吗？我是不是业务划分不清楚，才造成DB压力？
-
-程序员最常犯的错误就是认为所有问题都是技术问题，总试图用技术解决所有问题。任何试图用技术去解决其他关注点的问题，只是越挣扎，陷得越深。
-
-另外容易产生混淆的关注点是
-# 不同的数据变动方向
-做数据库访问用Spring Data JPA好，还是MyBatis好。Spring Data JPA简化了数据库访问，自动生成对应的SQL语句，而MyBatis则要自己手写SQL。
-
-普通的增删改查用Spring Data JPA非常省事，但对于一些复杂场景，他会担心自动生成SQL的性能有问题，还是手写SQL优化来得直接。是不是挺纠结的？
-
-为什么需要复杂查询？
-你会说有一些统计报表需要。
-
-那你发现了其中混淆关注点的地方？普通的增删改查需要经常改动数据库，而复杂查询的使用频率其实很低。
-
-之所以出现工具选择的困难，是因为把两种数据使用频率不同的场景混在一起。如果将前台访问（处理增删改查）和后台访问（统计报表）分开，纠结也就不复存在。
-
-不同的数据变动方向还有很多，比如：
-- 动静分离，就是把变和不变的内容分开
-- 读写分离，就是把读和写分开
-- 前面提到的高频和低频，也可以分解开；
-……
-
-不同的数据变动方向，就是一个潜在的、可以分离的关注点。
-
-分离关注点，不只适用于宏观的层面。
-在微观的代码层面，你用同样的思维方式，也可以帮助你识别出一些混在一起的代码。比如，很多程序员很喜欢写setter，但你真的有那么多要改变的东西吗？实际上可能就是封装没做好而已。
-
-分离关注点之所以重要，有两方面原因：
-- 不同的关注点混在一起会带来一系列的问题，正如前面提到的各种问题
-- 当分解得足够细小，你就会发现不同模块的共性，才有机会把同样的信息聚合在一起。这会为软件设计的后续过程，也就是组合，做好准备。
-
-# CQRS（Command Query Responsibility Segregation）
-命令与查询职责分离，其分离了增删改与查询这两个关注点。
-- 静态上，拆分了这两块代码。使各自可以采用不同技术栈，做针对性的调优。动态上，切分了流量，能够更灵活的做资源分配。
-
-- 查询服务的实现
-可以走从库，这有利于降低主库压力，也可以做到水平扩展。但需要注意数据延迟问题。在异步同步和同步多写上要做好权衡。
-也可都走主库，这时候查询服务最好增加缓存层，降低主库压力，而增删改服务要做好缓存的级联操作，以保证缓存时效
-当然也可以走非关系型数据库，搜索引擎类的es,solr，分布式存储的tidb等等，按需选择。
-
-通常增删改会涉及到很多domain knowledge. 平时更多的操作其实是查询，不需要通过从持久化生成domain model到内存中再返回。
-# FAQ
-## 订单系统
-- 先下单写到DB
-- 然后发送消息给MQ
-
-这两步没法放到一个事务。 如果用本地消息表：
-- order写DB
-- 然后在写本地消息表
-
-这两步就能放到一个事务了，保证肯定成功。
-然后再有线程读取本地消息表，MQ发消息，如果成功，更改本地消息表状态 。
-### 分析
-下单入库和发消息给下游确实是两个动作，但这两个动作的顺序一定是这样？一定要在一个线程完成吗？可不可以先发消息呢？
-比如，把消息发给下游后，有个下游接收到消息后，再把消息入库。
-如果这样，发消息，由MQ保证消息不丢，下游入库，又可保证订单持久化。这种设计下，其实并不需要事务，也就不必为事务纠结了。
-
-发现大家在工作中往往不做分离，分析需求的时候把方案揉在一起。可以怎样去练习做分离呢？
-有一种从小事练起的方法，就是写代码时，把自己写的函数行数限定在一定的规模之下，比如，10行。超过10行的代码，你就要去仔细想想是否是有东西混在了一起。
-这种方法锻炼的就是找出不同关注点的思维习惯，一旦你具备了这种思维习惯，再去看大的设计，自然也会发现不同的关注点。
-
-##  用户购买会员
-目前设计了两张表：
-- 存储用户购买会员的所有记录
-- 存当前的会员信息 (主要是开始、结束时间，但没有会员等级之类)
-设计这张表是为了SQL关联查询方便，不用再判断是否过期
-
-但有个问题：我要用定时器一直扫这表，等会员过期了，就得删除对应记录。
-这么做的问题在哪？ 更好的解决方式应该是什么？如果做到更细维度的拆分？
-
-首先，没有把业务和实现分清。
-业务是实现一个会员系统，涉及会员购买，主要是会员时间要延长，还会涉及会员资格的判断，即当前用户是否是会员。
-
-基于这些内容判断，可以有不同实现。根据当前实现，可以这样：
-购买会员；
-- 若会员信息不存在，则添加会员信息
-- 会员信息存在，则修改会员结束时间
-
-会员资格判别，根据用户 ID 和当前时间是否在时间范围内查询：
-- 记录存在，则是会员
-- 否则不是
-
-因此可考虑：
-- 购买会员时，可产生会员购买记录，此记录仅供后续查询用
-- 只有当会员信息表过大时，才考虑是否需要删除
-
-在这个实现中：
-- 把 购买 和 会员信息 分开
-- 把 会员信息是否生效 与 记录是否删除 分开
-# 总结
-软件设计第一步：分解。
-
-大多数系统的设计做得不够好，问题常常出现在分解这步就没做好。常见的分解问题就是分解的粒度太大，把各种维度混淆在一起。在设计中，将一个模块的不同维度分开，有一个专门的说法，叫分离关注点。
-
-分离关注点很重要，一方面，不同的关注点混在一起会带来许多问题；另一方面，分离关注点有助于我们发现不同模块的共性，更好地进行设计。分离关注点，是我们在做设计的时候，需要时时绷起的一根弦。
-
-- 分离关注点是一种意识，在设计中要意识到需要分离关注点。一旦自己在做设计时，出现纠结或者是觉得设计有些复杂，首先需要想想，是不是因为把不同的关注点混在了一起。这种意识是需要训练的，让自己从无意识中摆脱出来。
-一种可以考虑的做法是，把它与一些实践结合起来，比如，在设计评审的DoD中，增加一条“是否考虑了分离关注点”。
-- 分离关注点也要从小事开始练习
-比如，可以从编写小函数开始。给自己设定了一个目标，函数代码小于10行。每次写完代码，就可以对代码进行调整。超过10行代码的函数，问问自己，是不是有混在一起的内容？
-	- 如果有循环，循环里面的部分就是对单个元素的处理，可以提取到一个函数里
-	- 如果有if...else，每种情况都可以单独放到一个函数里
-	- 如果有多个 if...else，要问问自己是不是缺少了一些模型，是不是可以用多态解决
-...
-如果经过练习，函数都能写短，那就可以开始做类的练习。把每个类写小，以此类推，逐步训练。《重构》里《代码的坏味道》可以成为我们改进的参考点。
-
-只有在日常开发的过程中不断练习，才能成为我们的下意识反应。
-
-**分离关注点，发现的关注点越多越好，粒度越小越好。**
-![](https://img-blog.csdnimg.cn/99612de7ae6e46e2a8d0f1646a9d513a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\344\275\240\346\200\216\344\271\210\346\200\273\346\230\257\350\203\275\345\206\231\345\207\272\344\270\244\344\270\211\345\215\203\350\241\214\347\232\204controller\347\261\273\357\274\237.md" "b/\351\207\215\346\236\204/\344\275\240\346\200\216\344\271\210\346\200\273\346\230\257\350\203\275\345\206\231\345\207\272\344\270\244\344\270\211\345\215\203\350\241\214\347\232\204controller\347\261\273\357\274\237.md"
deleted file mode 100644
index 8fe5608175..0000000000
--- "a/\351\207\215\346\236\204/\344\275\240\346\200\216\344\271\210\346\200\273\346\230\257\350\203\275\345\206\231\345\207\272\344\270\244\344\270\211\345\215\203\350\241\214\347\232\204controller\347\261\273\357\274\237.md"
+++ /dev/null
@@ -1,126 +0,0 @@
-你一定经常见到一个两三千行的 controller 类，类之所以发展成如此庞大，有如下原因：
-- 长函数太多
-- 类里面有特别多的字段和函数
-量变引起质变，可能每个函数都很短小，但数量太多
-#  1 程序的modularity
-你思考过为什么你不会把all code写到一个文件？因为你的潜意识里明白：
-- 相同的功能模块无法复用
-- 复杂度远超出个人理解极限
-
-一个人理解的东西是有限的，在国内互联网敏捷开发环境下，更没有人能熟悉所有代码细节。
-
-解决复杂的最有效方案就是分而治之。所以，各种程序设计语言都有自己的模块划分（**modularity**）方案：
-- 从最初的按文件划分
-- 到后来使用OO按类划分
-
-开发者面对的不再是细节，而是模块，模块数量显然远比细节数量少，理解成本大大降低，开发效率也提高了，再也不用 996， 每天都能和妹纸多聊几句了。
-
-modularity，本质就是分解问题，其背后原因，就是个人理解能力有限。
-
-> 说这么多我都懂，那到底怎么把大类拆成小类？
-
-# 2 大类是怎么来的？
-## 2.1 职责不单一
-**最容易产生大类的原因**。
-
-CR一段代码：
-![](https://img-blog.csdnimg.cn/6544c51823ed4e45a1182220fae18d03.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-该类持有大类的典型特征，包含一坨字段：这些字段都缺一不可吗？
-- userId、name、nickname等应该是一个用户的基本信息
-- email、phoneNumber 也算是和用户相关联
-很多应用都提供使用邮箱或手机号登录方式，所以，这些信息放在这里，也能理解
-- authorType，作者类型，表示作者是签约作者还是普通作者，签约作者可设置作品的付费信息，但普通作者无此权限
-- authorReviewStatus，作者审核状态，作者成为签约作者，需要有一个申请审核的过程，该状态字段就是审核状态
-- editorType，编辑类型，编辑可以是主编，也可以是小编，权限不同
-
-这还不是 User 类的全部。但只看这些内容就能看出问题：
-- 普通用户既不是作者，也不是编辑
-作者和编辑这些相关字段，对普通用户无意义
-- 对那些成为作者的用户，编辑的信息意义不大
-因为作者不能成为编辑。编辑也不会成为作者，作者信息对成为编辑的用户无意义
-
-总有一些信息对一部分人毫无意义，但对另一部分人又必需。出现该问题的症结在于只有“一个”用户类。
-
-普通用户、作者、编辑，三种不同角色，来自不同业务方，关心的是不同内容。仅因为它们都是同一系统的用户，就把它们都放到一个用户类，导致任何业务方的需求变动，都会反复修改该类，**严重违反单一职责原则**。
-所以破题的关键就是职责拆分。
-
-虽然这是一个类，但它把不同角色关心的东西都放在一起，就愈发得臃肿了。
-
-只需将不同信息拆分即可：
-```java
-public class User {
-  private long userId;
-  private String name;
-  private String nickname;
-  private String email;
-  private String phoneNumber;
-  ...
-}
-
-public class Author {
-  private long userId;
-  private AuthorType authorType;
-  private ReviewStatus authorReviewStatus;
-  ...
-}
-
-public class Editor {
-  private long userId;
-  private EditorType editorType;
-  ...
-}
-```
-拆出 Author、Editor 两个类，将和作者、编辑相关的字段分别移至这两个类里。
-这俩类分别有个 userId 字段，用于关联该角色和具体用户。
-## 2.2 字段未分组
-有时觉得有些字段确实都属于某个类，结果就是，这个类还是很大。
-
-之前拆分后的新 User 类：
-```java
-public class User {
-  private long userId;
-  private String name;
-  private String nickname;
-  private String email;
-  private String phoneNumber;
-  ...
-}
-```
-这些字段应该都算用户信息的一部分。但依然也不算是个小类，因为该类里的字段并不属于同一种类型的信息。
-如，userId、name、nickname算是用户的基本信息，而 email、phoneNumber 则属于用户的联系方式。
-
-需求角度看，基本信息是那种一旦确定一般就不变的内容，而联系方式则会根据实际情况调整，如绑定各种社交账号。把这些信息都放到一个类里面，类稳定程度就差点。
-
-据此，可将 User 类的字段分组：
-```java
-public class User {
-  private long userId;
-  private String name;
-  private String nickname;
-  private Contact contact;
-  ...
-}
-
-public class Contact {
-  private String email;
-  private String phoneNumber;
-  ...
-}
-```
-引入一个 Contact 类（联系方式），把 email 和 phoneNumber 放了进去，后面再有任何关于联系方式的调整就都可以放在这个类里面。
-此次调整，把不同信息重新组合，但每个类都比原来要小。
-
-前后两次拆分到底有何不同？
-- 前面是根据职责，拆分出不同实体
-- 后面是将字段做了分组，用类把不同的信息分别封装
-
-大类拆解成小类，本质上是个设计工作，依据单一职责设计原则。
-
-若把大类都拆成小类，类的数量就会增多，那人们理解的成本是不是也会增加呢？
-**这也是很多人不拆分大类的借口。**
-
-各种程序设计语言中，本就有如包、命名空间等机制，将各种类组合在一起。在你不需要展开细节时，面对的是一个类的集合。
-再进一步，还有各种程序库把这些打包出来的东西再进一步打包，让我们只要面对简单的接口，而不必关心各种细节。
-
-软件正这样层层封装构建出来的。
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\344\275\240\347\232\204\345\233\242\351\230\237Code Review\344\270\200\345\244\251\345\207\240\346\254\241\357\274\237.md" "b/\351\207\215\346\236\204/\344\275\240\347\232\204\345\233\242\351\230\237Code Review\344\270\200\345\244\251\345\207\240\346\254\241\357\274\237.md"
deleted file mode 100644
index e69de29bb2..0000000000
diff --git "a/\351\207\215\346\236\204/\344\275\240\347\234\237\347\232\204\344\274\232\345\243\260\346\230\216\345\217\230\351\207\217\345\220\227\357\274\237.md" "b/\351\207\215\346\236\204/\344\275\240\347\234\237\347\232\204\344\274\232\345\243\260\346\230\216\345\217\230\351\207\217\345\220\227\357\274\237.md"
deleted file mode 100644
index 7ea637469d..0000000000
--- "a/\351\207\215\346\236\204/\344\275\240\347\234\237\347\232\204\344\274\232\345\243\260\346\230\216\345\217\230\351\207\217\345\220\227\357\274\237.md"
+++ /dev/null
@@ -1,180 +0,0 @@
-一个变量声明怎么还有坏味道？难道变量声明都不让用？
-
-变量声明是写程序不可或缺的一部分，我并不打算让你戒掉变量声明，严格地说，我们是要把变量初始化这件事做好。
-
-# 变量的初始化
-```java
-EpubStatus status = null;
-CreateEpubResponse response = createEpub(request);
-if (response.getCode() == 201) {
-  status = EpubStatus.CREATED;
-} else {
-  status = EpubStatus.TO_CREATE;
-}
-```
-向另外一个服务发请求创建 EPUB，若创建：
-- 成功
-返回 HTTP 201，表示创建成功，然后就把状态置为 **CREATED**
-- 失败
-则把状态置为 **TO_CREATE**。后面对 **TO_CREATE** 作品，还要再尝试创建
-
-> 暂且是否要写 else 放下，毕竟这也是一个坏味道。
-
-重点在 status 变量，虽然 status 在声明时，就赋个null，但实际上，这值没任何用，因为status值，其实是在经过后续处理之后，才有了真正值。
-即语义上，第一行的变量初始化没用，是假的初始化。
-
-一个变量的初始化分为：
-- 声明
-- 赋值
-
-两个部分。变量初始化最好一次性完成。这段代码里的变量赋值是在声明很久后才完成，即变量初始化没有一次性完成。
-
-这种代码真正的问题就是**不清晰，变量初始化与业务处理混在在一起**。
-通常这种代码后面紧接着就是一大堆复杂业务。当代码混在一起，必须从一堆业务逻辑里抽丝剥茧，才能理顺逻辑，知道变量到底怎么初始化。
-
-这种代码在实际的代码库中出现的频率非常高，各种变形形态。
-有的变量在很远地方才做真正赋值，完成初始化，进一步增加了理解复杂度。
-
-所以请变量一次性完成初始化。
-
-重构代码：
-```java
-final CreateEpubResponse response = createEpub(request);
-final EpubStatus status = toEpubStatus(response);
-
-
-private EpubStatus toEpubStatus(final CreateEpubResponse response) {
-  if (response.getCode() == 201) {
-    return EpubStatus.CREATED;
-  }
-  return EpubStatus.TO_CREATE;
-}
-```
-提取出了一个函数，将 response 转成对应的内部的 EPUB状态。
-
-很多人之所以这样写代码，一个重要的原因是很多人的编程习惯是从 C 语言来的。C 语言在早期的版本中，一个函数用到的变量必须在整个函数的一开始就声明出来。
-在 C 语言诞生的年代，当时计算机能力有限内存小，编译器技术也处于刚刚起步的阶段，把变量放在前面声明出来，有助于减小编译器编写的难度。到了 C++年代，这个限制就逐步放开了，所以，C++程序是支持变量随用随声明的。对于今天的大多数程序设计语言来说，这个限制早就不存在了，但很多人的编程习惯却留在了那个古老的年代。
-
-在新的变量声明中，我加上了 final，在 Java 的语义中，一个变量加上了 final，也就意味着这个变量不能再次赋值。对，我们需要的正是这样的限制。
-
-**尽可能编写不变的代码**。这里其实是这个话题的延伸，尽可能使用不变的量。
-
-如果我们能够按照使用场景做一个区分，把变量初始化与业务处理分开，你会发现，在很多情况下，变量只在初始化完成之后赋值，就足以满足我们的需求了，在一段代码中，要使用可变量的场景不多。
-
-**推广一下，在能使用 final 的地方尽量使用 final，限制变量的赋值！**
-“能使用”，不仅包括普通的变量声明，还包含参数声明，还有类字段的声明，甚至类和方法的声明。
-尝试着调整自己现有代码，给变量声明都加上 final，你会发现许多值得重构的代码。
-
-还有异常处理的场景，强迫你把变量的声明与初始化分开，就像下面这段代码：
-```java
-InputStream is = null;
-
-
-try {
-  is = new FileInputStream(...);
-  ...
-} catch (IOException e) {
-  ...
-} finally {
-  if (is != null) {
-    is.close(); 
-  }
-}
-```
-之所以要把 InputStream 变量 is 单独声明，是为了能够在 finanlly 块里面访问到。
-写成这样的原因是 Java 早期版本只能这样，而如果采用 Java 7 后版本，采用 `try-with-resource` 的写法，代码就可以更简洁了：
-```java
-try (InputStream is = new FileInputStream(...)) {
-  ...
-}
-```
-这样一来，InputStream 变量的初始化就一次性完成了，符合该原则！
-
-# 集合初始化
-```java
-List<Permission> permissions = new ArrayList<>();
-permissions.add(Permission.BOOK_READ);
-permissions.add(Permission.BOOK_WRITE);
-check.grantTo(Role.AUTHOR, permissions);
-```
-给作者赋予作品读写权限。
-注意 permissions 集合，先给 permission 初始化成了一个 ArrayList，这时，permissions 虽然存在，但不会把它传给 grantTo，还不能直接使用，因为它还缺少必要信息。
-然后，将 BOOK_READ 和 BOOK_WRITE 两个枚举对象添加，这时的 permissions 对象才是需要的对象。
-
-声明一个集合，然后，调用一堆添加的方法，将所需的对象添加进去，是不是就是你写代码的习性？
-
-其实 permissions 对象一开始的变量声明，并没有完成这个集合真正的初始化，只有当集合所需的对象添加完毕后，才是它应有的样子。
-即只有添加了元素的集合才是需要的。
-
-是不是就发现和前面说的变量先声明后赋值，其实一回事，从一个变量的声明到初始化成一个可用状态，中间隔了太远。
-
-之所以很多人习惯这么写，一个原因就是在早期的 Java 版本中，没有提供很好的集合初始化方法。这种代码，也是很多动态语言的支持者调侃 Java 啰嗦的靶子。
-
-现如今，Java在这方面早已经改进了许多，各种程序库已经提供了一步到位的写法，我们先来看看 Java 9 之后的写法：
-```java
-List<Permission> permissions = List.of(
-  Permission.BOOK_READ, 
-  Permission.BOOK_WRITE
-);
-check.grantTo(Role.AUTHOR, permissions);
-```
-如果你的项目还没有升级 Java 9 之后的版本，使用 Guava也可以：
-
-```java
-List<Permission> permissions = ImmutableList.of(
-  Permission.BOOK_READ, 
-  Permission.BOOK_WRITE
-);
-check.grantTo(Role.AUTHOR, permissions);
-```
-代码是不是清爽多了！
-
-用的一个 ImmutableList，即不可变 List，实际上，你查看第一段代码的实现就会发现，它也是一个不变 List。这是什么意思呢？
-这个 List 一旦创建好了，就不能修改了，对应的实现就是各种添加、删除之类的方法全部都禁用了。
-
-初看起来，这是限制了我们的能力，但我们对比一下代码就不难发现，很多时候，我们对于一个集合的使用，除了声明时添加元素之外，后续就只是把它当作一个只读集合。
-所以很多情况下，一个不变集合够用。
-
-再复杂一些的，集合的声明和添加元素之间隔了很远，不注意的话，甚至不觉得它们是在完成一次初始化。
-
-```java
-private static Map<Locale, String> CODE_MAPPING = new HashMap<>();
-...
-
-
-static {
-  CODE_MAPPING.put(LOCALE.ENGLISH, "EN");
-  CODE_MAPPING.put(LOCALE.CHINESE, "CH");
-}
-```
-传输时的映射方案，将不同的语言版本映射为不同的代码。这里 **CODE_MAPPING** 是一个类的 static 变量，而这个类的声明里还有其它一些变量。所以，隔了很远之后，才有一个 static 块向这个集合添加元素。
-
-如果我们能够用一次性声明的方式，这个单独的 static 块就是不需要的：
-
-```java
-private static Map<Locale, String> CODE_MAPPING = ImmutableMap.of(
-  LOCALE.ENGLISH, "EN",
-  LOCALE.CHINESE, "CH"
-);
-```
-
-对比我们改造前后的代码，二者之间还有一个更关键的区别：前面的代码是命令式代码，而后面的代码是声明式。
-
-命令式的代码，就是告诉你“怎么做”的代码，就像改造前的代码，声明一个集合，然后添加一个元素，再添加一个元素。而声明式的代码，是告诉你“做什么”的代码，改造后就是，我要一个包含了这两个元素的集合。
-
-声明式的代码体现的意图，是更高层面的抽象，把意图和实现分开，从某种意义上来说，也是一种分离关注点。
-
-所以，用声明式的标准来看代码，是一个发现代码坏味道的重要参考。
-
-无论是变量的声明与赋值分离，还是初始化一个集合的分步骤，其实反映的都是不同时代编程风格的烙印。变量的声明是 C 早期的编程风格，异常处理是 Java 早期的风格，而集合声明也体现出不同版本 Java 的影子。
-
-编程不仅仅是要学习实现功能，风格也要与时俱进。
-
-# 总结
-变量的初始化包含变量的声明和赋值两个部分，一个编程的原则是“变量要一次性完成初始化”。
-
-这就有个坏味道：变量的声明和赋值是分离的。二者分离带来的问题就是，把赋值的过程与业务处理混杂在一起。发现变量声明与赋值分离一个做法就是在声明前面加上 final，用“不变性”约束代码。
-
-传统的集合初始化方式是命令式的，而今天我们完全可以用声明式的方式进行集合的初始化，让初始化的过程一次性完成。再进一步，以声明式的标准来看代码，会帮助我们发现许多的坏味道。
-
-**请一次性完成变量的初始化！**
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\344\275\240\347\234\237\347\232\204\344\274\232\347\273\231\345\217\230\351\207\217\345\221\275\345\220\215\345\220\227\357\274\237.md" "b/\351\207\215\346\236\204/\344\275\240\347\234\237\347\232\204\344\274\232\347\273\231\345\217\230\351\207\217\345\221\275\345\220\215\345\220\227\357\274\237.md"
deleted file mode 100644
index 0aaa2ef0d3..0000000000
--- "a/\351\207\215\346\236\204/\344\275\240\347\234\237\347\232\204\344\274\232\347\273\231\345\217\230\351\207\217\345\221\275\345\220\215\345\220\227\357\274\237.md"
+++ /dev/null
@@ -1,246 +0,0 @@
-有读者看到标题就开始敲键盘了，我知道，命名不就是不能用 abc、123 命名，名字要有意义嘛，这有什么好讲的？
-然而，即便懂得了名字要有意义，很多程序员依然无法逃离命名沼泽。
-
-# 不精准的命名
-什么叫精准？
-废话不多说，CR 一段代码：
-```java
-public void processChapter(long chapterId) {
-  Chapter chapter = this.repository.findByChapterId(chapterId);
-  if (chapter == null) {
-    throw new IllegalArgumentException("Unknown chapter [" + chapterId + "]");
-t    
-  }
-  
-  chapter.setTranslationState(TranslationState.TRANSLATING);
-  this.repository.save(chapter);
-}
-```
-看上去挺正常。
-但我问你，这段代码在干嘛？你就需要调动全部注意力，去认真阅读这段代码，找出其中逻辑。经过阅读发现，这段代码做的就是把一个章节的翻译状态改成翻译中。
-
-> 为什么你需要阅读这段代码细节，才知道这段代码在干嘛？
-
-问题就在函数名，processChapter，这个函数确实是在处理章节，但这个名字太宽泛。如果说“将章节的翻译状态改成翻译中”叫做处理章节，那么：
-- “将章节的翻译状态改成翻译完”
-- “修改章节内容”
-
-是不是也能叫处理章节？
-所以，如果各种场景都能叫处理章节，那么处理章节就是个宽泛名，没有错，但**不精准**！
-
-表面看，这个名字是有含义，但实际上，并不能有效反映这段代码含义。
-如果我在做的是一个信息处理系统，你根本无法判断，是一个电商平台，还是一个图书管理系统，从沟通的角度看，这就不是一个有效的沟通。要想理解它，你需要消耗大量认知成本，无论是时间，还是精力。
-
-命名过于宽泛，不能精准描述，这是很多代码在命名上存在的严重问题，也是代码难以理解的根源所在。
-
-或许这么说你的印象还是不深刻，看看下面这些词是不是经常出现在你的代码里：data、info、flag、process、handle、build、maintain、manage、modify 等等。这些名字都属于典型的过宽泛名字，当这些名字出现在你的代码里，多半是写代码的人当时没有想好用什么名字，就开始写代码了。
-
-回到前面那段代码上，如果它不叫“处理章节”，那应该叫什么？
-- 命名要能够描述出这段代码在做的事情
-这段代码在做的事情就是“将章节修改为翻译中”。那是不是它就应该叫 changeChapterToTranlsating呢？
-相比于“处理章节”，changeChapterToTranlsating这个名字已经进了一步，然而，它也不算是一个好名字，因为它更多的是在描述这段代码在做的细节。
-之所以要将一段代码封装起来，是我们不想知道那么多细节。如果把细节平铺开来，那本质上和直接阅读代码细节差别不大。
-- 一个好的名字应该描述意图，而非细节
-就这段代码而言， 我们为什么要把翻译状态修改成翻译中，这一定是有**意图**。我们把翻译状态修改成翻译中，是因为我们在这里开启了一个翻译的过程。所以，这段函数应该命名 startTranslation。
-![](https://img-blog.csdnimg.cn/8aedb97fb18c45ec9d42a967830b52f5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-# 用技术术语命名
-![](https://img-blog.csdnimg.cn/fb4c6e2047cf4a8680fd0738b12b53cf.png)
-
-常见得不能再常见的代码，但却隐藏另外一个典型得不能再典型的问题：用技术术语命名。
-
-这个 bookList 变量之所以叫 bookList，原因就是它声明的类型是 List。这种命名在代码中几乎是随处可见的，比如 xxxMap、xxxSet。
-
-这是一种不费脑子的命名方式，但这种命名却会带来很多问题，因为它是一种基于实现细节的命名方式。
-
-面向接口编程，从另外一个角度理解，就是不要面向实现编程，因为接口是稳定的，而实现易变。虽然在大多数人的理解里，这个原则是针对类型的，但在命名上，我们也应该遵循同样的原则。为什么？我举个例子你就知道了。
-
-比如，如果我发现，我现在需要的是一个不重复的作品集合，也就是说，我需要把这个变量的类型从 List 改成 Set。变量类型你一定会改，但变量名你会改吗？这还真不一定，一旦出现遗忘，就会出现一个奇特的现象，一个叫 bookList 的变量，它的类型是一个 Set。这样，一个新的混淆产生了。
-
-有什么更好的名字吗？我们需要一个更面向意图的名字。其实，我们在这段代码里真正要表达的是拿到了一堆书，所以，这个名字可以命名成 books。
-List<Book> books = service.getBooks();
-这个名字其实更简单，但从表意的程度上来说，它却是一个更有效的名字。
-
-虽然这里我们只是以变量为例说明了以技术术语命名存在的问题，事实上，在实际的代码中，技术名词的出现，往往就代表着它缺少了一个应有的模型。
-
-比如，在业务代码里如果直接出现了 Redis：
-![](https://img-blog.csdnimg.cn/bbde8fc7605c4bd39e2e190ad41245d8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_17,color_FFFFFF,t_70,g_se,x_16)
-
-
-通常来说，这里真正需要的是一个缓存。Redis 是缓存这个模型的一个实现：
-![](https://img-blog.csdnimg.cn/046069b20b594824a996afccdc2f15cd.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_14,color_FFFFFF,t_70,g_se,x_16)
-再进一步，缓存这个概念其实也是一个技术术语，从某种意义上说，它也不应该出现在业务代码。
-这方面做得比较好的是 Spring。使用 Spring 框架时，如果需要缓存，我们通常是加上一个 注解：
-![](https://img-blog.csdnimg.cn/84151edee9094f27a8dc9c632235779b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_14,color_FFFFFF,t_70,g_se,x_16)
-
-之所以喜欢用技术名词去命名，一方面是因为，这是习惯的语言，另一方面也是因为学写代码，很大程度上是参考别人代码，而行业里面优秀的代码常常是一些开源项目，而这些开源项目往往是技术类项目。在一个技术类的项目中，这些技术术语其实就是它的业务语言。但对于业务项目，这个说法就必须重新审视了。
-
-如果这个部分的代码确实就是处理一些技术，使用技术术语无可厚非，但如果是在处理业务，就要尽可能把技术术语隔离开来。
-
-- xxxMap这种命名表示映射关系，比如：书id与书的映射关系，不能命名为bookIdMap么？
-Map 表示的是一个数据结构，而映射关系我会写成 Mapping
-
-# 用业务语言写代码
-无论是不精准的命名也好，技术名词也罢，归根结底，体现的是同一个问题：对业务理解不到位。
-
-编写可维护的代码要使用业务语言。怎么才知道自己的命名是否用的是业务语言呢？
-把这个词讲给产品经理，看他知不知道是怎么回事。
-
-从团队的角度看，让每个人根据自己的理解来命名，确实就有可能出现千奇百怪的名字，所以，一个良好的团队实践是，建立团队的词汇表，让团队成员有信息可以参考。
-
-团队对于业务有了共同理解，我们也许就可以发现一些更高级的坏味道，比如说下面这个函数声明：
-![](https://img-blog.csdnimg.cn/8155bcdf165d46679796a990f373b25b.png)
-
-确认章节内容审核通过。这里有一个问题，chapterId 是审核章节的 ID，这个没问题，但 userId 是什么呢？了解了一下背景，我们才知道，之所以这里要有一个 userId，是因为这里需要记录一下审核人的信息，这个 userId 就是审核人的 userId。
-
-你看，通过业务的分析，我们会发现，这个 userId 并不是一个好的命名，因为它还需要更多的解释，更好的命名是 reviewerUserId，之所以起这个名字，因为这个用户在这个场景下扮演的角色是审核人（Reviewer）。
-![](https://img-blog.csdnimg.cn/c0339ff4ad3d4e01adf2fcb049af1c46.png)
-
-这个坏味道也是一种不精准的命名，但它不是那种一眼可见的坏味道，而是需要在业务层面上再进行讨论，所以，它是一种更高级的坏味道。
-
-能够意识到自己的命名有问题，是程序员进阶的第一步。
-
-
-```java
-@GetMapping("getTotalSettlementInfoByYear")
-@ApiOperation("公司结算信息按年求和")
-public Result<List<RepMonthCompanyDTO>> getTotalSettlementInfoByYear(@RequestParam String year) {
-List<RepMonthCompanyDTO> list = repMonthCompanyService.getTotalSettlementInfoByYear(year);
-return new Result<List<RepMonthCompanyDTO>>().ok(list);
-}
-```
-名字长不是问题，问题是表达是否清晰，像repMonthCompanyService这个名字，是不太容易一眼看出来含义的。
-
-另外，传给 service 的参数是一个字符串，这个从逻辑上是有问题的，没有进行参数的校验。后面的内容也会讲到，这个做法是一种缺乏封装的表现。
-
-变量名是 list，按照这一讲的说法是用技术术语在命名。
-
-再有，这个 URI 是 getTotalSettlementInfoByYear，这是不符合 REST 的命名规范的，比如，动词不应该出现在 URI 里，分词应该是“-”，byYear 实际上是一个过滤条件等等。
-
-> 不管是日本人设计的 Ruby还是巴西人设计的 Lua，各种语法采用的全都是英语。所以，想要成为一个优秀的程序员，会用英语写代码是必要的。
-> 你肯定听说过，国内有一些程序员用汉语拼音写代码，这就是一种典型坏味道。而且程序设计语言已支持
-> UTF-8，用汉语拼音写代码，还不如用汉字直接写代码。这场景太低级了，不过多讨论。
-
-# 违反语法规则的命名
-CR一段代码：
-```java
-public void completedTranslate(final List<ChapterId> chapterIds) {
-  List<Chapter> chapters = repository.findByChapterIdIn(chapterIds);
-  chapters.forEach(Chapter::completedTranslate);
-  repository.saveAll(chapters); 
-}
-```
-乍看写得还不错，将一些章节信息标记为翻译完成。似乎方法名也能表达这意思，但经不起推敲。
-completedTranslate 并不是一个正常的英语方法名。从这个名字你能看出，作者想表达的是“完成翻译”，因为已经翻译完了，所以用完成时的 completed，而翻译是 translate。这个函数名就成了 completedTranslate。
-
-一般命名规则是：
-- 类名是个名词
-表示一个对象
-- 方法名是个动词或动宾短语
-表示一个动作
-
-以此为标准判断，completedTranslate 并不是一个有效的动宾结构。如果把这个名字改成动宾结构，只要把“完成”译为 complete，“翻译”用成它的名词形式 translation 就可以了。所以，这个函数名可以改成 completeTranslation：
-
-```java
-public void completeTranslation(final List<ChapterId> chapterIds) {
-	...
-}
-```
-这并不是个复杂的坏味道，但却随处可见。
-比如，一个函数名是 retranslation，其表达的意图是重新翻译，但作为函数名，它应该是一个动词，所以，正确的命名应该是 retranslate。
-
-只要你懂得最基本的命名要求，知道最基本的英语规则，就完全能够发现这类坏味道。
-
-# 不准确的英语词汇
-有一次，我们要实现一个章节审核的功能，一个同事先定义出了审核的状态：
-
-```java
-public enum ChapterAuditStatus {
-    PENDING,
-    APPROVED,
-    REJECTED;
-}
-```
-有问题吗？看不出来，一点都不奇怪。如果你用审核作为关键字去字典网站上搜索，确实会得到 audit 这个词。所以，审核状态写成 AuditStatus 太正常了。
-
-然而，看到这个词的时候，我的第一反应就是这个词好像不太对。因为之前我实现了一个作品审核的功能，不过我写的定义是这样的：
-```java
-public enum BookReviewStatus {
-    PENDING,
-    APPROVED,
-    REJECTED;
-}
-```
-抛开前缀不看，同样是审核，一个用 audit，一个用 review。本着代码一致性，希望这两个定义采用同样词汇。
-
-搜索引擎里查下。原来，audit 有更官方的味道，更合适的翻译应该是审计，而 review 则有更多核查的意思，二者相比，review 更适合这里的场景。于是，章节的审核状态也统一使用了 review：
-```java
-public enum ChapterReviewStatus {
-    PENDING,
-    APPROVED,
-    REJECTED;
-}
-```
-这个坏味道就是个高级的坏味道，英语单词用得不准确。
-但这个问题确实是国内程序员不得不面对的一个尴尬的问题，英语没那么好，体会不到不同单词之间差异。
-
-很多人就是把中文扔到 Google 翻译，然后从诸多返回的结果中找一个自己看着顺眼的，而这也往往是很多问题出现的根源。这样写出来的程序看起来就像一个不熟练的外国人在说中文，虽然你知道他在说的意思，但总觉得哪里怪怪的。
-
-最好的解决方案还是建立业务词汇表。一般情况下，我们都可以去和业务方谈，共同确定一个词汇表，包含业务术语的中英文表达。这样在写代码的时候，你就可以参考这个词汇表给变量和函数命名。
-
-下面是一个词汇表的示例，从这个词汇表中你不难看出：
-- 词汇表给出的都是业务术语，同时也给出了在特定业务场景下的含义
-- 它也给出了相应的英文，省得你费劲心思去思考
-
-遇到了一个词汇表中没有的术语，就找出这个术语相应的解释，然后补充到术语表。
-![](https://img-blog.csdnimg.cn/f6eb5f883787430188db4024e9bad3f4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-用集体智慧，而非个体智慧。你一个人的英语可能没那么好，但一群人总会找出一个合适的说法。业务词汇表也是构建通用语言的一部分成果。
-
-# 英语单词的拼写错误
-我再给你看一段曾经让我迷惑不已的代码：
-
-```java
-public class QuerySort {
-    private final SortBy sortBy;
-    private final SortFiled sortFiled;
-    ...
-}
-```
-初看这段代码时，我还想表扬代码的作者，他知道把查询的排序做一个封装，比起那些把字符串传来传去的做法要好很多。
-
-但仔细看，sortFiled 是啥？排序文件吗？为啥用的还是过去式？归档？
-找出这段代码的作者，向他求教，果然他把单词拼错了。
-
-偶尔的拼写错误不可避免，国内的拼写错误比例是偏高的。
-
-像 IntelliJ IDEA 这样的 IDE 甚至可以给你提示代码里有拼写错误（typo），只要稍微注意一下，就可以修正很多这样低级错误。
-# 总结
-两个典型的命名坏味道：
-
-不精准的命名；
-用技术术语命名。
-
-命名是软件开发中两件难事之一（另一个难事是缓存失效），不好的命名本质上是增加我们的认知成本，同样也增加了后来人（包括我们自己）维护代码的成本。
-
-- 好的命名要体现出这段代码在做的事情，而无需展开代码了解其中的细节
-- 再进一步，好的命名要准确地体现意图，而不是实现细节
-- 更高的要求是，用业务语言写代码
-
-**好的命名，是体现业务含义的命名。**
-
-几个英语使用不当造成的坏味道：
-- 违反语法规则的命名
-- 不准确的英语词汇
-- 英语单词的拼写错误
-
-还有一些常见的与语言相关的坏味道：
-- 使用拼音进行命名
-- 使用不恰当的单词简写（比如，多个单词的首字母，或者写单词其中的一部分）
-
-如何从实践层面上更好地规避这些坏味道：
-- 制定代码规范，比如，类名要用名词，函数名要用动词或动宾短语
-- 要建立团队的词汇表
-- 要经常进行CR
-
-**编写符合英语语法规则的代码。**
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\344\275\240\347\234\237\347\232\204\346\200\235\350\200\203\350\277\207\350\207\252\345\267\261\345\206\231\347\232\204\344\273\243\347\240\201\344\270\272\345\225\245\350\277\231\344\271\210\345\236\203\345\234\276\345\220\227\357\274\237.md" "b/\351\207\215\346\236\204/\344\275\240\347\234\237\347\232\204\346\200\235\350\200\203\350\277\207\350\207\252\345\267\261\345\206\231\347\232\204\344\273\243\347\240\201\344\270\272\345\225\245\350\277\231\344\271\210\345\236\203\345\234\276\345\220\227\357\274\237.md"
deleted file mode 100644
index 933cee64a4..0000000000
--- "a/\351\207\215\346\236\204/\344\275\240\347\234\237\347\232\204\346\200\235\350\200\203\350\277\207\350\207\252\345\267\261\345\206\231\347\232\204\344\273\243\347\240\201\344\270\272\345\225\245\350\277\231\344\271\210\345\236\203\345\234\276\345\220\227\357\274\237.md"
+++ /dev/null
@@ -1,114 +0,0 @@
-# 1 前言
-不一致的代码会给团队造成理解压力，明明大家都是在一个团队做项目：
-- 做同种事情，却有不同种做法
-- 起到类似作用的事物，却有不同名字
-
-大部分程序员对一致性的理解都表现在较宏观方面，比如，数据库访问是叫 DAO还是 Mapper、Repository？
-在一个团队内，这应该有统一标准，但编码层面，要求往往就没那么细致。所以，我们经常看到代码出现各种不一致写法。
-# 2 命名不一
-看一段代码：
-![](https://img-blog.csdnimg.cn/c17dbb9786644ed689587f67270a6ace.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_10,color_FFFFFF,t_70,g_se,x_16)
-可以看到，目前的分发渠道，看起来没啥问题。但我就疑惑了：
-- WEBSITE 和 KINDLE_ONLY 分别表示什么？
-WEBSITE 表示作品只会在我们自己的网站发布，KINDLE_ONLY 表示这部作品只会在 Kindle 的电子书商店里上架
-- 二者是不是都表示只在单独一个渠道发布？
-是啊！
-- 既然二者都有只在一个平台上架发布的含义，为什么不都叫 XXX 或 XXX_ONLY？
-好像也是哦
-
-所以问题就是这里 WEBSITE 和 KINDLE_ONLY 两个名字不一致。
-
-表示类似含义的代码应该名字一致。比如，很多团队里把业务写到服务层，各种服务的命名也都叫 XXXService。
-一旦出现不一致名字，通常都表示不同含义。比如，对于那些非业务入口的业务组件，它们的名字就会不一样，会更符合其具体业务行为，像BookSender ，它表示将作品发送到翻译引擎。
-
-一般枚举值表示的含义应该都有一致业务含义，一旦出现不同，就需要确定不同点到底在哪里，这也就是疑惑的原因。
-
-显然，这段代码的作者给这两个枚举值命名时，只分别考虑了它应该起什么名字，却忽略了这个枚举值在整体所扮角色。
-
-修正代码统一形式：
-![](https://img-blog.csdnimg.cn/b430f4b47e3848b8a313dc57b8bc8ad8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_10,color_FFFFFF,t_70,g_se,x_16)
-
-# 方案不一致
-![](https://img-blog.csdnimg.cn/2716a0801e3f41baa45f03e448f4b208.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-当一个系统向另外一个系统发送请求时，需要带一个时间戳过去，这里就是把这个时间戳按照一定格式转成了字符串类型，主要就是传输用，便于另外的系统进行识别，也方便在开发过程中进行调试。
-
-这段代码本身的实现是没有问题的。它甚至考虑到了 SimpleDateFormat 这个类本身存在的多线程问题，所以，它每次去创建了一个新的 SimpleDateFormat 对象。
-
-那我为什么还说它是有问题的呢？因为这种写法是 Java 8 之前的写法，而我们用的 Java 版本是 Java 8 之后的。
-
-在很长的一段时间里，Java 的日期时间解决方案一直是一个备受争议的设计，它的问题很多，有的是概念容易让人混淆（比如：Date 和 Calendar 什么情况下该用哪个），有的是接口设计的不直观（比如：Date 的 setMonth 参数是从 0 到 11），有的是实现容易造成问题（比如：前面提到的 SimpleDateFormat 需要考虑多线程并发的问题，需要每次构建一个新的对象出来）。
-
-这种乱象存在了很长时间，有很多人都在尝试解决这个问题（比如 Joda Time）。从 Java 8开始，Java 官方的 SDK 借鉴了各种程序库，引入了全新的日期时间解决方案。这套解决方案与原有的解决方案是完全独立的，也就是说，使用这套全新的解决方案完全可以应对我们的所有工作。
-
-我们现在的这个项目是一个全新的项目，我们使用的版本是 Java 11，这就意味着我们完全可以使用这套从 Java 8 引入的日期时间解决方案。所以，我们在项目里的约定就是所有的日期时间类型就是使用这套新的解决方案。
-
-现在你可能已经知道我说的问题在哪里了，在这个项目里，我们的要求是使用新的日期时间解决方案，而这里的 SimpleDateFormat 和 Date 是旧解决方案的一部分。所以，虽然这段代码本身的实现是没有问题的，然而，放在项目整体中，这却是一个坏味道，因为它没有和其它的部分保持一致。
-
-后来使用了新的解决方案：
-![](https://img-blog.csdnimg.cn/28fd48f9cfd244e0878551d2abc59d51.png)
-
-之所以会这样，因为一个项目中，应对同一个问题出现了多个解决方案，如果没有统一约定，项目成员会根据自己写代码时的感觉随机选择方案，导致方案不一致。
-
-为什么一个项目中会出现多个解决方案？
-- 时间
-时间消逝，技术发展，人们会主动意识到原方案的问题，就会提出新方案，像这里 Java 日期时间的解决方案，就是 JDK 本身随时间演化造成的。有的项目时间比较长，也会出现类似问题。
-- 因为自己的原因引入
-比如，在代码中引入做同一件事情类似的程序库。比如判断字符串是否为空或空串，就有 Guava 和 Apache Commons Lang，都能做同样事情，所以，程序员也会根据自己的熟悉程度选择其中之一来用，造成代码不一致。
-
-这两个程序库是很多程序库的基础，经常因为引入了其它程序库，相应的依赖就出现在我们的代码中。所以，我们必须约定，哪种做法是我们在项目中的标准做法，以防出现各自为战的现象。比如，在我的团队中，我们就选择 Guava 作为基础库，因为相对来说，它的风格更现代，所以，团队就约定类似的操作都以 Guava 为准。
-
-# 代码不一致
-![](https://img-blog.csdnimg.cn/ff8d919c9cd243e894887303de9abe93.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-在翻译引擎中创建作品的代码：
-- 首先，根据要处理的作品 ID，获取其中已审核通过的作品
-- 然后，发送一个 HTTP 请求在翻译引擎中创建出这个作品
-
-有什么问题？
-这段代码的不一致，这些代码不是一个层次的代码！
-
-首先是获取审核通过的作品，这是一个业务动作，接下来的三行其实是在做一件事，也就是发送创建作品的请求，这三行代码：
-- 创建请求的参数
-- 根据参数创建请求
-- 最后把请求发送出去
-
-三行代码合起来完成了一个发送创建作品请求这么一件事，而这件事才是一个完整的业务动作。
-
-所以，这个函数里的代码并不在一个层次上，有的是业务动作，有的是业务动作的细节。理解到这，把这些业务细节的代码提取到一个函数：
-![](https://img-blog.csdnimg.cn/19a0ff7bf05346dca9de9d37e7492a73.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-结果上看，原来的函数（createBook）里都是业务动作，而提取出来的函数（createRemoteBook）则都是业务动作的细节，各自语句都在一个层次。
-
-分清代码处于不同层次，基本功还是分离关注点！
-
-一旦分解出不同关注点，还可进一步调整代码的结构。
-像前面拆分出来的这个方法，我们已经知道它的作用是发出一个请求去创建作品，本质上并不属于这个业务类的一部分。
-所以，还可通过引入一个新模型，将这个部分调整出去：
-![](https://img-blog.csdnimg.cn/fda93405b5154966ab8a6ba40223bfc8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-一说到分层，大多数人想到的只是模型的分层，很少有人会想到在函数的语句中也要分层。各种层次的代码混在一起，许多问题也就随之而来了，最典型莫过长函数。
-
-我们在做的依然是模型分层，只不过，这次的出发点是函数的语句。“分离关注点，越小越好”的意义所在。观察代码的粒度足够小，很多问题自然就会暴露出来。
-
-程序员开始写测试时，有一个典型的问题：如何测试一个私有方法。有人建议用一些特殊能力（比如反射）去测试。我给这个问题的答案是，不要测私有方法。
-之所以想测试私有方法，就是分离关注点没有做好，把不同层次的代码混在一起。前面这段代码，如果要测试前面那个 createRemoteBook 方法还是有一定难度的，但调整之后，引入了 TranslationEngine 这个类，这个方法就变成了一个公开方法，就可以按照一个公开方法去测试了，所有问题迎刃而解。
-
-**很多程序员纠结的技术问题，其实是一个软件设计问题，不要通过奇技淫巧去解决一个本来不应该被解决的问题。**
-# 总结
-对于一个团队来说，一致是非常重要的，是降低集体认知成本的重要方式。我们分别见识了：
-- 命名中的不一致
-- 方案中的不一致
-- 代码中的不一致。
-
-类似含义的代码应该有类似的命名，不一致的命名表示不同含义，需要给出一个有效解释。
-
-方案中的不一致：
-- 由于代码长期演化造成的
-- 项目中存在完成同样功能的程序库
-
-无论是哪种原因，都需要团队先统一约定，保证所有人按照同一种方式编写代码。
-
-代码中的不一致常常是把不同层次的代码写在了一起，最典型的就是把业务层面的代码和实现细节的代码混在一起。解决这种问题的方式，就是通过提取方法，把不同层次的代码放到不同的函数里，而这一切的前提还是是分离关注点，这个代码问题的背后还是设计问题。
-
-**务必保持代码在各个层面上的一致性。**
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\345\217\257\345\217\230\347\232\204\346\225\260\346\215\256\346\234\211\345\244\232\345\217\257\346\200\225\357\274\214\344\275\240\344\272\206\350\247\243\345\220\227\357\274\237.md" "b/\351\207\215\346\236\204/\345\217\257\345\217\230\347\232\204\346\225\260\346\215\256\346\234\211\345\244\232\345\217\257\346\200\225\357\274\214\344\275\240\344\272\206\350\247\243\345\220\227\357\274\237.md"
deleted file mode 100644
index cadf3d7c1c..0000000000
--- "a/\351\207\215\346\236\204/\345\217\257\345\217\230\347\232\204\346\225\260\346\215\256\346\234\211\345\244\232\345\217\257\346\200\225\357\274\214\344\275\240\344\272\206\350\247\243\345\220\227\357\274\237.md"
+++ /dev/null
@@ -1,142 +0,0 @@
-程序=数据结构+算法，所以，数据几乎是软件开发最核心的一个组成部分。在一些人的认知中，所谓做软件，就是一系列的 CRUD 操作，也就是对数据进行增删改查。再具体一点，写代码就把各种数据拿来，然后改来改去。我们学习编程时，首先学会的，也是给变量赋值，类似 a = b + 1。
-
-改数据，几乎已经成了很多程序员写代码的标准做法。然而，这种做法也带来了很多的问题。
-
-# 满天飞的 Setter
-```java
-public void approve(final long bookId) {
-  ...
-  book.setReviewStatus(ReviewStatus.APPROVED);
-  ...
-}
-```
-对作品进行审核：通过 bookId，找到对应的作品，接下来，将审核状态设置成审核通过。
-
-因为这里用了 setter。setter 往往是缺乏封装的一种做法。
-很多人在写代码时，写完字段就会利用 Lombok 生成 getter、setter。setter 同 getter 一样，反映的都是对细节的暴露。
-
-这就意味着，你不仅可以读到一个对象的数据，还可以修改一个对象的数据。相比于读数据，修改是一个更危险操作。
-
-你不知道数据会在哪里被何人以什么方式修改，造成的结果是，别人的修改会让你的代码崩溃。与之相伴的还有各种并发问题。
-
-可变的数据可怕，比可变的数据更可怕的是不可控的变化，暴露 setter 就是这种不可控的变化。
-把各种实现细节完全交给对这个类不了解的使用者去修改，没有人会知道他会怎么改，所以，这种修改完全不可控。
-
-缺乏封装再加上不可控变化，setter 几乎是排名第一的坏味道。
-
-用一个函数替代 setter，也就是把它用行为封装起来：
-```java
-public void approve(final long bookId) {
-  ...
-  book.approve();
-  ...
-}
-```
-通过在 Book 类里引入了一个 approve 函数，将审核状态封装。
-```java
-class Book {
-  public void approve() {
-    this.reviewStatus = ReviewStatus.APPROVED;
-  }
-}
-```
-作为这个类的使用者，你并不需要知道这个类到底是怎么实现的。
-这里的变化也变得可控。虽然审核状态这个字段还是会修改，但你所有的修改都要通过几个函数作为入口。有任何业务上的调整，都会发生在类内部，只要保证接口行为不变，就不会影响到其它代码。
-
-setter 破坏了封装，相信你对这点已经有了理解。
-有时你会说，我这 setter 只用在初始化过程，并不需要在使用的过程去调用，就像下面这样：
-```java
-Book book = new Book();
-book.setBookId(bookId);
-book.setTitle(title);
-book.setIntroduction(introduction);
-```
-这种只在初始化中使用的代码，压根没必要以 setter 形式存在，真正需要的是一个有参数的构造函数：
-```java
-Book book = new Book(bookId, title, introduction);
-```
-消除 setter ，有一种专门的重构手法，叫做移除设值函数（Remove Setting Method）。总而言之，setter 完全没有必要存在。
-
-Lombok 可以在编译的过程中生成相应代码，最大的优点是不碍眼。因为它的代码是在编译阶段生成的，所以，那些生成的代码在源码级别上是不存在的。下面就是一个例子：
-```java
-@Getter
-@Setter
-class Book {
-  private BookId bookId;
-  private String title;
-  private String introduction;
-}
-```
-@Getter 表示为这个类的字段生成 getter
-@Setter 表示生成 setter
-因为@Setter的存在，其它代码还可以调用这个类的 setter，存在的问题并不会改变。
-所以，一个更好的做法是禁用@Setter。lombok.config 配置禁用@Setter：
-```java
-lombok.setter.flagUsage = error
-lombok.data.flagUsage = error
-```
-这里除了@Setter，还禁用了@Data，这是 Lombok 中另外一个 Annotation，表示同时生成 getter 和 setter。既然我们禁用@Setter 是为了防止生成 setter，当然也要禁用@Data了。
-
-# 可变的数据
-反对 setter，一个重要原因是它暴露了数据。
-暴露数据造成的问题在于数据的修改，进而导致出现难以预料的 Bug。在上面的代码中，我们把 setter 封装成一个个的函数，实际上是把不可控的修改限制在一个有限范围内。
-
-进一步，如果数据压根不让修改，犯下各种低级错误的机会就进一步降低。没错，在这种思路下，可变数据（Mutable Data）就成了一种坏味道，这是 Martin Fowler 在新版《重构》里增加的坏味道，它反映着整个行业对于编程的新理解。
-
-这种想法源自函数式编程，数据建立在不改变的基础上，如果需要更新，就产生一份新的数据副本，而旧有的数据保持不变。
-随着函数式编程在软件开发领域中的地位不断提高，人们对于不变性的理解也越发深刻，不变性有效地解决了可变数据产生的各种问题。
-
-> 所以，Martin Fowler 在《重构》第二版里新增了可变数据作为一种坏味道，这其实反映了行业的理解也是在逐渐推进的。不过，Martin
-> Fowler 对于可变数据给出的解决方案，基本上是限制对于数据的更新，降低其风险，这与我们前面提到的对 setter 的封装如出一辙。
-
-解决可变数据，还有一个解决方案
-# 编写不变类
-函数式编程的不变性，其中的关键点就是设计不变类。String 类就是一个不变类，比如，如果我们把字符串中的一个字符替换成另一个字符，String 类给出的函数签名是这样的：
-```java
-String replace(char oldChar, char newChar);
-```
-其含义是，这里的替换并不是在原有字符串上进行修改，而是产生了一个新的字符串。
-
-那么，在实际工作中，我们怎么设计不变类呢？
-- 所有的字段只在构造函数中初始化
-- 所有的方法都是纯函数
-- 如果需要有改变，返回一个新的对象，而不是修改已有字段
-
-回过头来看我们之前改动的“用构造函数消除 setter”的代码，其实就是朝着这个方向在迈进。如果按照这个思路改造我们前面提到的 approve 函数，同样也可以：
-```java
-class Book {
-  public void approve() {
-    return new Book(..., ReviewStatus.APPROVED, ...);
-  }
-}
-```
-
-这里，我们创建出了一个“其它参数和原有 book 对象一模一样，只是审核状态变成了 APPROVED ”的对象。
-
-在 JDK 的演化中，我们可以看到一个很明显的趋势，新增的类越来越多地采用了不变类设计，比如，用来表示时间的类。
-原来 Date 类里面还有各种 setter，而新增的 LocalDateTime 则一旦初始化就不会再修改。要操作这个对象，则会产生一个新对象：
-```java
-LocalDateTime twoDaysLater = now.plusDays(2);
-```
-就目前的开发状态而言，想要完全消除可变数据很难做到，但可尽可能编写一些不变类。
-区分类的性质。最核心要识别的对象分成两种，实体和值对象。实体对象要限制数据变化，而值对象就要设计成不变类。
-
-函数式编程的本质，是对程序中的赋值进行了约束。基于这样的理解，连赋值本身其实都会被归入到坏味道的提示，这才是真正挑战很多人编程习惯的一点。
-
-越来越多的语言中开始引入值类型，也就是初始化之后便不再改变的值，比如，Java 的 Valhalla 项目。像 Rust，缺省都是值类型，而如果你需要一个可以赋值的变量，反而要去专门声明。
-
-Martin Fowler 在《重构》中还提到一个与数据相关的坏味道：全局数据（Global Data）。如果你能够理解可变数据是一种坏味道，全局数据也就很容易理解了，它们处理手法基本上是类似的。
-# 总结
-可变数据最直白的体现就是各种 setter：
-- 破坏了封装
-- 带来不可控的修改，给代码增添许多问题
-
-解决它的一种方式就是移除设值函数（Remove Setting Method），将变化限制在一定的范围之内。
-
-可变数据是《重构》第二版新增的坏味道，这其实反映了软件开发行业的一种进步，它背后的思想是函数式编程所体现的不变性。解决可变数据，一种方式是限制其变化，另一种方式是编写不变类。
-
-在实践中，完全消除可变数据是很有挑战的。所以，一个实际的做法是，区分类的性质。值对象就要设计成不变类，实体类则要限制数据变化。
-
-函数式编程的本质是对于赋值进行了约束，我们甚至可以把赋值作为一种坏味道的提示。很多编程语言都引入了值类型，而让变量成为次优选项。
-
-**限制可变的数据。**
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\345\246\202\344\275\225\344\274\230\351\233\205\345\244\204\347\220\206\344\273\243\347\240\201\344\276\235\350\265\226\351\227\256\351\242\230\357\274\237.md" "b/\351\207\215\346\236\204/\345\246\202\344\275\225\344\274\230\351\233\205\345\244\204\347\220\206\344\273\243\347\240\201\344\276\235\350\265\226\351\227\256\351\242\230\357\274\237.md"
deleted file mode 100644
index e3b09d3112..0000000000
--- "a/\351\207\215\346\236\204/\345\246\202\344\275\225\344\274\230\351\233\205\345\244\204\347\220\206\344\273\243\347\240\201\344\276\235\350\265\226\351\227\256\351\242\230\357\274\237.md"
+++ /dev/null
@@ -1,206 +0,0 @@
-“大类”这个坏味道，为了避免同时面对所有细节，我们需要把程序进行拆分，分解成一个个小模块。
-随之而来的问题就是，需要把这些拆分出来的模块按照一定规则重新组装，这就是依赖的源泉。
-
-一个模块要依赖另外一个模块完成完整的业务功能，而到底怎么去依赖，这里就很容易产生问题。
-
-# 缺少防腐层
-```java
-@PostMapping("/books")
-public NewBookResponse createBook(final NewBookRequest request) {
-  boolean result = this.service.createBook(request);
-  ...
-}
-```
-创建一部作品的入口，提供一个 REST 服务，只要对`/books` 发个 POST 请求，就可以创建一部作品。
-
-按一般代码分层逻辑，一个 Resource （有人称 Controller）调用一个 Service，再正常不过了，有啥问题？
-
-从 Resource 调用 Service，几乎是行业里的标准做法，没有问题，问题出在传递的参数。
-这个 NewBookRequest 参数类应该属于哪层？resource or service 层？
-
-既然它是个请求参数，通常承载诸如参数校验和对象转换的职责，通常理解应该属 resource 层。
-如果这个理解是正确的，问题就来了，它为什么会传递给 service 层？
-
-按通常的架构设计原则，service 层属核心业务，resource 层属接口。
-核心业务重要度更高，所以，其稳定程度也该更高。
-同样的业务，可用 REST 方式对外提供，也可用 RPC 方式。
-
-NewBookRequest 这个本该属于接口层的参数，现在成了核心业务的一部分，即便将来提供了 RPC 接口，它也要知道 REST 接口长什么样子，显然，这有问题。
-
-既然 NewBookRequest 属于resource 层有问题，那假设它属 service 层？
-一般请求都要承担对象校验和转化工作。若这个类属 service 层，但它用在 resource 接口上，作为 resource 的接口，它会承载一些校验和对象转换的角色，而 service 层参数是不需要关心这些的。
-如果 NewBookRequest 属 service 层，校验和对象转换谁负责？
-
-有时 service 层参数和 resource 层参数并非严格一一对应。
-比如，创建作品时，需要一个识别作者身份的用户 ID，而这个参数并不是通过客户端发起的请求参数带过来，而是根据用户登录信息进行识别的。
-所以，用 service 层的参数做 resource 层的参数，就存在差异的参数如何处理的问题。
-
-看来我们陷入了两难境地，如此一个简单的参数，放到哪层都有问题。
-何解呢？
-之所以这么纠结，在于缺少了一个模型。
-
-NewBookRequest 之所以弄得如此“里外不是人”，主要就是因为它只能扮演**一个层中的模型**，所以，我们只要**再引入一个模型**就可以破解这个问题。
-
-```java
-class NewBookParameter {
-  ...
-}
-
-class NewBookRequest {
-  public NewBookParameters toNewBookRequest() {
-    ...
-  }
-}
-
-@PostMapping("/books")
-public NewBookResponse createBook(final NewBookRequest request) {
-  boolean result = this.service.createBook(request.toNewBookParameter());
-  ...
-}
-```
-引入一个 NewBookParameter 类，把它当作 service 层创建作品的入口，而在 resource 中，将 NewBookRequest 这个请求类的对象转换成了 NewBookParameter 对象，然后传到 service 层。
-
-该结构中：
-- NewBookParameter 属 service 层
-- NewBookRequest 属 resource 层
-
-二者相互独立，之前纠结的问题也就不存在了。
-
-增加一个模型，就破解了依赖关系上的纠结。
-
-虽然它们成了两个类，但它俩长得一模一样吧，这难道不算一种重复吗？
-但问题是，它们两个为什么要一样呢？有了两层不同的参数，我们就可以给不同层次上的模型以不同约定了。
-
-比如，对 resource 层的请求对象，它的主要作用是传输，所以，一般约定请求对象的字段主要是基本类型。
-而 service 的参数对象，已经是核心业务一部分，就需要全部转化为业务对象。
-
-比如，同样表示价格，在请求对象中，可以是个 double 类型，而在业务参数对象中，应是 Price 类型。
-
-再来解决 resource 层参数和 service 层参数不一致情况，现在二者分开了，那我们就很清楚地知道，其实，就是在业务参数对象构造时，传入必需的参数即可。
-比如，若需要传入 userId，可以这么做：
-
-```java
-class NewBookRequest {
-  public NewBookParameters toNewBookRequest(long userId) {
-    ...
-  }
-}
-
-@PostMapping("/books")
-public NewBookResponse createBook(final NewBookRequest request, final Authentication authentication) {
-  long userId = getUserIdentity(authentication);
-  boolean result = this.service.createBook(request.toNewBookParameter(userId));
-  ...
-}
-```
-能注意到这个坏味道，就是从依赖关系入手发现的。
-当初注意到这段代码，因为我团队内部的约定是，所有请求对象都属 resource 层，但在这段代码里，service 层出现了 resource 层的对象，它背离了我们对依赖关系设计的约定。
-
-这也是一个典型的软件设计问题：缺少防腐层，通过防腐层将外部系统和核心业务隔离开来。
-
-而很多人初见这个例子，可能压根想不到它与防腐层的关系，那只不过是因为你对这种结构太熟悉了。
-其实，resource 层就是外部请求和核心业务之间的防腐层。只要理解了这一点，你就能理解这里要多构建出一个业务参数对象的意义。
-
-下面这段代码，想必你也能轻易发现问题：
-```java
-@Entity
-@Table(name = "user")
-@JsonIgnoreProperties(ignoreUnknown = true)
-class User {
-  ...
-}
-```
-这是个 User 类的声明，它有 **@Entity** 这个 Anntation，表示它是一个业务实体的对象，但它的上面还出现了**@JsonIgnoreProperties**。JSON 通常都是在传输中用到。业务实体和传输对象应该具备的特质在同一类出现。
-显然，这也是没有构建好防腐层，把两个职责混在一起。
-
-# 业务代码里的具体实现
-```java
-@Task
-public void sendBook() {
-  try {
-    this.service.sendBook();
-  } catch (Throwable t) {
-    this.feishuSender.send(new SendFailure(t)));
-    throw t;
-  }
-}
-```
-一旦发送过程出了问题，要通过即时通信工具发给相关人，以防系统出现问题无人发觉。只不过，这里给出的是它最初的样子，也就是通过飞书进行消息发送。
-
-因为需求是通过飞书发送，所以，这里就写了飞书发送。这看上去很合理啊。
-这是一种符合直觉的做法，却不符合设计原则，违反依赖倒置原则。
-
-> 高层模块不应依赖低层模块，二者应依赖于抽象。 
-> High-level modules should not depend on low-level modules. Both should depend on abstractions.
-> 
-> 抽象不应依赖于细节，细节应依赖于抽象。 
-> Abstractions should not depend on details. Details  (concrete implementations) should depend on abstractions.
-
-能注意到这段代码，是因为在一段业务处理中出现了一个具体实现，即 feishuSender。
-
-**业务代码中任何与业务无关的东西都是潜在的坏味道。**
-
-飞书肯定不是业务的一部分，它只是当前选择的一个具体实现。
-即是否选择飞书，与团队当前状态相关，如果哪一天团队切换IM软件，这个实现就得换掉。但团队不可能切换业务，一旦切换，那就是一个完全不同的系统。
-
-**识别一个东西是业务的一部分，还是一个可以替换的实现，不妨问问自己，如果不用它，是否还有其它的选择？**
-
-就像这里的飞书，可被其它IM软件替换。
-常见的中间件，比如Kafka、Redis、MongoDB等，也都是一个具体实现，其它中间件都可以将其替换。所以，它们在业务代码里出现，那一定是坏味道。
-
-既然这些具体的东西是一种坏味道，怎么解决？
-可以引入一个模型，即这个具体实现所要扮演的角色，通过它，将业务和具体的实现隔离。
-
-```java
-interface FailureSender {
-  void send(SendFailure failure);
-}
-
-
-class FeishuFailureSenderS implements FailureSender {
-  ...
-}
-```
-引入一个 FailureSender，业务层只依赖于这个 FailureSender 接口，具体的飞书实现通过依赖注入。
-
-很多程序员在写代码，由于开发习惯，常忽略掉依赖关系。
-
-有一些工具，可以保证我们在写代码的时候，不会出现严重破坏依赖关系的情况，比如，像前面那种 service 层调用 resource 层的代码。
-
-ArchUnit 来保证这一切。它是把这种架构层面的检查做成了单元测试，下面就是这样的一个单元测试：
-```java
-@Test
-public void should_follow_arch_rule() {
-  JavaClasses clazz = new ClassFileImporter().importPackages("...");
-  ArchRule rule = layeredArchitecture()
-        .layer("Resource").definedBy("..resource..")
-        .layer("Service").definedBy("..service..")
-        .whereLayer("Resource").mayNotBeAccessedByAnyLayer()
-        .whereLayer("Service").mayOnlyBeAccessedByLayers("Resource");
-        
-   rule.check(clazz);       
-}
-```
-定义了两个层，分别是 Resource 层和 Service 层。
-要求 Resource 层代码不能被其它层访问，而 Service 层代码只能由 Resource 层方法访问。
-这就是我们的架构规则，一旦代码里有违反这个架构规则的代码，这个测试就会失败，问题也就会暴露出来。
-# 总结
-由于代码依赖关系而产生的坏味道：
-- 缺少防腐层，导致不同代码糅合在一起，一种是在业务代码中出现了具体的实现类。
-缺少防腐层，会让请求对象传导到业务代码中，造成了业务与外部接口的耦合，也就是业务依赖了一个外部通信协议。一般来说，业务的稳定性要比外部接口高，这种反向的依赖就会让业务一直无法稳定下来，继而在日后带来更多的问题。
-解决方案：引入一个防腐层，将业务和接口隔离开来。
-
-业务代码中出现具体的实现类，实际上是违反了依赖倒置原则。因为违反了依赖倒置原则，业务代码也就不可避免地受到具体实现的影响，也就造成了业务代码的不稳定。识别一段代码是否属于业务，我们不妨问一下，看把它换成其它的东西，是否影响业务。解决这种坏味道就是引入一个模型，将业务与具体的实现隔离开来。
-
-**代码应该向着稳定的方向依赖。**
-
-
-# FAQ
-- 对照DDD，是否可以理解为接口层接收DTO，转换为DO后传入业务层？那么缺少防腐层的问题也可能发生在业务层和持久化层之间，比如业务层直接操作持久化对象(PO)？
-相比把DTO当成DO用，把PO当DO用似乎更常见。除了违反单一职责原则，实际使用似乎问题不大，因为很多系统都是从先从数据表开始设计的。
-严格说，持久化对象也应该与业务对象分开，但在实际项目中，一般持久化对象和业务对象合在一起，主要原因是，一般持久化都是一个系统在内部完成的，对于一个系统而言，是一种可控的变化。
-
-- 按ddd的分层。应用层不能接收dto，应当新建一个参数类（这样工厂类的实现不依赖dto倒是很好保证了）？那么这个参数类归属什么？感觉不属于实体也不属于值对象。
-dto转pojo感觉还是不好。虽然从职责分配的角度看，dto能满足pojo创建的诉求，所以归属到dto做pojo的工厂方法。但这样dto就依赖了pojo，而这个依赖是非强必要的，所以减少依赖可能更好，借助工具单独实现一个dto转pojo的转换器。
-
-参数类属于业务层，不用纠结它属于什么，它就是一个参数。具体的转换其实就是一个工厂方法，放到 Request 里面而不是单独放置，就不用多谢一个额外的类。
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\345\246\202\344\275\225\345\210\206\346\236\220\350\275\257\344\273\266\347\232\204\346\250\241\345\236\213.md" "b/\351\207\215\346\236\204/\345\246\202\344\275\225\345\210\206\346\236\220\350\275\257\344\273\266\347\232\204\346\250\241\345\236\213.md"
deleted file mode 100644
index 45b89af01b..0000000000
--- "a/\351\207\215\346\236\204/\345\246\202\344\275\225\345\210\206\346\236\220\350\275\257\344\273\266\347\232\204\346\250\241\345\236\213.md"
+++ /dev/null
@@ -1,115 +0,0 @@
-面对一个新项目，如何理解它的模型呢？
-
-要先知道项目提供了哪些模型，模型又提供了怎样的能力。若只知道这些，你只是在了解别人设计的结果，这不足以支撑你后期对模型的维护。
-
-在一个项目中，常常会出现新人随意向模型中添加内容，修改实现，让模型变得难以维护。原因在于**对模型的理解不够**。
-
-模型都是为解决问题，理解一个模型，需要了解在没有这个模型之前，问题是如何被解决的？
-这样，你才能知道新的模型究竟提供了怎样的提升，这是理解一个模型的关键。
-
-本文以Spring的IoC容器为例，来看看怎样理解软件的模型。
-
-# 耦合的依赖
-Spring的根基就是IoC容器，即“ 控制反转”，也叫依赖注入。
-
-> IoC容器是为了解决什么问题呢？
-
-组件创建和组装问题。
-
-> 为什么这是个亟待解决的问题？
-
-软件设计需要有个分解过程，必然还面对一个组装过程，即将分解出的各组件组装到一起完成功能。
-
-# 案例
-某博客服务提供：根据标题查询博客。
-![](https://img-blog.csdnimg.cn/b8de0b116dde4de7bed99396d3a95c5d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-ArticleService处理业务过程中，需用ArticleRepository辅助完成功能，即ArticleService依赖ArticleRepository。
-
-> 这时你会怎么做？
-
-直男做法，在 ArticleService新增一个ArticleRepository字段：
-![](https://img-blog.csdnimg.cn/787ab78bbeea4e2db62a91f41ba04428.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-看起来好像还行。
-**那这个字段怎么初始化？**
-
-直男反应：直接new！
-![](https://img-blog.csdnimg.cn/64b5600ce483444d96ae63fda603e977.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-看起好还能用，但实际上DBArticleRepository不能这样初始化。
-就如实现类的名字，这里要用到DB。但在真实的项目中，由于资源所限，一般不会在应用中任意打开DB连接，而是会共享DB连接。
-所以，DBArticleRepository需要一个DB连接（Connection）参数。
-
-于是你决定通过构造器把这个参数传入：
-![](https://img-blog.csdnimg.cn/c6ab873516aa4309ae16cd1785022d59.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)看上去也还正常。既然开发完了，那么开始测试吧：
-要让ArticleService跑起来，就得让ArticleRepository也跑起来；就得准备好DB连接。
-
-是不是太麻烦，想放弃测试了？。但你还是决定坚持一下，去准备DB连接信息。
-
-然后，真正开始写测试时，发现，要测试，还要在DB里准备数据：
-- 测查询，得事先插入一些数据吧，看查出来的结果和插入的数据是否一致
-- 测更新，得先插入数据，测试跑完，再看数据更新是否正确
-
-咬咬牙准备了一堆数据，你开始困惑了：我在干什么？我不是要测试服务吗？做数据准备不是测试仓库的时候该做的事吗？
-
-> 所以，你发现问题在哪了吗？
-
-在你创建对象的那刻，问题就出现了。
-
-# 分离的依赖
-当我们创建一个对象时，就必须要有个实现类，即DBArticleRepository。
-虽然ArticleService写得很干净，其他部分根本不依赖DBArticleRepository，只在构造器里依赖，但依赖就是依赖。
-
-而且由于要构造DBArticleRepository，还引入了Connection类，该类只与DBArticleRepository的构造有关系，与ArticleService业务逻辑毫无关系。
-
-只是因为引入一个具体实现，就需要把它周边全部东西引入，而这一切都与业务类本身的业务逻辑没一毛钱关系。
-这就像是，你原本打算买套房子，现在却让你必须了解怎么和水泥、砌墙、怎么装修、户型怎么设计、各个家具怎么组装，而你想要的只是一套能住的婚房。
-
-实际项目，构建一个对象可能牵扯更多内容：
-- 根据不同的参数，创建不同的实现类对象，你可能需要用到工厂模式
-- 为了解方法执行时间，需要给被依赖的对象加上监控
-- 依赖的对象来自于某个框架，你自己都不知道具体的实现类咋样的
-……
-
-即便是最简单的对象创建和组装，看起来也不是多简单。
-
-直接构造存在这么多问题，最简单的就是把创建的过程拿出去，只留下与字段关联的过程：
-![](https://img-blog.csdnimg.cn/54155a57ccaa4ac098111b28812f7d27.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-这时的ArticleService就只依赖ArticleRepository。
-测试ArticleService也很简单，只要用一个对象模拟ArticleRepository的行为。通常这种模拟对象行为的工作用一个现成的程序库就可以完成，就是那些Mock框架。
-
-之前的代码里，如果我用Mock框架模拟Connection类是不是也可以？
-理论上，是可以。但是想要让ArticleService的测试通过，就必须打开DBArticleRepository的实现，只有配合着其中的实现，才可能让ArticleService跑起来。显然，你跑偏了。
-
-对象的创建已经分离了出去，但还是要要有一个地方完成这个工作，最简单的解决方案自然是，把所有的对象创建和组装在一个地方完成：
-![](https://img-blog.csdnimg.cn/67df925a361646a189e6d0dace96fbc7.png)
-相比业务逻辑，组装过程很简单，仅仅是个对象创建及传参。
-最好的解决方案就是有个框架。Java的这种组装一堆对象的东西一般被称为“容器”。
-![](https://img-blog.csdnimg.cn/001940d76418478f9240c6aa9870d89b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-至此，一个容器就此诞生。它解决的是依赖问题，把被依赖的对象注入到目标对象，所以叫“依赖注入”（Dependency Injection，简称 DI）。这个容器就叫DI容器。
-
-这种创建和组装对象的方式在当年引发了很大的讨论，直到最后Martin Fowler写了一篇《反转控制容器和依赖注入模式》，才算把大家的讨论做了一个总结，行业里总算达成共识。
-
-> 说了这么多，和我们要讨论的“模型”有什么关系？
-
-很多人习惯性把对象的创建和组装写到了一个类里，导致代码出现大量耦合。
-也导致项目很难测试，可测试性是衡量设计优劣的一个重要标准。
-
-有了IoC容器后,你的代码就只剩下关联的代码，对象的创建和组装都由IoC容器完成。不经意间，还做到了面向接口编程，实现是可以替换的，且可测试。
-容器概念还能继续增强。比如，我们想给所有与数据库相关的代码加上时间监控，只要在容器构造对象时添加处理即可。这就是 AOP，而这些改动，对业务代码透明。
-但还是很多程序员即便用Spring，依然是自己构造对象，静态方法随便写。
-
-# 总结
-理解模型，要知道项目提供了哪些模型，这些模型都提供了怎样的能力。
-更重要的是了解模型设计的渊源：
-- 可增进对它的了解
-- 也会减少我们对模型的破坏或滥用
-
-IoC容器有效地解决了对象的创建和组装的问题，让程序员们拥有了一个新的编程模型。
-
-按照这个编程模型去写代码，整体的质量会得到大幅度的提升，也会规避掉之前的许多问题。这也是一个好的模型对项目起到的促进作用。像DI这种设计得非常好的模型，你甚至不觉得自己在用一个特定的模型在编程。
-
-**理解模型，要了解模型设计的来龙去脉。**
-![](https://img-blog.csdnimg.cn/b8a20a4382c442088e76f24996971e2d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\345\246\202\344\275\225\345\272\224\345\257\271\347\201\253\350\275\246\344\273\243\347\240\201\345\222\214\345\237\272\346\234\254\347\261\273\345\236\213\345\201\217\346\211\247\351\227\256\351\242\230\357\274\237.md" "b/\351\207\215\346\236\204/\345\246\202\344\275\225\345\272\224\345\257\271\347\201\253\350\275\246\344\273\243\347\240\201\345\222\214\345\237\272\346\234\254\347\261\273\345\236\213\345\201\217\346\211\247\351\227\256\351\242\230\357\274\237.md"
deleted file mode 100644
index 1d965d3883..0000000000
--- "a/\351\207\215\346\236\204/\345\246\202\344\275\225\345\272\224\345\257\271\347\201\253\350\275\246\344\273\243\347\240\201\345\222\214\345\237\272\346\234\254\347\261\273\345\236\213\345\201\217\346\211\247\351\227\256\351\242\230\357\274\237.md"
+++ /dev/null
@@ -1,161 +0,0 @@
-这一讲，我们再来讲一类代码的坏味道：缺乏封装。
-
-封装，将碎片式代码封装成一个个可复用模块。但不同级别的程序员对封装的理解程度差异甚大，往往出现这么一种现象：写代码的人认为自己提供了封装，但实际上，我们还是看到许多的代码散落在那里。
-
-# 火车残骸
-```java
-String name = book.getAuthor().getName();
-```
-获得一部作品作者的名字。作品里有作者信息，想要获得作者的名字，通过“作者”找到“作者姓名”，这就是很多人凭借直觉写出的代码，不过这有问题！
-
-是不是感觉自己无法理解封装了？
-
-若你想写出上面这段，是不是得先了解Book、Author两个类的实现细节？
-即我们得知道，作者的姓名存储在作品的作者字段。
-这就是问题了：当你必须得先了解一个类的细节，才能写代码时，这只能说明一件事，这个封装不完美。
-
-翻翻你负责的项目，这种在一行代码中有连续多个方法调用的情况是不是随处可见？
-
-> Martin Fowler 在《重构》中给这种坏味道起的名字叫过长的消息链（Message
-> Chains），而有人则给它起了一个更为夸张的名字：火车残骸（Train Wreck），形容这样的代码像火车残骸一般，断得一节一节的。
-
-解决这种代码的重构方案叫隐藏委托关系（Hide Delegate），说人话，就是把这种调用封装起来：
-```java
-class Book {
-  ...
-  public String getAuthorName() {
-    return this.author.getName();
-  }
-  ...
-}
-
-String name = book.getAuthorName();
-```
-火车残骸这种坏味道的产生是缺乏对于封装的理解，因为封装这件事并不是很多程序员编码习惯的一部分，他们对封装的理解停留在数据结构加算法层面。
-
-在学习数据结构时，我们所编写的代码都是拿到各种细节直接操作，但那是在做编程练习，并不是工程上的编码方式。遗憾的是，很多人把这种编码习惯带到工作。
-
-比如说，有人编写一个新的类，第一步是写出这个类要用到的字段，然后，就是给这些字段生成各种 getXXX。很多语言或框架提供的约定就是基于这种 getter的，就像 Java 里的 JavaBean，所以相应的配套工具也很方便。这让暴露细节这种事越来越容易，封装反而成稀缺品。
-
-要想成为技术专家，先从少暴露细节开始。声明完一个类的字段之后，请停下生成 getter 的手，转而思考这个类应该提供的行为。
-
-迪米特法则（Law of Demeter）几乎就是针对这个坏味道的：
-- 每个单元对其它单元只拥有有限的知识，而且这些单元是与当前单元有紧密联系
-- 每个单元只能与其朋友交谈，不与陌生人交谈
-- 只与自己最直接的朋友交谈。
-
-这个原则需要我们思考，哪些算是直接的朋友，哪些算是陌生人。火车残骸代码显然就是没有考虑这些问题，而直接写出来的代码。
-
-或许你会说，按照迪米特法则这样写代码，会不会让代码里有太多简单封装的方法？
-有可能，不过，这也是单独解决这一个坏味道可能带来的结果。
-这种代码的出现，本质是缺乏对封装的理解，而一个好的封装是需要基于行为。所以，把视角再提升，应该考虑的问题是类应该提供哪些行为，而非简单地把数据换一种形式呈现出来就没了。
-
-有些内部 DSL 的表现形式也是连续的方法调用，但 DSL 是声明性的，是在说做什么（What），而这里的坏味道是在说怎么做（How），二者的抽象级别是不同的，不要混在一起。
-
-# 基本类型偏执
-```java
-public double getEpubPrice(final boolean highQuality,
-						   final int chapterSequence) {
-  ...
-}
-```
-根据章节信息获取 EPUB 价格。看上去非常清晰，也有坏味道？
-问题在返回值类型，即价格的类型。
-
-在DB存储价格时，就是用一个浮点数，用 double 可保证计算的精度，这样的设计有问题？
-确实，这就是很多人使用基本类型（Primitive）作为变量类型思考的角度。但实际上，这种采用基本类型的设计缺少了一个模型。
-
-虽然价格本身是用浮点数在存储，但价格和浮点数本身不是同一概念，有着不同行为需求。比如，一般要求商品价格大于 0，但 double 类型本身没这限制。
-
-就以“价格大于0”这个需求为例，如果使用 double 类型你会怎么限制？
-```java
-if (price <= 0) {
-  throw new IllegalArgumentException("Price should be positive");
-}
-```
-如果使用 double 作为类型，那我们要在使用的地方都保证价格的正确性，像这样的价格校验就应该是使用的地方到处写的。
-
-如果补齐这里缺失的模型，我们可以引入一个 Price 类型，这样的校验就可以放在初始化时：
-```java
-class Price {
-  private long price;
-  
-  public Price(final double price) {
-    if (price <= 0) {
-      throw new IllegalArgumentException("Price should be positive");
-    }
-    
-    this.price = price;
-  }
-}
-```
-这种引入一个模型封装基本类型的重构手法，叫以对象取代基本类型（Replace Primitive with Object）。
-有了这个模型，还可再进一步，比如，如果我们想要让价格在对外呈现时只有两位，在没有 Price 类的时候，这样的逻辑就会散落代码的各处，事实上，代码里很多重复的逻辑就是这样产生的。
-
-现在我们可以在 Price 类里提供一个方法：
-```java
-public double getDisplayPrice() {
-  BigDecimal decimal = new BigDecimal(this.price)；
-  return decimal.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
-}
-```
-其实，使用基本类型和使用继承出现的问题异曲同工。
-大部分程序员都了解一个设计原则：组合优于继承，即不要写出这样的代码：
-```java
-public Books extends List<Book> {
-  ...
-}
-```
-而应该写成组合的样子，也就是：
-```java
-public Books  {
-  private List<Book> books;
-  ...
-}
-```
-- 把Books写成继承，是因为在开发者眼里，Books 就是一个书的集合
-- 有人用 double 做价格的类型，因为在他看来，价格就是一个 double
-
-这里的误区在于，一些程序员只看到模型的相同之处，却忽略了差异。Books 可能不需要提供 List 的所有方法，价格的取值范围与 double 也有差异。
-
-但 Books 的问题相对容易规避，因为产生了一个新的模型，有通用的设计原则帮助我们判断这个模型构建得是否恰当，而价格的问题却不容易规避，因为这里没有产生新的模型，也就不容易发现问题。
-
-这种以基本类型为模型的坏味道称为基本类型偏执（Primitive Obsession）。
-这里的基本类型，不限于程序设计语言提供的各种基本类型，像字符串也是一个产生这种坏味道的地方。
-
-很多人对于集合类型（比如数组、List、Map 等等）的使用也属于这种坏味道：
-- 封装所有的基本类型和字符串
-- 使用一流的集合。
-
-封装之所以有难度，主要在于它是一个构建模型的过程，而很多程序员写程序，只是用着极其粗粒度的理解写着完成功能的代码，根本没有构建模型的意识；还有一些人以为划分了模块就叫封装，所以，我们才会看到这些坏味道的滋生。
-
-这里我给出的坏味道，其实也是在挑战一些人对于编程的认知：那些习以为常的代码居然成了坏味道。而这只是一个信号，一个起点，告诉你这段代码存在问题，但真正要写好代码，还是需要你对软件设计有着深入的学习。
-
-# 总结
-与封装有关的坏味道：
-- 过长的消息链，或者叫火车残骸
-- 基本类型偏执。
-
-火车残骸的代码就是连续的函数调用，它反映的问题就是把实现细节暴露了出去，缺乏应有的封装。重构的手法是隐藏委托关系，实际就是做封装。软件行业有一个编程指导原则，叫迪米特法则，可以作为日常工作的指导，规避这种坏味道的出现。
-
-基本类型偏执就是用各种基本类型作为模型到处传递，这种情况下通常是缺少了一个模型。解决它，常用的重构手法是以对象取代基本类型，也就是提供一个模型代替原来的基本类型。基本类型偏执不局限于程序设计语言提供的基本类型，字符串也是这种坏味道产生的重要原因，再延伸一点，集合类型也是。
-
-这两种与封装有关的坏味道，背后体现的是对构建模型了解不足，其实，也是很多程序员在软件设计上的欠缺。想成为一个更好的程序员，学习软件设计是不可或缺的。
-
-**构建模型，封装散落的代码。**
-
-#  怎样的封装才算是高内聚？
-链式调用不一定都是火车残骸。比如builder模式，每次调用返回的都是自身，不牵涉到其他对象，不违反迪米特法则。又比如java stream操作，就是声明性操作。
-
-构建模型还有一个好处是增加了一层抽象，屏蔽了外部变化，类似防腐层作用。比如写可移植的c代码用typedef定义内部使用的类型而非直接使用基本类型，或是DDD中领域内只处理本领域的对象，使用其他领域的对象要先经过转换而不会直接使用。
-
-Java 里的 JavaBean，用MyBatis Genarater或者Lombok生成都会有Setter方法，这样数据库查询或者接受参数时，数据自动映射到这个对象。如果不用setter的话，应该怎么赋值？
-其实，现在的数据库映射用的都是反射的方式实现，与setter关系不大。
-
-
-1.如果你的编码方式是置顶向下的，且当前层都只面向意图定义空类和空函数。那么写出提倡的这种风格其实很正常。
-2.结合1描述的编码方式。顶层类中不会有基础类型，每个属性的类型都会是一个面向意图的类来承接。顶层函数的实现部分只会有一个个函数，哪怕函数实现只有一行。
-
-
-我设计了一个客hu模型，包含客hu基本信息（证jian类型，证jian号码，名称），个人信息（有些客hu是自然人，客hu不是用户），企业信息，联xi电话List，地址List（注册地址，经营地址，身份证地址），等等；个人信息、企业信息、联xi电话等都是懒加载，需要用到的时候get才执行查询。如果按照本节的说法，可能这种设计就有问题，但是不知道怎么解决
-先要分析这些模型之间的关系，如果它们是聚合和聚合根之间的关系，那就要一次性的拿出来，没有什么懒加载的问题。如果是组合关系，也许用不同的访问入口更合适。
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\346\234\200\345\245\275\347\232\204\347\274\226\347\250\213\350\257\255\350\250\200\346\230\257\346\200\216\346\240\267\347\232\204?.md" "b/\351\207\215\346\236\204/\346\234\200\345\245\275\347\232\204\347\274\226\347\250\213\350\257\255\350\250\200\346\230\257\346\200\216\346\240\267\347\232\204?.md"
deleted file mode 100644
index ad8c2bd0c4..0000000000
--- "a/\351\207\215\346\236\204/\346\234\200\345\245\275\347\232\204\347\274\226\347\250\213\350\257\255\350\250\200\346\230\257\346\200\216\346\240\267\347\232\204?.md"
+++ /dev/null
@@ -1,56 +0,0 @@
-> 没有语言是完美的。
-
-因语言演化，不同时期不同版本的程序员写的代码，在用同一门语言在编程。所以，我们经常看到各种不同时期风格代码并存。
-
-新的语言特性都是为提高代码表达性，减少犯错几率。多用新语言特性写代码，绝对没毛病！
-
-> 那应该如何使用“新”语言特性，让代码写得更好？
-# Optional
-![](https://img-blog.csdnimg.cn/4b605b543d3e47198645f4fd71780cd2.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-暂不考虑缺乏封装问题。这段代码有问题。因为未考虑对象可能为 null。
-更好的写法：
-![](https://img-blog.csdnimg.cn/a73f3114268c42c5b601fb0fff3af399.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-这种写法很稀缺，所以，新项目总是各种NPE。如果你要问程序员为什么不写对象为 null 的判断，答曰：忘了。
-
-> 空指针的发明者 Tony Hoare 将其称为“自己犯下的十亿美元错误”。
-
-还好Java 8有Optional，它提供了一个对象容器，你需要从中“取出（get）”你所需要对象，但取出前，你需判断该对象容器中是否真的存在一个对象。
-![](https://img-blog.csdnimg.cn/e2a57e7b800d42999c06093ddc8e468a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-你再不会忘掉判断对象是否存在，因为你需要从 Optional 取出存在里面的对象。正是这**多余的**一步，避免你“忘”了。
-
-更简洁的写法：
-![](https://img-blog.csdnimg.cn/d4376180d76143b6a8772ec32071d89c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-项目中所有可能为 null 的返回值，都要返回 Optional，可大大减少各种意外惊喜。
-# 函数式编程
-![](https://img-blog.csdnimg.cn/662b603e9ed742d4a15a58cb2a7d0c8a.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-准备参数的代码：
-- 筛选出审核通过的章节
-- 再把章节转换成与翻译引擎通信的格式
-- 最后把所有得到的单个参数打包成一个完整的章节参数。
-
-Java8后，不是不需要遍历集合，而是有了更好的遍历集合方式。函数式编程，大部分操作都可归结成列表转换，最核心的列表转换就是 map、filter 和 reduce。
-
-大部分循环语句都是在对一个元素集合进行操作，而这些操作基本上都可以用列表操作进行替代。
-
-再CR这段代码，有一循环语句，这循环语句在处理的是一个集合中的元素，可用列表转换：
-![](https://img-blog.csdnimg.cn/be9db1cc30e04b2286dd71e5fcfd99e0.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-有人可能说这段代码还不如我原来的循环语句简单。两种写法根本差异是抽象层次不同，可读性完全不同：
-- 循环语句是在描述实现细节
-必须要做一次“阅读理解”知晓其中细节才能知晓整个场景
-- 列表转换的写法是在描述做什么
-基本上和我们用语言叙述过程对应。
-
-> 其实大多数人选择循环语句只是因为对列表转换不熟练，多写即可。
-
-为什么我的感觉实践中，使用这种风格，为写出来的代码更难理解？
-你在列表转换过程中写了太多代码！很多人直接在列表转换过程中写 lambda。lambda 本身相当于一个匿名函数，所以，很多人写成长函数了。
-lambda 是为了写短小代码提供的便利，所以，lambda 中写出大片代码，根本就是违反 lambda 设计初衷的。最好的 lambda 应只有一行代码。
-
-> 那若一个转换过程中就有很多操作咋办？
-
-提取出一个函数！就像 toSectionParameter：完成从 Section 到 SectionParameter 转换。这样一来，列表转换的本身就完全变成了一个声明，这样的写法才是能发挥出列表转换价值的写法。
-# 总结
-代码风格逐步演化，每个程序员对语言的理解程度都有所差异，所以，我们的屎山项目中，各种代码风格并存，各具风骚，加重代码理解难度，这其实就是：不一致的坏味道。
-
-编程风之所以格会过时，是因为它存在问题，新风格就是用更好方案，注意跟上时代，拥抱变化，多用新特性！
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\346\266\210\351\231\244\350\277\207\351\225\277\345\217\202\346\225\260\345\210\227\350\241\250.md" "b/\351\207\215\346\236\204/\346\266\210\351\231\244\350\277\207\351\225\277\345\217\202\346\225\260\345\210\227\350\241\250.md"
deleted file mode 100644
index 045966e5f5..0000000000
--- "a/\351\207\215\346\236\204/\346\266\210\351\231\244\350\277\207\351\225\277\345\217\202\346\225\260\345\210\227\350\241\250.md"
+++ /dev/null
@@ -1,222 +0,0 @@
-有经验的程序员应该都见过，一个方法坐拥几十上百个参数。
-
-# 方法为何要有参数？
-因为不同方法间需共享信息。
-
-但方法间共享信息的方式不止一种，除了参数列表，还有全局变量。但全局变量总能带来意外惊喜，所以，取消全局变量也是各大语言的趋势。
-
-但方法之间还是要传递信息的，不能用全局变量，于是参数就成了唯一选择，于是，只要你想到有什么信息要传给一个方法，就会直接它加到参数列表中，参数列表也越来越长。
-
-# 长参数列表的问题
-参数列表过长，你一个 crud 程序员就很难完全掌控这些逻辑了呀！
-
-所以症结是数量多，解决关键也就在于降低参数的数量。
-# 解决方案
-##  聚沙成塔
-一个简单的创建博客的方法：
-```java
-public void createActicle(final String title, 
-                       final String introduction,
-                       final URL coverUrl,
-                       final ActicleType type,
-                       final ActicleColumn column,
-                       final String protagonists,
-                       final String tags,
-                       final boolean completed) {
-  ...
-  Acticle acticle = Acticle.builder
-    .title(title) 
-    .introduction(introduction)
-    .coverUrl(coverUrl)
-    .type(type)
-    .column(column)
-    .protagonists(protagonists)
-    .tags(tags)
-    .completed(completed)
-    .build();
-    
-  this.repository.save(acticle);
-}
-```
-参数列表包含了一篇博客所要拥有的各种信息，比如：博客标题、博客简介、封面 URL、博客类型、博客归属的专栏、主角姓名、博客标签、博客是否完结......
-
-如果只是想理解逻辑，或许你还会觉得参数列表挺合理，毕竟它把创建一篇博客所需的各种信息都传给了方法，这也是大部分人面对一段代码时理解问题的最初角度。
-虽然这样写代码容易让人理解，但这不足以让你发现问题。
-
-> 现在产品要求在博客里增加一项信息，标识这部博客是否是签约博客，也就是这部博客是否可收费，咋办？
-
-很简单啊！我直接新增一个参数。很多屎山就这么来的，积少成多，量变引起质变！
-
-这里所有参数都是创建博客所必需的。所以，可以做的就是将这些参数封装成一个类，一个创建博客的参数类：
-```java
-public class NewActicleParamters {
-  private String title;
-  private String introduction;
-  private URL coverUrl;
-  private ActicleType type;
-  private ActicleColumn column;
-  private String protagonists;
-  private String tags;
-  private boolean completed;
-  ...
-}
-```
-这样参数列表就只剩下一个参数了：
-```java
-public void createActicle(final NewActicleParamters parameters) {
-  ...
-}
-```
-所以， **将参数列表封装成对象吧** ！
-
-只是把一个参数列表封装成一个类，然后，用到这些参数的时候，还需要把它们一个个取出来，这会不会是多此一举呢？就像这样：
-```java
-public void createActicle(final NewActicleParamters parameters) {
-  ...
-  Acticle acticle = Acticle.builder
-    .title(parameters.getTitle()) 
-    .introduction(parameters.getIntroduction())
-    .coverUrl(parameters.getCoverUrl())
-    .type(parameters.getType())
-    .channel(parameters.getChannel())
-    .protagonists(parameters.getProtagonists())
-    .tags(parameters.getTags())
-    .completed(parameters.isCompleted())
-    .build();
-    
-  this.repository.save(acticle);
-}
-```
-若你也这样想，说明：你还没有形成对软件设计的理解。我们并非简单地把参数封装成类，站在设计角度，这里引入的是一个新模型。
-**一个模型的封装应该以【行为】为基础**。
-之前没有这个模型，所以想不到它应该有什么行为，现在模型产生了，它就该有自己配套的行为。
-
-那这个模型的行为是什么？构建一个博客对象，这很清晰，则代码就能进一步重构：
-```java
-public class NewActicleParamters {
-  private String title;
-  private String introduction;
-  private URL coverUrl;
-  private ActicleType type;
-  private ActicleColumn column;
-  private String protagonists;
-  private String tags;
-  private boolean completed;
-  
-  public Acticle newActicle() {
-    return Acticle.builder
-      .title(title) 
-      .introduction(introduction)
-      .coverUrl(coverUrl)
-      .type(type)
-      .column(column)
-      .protagonists(protagonists)
-      .tags(tags)
-      .completed(completed)
-      .build();
-  }
-}
-```
-创建博客的方法就得到了极大简化：
-```java
-public void createActicle(final NewActicleParamters parameters) {
-  ...
-  Acticle acticle = parameters.newActicle();
-    
-  this.repository.save(acticle);
-}
-```
-
-“如何扩展需求”？如果需求扩展，需要增加创建博客所需的内容，那这个参数列表就是不变的，相对来说，它就是稳定的。
-
-那这个类就会不断膨胀，变成一个大类，那该怎么办呢？如何解决大类？
-## 动静分离
-不是所有情况下，参数都属于一个类：
-```java
-public void getChapters(final long acticleId, 
-                        final HttpClient httpClient,
-                        final ChapterProcessor processor) {
-  HttpUriRequest request = createChapterRequest(acticleId);
-  HttpResponse response = httpClient.execute(request);
-  List<Chapter> chapters = toChapters(response);
-  processor.process(chapters);
-}
-```
-根据博客 ID 获取其对应章节信息。
-纯以参数个数论，参数数量不多。
-
-如果你只是看这个方法，可能很难发现直接问题。绝对数量不是关键点，参数列表也应该是越少越好。
-
-在这几个参数里面，每次传进来的 acticleId 都不一样，随请求不同而改变。但 httpClient 和 processor 两个参数一样，因为都有相同逻辑，没什么变化。
-即acticleId 的变化频率同 httpClient 和 processor 这两个参数变化频率不同。
-
-不同的数据变动方向也是不同关注点。这里表现出来的就是典型的动数据（acticleId）和静数据（httpClient 和 processor），它们是不同关注点，应该分离。
-
-具体到这个场景下，静态不变的数据完全可以成为这个方法所在类的一个字段，而只将每次变动的东西作为参数传递就可以了。按照这个思路，代码可以改成这个样子：
-```java
-public void getChapters(final long acticleId) {
-  HttpUriRequest request = createChapterRequest(acticleId);
-  HttpResponse response = this.httpClient.execute(request);
-  List<Chapter> chapters = toChapters(response);
-  this.processor.process(chapters);
-}
-```
-这个坏味道其实是一个软件设计问题，代码缺乏应有的结构，所以，原本应该属于静态结构的部分却以动态参数的方式传来传去，无形之中拉长了参数列表。
-
-长参数列表固然可以用一个类进行封装，但能够封装出这个类的前提条件是：这些参数属于一个类，有相同变化原因。
-
-如果方法的参数有不同的变化频率，就要视情况而定了。对于静态的部分，我们前面已经看到了，它可以成为软件结构的一篇分，而如果有多个变化频率，我们还可以封装出多个参数类。
-
-## 告别标记
-```java
-public void editChapter(final long chapterId, 
-                        final String title, 
-                        final String content, 
-                        final boolean apporved) {
-  ...
-}
-```
-待修改章节的ID、标题和内容，最后一个参数表示这次修改是否直接审核通过。
-
-前面几个参数是修改一个章节的必要信息，重点在最后这个参数。
-从业务上说，如果是作者进行编辑，之后要经过审核，而如果编辑来编辑的，那审核就直接通过，因为编辑本身扮演了审核人的角色。所以，你发现了，这个参数实际上是一个标记，标志着接下来的处理流程会有不同。
-
-使用标记参数，是程序员初学编程时常用的一种手法。正是这种手法实在太好用，导致代码里flag肆意飘荡。不仅变量里有标记，参数里也有。很多长参数列表其中就包含了各种标记参数。
-
-在实际的代码中，必须小心翼翼地判断各个标记当前的值，才能做好处理。
-
-解决标记参数，一种简单的方式就是，将标记参数代表的不同路径拆分出来。
-
-这里的一个方法可以拆分成两个方法，一个方法负责“普通的编辑”，另一个负责“可以直接审核通过的编辑”。
-
-```java
-// 普通的编辑，需要审核
-public void editChapter(final long chapterId, 
-                        final String title, 
-                        final String content) {
-  ...
-}
-```
-```java
-// 直接审核通过的编辑
-public void editChapterWithApproval(final long chapterId,
-                                    final String title,
-                                    final String content) {
- ...
-}
-```
-标记参数在代码中存在的形式很多，有的是布尔值、枚举值、字符串或整数。都可以通过拆分方法的方式将它们拆开。在重构中，这种手法叫做移除标记参数（Remove Flag Argument）。
-
-只有短小的代码，我们才能有更好地把握，而要写出短小的代码，需要我们能够“分离关注点”。
-
-# 总结
-应对长参数列表主要的方式就是减少参数的数量，最直接的就是将参数列表封装成一个类。但并不是说所有的情况都能封装成类来解决，我们还要分析是否所有的参数都有相同的变动频率。
-
-变化频率相同，则封装成一个类。
-变化频率不同的话：
-- 静态不变的，可以成为软件结构的一篇分
-- 多个变化频率的，可以封装成几个类
-
-此外，参数列表中经常会出现标记参数，这是参数列表变长的另一个重要原因。对于这种标记参数，一种解决方案就是根据这些标记参数，将方法拆分成多个方法。
-
-**减少参数列表，越少越好。**
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\347\250\213\345\272\217\345\274\202\345\270\270\345\244\204\347\220\206\345\256\236\350\267\265.md" "b/\351\207\215\346\236\204/\347\250\213\345\272\217\345\274\202\345\270\270\345\244\204\347\220\206\345\256\236\350\267\265.md"
deleted file mode 100644
index e69de29bb2..0000000000
diff --git "a/\351\207\215\346\236\204/\347\274\226\347\250\213\344\271\213\344\270\215\345\217\230\346\200\247.md" "b/\351\207\215\346\236\204/\347\274\226\347\250\213\344\271\213\344\270\215\345\217\230\346\200\247.md"
deleted file mode 100644
index b28b40a2ce..0000000000
--- "a/\351\207\215\346\236\204/\347\274\226\347\250\213\344\271\213\344\270\215\345\217\230\346\200\247.md"
+++ /dev/null
@@ -1,138 +0,0 @@
-有一类Bug是很让人头疼的，就是你的代码怎么看都没问题，可是运行起来就是出问题了。
-
-某程序库是其他人封装的，我只是拿来用。按理我调用这个函数逻辑也不复杂，不应该有问题。
-为快定位问题，还是打开了这个程序库源码。发现底层实现中，出现全局变量。
-
-在我的代码执行过程中，有别的程序会调用另外函数，修改这个全局变量，导致程序执行失败。
-表面看，我调用的这个函数和另外那个函数没关系，但它们却通过一个底层全局变量，相互影响。
-
-有人认为这是全局变量使用不当，所以在Java设计中，甚至取消了全局变量，但类似问题并未减少，只是以不同面貌展现，比如，static 变量。
-
-这类问题真正原因还是在于变量可变。
-# 变量的危害
-变量不就应该是变的吗？
-先上代码：
-![](https://img-blog.csdnimg.cn/585a330aaf6646faba56e4311eedc7e6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_18,color_FFFFFF,t_70,g_se,x_16)
-但并发环境下有 bug。正确写法修正如下：
-![](https://img-blog.csdnimg.cn/395d080991d04e04bad65aaf64f99ecd.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-区别在于，SimpleDateFormat在哪里构建：
-- 被当作一个字段
-- 在方法内部
-
-不同做法根本差异在于SimpleDateFormat **对象是否共享**。
-
-为什么对象共享会有问题？
-查看源码：
-![](https://img-blog.csdnimg.cn/ddb7ceee54e1445a88ca2cee6228131a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-calendar是SimpleDateFormat类的一个字段
-![](https://img-blog.csdnimg.cn/ad3b46e87f824e20b0e1d2a42323b66f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-在执行format过程中修改了calendar，导致 bug。
-![](https://img-blog.csdnimg.cn/b04e706c500b4196bdda693562ef0497.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-1. A线程把变量值修改成自己需要的值
-2. 此时线程切换，B线程开始执行，将变量值修改成它需要值
-3. 线程切换回来，A继续执行，但此时变量已不是原来A自己所设值，所以，执行出错
-
-对于SimpleDateFormat，calendar就是那个共享变量：一个线程刚设置的值，可能会被另一个线程修改，导致意外惊喜。
-而Test2中，每次创建一个新SDF对象，避免了线程共享，bug解决。
-
-> 若就爱Test1写法，SDF该如何改写呢？
-
-SDF作者水平太次，换我写，就给它加synchronized或Lock锁，但你轻易地引入多线程的复杂性。多线程是另外一个关注点，能不用就不用。
-
-推荐方案：将calendar变成局部变量，从根本解决线程共享变量问题。
-
-这类问题在函数式编程中却几乎不可能存在，因函数式编程的不变性。
-# 不变性
-函数式编程的不变性主要体现在：
-- 值
-可理解为一个初始化之后就不再改变的量，即当你使用一个值时，值不会变
-很多人开始无界：初始化后不会改变的“值”，这不就是常量吗？注意，常量一般是预先确定的，而值是在运行过程中生成的。
-- 纯函数
-对于相同的输入，给出相同的输出；没有副作用
-
-二者结合：
-- 值保证不会显式改变一个量
-- 纯函数保证不会隐式改变一个量
-
-函数式编程中的函数源自数学的函数。当前语境下，函数就是纯函数，一个函数计算后不会产生额外改变，而函数中用到的一个个量就是值，它们是不会随着计算改变的。
-所以，在函数式编程中，计算天然不变。
-
-因为不变性，所以前面的问题也就不复存在：
-- 若你拿到一个量，这次的值是1，下一次它还是1，无需担心它会改变
-- 调用一个函数，传进去同样的参数，它保证给出同样的结果，行为是完全可以预期的，不会碰触到其他部分。即便是在多线程下，也无需担心同步问题
-
-传统方式的基础是面向内存单元，任性的改来改去已成为大多程序员的本能。所以习惯如下代码
-```java
-counter = counter + 1
-```
-
-传统的编程方式占优的地方是执行效率，而如今，这优点则越来越不明显，反而因为到处都可变，带来更多问题。
-我们更该在设计中，借鉴函数式编程，把不变性更多应用在业务代码中。
-
-> 怎么应用呢？
-
-## 值
-可以编写不变类，即对象一旦构造出来就不能改变，最常见的不变类就是String类了，
-
-> 如何编写一个不变类呢？
-
-- 所有的字段只在构造器初始化
-- 所有的方法都是纯函数
-- 若需要改变，返回一个新对象，而非修改已有字段
-如String#replace方法，会用一个新字符（newChar）替换掉这个字符串中原字符（oldChar），但并非直接修改已有字符串，而是创建一个新字符串对象返回。好处是，使用原来这个字符串的类无需担心自己引用的内容会随之变化。
-
-理解这个，你就更理解 DDD 里的值对象（Value Object）是啥了。
-
-## 纯函数
-写纯函数的关键：
-- 不修改任何字段
-- 不调用修改字段内容的方法
-
-Java并非严格函数式编程语言，不是所有量都是值。
-所以在实用性角度，可以实践：
-- 若要使用变量，就使用局部变量
-- 使用语法中不变的修饰符
-多用final。无论是修饰变量、方法，都是让编译器提醒你，要多在不变性上设计
-
-拥有不变性编程思维后，你会发现很多习惯都让你的代码陷入水深火热，比如你最爱的setter就是提供了一个接口，专门修改一个对象内部的值。
-
-但要承认现实，纯粹函数式编程很难，只能把编程原则设定为尽可能编写不变类和纯函数。但你依然能套在大量现存业务代码上。
-
-大多数涉及可变或副作用的代码，应该都是与外部系统交互。能够把大多数代码写成不变的，的确能减少许多后期维护成本。
-
-正由于不变性，有些新语言默认不再是变量，而是值。比如，Rust声明的是个值，一旦初始化，就无法修改：
-```rust
-let result = 1;
-```
-而若你想声明一个变量，必须显式告诉编译器：
-```rust
-let mut result = 1;
-```
-Java的Valhalla 项目也在尝试将值类型引入语言。所以，不变性，真的是减少程序问题的发展趋势。
-
-# 事件溯源
-事件源：不要轻易添加「状态」，取而代之的是通过事件源（通过事件的发生时间，去重建历史的对象及对应关系），我觉得这本质上是给实体模型赋予不变性，从而消除因为状态变化而引发的副作用。
-
-不变性，也是诸多编程原则背后的原则。例如，基于「不变性」这样一个目标，领悟驱动设计中的「值对象」 做法(定义一个不变的对对象，用于标识实体之外的其他业务模型)，以及马丁.福勒提出的「无副作用方法」（side-effect-free function，指代方法不会对对象状态产生任何改变) 等，就都显得非常恰如其分了。
-
-更极端的如 Rust ，直接让不变性成为语法语汇，有人评价这是一种把道德规范引入法律的做法，觉得这种类比有一定道理。然而在语言层面，至少倒逼程序员产出不那么坏的代码。
-
-对比一般的CRUD，就是没有修改，只有不断的插入值不同的同一条记录，下次修改时，在最新一条基础上修改值后再插入一条最新的。有点类似Java String 的处理方式，修改是生成另一个对象。
-
-
-# 总结
-函数式编程，限制使用赋值语句，它是对程序中的赋值施加了约束。一旦初始化好一个量，就不要随便给它赋值了。
-
-函数式编程相关的各种说法：无副作用、无状态、引用透明等，都是在说不变性。
-
-从Effect Java中学到了builder模式，实践DDD，也应多考虑不变性：
-比如修改用户信息，业务逻辑提取入参数据，返回值是通过builder构造一个新对象；builder中有完整性校验；这样可保证经过业务逻辑处理后返回的对象一定是一个新的并且是符合业务完整性的领域对象。
-
-变化是需求层面的不得已，不变是代码层面的努力控制。
-
-最后，请**尽量编写不变类和纯函数。**
-
-> 参考
-> - https://www.youtube.com/watch?v=mGR0A5Jyolg
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\344\275\277\347\224\250\351\235\231\346\200\201\345\267\245\345\216\202\346\226\271\346\263\225\357\274\210\347\256\200\345\215\225\345\267\245\345\216\202\357\274\211\346\233\277\344\273\243\346\236\204\351\200\240\345\231\250.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\344\275\277\347\224\250\351\235\231\346\200\201\345\267\245\345\216\202\346\226\271\346\263\225\357\274\210\347\256\200\345\215\225\345\267\245\345\216\202\357\274\211\346\233\277\344\273\243\346\236\204\351\200\240\345\231\250.md"
deleted file mode 100644
index 3cfa0d3065..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\344\275\277\347\224\250\351\235\231\346\200\201\345\267\245\345\216\202\346\226\271\346\263\225\357\274\210\347\256\200\345\215\225\345\267\245\345\216\202\357\274\211\346\233\277\344\273\243\346\236\204\351\200\240\345\231\250.md"
+++ /dev/null
@@ -1,195 +0,0 @@
-# 1 简单工厂
-- 定义
-由一个工厂对象决定创建出哪一种产品类的实例
-
-- 类型
-创建型，但不属于GOF23种设计模式
-
-- 官方定义
-Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses
-定义一个用于创建对象的接口，让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类
-
-
-## 1.1 基本案例
-![](https://img-blog.csdnimg.cn/d568ab17cf6249d482ac870b33f0bcd2.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/b258dd21e3a8416587146ad443ab8d20.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/5800debd53da414aaa70cfe5e33cec82.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/883600aaeb15432c97544c5cb5d2d727.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/8684e7ff9aa64b1aad86591c53d26c44.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/dc43af8bd45d485a98c652ec71df061c.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-## 1.2 JDK应用实例
-### 日历类
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTZkMDE4OWFkMmNhMTU0YjAucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTUwZjI0OTFmZTdhMmFjNzUucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTFjZjBlNWNkNTAxNzZjMDgucG5n?x-oss-process=image/format,png)
-### 迭代器
-Collection 接口就相当于 VideoFactory
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTdmMDFjMmRmMTBmN2IzMDkucG5n?x-oss-process=image/format,png)
-相当于各种具体的工厂，如 JavaVideoFactory
-![父类接口,子类实现](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWMxOTY5ZjUzYTllOGY5ZDIucG5n?x-oss-process=image/format,png)
-Itr 就是具体产品 JavaVideo
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWE3Mzc5MDliYjg0ZTQxNjcucG5n?x-oss-process=image/format,png)
-### 工厂应用
-#### 为解决 url 协议扩展使用
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWI5MjRiMDY0MjlmNTU0ZWQucG5n?x-oss-process=image/format,png)
-- Launcher#Factory静态类
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTRlOWJlYjBjODEwMDI1YTgucG5n?x-oss-process=image/format,png)
-#### logback 应用
-![LoggerFactory#getLogger(String name)](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTQwMzNkZjJhYjMyYTM0MWQucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWE1ZGZhYTU2Yjg2YjFiMzIucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTY4NWUzY2ZiMTQ0ODAxZTkucG5n?x-oss-process=image/format,png)
-
-### JDBC实例
-![直接注册 MySQL 驱动](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTRhNWQ1N2FlNjQxNmE1YWYucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQzMTFiYzYzMDRlMTdlZjUucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWRlZjU2ZjZmNTM0ZmE0NzIucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQwN2JmOThkNmI1N2MyMjcucG5n?x-oss-process=image/format,png)
-返回值是一个抽象类，必有一子类实现
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWIzNTgwYjdkNGQ3MTgwYjUucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWUzNmQ5MWU2OGZmMDgzNTIucG5n?x-oss-process=image/format,png)
-![通过间接继承此处理器](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTRhMjdiYjljM2IzZWQ2NGIucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQyYTEwNGM0YmM1NjZiNTYucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTNlYzcyZWQwN2Y5ZjJkNGUucG5n?x-oss-process=image/format,png)
-这其中URLStreamHandler就相当于各种抽象产品,而其实现类即各种具体的产品
-URLStreamHandlerFactory就相当于 VideoFactory
-而如下 Factory 就相当于如 JavaVideoFactory/PythonVideoFactory
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWUwZDJlYzUyZjNlZGIwMjMucG5n?x-oss-process=image/format,png)
-
-### Logback实例
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTA1NmI3ZTNlNmVlYmIxYzgucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTg4OGUxMGJiMmQwNzEzMjIucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWZiNWZjZDM5MTJlNGY2NDkucG5n?x-oss-process=image/format,png)
-
-
-客户端获得一个类实例的传统方式是调用由类提供的public构造器。但还有一种技术，一个类可以提供public的静态工厂方法，只是一个返回类实例的静态方法。
-
-> 静态工厂方法与设计模式的工厂方法模式不同。在设计模式中并无直接等价的说法。
-# 2 优点
-只需要传入一个正确的参数，即可获取所需对象，无需知其创建细节。
-## 2.1 实名制
-如果构造器的参数本身并不能描述清楚返回的对象，那么具有确切名称的静态工厂则代码可读性更佳！
-
-例如 BigInteger 类的构造器 `BigInteger(int, int, Random)` 返回值多半是质数，那么最好使用静态工厂方法： `BigInteger.probablePrime` 
-![](https://img-blog.csdnimg.cn/20200708215827121.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-一个类只能有一个带给定签名的构造器。可 SE 一般还能通过提供两个构造器来解决，而构造器的参数列表就仅在参数类型的顺序上不同。dirty code！这样的 API，用户永远无法记住该用哪个构造器，并且最终会错误地调用不合适的构造器。不阅读类文档，使用者人根本不知道代码的作用。
-
-而静态工厂方法有确切的名称，所以没这局限。如果一个类就是需要具有相同签名的多个构造器，那么静态工厂方法就很 nice，注意精心的命名来突出它们的区别。
-
-## 2.2 无需在每次调用时创建新对象
-这使得不可变类使用事先构造好的实例，或在构造实例时缓存实例，重复分配以避免创建不必要的重复对象。`Boolean.valueOf(boolean)` 方法：它从不创建对象。
-Boolean类中该方法将 boolean 基本类型值转换为一个 Boolean 对象引用
-- 返回一个Boolean表示指定实例boolean的值。 如果指定的boolean值是true ，则此方法返回Boolean.TRUE ; 如果是false ，这个方法返回Boolean.FALSE 。 如果并不需要一个**新的**Boolean 实例，该方法一般应优于构造器中使用Boolean(boolean) ，因为此方法可能产生显著的更好的空间和时间性能
-![](https://img-blog.csdnimg.cn/20200707204138711.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-这类似于[享元模式](https://javaedge.blog.csdn.net/article/details/107218915)。如果经常请求相同对象，特别是创建对象代价高时，可以极大提高性能。
-
-
-静态工厂方法在重复调用下返回相同对象，这样类能严格控制存在的实例。这样的类称为实例受控的类。编写实例受控的类有几个原因。
-- 允许一个类来保证它是一个单例或不可实例化的。同时，它允许一个不可变的值类保证不存在两个相同的实例：
-`a.equals(b)` 当且仅当 `a==b`。这是享元模式的基础。枚举类型提供了这种保证。
-
-
-## 2.3  获取返回类型的任何子类的对象
-这为选择返回对象的类提供灵活性。
-
-这种灵活性的一个应用是 API 可以在public其类的情况下返回对象。以这种方式隐藏实现类会形成一个非常紧凑的 API。这适用于基于接口的框架，其中接口为静态工厂方法提供了自然的返回类型。
-
-Java 8 前，接口不能有静态方法。按照惯例，一个名为 Type 的接口的静态工厂方法被放在一个名为 Types 的不可实例化的伴生类。例如，Java 的 Collections 框架有 45 个接口实用工具实现，提供了不可修改的集合、同步集合等。几乎所有这些实现都是通过一个非实例化类（`java.util.Collections`）中的静态工厂方法导出的。返回对象的类都是非public的。
-
-现在的Collections 框架 API 比它导出 45 个独立的public类小得多，每个公共类对应一个方便的实现。减少的不仅仅是 API 的数量，还有概念上的减少：程序员为了使用 API 必须掌握的概念的数量和难度。程序员知道返回的对象是由相关的接口精确指定的，因此不需阅读额外的类文档。
-**使用这种静态工厂方法需要客户端通过接口而不是实现类引用返回的对象，这通常是很好的做法**。
-
-Java 8 取消了接口不能包含静态方法的限制，因此通常没有理由为接口提供不可实例化的伴生类。许多公共静态成员应该放在接口本身中，而不是放在类中。但仍有必要将这些静态方法背后的大部分实现代码放到单独的包私有类中。因为 Java 8 要求接口的所有静态成员都是公共的，而 Java 9 允许接口有私有的静态方法，但是静态字段和静态成员类仍然需是public
-
-**A fourth advantage of static factories is that the class of the returned object can vary from call to call as a function of the input parameters.** Any subtype of the declared return type is permissible. The class of the returned object can also vary from release to release.
-
-## 2.4  返回对象的类可以随调用的不同而变化
-这当然取决于输入参数不同。只要是已声明的返回类型的子类型都是允许的。返回对象的类也可以因版本而异。
-
-- EnumSet 类没有public构造器
-![](https://img-blog.csdnimg.cn/20200709012340394.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-只有静态工厂。在 OpenJDK 中，它们返回两种子类之一的一个实例，这取决于底层 enum 类型的大小：
-- 如果它有 64 个或更少的元素，就像大多数 enum 类型一样，静态工厂返回一个 long 类型的 RegularEnumSet 实例
-- 如果 enum 类型有 65 个或更多的元素，工厂将返回一个由 `long[]` 类型的 JumboEnumSet 实例。
-
-客户端看不到这两个实现类的存在。如果 RegularEnumSet 不再为小型 enum 类型提供性能优势，它可能会在未来的版本中被删除，而不会产生任何负面影响。类似地，如果证明 EnumSet 有益于性能，未来的版本可以添加第三或第四个 EnumSet 实现。客户端既不知道也不关心从工厂返回的对象的类；它们只关心它是 EnumSet 的某个子类。
-
-## 2.5 当编写包含静态工厂方法的类时，返回对象的类可以不存在
-这种灵活的静态工厂方法构成了服务提供者框架（Service Provider Framework，SPF）的基础，比如 JDBC API。
-
-### SPF系统
-多个提供者实现一个服务，该系统使客户端可以使用这些实现，从而将客户端与实现分离。
-
-SPF有三个基本组件
-- 代表实现的服务接口
-- 提供者注册 API，提供者使用它来注册实现
-- 服务访问 API，客户端使用它来获取服务的实例。服务访问 API 允许客户端指定选择实现的条件。在没有这些条件的情况下，API 返回一个默认实现的实例，或者允许客户端循环使用所有可用的实现。服务访问 API 是灵活的静态工厂，它构成了服务提供者框架的基础。
-
-SPF第四个可选组件是服务提供者接口，它描述产生服务接口实例的工厂对象。在没有服务提供者接口的情况下，必须以反射的方式实例化实现。
-#### JDBC案例
-1. Connection扮演服务接口的一部分
-2. `DriverManager.registerDriver` 是提供者注册 API
-3. `DriverManager.getConnection` 是服务访问 API
-4. Driver是服务提供者接口
-
-SPF模式有许多变体。例如，服务访问 API 可以向客户端返回比提供者提供的更丰富的服务接口，这就是[桥接模式](https://javaedge.blog.csdn.net/article/details/107219110u) 。依赖注入（DI）框架就可以看成是强大的服务提供者。从Java 6开始，平台就提供了一个通用服务提供者框架 `Java.util.ServiceLoader`，所以你不需要，通常也不应该自己写。JDBC 不使用 ServiceLoader，因为前者比后者早！
-
-# 3 缺点
-- 工厂类的职责相对过重，增加新的产品
-- 需要修改工厂类的判断逻辑，违背开闭原则
-## 3.1 **仅提供静态工厂方法的主要局限是，没有public或protected构造器的类不能被继承** 
-例如，不可能在集合框架中子类化任何便利的实现类。这可能是一种因祸得福的做法，因为它鼓励程序员使用组合而不是继承，这对于不可变类型是必须的。
-
-
-## 3.2 程序员很难找到它们
-它们在 API 文档中不像构造器吸睛，因此很难弄清楚如何实例化一个只提供静态工厂方法而没有构造器的类。Javadoc 工具总有一天会关注到静态工厂方法。
-通过在类或接口文档中多关注静态工厂方法，遵守通用命名约定的方式来减少这个困扰。
-下面是一些静态工厂方法的习惯命名。
-
-
-> from，类型转换方法，接收单个参数并返回该类型的相应实例
-![](https://img-blog.csdnimg.cn/20200710023241706.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-> of，聚合方法，接受多个参数并返回一个实例
-![](https://img-blog.csdnimg.cn/20200710023550552.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-
-> valueOf，比 from 和 of 但更繁琐的一种替代方法
-![](https://img-blog.csdnimg.cn/20200710023748541.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-> instance 或 getInstance，返回一个实例，该实例由其参数（如果有的话）描述，但不和参数具有相同的值
-
-```java
-StackWalker luke = StackWalker.getInstance(options);
-```
-
-
-> create 或 newInstance，与 instance 或 getInstance 类似，只是该方法保证每个调用都返回一个新实例
-![](https://img-blog.csdnimg.cn/20200710024426667.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-> getType，类似于 getInstance，但如果工厂方法位于不同的类中，则使用此方法。其类型是工厂方法返回的对象类型，例如：
-
-```java
-FileStore fs = Files.getFileStore(path);
-```
-
-> newType，与 newInstance 类似，但是如果工厂方法在不同的类中使用。类型是工厂方法返回的对象类型，例如：
-![](https://img-blog.csdnimg.cn/20200710024712390.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-
-> type，一个用来替代 getType 和 newType 的比较简单的方式
-
-```java
-List<Complaint> litany = Collections.list(legacyLitany);
-```
-![](https://img-blog.csdnimg.cn/20200710025342311.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-### 适用场景
-- 工厂类负责创建的对象比较少
-- 客户端(应用层)只知道传入工厂类的参数，对于如何创建对象(逻辑)不关心
-# 总结
-静态工厂方法和public构造器 各有千秋，我们需要理解它们各自的优点。通常静态工厂更可取，因此切忌不考虑静态工厂就提供public构造器。
-
-> 参考 
-> - effective java
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md"
deleted file mode 100644
index f7ad2ba859..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\345\267\245\345\216\202\346\226\271\346\263\225\346\250\241\345\274\217.md"
+++ /dev/null
@@ -1,308 +0,0 @@
-
-- 源码
-https://github.com/Wasabi1234/design-patterns
-# 2 工厂方法模式
-- 定义
-定义一个创建对象的接口。但让实现这个接口的类来决定实例化哪个类，工厂方法让类的实例化推迟到子类中进行。
-- 类型
-创建型
-
-### 通用类图
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtOGUyMDQ1N2M5Yjg2ZjcxYi5qcGc?x-oss-process=image/format,png)
-在工厂方法模式中：
-- 抽象产品类Product负责定义产品的共性，实现对事物最抽象的定义
-- Creator为抽象创建类，即抽象工厂，具体如何创建产品类是由具体实现工厂ConcreteCreator完成
-## 2.1 简单工厂模式的升级
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTFlMzEzMGZmNTAxNjA0ODUucG5n?x-oss-process=image/format,png)
-
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTJlZGRhOTJiZDA5MmQ2MDAucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTA3NTFiMjZkNGQ1ODdiNWQucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTJhYTM5N2Q4NmY2OGZlOTMucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWFhNDU3MDhhNWZkM2FhMzgucG5n?x-oss-process=image/format,png)
-
-对造人过程进行分析，该过程涉及三个对象：女娲、八卦炉、三种不同肤色的人
-- 女娲可以使用场景类`Client`表示
-- 八卦炉类似于一个工厂，负责制造生产产品（即人类）
-- 三种不同肤色的人，他们都是同一个接口下的不同实现类,对于八卦炉来说都是它生产出的产品
-![女娲造人类图](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTIzYjcyOWFiYTI1MjNmZWUucG5n?x-oss-process=image/format,png)
-- 接口Human是对人类的总称，每个人种都至少具有两个方法
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQxYjI0YjBjZWU3MjBlYjcucG5n?x-oss-process=image/format,png)
-- 黑色人种
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTUxMjRmZTBhYWViYjIxMWYucG5n?x-oss-process=image/format,png)
-- 黄色人种
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTVkYzc5NDZkNTc5ZmZiNTYucG5n?x-oss-process=image/format,png)
-- 白色人种
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LThiMmQ5NTVlYmVmYzE0NzEucG5n?x-oss-process=image/format,png)
-
-所有人种定义完毕，下一步就是定义一个八卦炉，然后烧制。
-最可能给八卦炉下达什么样的生产命令呢？
-应该是
-- `给我生产出一个黄色人种（YellowHuman类）`
-
-而不会是
-- `给我生产一个会走、会跑、会说话、皮肤是黄色的人种`
-
-因为这样的命令增加了交流的成本，作为一个生产的管理者，只要知道生产什么就可以了，无需事物具体信息
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWJiMDA2MzA4NTcyZDM0MzkucG5n?x-oss-process=image/format,png)
-
-通过定义泛型对createHuman的输入参数产生两层限制
-- 必须是Class类型
-- 必须是Human的实现类
-其中的`T`表示，只要实现了Human接口的类都可以作为参数
-
-只有一个八卦炉，其实现生产人类的方法
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWViN2E3Yjc2Njk3NzI0NTIucG5n?x-oss-process=image/format,png)
-人种有了，八卦炉也有了，剩下的工作就是女娲采集黄土，然后命令八卦炉开始生产
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTE3NTZlODQwOWI2ZjU1OTgucG5n?x-oss-process=image/format,png)
-
-人种有了，八卦炉有了，负责生产的女娲也有了
-运行一下，结果如下所示
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTBlOTExMzgzYmE4NDE2ZDkucG5n?x-oss-process=image/format,png)
-## 案例 2
-# 注册工厂
-- `Pet` 层次生成对象的问题
-每当添加一种新`Pet` 类型，必须记住将其添加到 `LiteralPetCreator.java` 的条目中。在一个定期添加更多类的系统中，这可能会成为问题。
-
-你可能会考虑向每个子类添加静态初始值设定项，因此初始值设定项会将其类添加到某个列表中。但静态初始值设定项仅在首次加载类时调用：生成器的列表中没有类，因此它无法创建该类的对象，因此类不会被加载并放入列表中。
-
-必须自己手工创建列表。所以最好就是把列表集中放在一个明显的地方：层次结构的基类
-
-使用*工厂方法*设计模式将对象的创建推迟到类本身。
-工厂方法以多态方式调用，创建适当类型的对象。
-`java.util.function.Supplier` 用 `T get()` 描述了原型工厂方法。协变返回类型允许 `get()` 为 `Supplier` 的每个子类实现返回不同的类型。
-
-在本例中，基类 `Part` 包含一个工厂对象的静态列表，列表成员类型为 `Supplier<Part>`。对于应该由 `get()` 方法生成的类型的工厂，通过将它们添加到 `prototypes` 列表向基类“注册”。奇怪的是，这些工厂本身就是对象的实例。此列表中的每个对象都是用于创建其他对象的*原型*：
-
-```java
-// typeinfo/RegisteredFactories.java
-// 注册工厂到基础类
-import java.util.*;
-import java.util.function.*;
-import java.util.stream.*;
-
-class Part implements Supplier<Part> {
-    @Override
-    public String toString() {
-        return getClass().getSimpleName();
-    }
-
-    static List<Supplier<? extends Part>> prototypes =
-        Arrays.asList(
-          new FuelFilter(),
-          new AirFilter(),
-          new CabinAirFilter(),
-          new OilFilter(),
-          new FanBelt(),
-          new PowerSteeringBelt(),
-          new GeneratorBelt()
-        );
-
-    private static Random rand = new Random(47);
-    public Part get() {
-        int n = rand.nextInt(prototypes.size());
-        return prototypes.get(n).get();
-    }
-}
-
-class Filter extends Part {}
-
-class FuelFilter extends Filter {
-    @Override
-    public FuelFilter get() {
-        return new FuelFilter();
-    }
-}
-
-class AirFilter extends Filter {
-    @Override
-    public AirFilter get() {
-        return new AirFilter();
-    }
-}
-
-class CabinAirFilter extends Filter {
-    @Override
-    public CabinAirFilter get() {
-        return new CabinAirFilter();
-    }
-}
-
-class OilFilter extends Filter {
-    @Override
-    public OilFilter get() {
-        return new OilFilter();
-    }
-}
-
-class Belt extends Part {}
-
-class FanBelt extends Belt {
-    @Override
-    public FanBelt get() {
-        return new FanBelt();
-    }
-}
-
-class GeneratorBelt extends Belt {
-    @Override
-    public GeneratorBelt get() {
-        return new GeneratorBelt();
-    }
-}
-
-class PowerSteeringBelt extends Belt {
-    @Override
-    public PowerSteeringBelt get() {
-        return new PowerSteeringBelt();
-    }
-}
-
-public class RegisteredFactories {
-    public static void main(String[] args) {
-        Stream.generate(new Part())
-              .limit(10)
-              .forEach(System.out::println);
-    }
-}
-```
-并非层次结构中的所有类都应实例化；这里的 `Filter` 和 `Belt` 只是分类器，这样你就不会创建任何一个类的实例，而是只创建它们的子类（请注意，如果尝试这样做，你将获得 `Part` 基类的行为）。
-
-因为 `Part implements Supplier<Part>`，`Part` 通过其 `get()` 方法供应其他 `Part`。如果为基类 `Part` 调用 `get()`（或者如果 `generate()` 调用 `get()`），它将创建随机特定的 `Part` 子类型，每个子类型最终都从 `Part` 继承，并重写相应的 `get()` 以生成它们中的一个。
-
-
-
-工厂方法模式变种较多，看个比较实用的通用源码。
-- 抽象产品类
-![](https://img-blog.csdnimg.cn/01f25f3d12d844ef88a8a0b38e75b589.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 具体产品类，可以有多个，都继承于抽象产品类
-![](https://img-blog.csdnimg.cn/230d4301655a4170b5f405194525f530.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/cca3ac4a844045c5bde2fb090c309518.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-- 抽象工厂类，定义产品对象的产生
-![](https://img-blog.csdnimg.cn/f5e746211a684cdfab67bd96aa651c0b.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-- 具体工厂类，具体如何产生一个产品的对象，由具体工厂类实现
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWI2MGI5ODY1MDUxMzA5NWEucG5n?x-oss-process=image/format,png)
-- 场景类
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWI0NzliNjMyZWVlYjFjMDIucG5n?x-oss-process=image/format,png)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTYwN2EyYzU2MmVkMWEyZDAucG5n?x-oss-process=image/format,png)
-
-该通用代码是一个比较实用、易扩展的框架，读者可以根据实际项目需要进行扩展
-# 3 应用
-## 3.1 优点
-- 用户只需要关心所需产品对应的工厂，无须关心创建细节
-- 加入新产品符合开闭原则,提高可扩展性
-
-- 良好的封装性，代码结构清晰
-一个对象创建是有条件约束的，如一个调用者需要一个具体的产品对象，只要知道这个产品的类名（或约束字符串）就可以了，不用知道创建对象的艰辛过程，降低模块间的耦合
-- 工厂方法模式的扩展性非常优秀
-在增加产品类的情况下，只要适当地修改具体的工厂类或扩展一个工厂类，就可以完成“拥抱变化”
-例如在我们的例子中，需要增加一个棕色人种，则只需要增加一个BrownHuman类，工厂类不用任何修改就可完成系统扩展。
-- 屏蔽产品类
-这一特点非常重要，产品类的实现如何变化，调用者都不需要关心，它只需要关心产品的接口，只要接口保持不变，系统中的上层模块就不要发生变化
-因为产品类的实例化工作是由工厂类负责的，`一个产品对象具体由哪一个产品生成是由工厂类决定的`
-在数据库开发中，大家应该能够深刻体会到工厂方法模式的好处：如果使用JDBC连接数据库，数据库从MySQL切换到Oracle，需要改动的地方就是切换一下驱动名称（前提条件是SQL语句是标准语句），其他的都不需要修改，这是工厂方法模式灵活性的一个直接案例。
-- 典型的解耦框架
-高层模块值需要知道产品的抽象类，其他的实现类都不用关心
-符合迪米特法则，我不需要的就不要去交流
-也符合依赖倒置原则，只依赖产品类的抽象
-当然也符合里氏替换原则，使用产品子类替换产品父类，没问题！
-## 3.2 缺点
-- 类的个数容易过多，增加复杂度
-- 增加了系统的抽象性和理解难度
-## 3.3 适用场景
-- 创建对象需要大量重复的代码
-- 客户端(应用层)不依赖于产品类实例如何被创建、实现等细节
-- 一个类通过其子类来指定创建哪个对象
-- 工厂方法模式是new一个对象的替代品
-在所有需要生成对象的地方都可以使用，但是需要慎重地考虑是否要增加一个工厂类进行管理，增加代码的复杂度。
-
-### 需要灵活的、可扩展的框架时
-万物皆对象，那万物也就皆产品类。
-例如需要设计一个连接邮件服务器的框架，有三种网络协议可供选择：POP3、IMAP、HTTP。
-可以把这三种连接方法作为产品类，定义一个接口如`IConnectMail`
-然后定义对邮件的操作方法
-用不同的方法实现三个具体的产品类（也就是连接方式）
-再定义一个工厂方法，按照不同的传入条件，选择不同的连接方式
-如此设计，可以做到完美的扩展，如某些邮件服务器提供了WebService接口，很好，我们只要增加一个产品类就可以了
-### 异构项目
-例如通过WebService与一个非Java的项目交互，虽然WebService号称是可以做到异构系统的同构化，但是在实际的开发中，还是会碰到很多问题，如类型问题、WSDL文件的支持问题，等等。从WSDL中产生的对象都认为是一个产品，然后由一个具体的工厂类进行管理，减少与外围系统的耦合。
-### 使用在测试驱动开发的框架下
-例如，测试一个类A，就需要把与类A有关联关系的类B也同时产生出来，我们可以使用工厂方法模式把类B虚拟出来，避免类A与类B的耦合。目前由于JMock和EasyMock的诞生，该使用场景已经弱化了，读者可以在遇到此种情况时直接考虑使用JMock或EasyMock
-# 4 扩展
-工厂方法模式有很多扩展，而且与其他模式结合使用威力更大，下面将介绍4种扩展。
-## 4.1 缩小为简单工厂模式
-我们这样考虑一个问题：一个模块仅需要一个工厂类，没有必要把它产生出来，使用静态的方法就可以了，根据这一要求，我们把上例中的`AbstarctHumanFactory`修改一下
-![简单工厂模式类图](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNzJmODZiNWU5OTJjNTIzNy5qcGc?x-oss-process=image/format,png)
-我们在类图中去掉了`AbstractHumanFactory`抽象类，同时把`createHuman`方法设置为静态类型，简化了类的创建过程，变更的源码仅仅是HumanFactory和NvWa类
-
-- 简单工厂模式中的工厂类
-![待考证](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTRjOTc0MGVjOWJhZWM4N2EucG5n?x-oss-process=image/format,png)
-
-HumanFactory类仅有两个地方发生变化
-- 去掉继承抽象类
-- 在`createHuman`前增加static关键字
-
-工厂类发生变化，也同时引起了调用者NvWa的变化
- ![简单工厂模式中的场景类](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTU3NjVkZGMzNmEwYmRjODQucG5n?x-oss-process=image/format,png)
-运行结果没有发生变化，但是我们的类图变简单了，而且调用者也比较简单，该模式是工厂方法模式的弱化，因为简单，所以称为`简单工厂模式`（Simple Factory Pattern），也叫做`静态工厂模式`
-在实际项目中，采用该方法的案例还是比较多的
-- 其缺点
-工厂类的扩展比较困难，不符合开闭原则，但它仍然是一个非常实用的设计模式。
-## 4.2 升级为多个工厂类
-当我们在做一个比较复杂的项目时，经常会遇到初始化一个对象很耗费精力的情况，所有的产品类都放到一个工厂方法中进行初始化会使代码结构不清晰
-例如，一个产品类有5个具体实现，每个实现类的初始化（不仅仅是new，初始化包括new一个对象，并对对象设置一定的初始值）方法都不相同，如果写在一个工厂方法中，势必会导致该方法巨大无比，那该怎么办？
-
-考虑到需要结构清晰，我们就为每个产品定义一个创造者，然后由调用者自己去选择与哪个工厂方法关联
-我们还是以女娲造人为例，每个人种都有一个固定的八卦炉，分别造出黑色人种、白色人种、黄色人种
-![多个工厂类的类图](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNzlhZjQ3MjM0YWFjMjk5OS5qcGc?x-oss-process=image/format,png)
-
-- 每个人种（具体的产品类）都对应了一个创建者，每个创建者都独立负责创建对应的产品对象，非常符合单一职责原则，看看代码变化
-![多工厂模式的抽象工厂类](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTAzMDA2Y2VjYTJjOGYyNDIucG5n?x-oss-process=image/format,png)
-抽象方法中已经不再需要传递相关参数了，因为每一个具体的工厂都已经非常明确自己的职责：创建自己负责的产品类对象。
-
-- 黑色人种的创建工厂实现
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTUwNGQyNGFjYjRlMGIyNGQucG5n?x-oss-process=image/format,png)
-- 黄色人种的创建类
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWU1MjNiNTRlMmNiYWUzYTQucG5n?x-oss-process=image/format,png)
-- 白色人种的创建类
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWVmNmY2NWVjMDVjMTZjZjcucG5n?x-oss-process=image/format,png)
-
-三个具体的创建工厂都非常简单，但是，如果一个系统比较复杂时工厂类也会相应地变复杂。
-- 场景类NvWa修改后的代码
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWFkNTIxNTBmZDc5YzUyYzAucG5n?x-oss-process=image/format,png)
-
-运行结果还是相同
-每一个产品类都对应了一个创建类，好处就是创建类的职责清晰，而且结构简单，但是给可扩展性和可维护性带来了一定的影响。为什么这么说呢？如果要扩展一个产品类，就需要建立一个相应的工厂类，这样就增加了扩展的难度。因为工厂类和产品类的数量相同，维护时需要考虑两个对象之间的关系。
-
-当然，在复杂的应用中一般采用多工厂的方法，然后再增加一个协调类，避免调用者与各个子工厂交流，协调类的作用是封装子工厂类，对高层模块提供统一的访问接口。
-## 4.3 替代单例模式
-单例模式的核心要求就是`在内存中只有一个对象`，通过工厂方法模式也能只在内存中生产一个对象。
-- 工厂方法模式替代单例模式类图
-![](https://imgconvert.csdnimg.cn/aHR0cDovL3VwbG9hZC1pbWFnZXMuamlhbnNodS5pby91cGxvYWRfaW1hZ2VzLzQ2ODU5NjgtNmVlNzUwYmM2MTJiNmQ2Zi5qcGc?x-oss-process=image/format,png)
-Singleton定义了一个private的无参构造函数，目的是不允许通过new的方式创建一个对象
-![单例类](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWZjZGJjYjM1YjVlNzgwNWUucG5n?x-oss-process=image/format,png)
-Singleton保证不能通过正常的渠道建立一个对象
-
-那SingletonFactory如何建立一个单例对象呢？
-反射！
-- 负责生成单例的工厂类
-![](https://img-blog.csdnimg.cn/095f579f196847c6b9b0d1f3ed098e55.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-通过获得类构造器，然后设置private访问权限，生成一个对象，然后提供外部访问，保证内存中的对象唯一。
-
-以上通过工厂方法模式创建了一个单例对象，该框架可以继续扩展，在一个项目中可以产生一个单例构造器，所有需要产生单例的类都遵循一定的规则（构造方法是private），然后通过扩展该框架，只要输入一个类型就可以获得唯一的一个实例。
-## 3.4 延迟初始化（Lazy initialization）
-一个对象被消费完毕后，并不立刻释放，工厂类保持其初始状态，等待再次被使用
-延迟初始化是工厂方法模式的一个扩展应用
-- 延迟初始化的通用类图
-![](https://img-blog.csdnimg.cn/e63f7fb0b8ba47a0b91bb947b73c0874.png)
-
-ProductFactory负责产品类对象的创建工作，并且通过prMap变量产生一个缓存，对需要再次被重用的对象保留
-
-- 延迟加载的工厂类
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWZhZWFmM2RmNjc5YTI2YzUucG5n?x-oss-process=image/format,png)
-通过定义一个Map容器，容纳所有产生的对象，如果在Map容器中已经有的对象，则直接取出返回；如果没有，则根据需要的类型产生一个对象并放入到Map容器中，以方便下次调用。
-
-延迟加载框架是可以扩展的，例如限制某一个产品类的最大实例化数量，可以通过判断Map中已有的对象数量来实现，这样的处理是非常有意义的，例如JDBC连接数据库，都会要求设置一个MaxConnections最大连接数量，该数量就是内存中最大实例化的数量。
-
-延迟加载还可以用在对象初始化比较复杂的情况下，例如硬件访问，涉及多方面的交互，则可以通过延迟加载降低对象的产生和销毁带来的复杂性。
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md"
deleted file mode 100644
index bfce4f5f76..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\346\250\241\346\235\277\346\226\271\346\263\225\346\250\241\345\274\217.md"
+++ /dev/null
@@ -1,237 +0,0 @@
-一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现，但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。
-
-- 意图
-定义一个操作中的算法的骨架，而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
-
-- 主要解决
-一些方法通用，却在每一个子类都重新写了这一方法。
-
-- 关键代码
-在抽象类实现，其他步骤在子类实现。
-
-# 实例
-spring 中对 Hibernate 的支持，将一些已经定好的方法封装起来，比如开启事务、获取 Session、关闭 Session 等，程序员不重复写那些已经规范好的代码，直接丢一个实体就可以保存。
-
-# 优点
- 1、封装不变部分，扩展可变部分。 
- 2、提取公共代码，便于维护。 
- 3、行为由父类控制，子类实现。
-
-# 缺点
-每一个不同的实现都需要一个子类来实现，导致类的个数增加，使得系统更加庞大。
-
-# 使用场景
-1、有多个子类共有的方法，且逻辑相同。 2、重要的、复杂的方法，可以考虑作为模板方法。
-
-注意事项：为防止恶意操作，一般模板方法都加上 final 关键词。
-
-# 实现
-我们将创建一个定义操作的 Game 抽象类，其中，模板方法设置为 final，这样它就不会被重写。Cricket 和 Football 是扩展了 Game 的实体类，它们重写了抽象类的方法。
-
-TemplatePatternDemo，我们的演示类使用 Game 来演示模板模式的用法。
-
-![](https://img-blog.csdnimg.cn/20200517194325508.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-## 步骤 1
-创建一个抽象类，它的模板方法被设置为 final。
-
-```java
-Game.java
-public abstract class Game {
-   abstract void initialize();
-   abstract void startPlay();
-   abstract void endPlay();
- 
-   //模板
-   public final void play(){
- 
-      //初始化游戏
-      initialize();
- 
-      //开始游戏
-      startPlay();
- 
-      //结束游戏
-      endPlay();
-   }
-}
-```
-
-## 步骤 2
-创建扩展了上述类的实体类。
-
-```java
-Cricket.java
-public class Cricket extends Game {
- 
-   @Override
-   void endPlay() {
-      System.out.println("Cricket Game Finished!");
-   }
- 
-   @Override
-   void initialize() {
-      System.out.println("Cricket Game Initialized! Start playing.");
-   }
- 
-   @Override
-   void startPlay() {
-      System.out.println("Cricket Game Started. Enjoy the game!");
-   }
-}
-Football.java
-public class Football extends Game {
- 
-   @Override
-   void endPlay() {
-      System.out.println("Football Game Finished!");
-   }
- 
-   @Override
-   void initialize() {
-      System.out.println("Football Game Initialized! Start playing.");
-   }
- 
-   @Override
-   void startPlay() {
-      System.out.println("Football Game Started. Enjoy the game!");
-   }
-}
-```
-
-## 步骤 3
-使用 Game 的模板方法 play() 来演示游戏的定义方式。
-
-```java
-TemplatePatternDemo.java
-public class TemplatePatternDemo {
-   public static void main(String[] args) {
- 
-      Game game = new Cricket();
-      game.play();
-      System.out.println();
-      game = new Football();
-      game.play();      
-   }
-}
-```
-# 1 定义与类型
-- 模板方法模式（Template Method Pattern）
-Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.（定义一个操作中的算法的框架，而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。）
-
-- 定义
-定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现
-
-模板方法使得子类可以在不改变算法结构的情况下，重新定义算法的某些步骤
-
-行为型。
-![模板方法模式的通用类图](https://img-blog.csdnimg.cn/img_convert/cd217a65bf37b7874a4ae2871b42fd6c.png)
-模板方法模式确实非常简单，仅仅使用了Java的继承机制，AbstractClass叫做抽象模板，它的方法分为两类：
-- 基本方法
-基本操作，是由子类实现的方法，并且在模板方法被调用
-- 模板方法
-可有一个或几个，一般是一个具体方法，也就是一个框架，实现对基本方法的调度，完成固定的逻辑
-
-为了防止恶意操作，一般模板方法都加上final关键字，不允许被重写。
-
-在类图中还有一个角色：具体模板。ConcreteClass1和ConcreteClass2属于具体模板，实现父类所定义的一个或多个抽象方法，即父类定义的基本方法在子类中得以实现。
-AbstractClass抽象模板类
-```java
-public abstract class AbstractClass {
-     // 基本方法
-     protected abstract void doSomething();
-     // 基本方法
-     protected abstract void doAnything();
-     // 模板方法
-     public void templateMethod(){
-             /*
-              * 调用基本方法，完成相关的逻辑
-              */
-             this.doAnything();
-             this.doSomething();
-     }
-}
-```
-
-具体模板类
-```java
-public class ConcreteClass1 extends AbstractClass {
-     // 实现基本方法
-     protected void doAnything() {
-             // 业务逻辑
-     }
-     protected void doSomething() {
-             // 业务逻辑处理
-     }
-}
-public class ConcreteClass2 extends AbstractClass {
-     // 实现基本方法
-     protected void doAnything() {
-             // 业务逻辑处理
-     }
-     protected void doSomething() {
-             // 业务逻辑处理
-     }
-}
-```
-
-场景类
-```java
-public class Client {
-     public static void main(String[] args) {
-             AbstractClass class1 = new ConcreteClass1();
-             AbstractClass class2 = new ConcreteClass2();               
-             //调用模板方法
-             class1.templateMethod();
-             class2.templateMethod();
-     }
-}
-```
-抽象模板中的基本方法尽量设计为protected类型，符合迪米特法则，不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要，尽量不要扩大父类中的访问权限。
-# 2 适用场景
-- 一次性实现一个算法的不变的部分，并将可变的行为留给子类来实现
-- 各子类中公共的行为被提取出来并集中到一个公共父类中，从而避免代码重复
-
-- 多个子类有公有的方法，并且逻辑基本相同时
-- 重要、复杂的算法，可以把核心算法设计为模板方法，周边的相关细节功能则由各个子类实现
-- 重构时，模板方法模式是一个经常使用的模式，把相同的代码抽取到父类中，然后通过钩子函数（见“模板方法模式的扩展”）约束其行为。
-# 3 优点
-- 提高复用性
-- 提高扩展性
-- 符合开闭原则
-- 封装不变部分，扩展可变部分
-把认为是不变部分的算法封装到父类实现，而可变部分的则可以通过继承来继续扩展
-- 提取公共部分代码，便于维护
-如果我们不抽取到父类中，任由这种散乱的代码发生，想想后果是什么样子？维护人员为了修正一个缺陷，需要到处查找类似的代码
-- 行为由父类控制，子类实现
-基本方法由子类实现，因此子类可以通过扩展的方式增加相应的功能，符合开闭原则
-# 4 缺点
-- 类数目增加
-- 增加了系统实现的复杂度
-- 继承关系自身缺点，如果父类添加新的抽象方法，所有子类都要改一遍
-
-抽象类负责声明最抽象、最一般的事物属性和方法，实现类完成具体的事物属性和方法
-但是模板方法模式却颠倒了，抽象类定义了部分抽象方法，由子类实现，子类执行的结果影响了父类的结果，也就是子类对父类产生了影响，这在复杂的项目中，会带来代码阅读的难度，而且也会让新手产生不适感。
-# 相关设计模式
-模板方法模式和工厂方法模式
-模板方法模式和策略模式
-
-# 案例
-![](https://img-blog.csdnimg.cn/img_convert/e11e9b9b1c4545e739c6f013a0187f13.png)
-![](https://img-blog.csdnimg.cn/img_convert/211d1668926ee62aecc8d225c51dbf19.png)
-![](https://img-blog.csdnimg.cn/img_convert/0b4933194b0d54bf08ed4172d5ca0d18.png)
-![](https://img-blog.csdnimg.cn/img_convert/d5a0c37391d58ef0e1f746e6652e5f5a.png)
-
-# 最佳实践
-初级程序员在写程序的时候经常会问高手“父类怎么调用子类的方法”。这个问题很有普遍性，反正我是被问过好几回，那么父类是否可以调用子类的方法呢？我的回答是能，但强烈地、极度地不建议这么做，那该怎么做呢?
-
-● 把子类传递到父类的有参构造中，然后调用
-● 使用反射的方式调用
-● 父类调用子类的静态方法
-
-这三种都是父类直接调用子类的方法，好用不？好用！解决问题了吗？解决了！项目中允许使用不？不允许！
-我就一直没有搞懂为什么要用父类调用子类的方法。如果一定要调用子类，那为什么要继承它呢？搞不懂。其实这个问题可以换个角度去理解，父类建立框架，子类在重写了父类部分的方法后，再调用从父类继承的方法，产生不同的结果（而这正是模板方法模式）。这是不是也可以理解为父类调用了子类的方法呢？你修改了子类，影响了父类行为的结果，曲线救国的方式实现了父类依赖子类的场景，模板方法模式就是这种效果。
-
-模板方法在一些开源框架中应用非常多，它提供了一个抽象类，然后开源框架写了一堆子类。
-如果你需要扩展功能，可以继承这个抽象类，然后重写protected方法，再然后就是调用一个类似execute方法，就完成你的扩展开发，非常容易扩展的一种模式。
-# 应用
-## AbstractList
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\255\226\347\225\245\346\250\241\345\274\217(Strategy-Pattern).md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\255\226\347\225\245\346\250\241\345\274\217(Strategy-Pattern).md"
deleted file mode 100644
index ef6bcb6e23..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\347\255\226\347\225\245\346\250\241\345\274\217(Strategy-Pattern).md"
+++ /dev/null
@@ -1,106 +0,0 @@
-[相关源码](https://github.com/Wasabi1234/Java-DesignPatterns-Tuitorial)
-# 1 简介
-## 1.1 定义
-也叫做政策模式（Policy Pattern）
-- wiki
-对象有某个行为，但是在不同的场景中，该行为有不同的实现算法.。比如每个人都要“交个人所得税”，但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法.
-- 定义
-Define a family of algorithms,encapsulate each one,and make them interchangeable.
-定义一组算法，将每个算法都封装起来，并且使它们之间可以互换。
-
-常见 if/else 结构。
-
-## 1.2 类型
-行为型。
-在`运行时`(**非编译时**)改变软件的算法行为。
-
-## 1.3 主要思想
-定义一个通用的问题，使用不同的算法来实现，然后将这些算法都封装在一个统一接口。
-
-策略模式使用的就是OOP的继承和多态。
-
-## 1.4 主要角色
-### 通用类图
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWFkMWNhZjE4NDMyNGRlY2YucG5n?x-oss-process=image/format,png)
-
-- Context 封装角色
-即上下文角色，起承上启下的封装作用。屏蔽高层模块对策略&算法的直接访问，封装可能存在的变化。
-
-- Strategy 抽象策略角色
-策略&算法家族的抽象，通常为接口，定义每个策略或算法必须具有的方法和属性。
-
-- ConcreteStrategy 具体策略角色
-实现抽象策略中的操作，含有具体的算法。
-
-### 通用源码
-- 抽象策略角色
-一个非常普通的接口，在项目中就是一个普通接口，定义一或多个具体算法。
-# 2 适用场景
-一个对象，其行为有些固定不变，有些又容易变化。对于这些容易变化的行为，我们不希望将其实现绑定在对象中，而希望能够动态地针对不同场景产生不同应对的策略。
-这时就要用到策略模式，就是为了应对对象中复杂多变的行为而产生的：
-- 系统有很多类，而他们的区别仅在于行为不同
-- 一个系统需要动态地在几种算法中选择一种
-# 3 优点
-- 符合开闭原则
-- 避免使用多重条件转移语句
-e.g. 省去大量 if/else、switch，降低代码耦合度
-- 提高算法的保密性和安全性
-只需知道策略的业务功能，而不关心内部实现
-# 4 缺点
-- 客户端必须知道所有的策略类，并决定使用哪个策略类
-- 产生很多策略类
-
-# 5 相关设计模式的差异
-## 5.1 V.S 工厂模式
-- 行为型
-接收已经创建好的对象，从而实现不同的行为
-- 创造型
-接收指令，创建符合要求的具体对象
-
-## 5.2 V.S 状态模式
-- 若系统中某类的某行为存在多种实现方式，客户端需知道到底使用哪个策略
-- 若系统中某对象存在多种状态，不同状态下的行为又具有差异，状态之间会自动转换,客户端不需要关心具体状态
-## 5.3 V.S 模板模式
-- 策略模式：只有选择权(由用户自己选择已有的固定算法)
-- 模板模式，侧重点不是选择，你没得选择，你必须这么做。你可以参与某一部分内容自定义
-# 6 实战
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTBlYmMwOGY0MWUwN2NkY2EucG5n?x-oss-process=image/format,png)
-- 促销策略接口
-![](https://img-blog.csdnimg.cn/20201104133917501.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-- 返现策略
-![](https://img-blog.csdnimg.cn/20201104134155926.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-- 立减策略
-![](https://img-blog.csdnimg.cn/2020110413472547.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-- 满减策略
-![](https://img-blog.csdnimg.cn/20201104135011162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-- 测试类
-![](https://img-blog.csdnimg.cn/20201104135935601.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTg0NDA3NWYwMWE5ZTM0OWIucG5n?x-oss-process=image/format,png)
-改造后的测试类
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTQ5OTFkMmVhYWQ5MzU3YzEucG5n?x-oss-process=image/format,png)
-可见 if/else 语句过多,采取策略+工厂模式结合
-- 策略工厂
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTIzMDA4OGNhMjYwZGIyNTYucG5n?x-oss-process=image/format,png)
-- 最新测试类
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWFjZjgwZGE0ZmE1ZWE5NTQucG5n?x-oss-process=image/format,png)
-- 输出结果
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTdkMjYwMzNhMWIzOWJkNmEucG5n?x-oss-process=image/format,png)
-
-# 7 源码应用解析
-## JDK中的比较器接口
-- 策略比较器
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTMwNzY2Njg5NmMzZDE4MDAucG5n?x-oss-process=image/format,png)
-![具体策略](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWQ5MjhkZDE2YmVhNDRhNjAucG5n?x-oss-process=image/format,png)
-比如Arrays类中的 sort 方法通过传入不同比较接口器的实现达到不同排序策略
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LWY5MjA3MzcxMmUzMGNlNjYucG5n?x-oss-process=image/format,png)
-## JDK中的TreeMap
-类似于促销活动中有促销策略对象,在T reeMap 中也有比较器对象
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTQyNGY3ODdkYTE3ZDQ4NzYucG5n?x-oss-process=image/format,png)
-compare 方法进步加工
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTMyZTAyNDU2NTQyYzFlNDgucG5n?x-oss-process=image/format,png)
-## Spring 中的Resource
-不同访问策略
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LTY2ZDYxOTExNzdmYWFmMmEucG5n?x-oss-process=image/format,png)
-## Spring 中bean 的初始化ceInstantiationStrategy
-- 两种 bean 的初始化策略
-![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80Njg1OTY4LThmYTVlNDRlNDkxYWFmZGMucG5n?x-oss-process=image/format,png)
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217(Decorator Pattern).md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217(Decorator Pattern).md"
deleted file mode 100644
index 1c671ad61e..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\243\205\351\245\260\345\231\250\346\250\241\345\274\217(Decorator Pattern).md"	
+++ /dev/null
@@ -1,157 +0,0 @@
-# 导读
-一般有两种方式可以给一个类或对象新增行为：
-- 继承
-子类在拥有自身方法同时还拥有父类方法。但这种方法是静态的，用户无法控制增加行为的方式和时机。
-- 关联
-将一个类的对象嵌入另一个对象，由另一个对象决定是否调用嵌入对象的行为以便扩展自身行为，这个嵌入的对象就叫做装饰器(Decorator)。
-
-# 定义
-对象结构型模式。
-
-动态地给一个对象增加额外功能，装饰器模式比生成子类实现更为灵活。
-装饰模式以对用户透明的方式**动态**给一个对象附加功能。用户不会觉得对象在装饰前、后有何不同。装饰模式可在无需创造更多子类情况下，扩展对象的功能。
-
-# 角色
-- Component 接口: 抽象构件
-定义了对象的接口，可以给这些对象动态增加功能
-
-- ConcreteComponent 具体类: 具体构件
-定义了具体的构件对象，实现了 在抽象构件中声明的方法，装饰器可以给它增加额外的职责（方法）
-
-- Decorator 抽象类: 装饰类
-抽象装饰类是抽象构件类的子类，用于给具体构件增加职责，但是具 体职责在其子类中实现；
-
-- ConcreteDecorator 具体类: 具体装饰类
-具体装饰类是抽象装饰类的子类，负责向构 件添加新的职责。
-![](https://img-blog.csdnimg.cn/20210531150553798.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-
-# 代码实例
-窗口 接口
-```java
-public interface Window {
- 	// 绘制窗口
-	public void draw();
-	 // 返回窗口的描述
-	public String getDescription();
-}
-```
-无滚动条功能的简单窗口实现
-```java
-public class SimpleWindow implements Window {
-	public void draw() {
-		// 绘制窗口
-	}
-
-	public String getDescription() {
-		return "simple window";
-	}
-}
-```
-
-以下类包含所有Window类的decorator，以及修饰类本身。
-```java
-//  抽象装饰类 注意实现Window接口
-public abstract class WindowDecorator implements Window {
-	// 被装饰的Window
-    protected Window decoratedWindow;
-
-    public WindowDecorator (Window decoratedWindow) {
-        this.decoratedWindow = decoratedWindow;
-    }
-    
-    @Override
-    public void draw() {
-        decoratedWindow.draw();
-    }
-
-    @Override
-    public String getDescription() {
-        return decoratedWindow.getDescription();
-    }
-}
-
-
-// 第一个具体装饰器 添加垂直滚动条功能
-public class VerticalScrollBar extends WindowDecorator {
-	public VerticalScrollBar(Window windowToBeDecorated) {
-		super(windowToBeDecorated);
-	}
-
-	@Override
-	public void draw() {
-		super.draw();
-		drawVerticalScrollBar();
-	}
-
-	private void drawVerticalScrollBar() {
-		// Draw the vertical scrollbar
-	}
-
-	@Override
-	public String getDescription() {
-		return super.getDescription() + ", including vertical scrollbars";
-	}
-}
-
-
-// 第二个具体装饰器 添加水平滚动条功能
-public class HorizontalScrollBar extends WindowDecorator {
-	public HorizontalScrollBar (Window windowToBeDecorated) {
-		super(windowToBeDecorated);
-	}
-
-	@Override
-	public void draw() {
-		super.draw();
-		drawHorizontalScrollBar();
-	}
-
-	private void drawHorizontalScrollBar() {
-		// Draw the horizontal scrollbar
-	}
-
-	@Override
-	public String getDescription() {
-		return super.getDescription() + ", including horizontal scrollbars";
-	}
-}
-```
-
-# 优点
-使用装饰模式来实现扩展比继承更加灵活，它以对客户透明的方式动态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子类的情况下，将对象的功能加以扩展。
-
-与继承相比，关联关系的优势在于不破坏类的封装性，而且继承是一种耦合度较大的静态关系，无法在程序运行时动态扩展。
-可通过动态方式扩展一个对象的功能，通过配置文件可以在运行时选择不同装饰器，从而实现不同行为。
-
-在软件开发阶段，关联关系虽然不会比继承关系减少编码量，但到了软件维护阶段，由于关联关系使系统具有较好的松耦合性，所以更容易维护。
-
-通过使用不同具体装饰类以及这些装饰类的排列组合，可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象，得到功能更强大的对象。
-
-具体构件类与具体装饰类可以独立变化，用户可以根据需要增加新的具体构件类、具体装饰类，在使用时再对其进行组合，原有代码无须改变，符合“开闭原则”。
-#  缺点
-产生很多小对象，这些对象区别在于它们之间相互连接的方式不同，而不是它们的类或属性值不同，同时还将产生很多具体装饰类。这些装饰类和小对象的产生将增加系统的复杂度，加大学习与理解的难度。
-
-比继承更灵活，也意味着比继承更易出错，排查也更困难，对于多次装饰的对象，调试时寻找错误可能需要逐级排查，较为烦琐。
-
-# 适用场景
-在不影响其他对象的情况下，以动态、透明的方式给单个对象添加职责。
-需要动态地给一个对象增加功能，这些功能也可以动态地被撤销。
-当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
-
-不能采用继承的场景：
-- 系统存在大量独立扩展，为支持每一种组合将产生大量的子类，使得子类数目呈爆炸性增长
-- 类定义不能继承（如final类）
-
-# 扩展
-一个装饰类的接口必须与被装饰类的接口保持相同，对于客户端来说无论是装饰之前的对象还是装饰之后的对象都可以一致对待。
-尽量保持具体构件类的轻量，也就是说不要把太多的逻辑和状态放在具体构件类中，可以通过装饰类对其进行扩展。
-
-装饰模式可分为：
-- 透明装饰模式
-要求客户端完全针对抽象编程，装饰模式的透明性要求客户端程序不应该声明具体构件类型和具体装饰类型，而应该全部声明为抽象构件类型
-- 半透明装饰模式
-允许用户在客户端声明具体装饰者类型的对象，调用在具体装饰者中新增的方法。
-
-> 参考
-> - https://zh.wikipedia.org/wiki/%E4%BF%AE%E9%A5%B0%E6%A8%A1%E5%BC%8F#/
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md"
deleted file mode 100644
index a14806d986..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\247\202\345\257\237\350\200\205\346\250\241\345\274\217.md"
+++ /dev/null
@@ -1,39 +0,0 @@
-# 1 简介
-- 定义
-定义了对象之间的一对多依赖，让多个观察者对象同时监听某一个主题对象，当主题对象发生变化时，它的所有依赖者(观察者)都会收到通知并更新
-
-- 类型
-行为型
-#  2 适用场景
-关联行为场景，建立一套触发机制
-# 3 优点
-- 观察者和被观察者之间建立一个抽象的耦合
-- 观察者模式支持广播通信
-# 4 缺点
-- 观察者之间有过多的细节依赖、提高时间消耗及程序复杂度
-- 使用要得当，要避免循环调用
-# 5 实例
-![](https://img-blog.csdnimg.cn/img_convert/e2776da5ea2bbe93bc3dba9f55317885.png)
-![](https://img-blog.csdnimg.cn/img_convert/365560838866815621e5fba325024889.png)
-![](https://img-blog.csdnimg.cn/img_convert/d81962748cbf8ffd7cbfbfc12c38003f.png)
-![](https://img-blog.csdnimg.cn/img_convert/213d4cf8c5e9f6d539e703321c6ec3cf.png)
-![](https://img-blog.csdnimg.cn/img_convert/2c2cbadeaf4306466d809255f7ebb717.png)
-![](https://img-blog.csdnimg.cn/img_convert/add335433df999a5f805ea925a39a861.png)
-![](https://img-blog.csdnimg.cn/img_convert/e099de4d4c019d1cf0131b39b4b48e63.png)
-![](https://img-blog.csdnimg.cn/img_convert/c61ad7bf67bfb8ec91b2398025df2fd8.png)
-![](https://img-blog.csdnimg.cn/img_convert/092214dcd7fe0e2f02a9d8bc389cb120.png)
-![](https://img-blog.csdnimg.cn/img_convert/fa0fccf5f5d01d82f1651ec12ac01789.png)
-接下来,来到观察者- `Teacher`的代码区中
-![](https://img-blog.csdnimg.cn/img_convert/0d70288282b6fb5a7070f569d882d4fe.png)
-# 6 源码应用
-- JDK应用
-![](https://img-blog.csdnimg.cn/img_convert/95c6b248dd6d13bc8d32bb6b7662d401.png)
-## Guava
-![](https://img-blog.csdnimg.cn/img_convert/23ae47d1f0ddf8a9fbe9164e3b48afbe.png)
-![](https://img-blog.csdnimg.cn/img_convert/678b3f1db4fbbafb150276461f67a53f.png)
-- 注册，即添加观察者
-![](https://img-blog.csdnimg.cn/img_convert/c6f705554d52f53dd1c4f9a113726229.png)
-![](https://img-blog.csdnimg.cn/img_convert/b641ddc5914ae26315bc55beedd97563.png)
-![](https://img-blog.csdnimg.cn/img_convert/51752f3497f7bcd921c81d6939982cad.png)
-- 移除观察者![](https://img-blog.csdnimg.cn/img_convert/58020c5aeccbfa607983c2622eccf600.png)
-![](https://img-blog.csdnimg.cn/img_convert/54c962042877bd9cc2f0f9f748cd3847.png)
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\256\277\351\227\256\350\200\205\350\256\276\350\256\241\346\250\241\345\274\217(Visitor).md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\256\277\351\227\256\350\200\205\350\256\276\350\256\241\346\250\241\345\274\217(Visitor).md"
deleted file mode 100644
index 79a79b824c..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\256\277\351\227\256\350\200\205\350\256\276\350\256\241\346\250\241\345\274\217(Visitor).md"
+++ /dev/null
@@ -1,150 +0,0 @@
-# 1 简介
-## 1.1 定义
-封装某些作用于某种数据结构中各元素的操作，它可以在不改变数据结构的前提下定义作用于这些数据元素的新的操作
-
-## 思想
-将数据结构和数据操作分离
-
-## 目的
-稳定的数据结构和易变的操作的解耦
-
-## 适用场景
-假如一个对象中存在着一些与本对象不相干（或者关系较弱）的操作，可以使用访问者模式把这些操作封装到访问者中去，这样便避免了这些不相干的操作污染这个对象。
-
-假如一组对象中，存在着相似的操作，可以将这些相似的操作封装到访问者中去，这样便避免了出现大量重复的代码
-
-访问者模式适用于对功能已经确定的项目进行重构的时候适用，因为功能已经确定，元素类的数据结构也基本不会变了；如果是一个新的正在开发中的项目，在访问者模式中，每一个元素类都有它对应的处理方法，每增加一个元素类都需要修改访问者类，修改起来相当麻烦。
-
-# 2  示例
-如果老师教学反馈得分大于等于85分、学生成绩大于等于90分，则可以入选成绩优秀奖；如果老师论文数目大于8、学生论文数目大于2，则可以入选科研优秀奖。
-
-在这个例子中，老师和学生就是Element，他们的数据结构稳定不变。从上面的描述中，我们发现，对数据结构的操作是多变的，一会儿评选成绩，一会儿评选科研，这样就适合使用访问者模式来分离数据结构和操作。
-
-## 2.1 创建抽象元素
-
-```java
-public interface Element {
-    void accept(Visitor visitor);
-}
-```
-
-## 2.2 创建具体元素
-创建两个具体元素 Student 和 Teacher，分别实现 Element 接口
-
-```java
-public class Student implements Element {
-    private String name;
-    private int grade;
-    private int paperCount;
-
-    public Student(String name, int grade, int paperCount) {
-        this.name = name;
-        this.grade = grade;
-        this.paperCount = paperCount;
-    }
-
-    @Override
-    public void accept(Visitor visitor) {
-        visitor.visit(this);
-    }
-        
-    ......
-      
-}
-public class Teacher implements Element {
-    private String name;
-    private int score;
-    private int paperCount;
-
-    public Teacher(String name, int score, int paperCount) {
-        this.name = name;
-        this.score = score;
-        this.paperCount = paperCount;
-    }
-
-    @Override
-    public void accept(Visitor visitor) {
-        visitor.visit(this);
-    }
-
-        ......
-      
-}
-```
-
-## 2.3 创建抽象访问者
-
-```java
-public interface Visitor {
-
-    void visit(Student student);
-
-    void visit(Teacher teacher);
-}
-```
-
-## 2.4 创建具体访问者
-创建一个根据分数评比的具体访问者 GradeSelection，实现 Visitor 接口
-
-```java
-public class GradeSelection implements Visitor {
-
-    @Override
-    public void visit(Student student) {
-        if (student != null && student.getGrade() >= 90) {
-            System.out.println(student.getName() + "的分数是" + student.getGrade() + "，荣获了成绩优秀奖。");
-        }
-    }
-
-    @Override
-    public void visit(Teacher teacher) {
-        if (teacher != null && teacher.getScore() >= 85) {
-            System.out.println(teacher.getName() + "的分数是" + teacher.getScore() + "，荣获了成绩优秀奖。");
-        }
-    }
-}
-```
-
-## 2.5  调用
-
-```java
-public class VisitorClient {
-
-    public static void main(String[] args) {
-    	// 抽象元素 => 具体元素
-        Element element = new Student("lijiankun24", 90, 3);
-		// 抽象访问者 => 具体访问者
-        Visitor visitor = new GradeSelection();
-        // 具体元素 接收 具体访问者的访问
-        element.accept(visitor);
-    }
-}
-```
-
-上述代码即是一个简单的访问者模式的示例代码，输出如下所示：
-
-
-上述代码可以分为三步：
-1. 创建一个元素类的对象
-2. 创建一个访问类的对象
-3. 元素对象通过 Element#accept(Visitor visitor) 方法传入访问者对象
-
-# 3 ASM 中的访问者模式
-ASM 库就是 Visitor 模式的典型应用。
-
-## 3.1 ASM 中几个重要的类
-- ClassReader
-将字节数组或者 class 文件读入到内存当中，并以树的数据结构表示，树中的一个节点代表着 class 文件中的某个区域
-可以将 ClassReader 看作是 Visitor 模式中的访问者的实现类
-
-- ClassVisitor（抽象类）
-ClassReader 对象创建之后，调用 ClassReader#accept() 方法，传入一个 ClassVisitor 对象。在 ClassReader 中遍历树结构的不同节点时会调用 ClassVisitor 对象中不同的 visit() 方法，从而实现对字节码的修改。在 ClassVisitor 中的一些访问会产生子过程，比如 visitMethod 会产生 MethodVisitor 的调用，visitField 会产生对 FieldVisitor 的调用，用户也可以对这些 Visitor 进行自己的实现，从而达到对这些子节点的字节码的访问和修改。
-在 ASM 的访问者模式中，用户还可以提供多种不同操作的 ClassVisitor 的实现，并以责任链的模式提供给 ClassReader 来使用，而 ClassReader 只需要 accept 责任链中的头节点处的 ClassVisitor。
-- ClassWriter
-ClassVisitor 的实现类，它是生成字节码的工具类，它一般是责任链中的最后一个节点，其之前的每一个 ClassVisitor 都是致力于对原始字节码做修改，而 ClassWriter 的操作则是老实得把每一个节点修改后的字节码输出为字节数组。
-
-## 3.2 ASM 的工作流程
-1. ClassReader 读取字节码到内存中，生成用于表示该字节码的内部表示的树，ClassReader 对应于访问者模式中的元素
-2. 组装 ClassVisitor 责任链，这一系列 ClassVisitor 完成了对字节码一系列不同的字节码修改工作，对应于访问者模式中的访问者 Visitor
-3. 然后调用 ClassReader#accept() 方法，传入 ClassVisitor 对象，此 ClassVisitor 是责任链的头结点，经过责任链中每一个 ClassVisitor 的对已加载进内存的字节码的树结构上的每个节点的访问和修改
-4. 最后，在责任链的末端，调用 ClassWriter 这个 visitor 进行修改后的字节码的输出工作
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.md"
deleted file mode 100644
index 97b9bec16e..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\264\243\344\273\273\351\223\276\346\250\241\345\274\217.md"
+++ /dev/null
@@ -1,46 +0,0 @@
-# 1 导读
-## 1.1 定义
-它包含了一些命令对象和一系列处理对象。
-每个处理对象决定它能处理哪些命令对象，它也知道如何将它不能处理的命令对象传递给该链中的下一个处理对象。
-
-该模式还描述了往该处理链的末尾添加新的处理对象的方法。
-
-- 精简定义
-为请求创建一个接收此次请求对象的链。
-
-## 1.2 类型
-行为型
-# 2 适用场景
-一个请求的处理需要多个对象当中的一或几个协作处理
-# 3 优点
-请求的发送者和接收者(请求的处理)解耦，责任链可以动态组合。
-# 4 缺点
-- 责任链太长或者处理时间过长，影响性能
-- 责任链有可能过多
-
-# 5 相关设计模式
-## V.S 状态模式
-- 各个对象并不指定下一个所要处理的对象者是谁，只有在客户端类设置链顺序及元素，直到被某个责任链处理或整条链结束
-- 每个状态知道自己下一个所要处理的对象者是谁，即编译时确定
-
-# 6 实战
-![](https://img-blog.csdnimg.cn/20210717133837595.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-![](https://img-blog.csdnimg.cn/20210716225806274.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210717140319675.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210716230056567.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-- UML
-![](https://img-blog.csdnimg.cn/2021071714044678.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-
-- 测试类
-![](https://img-blog.csdnimg.cn/20210717134235324.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210717134302416.png)
-- 将博客注释掉
-![](https://img-blog.csdnimg.cn/20210717134415249.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210717134530753.png)
-# 框架应用
-## Tomcat#FilterChain
-FilterChain 是一个由 Servlet 容器提供给开发人员的对象，它提供了一个对资源的过滤请求的调用链的视图。 过滤器使用 FilterChain 调用链中的下一个过滤器，或者如果调用过滤器是链中的最后一个过滤器，则调用链末尾的资源
-![](https://img-blog.csdnimg.cn/20210717140946145.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/img_convert/92c634a095e759771415bfb4d4990b03.png)
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\277\255\344\273\243\345\231\250\346\250\241\345\274\217.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\256\276\350\256\241\346\250\241\345\274\217\345\256\236\346\210\230-\350\277\255\344\273\243\345\231\250\346\250\241\345\274\217.md"
deleted file mode 100644
index e69de29bb2..0000000000
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(1)-\345\215\225\344\270\200\350\201\214\350\264\243\345\216\237\345\210\231(Single Responsibility Principle,SRP).md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(1)-\345\215\225\344\270\200\350\201\214\350\264\243\345\216\237\345\210\231(Single Responsibility Principle,SRP).md"
deleted file mode 100644
index c2b13dbf16..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(1)-\345\215\225\344\270\200\350\201\214\350\264\243\345\216\237\345\210\231(Single Responsibility Principle,SRP).md"	
+++ /dev/null
@@ -1,261 +0,0 @@
-# 1 简介
-- 定义
-不要存在多于一个导致类变更的原因。
-
-- 特点
-一个类/接口/方法只负责一项职责。
-
-- 优点
-降低类的复杂度、提高类的可读性，提高系统的可维护性、降低变更引起的风险。
-
-名字容易让人望文生义，大部分人可能理解成：一个类只干一件事，看起来似乎很合理呀。几乎所有程序员都知道“高内聚、低耦合”，应该把相关代码放一起。
-
-若随便拿个模块去问作者，这个模块是不是只做了一件事，他们异口同声：对，只做了一件事。看来，这个原则很通用啊，所有人都懂，为啥还要有这样一个设计原则？
-
-因为一开始的理解就是错的！错在把单一职责理解成有关如何组合的原则，实际上，它是关于如何分解的。
-
-Robert Martin对单一职责的定义的变化：
-- 《敏捷软件开发：原则、实践与模式》
-一个模块应该有且仅有一个变化的原因
-- 《架构整洁之道》
-一个模块应该对一类且仅对一类行为者（actor）负责
-
-- 单一职责原则 V.S 一个类只干一件事
-最大的差别就是，将变化纳入考量。
-
-分析第一个定义：一个模块应该有且仅有一个变化的原因。
-软件设计关注长期变化，拥抱变化，我们最不愿意面对却不得不面对，只因变化会产生不确定性，可能：
-- 新业务的稳定问题
-- 旧业务遭到损害而带来的问题
-
-所以，一个模块最理想的状态是不改变，其次是少改变，它可成为一个模块设计好坏的衡量标准。
-
-但实际开发中，一个模块频繁变化，在于能诱导它改变的原因太多！
-# 2 案例
-## 2.1 鸟类案例
-- 最开始的 Bird 类
-![](https://img-blog.csdnimg.cn/20201011052517957.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-- 简单测试类
-![](https://img-blog.csdnimg.cn/img_convert/7ea1da00d7a9e0615db6170063d5d468.png)
-
-显然鸵鸟还用翅膀飞是错误的！于是，我们修改类实现
-![](https://img-blog.csdnimg.cn/2020101105314531.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-这种设计依旧很low，总不能一味堆砌 if/else 添加鸟类。结合该业务逻辑，考虑分别实现类职责，即根据单一原则创建两种鸟类即可：
-![](https://img-blog.csdnimg.cn/20201011053445284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-![](https://img-blog.csdnimg.cn/20201011053528342.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70#pic_center)
-![](https://img-blog.csdnimg.cn/20201011053706470.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-## 2.2 课程案例
-- 最初的课程接口有两个职责，耦合过大
-![](https://img-blog.csdnimg.cn/20201011054054803.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-- 按职责拆分
-![](https://img-blog.csdnimg.cn/20201011063546976.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-![](https://img-blog.csdnimg.cn/20201011063629925.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70#pic_center)
-
-![](https://img-blog.csdnimg.cn/img_convert/82c766b21a88c40cdc3a1946c5cf51c4.png)
-
-## 2.3 用户管理
-用户、机构、角色管理这些模块，基本上使用的都是RBAC模型（Role-Based Access Control，基于角色的访问控制，通过分配和取消角色来完成用户权限的授予和取消，使动作主体（用户）与资源的行为（权限）分离），这确实是一个很好的解决办法。
-
-对于用户管理、修改用户的信息、增加机构（一个人属于多个机构）、增加角色等，用户有这么多的信息和行为要维护，我们就把这些写到一个接口中，都是用户管理类：
-- 用户信息维护类图
-![](https://img-blog.csdnimg.cn/20210705144728253.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-很有问题，用户属性和用户行为严重耦合！这个接口确实设计得一团糟：
-- 应该把用户信息抽取成一个BO（Business Object，业务对象）
-- 把行为抽取成一个Biz（Business Logic，业务逻辑）
-
-- 职责划分后的类图
-![](https://img-blog.csdnimg.cn/20210705145341688.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-重新拆分成两个接口：
-- IUserBO
-负责用户的属性，职责就是收集和反馈用户的属性信息
-- IUserBiz
-负责用户的行为，完成用户信息的维护和变更
-
-我们是面向接口编程，所以产生了这个UserInfo对象后，当然可以把它当IUserBO接口使用，也可以当IUserBiz接口使用，这看你的使用场景。
-- 要获得用户信息，就当做IUserBO的实现类
-- 要是希望维护用户的信息，就把它当做IUserBiz的实现类
-```java
-IUserInfo userInfo = new UserInfo();
-// 我要赋值了，我就认为它是一个纯粹的BO
-IUserBO userBO = (IUserBO)userInfo;
-userBO.setPassword("abc");
-// 我要执行动作了，我就认为是一个业务逻辑类
-IUserBiz userBiz = (IUserBiz)userInfo;
-userBiz.deleteUser();
-```
-
-确实这样拆分后，问题就解决了，分析一下我们的做法，为什么要把一个接口拆分成两个？
-实际的使用中，更倾向于使用两个不同的类或接口：一个是IUserBO，一个是IUserBiz
-- 项目中经常采用的SRP类图
-![](https://img-blog.csdnimg.cn/img_convert/ddf683f0892e2a6a08726c7642b9dd25.png)
-
-以上我们把一个接口拆分成两个接口的动作，就是依赖了单一职责原则，那什么是单一职责原则呢？单一职责原则的定义是：应该有且仅有一个原因引起类的变更。
-
-## 2.4 电话通话
-电话通话的时候有4个过程发生：拨号、通话、回应、挂机。
-那我们写一个接口
-![电话类图](https://img-blog.csdnimg.cn/img_convert/a7fab40bd3a15f919374e59ecb03b59a.png)
-- 电话过程
-![](https://img-blog.csdnimg.cn/20210705150128534.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-这个接口有问题吗？是的，这个接口接近于完美。
-单一职责原则要求一个接口或类只有一个原因引起变化，即一个接口或类只有一个职责，它就负责一件事情，看看上面的接口：
-- 只负责一件事情吗？
-- 是只有一个原因引起变化吗？
-
-好像不是！IPhone这个接口可不是只有一个职责，它包含了两个职责：
-- 协议管理
-dial()和hangup()两个方法实现的是协议管理，分别负责拨号接通和挂机
-- 数据传送
-chat()实现的是数据的传送，把我们说的话转换成模拟信号或数字信号传递到对方，然后再把对方传递过来的信号还原成我们听得懂的语言。
-
-协议管理的变化会引起这个接口或实现类的变化吗？
-会的！
-那数据传送（电话不仅可以通话，还可以上网！）的变化会引起这个接口或实现类的变化吗？
-会的！
-
-这里有两个原因都引起了类变化。这两个职责会相互影响吗？
-- 电话拨号，我只要能接通就成，不管是电信的还是联通的协议
-- 电话连接后还关心传递的是什么数据吗？
-
-经过分析，我们发现类图上的IPhone接口包含了两个职责，而且这两个职责的变化不相互影响，那就考虑拆分成两个接口：
-- 职责分明的电话类图
-![](https://img-blog.csdnimg.cn/img_convert/a32b12f5a750fccb83178a41a30131d5.png)
-完全满足了单一职责原则要求，每个接口职责分明，结构清晰，但相信你在设计时肯定不会采用这种方式。一个 Phone类要把ConnectionManager和DataTransfer组合在一块才能使用。组合是一种强耦合关系，共同生命周期，这样强耦合不如使用接口实现，而且还增加了类的复杂性，多了俩类。
-
-那就再修改一下类图：
-- 简洁清晰、职责分明的电话类图
-![](https://img-blog.csdnimg.cn/img_convert/6440c34103fafd869b635156ddff80cf.png)
-
-一个类实现了两个接口，把两个职责融合在一个类中。
-你可能会说Phone有两个原因引起变化了呀！
-是的，但是别忘了我们是面向接口编程，我们对外公布的是接口而非实现类。而且，若真要实现类的单一职责，还就必须使用组合模式了，这会引起类间耦合过重、类的数量增加等问题，人为地增加了设计复杂性。
-
-## 好处
-- 类的复杂性降低，实现什么职责都有清晰明确的定义
-- 可读性提高，复杂性降低，那当然可读性提高了
-- 可维护性提高，可读性提高，那当然更容易维护了
-- 变更引起的风险降低，变更是必不可少的。若接口的单一职责做得好，一个接口修改只对相应的实现类有影响，对其他的接口无影响。这对系统的扩展性、维护性都有非常大帮助。
-
-单一职责原则最难划分的就是职责。
-一个职责一个接口，但问题是“职责”没有一个量化的标准，一个类到底要负责那些职责？这些职责该怎么细化？细化后是否都要有一个接口或类？
-这些都需要从实际的项目去考虑，从功能上来说，定义一个IPhone接口也没有错，实现了电话的功能，而且设计还很简单，仅仅一个接口一个实现类，实际的项目我想大家都会这么设计。项目要考虑可变因素和不可变因素，以及相关的收益成本比率，因此设计一个IPhone接口也可能是没有错的。
-
-但若纯从“学究”理论上分析就有问题了，有两个可以变化的原因放到了一个接口中，这就为以后的变化带来了风险。如果以后模拟电话升级到数字电话，我们提供的接口IPhone是不是要修改了？接口修改对其他的Invoker类是不是有很大影响？
-
-单一职责原则提出了一个编写程序的标准，用“职责”或“变化原因”来衡量接口或类设计得是否优良，但是“职责”和“变化原因”都是不可度量的，因项目而异，因环境而异。
-
-## 2.5 项目管理
-开发一个项目管理工具，可能设计如下用户类：
-![](https://img-blog.csdnimg.cn/ac02aaf76ba14b9ab65300e770cd750c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_15,color_FFFFFF,t_70,g_se,x_16)类设计得看着很合理，有用户信息管理、项目管理等。
-现在新需求规定每个用户能够设置电话号码，于是你新增方法：
-![](https://img-blog.csdnimg.cn/450501655028452b856e417b817f6657.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_19,color_FFFFFF,t_70,g_se,x_16)
-又来新需求：查看一个用户加入了多少项目：
-![](https://img-blog.csdnimg.cn/20a70e69316e47d7a8a671f8554b1524.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_19,color_FFFFFF,t_70,g_se,x_16)
-就这样，几乎每个和用户沾边的需求，你都改了user类，导致：
-- User类不断膨胀
-- 内部实现越来越复杂
-
-这个类变动的频繁程度显然不理想，在于它诱导变动的需求太多：
-- 为什么要增加电话号码？
-用户管理的需求。用户管理的需求还会有很多，比如，用户实名认证、用户组织归属等
-- 为什么要查看用户加入多少项目？
-项目管理的需求。项目管理的需求还会有很多，比如，团队管理、项目权限等。
-
-这是两种完全不同的需求，但你都改同一个类，所以，User类无法稳定。
-最好的方案是拆分不同需求引起的变动。
-对于用户管理、项目管理两种不同需求，完全可以把User拆成两个类：
-- 用户管理类需求放到User
-- 项目管理类的需求放到Member
-
-![](https://img-blog.csdnimg.cn/92686f9f4c774c909e7aa4227c44f0aa.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_15,color_FFFFFF,t_70,g_se,x_16)这样，用户管理的需求只需调整User类，项目管理的需求只需调整Member类，二者各自变动的理由就少了。
-
-### 变化的来源
-上面的做法类似分离关注点。
-
-要更好地理解单一职责原则，关键就是分离不同关注点。该案例分离的是不同的业务关注点。所以，理解单一职责原则奥义在于理解分离关注点。
-
-分离关注点，发现的关注点越多越好，粒度越小越好。你能看到的关注点越多，就能构建出更多的类，但每个类的规模相应越小，与之相关需求变动也越少，能稳定的几率就越大。
-代码库里稳定的类越多越好，这是我们努力的方向。
-
-> 如果将这种思路推演到极致，那一个类就应该只有一个方法，这样，它受到影响最小。
-
-的确如此，但实际项目，一个类通常都不只一个方法，要求所有人都做到极致，不现实。
-
-> 那应该把哪些内容组织到一起？
-
-这就需要考虑单一职责原则定义的升级版，即第二个定义：一个模块应该对一类且仅对一类行为者负责。
-
-若第一个定义将变化纳入考量，则升级版定义则将变化的来源纳入考量。
-
-> 需求为什么会改变？
-
-因为有各种人提需求，不同人提的需求关注点不同。
-关心用户管理和关心项目管理的可能是两种不同角色的人。两件不同的事，到了代码，却混在一起，这显然不合理。
-所以，分开才是一个好选择：
-- 用户管理的人，我和他们聊User
-- 项目管理的人，我们来讨论Member
-
-> 康威定律：一个组织设计出的系统，其结构受限于其组织的沟通结构。
-
-Robert Martin说，单一职责原则是基于康威定律的一个推论：一个软件系统的最佳结构高度依赖于使用这个软件的组织的内部结构。
-若我们的软件结构不能够与组织结构对应，就会带来一系列麻烦。
-
-实际上，当我们更新了对于单一职责原则的理解，你会发现，它的应用范围不仅可放在类这个级别，也可放到更大级别。
-
-某交易平台有个关键模型：手续费率，交易一次按xx比例收佣金。平台可以利用手续费率做不同的活动，比如，给一些人比较低的手续费率，鼓励他们来交易，不同的手续费率意味着对不同交易行为的鼓励。
-- 对运营人员
-手续费率是一个可以玩出花的东西
-- 对交易系统而言
-稳定高效是重点。显然，经常修改的手续费率和稳定的系统之间存在矛盾。
-
-分析发现，这是两类不同行为者。所以，设计时，把手续费率设置放到运营子系统，而交易子系统只负责读取手续费率:
-- 当运营子系统修改了手续费率，会把最新结果更新到交易子系统
-- 至于各种手续费率设置的花样，交易子系统根本无需关心
-
-单一职责原则还能指导我们在不同的子系统之间进行职责分配。所以，单一职责原则这个看起来最简单的原则，实际上也蕴含着很多值得挖掘的内容。
-要想理解好单一职责原则：
-- 需要理解封装，知道要把什么样的内容放到一起
-- 理解分离关注点，知道要把不同的内容拆分开来
-- 理解变化的来源，知道把不同行为者负责的代码放到不同的地方。
-
-
-你就可以更好地理解函数要小的含义了，每个函数承担的职责要单一，这样，它才能稳定。
-
-# 4 单一且快乐
-对于：
-- 接口，设计时一定要单一
-- 但对于实现类就需要多方面考虑
-
-生搬硬套单一职责原则会引起类的剧增，给维护带来非常多的麻烦，而且过分细分类的职责也会人为地增加系统的复杂性。本来一个类可以实现的行为硬要拆成两个类，然后再使用聚合或组合的方式耦合在一起，人为制造了系统的复杂性。所以原则是死的，人是活的。
-
-## 单一职责原则很难体现在项目
-国内的技术人员地位和话语权都是最低的，在项目中需要考虑环境、工作量、人员的技术水平、硬件的资源情况等，最终妥协经常违背单一职责原则。
-
-单一职责适用于接口、类，同时也适用于方法。一个方法尽可能做一件事情，比如一个方法修改用户密码，不要把这个方法放到“修改用户信息”方法中，这个方法的颗粒度很粗.
-
-- 一个方法承担多个职责
-![](https://img-blog.csdnimg.cn/img_convert/5cebbf619d200508262e3ddd37e4251e.png)
-
-在IUserManager中定义了一个方法changeUser，根据传递的类型不同，把可变长度参数changeOptions修改到userBO这个对象上，并调用持久层的方法保存到数据库中。
-
-这种代码看到，直接要求其重写即可：方法职责不清晰，不单一，不要让别人猜测这个方法可能是用来处理什么逻辑的。
-
-比较好的设计如下：
-- 一个方法承担一个职责
-![](https://img-blog.csdnimg.cn/img_convert/21d3889d0f1a23a242e5a24cc464dea5.png)
-若要修改用户名称，就调用changeUserName方法
-要修改家庭地址，就调用changeHomeAddress方法
-要修改单位电话，就调用changeOfficeTel方法
-每个方法的职责非常清晰明确，不仅开发简单，而且日后的维护也非常容易。
-# 5 最佳实践
-类的单一职责确实受非常多因素的制约，纯理论地来讲，这个原则很好，但现实有很多难处，你必须考虑项目工期、成本、人员技术水平、硬件情况、网络情况甚至有时候还要考虑政府政策、垄断协议等因素。
-
-对于单一职责原则，推荐：
-- 接口一定要做到单一职责
-- 类的设计尽量做到只有一个原因引起变化
-
-> 参考
-> - 《设计模式之蝉》
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(2)-\345\274\200\351\227\255\345\216\237\345\210\231.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(2)-\345\274\200\351\227\255\345\216\237\345\210\231.md"
deleted file mode 100644
index f0e9f5fe36..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(2)-\345\274\200\351\227\255\345\216\237\345\210\231.md"
+++ /dev/null
@@ -1,201 +0,0 @@
-# 1 定义
-作为程序员，来一个需求就改一次代码，似乎已经习惯这种节奏，甚至理所当然。反正修改也容易，只要按之前的代码再抄一段即可。
-
-之所以很多人这样，是因为这样做不费脑子。但每人每次改一点点，日积月累，再来个新需求，后人的改动量就很大了。这个过程中，每个人都很无辜，因为每个人都只是循规蹈矩地修改一点点。但最终导致伤害了所有人，代码已经无法维护。
-
-既然“修改”会带来这么多问题，那可以不修改吗？
-开放封闭原则就是一种值得努力的方向。
-
-`Software entities like classes,modules and functions should be open for extension but closed for modifications`
-一个软件实体如类、模块和方法应该对扩展开放，对修改关闭。
-
-这是Bertrand Meyer在其著作《面向对象软件构造》（Object-Oriented Software Construction）中提出，它给软件设计提出了一个极高要求：不修改代码。
-真让人魔怔。对扩展开放？开放什么？对修改关闭，怎么关闭？
-![](https://img-blog.csdnimg.cn/20210531162533878.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-不修改代码，那我怎么实现新需求？
-扩展，即新需求用新代码实现。
-
-开放封闭原则向我们描述的是个结果：可不修改代码而仅靠扩展就完成新功能。
-这个结果的前提是要在软件内部留好扩展点，这就是需要设计的地方。
-每一个扩展点都是一个需设计的模型。
-
-### 用抽象构建框架，用实现扩展细节
-一个软件实体应该通过扩展来实现变化，而不是通过修改已有代码来实现变化。它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。
-# 2 案例
-##  2.1 书籍
-书籍接口
-![](https://img-blog.csdnimg.cn/20210531170731356.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-Java书籍实现类
-![](https://img-blog.csdnimg.cn/20210531170902658.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-测试类
-![](https://img-blog.csdnimg.cn/20210531170942849.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-现在想添加一个折扣优惠方法：若直接修改原接口，则每个实现类都得重新添加方法实现。
-
-但接口应该是稳定的，不应频繁修改！
-
-Java 书籍折扣类
-![](https://img-blog.csdnimg.cn/20210531171331822.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-现在 UML
-![](https://img-blog.csdnimg.cn/20210531171412248.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)
-
-`接口应该是稳定且可靠的，不应该经常发生变化`，否则接口作为契约的作用就失去了效能。
-### 修改实现类
-直接在getPrice()中实现打折处理，大家应该经常这样，通过class文件替换的方式即可完成部分业务变化（或缺陷修复）。
-
-该方法在项目有明确的章程（团队内约束）或优良的架构设计时，是一个非常优秀的方法，但是该方法还是有缺陷：例如采购书籍人员也要看价格，由于该方法已实现打折处理价格，因此采购人员看到的也是折后价，会`因信息不对称而出现决策失误`的情况。因此，这也不是最优方案。
-### 通过扩展实现变化
-`增加一个子类`OffNovelBook，覆写getPrice方法，高层次的模块（static静态模块区）通过OffNovelBook类产生新的对象，完成业务变化对系统的最小化开发。好办法，修改也少，风险也小。
-
-开闭原则对扩展开放，对修改关闭，但并不意味着不做任何修改，低层模块的变更，必然要有高层模块进行耦合，否则就是一个孤立无意义的代码片段。
-
-# 变化的类型
-## 逻辑变化
-只变化一个逻辑，不涉及其它模块。比如原有的一个算法是`a*b+c`，现在需要修改为`a*b*c`，可以通过修改原有类中的方法完成，前提条件是所有依赖或关联类都按照相同的逻辑处理。
-## 子模块变化
-一个模块变化，会对其他的模块产生影响，特别是一个低层次的模块变化必然引起高层模块的变化，因此在通过扩展完成变化时，高层次的模块修改是必然的。
-## 可见视图变化
-可见视图是提供给客户使用的界面，如Swing。若仅是按钮、文字的重新排布倒是简单，最司空见惯的是业务耦合变化，什么意思呢？一个展示数据的列表，按照原有的需求是6列，突然有一天要增加1列，而且这一列要跨N张表，处理M个逻辑才能展现出来，这样的变化是比较恐怖的，但还是可以通过扩展来完成变化。
-
-所以放弃修改历史的想法吧，一个项目的基本路径：项目开发、重构、测试、投产、运维。
-其中的重构可对原有设计和代码进行修改，运维尽量减少对原有代码的修改，保持历史代码的纯洁性，提高系统的稳定性。
-## 会员案例
-开发一个酒店预订系统，针对不同的用户，计算出不同房价。
-比如：
-- 普通用户是全价
-- 金卡是8折
-- 银卡是9折
-
-代码可能如下：
-![](https://img-blog.csdnimg.cn/d74b8a7ac93f45a0b259be02bffe8c75.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)来新需求，增加白金卡会员，给出75折，一脉相承写法：
-![](https://img-blog.csdnimg.cn/e35d591b809f47bdbc8788bd6766e102.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)这就是修改代码的做法，每增加一个新类型，就修改次代码。
-但一个有各种级别用户的酒店系统肯定不只是房价不同，提供的服务也可能有区别，比如是否有早餐？预付还是现付？优惠券力度、连住优惠价格？。可以预见，每增加一个用户级别，要改的代码散布各地。
-
-> 该怎么办呢？
-
-应该考虑如何把它设计成一个可扩展模型。
-既然每次要增加的是用户级别，而且各种服务差异都体现在用户级别，就需要一个用户级别模型。
-前面代码，用户级别只是个简单枚举，丰富一下：
-![](https://img-blog.csdnimg.cn/8f1f220ec9114992a391bea76e2368bf.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_18,color_FFFFFF,t_70,g_se,x_16)原代码即可重构成：
-![](https://img-blog.csdnimg.cn/7e59471ba0924cccbd4fac2503eb4f7e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)这样一来，再增加铂金用户，只需新写一个类：
-![](https://img-blog.csdnimg.cn/34ec979369794553827bfe978fb70bc2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_18,color_FFFFFF,t_70,g_se,x_16)
-可以这么做，是因为在代码里留好了扩展点：UserLevel：把原来只支持枚举值的UserLevel，升级成了一个有行为的UserLevel。
-
-改造后，HotelService的getRoomPrice方法就稳定了，无需根据用户级别不断地调整。
-一旦有稳定的构造块，就能在后续将其当做一个稳定模块复用。
-# 构建扩展点
-其实我们修改代码效果不佳，但真到自己写代码了，就晕了。
-若问你，你开发的系统有问题吗？相信大部人都会不假思索地说有。
-但又问：你会经常主动优化它吗？大部人却又开始沉默了。
-它虽然垃圾，但在线上运行得好好的，万一我一优化，优化坏了咋办，今年绩效可就 3.25 了呀。
-你看，现实就是这样 ，系统宏观层面人人都懂，而在代码落地层，却总是习惯忽视。
-
-所以，写软件系统，就应该提供一个个稳定小模块，然后，将它们组合。一个经常变动的模块是不稳定的，用它去构造更大模块，必后患无穷。
-
-> 为什么我们这一懂了很多大道理，却依旧写不好代码呢？
-
-阻碍我们构造稳定模块的，是构建模型的能力。回想产生变化的UserLevel，是如何升级成一个有行为的UserLevel的。
-
-封装的要点是行为，数据只是实现细节，而很多人习惯性面向数据写法，这也是导致很多人设计缺乏扩展性。
-
-构建模型的难点：
-1. 分离关注点
-2. 找到共性
-
-**要构建起抽象就要找到事物的共同点**，业务处理过程发现共性对大部分人就已经开始有难度了。
-
-我们再来看个例子
-## 报表服务
-![](https://img-blog.csdnimg.cn/f2d1fb410ecb49ed8b2d30cf24a89d7c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-相信很多日常写代码就是这种风格，这个代码流程肯定是特别僵化。
-只要出现新需求，基本都需要调整这段。
-现在就来了个新需求：把统计信息发给另外一个内部系统，该内部系统可将统计信息展示出来，供外部合作伙伴查阅。
-### 分析
-发给另一个系统的内容是**统计信息**。
-原代码里：
-- 前两步是获取源数据，生成**统计信息**
-- 后两步生成报表，将**统计信息**通过邮件发出去
-
-后两步和即将添加的步骤有个共同点，都使用了统计信息。所以，就可以用一个共同模型去涵盖它们，如OrderStatisticsConsumer：
-![](https://img-blog.csdnimg.cn/927eade9b0d74d9392b5bb987100577a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-这样，新需求也只需添加一个新类，而非 if/else：
-![](https://img-blog.csdnimg.cn/07d88e8615564fb2974ea0631b48b087.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-该案例中，第一步要做的还是分解：把一个个步骤分开，然后找出步骤间相似点，并构建一个新模型。
-
-实际项目代码可能比这复杂，但并非一定是业务逻辑复杂，而是代码写得就复杂。
-所以，要先根据单一职责原则，将不同需求来源引起的变动拆分到不同方法，形成一个个小单元，再做这里的分析。
-
-实际项目中，要达到开放封闭原则也并非一朝一夕。这里只是因为有需求变动，才提取出一个OrderStatisticsConsumer。
-
-未来可能还有其它变动，如生成报表的逻辑。那时，也许还会提取一个新OrderStatisticsGenerator的接口。但不管怎样，每做一次这种模型构建，最核心的类就会朝着稳定发展。
-
-好的设计都会提供足够扩展点给新功能去扩展。
-《Unix 编程艺术》就提倡“提供机制，而非策略”，这就体现了开放封闭原则。
-
-很多系统有插件机制，如IDEA和VS Code，都体现开放封闭原则。去了解它们的接口，即可看到这个软件给我们提供的各种能力。
-
-开放封闭原则还可帮助我们优化系统，可通过查看Git，找出那些最经常变动的文件，它们通常都没有满足开放封闭原则，这就可以成为你系统优化的起航点。
-# 为什么选择开闭原则
-## 开闭原则对测试的影响
-有变化提出时，我们就需要考虑一下，原有的健壮代码是否可以不修改，仅仅通过扩展实现变化呢？
-否则，就需要把原有的测试过程回笼一遍，需要进行单元测试、功能测试、集成测试甚至是验收测试。
-
-以上面提到的书店售书为例，IBook接口写完了，实现类NovelBook也写好了，我们需要写一个测试类进行测试，测试类如代码
-```java
-public class NovelBookTest extends TestCase {
-     private String name = "平凡的世界";
-     private int price = 6000;
-     private String author = "路遥";      
-     private IBook novelBook = new NovelBook(name,price,author);
-     
-     // 测试getPrice方法
-     public void testGetPrice() {
-             //原价销售，根据输入和输出的值是否相等进行断言
-             super.assertEquals(this.price, this.novelBook.getPrice());
-     }
-}
-```
-若加个打折销售需求，直接修改getPrice，那就要修改单元测试类。而且在实际项目中，一个类一般只有一个测试类，其中可以有很多的测试方法，在一堆本来就很复杂的断言中进行大量修改，难免出现测试遗漏。
-
-所以，需要通过扩展实现业务逻辑变化，而非修改。可通过增加一个子类OffNovelBook完成业务需求变化，这对测试有什么好处呢？
-重新生成一个测试文件OffNovelBookTest，然后对getPrice进行测试，单元测试是孤立测试，只要保证我提供的方法正确就成，其他的不管。
-```java
-public class OffNovelBookTest extends TestCase {   
-     private IBook below40NovelBook = new OffNovelBook("平凡的世界",3000,"路遥");
-     private IBook above40NovelBook = new OffNovelBook("平凡的世界",6000,"路遥");
-      
-     // 测试低于40元的数据是否是打8折
-     public void testGetPriceBelow40() {
-             super.assertEquals(2400, this.below40NovelBook.getPrice());
-     }
-     
-     // 测试大于40的书籍是否是打9折
-     public void testGetPriceAbove40(){
-             super.assertEquals(5400, this.above40NovelBook.getPrice());
-     }
-}
-```
-新增加的类，新增加的测试方法，只要保证新增加类是正确的就可以了。
-## 提高复用性
-OOP中，所有逻辑都是从原子逻辑组合而来，而非在一个类中独立实现一个业务逻辑。只有这样代码才可复用，粒度越小，被复用可能性越大。
-- 为什么要复用？
-减少代码量，避免相同逻辑分散，避免后来的维护人员为修改一个小bug或加个新功能而在整个项目中到处查找相关代码，然后发出对开发人员“极度失望”的感慨。
-- 如何才能提高复用率？
-缩小逻辑粒度，直到一个逻辑不可再拆分为止。
-## 提高可维护性
-一款软件投产后，维护人员的工作不仅仅是对数据进行维护，还可能要对程序进行扩展，维护人员最乐意做的事情就是扩展一个类，而非修改一个类，甭管原有代码写得好坏，让维护人员读懂原有代码，然后再修改，是炼狱！不要让他在原有代码海洋里瞎游完毕后再修改，那是对维护人员的摧残。
-
-## OOP
-万物皆对象，我们需要把所有的事物都抽象成对象，然后针对对象进行操作，但运动是一定的，有运动就有变化，有变化就要有策略去应对，怎么快速应对呢？这就需要在设计之初考虑到所有可能变化的因素，然后留下接口，等待“可能”转为“现实”。
-
-- 优点
-提高软件系统的可复用性及可维护性
-# 总结
-若说单一职责原则主要看封装，开放封闭原则就必须有多态参与。
-要想提供扩展点，就需面向接口编程。
-
-java的SPI给开发者提供了不错的扩展机制，像spring boot 和dubbo就在此基础上做了改进，各自提供了扩展点，spring boot允许用户自定义starter,dubbo可以自定义协议等
-
-1、识别修改点，构建模型，将原来静态的逻辑转为动态的逻辑
-2、构建模型的难点在于分离关注点，其次就是找到共性
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(3)-\344\276\235\350\265\226\345\200\222\347\275\256\345\216\237\345\210\231.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(3)-\344\276\235\350\265\226\345\200\222\347\275\256\345\216\237\345\210\231.md"
deleted file mode 100644
index da9d6780d1..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(3)-\344\276\235\350\265\226\345\200\222\347\275\256\345\216\237\345\210\231.md"
+++ /dev/null
@@ -1,259 +0,0 @@
-# 1 定义
-Dependence Inversion Principle，DIP
-High level modules should not depend upon low level modules.Both should depend upon abstractions.高层模块不应该依赖低层模块，二者都应该依赖其抽象
-Abstractions should not depend upon details.Details should depend upon abstractions.抽象不应该依赖细节；细节应该依赖抽象
-
-针对接口编程，不要针对实现编程。
-
-每个逻辑的实现都是由原子逻辑组成的，不可分割的原子逻辑就是低层模块，原子逻辑的再组装就是高层模块
-在Java语言中，抽象就是指接口或抽象类，两者都是不能直接被实例化的
-细节就是实现类，实现接口或继承抽象类而产生的类就是细节，其特点就是可以直接被实例化，也就是可以加上一个关键字new产生一个对象。
-依赖倒置原则在Java语言中的表现就是：
-● 模块间的依赖通过抽象发生，实现类之间不发生直接的依赖关系，其依赖关系是通过接口或抽象类产生的；
-● 接口或抽象类不依赖于实现类；
-● 实现类依赖接口或抽象类。
-
-优点：可以减少类间的耦合性、提高系统稳定性，提高代码可读性和可维护性，可降低修改程序所造成的风险。
-
-能不能把依赖弄对，要动点脑。依赖关系没处理好，就会导致一个小改动影响一大片。
-把依赖方向搞反，就是最典型错误。
-
-最重要的是要理解“倒置”，而要理解什么是“倒置”，就要先理解所谓的“正常依赖”是什么样的。
-结构化编程思路是自上而下功能分解，这思路很自然地就会延续到很多人的编程习惯。按照分解结果，进行组合。所以，很自然地写出下面这种代码
-![](https://img-blog.csdnimg.cn/72ee743f28ed4577b3ac72117dd6528b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_9,color_FFFFFF,t_70,g_se,x_16)
-这种结构天然的问题：高层模块依赖于低层模块：
-- CriticalFeature是高层类
-- Step1和Step2就是低层模块，而且Step1和Step2通常都是具体类
-
->  很多人好奇了：step1和step2如果是接口，还有问题吗？像这种流程式的代码还挺常见的?
-> 有问题，你无法确定真的是Step1和Step2，还会不会有Step3，所以这个设计依旧是不好的。如果你的设计是多个Step，这也许是个更好的设计。
-
-在实际项目中，代码经常会直接耦合在具体的实现上。比如，我们用Kafka做消息传递，就在代码里直接创建了一个KafkaProducer去发送消息。我们就可能会写出这样的代码：
-![](https://img-blog.csdnimg.cn/5706917b48204378aa6a4c0a6d3c8794.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-我用Kafka发消息，创建个KafkaProducer，有什么问题吗？
-我们需要站在长期角度去看，什么东西是变的、什么东西是不变的。Kafka虽然很好，但它并不是系统最核心部分，未来是可能被换掉的。
-
-你可能想，这可是我的关键组件，怎么可能会换掉它？
-软件设计需要关注长期、放眼长期，所有那些不在自己掌控之内的东西，都有可能被替换。替换一个中间件是经常发生的。所以，依赖于一个可能会变的东西，从设计的角度看，并不是一个好的做法。
-
-> 那该怎么做呢？这就轮到倒置了。
-
-倒置，就是把这种习惯性的做法倒过来，让高层模块不再依赖低层模块。
-功能该如何完成？
-
-> 计算机科学中的所有问题都可以通过引入一个间接层得到解决。 All problems in computer science can be
-> solved by another level of indirection —— David Wheeler
-
-引入一个间接层，即DIP里的抽象，软件设计中也叫模型。这段代码缺少了一个模型，而这个模型就是这个低层模块在这个过程中所承担角色。
-
-# 2 实战
-## 重构
-既然这个模块扮演的就是消息发送者的角色，那我们就可以引入一个消息发送者（MessageSender）的模型：
-![](https://img-blog.csdnimg.cn/ec275a7b4a5845d38ad27c4a361f43d9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_12,color_FFFFFF,t_70,g_se,x_16)
-有了消息发送者这个模型，又该如何把Kafka和这个模型结合呢？
-实现一个Kafka的消息发送者：
-![](https://img-blog.csdnimg.cn/b214d3381da04d22b4344ab9fece5322.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-这样高层模块就不像之前直接依赖低层模块，而是将依赖关系“倒置”，让低层模块去依赖由高层定义好的接口。
-好处就是解耦高层模块和低层实现。
-
-若日后替换Kafka，只需重写一个MessageSender，其他部分无需修改。这就能让高层模块保持稳定，不会随低层代码改变。
-
-这就是建立模型（抽象）的意义。
-
-所有软件设计原则都在强调尽可能分离变的部分和不变的部分，让不变的部分保持稳定。
-模型都是相对稳定的，而实现细节是易变的。所以，构建稳定的模型层是关键。
-# 依赖于抽象
-**抽象不应依赖于细节，细节应依赖于抽象。**
-
-简单理解：依赖于抽象，可推出具体编码指导：
-- 任何变量都不应该指向一个具体类
-如最常用的List声明![](https://img-blog.csdnimg.cn/cde6a7acfa034a779fbdd6bbf3ebf365.png)
-
-- 任何类都不应继承自具体类
-- 任何方法都不应该改写父类中已经实现的方法
-
-当然了，如上指导并非绝对。若一个类特稳定，也可直接用，比如String 类，但这种情况很少！
-因为大多数人写的代码稳定度都没人家 String 类设计的高。
-
-
-## 学习
-首先定义一个学习者类
-![](https://img-blog.csdnimg.cn/9d5549c4022d49a689a9855d75a31401.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-简单的测试类
-![](https://img-blog.csdnimg.cn/99e7fb4d2dab4263a0877222b926b9e5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-假如现在又想学习Python，则面向实现编程就是直接在类添加方法
-![](https://img-blog.csdnimg.cn/24ebf1765efe4a78b6dc5a7c9ed60caf.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-此类需经常改变，扩展性太差，Test 高层模块, Learner 类为低层模块，耦合度过高！
-
-让我们引入抽象：
-![](https://img-blog.csdnimg.cn/573f54c53f1443b58a36fafef11d79c0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/5b6d180123974a23bf3971391b8197b3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/4633628a03c941cb839fe5dfa23e19bb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/fe10ce1aea6e414785543a2a2529b87a.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-现在就能将原学习者类的方法都消除
-![](https://img-blog.csdnimg.cn/8c0e7b8aa3d14d04baef534ba6813c09.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-![](https://img-blog.csdnimg.cn/e7b06faa36834adfb7029e1a682e5f1b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-# 3 证明
-采用依赖倒置原则可减少类间耦合，提高系统稳定性，降低并行开发引起的风险，提高代码的可读性和可维护性。
-
-证明一个定理是否正确，有两种常用方法：
-- 顺推证法
-根据论题，经过论证，推出和定理相同结论
-- 反证法
-先假设提出的命题是伪命题，然后推导出一个与已知条件互斥结论
-
-反证法来证明依赖倒置原则的优秀！
-## 论题
-依赖倒置原则可减少类间的耦合性，提高系统稳定性，降低并行开发引起的风险，提高代码的可读性和可维护性。
-## 反论题
-不使用依赖倒置原则也可减少类间的耦合性，提高系统稳定性，降低并行开发引起的风险，提高代码的可读性和可维护性。
-
-通过一个例子来说明反论题不成立！
-- 司机驾驶奔驰车类图
-![](https://img-blog.csdnimg.cn/img_convert/d5411d44da28d7d9af4889615357be2c.png)
-
-奔驰车可提供一个方法run，代表车辆运行：
-![](https://img-blog.csdnimg.cn/1ecd458d153b4184a32045e088acd7d1.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_19,color_FFFFFF,t_70,g_se,x_16)
-
-司机通过调用奔驰车的run方法开动奔驰车
-![](https://img-blog.csdnimg.cn/4ab78125c7e44ed596512f1ff7582fdd.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-有车，有司机，在Client场景类产生相应的对象
-![](https://img-blog.csdnimg.cn/13b5f3140c76418fad3772cbe2a93849.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-现在来了新需求：张三司机不仅要开奔驰车，还要开宝马车，又该怎么实现呢？
-走一步是一步，先把宝马车产生出来
-![](https://img-blog.csdnimg.cn/eb47654721bc4f7faf2cfa8a60150fd8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-宝马车产生了，但却没有办法让张三开起来，为什么？
-张三没有开动宝马车的方法呀！一个拿有C驾照的司机竟然只能开奔驰车而不能开宝马车，这也太不合理了！在现实世界都不允许存在这种情况，何况程序还是对现实世界的抽象。
-
-我们的设计出现了问题：`司机类和奔驰车类紧耦合，导致系统可维护性大大降低，可读性降低`。
-两个相似的类需要阅读两个文件，你乐意吗？还有稳定性，什么是稳定性？固化的、健壮的才是稳定的，这里只是增加了一个车类就需要修改司机类，这不是稳定性，这是易变性。
-`被依赖者的变更竟然让依赖者来承担修改成本，这样的依赖关系谁肯承担？`
-证明至此，反论题已经部分不成立了。
-
-继续证明，`“减少并行开发引起的风险”`
-
-> 什么是并行开发的风险？
-
-并行开发最大的风险就是风险扩散，本来只是一段程序的异常，逐步波及一个功能甚至模块到整个项目。一个团队，一二十个开发人员，各人负责不同功能模块，甲负责汽车类的建造，乙负责司机类的建造，在甲没有完成的情况下，乙是不能完全地编写代码的，缺少汽车类，编译器根本就不会让你通过！在缺少Benz类的情况下，Driver类能编译吗？更不要说是单元测试了！在这种不使用依赖倒置原则的环境中，所有开发工作都是“单线程”，甲做完，乙再做，然后是丙继续……这在20世纪90年代“个人英雄主义”编程模式中还是比较适用的，一个人完成所有的代码工作。但在现在的大中型项目中已经是完全不能胜任了，一个项目是一个团队协作的结果，一个“英雄”再牛也不可能了解所有的业务和所有的技术，要协作就要并行开发，要并行开发就要解决模块之间的项目依赖关系，那然后呢？
-依赖倒置原则隆重出场！
-
-根据以上证明，若不使用依赖倒置原则就会加重类间的耦合性，降低系统的稳定性，增加并行开发引起的风险，降低代码的可读性和可维护性。
-
-引入DIP后的UML：
-![](https://img-blog.csdnimg.cn/aba59e6524fb48f5bd2e69d4c8c63378.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-建立两个接口：IDriver和ICar，分别定义了司机和汽车的各个职能，司机就是驾驶汽车，必须实现drive()方法
-![](https://img-blog.csdnimg.cn/e69064246bd14ff0be1008ea94e3a2f2.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-接口只是一个抽象化的概念，是对一类事物的最抽象描述，具体的实现代码由相应的实现类来完成
-![](https://img-blog.csdnimg.cn/d15ce9e76e494223aa5be2c8b5fb567d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-IDriver通过传入ICar接口实现了抽象之间的依赖关系，Driver实现类也传入了ICar接口，至于到底是哪个型号的Car，需要声明在高层模块。
-
-ICar及其两个实现类的实现过程：
-![](https://img-blog.csdnimg.cn/58f0b6e79d204818833e484cf85075f8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-业务场景应贯彻“抽象不应依赖细节”，即抽象（ICar接口）不依赖BMW和Benz两个实现类（细节），因此在高层次的模块中应用都是抽象：
-![](https://img-blog.csdnimg.cn/0ffe565e8168413297541c14fe734702.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-Client属于高层业务逻辑，它对低层模块的依赖都建立在抽象上，java的表面类型是IDriver，Benz的表面类型是ICar。
-
-> 在这个高层模块中也调用到了低层模块，比如new Driver()和new Benz()等，如何解释？
-
-java的表面类型是IDriver，是一个接口，是抽象的、非实体化的，在其后的所有操作中，java都是以IDriver类型进行操作，屏蔽了细节对抽象的影响。当然，java如果要开宝马车，也很容易，只需修改业务场景类即可。
-
-在新增加低层模块时，只修改了业务场景类，也就是高层模块，对其他低层模块如Driver类不需要做任何修改，业务就可以运行，把“变更”引起的风险扩散降到最低。
-
-Java只要定义变量就必然要有类型，一个变量可以有两种类型：
-- 表面类型
-在定义的时候赋予的类型
-- 实际类型
-对象的类型，如java的表面类型是IDriver，实际类型是Driver。
-
-思考依赖倒置对并行开发的影响。两个类之间有依赖关系，只要制定出两者之间的接口（或抽象类）即可独立开发，而且项目之间的单元测试也可以独立地运行，而TDD（Test-Driven Development，测试驱动开发）开发模式就是依赖倒置原则的最高级应用。
-
-回顾司机驾驶汽车的例子：
-- 甲程序员负责IDriver开发
-- 乙程序员负责ICar的开发
-
-两个开发人员只要制定好了接口就可以独立地开发了，甲开发进度比较快，完成了IDriver以及相关的实现类Driver的开发工作，而乙程序员滞后开发，那甲是否可以进行单元测试呢？
-根据抽象虚拟一个对象进行测试：
-![](https://img-blog.csdnimg.cn/974e7b67697144f4842d5d608666d8fb.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-只需要一个ICar接口，即可对Driver类进行单元测试。
-这点来看，两个相互依赖的对象可分别进行开发，各自独立进行单元测试，保证了并行开发的效率和质量，TDD开发的精髓不就在这？
-TDD，先写好单元测试类，然后再写实现类，这对提高代码的质量有非常大的帮助，特别适合研发类项目或在项目成员整体水平较低情况下采用。
-
-抽象是对实现的约束，对依赖者而言，也是一种契约，不仅仅约束自己，还同时约束自己与外部的关系，其为保证所有细节不脱离契约范畴，确保约束双方按既定契约（抽象）共同发展，只要抽象这根基线在，细节就脱离不了这个圈圈。
-# 4 依赖
-依赖可以传递，A对象依赖B对象，B又依赖C，C又依赖D……
-`只要做到抽象依赖，即使是多层的依赖传递也无所畏惧！`
-
-对象的依赖关系有如下传递方式：
-## 构造器传递
-在类中通过构造器声明依赖对象，`构造函数注入`
-![](https://img-blog.csdnimg.cn/77920db0598846fb9914a1bb254e0b19.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_17,color_FFFFFF,t_70,g_se,x_16)
-## 4.2 Setter传递
-在抽象中设置Setter方法声明依赖关系,`Setter依赖注入`
-![](https://img-blog.csdnimg.cn/556f0648b1cf4d39ba350a61468efc36.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_18,color_FFFFFF,t_70,g_se,x_16)
-## 4.3 接口声明依赖对象
-在接口的方法中声明依赖对象
-# 5  最佳实践
-依赖倒置原则的本质就是`通过抽象（接口或抽象类）使各个类或模块的实现彼此独立，不互相影响，实现模块间的松耦合`。
-
-DIP指导下，具体类能少用就少用。
-具体类我们还是要用的，毕竟代码要运行起来不能只依赖于接口。那具体类应该在哪用？
-这些设计原则，核心关注点都是一个个业务模型。此外，还有一些代码做的工作是负责组装这些模型，这些负责组装的代码就需要用到一个个具体类。
-
-做这些组装工作的就是DI容器。因为这些组装几乎是标准化且繁琐。如果你常用的语言中，没有提供DI容器，最好还是把负责组装的代码和业务模型放到不同代码。
-
-DI容器另外一个说法叫IoC容器，Inversion of Control，你会看到IoC和DIP中的I都是inversion，二者意图是一致的。
-
-依赖之所以可注入，是因为我们的设计遵循 DIP。而只知道DI容器不了解DIP，时常会出现让你觉得很为难的模型组装，根因在于设计没做好。
-
-DIP还称为好莱坞规则：“Don’t call us, we’ll call you”，“别调用我，我会调你的”。这是框架才会有的说法，有了一个稳定抽象，各种具体实现都应由框架去调用。
-
-为什么说一开始TransactionRequest是把依赖方向搞反了？因为最初的TransactionRequest是一个具体类，而TransactionHandler是业务类。
-
-我们后来改进的版本里引入一个模型，把TransactionRequest变成了接口，ActualTransactionRequest 实现这个接口，TransactionHandler只依赖于接口，而原来的具体类从这个接口继承而来，相对来说，比原来的版本好一些。
-
-对于任何一个项目而言，了解不同模块的依赖关系是一件很重要的事。你可以去找一些工具去生成项目的依赖关系图，然后，你就可以用DIP作为一个评判标准，去衡量一下你的项目在依赖关系上表现得到底怎么样了。很有可能，你就找到了项目改造的一些着力点。
-
-理解了 DIP，再来看一些关于依赖的讨论，我们也可以看到不同的角度。
-比如，循环依赖，循环依赖就是设计没做好的结果，把依赖关系弄错，才可能循环依赖，先把设计做对，把该有的接口提出来，就不会循环了。
-
-我们怎么在项目中使用这个规则呢？只要遵循以下的几个规则就可以：
-- 每个类尽量都有接口或抽象类，或者抽象类和接口两者都具备
-这是依赖倒置的基本要求，接口和抽象类都是属于抽象的，有了抽象才可能依赖倒置
-
-- 变量的表面类型尽量是接口或者是抽象类
-很多书上说变量的类型一定要是接口或者是抽象类，这个有点绝对化了
-    - 比如一个工具类，xxxUtils一般是不需要接口或是抽象类的
-    - 如果你要使用类的clone方法，就必须使用实现类，这个是JDK提供的一个规范。
-
-- 任何类都不应该从具体类派生
-如果一个项目处于开发状态，确实不应该有从具体类派生出子类的情况，但这也不是绝对的，因为人都是会犯错误的，有时设计缺陷是在所难免的，因此只要不超过两层的继承都是可以忍受的
-
-- 尽量不要覆写基类方法
-如果基类是一个抽象类，而且这个方法已经实现了，子类尽量不要覆写
-类间依赖的是抽象，覆写了抽象方法，对依赖的稳定性会产生一定的影响
-
-- 结合里氏替换原则使用
-父类出现的地方子类就能出现， 接口负责定义public属性和方法，并且声明与其他对象的依赖关系，抽象类负责公共构造部分的实现，实现类准确的实现业务逻辑，同时在适当的时候对父类进行细化。
-
-## 到底什么是“倒置”
-依赖正置就是类间的依赖是实实在在的实现类间的依赖，也就是面向实现编程，这也是正常人的思维方式，我要开奔驰车就依赖奔驰车，我要使用笔记本电脑就直接依赖笔记本电脑。
-而编写程序需要的是对现实世界的事物进行抽象，抽象的结果就是有了抽象类和接口，然后我们根据系统设计的需要产生了抽象间的依赖，代替了人们传统思维中的事物间的依赖，“倒置”就是从这里产生的
-
-
-依赖倒置原则是实现开闭原则的重要途径，依赖倒置原则没有实现，就别想实现对扩展开放，对修改关闭。
-只要记住`面向接口编程`就基本上抓住了依赖倒置原则的核心。
-![](https://img-blog.csdnimg.cn/037c5d536a184ef8b99df4f69ea9aed9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-依赖倒置原则说的是：
-1.高层模块不应依赖于低层模块，二者都应依赖于抽象
-2.抽象不应依赖于细节，细节应依赖于抽象
-总结起来就是依赖抽象（模型），具体实现抽象接口，然后把模型代码和组装代码分开，这样的设计就是分离关注点，将不变的与不变有效的区分开
-
-防腐层可以解耦对外部系统的依赖。包括接口和参数。防腐层还可以贯彻接口隔离的思想，以及做一些功能增强(加缓存,异步并发取值)。
-
-> 参考
-> - 设计模式之婵
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(4)-\346\216\245\345\217\243\351\232\224\347\246\273\345\216\237\345\210\231.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(4)-\346\216\245\345\217\243\351\232\224\347\246\273\345\216\237\345\210\231.md"
deleted file mode 100644
index adc8d73f90..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(4)-\346\216\245\345\217\243\351\232\224\347\246\273\345\216\237\345\210\231.md"
+++ /dev/null
@@ -1,7 +0,0 @@
-接口隔离原则（英语：interface-segregation principles， 缩写：ISP）指明客户（client）不应被迫使用对其而言无用的方法或功能。
-
-接口隔离原则（ISP）拆分非常庞大臃肿的接口成为更小的和更具体的接口，这样客户将会只需要知道他们感兴趣的方法。这种缩小的接口也被称为角色接口（role interfaces）。
-
-接口隔离原则（ISP）的目的是系统解开耦合，从而容易重构，更改和重新部署。接口隔离原则是在SOLID中五个面向对象设计（OOD）的原则之一，类似于在GRASP中的高内聚性。
-
-在面向对象设计中，接口（interface）提供了便于代码在概念上解释的抽象层，并创建了避免依赖的一个屏障。
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(5)-\350\277\252\347\261\263\347\211\271\346\263\225\345\210\231\357\274\210LOD\357\274\211.md" "b/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(5)-\350\277\252\347\261\263\347\211\271\346\263\225\345\210\231\357\274\210LOD\357\274\211.md"
deleted file mode 100644
index 6acc76f4fd..0000000000
--- "a/\351\207\215\346\236\204/\350\256\276\350\256\241\346\250\241\345\274\217/\350\275\257\344\273\266\345\267\245\347\250\213\350\256\276\350\256\241\345\216\237\345\210\231/\350\275\257\344\273\266\350\256\276\350\256\241\345\216\237\345\210\231(5)-\350\277\252\347\261\263\347\211\271\346\263\225\345\210\231\357\274\210LOD\357\274\211.md"
+++ /dev/null
@@ -1,266 +0,0 @@
-尽管不像SOLID、KISS、DRY原则一样家喻户晓，但它却非常实用。
-- 啥是“高内聚、松耦合”？
-- 如何利用迪米特法则来实现“高内聚、松耦合”？
-- 有哪些代码设计是明显违背迪米特法则的？对此又该如何重构？
-
-# 何为“高内聚、松耦合”？
-“高内聚、松耦合”，能有效地提高代码可读性、可维护性，缩小功能改动导致的代码改动范围。
-很多设计原则都以实现代码“高内聚、松耦合”为目的，比如：
-- 单一职责原则
-- 基于接口而非实现编程
-
-“高内聚、松耦合”是个较通用的设计思想，可用来指导不同粒度代码的设计与开发，比如系统、模块、类，甚至是函数，也可以应用到不同的开发场景中，比如微服务、框架、组件、类库等。
-
-本文以“类”作为这个设计思想的应用对象来讲解。
-
-- “高内聚”用来指导类本身的设计
-- “松耦合”用来指导类与类之间依赖关系的设计
-
-这两者并非完全独立不相干。高内聚有助于松耦合，松耦合又需要高内聚的支持。
-## 什么是“高内聚”？
-- 相近功能，应放到同一类
-- 不相近的功能，不要放到同一类
-
-相近的功能往往会被同时修改，放到同一类中，修改会比较集中，代码容易维护。
-
-单一职责原则就是实现代码高内聚非常有效的设计原则。
-## 什么是“松耦合”？
-在代码中，类与类之间的依赖关系简单清晰。
-
-即使两个类有依赖关系，一个类的代码改动不会或者很少导致依赖类的代码改动。依赖注入、接口隔离、基于接口而非实现编程及迪米特法则，都是为实现代码松耦合。
-
-## “内聚”和“耦合”的关系
-图中左边部分的代码结构是“高内聚、松耦合”；右边部分正好相反，是“低内聚、紧耦合”。
-![](https://img-blog.csdnimg.cn/474e839624224ad0b7b35f9f216ab43e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-## 左半部分的代码设计
-类的粒度较小，每个类的职责都比较单一。相近的功能都放到了一个类中，不相近的功能被分割到了多个类中。
-这样类更加独立，代码的内聚性更好。
-因为职责单一，所以每个类被依赖的类就会比较少，代码低耦合。
-一个类的修改，只会影响到一个依赖类的代码改动。
-只需要测试这一个依赖类是否还能正常工作即可。
-
-## 右边部分的代码设计
-类粒度比较大，低内聚，功能大而全，不相近的功能放到了一个类中。
-导致很多其他类都依赖该类。
-修改这个类的某功能代码时，会影响依赖它的多个类。
-我们需要测试这三个依赖类，是否还能正常工作。这也就是所谓的“牵一发而动全身”。
-
-高内聚、低耦合的代码结构更加简单、清晰，相应地，在可维护性和可读性上确实要好很多。
-
-# 迪米特法则
-Law of Demeter，LOD，这个名字看不出这个原则讲的是什么。
-它还有另外一个名字，叫作最小知识原则，The Least Knowledge Principle：
-Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.
-每个模块（unit）只应该了解那些与它关系密切的模块（units: only units “closely” related to the current unit）的有限知识（knowledge）。或者说，每个模块只和自己的朋友“说话”（talk），不和陌生人“说话”（talk）。
-
-结合经验，定义描述中的“模块”替换成“类”：
-
-> 不该有直接依赖关系的类之间，不要有依赖；
-> 有依赖关系的类之间，尽量只依赖必要的接口（也就是定义中的“有限知识”）。
-
-可见，迪米特法则包含前后两部分，讲的是两件事情，我用两个实战案例分别来解读一下。
-# 案例
-**不该有直接依赖关系的类之间，不要有依赖。**
-
-实现了简化版的搜索引擎爬取网页，包含如下主要类：
-- NetworkTransporter，负责底层网络通信，根据请求获取数据
-![](https://img-blog.csdnimg.cn/461d68b211254631adedf747ee28f87d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-- HtmlDownloader，通过URL获取网页
-![](https://img-blog.csdnimg.cn/27ed63b4a4f0439095e8f12a1785de40.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-
-- Document，表示网页文档，后续的网页内容抽取、分词、索引都是以此为处理对象
-![](https://img-blog.csdnimg.cn/7ad339dd22614416b56fe15e2d96933c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-### 分析
-这段代码虽然“能用”，但不够“好用”，有很多设计缺陷：
-- NetworkTransporter，作为一个底层网络通信类，我们希望它的功能尽可能通用，而不只是服务于下载HTML。所以，不应该直接依赖太具体的发送对象HtmlRequest。
-这点，NetworkTransporter类的设计违背迪米特法则，依赖了不该有直接依赖关系的HtmlRequest类。
-
-应该如何重构？
-假如你现在要去买东西，你肯定不会直接把钱包给收银员，让收银员自己从里面拿钱，而是你从钱包里把钱拿出来交给收银员。
-HtmlRequest对象相当于钱包，HtmlRequest里的address和content对象就相当于钱。
-应该把address和content交给NetworkTransporter，而非直接把HtmlRequest交给NetworkTransporter：
-![](https://img-blog.csdnimg.cn/c1c2dbc6d3e64fd681373fa854734df2.png)
-
-HtmlDownloader类的设计没问题。不过，因为修改了NetworkTransporter#send()，而这个类用到了send()，所以要做相应修改，修改后的代码如下所示：
-```java
-public class HtmlDownloader {
-
-  private NetworkTransporter transporter;
-  
-  public Html downloadHtml(String url) {
-    HtmlRequest htmlRequest = new HtmlRequest(url);
-    Byte[] rawHtml = transporter.send(
-      htmlRequest.getAddress(), htmlRequest.getContent().getBytes());
-    return new Html(rawHtml);
-  }
-}
-```
-
-Document的问题较多：
-- 构造器中的`downloader.downloadHtml()`逻辑复杂，耗时长，不应放到构造器，会影响代码的可测试性
-- HtmlDownloader对象在构造器通过new来创建，违反了基于接口编程，也影响代码可测试性
-- 业务含义上说，Document网页文档没必要依赖HtmlDownloader类，违背迪米特法则
-
-问题虽多，但修改简单：
-```java
-public class Document {
-
-  private Html html;
-  private String url;
-  
-  public Document(String url, Html html) {
-    this.html = html;
-    this.url = url;
-  }
-  // ...
-}
-
-// 工厂方法创建Document
-public class DocumentFactory {
-  private HtmlDownloader downloader;
-  
-  public DocumentFactory(HtmlDownloader downloader) {
-    this.downloader = downloader;
-  }
-  
-  public Document createDocument(String url) {
-    Html html = downloader.downloadHtml(url);
-    return new Document(url, html);
-  }
-}
-```
-## 序列化
-**有依赖关系的类之间，尽量只依赖必要的接口。**
-
-Serialization类负责对象的序列化和反序列化。
-```java
-public class Serialization {
-
-  public String serialize(Object object) {
-    String serializedResult = ...;
-    //...
-    return serializedResult;
-  }
-  
-  public Object deserialize(String str) {
-    Object deserializedResult = ...;
-    //...
-    return deserializedResult;
-  }
-}
-```
-单看类的设计，没问题。
-但若把它放到特定应用场景，假设项目中的有些类只用到序列化操作，而另一些类只用到反序列化。
-那么，基于 **有依赖关系的类之间，尽量只依赖必要的接口**，只用到序列化操作的那部分类不应依赖反序列化接口，只用到反序列化操作的那部分类不应依赖序列化接口。
-
-据此，应将Serialization类拆分为两个更小粒度的类：
-- 一个只负责序列化（Serializer类）
-- 一个只负责反序列化（Deserializer类）
-
-拆分后，使用序列化操作的类只需依赖Serializer类，使用反序列化操作的类依赖Deserializer类。
-```java
-public class Serializer {
-  public String serialize(Object object) {
-    String serializedResult = ...;
-    ...
-    return serializedResult;
-  }
-}
-
-public class Deserializer {
-  public Object deserialize(String str) {
-    Object deserializedResult = ...;
-    ...
-    return deserializedResult;
-  }
-}
-```
-尽管拆分后的代码更能满足迪米特法则，但却违背高内聚。高内聚要求相近功能放到同一类中，这样可以方便功能修改时，修改的地方不至于太散乱。
-针对本案例，若业务要求修改了序列化实现方式，从JSON换成XML，则反序列化实现逻辑也要一起改。
-在未拆分时，只需修改一个类。在拆分之后，却要修改两个类。这种设计思路的代码改动范围变大了！
-
-既不想违背高内聚，也不想违背迪米特法则，怎么办？
-引入两个接口即可：
-```java
-public interface Serializable {
-  String serialize(Object object);
-}
-
-public interface Deserializable {
-  Object deserialize(String text);
-}
-
-public class Serialization implements Serializable, Deserializable {
-
-  @Override
-  public String serialize(Object object) {
-    String serializedResult = ...;
-    ...
-    return serializedResult;
-  }
-  
-  @Override
-  public Object deserialize(String str) {
-    Object deserializedResult = ...;
-    ...
-    return deserializedResult;
-  }
-}
-
-public class DemoClass_1 {
-  private Serializable serializer;
-  
-  public Demo(Serializable serializer) {
-    this.serializer = serializer;
-  }
-  //...
-}
-
-public class DemoClass_2 {
-  private Deserializable deserializer;
-  
-  public Demo(Deserializable deserializer) {
-    this.deserializer = deserializer;
-  }
-  //...
-}
-```
-
-尽管还是要往DemoClass_1的构造器传入包含序列化和反序列化的Serialization实现类，但依赖的Serializable接口只包含序列化操作，DemoClass_1无法使用Serialization类中的反序列化接口，对反序列化操作无感知，就符合了迪米特法则的“依赖有限接口”。
-
-实际上，上面的的代码实现思路，也体现“面向接口编程”，结合迪米特法则，可总结出：“基于最小接口而非最大实现编程”。
-
-**新的设计模式和设计原则也就是在大量的实践中，针对开发痛点总结归纳出来的套路。**
-
-## 多想一点
-对于案例二的重构方案，你有啥不同观点？
-
-整个类只包含序列化、反序列化俩操作，只用到序列化操作的使用者，即便能够感知到仅有的一个反序列化方法，问题也不大。
-为了满足迪米特法则，将一个简单的类，拆出两个接口，是否有过度设计之嫌？
-
-设计原则本身无对错，只有能否用对之说。不要为了应用设计原则而应用，具体问题具体分析。
-
-对Serialization类，只包含两个操作，确实没太大必要拆成俩接口。
-但若我们对Serialization类添加更多功能，实现更多更好用的序列化、反序列化方法，重新考虑该问题：
-```java
-public class Serializer { // 参看JSON的接口定义
-  public String serialize(Object object) { //... }
-  public String serializeMap(Map map) { //... }
-  public String serializeList(List list) { //... }
-  
-  public Object deserialize(String objectString) { //... }
-  public Map deserializeMap(String mapString) { //... }
-  public List deserializeList(String listString) { //... }
-}
-```
-这种场景下，第二种设计思路更好。因为基于之前的应用场景来说，大部分代码只用到序列化功能。对于这部分使用者，不必了解反序列化，而修改之后的Serialization类，反序列化的“知识”，从一个函数变成了三个。一旦任一反序列化操作有代码改动，我们都需要检查、测试所有依赖Serialization类的代码是否还能正常工作。为了减少耦合和测试工作量，我们应该按照迪米特法则，将反序列化和序列化的功能隔离开来。
-
-# 总结
-## 高内聚、松耦合
-重要的设计思想，能够有效提高代码的可读性和可维护性，缩小功能改动导致的代码改动范围。“高内聚”用来指导类本身的设计，“松耦合”用来指导类与类之间依赖关系的设计。
-
-所谓高内聚，就是指相近的功能应该放到同一个类中，不相近的功能不要放到同一类中。相近的功能往往会被同时修改，放到同一个类中，修改会比较集中。所谓松耦合指的是，在代码中，类与类之间的依赖关系简单清晰。即使两个类有依赖关系，一个类的代码改动也不会或者很少导致依赖类的代码改动。
-## 迪米特法则
-
-不该有直接依赖关系的类之间，不要有依赖；有依赖关系的类之间，尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合，让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化，需要了解这一变化的类就会比较少。
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\350\275\257\344\273\266\345\256\236\347\216\260\345\210\206\346\236\220\344\271\213\351\201\223.md" "b/\351\207\215\346\236\204/\350\275\257\344\273\266\345\256\236\347\216\260\345\210\206\346\236\220\344\271\213\351\201\223.md"
deleted file mode 100644
index 0df1e1c7ea..0000000000
--- "a/\351\207\215\346\236\204/\350\275\257\344\273\266\345\256\236\347\216\260\345\210\206\346\236\220\344\271\213\351\201\223.md"
+++ /dev/null
@@ -1,152 +0,0 @@
-在一个系统中，模型和接口是相对稳定的部分。
-但同样的模型和接口，若采用不同实现，稳定性、可扩展性和性能等诸多方面相差极大。只有熟悉实现，才有改代码写新需求的基础。
-
-“看实现”的确是个大难题，因有无数细节怪在等你。所以，团队的新人都需要几个月试用期去熟悉代码细节。
-
-你不可能记住项目所有细节，但这不妨碍你工作。但若你心中没有一份关于项目实现的地图，你就一定会迷失。
-
-新人一般用几个月熟悉代码，就是在通过代码一点点展开地图，但是，这不仅极其浪费时间，也很难形成整体认知。
-
-> 推荐你应该直接把地图展开。怎么展开？
-
-要找到两个关键点：软件的结构和关键的技术。
-
-以Kafka为例，了解一个软件设计三步走：“模型、接口、实现”。
-先看Kafka的模型和接口。
-
-# MQ的模型与接口
-Kafka自我介绍是个分布式流平台，这是它现在的发展方向，但更多人觉得它是个MQ。
-MQ是Kafka这个软件的核心模型，而流平台显然是这个核心模型存在之后的扩展。所以，要先把焦点放在Kafka的核心模型——MQ。
-
-MQ（Messaging Queue）是一种进程间通信方式，发消息的一方（即生产者）将消息发给MQ，收消息的一方（即消费者）将队列中的消息取出并处理。
-
-看模型，MQ是很简单的，不就是生产者发消息，消费者消费消息，还有个topic，区分发给不同目标的消息。
-
-基本接口也很简单：
-生产者发消息：
-![](https://img-blog.csdnimg.cn/201185670ef445798199f9224f4bb5c4.png)
-消费者收消息：
-![在这里插入图片描述](https://img-blog.csdnimg.cn/77042fe28bee460b9321cfda28384769.png)
-看完模型和接口，你会感觉MQ本身并不难。
-
-但MQ实现有很多，Kafka只是其中一种，为什么会有这么多不同MQ实现呢？因为每个MQ实现有所侧重，有其适用场景。
-
-MQ还提供一定的消息存储能力。当
-
-```java
-Pro发消息速度>Con处理消息速度
-```
-MQ可起到缓冲作用。所以MQ还能“削峰填谷”：在消息量特别大时，先把消息收下来，慢慢处理，以减小系统压力。
-
-Kafka之所以突出于一大堆MQ实现，关键在于它针对消息写入做优化，它的生产者写入特快，即吞吐力特强。
-
-显然，接口和模型不足以将Kafka与其他MQ实现区分。所以，必须开始了解它的实现。
-看软件实现时的关键：
-- 软件的结构
-- 关键的技术
-
-模型是个抽象概念，被抽象的对象可以是某个聚合实体(订单中心中的订单)，也可以是某个流程或功能(Java内存模型中的主存与缓存同步的规则)。
-分层对模型来说是实现层面的东西，是一种水平方向的拆分，是一个实现上的规范；
-模型的细粒度拆分（父模型、子模型），应该是一种垂直维度的拆分，子模型的功能要高内聚，其复杂性不该发散到外部。
-
-
-# 软件结构
-软件结构也是软件模型，只不过，它不是整体上的模型，而是展开实现细节之后的模型。模型是分层的。
-
-对每个软件，当你从整体去了解它时，它是完整的一块。但当你打开它的时候，就成了多模块组合，这也是“分层”意义。上一层只要使用下一层提供给它的接口。
-
-所以，当打开一个层次，了解其实现时，先从大处着手。最好找到一张结构图，准确了解它的结构。
-
-如果你能够找到这样一张图，你还是很幸运的。因为在真实的项目中，你可能会碰到各种可能性：
-- 结构图混乱：你找到一张图，上面包含了各种内容。比如，有的是模块设计，有的是具体实现，更有甚者，还包括了一些流程
-- 结构图复杂：一个比较成熟的项目，图上画了太多的内容。确实，随着项目的发展，软件解决的问题越来越多，它必然包含了更多的模块。但对于初次接触这个项目的我们而言，它就过于复杂了
-- 无结构图
-想办法画一张
-
-先了解模型和接口，因为它们永远是你的主线。
-
-假设：现在你有了一张结构图，你打算做什么？
-了解它的结构？是，但不够。不仅要知道一个设计的结果，最好还要推断出设计原因。
-
-所以，一种更好的做法：带问题上路。
-假设自己就是这个软件设计者，问问自己要怎么做。再去对比别人的设计，你就会发现，自己的想法和别人想法的相同或不同。
-
-让你来设计MQ，你会怎么做？
-Kafka网上能搜到各种架构图，看个 最简单的架构图，因为最贴近MQ基础模型：
-![](https://img-blog.csdnimg.cn/5161ae8c5cb24c25be134cf74a5ecfe6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-你能看到什么？
-- Kafka的生产者一端将消息发给Kafka集群
-- 消费者一端将消息取出来进行处理
-
-这样的结构和你想的是不是一样？
-
-> 进一步设计，会干啥？
-
-- 生产者端封装出一个 SDK，负责消息的发送
-- 消费者端封装出一个 SDK，负责消息的接收
-- 设计一个集群系统，作为生产者和消费者之间的连接
-
-可以问自己更多的问题：
-生产端如果出现网络抖动，消息没有成功发送，怎么重试？
-消费端处理完的消息，如何保证集群不重复发送？
-为什么要设计一个集群呢？要防止出现单点的故障，而一旦有了集群，就会牵扯到下一个问题，集群内的节点如何保证消息的同步呢？
-消息在集群里是怎么存储的？
-生产端也好，消费端也罢，如果一个节点彻底掉线，集群该怎么处理呢？
-……
-
-有了更多问题，你就会在代码里更深入探索。你可根据需要，打开对应模块，进一步了解实现：
-比如消息重发问题，可看生产端是如何解决的。当问题细化到具体实现时，就可以打开源码，去寻找答案。
-
-结构上，Kafka不是一个特复杂系统。所以，若你的项目更复杂，层次更多，推荐把各层次逐一展开，先把整体结构放在心中，再做细节探索。
-# 核心技术
-就是能够让这个软件的“实现”与众不同的地方。
-了解关键技术可保证我们对代码的调整不会使项目出现明显劣化。
-大多数项目都愿意把自己的关键技术讲出来，所以，找到这些信息不难。
-
-## Kafka
-对写入做了专门优化，使其整体吞吐能力很强。
-
-> 咋做到的？
-
-MQ实现消息存储的方式通常是把它写入磁盘，而Kafka不同之处在于，它利用磁盘顺序读写特性。
-普通机械硬盘：
-- 随机写，需按机械硬盘方式寻址，然后磁头做机械运动，写入很慢
-- 顺序写，会大幅减少磁头运动
-
-可以这样实现，也是充分利用MQ本身特性：有序，**技术实现与需求完美结合的产物**。还可进一步优化：利用内存映射文件减少用户空间到内核空间复制的开销。
-
-若站在了解实现的角度，你会觉得这都很自然。
-
-> 但要想从设计角度学到更多，还是应带着问题上路，多问自己，为什么其它MQ不这么做？
-
-这的确值得深思。Kafka这个实现到底是哪里不容易想到呢？
-**软硬结合。**
-
-其它MQ实现也会把消息写入文件，但文件对于它们只是个通用接口。开发者并没有想过利用硬件的特性做开发。而Kafka开发者突破此限制，把硬件特性利用起来，取得更好结果。
-
-## LMAX Disruptor
-最强劲的线程通信库。经典代码片段：
-![](https://img-blog.csdnimg.cn/8131301192d441f48a69d9283e325e4b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-要理解这段代码，必须理解CPU缓存行，这也是软硬结合的设计。
-Disruptor缓存行填充中的填充字段。Disruptor中的一个元素是个volatile long类型，占用8字节。
-一但一个元素被修改，则与其在同一缓存行的所有元素的缓存都会失效。这就导致变更索引位1的元素，会导致索引位0的元素缓存也失效(操作时需重新从主内存加载)。
-所以，Disruptor做了一个缓存行填充的优化，在目标元素的前后都加7个类型字段，两边都占据掉56个字节。故而保证每个元素都独占缓存行。是一种用空间换时间的思想。
-# 总结
-理解一个实现，是以对模型和接口的理解为前提。
-如果想了解一个系统的实现，应从软件结构和关键技术两个方面着手。无论是软件结构，还是关键技术，我们都需要带着自己的问题入手，而问题的出发点就是我们对模型和接口的理解。
-
-了解软件的结构，其实，就是把分层的模型展开，看下一层模型：
-- 要知道这个层次给你提供了怎样的模型
-- 要带着自己的问题去了解这些模型为什么要这么设计
-
-Kafka的实现主要是针对机械硬盘做的优化，现在的SSD硬盘越来越多，成本越来越低，这个立意的出发点已经不像以前那样稳固了。
-
-软件的结构和核心技术应该分开，kafka之所以是：
-- MQ，看的是对MQ这个模型结构的实现
-就没必看存储的实现，应该看路由信息管理、消息生产、消息消费等核心实现及其旁支功能的选择(限制消息大小、故障节点延后、延迟消费)
-- kafka，看的是其消息存储核心技术实现
-
-如果想知道kafka为什么在 MQ如此突出，那就得了解其核心技术实现，即这里的软硬结合的存储设计。
-
-**理解实现，带着自己的问题，了解软件的结构和关键的技术。**
-![](https://img-blog.csdnimg.cn/0a05699fafbb4c2a8b760a8ab3e4c975.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
\ No newline at end of file
diff --git "a/\351\207\215\346\236\204/\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213 V.S \345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md" "b/\351\207\215\346\236\204/\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213 V.S \345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md"
deleted file mode 100644
index 27778aba37..0000000000
--- "a/\351\207\215\346\236\204/\351\235\242\345\220\221\345\257\271\350\261\241\347\274\226\347\250\213 V.S \345\207\275\346\225\260\345\274\217\347\274\226\347\250\213.md"	
+++ /dev/null
@@ -1,81 +0,0 @@
-# 面对不断增加的需求
-假设有一组学生：
-![](https://img-blog.csdnimg.cn/b82c41eefdbc4916ab8530bec8f79068.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_13,color_FFFFFF,t_70,g_se,x_16)
-若按姓名找出其中一个，你的代码可能如下：
-![](https://img-blog.csdnimg.cn/d159c250c9754da6a5475aafe1737564.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_17,color_FFFFFF,t_70,g_se,x_16)
-突然紧急需求来了，按学号找人，代码如下：
-![](https://img-blog.csdnimg.cn/012914c7599f4366b1fe52624733efac.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_15,color_FFFFFF,t_70,g_se,x_16)
-又一个新需求来了，这次按照ID 找人，代码可以如法炮制：
-![](https://img-blog.csdnimg.cn/59b45b54cf2e480e9371e4311465d334.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_14,color_FFFFFF,t_70,g_se,x_16)
-你发现，它们除查询条件不同，其余基本一模一样，别忘了代码结构重复也是代码重复！
-
-> 如何消除重复呢？
-
-引入查询条件，这里只需要返回一个bool值，可这样定义：
-![](https://img-blog.csdnimg.cn/44989bcbe1a84c02bcafa36df8e1fad8.png)
-通过查询条件，改造查询方法，把条件作为参数传入：
-
-于是，按名字查找变成：
-![](https://img-blog.csdnimg.cn/79abe26749554583adcb9e4fed93abd6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_18,color_FFFFFF,t_70,g_se,x_16)
-已经很好了，但你发现，每有一个新查询，都要做一层封装。
-
-> 如何才能省去这层封装？
-
-可将查询条件做成一个方法：
-![](https://img-blog.csdnimg.cn/9b69ac1bdf9848bb957fdc12f86a77ee.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-其它字段也可做类似封装，如此，要查询什么就由使用方自行决定：
-![](https://img-blog.csdnimg.cn/a34c2a3780b74569a58f2bf77085dc92.png)
-
-
-现在想用名字和学号同时查询，咋办？
-我猜你肯定要写一个byNameAndSno方法。若是如此，岂不是每种组合你都要新写一个？。
-完全可以用已有的两个方法组合出一个新查询：
-![](https://img-blog.csdnimg.cn/2621efeaeab94b4ca7321b93d1008fc1.png)
-
-> 这个神奇的and方法是如何实现的呢？
-
-按普通and逻辑写即可：
-![](https://img-blog.csdnimg.cn/67fb3925fe104efdbf4f617c1efa0bf6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-or和not同理，相信聪明如你也会实现。这样，使用方能够使用的查询条件完全可按需组合。
-
-现在想找出所有指定年龄的人。写个byAge就很简单了。
-那找到所有人该怎么写？
-![](https://img-blog.csdnimg.cn/29accac3556c4b2896a3bf403cda9139.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-
-- 要做什么动作（查询一个、所有）
-- 用什么条件（名字、学号、ID、年龄等）
-
-就成了两个维度，使用方可按需组合。
-
-同样都是常规Java代码，效果确很奇妙。这段代码：
-- 作者只提供了各种基本元素（动作和条件）
-- 用户可通过组合这些元素完成需求
-
-这种做法完全不同于常规OO，其思想源自函数式编程。
-质变在于引入了Predicate，它就是个函数。
-
-按“消除重复”这样一个简单目的，不断调整代码，就能写出这种函数式风格代码。
-
-现在看看函数式编程到底是啥
-# 函数式编程
-一种编程范式，提供的编程元素就是函数。
-这个函数源于数学里的函数，因为它的起源是数学家Alonzo Church发明的Lambda演算（Lambda calculus，也写作 λ-calculus）。所以，Lambda这个词在函数式编程中经常出现，可简单理解成匿名函数。
-
-和 Java的方法相比，它要规避状态和副作用，即同样输入一定会给出同样输出。
-
-虽然函数式编程语言早就出现，但函数式编程概念却是John Backus在其1977 年图灵奖获奖的演讲上提出。
-
-函数式编程第一个需要了解的概念就是函数。在函数式编程中，函数是一等公民（first-class citizen）：
-- 可按需创建
-- 可存储在数据结构中
-- 可以当作实参传给另一个函数
-- 可当作另一个函数的返回值
-
-对象，是OOP语言的一等公民，它就满足上述所有条件。所以，即使语言没有这种一等公民的函数，也完全能模拟。之前就用Java对象模拟出一个函数Predicate。
-
-随着函数式编程这几年蓬勃的发展，越来越多的“老”程序设计语言已经在新的版本中加入了对函数式编程的支持。所以，如果你用的是新版本，可以不必像我写得那么复杂。
-
-比如，在Java里，Predicate是JDK自带的，and方法也不用自己写，加上Lambda语法简化代码：
-![](https://img-blog.csdnimg.cn/cab94be419d24ede8ce83b515aa4ad27.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBASmF2YUVkZ2Uu,size_20,color_FFFFFF,t_70,g_se,x_16)
-按对象的理解方式，Predicate是个对象接口，但它可接受Lambda为其赋值。
-可将其理解成一个简化版匿名内部类。主要工作都是编译器帮助做了类型推演（Type Inference）。
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/MySQL\345\210\206\345\272\223\345\210\206\350\241\250.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/MySQL\345\210\206\345\272\223\345\210\206\350\241\250.md"
deleted file mode 100644
index 4f23387739..0000000000
--- "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/MySQL\345\210\206\345\272\223\345\210\206\350\241\250.md"
+++ /dev/null
@@ -1,129 +0,0 @@
-# 1 为什么要分库分表
-业务飞速发展导致了数据规模的急速膨胀，单机数据库已经无法适应互联网业务的发展。
-
-传统的将数据集中存储至单一数据节点的解决方案，在容量、性能、可用性和运维成本这三方面难于满足海量数据场景。在单库单表数据量超过一定容量水位的情况下，索引树层级增加，磁盘 IO 也很可能出现压力，会导致很多问题。
-
-- 性能
-由于MySQL采用 B+树索引，数据量超过阈值时，索引深度的增加也将使得磁盘访问的 IO 次数增加，进而导致查询性能的下降；高并发访问请求也使得集中式数据库成为系统的最大瓶颈。
-
-- 可用性
-服务化的无状态型，能够达到较小成本的随意扩容，这必然导致系统的最终压力都落在数据库之上。而单一的数据节点，或者简单的主从架构，已经越来越难以承担。从运维成本方面考虑，当一个数据库实例中的数据达到阈值以上，数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。
-
-> 主从复制只能减轻读压力，但容量问题是无法解决的。
-
-# 2 概念辨析
-分库和分表是两码事，可能光分库不分表，也可能光分表不分库。
-比如业务发展迅猛，注册用户数达到了2000万！每天活跃用户数100万！每天单表数据量10万条！高峰期每秒最大请求达到1000！感觉压力大。
-因为每天多10万条数据，一个月就多300万条数据，现在咱们单表已经几百万数据了，马上就破千万了。但是勉强还能撑着。高峰期请求现在是1000，咱们线上部署了几台机器，负载均衡搞了一下，数据库撑1000 QPS也还行。但现在开始担心了，接下来咋整？
-
-此时每天活跃用户数上千万，每天单表新增数据达50万，目前一个表总数据量都已经达到了两三千万了！扛不住！数据库磁盘容量不断消耗！高峰期并发达到5000~8000！你的系统绝对支撑不到现在，已经挂掉了！
-
-所以分库分表实际上是跟着公司业务发展走的，你公司业务发展越好，用户就越多，数据量越大，请求量越大，那你单个数据库一定扛不住。
-
-比如你单表都几千万数据了，你确定你能抗住么？
-绝对不行，单表数据量太大，会极大影响你的 SQL执行性能，到了后面你的SQL可能就跑的很慢。经验来看，单表到几百万时性能就会相对差点，就得分表了。
-
-## 分表
-把一个表的数据放到多个表中，然后查询时，就查一个表。
-
-比如按用户id分表，将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内，比如每个表就固定在200万以内。
-
-## 分库
-单库一般达到2000并发，亟需扩容，合适的单库并发值推荐在1000/s。可将一个库的数据拆分到多个库，访问时就访问一个库。
-
-- 分库分表的由来
-![](https://img-blog.csdnimg.cn/20190712220954195.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-# 3 分库分表中间件
-
-## cobar
-阿里b2b团队开发，proxy层方案，但已停止维护。
-- [Github地址](https://github.com/alibaba/cobar/)
-![](https://img-blog.csdnimg.cn/20210203103643124.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-不支持读写分离、存储过程、跨库join和分页。
-
-## TDDL
-淘宝团队开发，client层方案
-- [Github地址](https://github.com/alibaba/tb_tddl)
-![](https://img-blog.csdnimg.cn/20190712202411687.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-不支持join、多表查询等语法，支持读写分离。
-使用的也不多，因为还依赖淘宝的diamond配置管理系统，而且已被阿里云商用，不再开源。
-
-##  atlas
--  [Github地址](https://github.com/Qihoo360/Atlas)
-![](https://img-blog.csdnimg.cn/20210203104038930.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-360开源的，proxy层方案，但六年前就不维护了。
-
-## sharding-jdbc（shardingsphere）
--  [Github地址](https://github.com/apache/incubator-shardingsphere)
-![](https://img-blog.csdnimg.cn/20190712204304352.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20210203104142767.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)最初由当当开源，client层方案。
-SQL语法支持较多，支持分库分表、读写分离、分布式id生成、柔性事务（最大努力送达型事务、TCC事务）。被大量公司使用，我司也在用。现在已经升级为Apache组织的项目。
-
-这种client层方案的优点：
-不用部署，运维成本低，无需代理层的二次转发请求，性能很高
-但遇到升级啥的需要各个系统都重新升级版本再发布，各个系统都需要耦合sharding-jdbc的依赖。
-
-## mycat
--  [Github地址](https://github.com/MyCATApache/Mycat-Server)
-![](https://img-blog.csdnimg.cn/20190712204603810.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-基于cobar改造，proxy层方案，支持的功能非常完善，社区活跃。但相比sharding jdbc年轻一些。
-
-proxy层方案的缺点：
-需要部署，自己及运维一套中间件，运维成本高，但是好处在于对于各个项目是透明的，如果遇到升级之类的都是自己中间件那里搞就行了。
-
-
-## 总结
-综上，sharding-jdbc和mycat可以考虑使用。推荐小型公司选用sharding-jdbc，client层方案轻便，而且维护成本低，不需要额外增派人手，而且中小型公司系统复杂度会低一些，项目也没那么多。
-
-但中大型公司最好还是选用mycat这类proxy层方案，因为可能大公司系统和项目非常多，团队很大，人员充足，那么最好是专门弄个人来研究和维护mycat，然后大量项目直接透明使用即可。
-
-
-# 数据库垂直拆分/水平拆分
-- 数据库如何拆分
-![](https://img-blog.csdnimg.cn/20190712221229332.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-
-## 7.1 水平拆分
-把一个表的数据给弄到多个库的多个表里，但每个库的表结构都一样，只不过每个库中表放的数据是不同的，所有库表的数据加起来就是全部数据
-
-### 水平拆分的意义
-将数据均匀放更多的库里，然后用多个库来抗更高的并发，还有就是用多个库的存储容量来进行扩容。
-
-## 7.2 垂直拆分
-把一个有很多字段的表给拆分成多个表，或者是多个库
-每个库表的结构都不一样，每个库表都包含部分字段。一般来说，会将较少的访问频率很高的字段放到一个表里去，然后将较多的访问频率很低的字段放到另外一个表里去
-因为数据库是有缓存的，访问频率高的行字段越少，就可以在缓存里缓存更多的行，性能就越好。这个一般在表层面做的较多一些。
-
-这个其实挺常见的，很多同学可能自己都做过，把一个大表拆开，订单表、订单支付表、订单商品表。
-
-还有表层面的拆分，就是分表，将一个表变成N个表，就是让每个表的数据量控制在一定范围内，保证SQL的性能。
-否则单表数据量越大，SQL性能就越差。一般是200万行左右，不要太多，但是也得看具体你怎么操作，也可能是500万，或者是100万。你的SQL越复杂，就最好让单表行数越少。
-
-无论是分库还是分表，上面说的那些数据库中间件都可支持。就是基本上那些中间件可以做到你分库分表之后，中间件可以根据你指定的某个字段值，比如说userid，自动路由到对应的库上去，然后再自动路由到对应的表里去。
-
-你就得考虑一下，你的项目里该如何分库分表？
-- 垂直拆分，你可以在表层面来做，对一些字段特别多的表做一下拆分
-- 水平拆分，你可以说是并发承载不了，或者是数据量太大，容量承载不了，你给拆了，按什么字段来拆，你自己想好
-- 分表，你考虑一下，你如果哪怕是拆到每个库里去，并发和容量都ok了，但是每个库的表还是太大了，那么你就分表，将这个表分开，保证每个表的数据量并不是很大
-
-# 8  分库分表的方式
-## 按range分
-就是每个库一段连续的数据，一般按比如时间范围来的，但是这种一般较少用，因为很容易产生热点问题，大量的流量都打在最新的数据上了
-
-- 好处
-后面扩容的时候，就很容易，因为你只要预备好，给每个月都准备一个库就可以了，到了一个新的月份的时候，自然而然，就会写新的库了
-
-- 缺点
-但是大部分的请求，都是访问最新的数据。实际生产用range，要看场景，你的用户不是仅仅访问最新的数据，而是均匀的访问现在的数据以及历史的数据
-## 按某字段hash
-均匀分散，最为常用。
-- 好处
-可以平均分配没给库的数据量和请求压力
-
-- 坏处
-扩容起来比较麻烦，会有一个数据迁移的过程
-
-参考
-- 《Java工程师面试突击第1季-中华石杉老师》
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/kafka\351\253\230\346\200\247\350\203\275\346\200\273\347\273\223.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/kafka\351\253\230\346\200\247\350\203\275\346\200\273\347\273\223.md"
deleted file mode 100644
index f6a8ac9109..0000000000
--- "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/kafka\351\253\230\346\200\247\350\203\275\346\200\273\347\273\223.md"
+++ /dev/null
@@ -1,21 +0,0 @@
-kafka高性能原理
-==============
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;最近翻了下kafka官方关于kafka设计的文档，面试上用，这里就总结下自己了解到的kafka设计上支持那么大吞吐量的原因  
-消息传递及存储
-------------
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;从上层设计来说，kafka的生产者支持批量发送消息（可以设置发送的内容最大大小和最长等待时间）当这些批量的消息到达kafak的broker上后会通过硬盘的线性写操作将日志记录进硬盘，这种操作的速度是很快的（中间也涉及到操作系统的pagecache，kafka也可以设置这种缓存刷盘的频率比如：一秒刷一次，每条消息刷一次，按照操作系统的配置去刷），这个是说从生产者发送消息给broker很快，那么消费者消费速度呢？kafka在消费上使用pull的方式去主动向broker节点请求获取消息，而具体的offset是由消费者去指定的（这个offset其实broker上也有维护一份，但是我理解的是拉取offset的决定权是掌握在消费者手里的，只不过如果消费者挂了后，其他替代的消费者如何知道原来的offset呢，那就需要broker也存一份），Kafka底层是通过linux的sendfile函数直接将消息存储的消息内容转发到网络的socket buffer然后在copy到NIC buffer发送到网络上。这个用到的是零拷贝技术，而正常的情况是需要以下几步：
-
-> 1，从硬盘读取到pagecache  
-> 2，从pagecache读取到用户内存  
-> 3，从用户内存读到socket buffer中    
-> 4，从socket buffer读取到 NIC buffer中然后NIC自动硬件发送（这步是不需要耗费CPU时间的）
->>kafka使用零拷贝总共节省了从pagecache拷贝到用户内存和从用户内存拷贝到socket buffer的两次拷贝，节省了拷贝过程中用户态和心态的切换，同时因为网卡,显卡,声卡等支持了DMA也就是直接访问主内存而不需要经过CPU，那么网卡可以直接访问硬盘的pagecache而不需要在经过pagechche到socket buffer的这一步拷贝真正实现了零拷贝。
-
-
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;而kafka消费方式是通过消费者拉取的方式而消费者可以根据自己的消费速度批量拉取消息，消息又都是顺序读，所以kafka在发送消息给消费者的时候速度也很快。同时，kafka也支持数据的压缩，这种压缩的数据在生产者，broker，消费者都是一致的可以直接传输。  
-
-集群
----------------
-
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;说到大吞吐量必须也得涉及到kafka集群，现将Kafka集群我认为的重点知识记录如下：
-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;主要涉及两个方面吧，**一个是多boker节点，一个是主从复制**。Kafka使用多croker节点来进行负载均衡，而生产者按照topic发送消息到broker的规则可以选用轮询或者指定规则，消费者按照group进行消费，每个group中只会有一个消费者消费同一条消息，如果同一个group中有消费者挂了，那么这个消费者对应消费的broker也会分配到同一个group中的其他消费者上。但是如果broker挂了呢？这就需要用到kafka的主从节点设置了。其实broker的从节点数据同步方式跟普通的消费者没什么区别，而在同步数据的时候主节点会维护一套ISR节点群，在这个节点群的从节点，kafka认为他们的数据是比较完整的，如果主节点挂了之后，这些从节点的任意一台节点都可以替换主节点。那么怎么保证一个消息会被同步到从节点了呢，这个可以在生产者配置acks=0,1,-1来决定一条消息只有在收到多少个从节点的确认后才算真正的落地成功，当选择-1的时候那么在ISR集合中的所有节点都要收到这条消息并返回确认后，这条消息才算发送成功，这个时候延迟也会比较高，所以可以根据线上系统的特点来综合判断这个配置如何设置。
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.md"
index 8846304553..eae6b6f2ba 100644
--- "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.md"
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\345\210\206\345\270\203\345\274\217\344\272\213\345\212\241\350\247\243\345\206\263\346\226\271\346\241\210.md"
@@ -1,109 +1,122 @@
 # 1 CAP 定理
 ##  1.1 概念
-CAP 理论在分布式系统中：
-- 一致性
-分布式环境下**多个节点的数据是否强一致**
-- 可用性
-分布式**服务能一直保证可用状态**。当用户发出一个请求后，服务能在有限时间内返回结果
-- 分区容忍性
-特指对网络分区的容忍性
-
-对于共享数据系统，最多只能同时拥有CAP其中的两个，无法三者兼顾。
+CAP 理论在分布式系统中
+- 一致性：分布式环境下**多个节点的数据是否强一致**
+- 可用性：分布式**服务能一直保证可用状态**。当用户发出一个请求后，服务能在有限时间内返回结果
+- 分区容忍性：特指对网络分区的容忍性
+
+对于共享数据系统，最多只能同时拥有CAP其中的两个，没法三者兼顾。
 - 任两者的组合都有其适用场景
 - 真实系统应当是ACID与BASE的混合体
 - 不同类型的业务可以也应当区别对待
-- **分区容忍性是不可或缺的**
+- `其中，分区容忍性又是不可或缺的。`
 
-![](https://img-blog.csdnimg.cn/20200111011231456.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20200111011231456.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qYXZhZWRnZS5ibG9nLmNzZG4ubmV0,size_1,color_FFFFFF,t_70)
 
 结论：分布式系统中，最重要的是满足业务需求，而不是追求抽象、绝对的系统特性。
-
 ## 2.2 中间件实例
-- 优先选择AP，弱化C
+- 默认优先选择AP，弱化C
 Cassandra、Dynamo 等
 
-- 优先选择CP，弱化A
+- 默认优先选择CP，弱化A
 HBase、MongoDB 等
 
 # 2 BASE 理论
-## 2.1 核心思想
-- 基本可用（BasicallyAvailable）
+核心思想
+
+## 基本可用（BasicallyAvailable）
 分布式系统在出现故障时，允许损失部分的可用性来保证核心可用。
-- 软状态（SoftState）
+
+## 软状态（SoftState）
 允许分布式系统存在中间状态，该中间状态不会影响到系统的整体可用性。
-- 最终一致性（EventualConsistency）
+
+## 最终一致性（EventualConsistency）
 分布式系统中的所有副本数据经过一定时间后，最终能够达到一致的状态
 
-## 2.2 一致性模型
-数据的一致性模型可以分类如下：
+
+一致性模型数据的一致性模型可以分成以下 3 类：
 - 强一致性
-数据更新成功后，任意时刻所有副本中的数据都是一致的，一般采用同步实现
+数据更新成功后，任意时刻所有副本中的数据都是一致的，一般采用同步方式实现
 - 弱一致性
 数据更新成功后，系统不承诺立即可以读到最新写入的值，也不承诺具体多久后可读到
-- 最终一致性
-弱一致性的一种形式，数据更新成功后，系统不承诺立即可以返回最新写入的值，但是保证最终会返回上一次更新操作的值
+- 最终一致性：弱一致性的一种形式，数据更新成功后，系统不承诺立即可以返回最新写入的值，但是保证最终会返回上一次更新操作的值。
+
+分布式系统数据的强一致性、弱一致性和最终一致性可以通过Quorum NRW算法分析。
+
+只要聊到做了分布式系统，必问分布式事务，若你对分布式事务一无所知的话，确实很坑，起码得知道有哪些方案，一般怎么来做，每个方案的优缺点是什么。
 
-> 分布式系统数据的强一致性、弱一致性和最终一致性可以通过Quorum NRW算法分析。
+现在面试，分布式系统成了标配，而分布式系统带来的分布式事务也成了标配.
+你做系统肯定要用事务，那你用事务的话，分布式系统之后肯定要用分布式事务.
+先不说你搞过没有，起码你得明白有哪几种方案，每种方案可能有啥坑？比如TCC方案的网络问题、XA方案的一致性问题
+- 单块系统里的事务
+![](https://img-blog.csdnimg.cn/20190711234455282.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 
-# 3 分布式事务的解决方案
-● 异步校对数据
+
+- 分布式系统里的事务
+![](https://img-blog.csdnimg.cn/20190711234536741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+## 分布式事务的几种解决方案
+● 异步校对数据的方式
 支付宝、微信支付主动查询支付状态、对账单的形式;
-● 基于可靠消息(MQ)
+● 基于可靠消息(MQ)的解决方案
 异步场景;通用性较强;拓展性较高
 ● TCC编程式解决方案
 严选、阿里、蚂蚁金服自己封装的DTX
+# 3 XA（两阶段提交）
+需要数据库厂商支持，JAVA组件有atomikos。
+
+## 3.1 程序理解
+有一个事务管理器，负责协调多个数据库（资源管理器）的事务
+
+1. 事务管理器先问各个DB：预提交 ok 吗？
+2. 如果每个数据库都回复ok，即预提交成功，开始正式提交事务，在各DB开始执行操作，这里失败会有失败异常重试，日志分析，人工重试。
 
-## 3.1 实现思路
-### 理想状态
-像单机数据库事务一样，多个数据库自动通过某种协调机制，实现跨数据库节点的一致性。
+## 3.3 适用场景
+适合单块应用中，跨多库的分布式事务，而且因其严重依赖DB层面解决事务，所以效率很低，不适合高并发场景。
 
-- 使用场景
-要求严格的一致性，比如金融交易类业务。
+如果要玩，可以尝试基于Spring + JTA就可以搞定。
 
-### 一般情况
-可容忍一段时间的数据不一致，最终通过超时终止，调度补偿等方式，实现数据的最终状态一致性。
+互联网公司基本都不用，因某个系统内部如果出现跨多库的操作，是不合规的。现在的微服务，一个大的系统分成几十甚至上百个服务。一般规约每个服务只能操作自己对应的一个数据库。
 
-- 使用场景
-准实时或非实时的处理，比如 T+1 的各类操作，或者电商类操作。
+如果你要操作别的服务对应的库，不允许直连别的服务的库。要操作别人的服务的库，必须通过**调用别的服务的接口**。
 
-## 3.2 实现方案
-### 3.2.1 XA方案
-### 3.2.2 TCC方案
-#### 三阶段
+# 4 TCC方案
+## 4.1 三阶段
 - Try
-对各个服务的资源做检测，对资源进行提前锁定或者预留
+对各个服务的资源做检测,对资源进行提前锁定或者预留
 - Confirm
 在各个服务中执行实际的操作
 - Cancel
 如果任何一个服务的业务方法执行出错，那么这里就需要进行补偿，即执行已操作成功的业务逻辑的回滚操作
 
 ## 4.2 跨行转账案例
-涉及到两个银行的分布式事务，如果用TCC实现：
+涉及到两个银行的分布式事务，如果用TCC方案来实现，思路是这样的：
 
 - Try阶段
-先把两个银行账户中的资金给它冻结住，不让操作
+先把两个银行账户中的资金给它冻结住,不让操作了
 - Confirm阶段
 执行实际的转账操作，A银行账户的资金扣减，B银行账户的资金增加
 - Cancel阶段
 如果任何一个银行的操作执行失败，那么就需要回滚进行补偿
-比如A银行账户如果已经扣减了，但是B银行账户资金增加失败了，那么就得把A银行账户资金给加回去。
+比如A银行账户如果已经扣减了，但是B银行账户资金增加失败了，那么就得把A银行账户资金给加回去
 
-该方案很少使用，但也有使用场景。
+该方案说实话几乎很少使用，但也有使用场景.
 因为这个事务的回滚实际上严重依赖于你自己写代码来回滚和补偿了，会造成补偿代码巨大，非常恶心!
 
 比如说我们，一般来说和钱相关的支付、交易等相关的场景，我们会用TCC，严格严格保证分布式事务要么全部成功，要么全部自动回滚，严格保证资金的正确性!
 
 ## 4.3 适用场景
 除非你是真的一致性要求太高，是系统中核心之核心的场景!
-常见的就是资金类的场景，那可以用TCC方案，自己编写大量的业务逻辑，自己判断一个事务中的各个环节是否ok，不ok就执行补偿/回滚代码。
+常见的就是资金类的场景，那可以用TCC方案，自己编写大量的业务逻辑，自己判断一个事务中的各个环节是否ok，不ok就执行补偿/回滚代码
 
-而且最好是你的各个业务执行的时间都比较短。
-但尽量别这么搞，自己手写回滚逻辑，或者是补偿逻辑，实在太恶心了，业务代码也很难维护。
+而且最好是你的各个业务执行的时间都比较短
+
+但是说实话，一般尽量别这么搞，自己手写回滚逻辑，或者是补偿逻辑，实在太恶心了，业务代码也很难维护
 
 ## 4.4 方案示意图
 ![](https://img-blog.csdnimg.cn/20200509153432129.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 # 5 本地消息表
-ebay搞出来的思想。
+ebay搞出来的这么一套思想
 
 ## 5.1 简介
 - A系统在本地一个事务里操作的同时，插入一条数据到消息表
@@ -114,7 +127,7 @@ ebay搞出来的思想。
 
 ## 5.2 优点
 这个方案保证了**最终一致性**
-哪怕B事务失败了，但是A会不断重发消息，直到B那边成功。
+哪怕B事务失败了，但是A会不断重发消息，直到B那边成功为止
 
 ## 5.3 缺陷
 最大的问题就在于严重依赖于数据库的消息表来管理事务,这个会导致高并发场景无力,难以扩展呢,一般确实很少用
@@ -168,7 +181,4 @@ ebay搞出来的思想。
 # 权衡
 用分布式事务有性能成本，代码也很复杂，开发很长时间，性能和吞吐量下跌，系统更加复杂更加脆弱反而更加容易出bug；好处，如果做好了，TCC、可靠消息最终一致性方案，一定可以100%保证你那快数据不会出错。
 
-像资金、交易、订单这些业务，我们才可用会使用分布式事务方案来保证，会员积分、优惠券、商品信息，其实就不要这么搞了，严重影响性能！
-
-
-https://developer.ibm.com/zh/articles/j-lo-jta/
\ No newline at end of file
+像资金、交易、订单这些业务，我们才可用会使用分布式事务方案来保证，会员积分、优惠券、商品信息，其实就不要这么搞了，严重影响性能！
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(05)- \346\266\210\346\201\257\351\230\237\345\210\227\346\212\200\346\234\257\351\200\211\345\236\213.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(05)- \346\266\210\346\201\257\351\230\237\345\210\227\346\212\200\346\234\257\351\200\211\345\236\213.md"
new file mode 100644
index 0000000000..c5f33d3de7
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(05)- \346\266\210\346\201\257\351\230\237\345\210\227\346\212\200\346\234\257\351\200\211\345\236\213.md"	
@@ -0,0 +1,131 @@
+# 1 面试题
+消息队列(以下简称MQ),面试必问技术,工作必会技术,掌握它,是你的义务!!!
+
+- 为什么使用MQ啊？
+- MQ有什么优点和缺点啊？
+- kafka、activemq、rabbitmq、rocketmq都有什么区别以及适合哪些场景？
+
+# 2 考点分析
+其实面试官主要是想看看：
+
+## 2.1 知否系统缘何使用MQ？
+大量的候选人，说自己项目里用了redis、mq，但是其实他并不知道自己为什么要用这个东西。
+其实说白了，就是为了用而用，或者是别人设计的架构，他从头到尾没思考过!!!
+
+没有对自己的架构问过为什么的人，一定是平时没有思考的人，面试官对这类候选人印象通常很不好。因为进了团队担心你就木头木脑的干呆活儿，不会自己思考。
+
+## 2.2 既然用MQ，知否好处？
+系统中引入MQ之后会不会有什么坏处？
+你要是没考虑过这个，那你盲目弄个MQ进系统里，后面出了问题你是不是就自己删库跑路给公司留坑？你要是没考虑过引入一个技术可能存在的弊端和风险，面试官把这类候选人招进来了，基本可能就是挖坑型选手。
+
+就怕你干1年挖一堆坑，自己跑路了，给公司后患无穷,你还野火烧不尽
+
+## 2.3 既然你用了某种MQ,做过调研否？
+你别傻乎乎的自己拍脑袋看个人喜好就瞎用了一个MQ，比如kafka。
+甚至都从没调研过业界到底流行的MQ有哪几种？
+每一个MQ的优点和缺点是什么？
+每一个MQ没有绝对的好坏，但是就是看用在哪个场景可以扬长避短，利用其优势，规避其劣势。
+
+如果是一个不考虑技术选型的候选人招进了团队，面试官交给他一个任务，去设计个什么系统，他在里面用一些技术，可能都没考虑过选型，最后选的技术可能并不一定合适，一样是留坑
+
+> 同学啊，如果你看到这里，连activemq、rabbitmq、rocketmq、kafka是什么都不知道？连个hello world demo都没写过？那你。。。还不快关注我的公众号去学习这些!!!
+通过网上查阅技术资料和博客，用于快速入门，是比较合适的
+但是如果要比如系统梳理你的面试技术体系，或者是系统的深入的研究和学习一些东西，看博客实际上是不太合适的
+那也没事，这个教程的定位是不会去讲这些的，建议你马上暂停一下，然后搜一下我的博客，这4个东西是什么？每个东西找一个教你hello world的博客，自己跟着做一遍。我保证你1个小时之内就可以快速入门这几个东西。
+等你先知道这几个东西是什么，同时写过hello world之后，你再来继续看我们的教程
+
+# 3 面试题详解
+## 3.1 缘何使用MQ
+其实就是问问你
+- 消息队列都有哪些使用场景
+- 你项目里具体是什么场景
+- 说说你在这个场景里用消息队列搞什么
+
+面试官问你这个问题，期望的一个回答是说，你有个什么业务场景，这个业务场景有个什么技术挑战，如果不用MQ可能会很麻烦，但是你现在用了MQ之后带给了你很多的好处
+
+先说一下
+### 3.1.1 消息队列的常见使用场景
+其实场景有很多，但是比较核心的有3个：解耦、异步、削峰
+
+####  3.1.1.1 解耦
+![](https://img-blog.csdnimg.cn/20190516174449953.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+A系统发送个数据到BCD三个系统，接口调用发送
+
+那如果E系统也要这个数据呢？
+那如果C系统现在不需要了呢？
+现在A系统又要发送第二种数据了呢？
+
+A系统负责人濒临崩溃中。。。再来点更加崩溃的事儿，A系统要时时刻刻考虑BCDE四个系统如果挂了咋办？我要不要重发？我要不要把消息存起来？头都秃了啊!!!
+
+##### 面试技巧
+你需要去考虑一下你负责的系统中是否有类似的场景，就是一个系统或者一个模块，调用了多个系统或者模块，互相之间的调用很复杂，维护起来很麻烦。
+但是其实这个调用是不需要直接同步调用接口的，如果用MQ给他异步化解耦，也是可以的，你就需要去考虑在你的项目里，是不是可以运用这个MQ去进行系统的解耦。在简历中体现出来这块东西，用MQ作解耦。
+
+####  3.1.1.2 异步
+![](https://img-blog.csdnimg.cn/20190516175129333.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+- 使用MQ进行异步化之后的接口性能优化
+![在这里插入图片描述](https://img-blog.csdnimg.cn/2019051617523565.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+A系统接收一个请求，需要在自己本地写库，还需要在BCD三个系统写库
+自己本地写库要3ms，BCD三个系统分别写库要300ms、450ms、200ms
+最终请求总延时是3 + 300 + 450 + 200 = 953ms，接近1s，用户感觉搞个什么东西，慢死了慢死了!!!卸载!
+![](https://img-blog.csdnimg.cn/20190516175419852.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+![](https://img-blog.csdnimg.cn/20190516175503573.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+####  3.1.1.3 削峰
+![](https://img-blog.csdnimg.cn/20190516175633796.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+每天0点到11点，A系统风平浪静，每秒并发请求数量就100个。结果每次一到11点~1点，每秒并发请求数量突然会暴增到1万条。但是系统最大的处理能力就只能是每秒钟处理1000个请求啊。。。尴尬了，系统会死。。。
+![](https://img-blog.csdnimg.cn/20190516175748284.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+
+## 3.2 MQ的优缺点
+
+优点上面已经说了，就是在特殊场景下有其对应的好处，解耦、异步、削峰
+
+缺点呢？显而易见的
+
+### 3.2.1 系统可用性降低
+![](https://img-blog.csdnimg.cn/20190516175809538.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+系统引入的外部依赖越多，越容易挂掉，本来你就是A系统调用BCD三个系统的接口就好了，人ABCD四个系统好好的，没啥问题
+你偏加个MQ进来，万一MQ挂了咋整？MQ挂了，整套系统崩溃了，你不就完了么。
+
+### 3.2.2 系统复杂性提高
+硬生生加个MQ进来
+- 你怎么保证消息没有重复消费？
+- 怎么处理消息丢失的情况？
+- 怎么保证消息传递的顺序性？
+
+头大头大，问题一大堆，痛苦不已
+
+一致性问题：A系统处理完了直接返回成功了，人都以为你这个请求就成功了；但是问题是，要是BCD三个系统那里，BD两个系统写库成功了，结果C系统写库失败了，咋整？你这数据就不一致了。
+
+所以MQ实际是一种非常复杂的架构，你引入它有很多好处，但是也得针对它带来的坏处做各种额外的技术方案和架构来规避掉，最好之后，你会发现，妈呀，系统复杂度提升了一个数量级，也许是复杂了10倍。但是关键时刻，用，还是得用的。。。
+
+## 3.3 kafka、activemq、rabbitmq、rocketmq各自的优缺点
+常见的MQ其实就这几种，别的还有很多其他MQ，但是比较冷门的，那么就别多说了
+
+作为一个码农，你起码得知道各种mq的优点和缺点吧，咱们来画个表格看看
+![](https://img-blog.csdnimg.cn/20190516180246505.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+综上所述，各种对比之后，倾向于是：
+
+一般的业务系统要引入MQ，最早大家都用ActiveMQ，但是现在确实大家用的不多了，没经过大规模吞吐量场景的验证，社区也不是很活跃，所以大家还是算了吧，我个人不推荐用这个了；
+
+后来大家开始用RabbitMQ，但是确实erlang语言阻止了大量的java工程师去深入研究和掌控他，对公司而言，几乎处于不可控的状态，但是确实人是开源的，比较稳定的支持，活跃度也高；
+
+不过现在确实越来越多的公司，会去用RocketMQ，确实很不错，但是我提醒一下自己想好社区万一突然黄掉的风险，对自己公司技术实力有绝对自信的，我推荐用RocketMQ，否则回去老老实实用RabbitMQ吧，人是活跃开源社区，绝对不会黄
+
+所以中小型公司，技术实力较为一般，技术挑战不是特别高，用RabbitMQ是不错的选择；大型公司，基础架构研发实力较强，用RocketMQ是很好的选择
+
+如果是大数据领域的实时计算、日志采集等场景，用Kafka是业内标准的，绝对没问题，社区活跃度很高，绝对不会黄，何况几乎是全世界这个领域的事实性规范
+
+# 参考
+《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(07)-\345\246\202\344\275\225\344\277\235\350\257\201\346\266\210\346\201\257\351\230\237\345\210\227\344\270\215\351\207\215\345\244\215\346\266\210\350\264\271\346\225\260\346\215\256.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(07)-\345\246\202\344\275\225\344\277\235\350\257\201\346\266\210\346\201\257\351\230\237\345\210\227\344\270\215\351\207\215\345\244\215\346\266\210\350\264\271\346\225\260\346\215\256.md"
new file mode 100644
index 0000000000..4583ec1a13
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(07)-\345\246\202\344\275\225\344\277\235\350\257\201\346\266\210\346\201\257\351\230\237\345\210\227\344\270\215\351\207\215\345\244\215\346\266\210\350\264\271\346\225\260\346\215\256.md"
@@ -0,0 +1,60 @@
+# 1 面试题
+如何保证消息队列不重复消费数据,即如何保证消息消费时的幂等性?
+
+# 2 考点分析
+消费消息，那肯定要考虑考虑会不会重复消费.
+能不能避免重复消费,或者重复消费了也别造成系统异常可以嘛?
+
+这个是MQ领域的基本问题，其实本质上还是问你使用消息队列如何保证幂等性，这个是你架构里要考虑的一个问题。
+
+面试官问你，肯定是必问的，这是你要考虑的实际生产上的系统设计问题。
+
+# 3 详解
+先大概说一说可能会有哪些重复消费的问题。
+
+首先就是比如rabbitmq、rocketmq、kafka，都有可能会出现消费重复消费的问题.
+因为这问题通常不是mq自己保证的，是给你保证的.
+
+我们挑一个kafka来举个例子，说说
+## 3.1 如何重复消费
+kafka实际上有个offset的概念，就是每个消息写进去，都有一个offset，代表他的序号，然后consumer消费数据之后，每隔一段时间，会把自己消费过的消息的offset提交一下，代表我已经消费过了，下次我要是重启啥的，你就让我继续从上次消费到的offset来继续消费吧.
+
+但是凡事总有意外，有时候重启系统，看你怎么重启了，如果碰到点着急的，直接kill进程了，再重启。这会导致consumer有些消息处理了，但是没来得及提交offset
+尴尬了!重启之后，少数消息会再次消费一次。
+
+其实重复消费不可怕，可怕的是你没考虑到重复消费之后，怎么保证幂等性。
+
+给你举个例子吧。假设你有个系统，消费一条往数据库里插入一条，要是你一个消息重复两次，你不就插入了两条，这数据不就错了？
+但是你要是消费到第二次的时候，自己判断一下已经消费过了，直接扔了，不就保留了一条数据？
+
+![](https://uploadfiles.nowcoder.com/files/20190625/5088755_1561445338875_20190625142648438.png)
+
+一条数据重复出现两次，数据库里就只有一条数据，这就保证了系统的幂等性
+
+> 幂等性，通俗点说，就一个数据，或者一个请求，给你重复来多次，你得确保对应的数据是不会改变的，不能出错。
+
+那所以第二个问题来了
+## 3.2 怎么保证消息队列消费的幂等性？
+其实还是得结合业务来思考，这里给几个思路：
+- 比如你拿个数据要写库，你先根据主键查一下，如果这数据都有了，你就别插入了，update一下好嘛
+- 比如你是写redis，那没问题了，反正每次都是set，天然幂等性
+- 比如你不是上面两个场景，那做的稍微复杂一点，你需要让生产者发送每条数据的时候，里面加一个全局唯一的id，类似订单id之类的东西，然后你消费后，先根据该id去比如redis里查询，之前消费过吗？
+  - 未消费，就处理，然后将该id写redis
+  - 已消费，就别处理了，保证不重复处理相同的消息即可。
+
+还有比如基于数据库的唯一键来保证重复数据不会重复插入多条
+> 拿到数据的时候，每次重启可能会有重复，因为kafka消费者还没来得及提交offset，重复数据拿到了以后我们插入的时候，因为有唯一键约束了，所以重复数据只会插入报错，不会导致数据库中出现脏数据
+
+
+![](https://uploadfiles.nowcoder.com/files/20190625/5088755_1561445338863_20190625142719969.png)
+如何保证MQ的消费是幂等性的，是需要结合具体的业务来看的
+
+# 参考
+《Java工程师面试突击第1季-中华石杉老师》
+
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(12) - \346\200\273\347\273\223\346\266\210\346\201\257\351\230\237\345\210\227\347\233\270\345\205\263\351\227\256\351\242\230\347\232\204\351\235\242\350\257\225\346\212\200\345\267\247.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(12) - \346\200\273\347\273\223\346\266\210\346\201\257\351\230\237\345\210\227\347\233\270\345\205\263\351\227\256\351\242\230\347\232\204\351\235\242\350\257\225\346\212\200\345\267\247.md"
new file mode 100644
index 0000000000..476932a7f5
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(12) - \346\200\273\347\273\223\346\266\210\346\201\257\351\230\237\345\210\227\347\233\270\345\205\263\351\227\256\351\242\230\347\232\204\351\235\242\350\257\225\346\212\200\345\267\247.md"	
@@ -0,0 +1,24 @@
+
+一般而言，如果一个面试官水平还算不错，会沿着从浅入深的环节深入挖一个点。
+
+其实按照这个思路可以一直问下去，除了这里的7个问题之外，甚至能挑着你熟悉的一个mq一直问到源码级别非常底层。
+
+还可能会结合项目来仔细问，可能会先让你给我详细说说你的业务细节，然后将你的业务跟这些mq的问题场景结合起来，看看你每个细节是怎么处理的。
+
+但是确实因为我们这个是面试突破型教程，不是什么kafka源码剖析课，也不是什么RocketMQ高并发架构项目实战课程，所以只能讲到这个程度。
+
+所以我们这个课程只能让你从大面儿上，基本常见问题可以回答出来。基本上mq这块你能答到这个程度，你基本知识面儿是有了，但是深度是绝对没有的。所以如果一个面试官就问问这些问题，感觉你面儿上过的去了，那就恭喜你了。但是如果碰到我这种难缠的面试官，喜欢深挖底层，细扣项目细节的，那可能确实是不行的。
+
+如果你碰到人家在7个问题之外还死扣着你问的，那你最好是认一下怂，就说你确实没研究那么深过，如果你面的就是个一般的职位，那可能就过去了。就我而言，如果招聘的就是个普通职位，而你能答到这个程度，那么就觉得说的过去了。毕竟说实话，相当大比例的程序员出去面java职位的时候，mq这块还回答不到这个程度呢。你能答好这些，至少比之前一无所知的你好了一些，也比很多没准备过的程序员都好了很多。
+
+最后说一个技巧，要是确实碰一个面试官连这7个问题都没问满，只要他提到mq，你自己就和盘托出一整套的东西，你就说，mq你们之前遇到过什么问题，巴拉巴拉，你们的方案是什么，自己突出自己会的东西
+
+# 参考
+《Java工程师面试突击第1季-中华石杉老师》
+
+
+# X 交流学习
+![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(13)\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216\350\277\236\347\216\257\347\202\256.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(13)\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216\350\277\236\347\216\257\347\202\256.md"
new file mode 100644
index 0000000000..3f36d55b7b
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(13)\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216\350\277\236\347\216\257\347\202\256.md"
@@ -0,0 +1,32 @@
+业内目前来说事实上的一个标准，就是分布式搜索引擎一般大家都用elasticsearch
+
+
+# lucene
+如果你确实真的不连lucene都不知道是什么？我觉得你确实不应该，lucene底层的原理是一个东西，叫做倒排索引。太基础了。
+
+百度，搜索一下lucene入门，了解一下lucene是什么？倒排索引是什么？全文检索是什么？写个lucene的demo程序体验一把。
+
+# elasticsearch
+
+百度，搜索一下：elasticsearch入门，初步至少知道es的一些基本概念，然后包括es的基本部署和基本的使用
+
+
+# 面试官可能会怎么问？
+
+（1）es的分布式架构原理能说一下么（es是如何实现分布式的啊）？
+
+（2）es写入数据的工作原理是什么啊？es查询数据的工作原理是什么啊？
+
+（3）es在数据量很大的情况下（数十亿级别）如何提高查询性能啊？
+
+（4）es生产集群的部署架构是什么？每个索引的数据量大概有多少？每个索引大概有多少个分片？
+
+# 参考
+《Java工程师面试突击第1季-中华石杉老师》
+
+
+# X 交流学习
+![](https://upload-images.jianshu.io/upload_images/16782311-8d7acde57fdce062.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(15)-\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216Elastic Search\347\232\204\345\267\245\344\275\234\346\265\201\347\250\213.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(15)-\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216Elastic Search\347\232\204\345\267\245\344\275\234\346\265\201\347\250\213.md"
new file mode 100644
index 0000000000..b24b630eda
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(15)-\345\210\206\345\270\203\345\274\217\346\220\234\347\264\242\345\274\225\346\223\216Elastic Search\347\232\204\345\267\245\344\275\234\346\265\201\347\250\213.md"	
@@ -0,0 +1,134 @@
+> 以下用ES表Elastic Search
+
+# 1 面试题
+ES写入/查询数据的工作原理是什么呀？
+
+# 2 考点分析
+面试官就是想看看你是否了解ES的一些基本原理.
+ES无非就是写/查数据,你如果不明白你发起写入/搜索请求后,ES做了什么,那你该劝退了.
+
+# 3 详解
+## 3.1 ES写数据的执行流程
+- 客户端选择一个node发送请求过去,该node就是coordinating node(协调节点);
+- coordinating node对document进行路由,将请求转发给对应的node(有primary shard);
+- 实际的node上的primary shard处理请求,然后将数据同步到replica node;
+- coordinating node若发现primary node和所有replica node都响应完操作后,就返回结果给客户端.
+
+## 3.2 ES读数据的执行流程
+查询，GET某一条数据，写入了某个document,该document会自动给你分配一个全局唯一id-doc id,同时也是根据doc id进行hash路由到对应的primary shard上面去.也可以手动指定doc id,比如用订单id,用户id.
+
+可以通过doc id来查询,会根据doc id进行hash,判断出当时把doc id分配到了哪个shard,从那个shard去查询
+
+- 客户端发送请求到任意一个node，成为coordinate node
+- coordinate node对document路由,将请求转发到对应的node,此时会使用round-robin随机轮询算法，在primary shard及其所有replica中随机选择，使读请求负载均衡
+- 接收请求的node返回document给coordinate node
+- coordinate node返回document给客户端
+
+## 3.3 ES查询数据的执行流程
+最强大的是做全文检索,比如有三条数据
+```
+JavaEdge公众号呀
+Java学习者们建议关注哦
+java就很好学了呢
+```
+> 注意这里的字母大小写哟~
+
+根据Java关键词来搜索,将包含Java的document给搜索出来
+
+ES就会给你返回：JavaEdge公众号呀,Java学习者们建议关注哦
+
+- 客户端发送请求到一个coordinate node
+- 协调节点将搜索请求转发到所有的shard对应的primary shard或replica shard
+- query phase
+每个shard将自己的搜索结果（本质上就是一些doc id），返回给coordinate node，由coordinate node进行数据的合并、排序、分页等,以生成最终结果
+- fetch phase
+接着由coordinate node,根据doc id去各节点中拉取实际的document数据,最终返回给客户端
+
+## 3.4 搜索的底层原理 - 倒排索引
+### 画图说明传统数据库和倒排索引的区别
+(待更新...)
+## 3.5 ES 写数据的执行流程
+- ES读写底层原理示意图
+![](https://uploadfiles.nowcoder.com/files/20190627/5088755_1561579921192_20190627022247544.png)
+
+
+(1)  先写入buffer,在buffer里的时候数据是搜索不到的;同时将数据写入translog日志文件
+(2)  如果buffer将满,或者定时,就会将buffer中的数据refresh到一个新的segment file中
+但此时数据不是直接进入segment file磁盘文件的,而是先进入os cache,即refresh.
+
+每1s,ES 将buffer中的数据写到一个**新的segment file**,segment file磁盘文件每 s 生成一个,其只存储最近1s内buffer中写入的数据
+
+- 如果buffer中此时无数据,自然不会执行refresh操作
+- 如果buffer中有数据,默认每1s执行一次refresh,刷入一个新的segment file中
+
+在操作系统的磁盘文件中都有os cache(操作系统缓存),即数据写入磁盘文件前,会先进入os cache,即进入OS级别的一个内存缓存
+
+只要buffer中的数据被refresh刷入os cache,该数据就可被搜索到
+
+为什么称 ES 是准实时(NRT，near real-time)的?
+默认每1 s refresh一次,所以 ES 是准实时的,写入的数据1s之后才能被观测到.
+可以通过ES的RESRful API或者Java API,手动执行一次refresh,即手动将buffer中数据刷入os cache,让数据立马就可被搜索到.只要数据被输入os cache中,buffer就会被清空,因为不需要保留缓存了,数据在translog里面已经持久化到磁盘.
+
+(3) 只要数据进入os cache，此时就可以让这个segment file的数据对外提供搜索服务了.
+
+(4) 重复1~3步骤,新数据不断进入buffer和translog,不断将buffer数据写入一个个segment file,每次refresh完,清空buffer,保留translog.
+随着该过程不断推进,translog会变臃肿,当translog达到一定大小时,就会触发commit操作.
+
+> buffer中的数据，倒是好，每隔1秒就被刷到os cache中去，然后这个buffer就被清空了。所以说这个buffer的数据始终是可以保持住不会填满es进程的内存的。
+每次一条数据写入buffer，同时会写入一条日志到translog日志文件中去，所以这个translog日志文件是不断变大的，当translog日志文件大到一定程度的时候，就会执行commit操作。
+
+(5) commit操作第一步,就是将buffer中现有数据refresh到os cache,清空buffer
+
+(6）将一个commit point写到磁盘,以标识该commit point对应的所有segment file
+
+(7）强行将os cache中所有数据都fsync到磁盘
+
+> translog日志文件的作用是什么？
+就是在你执行commit之前,数据要么是停留在buffer中,要么os cache中
+无论是buffer还是os cache都是内存,一旦这台机器宕掉,数据就会全丢
+所以需要将数据对应的操作写入一个专门的日志文件，translog日志文件中，一旦此时机器宕机，再次重启的时候，ES会自动读取translog日志文件中的数据，恢复到内存buffer和os cache中去。
+
+#### commit操作
+- 写commit point
+- 将os cache数据fsync强刷到磁盘上去
+- 清空translog日志文件
+
+(8) 将现有的translog清空,接着重启启用一个translog，此时commit操作完成。默认每隔30分钟会自动执行一次commit，但是如果translog过大，也会触发commit。整个commit的过程，叫做flush操作。我们可以手动执行flush操作，就是将所有os cache数据刷到磁盘文件中去。
+
+不叫做commit操作，flush操作。es中的flush操作，就对应着commit的全过程。我们也可以通过es api，手动执行flush操作，手动将os cache中的数据fsync强刷到磁盘上去，记录一个commit point，清空translog日志文件。
+
+9）translog其实也是先写入os cache,默认每5s刷到磁盘
+所以默认情况下,可能有5秒的数据仅仅驻存在buffer或者translog文件的os cache中,若此时机器宕机,会丢失5s的数据.
+但是这样性能比较好,最多丢5s的数据.也可将translog设置成每次写操作必须是直接fsync到磁盘，但是性能会差很多.
+
+> 实际上在这里,若面试官没有问你ES丢数据的问题,就可在这里给面试官炫一把:
+其实ES第一是准实时性的,数据写入1s后可以搜索到;
+可能会丢失数据，你的数据有5s会停留在buffer/translog os cache/segment file os cache中,有5s的数据不在磁盘上,此时如果宕机,会导致这5s的数据丢失.
+
+如果你希望一定不能丢失数据的话，你可以设置个参数，官方文档，百度一下.
+每次写入一条数据，都是写入buffer，同时写入磁盘上的translog，但是这会导致写性能、写入吞吐量会下降一个数量级.
+本来一秒钟可以写2000条，现在你一秒钟只能写200条，都有可能.
+
+
+### 小结
+数据先写入内存 buffer，然后每隔 1s，将数据 refresh 到 os cache，到了 os cache 数据就能被搜索到（所以我们才说 es 从写入到能被搜索到，中间有 1s 的延迟）.
+每隔 5s，将数据写入 translog 文件（这样如果机器宕机，内存数据全没，最多会有 5s 的数据丢失），translog 大到一定程度，或者默认每隔 30mins，会触发 commit 操作，将缓冲区的数据都 flush 到 segment file 磁盘文件中.
+
+> 数据写入 segment file 之后，同时就建立好了倒排索引。
+
+## 3.6 ES 删除数据的执行流程
+(1) commit时会生成一个.del文件,将某个doc标识为deleted态,那么搜索的时候根据.del文件就知道该doc已被删除
+
+## 3.7 ES 更新数据的执行流程
+(1) 将原来的doc标识为deleted状态，然后新写入一条数据
+
+(2) buffer每refresh一次，就会产生一个segment file，所以默认情况下是1s一个segment file，segment file会越来越多，此时会定期执行merge
+
+(3) 每次merge时,会将多个segment file合并成一个，同时这里会将标识为deleted的doc给**物理删除**掉，然后将新的segment file写入磁盘，这里会写一个commit point，标识所有新的segment file，然后打开segment file供搜索使用，同时删除旧的segment file.
+
+> ES 里的写流程，有4个底层的核心概念，refresh、flush、translog、merge
+当segment file多到一定程度的时候，es就会自动触发merge操作，将多个segment file给merge成一个segment file。
+
+
+# 参考
+《Java工程师面试突击第1季-中华石杉老师》
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-7) - Redis\347\232\204\345\223\250\345\205\265\346\236\266\346\236\204.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-7) - Redis\347\232\204\345\223\250\345\205\265\346\236\266\346\236\204.md"
new file mode 100644
index 0000000000..f812d48b2d
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-7) - Redis\347\232\204\345\223\250\345\205\265\346\236\266\346\236\204.md"	
@@ -0,0 +1,82 @@
+
+# 1 Redis Sentinal机制
+sentinal，中文名哨兵
+
+哨兵是redis集群架构中非常重要的一个组件，主要功能如下
+- 集群监控
+监控Redis master和slave进程的正常工作
+- 消息通知
+如果某个Redis实例有故障，那么哨兵负责发送报警消息给管理员
+- 故障转移
+若master node宕机，会自动转移到slave node上
+- 配置中心
+若发生故障转移，通知client客户端新的master地址
+
+哨兵本身也是分布式的，作为一个哨兵集群去运行，协同工作
+- 故障转移时，判断一个master node是否宕机，需要大部分的哨兵都同意，涉及到了分布式选举问题
+- 即使部分哨兵节点宕机，哨兵集群还是能正常工作的
+
+
+> 目前采用的是sentinal 2版本，sentinal 2相对于sentinal 1来说，重写了很多代码，主要是让故障转移的机制和算法变得更加健壮和简单
+
+# 2 哨兵的核心知识
+1. 哨兵需要至少3个实例，保证自己的健壮性
+2. 哨兵 + Redis主从的部署架构，是不会保证数据零丢失的，只能保证redis集群的高可用性
+3. 对于哨兵 + Redis主从这种复杂的部署架构，尽量在测试环境和生产环境，都进行充足的测试和演练
+
+# 3 为什么仅2个节点时无法正常工作
+必须部署2个以上的节点
+
+若仅部署2个实例，quorum=1
+
+```
++----+         +----+
+| M1 |---------| R1 |
+| S1 |         | S2 |
++----+         +----+
+```
+
+Configuration: `quorum = 1`
+
+
+master宕机，s1和s2中只要有1个哨兵认为master宕机就可以进行切换，同时会在s1和s2中选举出一个执行故障转移.
+但此时，需要majority，也就是大多数哨兵都是运行的，2个哨兵的majority就是2
+
+> 2个哨兵的majority=2
+> 3个哨兵的majority=2
+> 4个哨兵的majority=2
+> 5个哨兵的majority=3
+
+2个哨兵都运行着，就可以允许执行故障转移
+
+若整个M1和S1运行的机器宕机了，那么哨兵仅剩1个，此时就无majority来允许执行故障转移，虽然另外一台机器还有一个R1，但故障转移不会执行
+
+# 4 3-节点哨兵集群(经典)
+```
+       +----+
+       | M1 |
+       | S1 |
+       +----+
+          |
++----+    |    +----+
+| R2 |----+----| R3 |
+| S2 |         | S3 |
++----+         +----+
+```
+Configuration: quorum = 2，majority
+
+若M1节点宕机了，还剩下2个哨兵，S2和S3可以一致认为master宕机了，然后选举出一个来执行故障转移
+
+同时3个哨兵的`majority`是2，所以余存的2个哨兵运行着，就可执行故障转移
+
+
+
+# 参考
+
+《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-8) - Redis\345\223\250\345\205\265\344\270\273\345\244\207\345\210\207\346\215\242\347\232\204\346\225\260\346\215\256\344\270\242\345\244\261\351\227\256\351\242\230.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-8) - Redis\345\223\250\345\205\265\344\270\273\345\244\207\345\210\207\346\215\242\347\232\204\346\225\260\346\215\256\344\270\242\345\244\261\351\227\256\351\242\230.md"
new file mode 100644
index 0000000000..c4739c1a07
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-8) - Redis\345\223\250\345\205\265\344\270\273\345\244\207\345\210\207\346\215\242\347\232\204\346\225\260\346\215\256\344\270\242\345\244\261\351\227\256\351\242\230.md"	
@@ -0,0 +1,70 @@
+# 1 数据丢失的两个场景
+
+主备切换的过程，可能会导致数据丢失
+
+## 1.1 异步复制
+
+由于 `master => slave`的复制是异步的，所以可能有部分数据还没复制到slave，master就宕机，于是这些数据就丢失了
+
+![](https://ask.qcloudimg.com/http-save/1752328/93jl4vswgy.png)
+
+## 1.2 脑裂导致
+
+脑裂，也就是说，某个master所在节点突然脱离正常的网络，无法和其他slave机器连接，但实际上master还运行着
+
+此时哨兵可能就会认为master宕机了，然后开启选举，将其他slave切换成了master
+
+这个时候，集群里就会有两个master，也就是所谓的`脑裂`
+
+此时虽然某个slave被切换成了master，但是可能client还没来得及切换到新的master，还继续写向旧master的数据可能也丢失了
+
+因此旧master再次恢复时，会被作为一个slave挂到新的master上去，自己的数据会被清空，重新从新的master复制数据
+
+![](https://ask.qcloudimg.com/http-save/1752328/q5320luqi1.png)
+
+# 2 数据丢失的解决方案
+
+如下配置可以减少异步复制和脑裂导致的数据丢失
+
+```
+min-slaves-to-write 1
+min-slaves-max-lag 10
+```
+
+配置要求至少有1个slave，数据复制和同步的延迟不能超过10秒
+
+一旦所有的slave，数据复制和同步的延迟都超过了10秒钟，master就不再接收任何请求!
+
+## 2.1 异步复制数据丢失解决方案
+
+`min-slaves-max-lag` 配置
+
+即可确保，一旦slave复制数据和ack延时过长，就认为可能master宕机后损失的数据太多了，那么就拒绝写请求
+
+这样就可把master宕机时由于部分数据未同步到slave导致的数据丢失降低在可控范围
+
+![](https://ask.qcloudimg.com/http-save/1752328/znnqfrs21u.png)
+
+## 2.2 脑裂数据丢失解决方案
+
+若一个master出现了脑裂，跟其他slave失去连接，那么开始的两个配置可以确保
+
+若不能继续给指定数量的slave发送数据，而且slave超过10秒没有给自己ack消息，那么就直接拒绝客户端的写请求
+
+这样脑裂后的旧master就不会接受client的新数据，也就避免了数据丢失
+
+上面的配置就确保了，如果跟任何一个slave丢了连接，在10秒后发现没有slave给自己ack，那么就拒绝新的写请求
+
+因此在脑裂场景下，最多就丢失10秒的数据
+
+![](https://ask.qcloudimg.com/http-save/1752328/aamxilr8we.png)
+
+# 参考
+
+《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-9) - \346\267\261\345\205\245\350\247\243\346\236\220Redis\345\223\250\345\205\265\345\272\225\345\261\202\345\216\237\347\220\206.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-9) - \346\267\261\345\205\245\350\247\243\346\236\220Redis\345\223\250\345\205\265\345\272\225\345\261\202\345\216\237\347\220\206.md"
index 71170b1804..8fe8941f46 100644
--- "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-9) - \346\267\261\345\205\245\350\247\243\346\236\220Redis\345\223\250\345\205\265\345\272\225\345\261\202\345\216\237\347\220\206.md"	
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(23-9) - \346\267\261\345\205\245\350\247\243\346\236\220Redis\345\223\250\345\205\265\345\272\225\345\261\202\345\216\237\347\220\206.md"	
@@ -69,4 +69,16 @@ quorum数量的哨兵都觉得一个master宕机
 
 其他的哨兵也都是根据版本号的大小来更新自己的master配置的
 
+# 参考
+
+《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
+
+
+
 
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(27) - \345\246\202\344\275\225\344\277\235\350\257\201\347\274\223\345\255\230\344\270\216\346\225\260\346\215\256\345\272\223\347\232\204\346\225\260\346\215\256\344\270\200\350\207\264\346\200\247.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(27) - \345\246\202\344\275\225\344\277\235\350\257\201\347\274\223\345\255\230\344\270\216\346\225\260\346\215\256\345\272\223\347\232\204\346\225\260\346\215\256\344\270\200\350\207\264\346\200\247.md"
new file mode 100644
index 0000000000..6e424918d5
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(27) - \345\246\202\344\275\225\344\277\235\350\257\201\347\274\223\345\255\230\344\270\216\346\225\260\346\215\256\345\272\223\347\232\204\346\225\260\346\215\256\344\270\200\350\207\264\346\200\247.md"	
@@ -0,0 +1,216 @@
+# 1 面试题
+
+如何保证缓存与数据库的双写一致性？
+
+# 2 考点分析
+
+你只要用缓存，就可能会涉及到缓存与数据库双存储双写，你只要是双写，就一定会有数据一致性的问题，那么你如何解决一致性问题？
+
+# 3 详解
+
+一般来说，就是如果你的系统不是严格要求缓存+数据库必须一致性的话，缓存可以稍微的跟数据库偶尔有不一致的情况，最好不要做这个方案
+
+读请求和写请求串行化，串到一个内存队列里去，这样就可以保证一定不会出现不一致的情况
+
+串行化之后，就会导致系统的吞吐量会大幅度的降低，用比正常情况下多几倍的机器去支撑线上的一个请求。
+
+## 3.1 Cache Aside Pattern缓存+数据库读写模式的分析
+
+> 最经典的缓存+数据库读写的模式 cache aside pattern
+
+### 3.1.1 Cache Aside Pattern
+
+（1）读的时候，先读缓存，缓存没有的话，就读数据库，然后取出数据后放入缓存，同时返回响应
+
+（2）更新的时候，先删除缓存，然后再更新数据库
+
+- cache aside pattern
+![](https://ask.qcloudimg.com/http-save/1752328/nlcbi9k7yy.png)3.1.2 为什么是删除缓存，而不是更新缓存呢？很多时候，复杂点的缓存的场景，因为缓存有的时候，不单是数据库中直接取出来的值
+
+商品详情页的系统，修改库存，只是修改了某个表的某些字段，但是要真正把这个影响的最终的库存计算出来，可能还需要从其他表查询一些数据，然后进行一些复杂的运算，才能最终计算出
+
+现在最新的库存是多少，然后才能将库存更新到缓存中去
+
+比如可能更新了某个表的一个字段，然后其对应的缓存，是需要查询另外两个表的数据，并运算，才能计算出缓存最新的值的
+
+> 更新缓存的代价是很高的
+
+是不是说，每次修改数据库的时候，都一定要将其对应的缓存去更新一份？
+
+也许有的场景是这样的，但是对于比较复杂的缓存数据计算的场景，就不是这样了
+
+如果你频繁修改一个缓存涉及的多个表，那么这个缓存会被频繁的更新，频繁的更新缓存
+
+但是问题在于，这个缓存到底会不会被频繁访问到？？？
+
+举个例子，一个缓存涉及的表的字段，在1分钟内就修改了20次，或者是100次，那么缓存更新20次，100次; 但是这个缓存在1分钟内就被读取了1次，有大量的冷数据
+
+> 28法则，黄金法则，20%的数据，占用了80%的访问量
+
+实际上，如果你只是删除缓存的话，那么1分钟内，这个缓存不过就重新计算一次而已，开销大幅度降低
+
+每次数据过来，就只是删除缓存，然后修改数据库，如果这个缓存，在1分钟内只是被访问了1次，那么只有那1次，缓存是要被重新计算的，用缓存才去算缓存
+
+其实删除缓存，而不是更新缓存，就是一个惰性延迟计算的思想，不要每次都重新做复杂的计算，不管它会不会用到，而是让它到需要被使用的时候再重新计算
+
+mybatis，hibernate，懒加载，思想
+
+查询一个部门，部门带了一个员工的list，没有必要说每次查询部门，都里面的1000个员工的数据也同时查出来啊
+
+80%的情况，查这个部门，就只是要访问这个部门的信息就可以了
+
+先查部门，同时要访问里面的员工，那么这个时候只有在你要访问里面的员工的时候，才会去数据库里面查询1000个员工
+
+更多缓存设计模式请阅读
+
+[大行缓存更新之道.md](https://github.com/Wasabi1234/JavaEdge/blob/master/%25E6%2595%25B0%25E6%258D%25AE%25E5%25BA%2593/%25E5%25A4%25A7%25E8%25A1%258C%25E7%25BC%2593%25E5%25AD%2598%25E6%259B%25B4%25E6%2596%25B0%25E4%25B9%258B%25E9%2581%2593.md)
+
+## 3.2 高并发场景下的缓存+数据库双写不一致问题分析与解决方案设计
+
+开发业务系统
+
+从哪一步开始做，从比较简单的那块开始做，实时性要求比较高的那块数据的缓存去做
+
+> 实时性比较高的数据缓存，就是库存的服务
+
+库存可能会修改，每次修改都要去更新这个缓存数据; 每次库存的数据，在缓存中一旦过期，或者是被清理掉了，前端的nginx服务都会发送请求给库存服务，去获取相应的数据
+
+库存这一块，写数据库的时候，直接更新redis缓存
+
+实际上没有这么的简单，这里，其实就涉及到了一个问题
+
+### 数据库与缓存双写，数据不一致的问题
+
+围绕和结合实时性较高的库存服务，把数据库与缓存双写不一致问题以及其解决方案，给大家讲解一下
+
+数据库与缓存双写不一致，很常见的问题，大型的缓存架构中，第一个解决方案
+
+也可能说，有些方案只是适合某些场景，在某些场景下，可能需要你进行方案的优化和调整才能适用于你自己的项目
+
+### 3.2.1 最初级的缓存不一致问题以及解决方案
+
+Q：先修改数据库，再删除缓存，如果缓存删除失败，那么会导致数据库中是新数据，缓存中是旧数据，数据出现不一致!
+
+- 最初级的数据库+缓存双写不一致问题
+![](https://ask.qcloudimg.com/http-save/1752328/pin0wcbj7t.png)
+
+A:先删除缓存，再修改数据库，如果删除缓存成功了，如果修改数据库失败了，那么数据库中是旧数据，缓存中是空的，那么数据不会不一致
+
+因为读的时候缓存没有，则读数据库中旧数据，然后更新到缓存中
+
+### 3.2.2 比较复杂的数据不一致问题分析
+
+数据发生了变更，先删除了缓存，然后要去修改数据库，`此时还没修改`
+
+一个请求过来，去读缓存，发现缓存空了，去查询数据库，查到了修改前的旧数据，放到了缓存中
+
+数据变更的程序完成了数据库的修改
+
+完了，数据库和缓存中的数据不一样了。。。。
+
+![](https://ask.qcloudimg.com/http-save/1752328/838xpfw8gg.png)
+
+### 3.2.3 为什么上亿流量高并发场景下，缓存会出现这个问题？
+
+只有在对一个数据在并发读写时，才可能会出现这种问题
+
+其实如果说你的并发量很低的话，特别是读很低，每天访问量就1万次，那么很少会出现刚才描述的那种不一致的场景
+
+但问题是，如果每天的是上亿的流量，每秒并发读是几万，每秒只要有数据更新的请求，就可能会出现上述的数据库+缓存不一致的情况
+
+高并发了以后，问题是很多的
+
+### 3.2.4 数据库 & 缓存更新与读取 异步串行化
+
+更新数据的时候，根据数据的唯一标识，将操作路由之后，发送到一个JVM内部的队列中
+
+读数据的时候，如果发现数据不在缓存中，那么将重读数据+更新缓存，根据唯一标识路由之后，也发送同一个JVM内部的队列中
+
+`一个队列对应一个工作线程`
+
+每个工作线程串行拿到对应的操作，然后一条一条的执行
+
+这样的话，一个数据变更的操作，先执行删除缓存，然后再更新数据库，但是还没完成更新
+
+此时如果一个读请求过来，读到了空缓存，则可以先将缓存更新的请求发送到队列中，此时会在队列中积压，然后同步等待缓存更新完成
+
+> 这里有一个优化点，一个队列中，其实多个更新缓存请求串在一起是没意义的，因此可以做过滤，如果发现队列中已经有一个更新缓存的请求了，那么就不用再放个更新请求操作进去了，直接等待前面的更新操作请求完成即可
+
+待那个队列对应的工作线程完成了上一个操作的数据库的修改之后，才会去执行下一个操作，也就是缓存更新的操作，此时会从数据库中读取最新的值，然后写入缓存中
+
+- 如果请求还在等待时间范围内，轮询发现可以取到值了，那么就直接返回
+- 如果请求等待的时间超过一定时长，那么这一次直接从数据库中读取当前的旧值
+
+### 3.2.5 高并发的场景下，该解决方案要注意的问题
+
+#### （1）读请求长时阻塞
+
+由于读请求进行了非常轻度的异步化，所以一定要注意读超时的问题，每个读请求必须在超时时间范围内返回
+
+该解决方案，最大的风险点在于，可能数据更新很频繁，导致队列中积压了大量更新操作，然后读请求会发生大量的超时，最后导致大量的请求直接走数据库
+
+务必通过一些模拟真实的测试，看看更新数据的频繁是怎样的
+
+另外一点，因为一个队列中，可能会积压针对多个数据项的更新操作，因此需要根据自己的业务情况进行测试，可能需要部署多个服务，每个服务分摊一些数据的更新操作
+
+如果一个内存队列里居然会挤压100个商品的库存修改操作，每隔库存修改操作要耗费10ms区完成，那么最后一个商品的读请求，可能等待10 \* 100 = 1000ms = 1s后，才能得到数据
+
+这个时候就导致读请求的长时阻塞
+
+一定要做根据实际业务系统的运行情况，去进行一些压力测试，和模拟线上环境，去看看最繁忙的时候，内存队列可能会挤压多少更新操作，可能会导致最后一个更新操作对应的读请求，会hang多少时间，如果读请求在200ms返回，如果你计算过后，哪怕是最繁忙的时候，积压10个更新操作，最多等待200ms，那还可以的
+
+如果一个内存队列可能积压的更新操作特别多，那么你就要加机器，让每个机器上部署的服务实例处理更少的数据，那么每个内存队列中积压的更新操作就会越少
+
+其实根据之前的项目经验，一般来说数据的写频率是很低的，因此实际上正常来说，在队列中积压的更新操作应该是很少的
+
+针对读高并发，读缓存架构的项目，一般写请求相对读来说，是非常非常少的，每秒的QPS能到几百就不错了
+
+一秒，500的写操作，5份，每200ms，就100个写操作
+
+单机器，20个内存队列，每个内存队列，可能就积压5个写操作，每个写操作性能测试后，一般在20ms左右就完成
+
+那么针对每个内存队列中的数据的读请求，也就最多hang一会儿，200ms以内肯定能返回了
+
+写QPS扩大10倍，但是经过刚才的测算，就知道，单机支撑写QPS几百没问题，那么就扩容机器，扩容10倍的机器，10台机器，每个机器20个队列，200个队列
+
+大部分的情况下，应该是这样的，大量的读请求过来，都是直接走缓存取到数据的
+
+少量情况下，可能遇到读跟数据更新冲突的情况，如上所述，那么此时更新操作如果先入队列，之后可能会瞬间来了对这个数据大量的读请求，但是因为做了去重的优化，所以也就一个更新缓存的操作跟在它后面
+
+等数据更新完了，读请求触发的缓存更新操作也完成，然后临时等待的读请求全部可以读到缓存中的数据
+
+#### (2）读请求并发量过高
+
+这里还必须做好压力测试，确保恰巧碰上上述情况的时候，还有一个风险，就是突然间大量读请求会在几十毫秒的延时hang在服务上，看服务能不能抗的住，需要多少机器才能抗住最大的极限情况的峰值
+
+但是因为并不是所有的数据都在同一时间更新，缓存也不会同一时间失效，所以每次可能也就是少数数据的缓存失效了，然后那些数据对应的读请求过来，并发量应该也不会特别大
+
+按1:99的比例计算读和写的请求，每秒5万的读QPS，可能只有500次更新操作
+
+如果一秒有500的写QPS，那么要测算好，可能写操作影响的数据有500条，这500条数据在缓存中失效后，可能导致多少读请求，发送读请求到库存服务来，要求更新缓存
+
+一般来说，1:1，1:2，1:3，每秒钟有1000个读请求，会hang在库存服务上，每个读请求最多hang多少时间，200ms就会返回
+
+在同一时间最多hang住的可能也就是单机200个读请求，同时hang住
+
+单机hang200个读请求，还是ok的
+
+1:20，每秒更新500条数据，这500秒数据对应的读请求，会有20 \* 500 = 1万
+
+1万个读请求全部hang在库存服务上，就死定了
+
+#### （3）多服务实例部署的请求路由
+
+可能这个服务部署了多个实例，那么必须保证说，执行数据更新操作，以及执行缓存更新操作的请求，都通过nginx服务器路由到相同的服务实例上
+
+#### （4）热点商品的路由问题，导致请求的倾斜
+
+万一某个商品的读写请求特别高，全部打到相同的机器的相同的队列里面去了，可能造成某台机器的压力过大
+
+就是说，因为只有在商品数据更新的时候才会清空缓存，然后才会导致读写并发，所以更新频率不是太高的话，这个问题的影响并不是特别大
+
+但是的确可能某些机器的负载会高一些
+
+# 参考
+
+《Java工程师面试突击第1季-中华石杉老师》
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(28) - \345\246\202\344\275\225\350\247\243\345\206\263Redis\347\232\204\345\271\266\345\217\221\347\253\236\344\272\211\351\227\256\351\242\230.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(28) - \345\246\202\344\275\225\350\247\243\345\206\263Redis\347\232\204\345\271\266\345\217\221\347\253\236\344\272\211\351\227\256\351\242\230.md"
new file mode 100644
index 0000000000..097a0ca853
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(28) - \345\246\202\344\275\225\350\247\243\345\206\263Redis\347\232\204\345\271\266\345\217\221\347\253\236\344\272\211\351\227\256\351\242\230.md"	
@@ -0,0 +1,24 @@
+
+# 1 面试题
+redis的并发竞争问题是什么？如何解决这个问题？了解Redis事务的CAS方案吗？
+
+# 2 考点分析
+这个也是线上非常常见的一个问题，就是多客户端同时并发写一个key，可能本来应该先到的数据后到了，导致数据版本错了。或者是多客户端同时获取一个key，修改值之后再写回去，只要顺序错了，数据就错了。
+
+而且redis自己就有天然解决这个问题的CAS类的乐观锁方案
+
+#  3 详解
+- redis并发竞争问题以及解决方案
+![](https://img-blog.csdnimg.cn/20190509175418361.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+
+# 参考
+
+《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
+
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(30) - \345\210\206\345\270\203\345\274\217\347\274\223\345\255\230\351\235\242\350\257\225\351\242\230\345\233\236\347\255\224\346\212\200\345\267\247\346\200\273\347\273\223.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(30) - \345\210\206\345\270\203\345\274\217\347\274\223\345\255\230\351\235\242\350\257\225\351\242\230\345\233\236\347\255\224\346\212\200\345\267\247\346\200\273\347\273\223.md"
new file mode 100644
index 0000000000..1c48d359c7
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(30) - \345\210\206\345\270\203\345\274\217\347\274\223\345\255\230\351\235\242\350\257\225\351\242\230\345\233\236\347\255\224\346\212\200\345\267\247\346\200\273\347\273\223.md"	
@@ -0,0 +1,25 @@
+这套东西基本构成了缓存这块你必须知道的基础性的知识，如果你不知道，那么说明你有点懒惰了，平时没好好积累。
+
+因为这些问题确实不难，如果往深了问，可以问的很细，结合项目扣的很细
+比如你们公司线上系统高峰QPS 3000？
+那请求主要访问哪些接口？
+redis抗了多少请求？
+mysql抗了多少请求？
+你到底是怎么实现高并发的？
+咱们聊聊redis的内核吧，看看你对底层了解的多么？
+如果要缓存几百GB的数据会有什么坑该这么弄？
+如果缓存出现热点现象该这么处理？
+某个value特别大把网卡给打死了怎么办？
+等等等等，可以深挖的东西其实有很多。。。。。
+
+但是如果你掌握好了这套东西的回答，那么你在面试的时候，如果面试官没有全都问到，你可以自己主动合盘脱出。
+比如你可以说，我们线上的缓存，做了啥啥机制，防止雪崩、防止穿透、保证双写时的数据一致性、保证并发竞争时的数据一致性，我们线上咋部署的，啥架构，怎么玩儿的。
+这套东西你可以自己说出来，展示一下你对缓存这块的掌握。
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(34)-dubbo\346\224\257\346\214\201\347\232\204\345\215\217\350\256\256.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(34)-dubbo\346\224\257\346\214\201\347\232\204\345\215\217\350\256\256.md"
new file mode 100644
index 0000000000..d37ff4d30a
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(34)-dubbo\346\224\257\346\214\201\347\232\204\345\215\217\350\256\256.md"
@@ -0,0 +1,66 @@
+# 1 面试题
+
+- dubbo支持哪些通信协议？
+- 支持哪些序列化协议？
+
+# 2 考点分析
+
+在上一篇文章，我们说到dubbo的基本工作原理，那是你必须知道的，至少知道dubbo分成哪些层，然后平时怎么发起rpc请求的，注册、发现、调用，这些是最基本的。
+
+接着就可以针对底层进行深入的讨伐了，比如第一步就可以先问问序列化协议这块，就是平时rpc的时候怎么走的？
+
+# 3 面试题详解
+
+## 3.1 dubbo支持的广阔通信协议
+
+![](https://ask.qcloudimg.com/http-save/1752328/vd6y05p1i2.png)
+
+### 3.1.1 dubbo协议
+
+```
+dubbo://192.168.0.1:20188
+```
+
+`默认`就是走dubbo协议的，单一长连接，NIO异步通信，基于hessian作为序列化协议
+
+#### 适用场景
+
+传输数据量很小（每次请求在100kb以内），但是并发量很高
+
+为了要支持高并发场景，一般是服务提供者就几台机器，但是服务消费者有上百台，可能每天调用量达到上亿次！此时用长连接是最合适的，就是跟每个服务消费者维持一个长连接就可以，可能总共就100个连接。然后后面直接基于长连接NIO异步通信，可以支撑高并发请求。
+
+否则如果上亿次请求每次都是短连接的话，服务提供者会扛不住。
+
+而且因为走的是单一长连接，所以传输数据量太大的话，会导致并发能力降低。所以一般建议是传输数据量很小，支撑高并发访问。
+
+### 3.1.2 rmi协议
+
+走java二进制序列化，多个短连接，适合消费者和提供者数量差不多，适用于文件的传输，一般较少用
+
+### 3.1.3 hessian协议
+
+走hessian序列化协议，多个短连接，适用于提供者数量比消费者数量还多，适用于文件的传输，一般较少用
+
+### 3.1.4 http协议
+
+走json序列化
+
+### 3.1.5 webservice
+
+走SOAP文本序列化
+
+## 3.2 dubbo支持的序列化协议
+
+所以dubbo实际基于不同的通信协议，支持hessian、java二进制序列化、json、SOAP文本序列化多种序列化协议。但是hessian是其默认的序列化协议。
+
+# 参考
+
+《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+
+## [Github](https://github.com/Wasabi1234)
+
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\347\232\204SPI\346\234\272\345\210\266.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(36)-\350\201\212\350\201\212Dubbo\347\232\204SPI\346\234\272\345\210\266.md"
similarity index 56%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\347\232\204SPI\346\234\272\345\210\266.md"
rename to "\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(36)-\350\201\212\350\201\212Dubbo\347\232\204SPI\346\234\272\345\210\266.md"
index 15237562fb..140e118f27 100644
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/Dubbo/Dubbo\347\232\204SPI\346\234\272\345\210\266.md"
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(36)-\350\201\212\350\201\212Dubbo\347\232\204SPI\346\234\272\345\210\266.md"
@@ -1,34 +1,66 @@
-Dubbo 没使用 Java SPI，而重新实现了一套功能更强的 SPI。
+# 1 SPI简介
+SPI，Service Provider Interface，一种服务发现机制。
 
-Dubbo SPI 逻辑封装在 ExtensionLoader 类，通过 ExtensionLoader，可加载指定实现类。Dubbo SPI 所需配置文件需放置在 `META-INF/dubbo` 路径：![](https://img-blog.csdnimg.cn/20201220143821997.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
+比如一接口有3个实现类，那么在系统运行时，这个接口到底该选择哪个实现类？
+这就需要SPI，**根据指定或默认的配置，找到对应的实现类，加载进来，然后使用该实现类实例**。
+![](https://img-blog.csdnimg.cn/20201220141747102.png)
 
-配置内容如下：
+在系统实际运行的时候，会加载你的配置，用实现A2实例化一个对象来提供服务。
+
+比如你要通过jar包给某个接口提供实现，然后你就在自己jar包的`META-INF/services/`目录下放一个接口同名文件，指定接口的实现是自己这个jar包里的某个类。
+![](https://img-blog.csdnimg.cn/20201220142131599.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
+别人用这个接口，然后用你的jar包，就会在运行时通过你的jar包的那个文件找到这个接口该用哪个实现类。这是JDK提供的功能。
+
+比如你有个工程A，有个接口A，接口A在工程A没有实现类，系统运行时怎么给接口A选个实现类呢?
+可以自己搞个jar包，`META-INF/services/`，放上一个文件，文件名即接口名，接口A，接口A的实现类=`com.javaedge.service.实现类A2`。
+
+让工程A来依赖你的jar包，然后在系统运行时，工程A跑起来，对于接口A，就会扫描依赖的jar包，看看有没有`META-INF/services`文件夹。
+如果有，再看看有没有名为接口A的文件，如果有，在里面找一下指定的接口A的实现是你的jar包里的哪个类!
+
+# 2 适用场景
+插件扩展的场景，比如你开发了一个开源框架，若你想让别人自己写个插件，安排到你的开源框架里中，扩展功能
+
+## 2.1  Java中的SPI
+经典的思想体现，其实大家平时都在用，比如JDBC。Java定义了一套JDBC的接口，但并未提供其实现类。
+
+但实际上项目运行时，要使用JDBC接口的哪些实现类呢?
+一般要根据自己使用的数据库引入：
+- MySQL，`mysql-jdbc-connector.jar`
+![](https://img-blog.csdnimg.cn/20201220151405844.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+系统运行时碰到你使用JDBC的接口，就会在底层使用你引入的那个jar中提供的实现类。
+
+## 2.2 Dubbo中的SPI
+Dubbo 并未使用 Java SPI，而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类，通过 ExtensionLoader，可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下
+![](https://img-blog.csdnimg.cn/20201220143821997.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+配置内容如下。
 ```java
-Protocol protocol = ExtensionLoader
-					.getExtensionLoader(Protocol.class)
-					.getAdaptiveExtension();
+Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
 ```
-Dubbo要判断系统运行时，应该选用该Protocol接口的哪个实现类。
-它会去找一个你配置的Protocol，将你配置的Protocol实现类，加载进JVM，将其实例化。
 
+Dubbo要判断一下，在系统运行时，应该选用这个Protocol接口的哪个实现类。
+它会去找一个你配置的Protocol，将你配置的Protocol实现类，加载进JVM，将其实例化。
 微内核，可插拔，大量的组件，Protocol负责RPC调用的东西，你可以实现自己的RPC调用组件，实现Protocol接口，给自己的一个实现类即可。
-这行代码就是Dubbo里大量使用的，对很多组件都保留一个接口和多个实现，然后在系统运行的时候动态根据配置去找到对应实现类。
-若你没配置，那就走默认实现。
-# 实例
-![](https://img-blog.csdnimg.cn/2020122014531547.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
+这行代码就是Dubbo里大量使用的，就是对很多组件，都是保留一个接口和多个实现，然后在系统运行的时候动态根据配置去找到对应的实现类。如果你没配置，那就走默认的实现。
+
+### 2.2.1 实例
+![](https://img-blog.csdnimg.cn/2020122014531547.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+
 在Dubbo自己的jar里
 在`/META_INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol`文件中：
-![](https://img-blog.csdnimg.cn/20201220145724211.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20201220150004508.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
-![](https://img-blog.csdnimg.cn/20201220150358794.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_1,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20201220145724211.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20201220150004508.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+![](https://img-blog.csdnimg.cn/20201220150358794.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
 即可看到Dubbo的SPI机制默认流程，就是Protocol接口
-- **@SPI("dubbo")**
+- @SPI("dubbo")
 通过SPI机制提供实现类，实现类是通过将`dubbo`作为默认key去配置文件里找到的，配置文件名称为接口全限定名，通过`dubbo`作为key可以找到默认的实现类`org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol`
 
 > Dubbo的默认网络通信协议，就是dubbo协议，用的DubboProtocol
 > 在 Java 的 SPI 配置文件里每一行只有一个实现类的全限定名，在 Dubbo的 SPI配置文件中是 key=value 的形式，我们只需要对应的 key 就能加载对应的实现。
-# 源码
-```java
+
+### 实现源码
+
+```csharp
 /**
   * 返回指定名字的扩展。如果指定名字的扩展不存在，则抛异常 {@link IllegalStateException}.
   */
@@ -91,7 +123,7 @@ private T createExtension(String name) {
 
 比如这个Protocol接口搞了俩`@Adaptive`注解了方法，在运行时会针对Protocol生成代理类，该代理类的那俩方法中会有代理代码，代理代码会在运行时动态根据url中的protocol来获取key(默认是dubbo)，也可以自己指定，如果指定了别的key，那么就会获取别的实现类的实例。通过这个url中的参数不同，就可以控制动态使用不同的组件实现类
 
-# 扩展Dubbo组件
+# 3 扩展Dubbo组件
 自己写个工程，可以打成jar包的那种哦
 - 里面的`src/main/resources`目录下
 - 搞一个`META-INF/services`
@@ -106,5 +138,10 @@ private T createExtension(String name) {
 这个时候provider启动的时候，就会加载到我们jar包里的`my=com.javaedge.MyProtocol`这行配置，接着会根据你的配置使用你定义好的MyProtocol了，这个就是简单说明一下，你通过上述方式，可以替换掉大量的dubbo内部的组件，就是扔个你自己的jar包，然后配置一下即可~
 - Dubbo的SPI原理图
 ![](https://img-blog.csdnimg.cn/20190709133144886.png)
-Dubbo中提供了大量的类似上面的扩展点。
-你要扩展一个东西，只需自己写个jar，让你的consumer或者是provider工程，依赖它，在你的jar里指定目录下配置好接口名称对应的文件，里面通过`key=实现类`然后对对应的组件，用类似`<dubbo:protocol>`用你的哪个key对应的实现类来实现某个接口，你可以自己去扩展dubbo的各种功能，提供你自己的实现!
\ No newline at end of file
+
+Dubbo中提供了大量的类似上面的扩展点.
+你要扩展一个东西，只需自己写个jar，让你的consumer或者是provider工程，依赖它，在你的jar里指定目录下配置好接口名称对应的文件，里面通过`key=实现类`然后对对应的组件，用类似`<dubbo:protocol>`用你的哪个key对应的实现类来实现某个接口，你可以自己去扩展dubbo的各种功能，提供你自己的实现!
+
+参考
+- 《Java工程师面试突击第1季-中华石杉老师》
+- https://dubbo.apache.org/zh-cn/docs/source_code_guide
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(39)-\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\350\257\267\346\261\202\347\232\204\351\241\272\345\272\217\346\200\247.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(39)-\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\350\257\267\346\261\202\347\232\204\351\241\272\345\272\217\346\200\247.md"
new file mode 100644
index 0000000000..775cec779c
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(39)-\345\210\206\345\270\203\345\274\217\346\234\215\345\212\241\346\216\245\345\217\243\350\257\267\346\261\202\347\232\204\351\241\272\345\272\217\346\200\247.md"
@@ -0,0 +1,43 @@
+# 1 面试题
+
+分布式服务接口请求的顺序性如何保证？
+
+# 2 考点分析
+
+分布式系统接口的调用顺序,一般来说是不用保证的.
+
+但是有时候可能确实需要**严格的顺序保证**.
+
+举个例子:
+
+服务 A 调用服务 B，先插入再删除。好，结果俩请求过去了，落在不同机器上，可能插入请求因为某些原因执行慢了一些，导致删除请求先执行了，此时因为没数据所以啥效果也没有；结果这个时候插入请求过来了，好，数据插入进去了，尴尬咯~
+
+本该 “先插入 => 再删除”，这条数据应该没了，结果现在 “先删除 => 再插入”，数据还存在!
+
+所以这都是分布式系统一些很常见的问题。
+
+# 3 解决方案
+
+一般来说,建议从业务逻辑上设计的系统最好是不需要这种顺序性的保证，因为一旦引入顺序性保障，比如使用**分布式锁**，会导致**系统复杂度上升**，而且会带来**效率低下**，**热点数据压力过大**等问题。
+
+简单来说，首先你得用 Dubbo 的一致性 hash 负载均衡策略,将比如某一个订单 id 对应的请求都给分发到某个机器上去，接着就是在那个机器上因为可能还是多线程并发执行的，你可能得立即将某个订单 id 对应的请求扔一个**内存队列**里去，强制排队，来确保他们的顺序性
+
+- 分布式系统接口调用顺序性图示
+![](https://ask.qcloudimg.com/http-save/1752328/m3u86wst0u.png)
+
+但是这样引发的后续问题就很多!
+
+比如说要是某个订单对应的请求特别多，造成某台机器成**热点**怎么办？解决这些问题又要开启后续一连串的复杂技术方案......曾经这类问题弄的我们头疼不已，所以，还是建议什么呢？
+
+最好是比如说刚才那种，一个订单的插入和删除操作，能不能合并成一个操作，就是一个删除，或者是什么，避免这种问题的产生。
+
+# 参考
+
+- 《Java工程师面试突击第1季-中华石杉老师》
+
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(46)-\345\210\206\345\272\223\345\210\206\350\241\250.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(46)-\345\210\206\345\272\223\345\210\206\350\241\250.md"
new file mode 100644
index 0000000000..dbf6a353e5
--- /dev/null
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(46)-\345\210\206\345\272\223\345\210\206\350\241\250.md"
@@ -0,0 +1,171 @@
+# 0  [Github](https://github.com/Wasabi1234)
+
+# 1 面试题
+
+为什么要分库分表（设计高并发系统的时候，数据库层面该如何设计）？用过哪些分库分表中间件？不同的分库分表中间件都有什么优点和缺点？你们具体是如何对数据库如何进行垂直拆分或水平拆分的？
+
+# 2 分析
+
+其实这块肯定是扯到高并发了，因为分库分表一定是为了支撑高并发、数据量大两个问题的。而且现在说实话，尤其是互联网类的公司面试，基本上都会来这么一下，分库分表如此普遍的技术问题，不问实在是不行，而如果你不知道那也实在是说不过去！
+
+# 3 业务分析
+
+说白了，分库分表是两回事儿，大家可别搞混了，可能是光分库不分表，也可能是光分表不分库，都有可能。我先给大家抛出来一个场景。
+
+业务发展迅猛，注册用户数达到了2000万！每天活跃用户数100万！每天单表数据量10万条！高峰期每秒最大请求达到1000！感觉压力已经有点大了，为啥呢？因为每天多10万条数据，一个月就多300万条数据，现在咱们单表已经几百万数据了，马上就破千万了。但是勉强还能撑着。高峰期请求现在是1000，咱们线上部署了几台机器，负载均衡搞了一下，数据库撑1000 QPS也还凑合。但是大家现在开始感觉有点担心了，接下来咋整呢。。。。。。
+
+此时每天活跃用户数上千万，每天单表新增数据多达50万，目前一个表总数据量都已经达到了两三千万了！扛不住啊！数据库磁盘容量不断消耗掉！高峰期并发达到惊人的5000~8000！别开玩笑了，哥。我跟你保证，你的系统支撑不到现在，已经挂掉了！
+
+看到你这里你差不多就理解分库分表是怎么回事儿了，实际上这是跟着公司业务发展走的，你公司业务发展越好，用户就越多，数据量越大，请求量越大，那你单个数据库一定扛不住。
+
+比如你单表都几千万数据了，你确定你能抗住么？
+
+绝对不行，单表数据量太大，会极大影响你的sql执行的性能，到了后面你的sql可能就跑的很慢了。一般来说，以经验来看，单表到几百万的时候，性能就会相对差一些了，你就得分表了。
+
+# 4 分表
+
+把一个表的数据放到多个表中，然后查询的时候你就查一个表
+
+比如按照用户id来分表，将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内，比如每个表就固定在200万以内。
+
+# 5 分库
+
+一个库一般我们经验而言，最多支撑到并发2000，一定要扩容了，而且一个健康的单库并发值你最好保持在每秒1000左右，不要太大。那么你可以将一个库的数据拆分到多个库中，访问的时候就访问一个库好了。
+
+这就是所谓的分库分表，为啥要分库分表？你明白了吧
+
+- 分库分表的由来
+![](https://ask.qcloudimg.com/http-save/1752328/3ahrs1ykbb.png)
+
+# 6 分库分表中间件
+
+不同的分库分表中间件都有什么优点和缺点？
+
+比较常见的包括：cobar、TDDL、atlas、sharding-jdbc、mycat
+
+## 6.1 cobar
+
+阿里b2b团队开发和开源的，属于proxy层方案
+
+早些年还可以用，但是最近几年都没更新了
+
+- [Github地址](https://github.com/alibaba/cobar/)
+![](https://ask.qcloudimg.com/http-save/1752328/j4b15539gb.png)
+
+基本没啥人用了，差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库join和分页等操作。
+
+## 6.2 TDDL
+
+淘宝团队开发的，属于client层方案
+
+- [Github地址](https://github.com/alibaba/tb_tddl)
+![](https://ask.qcloudimg.com/http-save/1752328/eeff579hve.png)
+
+不支持join、多表查询等语法，就是基本的crud语法是ok，但是支持读写分离。
+
+目前使用的也不多，因为还依赖淘宝的diamond配置管理系统,而且已被商用,不再开源
+
+## 6.3 atlas
+
+- [Github地址](https://github.com/Qihoo360/Atlas)
+
+![](https://ask.qcloudimg.com/http-save/1752328/j6lef79flu.png)
+
+360开源的，属于proxy层方案，以前是有一些公司在用的，但是确实有一个很大的问题就是社区最新的维护都在6年前了。所以，现在用的公司基本也很少了。
+
+## 6.4 sharding-jdbc
+
+- [Github地址](https://github.com/apache/incubator-shardingsphere)
+![](https://ask.qcloudimg.com/http-save/1752328/amelo94p01.png)
+
+当初由当当开源的，属于client层方案。确实之前用的还比较多一些，因为SQL语法支持也比较多，没有太多限制，而且目前推出到了2.0版本，支持分库分表、读写分离、分布式id生成、柔性事务（最大努力送达型事务、TCC事务）。而且确实之前使用的公司会比较多一些（这个在官网有登记使用的公司，可以看到从2017年一直到现在，是不少公司在用的），目前社区也还一直在开发和维护，还算是比较活跃，算是一个现在也可以选择的方案。现在已经升级为Apache组织的项目
+
+sharding-jdbc这种client层方案的优点在于不用部署，运维成本低，不需要代理层的二次转发请求，性能很高，但是如果遇到升级啥的需要各个系统都重新升级版本再发布，各个系统都需要耦合sharding-jdbc的依赖；
+
+## 6.5 mycat
+
+- [Github地址](https://github.com/MyCATApache/Mycat-Server)
+![](https://ask.qcloudimg.com/http-save/1752328/afi2b4q0kj.png)
+
+基于cobar改造的，属于proxy层方案，支持的功能非常完善，而且目前应该是非常火的而且不断流行的数据库中间件，社区很活跃，也有一些公司开始在用了。但是确实相比于sharding jdbc来说，年轻一些，经历的锤炼少一些。
+
+mycat这种proxy层方案的缺点在于需要部署，自己及运维一套中间件，运维成本高，但是好处在于对于各个项目是透明的，如果遇到升级之类的都是自己中间件那里搞就行了。
+
+## 6.6  小结
+
+所以综上所述，现在其实建议考量的，就是sharding-jdbc和mycat，这两个都可以去考虑使用。
+
+通常来说，这两个方案其实都可以选用，但是我个人建议中小型公司选用sharding-jdbc，client层方案轻便，而且维护成本低，不需要额外增派人手，而且中小型公司系统复杂度会低一些，项目也没那么多；
+
+但是中大型公司最好还是选用mycat这类proxy层方案，因为可能大公司系统和项目非常多，团队很大，人员充足，那么最好是专门弄个人来研究和维护mycat，然后大量项目直接透明使用即可。
+
+# 7 数据库的垂直拆分或水平拆分
+
+- 数据库如何拆分
+![](https://ask.qcloudimg.com/http-save/1752328/5z5jzmc30t.png)
+
+## 7.1 水平拆分
+
+把一个表的数据给弄到多个库的多个表里，但每个库的表结构都一样，只不过每个库中表放的数据是不同的，所有库表的数据加起来就是全部数据
+
+### 水平拆分的意义
+
+将数据均匀放更多的库里，然后用多个库来抗更高的并发，还有就是用多个库的存储容量来进行扩容。
+
+## 7.2 垂直拆分
+
+把一个有很多字段的表给拆分成多个表，或者是多个库
+
+每个库表的结构都不一样，每个库表都包含部分字段。一般来说，会将较少的访问频率很高的字段放到一个表里去，然后将较多的访问频率很低的字段放到另外一个表里去
+
+因为数据库是有缓存的，访问频率高的行字段越少，就可以在缓存里缓存更多的行，性能就越好。这个一般在表层面做的较多一些。
+
+这个其实挺常见的，很多同学可能自己都做过，把一个大表拆开，订单表、订单支付表、订单商品表。
+
+还有表层面的拆分，就是分表，将一个表变成N个表，就是让每个表的数据量控制在一定范围内，保证SQL的性能
+
+否则单表数据量越大，SQL性能就越差。一般是200万行左右，不要太多，但是也得看具体你怎么操作，也可能是500万，或者是100万。你的SQL越复杂，就最好让单表行数越少
+
+无论是分库还是分表，上面说的那些数据库中间件都是可以支持的。就是基本上那些中间件可以做到你分库分表之后，中间件可以根据你指定的某个字段值，比如说userid，自动路由到对应的库上去，然后再自动路由到对应的表里去。
+
+你就得考虑一下，你的项目里该如何分库分表？一般来说
+
+- 垂直拆分，你可以在表层面来做，对一些字段特别多的表做一下拆分
+- 水平拆分，你可以说是并发承载不了，或者是数据量太大，容量承载不了，你给拆了，按什么字段来拆，你自己想好
+- 分表，你考虑一下，你如果哪怕是拆到每个库里去，并发和容量都ok了，但是每个库的表还是太大了，那么你就分表，将这个表分开，保证每个表的数据量并不是很大
+
+# 8  **分库分表**的方式
+
+## 8.1 按照range分
+
+就是每个库一段连续的数据，一般按比如时间范围来的，但是这种一般较少用，因为很容易产生热点问题，大量的流量都打在最新的数据上了
+
+### 好处
+
+后面扩容的时候，就很容易，因为你只要预备好，给每个月都准备一个库就可以了，到了一个新的月份的时候，自然而然，就会写新的库了
+
+### 缺点
+
+但是大部分的请求，都是访问最新的数据。实际生产用range，要看场景，你的用户不是仅仅访问最新的数据，而是均匀的访问现在的数据以及历史的数据
+
+## 8.2 按照某个字段hash
+
+一下均匀分散，较为常用。
+
+### 好处
+
+可以平均分配没给库的数据量和请求压力
+
+### 坏处
+
+扩容起来比较麻烦，会有一个数据迁移的过程
+
+# 参考
+
+- 《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(47)-\345\210\206\345\272\223\345\210\206\350\241\250\347\232\204\345\256\236\350\267\265.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(47)-\345\210\206\345\272\223\345\210\206\350\241\250\347\232\204\345\256\236\350\267\265.md"
index cf40841088..0d9c8ef8a8 100644
--- "a/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(47)-\345\210\206\345\272\223\345\210\206\350\241\250\347\232\204\345\256\236\350\267\265.md"
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(47)-\345\210\206\345\272\223\345\210\206\350\241\250\347\232\204\345\256\236\350\267\265.md"
@@ -1,3 +1,5 @@
+# 0 [Github](https://github.com/Wasabi1234)
+
 # 1 面试题
 
 现在有一个未分库分表的系统，未来要分库分表，如何设计才可以让系统从未分库分表动态切换到分库分表上？
@@ -58,6 +60,12 @@ mycat：自己上官网，找一个官网最基本的例子，自己写一下，
 - 不停机双写方案
 ![](https://ask.qcloudimg.com/http-save/1752328/8lt8bqddaz.png)
 
+# 参考
 
-参考
 - 《Java工程师面试突击第1季-中华石杉老师》
+
+# X 交流学习
+![](https://img-blog.csdnimg.cn/20190504005601174.jpg)
+## [Java交流群](https://jq.qq.com/?_wv=1027&k=5UB4P1T)
+## [博客](https://blog.csdn.net/qq_33589510)
+## [Github](https://github.com/Wasabi1234)
\ No newline at end of file
diff --git "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\217\257\345\212\250\346\200\201\346\211\251\345\256\271\347\274\251\347\232\204\345\210\206\345\272\223\345\210\206\350\241\250.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(48)-\350\256\276\350\256\241\345\217\257\345\212\250\346\200\201\346\211\251\345\256\271\347\274\251\347\232\204\345\210\206\345\272\223\345\210\206\350\241\250.md"
similarity index 56%
rename from "\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\217\257\345\212\250\346\200\201\346\211\251\345\256\271\347\274\251\347\232\204\345\210\206\345\272\223\345\210\206\350\241\250.md"
rename to "\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(48)-\350\256\276\350\256\241\345\217\257\345\212\250\346\200\201\346\211\251\345\256\271\347\274\251\347\232\204\345\210\206\345\272\223\345\210\206\350\241\250.md"
index 75d6e6427e..b3fc19c191 100644
--- "a/\346\225\260\346\215\256\345\255\230\345\202\250/MySQL/MySQL\345\217\257\345\212\250\346\200\201\346\211\251\345\256\271\347\274\251\347\232\204\345\210\206\345\272\223\345\210\206\350\241\250.md"
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225(48)-\350\256\276\350\256\241\345\217\257\345\212\250\346\200\201\346\211\251\345\256\271\347\274\251\347\232\204\345\210\206\345\272\223\345\210\206\350\241\250.md"
@@ -1,75 +1,108 @@
+# 0  [Github](https://github.com/Wasabi1234)
+
+# 1 面试题
+
+设计可动态扩容的分库分表
+
+# 2 考点分析
+
 - 选一个数据库中间件,然后深入之
 - 设计分库分表的方案，要分成多少个库，每个库分成多少个表
-- 基于已选的数据库中间件，以及在测试环境建立好的分库分表，?能否正常执行分库分表的读写
+- 基于已选的数据库中间件，以及在测试环境建立好的分库分表，👀能否正常执行分库分表的读写
 - 完成单库单表到分库分表的迁移(使用上一文提到的双写方案)
 - 线上系统,开始基于分库分表对外服务
 
-突然! 扩容了，扩容成6个库，每个库需要12个表，你怎么来增加更多库和表？
-当你已经弄好分库分表方案，测试也通过了，数据能均匀分布到各个库和表里去，而且接着你还通过双写方案上了系统，已经直接基于分库分表方案在搞了。
-需求来了~现在这些库和表又支撑不住了，要继续扩容，咋办？
+突然! 扩容了，扩容成6个库，每个库需要12个表，你怎么来增加更多库和表呢？
+
+这个你必须面对的事，就是当你已经弄好分库分表方案，测试也通过了，数据能均匀分布到各个库和表里去，而且接着你还通过双写方案上了系统，已经直接基于分库分表方案在搞了。
+
+需求来了~现在这些库和表又支撑不住了，要继续扩容,咋办？
+
+这个可能就是每个库的容量又快满了，或者表数据量又太大了，也可能每个库的写并发太高了，得继续扩容!
+
+# 3 停机扩容(不推荐)
 
-可能
-- 每个库的容量又快满了
-- 表数据量又太大
-- 每个库的写并发太高
+这就跟停机迁移一样，步骤几乎一致，唯一不同是导数的工具，是把现有库表的数据抽出来慢慢导入到新的库和表里去。但是最好别这样，有点不太靠谱，既然分库分表,就说明数据量实在太大了,你这么玩，可能会出问题!
 
-得继续扩容!
+从单库单表迁移到分库分表时，数据量并不是很大，单表最大也就两三千万
 
-# 停机扩容(不推荐)
-和停机迁移一样，步骤几乎一致，唯一不同是导数据的工具，是把现有库表的数据抽出来慢慢导入到新的库和表里去。
-最好别这样，有点不太靠谱，既然分库分表，就说明数据量实在太大，这么玩可能玩脱。
+写个工具，多弄几台机器并行跑，1小时数据就导完了
 
-从单库单表迁移到分库分表时，数据量并不是很大，单表最大也就两三千万。写个工具，多弄几台机器并行跑，1小时数据就导完了。
+但如果是:3个库+12个表
 
-但如果是：3个库+12个表。跑一段时间，数据量都1亿~2亿了，光是导2亿数据，都要导个几个小时，6点，刚刚导完数据，还要搞后续的修改配置，重启系统，测试验证，大半天才搞完。
-# 优化方案
-一开始上来就是32个库，每个库32个表，1024张表：
-- 基本上国内互联网肯定都够用
+跑了一段时间了，数据量都1亿~2亿了
+
+光是导2亿数据，都要导个几个小时，6点，刚刚导完数据，还要搞后续的修改配置，重启系统，测试验证，10点才可以搞完!
+
+# 4 优化方案
+
+一开始上来就是32个库，每个库32个表，1024张表
+
+这个分法
+
+- 基本上国内的互联网肯定都够用
 - 无论并发支撑还是数据量支撑都没问题
 
-每个库正常承载的写入并发量是1000，那么32个库就可以承载`32 * 1000 = 32000`的写并发。
-如果每个库承载1500的写并发，`32 * 1500 = 48000`的写并发，接近5万/s的写并发。
-前面再加个MQ，削峰，每秒写入MQ 8万条数据，每秒消费5万条数据。
-1024张表，假设每个表放500万数据，在MySQL里可以放50亿条数据。
-每秒的5万写并发，总共50亿条数据，对于国内大部分互联网公司来说都够。
+每个库正常承载的写入并发量是1000，那么32个库就可以承载`32 * 1000 = 32000`的写并发
+
+如果每个库承载1500的写并发，`32 * 1500 = 48000`的写并发，接近5万/s的写入并发
+
+前面再加个MQ，削峰，每秒写入MQ 8万条数据，每秒消费5万条数据
 
-分库分表的扩容，**第一次分库分表，就一次性给他分个够**。
-32个库，1024张表，对大部分的中小型互联网公司来说，已经可以支撑好几年。
-一个实践是利用`32 * 32`来分库分表，即分为32个库，每个库里一个表分为32张表，一共就是1024张表。根据某个id先根据32取模路由到库，再根据32取模路由到库里的表。
+1024张表，假设每个表放500万数据，在MySQL里可以放50亿条数据
+
+每秒的5万写并发，总共50亿条数据，对于国内大部分互联网公司来说都够了
+
+谈分库分表的扩容，**第一次分库分表，就一次性给他分个够**
+
+32个库，1024张表，对大部分的中小型互联网公司来说，已经可以支撑好几年
+
+一个实践是利用`32 * 32`来分库分表，即分为32个库，每个库里一个表分为32张表,一共就是1024张表
+
+根据某个id先根据32取模路由到库，再根据32取模路由到库里的表。
 
 刚开始的时候，这个库可能就是逻辑库，建在一个数据库上的
+
 也就是一个MySQL服务器可能建了n个库,后面如果要拆分，就不断在库和MySQL服务器之间做迁移就可以了。然后系统配合改一下配置即可。
 
 比如说最多可以扩展到32个数据库服务器，每个数据库服务器是一个库。如果还是不够？
+
 最多可以扩展到1024个数据库服务器，每个数据库服务器上面一个库一个表。因为最多是1024个表
 
 这么搞，是不用自己写代码做数据迁移的，都交给DBA来搞好了，但是DBA确实需要做一些库表迁移的工作，总比你自己写代码，抽数据导数据来的效率高得多
 
 哪怕是要减少库的数量，也很简单，按倍数缩容就可以了，然后修改一下路由规则。
 
-![](https://img-blog.csdnimg.cn/20190713122920517.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_1,color_FFFFFF,t_70)
+![](https://ask.qcloudimg.com/http-save/1752328/rprqxeb7cn.png)
 
 # 5 总结
+
 ## 5.1 确定方案
+
 几台数据库服务器,每台服务器上几个库，每个库多少个表，推荐是`32库 * 32表`，对于大部分公司来说，可能几年都够了
 
 ## 5.2 路由规则
+
 orderId 模 32 = 库，orderId / 32 模 32 = 表
 
 ## 5.3 扩容
+
 当扩容时,申请增加更多的数据库服务器，装好MySQL，倍数扩容，4台服务器，扩到8台服务器，16台服务器
 
 ## 5.4 迁移
+
 由DBA负责将原先数据库服务器的库，迁移到新的数据库服务器上去，很多工具，库迁移，比较便捷
 
 ## 5.5 配置
+
 我们这边就是修改一下配置，调整迁移的库所在数据库服务器的地址
 
 ## 5.6 发布
+
 重新发布系统，上线，原先的路由规则变都不用变，直接可以基于2倍的数据库服务器的资源，继续进行线上系统的提供服务
 
+- 分库分表扩容方案![](https://ask.qcloudimg.com/http-save/1752328/88hu11t4xh.png)
 
-- 分库分表扩容方案![](https://img-blog.csdnimg.cn/20190713124154927.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
+# 参考
 
-参考
-- 《Java工程师面试突击》
\ No newline at end of file
+- 《Java工程师面试突击第1季-中华石杉老师》
\ No newline at end of file
diff --git "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\346\274\224\350\277\233\345\217\262.md" "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\357\274\21053\357\274\211- \345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\347\232\204\346\274\224\350\277\233\350\277\207\347\250\213.md"
similarity index 86%
rename from "\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\346\274\224\350\277\233\345\217\262.md"
rename to "\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\357\274\21053\357\274\211- \345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\347\232\204\346\274\224\350\277\233\350\277\207\347\250\213.md"
index b48e2e87a1..b05cc7fd5e 100644
--- "a/\346\236\266\346\236\204/\345\210\206\345\270\203\345\274\217\345\276\256\346\234\215\345\212\241/\345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\346\274\224\350\277\233\345\217\262.md"
+++ "b/\351\235\242\350\257\225\351\242\230\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\347\263\273\345\210\227/\347\252\201\347\240\264Java\351\235\242\350\257\225\357\274\21053\357\274\211- \345\210\206\345\270\203\345\274\217\346\236\266\346\236\204\347\232\204\346\274\224\350\277\233\350\277\207\347\250\213.md"	
@@ -1,3 +1,10 @@
+目标
+1. 了解分布式架构中的相关概念 
+2. 初始分布式架构及意义
+3. 分布式架构的发展过程和历史
+4. 分布式架构的演进过程
+5. 构建分布式架构最重要的因素
+
 # 1 分布式架构的发展历史
 1946 年情人节(2.14) , 世界上第一台电子数字计算机诞生在美 国宾夕法尼亚大学大学，它的名字是:ENIAC; 这台计算机占地 170 平米、重达 30 吨，每秒可进行 5000 次加法运算。 第一台电子计算机诞生以后，意味着一个日新月异的 IT 时代 的到来。一方面单台计算机的性能每年都在提升:从最早的 8 位 CPU 到现在的 64 位 CPU;从早期的 MB 级内存到现在的 GB 级别内存;从慢速的机械存储到现在的固态 SSD 硬盘存储。
 
@@ -102,7 +109,7 @@ session
 
 ## 6.6 阶段六 - 引入缓存机制缓解数据库的压力
 ![](https://img-blog.csdnimg.cn/20190522001432387.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
-随着访问量的持续增加，逐渐出现许多用户访问统一部分内容的情况，对于这些热点数据，没必要每次都从数据库去读取，我们可以使用缓存技术，比如 memcache、redis 来作为我们应用层的缓存; 另外在某些场景下，比如我们对用户的某些 IP 的访问频率做限制， 那这个放内存中又不合适，放数据库又太麻烦，这个时候可以使用 Nosql 的方式比如 mongDB 来代替传统的关系型数据库
+随着访问量的持续增加，逐渐出现许多用户访问统一部分内容的情况，对于这些热点数据，没必要每次都从数据库去读取，我们可以 使用缓存技术，比如 memcache、redis 来作为我们应用层的缓存; 另外在某些场景下，比如我们对用户的某些 IP 的访问频率做限制， 那这个放内存中又不合适，放数据库又太麻烦，这个时候可以使用 Nosql 的方式比如 mongDB 来代替传统的关系型数据库
 
 ## 6.7 阶段七 - 数据库的水平/垂直拆分
  ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190522001447457.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_SmF2YUVkZ2U=,size_16,color_FFFFFF,t_70)
@@ -141,7 +148,7 @@ session
 输出和输入类似，也有两种，一种是系统中的节点向其他节点传输 信息时，该节点可以看作是输出设备;另一种就是传统意义上的人 际交互的输出设备，比如用户的终端
 
 控制器的变化
-在单机中，控制器指的是 CPU 中的控制器，在分布式系统中，控 制器主要的作用是协调或控制节点之间的动作和行为;比如硬件负 载均衡器;LVS 软负载;规则服务器等
+在单机中，控制器指的是 CPU 中的控制器，在分布式系统中，控制器主要的作用是协调或控制节点之间的动作和行为;比如硬件负载均衡器;LVS 软负载;规则服务器等
 
 运算器
 在分布式系统中，运算器是由多个节点来组成的。运用多个节点的 计算能力来协同完成整体的计算任务
@@ -157,14 +164,10 @@ session
 在集中式架构中，我们调用一个接口返回的结果只有两种， 成 功或者失败，但是在分布式领域中，会出现“超时”这个状态。
 
 ### 分布式事务
-事务就是一些列操作的原子性保证。单机时，我们能仅依靠本机的数据库连接和组件就做到事务控制。
+这是一个老生常谈的问题，我们都知道事务就是一些列操作的 原子性保证，在单机的情况下，我们能够依靠本机的数据库连 接和组件轻易做到事务的控制，但是分布式情况下，业务原子 性操作很可能是跨服务的，这样就导致了分布式事务，例如 A 和 B 操作分别是不同服务下的同一个事务操作内的操作，A 调 用 B，A 如果可以清楚的知道 B 是否成功提交从而控制自身的 提交还是回滚操作，但是在分布式系统中调用会出现一个新状 态就是超时，就是 A 无法知道 B 是成功还是失败，这个时候 A 是提交本地事务还是回滚呢?其实这是一个很难的问题，如果 强行保证事务一致性，可以采取分布式锁，但是那样会增加系 统复杂度而且会增大系统的开销，而且事务跨越的服务越多， 消耗的资源越大，性能越低，所以最好的解决方案就是避免分 布式事务。 还有一种解决方案就是重试机制，但是重试如果不是查询接口，
+
 
-但在分布式下，业务的原子性操作大多跨服务，这就产生分布式事务。例如 A 和 B 操作分别是不同服务下的同一个事务操作内的操作，A 调用 B，A 若可清楚 B 是否成功提交从而控制自身的提交or回滚。但在分布式系统中调用会出现一个新状态就是超时：即A 无法知道 B 是成功还是失败，这时 A 是提交本地事务还是回滚呢?
-这是一个很难回答的问题，如果强行保证事务一致性，可采取分布式锁，但那会增加系统复杂度&&增大系统性能开销，而且事务跨越的服务越多，消耗的资源越大，性能越低，所以
-- 最好的解决方案就是避免分布式事务
-- 还有一种解决方案就是重试机制，但重试如果不是重试查询接口，必然涉及到数据库变更，如果第一次调用成功但是没返回成功结果，那调用方第二次调用对调用方来说依然是重试，但对于被调用方来说是重复调用。
-例如 A 向 B 转账，A-100，B + 100，这会导致 A 扣了 100，而 B 增加 200。这样的结果不是期望的，因此需在要写入的接口做幂等设计。多次调用和单次调用是一样效果。通常可设置一个唯一键，在写入时查询是否已经存，避免重复写入。但幂等设计的一个前提是**服务高可用**，否则无论怎么重试都不能调用返回 一个明确的结果，调用方就会一直等待。虽然可以限制重试的次数， 但这已进入异常状态了，甚至到了极端情况还是需要人工补偿。
-根据 CAP 和 BASE 理论可知，不可能在高可用和分布式情况下还做到一致性，所以一般都是做最终一致性保证。
+ 必然涉及到数据库的变更，如果第一次调用成功但是没返回成 功结果，那调用方第二次调用对调用方来说依然是重试，但是 对于被调用方来说是重复调用，例如 A 向 B 转账，A-100,B + 100，这样会导致 A 扣了 100，而 B 增加 200。这样的结果不 是我们期望的，因此需在要写入的接口做幂等设计。多次调用 和单次调用是一样的效果。通常可以设置一个唯一键，在写入 的时候查询是否已经存在，避免重复写入。但是幂等设计的一 个前提就是服务是高可用，否则无论怎么重试都不能调用返回 一个明确的结果调用方会一直等待，虽然可以限制重试的次数， 但是这已经进入了异常状态了，甚至到了极端情况还是需要人 肉补偿处理。其实根据 CAP 和 BASE 理论，不可能在高可用分 布式情况下做到一致性，一般都是最终一致性保证。
 
 ### 负载均衡
 每个服务单独部署，为了达到高可用，每个服务至少是两台机 器，因为互联网公司一般使用可靠性不是特别高的普通机器， 长期运行宕机概率很高，所以两台机器能够大大降低服务不可 用的可能性，这正大型项目会采用十几台甚至上百台来部署一 个服务，这不仅是保证服务的高可用，更是提升服务的 QPS， 但是这样又带来一个问题，一个请求过来到底路由到哪台机器? 路由算法很多，有 DNS 路由，如果 session 在本机，还会根据 用户 id 或则 cookie 等信息路由到固定的机器，当然现在应用
@@ -174,4 +177,4 @@ session
 ### 一致性
 数据被分散或者复制到不同的机器上，如何保证各台主机之间 的数据的一致性将成为一个难点。
 故障的独立性
-分布式系统由多个节点组成，整个分布式系统完全出问题的概 率是存在的，但是在时间中出现更多的是某个节点出问题，其 他节点都没问题。这种情况下我们实现分布式系统时需要考虑 得更加全面些
\ No newline at end of file
+分布式系统由多个节点组成，整个分布式系统完全出问题的概 率是存在的，但是在时间中出现更多的是某个节点出问题，其 他节点都没问题。这种情况下我们实现分布式系统时需要考虑 得更加全面些
