From f38c36012e76b218a8fa8eb92dcf9e1be84a1ff7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Fri, 21 Dec 2018 18:03:37 +0800 Subject: [PATCH 01/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=AE=98?= =?UTF-8?q?=E7=BD=91Tomcat=E7=9A=84=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../networking/JavaNetServerSocket.md | 0 resource/markdown/networking/JavaNetSocket.md | 0 resource/markdown/tomcat8/conf-server.md | 105 ++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 resource/markdown/networking/JavaNetServerSocket.md create mode 100644 resource/markdown/networking/JavaNetSocket.md create mode 100644 resource/markdown/tomcat8/conf-server.md diff --git a/resource/markdown/networking/JavaNetServerSocket.md b/resource/markdown/networking/JavaNetServerSocket.md new file mode 100644 index 0000000..e69de29 diff --git a/resource/markdown/networking/JavaNetSocket.md b/resource/markdown/networking/JavaNetSocket.md new file mode 100644 index 0000000..e69de29 diff --git a/resource/markdown/tomcat8/conf-server.md b/resource/markdown/tomcat8/conf-server.md new file mode 100644 index 0000000..ab0b632 --- /dev/null +++ b/resource/markdown/tomcat8/conf-server.md @@ -0,0 +1,105 @@ +

Tomcat

+ +#### Servlet是什么? + +Servlet的全称Server Applet,是Java Servlet的简称,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中。从原理上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。 + +#### Server Applet是什么? + +Server Applet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面。它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器响应(HTTP服务器上的数据库或应用程序)的中间层。Servlet是位于Web服务器内部的服务器端的Java应用程序,与传统的从命令启动的Java应用程序不同,Servlet由服务器加载,该Web服务器必须包含支持Servlet的Java虚拟机。 + +#### Tomcat是什么? + +Tomcat 服务器是一个免费的开放源代码的Web应用服务器,Servlet容器,Catalina容器。 + +#### Catalina是什么? + +这个Web容器的代号(他称、小名)。 + + + +--- + +#### 支持Servlet的Web容器是如何工作的? + +1、创建一个request对象,接受请求并填充可能被servlet用到的信息,比如:URI、头信息、cookies、参数等等。一个request对象是 `javax.servlet.ServletRequest` 或 `javax.servlet.http.ServletRequest` 接口的一个实例对象。 + +2、创建一个response对象,填充数据,它所引用的servlet使用它给客户端发送响应。一个response对象是 `javax.servlet.ServletResponse` 或 `javax.servlet.http.ServletResponse` 接口的一个实例对象。 + +3、调用servlet的service方法,并传入request和response对象,从request对象里面取值,处理,将处理后的结果写入response。 + + + +### Tomcat的架构 + +![Tomcat的架构]() + +Tomcat容器最重要的两个组件是 **连接器**`Connector` 和 **容器**`Container` + + + +1、网络基础: + +HTTP服务器,java.net.Socket和java.net.ServerSocket源码与原理分析。 + +2、Servlet容器工作机制 + +Servlet是如何工作的,创建request和response对象,并传递给service方法。 + +3、连接器Connector + +4、容器Container + +这里的容器是org.apache.catalina.Container接口,有四种类型的Container:Engine、Host、Context、Wrapper。主要是Context和Wrapper。 + +5、生命周期Lifecycle接口 + +是如何start/stop启动和停止组件的。 + +6、Tomcat日志框架 + +7、加载器 + +加载servlet和第一个web应用所需的其他类。 + +8、管理器Manager + +管理会话、管理会话信息。如何把会话持久化。使用StandardManager实例来运行一个使用会话对象进行储值的servlet。 + +9、Web应用程序的安全性 + +实例:主角(principals)、角色(Roles)、登录配置,认证等等。 + +10、用servlet的org.apache.catalina.core.StandardWrapper类的实例对象来表示一个Web应用 + +过滤器filter和service方法是如何调用的 + +11、用servlet的org.apache.catalina.core.StandardContext类的实例对象来表示一个Web应用 + +StandardContext是如何配置的,每次HTTP请求发生了什么,如何支持自动重新加载。如何处理共享的定时任务。 + +12、Engine和Host容器 + +它们的标准实现:`org.apache.catalina.core.StandardHost` 和 `org.apache.catalina.core.StandardEngine` 。 + +13、服务器和服务组件 + +服务器为 servlet 容器提供了优雅的启动和停止机制,而服务为容器和一个或多个连接器提供了一个支架。 + +14、通过 Digester 来配置Web应用 + +使用Digester库把Xml转换成Java对象。然后解释成一个StandardContext对象。 + +15、shutdown hook钩子 + +Tomcat使用它总能获得一个机会用户 clean-up,而无论用户怎么停止它(既适当的发送一个shutdown命令或者不适当的简单关闭控制台)。 + + + + + +#### Tomcat的IO模型 + + + +#### conf/server.xml配置 \ No newline at end of file From 72a84fef11889fe05456db54370b0590735717ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Fri, 28 Dec 2018 17:52:10 +0800 Subject: [PATCH 02/39] =?UTF-8?q?tomcat=E5=88=86=E6=9E=90=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BA=86=E7=AC=AC=E4=B8=80=E7=AB=A0=EF=BC=9A=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E7=AE=80=E5=8D=95=E7=9A=84web=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E5=99=A8=20=E7=AC=94=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/tomcat8/conf-server.md | 80 ++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/resource/markdown/tomcat8/conf-server.md b/resource/markdown/tomcat8/conf-server.md index ab0b632..4b7f75e 100644 --- a/resource/markdown/tomcat8/conf-server.md +++ b/resource/markdown/tomcat8/conf-server.md @@ -16,6 +16,20 @@ Tomcat 服务器是一个免费的开放源代码的Web应用服务器,Servlet 这个Web容器的代号(他称、小名)。 +#### Web是什么? + +Web(World Wide Web)即全球广域网,也称为万维网,它是一种基于超文本和HTTP的、全球性的、动态交互的、跨平台的分布式图形信息系统。是建立在Internet上的一种网络服务,为浏览者在Internet上查找和浏览信息提供了图形化的、易于访问的直观界面,其中的文档及超级链接将Internet上的信息节点组织成一个互为关联的网状结构。 + +#### Web服务器是什么? + +Web服务器一般指网站服务器,是指驻留于[因特网](https://baike.baidu.com/item/%E5%9B%A0%E7%89%B9%E7%BD%91/114119)上某种类型计算机的程序,可以向浏览器等Web客户端提供文档,也可以放置网站文件,让全世界浏览;可以放置数据文件,让全世界下载。目前最主流的三个Web服务器是Apache、Nginx、IIS。 + + + +#### Apache、Nginx、IIS、Jetty区别? + + + --- @@ -94,6 +108,70 @@ StandardContext是如何配置的,每次HTTP请求发生了什么,如何支 Tomcat使用它总能获得一个机会用户 clean-up,而无论用户怎么停止它(既适当的发送一个shutdown命令或者不适当的简单关闭控制台)。 +16、通过batch批处理文件和shell脚本对Tomcat进行启动和停止 + +17、部署工具deployer + +deployer组件负责部署和安装web应用 + +18、ContainerServlet 特殊接口 + +能够让servlet访问Catalina的内部对象。通过Manager应用来部署应用程序。 + +19、JMX以及Tomcat是如何为其内部对象创建MBeans 是得这些对象可管理的 + + + +#### 第一章:一个简单的web服务器 + +Web服务器也称谓超文本传输协议(HTTP)服务器,因为它是基于HTTP来进行客户端和服务端进行通信的。一个基于Java的Web服务器使用的两个重要的类:`java.net.Socket` 和 `java.net.ServerSocket` ,并通过HTTP消息通信。 + +HTTP协议是基于请求(request)和响应(response)的协议。HTTP协议又是使用可靠的TCP来连接,默认80端口(HTTPS默认使用443端口)。 + +**HTTP请求** + +一个http请求有三个部分: + +- 方法 / 统一资源标识符(URI) / 协议版本 +- 请求的头head +- 请求的体body + +**HTTP响应** + +一个http响应有三个部分: + +- 方法 / 统一资源标识符(URI) / 协议版本 +- 响应的头head +- 响应的体body + + + +**java.net.Socket 类** + +套字节是网络连接的端点。套字节使得每一个应用可以从网络中写入和读取数据。在网络端点的两个不同的计算上的两个应用可以通过连接发送和接受字节流。 + +**Socket** 带表着客户端,**ServerSocket** 带表着服务端,服务器总是等待着客户端的连接。 + +Socket -- 网络与机器流 + +I/O流 -- 动态流动 + +File本地 -- 静态与动态转换 + + + +#### 第二章 :一个简单的Servlet容器 + +Servlet编程需要 `javax.servlet` 和 `javax.servlet.http` 这两个包中的接口来实现。 + +`javax.servlet.ServletRequest` 接口封装着客户端的HTTP请求,`javax.servlet.ServletResponse` 封装了 servlet 响应。 + +使用 `java.net.URLClassLoader` 类加载 servlet 。 + + + + + @@ -102,4 +180,6 @@ Tomcat使用它总能获得一个机会用户 clean-up,而无论用户怎么 + + #### conf/server.xml配置 \ No newline at end of file From c909760e23ec855757f17e8467c36460a9226ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 2 Jan 2019 19:04:51 +0800 Subject: [PATCH 03/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=AF=B9tomca?= =?UTF-8?q?t=E7=9A=84server.xml=E8=8A=82=E7=82=B9=E7=9A=84=E5=88=86?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/tomcat8/conf-server.md | 145 ++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/resource/markdown/tomcat8/conf-server.md b/resource/markdown/tomcat8/conf-server.md index 4b7f75e..01e90cd 100644 --- a/resource/markdown/tomcat8/conf-server.md +++ b/resource/markdown/tomcat8/conf-server.md @@ -182,4 +182,147 @@ Servlet编程需要 `javax.servlet` 和 `javax.servlet.http` 这两个包中的 -#### conf/server.xml配置 \ No newline at end of file +#### conf/server.xml配置 + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + From 1f85901be0e37ef03083c457141dbe20e3c074c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Thu, 3 Jan 2019 19:41:46 +0800 Subject: [PATCH 04/39] =?UTF-8?q?=E6=BB=91=E5=8A=A8=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../multithreads/AbstractQueuedSynchronizer.md | 4 ++-- .../markdown/networking/SlidingWindowProtocol.md | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/resource/markdown/multithreads/AbstractQueuedSynchronizer.md b/resource/markdown/multithreads/AbstractQueuedSynchronizer.md index 25bcacf..c184c19 100644 --- a/resource/markdown/multithreads/AbstractQueuedSynchronizer.md +++ b/resource/markdown/multithreads/AbstractQueuedSynchronizer.md @@ -84,7 +84,7 @@ public abstract class AbstractOwnableSynchronizer > * java.util.concurrent.locks.AbstractQueuedSynchronizer.**setState(int newState)** > * java.util.concurrent.locks.AbstractQueuedSynchronizer.**compareAndSetState(int expect, int update)** * **2.1、源码分析:** -``` +```java /** * 返回同步状态的当前值。 * 此操作具有易失性 volatile 读取的内存语义。 @@ -121,7 +121,7 @@ protected final boolean compareAndSetState(int expect, int update) { * **2.2、FIFO 队列** (类似双向链表) > 同步器的开始提到了其实现依赖于一个 **FIFO 队列**,那么队列中的 **元素节点Node** 就是保存着 **线程引用** 和 **线程状态** 的 **容器**,**每个线程对同步器的访问,都可以看做是队列中的一个节点Node**。Node的主要包含以下成员变量: -``` +```java static final class Node { /** 作为标记表示节点正在 共享模式 中等待 */ static final Node SHARED = new Node(); diff --git a/resource/markdown/networking/SlidingWindowProtocol.md b/resource/markdown/networking/SlidingWindowProtocol.md index 98bde25..859595f 100644 --- a/resource/markdown/networking/SlidingWindowProtocol.md +++ b/resource/markdown/networking/SlidingWindowProtocol.md @@ -1,3 +1,12 @@ -

滑动窗口协议

+![滑动窗口](https://i.loli.net/2019/01/03/5c2d78ac2d805.jpeg) + +

什么是滑动窗口协议

+ +**滑动窗口协议**(*Sliding Window Protocol*),属于[TCP协议](https://baike.baidu.com/item/TCP%E5%8D%8F%E8%AE%AE)的一种应用,用于网络数据传输时的 **流量控制**,以 **避免拥塞** 的发生。该协议允许发送方在停止并等待确认前发送多个数据分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据的传输,提高网络吞吐量。 + + + +

滑动窗口协议的机制

+ +![滑动窗口协议的机制]() -> TCP 的安全 \ No newline at end of file From b6e6f200e1c43b772b5a7afafdc977fa10bc91e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Thu, 10 Jan 2019 23:46:57 +0800 Subject: [PATCH 05/39] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E7=BD=91?= =?UTF-8?q?=E7=BB=9C=E6=A8=A1=E5=9E=8B=E5=92=8CTCP=E4=B8=89=E6=AC=A1?= =?UTF-8?q?=E6=8F=A1=E6=89=8B=E5=92=8C=E5=9B=9B=E6=AC=A1=E6=8C=A5=E6=89=8B?= =?UTF-8?q?=E7=9A=84=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/networking/NetworkModel.md | 8 +++++--- resource/markdown/networking/TCPConnectAndDisconnect.md | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/resource/markdown/networking/NetworkModel.md b/resource/markdown/networking/NetworkModel.md index 32ef43e..73b4c0b 100644 --- a/resource/markdown/networking/NetworkModel.md +++ b/resource/markdown/networking/NetworkModel.md @@ -10,7 +10,7 @@ > > 七层模型的划分如下: -![ISO的OSI七层网络模型]() +![ISO的OSI七层网络模型](https://i.loli.net/2019/01/10/5c3753e50b452.png) #### 7、应用层 @@ -74,7 +74,7 @@ > OSI七层网络模型 过于繁杂,而应用更为广泛的网络模型是 TCP/IP四层的参考模型,它们都很相似。 -![ TCP/IP四层参考模型]() +![ TCP/IP四层参考模型](https://i.loli.net/2019/01/10/5c37573d0cc07.png) #### 4、应用层 @@ -98,4 +98,6 @@

三、五层网络模型

-> 还有少数资料将网络协议划分为五层:应用层、传输层、网络层、数据链路层、物理层。 \ No newline at end of file +> 还有少数资料将网络协议划分为五层:应用层、传输层、网络层、数据链路层、物理层。 + +![五层因特网协议栈](https://i.loli.net/2019/01/10/5c375848ea8f7.png) \ No newline at end of file diff --git a/resource/markdown/networking/TCPConnectAndDisconnect.md b/resource/markdown/networking/TCPConnectAndDisconnect.md index 78f3ae0..4a947c5 100644 --- a/resource/markdown/networking/TCPConnectAndDisconnect.md +++ b/resource/markdown/networking/TCPConnectAndDisconnect.md @@ -4,7 +4,7 @@

一、3次握手过程

-![三次握手]() +![TCP三次握手](https://i.loli.net/2019/01/10/5c3768734d0d2.png) #### 第一次握手 @@ -32,7 +32,7 @@

二、4次挥手过程

-![三次挥手]() +![TCP四次挥手](https://i.loli.net/2019/01/10/5c376853c0109.png) #### 第一次挥手 From 8e080ba4e2c5e6db35da7f65ed895a4b55baef51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Mon, 14 Jan 2019 00:05:37 +0800 Subject: [PATCH 06/39] =?UTF-8?q?1=E3=80=81=E5=88=86=E6=9E=90=E4=BA=86TCP?= =?UTF-8?q?=E5=92=8CUDP=E7=9A=84=E5=A4=B4=E9=83=A8=EF=BC=9B2=E3=80=81?= =?UTF-8?q?=E5=88=86=E6=9E=90=E4=BA=86=E6=BB=91=E5=8A=A8=E7=AA=97=E5=8F=A3?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../networking/SlidingWindowProtocol.md | 23 +++++- resource/markdown/networking/TCPAndUDP.md | 76 ++++++++++++++++++- 2 files changed, 96 insertions(+), 3 deletions(-) diff --git a/resource/markdown/networking/SlidingWindowProtocol.md b/resource/markdown/networking/SlidingWindowProtocol.md index 859595f..6f45702 100644 --- a/resource/markdown/networking/SlidingWindowProtocol.md +++ b/resource/markdown/networking/SlidingWindowProtocol.md @@ -6,7 +6,26 @@ -

滑动窗口协议的机制

+

TCP流量控制与滑动窗口协议

-![滑动窗口协议的机制]() +![滑动窗口协议的机制](https://i.loli.net/2019/01/13/5c3b5c7c55887.png) + +TCP滑动窗口从发送端和接收端分为发送窗口和接收窗口,各自维护着一个滑动窗口,以此来达到流量控制。 + + + +#### 关于滑动窗口协议中的概念 + +**发送端:** + +* 已发送并收到确认:已经发送数据段,并且这些数据被接收端已确认接收; +* 已发送,未收到确认:已经发送数据段,但发送端未接收到由接收端发送的已接收的确认消息; +* 未发送,可以发送:在流量控制中,数据量未达发送的到阈值,是可以由发送端发送的,但发送端还未来得及发送; +* 未发送,不允许发送:发送端发送的数据过多,但很多数据未被确认接收,为达到流量控制的效果,这些数据段不允许被发送。 + +**接收端:** + +* 已发送确认并已交付主机:接收端对已接收的数据交付给内部使用,并且发送确认已收到的消息给发送端,表示客户端已收到相应的数据,避免数据丢失; +* 允许接受:在接受窗口中,未达到阈值的情况下,接收端允许接受数据的窗口; +* 不允许接受:为了达到流量控制,接收到不能无限制的接受数据,达到接受窗口的阈值时,接受端将不允许接受数据。 diff --git a/resource/markdown/networking/TCPAndUDP.md b/resource/markdown/networking/TCPAndUDP.md index 1683a51..2c2e9f8 100644 --- a/resource/markdown/networking/TCPAndUDP.md +++ b/resource/markdown/networking/TCPAndUDP.md @@ -10,13 +10,80 @@ +**TCP的头部** + +![TCP包首部格式](https://i.loli.net/2019/01/13/5c3b36cf5fc20.png) + +* **32位端口号:** + +源端口和目的端口各占16位,2的16次方等于65536,查看看端口的命令:netstat。 + +* **32位序列号:** + +也称为顺序号(Sequence Number),简写为seq, + +* **32位确认序号:** + +也称为应答号(Acknowledgment Number),简写为ACK。在握手阶段,确认序号将发送方的序号加1作为回答。 + +* **4位首部长度:** + +这个字段占4位,它的单位时32位(4个字节)。本例值为7,TCP的头长度为28字节,等于正常的长度2 0字节加上可选项8个字节。,TCP的头长度最长可为60字节(二进制1111换算为十进制为15,15*4字节=60字节)。 + +* **6位标志字段:** + +ACK 置1时表示确认号为合法,为0的时候表示数据段不包含确认信息,确认号被忽略。 + +RST 置1时重建连接。如果接收到RST位时候,通常发生了某些错误。 + +SYN 置1时用来发起一个连接。 + +FIN 置1时表示发端完成发送任务。用来释放连接,表明发送方已经没有数据发送了。 + +URG 紧急指针,告诉接收TCP模块紧要指针域指着紧要数据。注:一般不使用。 + +PSH 置1时请求的数据段在接收方得到后就可直接送到应用程序,而不必等到缓冲区满时才传送。注:一般不使用。 + +* **16位窗口大小:** + +又称接受窗口字段(Receive Window Field),该字段用于流量控制。用于指示接收方愿意接受的字节数量。 + +* **16位检验和:** + +又称因特网校验和,检验和覆盖了整个的TCP报文段: TCP首部和TCP数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。 + +* **16位紧急指针:** + +**注:**一般不使用。 + +又称紧急数据指针,只有当URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。 + +* **可选与变长选项**: + +通常为空,可根据首部长度推算。用于发送方与接收方协商最大报文段长度(MSS),或在高速网络环境下作窗口调节因子时使用。首部字段还定义了一个时间戳选项。 + +最常见的可选字段是最长报文大小,又称为MSS (Maximum Segment Size)。每个连接方通常都在握手的第一步中指明这个选项。它指明本端所能接收的最大长度的报文段。1460是以太网默认的大小。 + + +

UDP

> UDP,User Data Protocol,用户数据报协议,面向 **非连接** 的传输协议,既传输数据前客户端和服务端 **无需** 建立连接,数据也可被传输,但 *被传输* 不带表一定传输成功。传世时直接将数据包丢给对方。 -#### 对比 +**UPD的头部** + +![UDP包首部格式](https://i.loli.net/2019/01/13/5c3b39228488e.png) + +* 16位源端口号 记录源端口号,在需要对方回信时选用。不需要时可用全0。 +* 16位目的端口号 记录目标端口号。这在终点交付报文时必须要使用到。 +* 长度 UDP数据报的长度(包括数据和首部),其最小值为8B(即仅有首部没有数据的情况)。 +* 校验和 检测UDP数据报在传输中是否有错,有错就丢弃。该字段时可选的,当源主机不想计算校验和,则直接令该字段为全0。当传输层从IP层收到UDP数据报时,就根据首部中的目的端口,把UDP数据报通过相应的端口,上交给进程。如果接收方UDP发现收到的报文中目的端口号不正确(即不存在对应端口号的应用进程),就丢弃该报文,并由ICMP发送“端口不可达”差错报文交给发送方。 + + + +

对比

| 对比维度\传输协议 | TCP | UDP | | :---------------- | ---------------------- | -------------------- | @@ -25,3 +92,10 @@ | 速度 | 慢 | 快 | | 使用环境 | 传输数据量少、可靠性高 | 传输数量多、可靠性低 | + + +> 参考资料: +> +> [TCP、UDP报文结构与区别](https://blog.csdn.net/cbjcry/article/details/84650730) +> +> [UDP协议的特点及UDP头部结构](https://blog.csdn.net/ASJBFJSB/article/details/80357111) \ No newline at end of file From 48efca39658ccafe30437185371e662ee7cc5d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Mon, 14 Jan 2019 19:01:16 +0800 Subject: [PATCH 07/39] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E4=BA=8B=E5=8A=A1=E7=9A=84ACID=E7=89=B9?= =?UTF-8?q?=E6=80=A7=E3=80=81InnoDB=E8=AE=B2=E8=A7=A3=E3=80=81MySQL?= =?UTF-8?q?=E9=94=81=E6=80=BB=E7=BB=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 ++-- .../database/ACIDAndIsolationLevel.md | 39 +++-- resource/markdown/database/InnoDB.md | 86 +++++++++- resource/markdown/database/MySQLLock.md | 155 +++++++++++++++++- ...simisticlockAndOptimisticlock.md => XA.md} | 3 +- .../networking/TCPConnectAndDisconnect.md | 100 ++--------- 6 files changed, 288 insertions(+), 127 deletions(-) rename resource/markdown/database/{PessimisticlockAndOptimisticlock.md => XA.md} (57%) diff --git a/README.md b/README.md index 33912d0..e744acb 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ * [Java多线程与并发框 (第 05 篇) 深入理解:顺序一致性模型](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/SequentialConsistencyModel.md) * [Java多线程与并发框 (第 06 篇) 深入理解:synchronized 关键字](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/synchronized.md) * [Java多线程与并发框 (第 07 篇) 深入理解:volatile 关键字](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/volatile.md) -* [Java多线程与并发框 (第 08 篇) 深入理解:Java各种锁与无锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/.md) +* [Java多线程与并发框 (第 08 篇) 深入理解:Java各种锁与无锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/LockAndLock-free.md) * [Java多线程与并发框 (第 09 篇) 深入理解:CAS](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/CompareAndSwap.md) * [Java多线程与并发框 (第 10 篇) 深入理解:并发包的基石 -- 队列同步器 AQS](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/AbstractQueuedSynchronizer.md) * [Java多线程与并发框 (第 11 篇) 深入理解:并发辅助工具类(很好的玩的工具类)](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/ConcurrentHelperUtil.md) @@ -59,7 +59,7 @@ * [网络协议 (第 04 篇) 精讲:TCP和UPD的区别与对比](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/TCPAndUDP.md) * [网络协议 (第 05 篇) 精讲:TCP三次握手与四次挥手](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/TCPConnectAndDisconnect.md) * [网络协议 (第 06 篇) 精讲:滑动窗口协议](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/SlidingWindowProtocol.md) -* [网络协议 (第 07 篇) 精讲:一位用户从浏览器地址框输入url、按下回车键后,经历了什么](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/RequestAndResponse.md) +* 网络协议 (第 07 篇) 精讲:一位用户从浏览器地址框输入url、按下回车键后,经历了什么] * 网络协议 (第 08 篇) 精讲:加密 * [网络协议 (第 09 篇) 精讲:IP地址的分类](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/IPAddressClassification.md) * [网络协议 (第 10 篇) 精讲:常见HTTP状态码及含义](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/StateCode.md) @@ -109,25 +109,25 @@ * [数据库 (第 02 篇)精讲:事务的ACID四大特性与四种隔离级别](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/ACIDAndIsolationLevel.md) * [数据库 (第 03 篇)精讲: MySQL InnoDB 索引](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/InnoDB.md) * [数据库 (第 04 篇)精讲: MySQL锁机制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/MySQLLock.md) -* [数据库 (第 05 篇)精讲: 悲观锁与乐观锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/PessimisticlockAndOptimisticlock.md) -* [数据库 (第 06 篇)精讲: SQL优化](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/SQLOptimization.md) -* [数据库 (第 07 篇)精讲: 数据库拆分](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/DBSplit.md) +* [数据库 (第 05 篇)精讲: SQL优化](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/SQLOptimization.md) +* [数据库 (第 06 篇)精讲: 数据库拆分](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/DBSplit.md) ### 八、:telescope::tokyo_tower::satellite:分布式系统 * [分布式系统 (第 01 篇) 精讲:集群与分布式](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/WhatisDistributed.md) * [分布式系统 (第 02 篇) 精讲:CAP定理与BASE理论](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/CAPandBASE.md) -* [分布式系统 (第 03 篇) 精讲:2PC协议和3PC协议](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/2PCand3PC.md) -* 分布式系统 (第 04 篇) 精讲:TCC事务补偿机制(柔性事务方案) -* 分布式系统 (第 05 篇) 精讲:Paxos算法(强一致性算法) -* 分布式系统 (第 06 篇) 精讲:Chubby 与 Zookeeper -* 分布式系统 (第 07 篇) 精讲:设计分布式锁 -* 分布式系统 (第 08 篇) 精讲:分布式session/token一致性设计 -* 分布式系统 (第 09 篇) 精讲:分布式事务 -* 分布式系统 (第 10 篇) 精讲:分布式缓存 -* 分布式系统 (第 11 篇) 精讲:分布式高并发的支持与控制 -* 分布式系统 (第 12 篇) 精讲:分布式下悲观锁和乐观锁的实现 -* 分布式系统 (第 13 篇) 精讲:分布式支付框架的实现 +* [分布式系统 (第 03 篇) 精讲:XA事务](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/XA.md) +* [分布式系统 (第 04 篇) 精讲:2PC协议和3PC协议](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/2PCand3PC.md) +* 分布式系统 (第 05 篇) 精讲:TCC事务补偿机制(柔性事务方案) +* 分布式系统 (第 06 篇) 精讲:Paxos算法(强一致性算法) +* 分布式系统 (第 07 篇) 精讲:Chubby 与 Zookeeper +* 分布式系统 (第 08 篇) 精讲:设计分布式锁 +* 分布式系统 (第 09 篇) 精讲:分布式session/token一致性设计 +* 分布式系统 (第 10 篇) 精讲:分布式事务 +* 分布式系统 (第 11 篇) 精讲:分布式缓存 +* 分布式系统 (第 12 篇) 精讲:分布式高并发的支持与控制 +* 分布式系统 (第 13 篇) 精讲:分布式下悲观锁和乐观锁的实现 +* 分布式系统 (第 14 篇) 精讲:分布式支付框架的实现 ### 九、:microscope::microscope::microscope:微服务 diff --git a/resource/markdown/database/ACIDAndIsolationLevel.md b/resource/markdown/database/ACIDAndIsolationLevel.md index 7ab49b5..205be14 100644 --- a/resource/markdown/database/ACIDAndIsolationLevel.md +++ b/resource/markdown/database/ACIDAndIsolationLevel.md @@ -12,23 +12,27 @@ > > 比如一个事务包含两个更新操作A和B,A操作更新成功,而B操作更新失败,则A操作会被回滚。绝不会出现一个成功、一个失败的场景,否则这不是一个事务。 -#### :four_leaf_clover:一致性(Consistency ) -> **一致性** 是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。 -常见的案例描述: +#### :four_leaf_clover:一致性(Consistency ) -> 拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。 +> **一致性** 是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。 -看完上面的故事没发现啥毛病,但不能说明数据库事务的一致性问题。 +保证数据库一致性的主要机制层面:数据库机制层面、业务层面。 #### :umbrella:隔离性(Isolation ) +每个事务都有各自的资源单位,事务与事务之间是互相隔离的、不可见的,而事务的结果是对其他事务可见的。 + +一个数据库系统中存在多个事务,每个事务中的各个子操作是对其他事务不可见的,在提交后的结果是对其他事务可见的。可以理解为资源粒子度的划分与隔离。 -#### :dart:持久性(Durability ) + +#### :memo:持久性(Durability ) + +持久性确保的是一旦提交了事务,即使出现系统故障,该事务的更新也不会丢失。我们可以宽泛的认为,将数据持久化到磁盘。 @@ -38,30 +42,39 @@ -#### 读未提交(read uncommited) +#### 读未提交(READ_UNCOMMITTED) > 就是一个事务A读取另一个事务B *未提交* 的数据。(如果如果事务B出现 **回滚**,那么事务A就会出现 ~~*脏读*~~:x: 的问题)。 - - -#### 读已提交(read commited) +#### 读已提交(READ_COMMITTED) > 一个事务A读取另一个事务B *已提交* 的数据,那么这样可以解决 ~~`脏读`~~ 的问题。保证读取的数据 **已提交**:white_check_mark: 而不能被 ~~回滚~~:back:。在事务B提交前的 **update**,事务A是读取不到的。只有事务B提交后,事务A才能读取到事务B的 `update改动`。出现的问题是 *一个事务范围内两个相同的查询却返回了不同的数据*,那么这就是`不可重复读`。 -#### 可重复读(Repeatable read) +#### 可重复读(REPETABLE_READ) > 事务开启时,不再允许其他事务 ~~修改(update)~~ 数据。这样就可以无限制的读取没有被 ~~修改(update)~~ 的数据了。出现的问题是,当有 **并行插入(insert)** 操作时就会出现 **幻读**。 -#### 可串行化(Serializable) +#### 可串行化(SERIALIZABLE) > 在可串行化的隔离级别下,将事务 **串行化** 顺序执行。那么事务不能进行并行操作,也就解决了 ~~*幻读*~~ 的问题。 -***InnoDB** 的默认事务隔离级别是:**可重复读**。 \ No newline at end of file +**总结:** + +| 隔离级别 | 脏读 | 可重复读 | 幻读 | +| ------------------ | ---- | -------- | ---- | +| READ_UNCOMMITTED | 允许 | 允许 | 允许 | +| READ_COMMITTED | 阻止 | 允许 | 允许 | +| **REPETABLE_READ** | 阻止 | 阻止 | 允许 | +| SERIALIZABLE | 阻止 | 阻止 | 阻止 | + + + +***InnoDB** 的默认事务隔离级别是:**可重复读(REPETABLE_READ)**。 \ No newline at end of file diff --git a/resource/markdown/database/InnoDB.md b/resource/markdown/database/InnoDB.md index 3daa004..132da3b 100644 --- a/resource/markdown/database/InnoDB.md +++ b/resource/markdown/database/InnoDB.md @@ -1,13 +1,83 @@ -

MySQL存储引擎

+

InnoDB 索引

-存储引擎对比 +#### InnoDB特征 -| 引擎 | 存储限制 | 支持事务 | 全文索引 | 哈希索引 | 数据缓存 | 支持外键 | -| ------- | -------- | :------: | :------: | :------: | :------: | :------: | -| InnoDB | 64TB | ✅ | :x: | ✅ | ✅ | ✅ | -| MyISAM | 256TB | :x: | ✅ | ✅ | :x: | :x: | -| Memory | RAM | :x: | :x: | ✅ | N/A | :x: | -| Archive | None | :x: | :x: | :x: | :x: | :x: | +* 完全的事务支持 +* 基于行存储的行级锁 +* 多版本并发控制 +* 原子死锁检测 +* 原子崩溃恢复 + + +#### InnoDB架构 + +![InnoDB架构](https://i.loli.net/2019/01/14/5c3c064b9dcee.png) + +#### InnoDB 逻辑存储结构 + +在InnoDB 下,所有的数据都存储在一个 **表空间(tablespace)** 中。表空间又由段(segment)、区(extent)、页(page)、行(row)组成。页在一些文档中有时也称为块(block),1 extent = 64 pages,InnoDB存储引擎的逻辑存储结构大致如图所示。 + +![InnoDB表结构](https://images2015.cnblogs.com/blog/990532/201701/990532-20170116094754739-1789768872.png) + + + +#### B-tree + +定义: + +B树(B-TREE)满足如下条件,即可称之为m阶B树: + +- 每个结点最多拥有m棵子树; +- 根结点最少拥有2颗子树(存在子树的情况下); +- 除了根结点以外,其余每个分支结点至少拥有 m/2 棵子树; +- 所有的叶结点都在同一层上; +- 有 k 棵子树的分支结点则存在 k-1 个关键码,关键码按照递增次序进行排列; +- 关键字数量需要满足ceil(m/2)-1 <= n <= m-1; + +![B-tree](https://i.loli.net/2019/01/14/5c3c2564e867a.png) + +B-tree的特点是每个结点不仅存放键值,而且存放数据。 + +--- + +#### B+tree + +![B+tree](https://i.loli.net/2019/01/14/5c3c24d11402a.png) + + + +**B+树特点:** + +* 所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。 +* 所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。 + + + +**B+树的优点:** + +* 单一节点存储更多的元素(因为不含有对应的值,仅仅含有键),使得查询的IO次数更少。 +* 所有查询都要从根节点查找到叶子节点,查询性能稳定,相对于B树更加稳定,以为B+树只有叶子节点存储了对应的值信息。 +* 所有叶子节点形成有序双向链表,对于SQL的范围查询以及排序查询都很方便。 +* B/B+树的共同优点的每个节点有更多的孩子,插入不需要改变树的高度,从而减少重新平衡的次数,非常适合做数据库索引这种需要持久化在磁盘,同时需要大量查询和插入的应用。树中节点存储这指向页的信息,可以快速定位到磁盘对应的页上面。 + + + +#### 存储引擎对比 + +| 引擎 | 存储限制 | 支持事务 | 全文索引 | 哈希索引 | 数据缓存 | 支持外键 | +| ---------- | -------- | :------: | :------: | :------: | :------: | :------: | +| **InnoDB** | 64TB | ✅ | :x: | ✅ | ✅ | ✅ | +| **MyISAM** | 256TB | :x: | ✅ | ✅ | :x: | :x: | +| Memory | RAM | :x: | :x: | ✅ | N/A | :x: | +| Archive | None | :x: | :x: | :x: | :x: | :x: | + + + +参考资料: + +[InnoDB 存储引擎原理解析](https://zhuanlan.zhihu.com/p/35925589) + +[InnoDB索引最通俗的解释](https://blog.csdn.net/u010710458/article/details/80209909) \ No newline at end of file diff --git a/resource/markdown/database/MySQLLock.md b/resource/markdown/database/MySQLLock.md index cfe4dc7..09f733e 100644 --- a/resource/markdown/database/MySQLLock.md +++ b/resource/markdown/database/MySQLLock.md @@ -1,2 +1,155 @@ -

一、ArrayList

+

MySQL 锁总结

+![Lock](https://i.loli.net/2019/01/14/5c3c27dcc66ca.jpeg) + +**锁**不仅是资源占有的一种处理机制,更是多线程或并发编程下对数据一致性的一种保证。加锁和释放锁本身也会消耗资源。了解并合理利用锁机制,能大大提升数据库的性能。 + + + +锁的作用者是事务,也就是说,锁是针对事务使用而言。单个操作不显式的开启和提交/回滚事务,默认情况下每个操作会自动的开启一个事务。 + +--- + + + +#### 共享锁(S): + +一个事务对数据加共享锁,也可以允许其他事务对此交集数据加此锁。但阻止其他事务对此交集数据加排他锁。 + +加共享锁语句: + +```mysql +SELECT * FROM table_name WHERE ... +LOCK IN SHARE MODE +``` + + + +#### 排他锁(X): + +一个事务对数据加排他锁,会阻止其他事务对此交集数据加任何锁。 + +加排他锁锁语句: + +```mysql +SELECT * FROM table_name WHERE ... +FOR UPDATE +``` + + + +--- + +#### 意向锁: + +为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),在这里的两种意向锁都是表锁(绕脑)。 + + + +#### 意向共享锁(IS): + +事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。 + + + +#### 意向排他锁(IX): + +事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。 + + + +**锁的冲突与兼容:** + +| 锁的冲突与兼容 | X | IX | S | IS | +| -------------- | ---- | ---- | ---- | ---- | +| **X** | 冲突 | 冲突 | 冲突 | 冲突 | +| **IX** | 冲突 | 兼容 | 冲突 | 兼容 | +| **S** | 冲突 | 冲突 | 兼容 | 兼容 | +| **IS** | 冲突 | 兼容 | 兼容 | 兼容 | + +**备注:**意向锁是 InnoDB 自动加的, 不需用户干预。 + + + +--- + +#### 表级锁: + +每个事务操作会锁住整张表,粒子度最大,简单粗暴。优点是加锁和释放锁次数会大大减少。缺点是锁冲突的概率会大大增加,高并发情况下不可取。 + + + +#### 页级锁: + +资源开销介于行级锁和表级锁,会出现死锁。 + + + +#### 行级锁: + +每个事务仅会锁住被影响的行,也就是说,涉及到哪些行记录,哪些行才会被锁住,会出现死锁。优点是锁冲突概率小,并发度高。缺点是由于锁离子度小,加锁和释放锁的次数会大大增加,资源开销大。 + +**切记:**MySQL的行级锁通过索引上的索引项来实现的,InnoDB这种行锁实现特点意味者:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁。 + + + +--- + +#### 间隙锁(Next-Key锁) + +当我们用范围条件而不是相等条件检索数据,并请求共享锁或排他锁时,InnoDB会给符合条件的已有数据的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做 **“间隙(GAP)”** ,InnoDB也会对这个“间隙”加锁,这种锁机制不是所谓的 **间隙锁(Next-Key锁)**。 + +举例来说,假如employee表中只有101条记录,其employee_id的值分别是1,2,...,100,101,下面的SQL: + +```MySQL +SELECT id, name, age, department +FROM employee +WHERE employee_id > 100 +FOR UPDATE +``` + +是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。 + +InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了employee_id大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另一方面,是为了满足其恢复和复制的需要。有关其恢复和复制对机制的影响,以及不同隔离级别下InnoDB使用间隙锁的情况。 + +很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。 + + + +--- + +#### 死锁: + +死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环。当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁。 + +#### InnoDB避免死锁: + +- 为了在单个InnoDB表上执行多个并发写入操作时避免死锁,可以在事务开始时通过为预期要修改的每个记录(行)使用SELECT ... FOR UPDATE语句来获取必要的锁,即使这些行的更改语句是在之后才执行的。 +- 在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁、更新时再申请排他锁,因为这时候当用户再申请排他锁时,其他事务可能又已经获得了相同记录的共享锁,从而造成锁冲突,甚至死锁 +- 如果事务需要修改或锁定多个表,则应在每个事务中以相同的顺序使用加锁语句。 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会 +- 通过SELECT ... LOCK IN SHARE MODE获取行的读锁后,如果当前事务再需要对该记录进行更新操作,则很有可能造成死锁。 +- 改变事务隔离级别 + +如果出现死锁,可以用 SHOW INNODB STATUS 命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的 SQL 语句,事务已经获得的锁,正在等待什么锁,以及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。 + + + +--- + +#### 乐观锁: + +加锁是为了占用资源,我们上面说过加锁和释放锁都会有资源开销。在有些不需要加锁就能获取资源岂不是更好?。乐观锁是乐观的认为在抢占资源时不用加锁就能获取资源(因为没有其他事务抢占资源或者发生的冲突概率小,稍稍尝试几次就能成功,美滋滋)。适用于冲突概率小的情景下。可以参考之前 [关于CAS的文章](https://github.com/about-cloud/JavaCore)。 + + + +#### 悲观锁: + +在冲突概率大的情况下,悲观的认为抢不到资源或者多次都抢不到资源。只能通过加锁的方式的抢占资源,然后再做处理,最后释放资源。 + + + +--- + +参考资料: + +[MySQL锁总结](https://zhuanlan.zhihu.com/p/29150809) \ No newline at end of file diff --git a/resource/markdown/database/PessimisticlockAndOptimisticlock.md b/resource/markdown/database/XA.md similarity index 57% rename from resource/markdown/database/PessimisticlockAndOptimisticlock.md rename to resource/markdown/database/XA.md index cfe4dc7..e1c026d 100644 --- a/resource/markdown/database/PessimisticlockAndOptimisticlock.md +++ b/resource/markdown/database/XA.md @@ -1,2 +1,3 @@ -

一、ArrayList

+

XA事务

+ diff --git a/resource/markdown/networking/TCPConnectAndDisconnect.md b/resource/markdown/networking/TCPConnectAndDisconnect.md index 4a947c5..cb61e37 100644 --- a/resource/markdown/networking/TCPConnectAndDisconnect.md +++ b/resource/markdown/networking/TCPConnectAndDisconnect.md @@ -12,7 +12,7 @@ #### 第二次握手 -> 服务端接收到客户端的TCP连接请求,响应 **ACK**(ack=i+1) + **SYN**(seq=j)表示已确认客户端的请求,并表示可以连接,之后服务端处于 **SYN_RCVD** 状态; +> 服务端接收到客户端的TCP连接请求,最起码能表示双方具有连接能力,然后响应 **ACK**(ack=i+1) + **SYN**(seq=j)表示已确认客户端的请求,并表示可以连接,之后服务端处于 **SYN_RCVD** 状态; #### 第三次握手 @@ -28,7 +28,7 @@ ESTABLISHED 表示已建立。 -#### +

二、4次挥手过程

@@ -36,107 +36,31 @@ #### 第一次挥手 -> +>从双方建立连接的ESTABLISHED状态,客户端想要断开连接,向服务端发送 **FIN** 标识报文段,表示客户端要 **安全地** 断开连接。客户端进入 **FIN_WAIT_1** 状态。 #### 第二次挥手 -> +>服务端收到客户端的终止连接的请求,并回复确认的 **ACK** 报文段,此后服务端进入 **CLOSE_WAIT** 状态,并且内部正在处理数据。 #### 第三次挥手 -> +>待服务端处理好任务,才能发送 **FIN** 结束标识的状态,并发送 **ACK** 报文,确认FIN。 #### 第四次挥手 -> - - - -

一次线上问题分析

- -```python -tcp 1 1 111.16.111.156:8089 203.111.111.111:18201 LAST_ACK - -tcp 1 1 111.16.111.156:8089 203.111.135.187:49694 LAST_ACK - -tcp 1 1 111.16.111.156:8089 203.111.111.60:5529 LAST_ACK - -``` - - - -```python -tcp 0 0 0.0.0.0:8089 0.0.0.0:* LISTEN 31467/java -tcp 1 1 111.16.111.156:8089 203.111.111.111:18201 LAST_ACK - -tcp 0 0 111.16.111.156:8089 203.111.196.200:50192 ESTABLISHED 31467/java -tcp 1 1 111.16.111.156:8089 203.111.135.187:49694 LAST_ACK - -tcp 1 1 111.16.111.156:8089 203.111.111.60:5529 LAST_ACK - -tcp 0 0 111.16.111.156:8089 203.111.193.223:48753 TIME_WAIT - -``` - - - -```python -tcp 0 0 0.0.0.0:8089 0.0.0.0:* LISTEN 31467/java -tcp 1 1 111.16.111.156:8089 203.111.111.111:18201 LAST_ACK - -tcp 1 0 111.16.111.156:8089 203.111.196.200:50192 CLOSE_WAIT 31467/java -tcp 0 0 111.16.111.156:8089 203.111.135.83:22170 ESTABLISHED 31467/java -tcp 1 1 111.16.111.156:8089 203.111.135.187:49694 LAST_ACK - -tcp 0 0 111.16.111.156:8089 203.111.135.99:53735 ESTABLISHED 31467/java -tcp 1 1 111.16.111.156:8089 203.111.111.60:5529 LAST_ACK - -tcp 0 0 111.16.111.156:8089 203.111.111.164:29548 ESTABLISHED 31467/java -tcp 0 0 111.16.111.156:8089 203.111.193.223:48753 TIME_WAIT - -``` - - - - - -```python -tcp 0 0 0.0.0.0:8089 0.0.0.0:* LISTEN 31467/java -tcp 1 1 111.16.111.156:8089 203.111.111.111:18201 LAST_ACK - -tcp 1 0 111.16.111.156:8089 203.111.111.128:31525 CLOSE_WAIT 31467/java -tcp 0 0 111.16.111.156:8089 203.111.135.35:30314 ESTABLISHED 31467/java -tcp 1 0 111.16.111.156:8089 203.111.196.200:50192 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.135.83:22170 CLOSE_WAIT 31467/java -tcp 1 1 111.16.111.156:8089 203.111.135.187:49694 LAST_ACK - -tcp 1 0 111.16.111.156:8089 203.111.135.99:53735 CLOSE_WAIT 31467/java -tcp 1 1 111.16.111.156:8089 203.111.111.60:5529 LAST_ACK - -tcp 0 0 111.16.111.156:8089 203.111.111.164:29548 TIME_WAIT - -tcp 0 0 111.16.111.156:8089 203.111.111.95:49963 ESTABLISHED 31467/java -tcp 0 0 111.16.111.156:8089 203.111.193.223:48753 TIME_WAIT - -``` - - +>客户端收到断开连接FIN标识后,向服务端发送**ACK**确认应答,此时服务端进入TIME-WAIT状态。该状态会持续**2MSL**时间,若该时间段内没有服务端的重发请求的话,就进入CLOSED状态,撤销TCB。当B收到确认应答后,也便进入CLOSED状态,撤销TCB。 -```python -tcp 0 0 0.0.0.0:8089 0.0.0.0:* LISTEN 31467/java -tcp 1 1 111.16.111.156:8089 203.111.111.111:18201 LAST_ACK - -tcp 1 0 111.16.111.156:8089 203.111.111.128:31525 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.196.17:43639 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.135.35:30314 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.196.200:50192 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.135.83:22170 CLOSE_WAIT 31467/java -tcp 1 1 111.16.111.156:8089 203.111.135.187:49694 LAST_ACK - -tcp 1 0 111.16.111.156:8089 203.111.196.40:20062 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.135.99:53735 CLOSE_WAIT 31467/java -tcp 1 1 111.16.111.156:8089 203.111.111.60:5529 LAST_ACK - -tcp 0 0 111.16.111.156:8089 203.111.111.164:29548 TIME_WAIT - -tcp 1 0 111.16.111.156:8089 203.111.111.95:49963 CLOSE_WAIT 31467/java -``` +

三、为什么?

+**为什么连接建立需要三次握手,而不是两次握手?** +> 为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误。 -```python -tcp 0 0 0.0.0.0:8089 0.0.0.0:* LISTEN 31467/java -tcp 1 0 111.16.111.156:8089 203.111.111.128:31525 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.196.17:43639 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.135.35:30314 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.196.200:50192 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.135.83:22170 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.196.40:20062 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.135.99:53735 CLOSE_WAIT 31467/java -tcp 1 0 111.16.111.156:8089 203.111.111.95:49963 CLOSE_WAIT 31467/java -``` +**为什么A要先进入TIME-WAIT状态,等待2MSL时间后才进入CLOSED状态?** +> 为了保证服务端能收到客户端的确认应答。 +> 若客户端发完确认应答后直接进入CLOSED状态,那么如果该应答丢失,服务端等待超时后就会重新发送连接释放请求,但此时客户端已经关闭了,不会作出任何响应,因此服务端永远无法正常关闭。 \ No newline at end of file From 24e290d0eecea9fdc6c360b3e889edbdb50aad90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Mon, 14 Jan 2019 19:04:34 +0800 Subject: [PATCH 08/39] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E5=AF=B9?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E4=BA=8B=E5=8A=A1=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E6=80=A7=E7=9A=84=E8=AE=B2=E8=A7=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/database/ACIDAndIsolationLevel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource/markdown/database/ACIDAndIsolationLevel.md b/resource/markdown/database/ACIDAndIsolationLevel.md index 205be14..5637cbd 100644 --- a/resource/markdown/database/ACIDAndIsolationLevel.md +++ b/resource/markdown/database/ACIDAndIsolationLevel.md @@ -16,7 +16,7 @@ #### :four_leaf_clover:一致性(Consistency ) -> **一致性** 是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。 +> **一致性** 是指在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。这是说数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。这里有个容易混淆点,容易和数据一致性混淆。这里更多的强调的是单机下的事务一致性,必须是一个事务内部。 保证数据库一致性的主要机制层面:数据库机制层面、业务层面。 From eec72a287cd37c83bd03aa780408ae84bdc92a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Mon, 14 Jan 2019 23:38:07 +0800 Subject: [PATCH 09/39] =?UTF-8?q?=E5=88=86=E6=9E=90=E4=BA=86=E5=88=86?= =?UTF-8?q?=E5=B8=83=E5=88=86=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/database/DBSplit.md | 32 +++++++++++++++++-- .../markdown/{database => distribution}/XA.md | 0 2 files changed, 30 insertions(+), 2 deletions(-) rename resource/markdown/{database => distribution}/XA.md (100%) diff --git a/resource/markdown/database/DBSplit.md b/resource/markdown/database/DBSplit.md index 495fb79..b5943c1 100644 --- a/resource/markdown/database/DBSplit.md +++ b/resource/markdown/database/DBSplit.md @@ -1,14 +1,42 @@

数据库拆分

+![database](https://i.loli.net/2019/01/14/5c3c8c06cd185.jpeg) + +数据库承载的数据以及请求负载较高时,我们就要考虑使用读写分离、数据缓存。随着业务的增长,数据库的压力达到了承载的阈值,就要考虑分库分表,分解、分摊单个数据库的压力。 + + + +

垂直拆分

+ +**数据库的垂直拆分:**通常,我们将所有的数据按照不同的业务建立并存储不同的表(table),垂直拆分是按照业务将一个数据库拆分多个数据库。原来每个业务对应一张表,垂直拆分后,是一个业务对应一个数据库(当然也有坑可能是多个业务对应一个数据库)。其核心是专库专用。达到的结果是将原来一个数据库系统的压力按照业务均摊到各个拆分后的数据库中。垂直拆分也是比较推荐的一直拆分方式。 + +垂直分片往往需要对架构和设计进行调整。在当前微服务化的进程中,对数据库的垂直拆分是非常友好的。 + + + +**数据表的垂直拆分:**单表的数据达到2GB或500万行记录就要考虑拆分数据表,垂直拆分表就将热点列和不经常使用的列表拆分开,降低单表的大小。 +

水平拆分

+当一般垂直拆分遇到瓶颈时,会对数据表进行水平拆分。这种方式与垂直拆分不同的地方是,它不会更改表结构。水平分表是将一个表的拆分多结构相同的多个表;水平分库是将一个表拆分成多个结构相同的表,并且这些表分布在不同的数据库。 -

垂直拆分

+ +#### 分布分表中间件有两种: + +1、代理模式的分库分表中间件:MyCat; + +2、客户端模式的分库分表中间件:ShardingJDBC; + +3、支持事务的分布式数据库。 + +(当然ShardingProxy也是代理分库分表中间件) + +#### 总结: -

混合拆分

+结合着微服务体系,一般会进行垂直拆分。当微服务中的数据库出现压力时,然后进行水平拆分。 diff --git a/resource/markdown/database/XA.md b/resource/markdown/distribution/XA.md similarity index 100% rename from resource/markdown/database/XA.md rename to resource/markdown/distribution/XA.md From ea8310477cefb71a6f1e99f84e0381bd175c0381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Mon, 14 Jan 2019 23:44:47 +0800 Subject: [PATCH 10/39] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=BA=86=E5=AD=97?= =?UTF-8?q?=E4=BD=93=E5=8A=A0=E7=B2=97=E4=B8=8D=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/database/DBSplit.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/resource/markdown/database/DBSplit.md b/resource/markdown/database/DBSplit.md index b5943c1..34d272b 100644 --- a/resource/markdown/database/DBSplit.md +++ b/resource/markdown/database/DBSplit.md @@ -6,17 +6,21 @@ +--- +

垂直拆分

-**数据库的垂直拆分:**通常,我们将所有的数据按照不同的业务建立并存储不同的表(table),垂直拆分是按照业务将一个数据库拆分多个数据库。原来每个业务对应一张表,垂直拆分后,是一个业务对应一个数据库(当然也有坑可能是多个业务对应一个数据库)。其核心是专库专用。达到的结果是将原来一个数据库系统的压力按照业务均摊到各个拆分后的数据库中。垂直拆分也是比较推荐的一直拆分方式。 +**数据库的垂直拆分:** 通常,我们将所有的数据按照不同的业务建立并存储不同的表(table),垂直拆分是按照业务将一个数据库拆分多个数据库。原来每个业务对应一张表,垂直拆分后,是一个业务对应一个数据库(当然也有坑可能是多个业务对应一个数据库)。其核心是专库专用。达到的结果是将原来一个数据库系统的压力按照业务均摊到各个拆分后的数据库中。垂直拆分也是比较推荐的一直拆分方式。 垂直分片往往需要对架构和设计进行调整。在当前微服务化的进程中,对数据库的垂直拆分是非常友好的。 -**数据表的垂直拆分:**单表的数据达到2GB或500万行记录就要考虑拆分数据表,垂直拆分表就将热点列和不经常使用的列表拆分开,降低单表的大小。 +**数据表的垂直拆分:** 单表的数据达到2GB或500万行记录就要考虑拆分数据表,垂直拆分表就将热点列和不经常使用的列表拆分开,降低单表的大小。 + +---

水平拆分

From 406edebd2e5dc1e0cab11fff453d3007c6024b30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Tue, 15 Jan 2019 20:51:40 +0800 Subject: [PATCH 11/39] =?UTF-8?q?=E5=88=86=E6=9E=90=E4=BA=86X/Open=20DTP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- resource/markdown/distribution/XA.md | 33 +++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e744acb..37b7a50 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ ### 八、:telescope::tokyo_tower::satellite:分布式系统 * [分布式系统 (第 01 篇) 精讲:集群与分布式](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/WhatisDistributed.md) * [分布式系统 (第 02 篇) 精讲:CAP定理与BASE理论](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/CAPandBASE.md) -* [分布式系统 (第 03 篇) 精讲:XA事务](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/XA.md) +* [分布式系统 (第 03 篇) 精讲:X/Open DTP 与 XA 事务](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/XA.md) * [分布式系统 (第 04 篇) 精讲:2PC协议和3PC协议](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/2PCand3PC.md) * 分布式系统 (第 05 篇) 精讲:TCC事务补偿机制(柔性事务方案) * 分布式系统 (第 06 篇) 精讲:Paxos算法(强一致性算法) diff --git a/resource/markdown/distribution/XA.md b/resource/markdown/distribution/XA.md index e1c026d..9d46793 100644 --- a/resource/markdown/distribution/XA.md +++ b/resource/markdown/distribution/XA.md @@ -1,3 +1,34 @@ -

XA事务

+

X/Open DTP 与 XA事务

+**X/Open** 是一个欧洲基金会的公司名称,中文名称国际联盟有限公司。 + + + +**X/Open DTP** 全称 *X/Open Distributed Transaction Processing Reference Model* ,中文名称 **X/Open 分布式事务处理参考模型**,是X/Open 基金会规范的一个分布式事务处理模型,当然这个模型也是概念模型。 + +![X/Open DTP](https://i.loli.net/2019/01/15/5c3dcc685cfb5.png) + +**X/Open DTP** 中定义了三个组件: + +- **AP(Application Program):**参与分布式事务中的应用程序,构成了所需的一个或多个操作; +- **RM(Resource Manager):**资源管理器,类似的RDBMS(Relational Database Management System)关系数据库管理系统,主要作用还是管理资源,那就意味着,事务想要获得资源,必须要经过RM管理。其中RM自身支持事务(比如InnoDB),RM能够根据将全局(分布式)事务标识定位到自己内部的对应事务。 +- **TM(Transaction Manager):**事务管理器,负责协调和管理AP和RM之间的分布式事务完整性。主要的工作是提供AP注册全局事务的接口,颁发全局事务标识(GTID之类 的),存储/管理全局事务的内容和决策并指挥RM做commit/rollback。 + + + +其中在DTP定了以下几个概念: + +**事务:** 一个事务是一个完整的工作单元,由多个独立的计算任务组成,这多个任务在逻辑上是原子的。 + +**全局事务:** 对于一次性操作多个资源管理器的事务,就是全局事务 + +**分支事务:** 在全局事务中,某一个资源管理器有自己独立的任务,这些任务的集合作为这个资源管理器的分支任务 + +**控制线程:** 用来表示一个工作线程,主要是关联AP,TM,RM三者的一个线程,也就是事务上下文环境。简单的说,就是需要标识一个全局事务以及分支事务的关系。 + + + +--- + +**XA**,全称 *eXtended Architecture*, From c1d0df0e671f6f3d16491af542defd42d79ee4c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 16 Jan 2019 20:05:47 +0800 Subject: [PATCH 12/39] =?UTF-8?q?=E4=B8=B0=E5=AF=8C=E4=BA=86XA=E4=BA=8B?= =?UTF-8?q?=E5=8A=A1=E4=B8=8E2PC/3PC=E5=8D=8F=E8=AE=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/distribution/2PCand3PC.md | 98 +++++++++++++++------ resource/markdown/distribution/XA.md | 51 ++++++++--- 2 files changed, 108 insertions(+), 41 deletions(-) diff --git a/resource/markdown/distribution/2PCand3PC.md b/resource/markdown/distribution/2PCand3PC.md index f5154d8..807a56f 100644 --- a/resource/markdown/distribution/2PCand3PC.md +++ b/resource/markdown/distribution/2PCand3PC.md @@ -1,18 +1,22 @@

一、2PC协议

-> 2PC 是 *Two-Phase Commit Protocol* 的缩写,中文名称 **两阶段提交协议**,它是为了解决 *分布式事务一致性* 问题而产生的算法。分布式一致性问题是分布式系统各个节点(分区)如何就一项决议达成一致的问题,构成的一组操作称为**事务** (又称 **分布式事务**)。 +> 2PC 是 *Two-Phase Commit Protocol* 的缩写,中文名称 **两阶段提交协议**,它是为了解决 *分布式事务一致性* 问题而产生的算法。分布式一致性问题是分布式系统各个节点(分区)如何就一项决议达成一致的问题,构成的一组操作称为**事务** (又称 **分布式事务**,**全局事务**),各个节点的事务称为**分支事务**。**两阶段提交协议** 是一种原子提交协议、共识协议。 -两阶段提交协议是指事务提交的过程分为两个阶段:**准备阶段** 和 **提交阶段**。其中事务的发起者称 **协调者** ,事务的执行者称 **参与者** 。一般,在一个事务中有一个协调者和多个参与者。 +**两阶段提交协议** 是指事务提交的过程分为两个阶段:**准备阶段** 和 **提交阶段**。其中事务的发起者称为 **事务协调者** ,事务的执行者称为 **参与者**。一般,在一个事务中有一个事务协调者和多个参与者。 ![2PC]() #### 阶段一:准备阶段: -首先,协调者询问事务中各个参与者是否可以执行操作。然后,事务中的各个参与者执行操作(占有资源)并做出回答YES/NO给协调者; +又称 投票阶段。首先,协调者询问全局事务中各个参与者是否可以执行操作。然后,全局事务中的各个参与者执行操作(占有资源)并做出回答 YES/NO 给协调者; #### 阶段二:提交阶段: -最后,协调者检查事务中的各个参与者的反馈,全部反馈YES则做事务提交,否则存在NO则做事务回滚。 +又称 完成阶段。最后,协调者检查事务中的各个参与者的反馈。 + +**成功:** 在规定的时效内全部反馈 YES,则协调器向所有参与者发送事务提交消息。每个参与者完成操作,并释放事务期间持有的所有锁和资源。每个参与者向协调器发送一个确认。当事务协调器收到所有确认时,协调器完成事务。 + +**失败:** 在规定的时效内有参与者未对阶段一做回复(超时)或有参与者回复 NO,则协调器向所有参与者发送事务回滚消息。每个参与者使用撤消日志撤消事务,并释放事务期间保留的资源和锁。每个参与者向协调器发送一个确认。当收到所有确认时,协调器将撤消事务。 @@ -36,27 +40,59 @@ -#### 2PC的缺陷: +#### 2PC缺点: **同步阻塞**:我们前面讲过,2PC协议就是为了解决分布式事务一致性问题的,而可用性与一致性往往存在矛盾。假设分区节点之间出现网络通信问题,那么消息就不能及时传递给响应的节点,为了到达一致性,整个分布式系统(准确地将是参与事务的机器组成的系统)将会陷入阻塞状态,所以2PC协议是一个典型的 **阻塞式协议**。 -**单点问题**:如果协调者出现故障,参与者将一直处在等待状态(等待协调者发送消息指令),被占有资源也不能释放,这就会协调者的单点问题。 +**单点问题**:如果协调者出现故障,参与者将一直处在等待状态(等待协调者发送消息指令),分支事务占有资源也不能释放,直到收到提交/回滚命令为止。这就会协调者的单点问题。 + +**脑裂问题**:在提交阶段中,只有部分参与者接收到协调者发送的 `Commit` 指令,则会出现数据不一致的问题。 + + + +**2PC如何解决故障问题?** + +> 两阶段提交协议的参与者使用协议状态的日志记录。协议的恢复过程使用日志记录,这些记录通常生成速度很慢,但在失败后仍然存在。存在许多协议变体,它们主要在日志策略和恢复机制方面有所不同。尽管恢复过程通常很少使用,但由于协议要考虑和支持许多可能的故障场景,因此它构成了协议的一个重要部分。 +> +> 协议的工作方式如下:一个节点是指定的协调器,它是主站点,网络中的其余节点被指定为参与者。协议假定每个节点上都有一个稳定的存储,有一个提前写入日志,不会永远崩溃,提前写入日志中的数据不会在崩溃中丢失或损坏,并且任何两个节点都可以相互通信。最后一个假设没有太多限制,因为网络通信通常可以被重新路由。前两个假设要强大得多;如果一个节点被完全破坏,那么数据可能会丢失。 + +> 在到达事务的最后一步之后,协调器将启动该协议。然后,参与者根据是否在参与者处成功处理了事务,使用协议消息或中止消息进行响应。 + + + +#### 2PC与XA事务 + +X/Open 的XA事务就是使用的2PC协议,事务的协调者是TM(事务管理器),事务的参与者是TM(资源管理器)。为了解决2PC中事务协调者的单点问题,又出于性能和可靠性的原因,协调者的角色可以转移到另一个TM(事务管理器)。参与者之间不交换2PC信息,而是与各自的TM(事务管理器)交换信息。相关的TM(事务管理器)相互通信以执行上面的2PC协议模式,“代表”各自的参与者,以终止该事务。在这种体系结构中,协议是完全分布式的(不需要任何中央处理组件或数据结构),并且可以有效地随着网络节点的数量(网络大小)而扩展。 + + + +#### 树型两阶段提交协议 + +The TreeTwo-phase Commit Protocol (树形两阶段提交,T2PC,t2pc,Tree-2PC)也称为嵌套2PC或递归2PC,是2PC的常见变体协议,它更好地利用了底层通信基础设施。分布式事务中的参与者通常按照定义树结构的顺序调用,即调用树,其中参与者是节点,边缘是调用(通信链接)。通常使用同一个树通过2PC协议完成事务,但原则上也可以使用另一个通信树来完成事务。在 Tree-2PC 中,协调器被认为是通信树(倒挂树)的根(“top”),而参与者是其他节点。协调器可以是发起事务的节点(递归地(传递地)调用其他参与者),但同一树中的另一个节点可以代替协调器角色。来自协调器的2PC消息被“向下”传播到树上,而发送到协调器的消息则被一个参与者从树下的所有参与者“收集”到,然后在树上发送适当的消息“向上”(除中止消息外,该消息在接收到该消息后立即被“向上”传播,或者如果当前参与者启动了博尔特) + + + +#### 动态两阶段提交 + +The Dynamic Two-phase Commit Protocol (动态两阶段提交,D2PC,d2pc,Dynamic-2PC)是没有预定协调器的 树形两阶段提交协议 的变体协议。协议消息(是投票)从所有叶开始传播,每个叶在代表事务完成其任务时(准备就绪)。中间(非叶)节点在协议消息发送到最后一个(单个)相邻节点(尚未从该节点接收到协议消息)时发送就绪。协调器是通过在事务树上的冲突位置对协议消息进行竞速来动态确定的。它们要么在事务树节点上发生冲突,要么作为协调器,要么在树边缘发生冲突。在后一种情况下,两个边缘的节点之一被选为协调器(任何节点)。D2PC是时间最优的(在特定事务树的所有实例和任何特定 Tree-2PC 协议实现中;所有实例都有相同的树;每个实例都有不同的节点作为协调器):通过选择最佳协调器,D2PC在尽可能短的时间内提交协调器和每个参与者,从而允许在每个事务参与者(树节点)中尽早释放锁定的资源。 + -**脑裂问题**:在提交阶段中,只有部分参与者接收到协调发送的commit指令,则会出现数据不一致的问题。 ---

二、3PC协议

-> 3PC 是 *Three-Phase Commit Protocol* 的缩写,中文名称 **三阶段提交协议**,它为了解决2PC问题,而产生的改进版本的分布式一致性协议。 +> 3PC 是 *Three-Phase Commit Protocol* 的缩写,中文名称 **三阶段提交协议**,与两阶段提交协议(2PC)不同,3PC是非阻塞协议。具体来说,3PC对事务提交或中止之前所需的时间量设置了一个上限。此属性确保如果给定的事务正试图通过3PC提交并持有一些资源锁,则它将在超时后释放这些锁。 > -> 在原来2PC协议的基础上,将2阶段扩展到3阶段:**能否提交阶段**、**预提交阶段**、**执行提交阶段**。 +> 在原来2PC协议的基础上,将2阶段扩展到3阶段:**询问提交阶段**、**预提交阶段**、**执行提交阶段**。 -#### 阶段一:能否提交阶段(CanCommit): +![3PC](https://i.loli.net/2019/01/16/5c3f0b5b4af27.png) -这个阶段(CanCommit)和2PC的准备阶段非常相似。 +#### 阶段一:询问提交阶段(CanCommit): -​ **1.事务询问**:事务中的协调者向事务中的参与者发送信息,询问各个参与者是否可以提交事务。 +这个阶段(CanCommit)和2PC的准备阶段非常相似。事务协调者接受事务请求。如果出现协调者出现故障,则中止事务(即使在恢复时,它将认为事务已中止)。否则继续做以下操作: + +​ **1.事务询问**:事务中的协调者向事务中的所有参与者发送消息,询问各个参与者是否可以提交事务; ​ **2.事务反馈**:各个参与者根据当前资源判断是否可以提交事务(一般会占有资源),并反馈给协调者。 @@ -66,19 +102,17 @@ 事务中的协调者在收到各个参与者反馈后,做出判断,出现以下两种情况: -**1、**如果事务中的所有参与者都表示可以提交事务,则做执行 **事务预提交** ; - -​ **⑴、发送预提交请求:**事务中的协调者向各个参与者发送预提交(PreCommit)指令,并进入已准备(prepared)状态。 +**1、**如果事务中的所有参与者都表示可以提交事务,则做执行 **事务预提交** -​ **⑵、事务预提交:**事务中的参与者接受到协调者的预提交(PreCommit)指令,会执行事务操作,并将undo和redo信息记录到事务日志中。(undo和redo分别表示撤销和恢复) +​ **⑴、发送预提交请求:**事务中的协调者向各个参与者发送预提交(PreCommit)指令,并进入已准备(prepared)状态; -​ **⑶、反馈事务预提交的结果:**在参与者成功的执行了事务操作,并向协调者返回ACK响应,同时开始等待最终事务提交指令。 +​ **⑵、事务预提交:**事务中的参与者接受到协调者的预提交(PreCommit)指令,会执行事务操作,并将undo和redo信息记录到事务日志中。(undo和redo分别表示撤销和恢复); +​ **⑶、反馈事务预提交的结果:**在参与者成功的执行了事务操作,并向协调者返回ACK响应,如果协调者出现故障,那么参与者将不断的重试发送ACK。之后开始等待最终事务提交指令。 +**2、**如果其中之一的参与者表示不能提交事务或超时反馈,则做 **中断事务** 处理 -**2、**如果其中之一的参与者表示不能提交事务或超时反馈,则做 **中断事务** 处理。 - -​ **⑴、发送中断事务指令:**事务中的协调者向各个参与者发送中断指令(AbortCommand)。 +​ **⑴、发送中断事务指令:**事务中的协调者向各个参与者发送中断指令(abort); ​ **⑵、中断事务:**各个参与者就收到中断指令或超时未收到任何指令时,会做中断事务处理。 @@ -86,15 +120,15 @@ #### 阶段三:执行提交阶段(DoCommit): -事务中的协调者在此阶段也是先接收各个参与者的反馈,然后做出判断,也会出现以下两种情况: +事务中的协调者在先接收各个参与者的反馈,然后做出判断,也会出现以下两种情况: -**1、**如果事务中的参与者成功执行的预提交事务操作,并返回成功,则 **执行事务提交**: +**1、**如果事务中的参与者成功执行的预提交事务操作,并返回成功ACK,则 **执行事务提交**: -​ **⑴、发送执行事务提交指令:**事务协调者发送执行事务提交指令给各个参与者。 +​ **⑴、发送执行事务提交指令:**事务协调者发送执行事务提交指令给各个参与者; -​ **⑵、事务提交:**各个参与者收到协调者发送的执行事务提交的指令,然后执行事务提交。 +​ **⑵、事务提交:**各个参与者收到协调者发送的执行事务提交的指令,然后执行事务提交; -​ **⑶、反馈事务提交结果:**各个参与者执行完事务提交操作后,反馈结果给事务协调者。 +​ **⑶、反馈事务提交结果:**各个参与者执行完事务提交操作后,反馈结果给事务协调者; ​ **⑷、完成事务:**协调者接收到各个参与者执行提交的反馈,事务完成。 @@ -102,14 +136,20 @@ **2、**如果事务中的参与者执行预提交事务操作失败或超时反馈,则 **中断事务**: -​ **⑴、发送中断事务指令:**事务中的协调者向各个参与者发送中断指令(AbortCommand)。 +​ **⑴、发送中断事务指令:**事务中的协调者向各个参与者发送中断指令(abort); -​ **⑵、事务回滚:**各个参与者收到协调者发送的执行事务回滚的指令,然后执行事务回滚。 +​ **⑵、事务回滚:**各个参与者收到协调者发送的执行事务回滚的指令,然后执行事务回滚; -​ **⑶、反馈事务回滚结果:**各个参与者执行完事务回滚操作后,反馈结果给事务协调者。 +​ **⑶、反馈事务回滚结果:**各个参与者执行完事务回滚操作后,反馈结果给事务协调者; ​ **⑷、中断事务:**协调者接收到各个参与者执行回滚的反馈,事务中断。 -**说明:**执行提交阶段(DoCommit)时,可能协调者或网络出现问题,都有可能导致事务的部分参与者无法接收到协调者的指令(包括执行提交和中断事务),在这种情况下,部分参与者会在超时等待之后继续进行事务提交。有一定小概率会指定数据不一致的情况,所以这也是3PC协议的一大缺点。 \ No newline at end of file +**说明:**执行提交阶段(DoCommit)时,可能协调者或网络出现问题,都有可能导致事务的部分参与者无法接收到协调者的指令(包括执行提交和中断事务),在这种情况下,部分参与者会在超时等待之后继续进行事务提交。有一定小概率会指定数据不一致的情况,所以这也是3PC协议的一大缺点。 + + + +参考资料: + +[WIKIPEDIA:Two-phase commit protocol](https://en.wikipedia.org/wiki/Two-phase_commit_protocol) \ No newline at end of file diff --git a/resource/markdown/distribution/XA.md b/resource/markdown/distribution/XA.md index 9d46793..36ef20a 100644 --- a/resource/markdown/distribution/XA.md +++ b/resource/markdown/distribution/XA.md @@ -1,4 +1,4 @@ -

X/Open DTP 与 XA事务

+

X/Open DTP 与 XA 事务

**X/Open** 是一个欧洲基金会的公司名称,中文名称国际联盟有限公司。 @@ -6,29 +6,56 @@ **X/Open DTP** 全称 *X/Open Distributed Transaction Processing Reference Model* ,中文名称 **X/Open 分布式事务处理参考模型**,是X/Open 基金会规范的一个分布式事务处理模型,当然这个模型也是概念模型。 -![X/Open DTP](https://i.loli.net/2019/01/15/5c3dcc685cfb5.png) +![X/Open DTP](https://i.loli.net/2019/01/16/5c3ea241598f8.png) **X/Open DTP** 中定义了三个组件: -- **AP(Application Program):**参与分布式事务中的应用程序,构成了所需的一个或多个操作; -- **RM(Resource Manager):**资源管理器,类似的RDBMS(Relational Database Management System)关系数据库管理系统,主要作用还是管理资源,那就意味着,事务想要获得资源,必须要经过RM管理。其中RM自身支持事务(比如InnoDB),RM能够根据将全局(分布式)事务标识定位到自己内部的对应事务。 -- **TM(Transaction Manager):**事务管理器,负责协调和管理AP和RM之间的分布式事务完整性。主要的工作是提供AP注册全局事务的接口,颁发全局事务标识(GTID之类 的),存储/管理全局事务的内容和决策并指挥RM做commit/rollback。 +- **AP(Application Program):**应用程序,参与分布式事务中的应用程序,比如微服务程序; +- **RM(Resource Manager):**资源管理器,比如RDBMS(Relational Database Management System)关系数据库管理系统,主要作用还是管理资源,那就意味着,事务想要获得资源,必须要经过RM管理。其中RM自身支持事务(比如InnoDB)。 +- **TM(Transaction Manager):**事务管理器,负责协调和管理AP和RM之间的分布式事务完整性。主要的工作是提供AP注册全局事务的接口,颁发全局事务标识(GTID、XID之类 的),存储/管理全局事务的内容和决策并指挥RM做commit/rollback。 -其中在DTP定了以下几个概念: +**分布式事务下几个概念:** -**事务:** 一个事务是一个完整的工作单元,由多个独立的计算任务组成,这多个任务在逻辑上是原子的。 +- **事务:**一个事务是一个完整的工作单元,由多个独立的计算任务组成,这多个任务在逻辑上是原子的; +- **全局事务:** 对于一次性操作多个资源管理器的事务,就是全局事务; +- **分支事务:** 在全局事务中,某一个资源管理器有自己独立的任务,这些任务的集合作为这个资源管理器的分支任务; +- **TX协议:** AP与TM之间交互协议(接口); +- **XA协议:** TM与RM之间交互协议(接口)。 -**全局事务:** 对于一次性操作多个资源管理器的事务,就是全局事务 -**分支事务:** 在全局事务中,某一个资源管理器有自己独立的任务,这些任务的集合作为这个资源管理器的分支任务 -**控制线程:** 用来表示一个工作线程,主要是关联AP,TM,RM三者的一个线程,也就是事务上下文环境。简单的说,就是需要标识一个全局事务以及分支事务的关系。 +--- +#### XA事务 + +提起MySQL的事务,我们最熟悉的几个字 -- **XA事务**。那 XA到底是什么? + +![2PC](https://i.loli.net/2019/01/16/5c3eaf76a1bd9.png) + +**XA** ,全称 *eXtended Architecture*,是 X/Open组织在1991年发布的分布式事务处理(DTP) **规范**。**XA** 的目标确保跨异构后端数据存储(如数据库、应用程序服务器、消息队列、事务缓存等)执行的“全局事务”中的原子性。为了保证原子性,XA使用 [两阶段提交(2PC)](https://github.com/about-cloud/JavaCore) 来确保所有资源都一致地提交或回滚任何特定事务(即,所有资源都以原子方式执行相同的操作)。 + +具体来说,**XA** 描述了全局事务管理器和特定应用程序之间的接口。希望使用**XA**的应用程序将使用库或单独的服务与XA事务管理器进行交互。然后事务管理器将跟踪事务中的参与者(即应用程序正在写入的各种其他数据存储),并与他们一起执行两阶段提交协议。换句话说,**XA**事务管理器通常位于应用程序与要向其写入事务的服务器的现有连接之上。它维护一个关于提交或中止的决定的**日志**(类似于数据库维护自己写的日志的方式),当数据库失败并需要重新启动时,它可以使用该日志进行恢复。 + +许多软件供应商继续支持**XA**(这意味着软件可以参与XA事务),包括各种关系数据库和消息代理。 + + + +#### 优缺点 + +> 由于XA使用两阶段提交(2PC),所以该协议的优点和缺点通常适用于XA。其主要优点是XA(使用2PC)允许跨多个异构技术进行原子事务(例如,单个**XA事务**可以包含来自不同供应商的多个数据库和消息代理),而传统的数据库事务仅限于单个数据库。(这句话很重要,也是描述分布式事务和传统数据库事务的最大区别) + + + +> 主要缺点是2PC是一个阻塞协议:其他服务器需要等待 事务管理器 发出关于是否提交或中止每个事务的决定。如果 事务管理器 在等待其最终决定时脱机,则它们将被卡住并保持数据库锁,直到 事务管理器 再次联机并发布其决定。这种对锁的扩展持有可能会破坏使用相同数据库的其他应用程序。解决方案是超时回滚,具体[关于2PC与3PC的文章参考这里](https://github.com/about-cloud/JavaCore)。 + + + +> 此外,如果 事务管理器 崩溃并且无法恢复其决策记录(例如,由于如何记录决策的错误,或由于服务器上的数据损坏),则可能需要手动干预。许多XA实现为事务提供了一个“逃生门”,以便独立地决定是提交还是中止(不等待事务管理器的消息),但这有违反原子性保证的风险,因此保留用于紧急情况。 ---- -**XA**,全称 *eXtended Architecture*, +**参考资料:** +[XA 分布式事务原理](https://blog.csdn.net/wuzhiwei549/article/details/79925618) \ No newline at end of file From ca39034497f5d982837482a1cdca08dd6e78a09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 16 Jan 2019 23:42:55 +0800 Subject: [PATCH 13/39] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0=E4=BA=862PC=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E7=9A=84=E9=85=8D=E5=9B=BE=EF=BC=9B2.=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=BA=86TCC=E8=AE=B2=E8=A7=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- resource/markdown/distribution/2PCand3PC.md | 12 ++++--- .../markdown/distribution/TryConfirmCancel.md | 32 +++++++++++++++++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 37b7a50..915311c 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ * [分布式系统 (第 02 篇) 精讲:CAP定理与BASE理论](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/CAPandBASE.md) * [分布式系统 (第 03 篇) 精讲:X/Open DTP 与 XA 事务](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/XA.md) * [分布式系统 (第 04 篇) 精讲:2PC协议和3PC协议](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/2PCand3PC.md) -* 分布式系统 (第 05 篇) 精讲:TCC事务补偿机制(柔性事务方案) +* [分布式系统 (第 05 篇) 精讲:TCC事务补偿机制(柔性事务方案)](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/TryConfirmCancel.md) * 分布式系统 (第 06 篇) 精讲:Paxos算法(强一致性算法) * 分布式系统 (第 07 篇) 精讲:Chubby 与 Zookeeper * 分布式系统 (第 08 篇) 精讲:设计分布式锁 diff --git a/resource/markdown/distribution/2PCand3PC.md b/resource/markdown/distribution/2PCand3PC.md index 807a56f..858677b 100644 --- a/resource/markdown/distribution/2PCand3PC.md +++ b/resource/markdown/distribution/2PCand3PC.md @@ -4,7 +4,7 @@ **两阶段提交协议** 是指事务提交的过程分为两个阶段:**准备阶段** 和 **提交阶段**。其中事务的发起者称为 **事务协调者** ,事务的执行者称为 **参与者**。一般,在一个事务中有一个事务协调者和多个参与者。 -![2PC]() +![2PC](https://i.loli.net/2019/01/16/5c3f3a888ca74.png) #### 阶段一:准备阶段: @@ -26,17 +26,21 @@ 他想通过携程APP进行购买两张票,要么两张票都购买成功,要么两张票都购买失败。 -**过程**:首先用户先通过携程选择杭州到北京、北京到巴黎的机票;如果两个航班都有机票话,然后下单购买两个航班的机票。 +**过程**:首先用户先通过携程选择杭州到北京、北京到巴黎的机票;如果两个航班都有机票话,然后下单购买两个航班的机票,从杭州到北京的机票是首都航空公司提供,从北京到巴黎的机票是法国航空公司提供。 **分析**:整个购买两张机票的过程就是一个事务,分别购买两个班次的行为就是两个操作。 准备阶段:选择航班机票时就是准备阶段,被选择的航空公司票务系统就是参与者,通过选择机票就是占有资源(占有资源是有时间限制的); -![准备阶段]() +![准备阶段](https://i.loli.net/2019/01/16/5c3f3e5d530ae.png) + +消息反馈: + +![反馈](https://i.loli.net/2019/01/16/5c3f3e94d2bdd.png) 提交阶段:携程作为协调者会查看参与者(各航空公司票务系统)是否可以提供机票,如果都是YES,表示可以提交订单,否则有NO,只能放弃操作(事务回滚),也是放弃对资源的占有。 -![提交阶段]() +![提交阶段](https://i.loli.net/2019/01/16/5c3f40464dec3.png) diff --git a/resource/markdown/distribution/TryConfirmCancel.md b/resource/markdown/distribution/TryConfirmCancel.md index 871fd17..532383b 100644 --- a/resource/markdown/distribution/TryConfirmCancel.md +++ b/resource/markdown/distribution/TryConfirmCancel.md @@ -1,8 +1,20 @@

TCC事务补偿机制(柔性事务方案)

-**TCC** 是 *Try Confirm-Cancel* 的缩写,其本质上还是2PC(两阶段提交协议)。它不像具有[ACID特性](https://github.com/about-cloud/JavaCore)的数据库事务那样刚性 ,TCC也没有XA事务那样 **直接依赖** 于资源管理器(RM),它是基于业务层面的事务操作,所以(占有资源的)粒子度是可控的,所以又称为**柔性事务方案**。TCC分为两阶段:**Try阶段** 和 **Confirm/Cancel阶段**。 +**TCC** 是 *Try Confirm-Cancel* 的缩写,其本质上还是2PC(两阶段提交协议)。它不像具有[ACID特性](https://github.com/about-cloud/JavaCore)的数据库事务那样刚性 ,TCC是 **柔性事务方案**。为什么称为柔性事务方案呢?因为它不直接依赖于资源管理(RM),回想一下[前面的XA事务](https://github.com/about-cloud/JavaCore),其中就直接依赖于资源管理器,而 TCC 是在业务层面处理的,业务依赖于何种资源管理器、依赖多少资源管理器,TCC 中是不需要担心,所以它的方式更为弹性。 + +![TCC](https://i.loli.net/2019/01/16/5c3f4b5b2e21d.png) + + + +一个完整的TCC模式有以下组成部分: + +1、业务应用:直接使用的应用; + +2、被调用的服务:一般是多个,是主业务应用被调用的服务,以此来获得资源; + +3、事务协调器:开启事务、注册事务,协调事务内的服务提交/回滚活动,从而达到全局事务的一致性。 + -![TCC]() #### 第一阶段:Try阶段: @@ -12,4 +24,18 @@ #### 第二阶段:Confirm/Cancel阶段: -Confirm表示确认执行业务操作,Cancel表示取消执行业务操作。 \ No newline at end of file +Confirm表示确认执行业务操作,Cancel表示取消执行业务操作。 + + + +#### TCC 这种入侵业务而不接触资源管理器的优缺点如下: + +**优点:** 资源管理的粒子度可自控,在资源层面可以做到很好的控制; + +**缺点:** 入侵业务代码,耦合度增加,每个被调用的服务都要重复实现Try、Confirm、Cancel接口代码,改造成本高。 + + + +参考资料: + +阿里技术 \ No newline at end of file From 1a27943ed52e7eb604e30dba413cf71d310053ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Thu, 17 Jan 2019 00:22:22 +0800 Subject: [PATCH 14/39] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E4=BA=862PC=E6=96=87?= =?UTF-8?q?=E7=AB=A0=E4=B8=AD=EF=BC=8C=E4=B8=AD=E6=96=87=E9=A1=BF=E5=8F=B7?= =?UTF-8?q?=E5=9C=A8=E5=8A=A0=E7=B2=97=E5=AD=97=E4=BD=93=E4=B8=AD=E4=B8=8D?= =?UTF-8?q?=E9=80=82=E5=BA=94GitHub=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/distribution/2PCand3PC.md | 36 ++++++++++----------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/resource/markdown/distribution/2PCand3PC.md b/resource/markdown/distribution/2PCand3PC.md index 858677b..371fa67 100644 --- a/resource/markdown/distribution/2PCand3PC.md +++ b/resource/markdown/distribution/2PCand3PC.md @@ -106,19 +106,19 @@ The Dynamic Two-phase Commit Protocol (动态两阶段提交,D2PC,d2pc,Dyn 事务中的协调者在收到各个参与者反馈后,做出判断,出现以下两种情况: -**1、**如果事务中的所有参与者都表示可以提交事务,则做执行 **事务预提交** +**1.** 如果事务中的所有参与者都表示可以提交事务,则做执行 **事务预提交** -​ **⑴、发送预提交请求:**事务中的协调者向各个参与者发送预提交(PreCommit)指令,并进入已准备(prepared)状态; +​ **1.1.发送预提交请求**:事务中的协调者向各个参与者发送预提交(PreCommit)指令,并进入已准备(prepared)状态; -​ **⑵、事务预提交:**事务中的参与者接受到协调者的预提交(PreCommit)指令,会执行事务操作,并将undo和redo信息记录到事务日志中。(undo和redo分别表示撤销和恢复); +​ **1.2.事务预提交**:事务中的参与者接受到协调者的预提交(PreCommit)指令,会执行事务操作,并将undo和redo信息记录到事务日志中。(undo和redo分别表示撤销和恢复); -​ **⑶、反馈事务预提交的结果:**在参与者成功的执行了事务操作,并向协调者返回ACK响应,如果协调者出现故障,那么参与者将不断的重试发送ACK。之后开始等待最终事务提交指令。 +​ **1.3.反馈事务预提交的结果**:在参与者成功的执行了事务操作,并向协调者返回ACK响应,如果协调者出现故障,那么参与者将不断的重试发送ACK。之后开始等待最终事务提交指令。 -**2、**如果其中之一的参与者表示不能提交事务或超时反馈,则做 **中断事务** 处理 +**2.** 如果其中之一的参与者表示不能提交事务或超时反馈,则做 **中断事务** 处理 -​ **⑴、发送中断事务指令:**事务中的协调者向各个参与者发送中断指令(abort); +​ **2.1.发送中断事务指令**:事务中的协调者向各个参与者发送中断指令(abort); -​ **⑵、中断事务:**各个参与者就收到中断指令或超时未收到任何指令时,会做中断事务处理。 +​ **2.2.中断事务**:各个参与者就收到中断指令或超时未收到任何指令时,会做中断事务处理。 @@ -126,31 +126,31 @@ The Dynamic Two-phase Commit Protocol (动态两阶段提交,D2PC,d2pc,Dyn 事务中的协调者在先接收各个参与者的反馈,然后做出判断,也会出现以下两种情况: -**1、**如果事务中的参与者成功执行的预提交事务操作,并返回成功ACK,则 **执行事务提交**: +**1.** 如果事务中的参与者成功执行的预提交事务操作,并返回成功ACK,则 **执行事务提交**: -​ **⑴、发送执行事务提交指令:**事务协调者发送执行事务提交指令给各个参与者; +​ **1.1.发送执行事务提交指令**:事务协调者发送执行事务提交指令给各个参与者; -​ **⑵、事务提交:**各个参与者收到协调者发送的执行事务提交的指令,然后执行事务提交; +​ **1.2.事务提交**:各个参与者收到协调者发送的执行事务提交的指令,然后执行事务提交; -​ **⑶、反馈事务提交结果:**各个参与者执行完事务提交操作后,反馈结果给事务协调者; +​ **1.3.反馈事务提交结果**:各个参与者执行完事务提交操作后,反馈结果给事务协调者; -​ **⑷、完成事务:**协调者接收到各个参与者执行提交的反馈,事务完成。 +​ **1.4.完成事务**:协调者接收到各个参与者执行提交的反馈,事务完成。 -**2、**如果事务中的参与者执行预提交事务操作失败或超时反馈,则 **中断事务**: +**2.** 如果事务中的参与者执行预提交事务操作失败或超时反馈,则 **中断事务**: -​ **⑴、发送中断事务指令:**事务中的协调者向各个参与者发送中断指令(abort); +​ **1.1.发送中断事务指令**:事务中的协调者向各个参与者发送中断指令(abort); -​ **⑵、事务回滚:**各个参与者收到协调者发送的执行事务回滚的指令,然后执行事务回滚; +​ **1.2.事务回滚**:各个参与者收到协调者发送的执行事务回滚的指令,然后执行事务回滚; -​ **⑶、反馈事务回滚结果:**各个参与者执行完事务回滚操作后,反馈结果给事务协调者; +​ **1.3.反馈事务回滚结果**:各个参与者执行完事务回滚操作后,反馈结果给事务协调者; -​ **⑷、中断事务:**协调者接收到各个参与者执行回滚的反馈,事务中断。 +​ **1.4.中断事务**:协调者接收到各个参与者执行回滚的反馈,事务中断。 -**说明:**执行提交阶段(DoCommit)时,可能协调者或网络出现问题,都有可能导致事务的部分参与者无法接收到协调者的指令(包括执行提交和中断事务),在这种情况下,部分参与者会在超时等待之后继续进行事务提交。有一定小概率会指定数据不一致的情况,所以这也是3PC协议的一大缺点。 +**说明**:执行提交阶段(DoCommit)时,可能协调者或网络出现问题,都有可能导致事务的部分参与者无法接收到协调者的指令(包括执行提交和中断事务),在这种情况下,部分参与者会在超时等待之后继续进行事务提交。有一定小概率会指定数据不一致的情况,所以这也是3PC协议的一大缺点。 From 419a2a77fd135c9eeed91f917af9ef4de97f6b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Thu, 17 Jan 2019 17:41:15 +0800 Subject: [PATCH 15/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86Paxos=E7=AE=97?= =?UTF-8?q?=E6=B3=95=E5=88=86=E6=9E=90=EF=BC=88=E6=A6=82=E5=BF=B5=E7=AF=87?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../markdown/distribution/PaxosAlgorithm.md | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/resource/markdown/distribution/PaxosAlgorithm.md b/resource/markdown/distribution/PaxosAlgorithm.md index 892f721..9a9d7e0 100644 --- a/resource/markdown/distribution/PaxosAlgorithm.md +++ b/resource/markdown/distribution/PaxosAlgorithm.md @@ -1,13 +1,41 @@ -**Paxos算法** 是基于消息传递的一致性算法,当前在分布式系统应用中被公认的最有效的强一致性算法。 +

Paxos算法(强一致性算法)

+**Paxos协议** 是一种就某个议题打成一致共识的协议(共识协议)。 -角色: -Proposer :提议者 +#### 什么是共识? -Acceptor:决策者 +比如在一个部门内,部门老大问大家年度旅游去哪里玩,大家先是选取旅游地点,可能大家的旅游向往地不一致,有人想去新加坡,有人想去泰国,意见产生分歧,这就是没打成**共识**。为了打成共识,部门老大说“既然意见不一致,大家就旅游目的地进行投票表决吧,选取票最多的目的地”。大家在参与投票,以及认可投票结果,最后大家打成共识。 -Client:产生议题者 +共识是一组参与者就一个结果达成一致的过程。如果参与者或他们的交流媒介可能遇到故障时,这个问题就变得困难了。比如上述投票过程是用的微信投票小程序(交流媒介),假设微信投票小程序挂了(仅仅是假设),那么打成共识的过程就会出现问题。 -Learner:最终决策学习者 \ No newline at end of file +--- + +基于Paxos协议实现的算法,当前在分布式系统应用中被公认的最有效的强一致性算法。下面就来分析一下“强一致性”的Paxos算法的机制。 + + + +#### 角色: + +**Client**:产生议题者,客户机向分布式系统发出请求,并等待响应。例如,分布式文件服务器中文件的写入请求。 + +**Acceptor(Voters)**:决策者(投票者),接受者,接受方充当协议的容错“存储器”。接受者被收集到称为引言的组中。任何发送给承兑人的信息必须发送到承兑人的法定人数。除非从仲裁中的每个接受方收到副本,否则从接受方收到的任何消息都将被忽略。 + +**Proposer** :提议者,提议者主张客户请求,试图说服接受方同意该请求,并在发生冲突时充当协调人,推动协议向前发展。 + +**Learner**:(最终决策)学习者,学习者充当协议的复制因素。一旦接受方同意客户请求,学习者可以采取行动(即:执行请求并向客户发送响应)。为了提高处理的可用性,可以添加更多的学习者。 + +**Leader**:领导者,Paxos需要一个杰出的提议者(称为领导者)来取得进展。许多进程可能认为它们是领导者,但协议只保证最终选择其中一个进程时才能取得进展。如果两个进程认为它们是领导者,它们可能会通过不断提出冲突的更新来拖延协议。然而,在这种情况下,安全性能仍然保持不变。 + + + +Quorums通过确保至少有一些幸存的处理器保留对结果的了解,来表达Paxos的安全性(或一致性)属性。Quorums定义为一组接受者的子集,其中任意两个子集(即任意两个quorum)共享至少一个成员。通常,仲裁是参与的接受方的任何多数。例如,给定一组接受者{A,B,C,D},多数仲裁将是任意三个接受者:{A,B,C}, {A,C,D}, {A,B,D}, {B,C,D}, {B,C,D}。更一般地,可以给受体分配任意的正权值;在这种情况下,仲裁可以定义为接受方的任何子集,其汇总权重大于所有接受方总权重的一半。 + + + + + +--- + +Paxos通常用于需要持久性的地方(例如,复制文件或数据库),其中持久性状态的数量可能很大。即使在一定数量的副本没有响应的情况下,协议也会尝试取得进展。还有一种机制可以删除永久失败的副本或添加新副本。 \ No newline at end of file From 4aa25baec7edd53c8553c105fc77aaa24bbadd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Fri, 25 Jan 2019 18:09:45 +0800 Subject: [PATCH 16/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E6=80=A7=E5=93=88=E5=B8=8C=E7=AE=97=E6=B3=95=E7=A9=BA?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/cache/ConsistentHashing.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 resource/markdown/cache/ConsistentHashing.md diff --git a/resource/markdown/cache/ConsistentHashing.md b/resource/markdown/cache/ConsistentHashing.md new file mode 100644 index 0000000..f47703b --- /dev/null +++ b/resource/markdown/cache/ConsistentHashing.md @@ -0,0 +1,2 @@ +

Redis集群

+ From 74d82be68c729c080807551944c10b03784d5eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Sat, 26 Jan 2019 18:17:19 +0800 Subject: [PATCH 17/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86LeetCode?= =?UTF-8?q?=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../markdown/algorithm/0.array.maxProfit.md | 63 ++++++++++++++++ .../algorithm/0.array.removeDuplicates.md | 74 +++++++++++++++++++ resource/markdown/algorithm/0.array.rotate.md | 46 ++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 resource/markdown/algorithm/0.array.maxProfit.md create mode 100644 resource/markdown/algorithm/0.array.removeDuplicates.md create mode 100644 resource/markdown/algorithm/0.array.rotate.md diff --git a/resource/markdown/algorithm/0.array.maxProfit.md b/resource/markdown/algorithm/0.array.maxProfit.md new file mode 100644 index 0000000..75a34f2 --- /dev/null +++ b/resource/markdown/algorithm/0.array.maxProfit.md @@ -0,0 +1,63 @@ +

Leetcode:买卖股票的最佳时机 II

+ +给定一个数组,它的第 *i* 个元素是一支给定股票第 *i* 天的价格。 + +设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。 + +**注意:**你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 + +**示例 1:** + +```java +输入: [7,1,5,3,6,4] +输出: 7 +解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + 随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 +``` + +**示例 2:** + +```java +输入: [1,2,3,4,5] +输出: 4 +解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。 + 注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。 + 因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 +``` + +**示例 3:** + +```java +输入: [7,6,4,3,1] +输出: 0 +解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 +``` + + + +**解题思路:** + +只看涨,不看跌。不能同参与多个交易,只能根据时间分段买。 + +![过程说明图](https://i.loli.net/2019/01/24/5c49cf8b2385e.png) + + + +**Java代码如下:** + +```java +class Solution { + public int maxProfit(int[] prices) { + int profit = 0; + for (int i = 0; i < prices.length - 1; i++) { + // 涨钱,就获利润 + if (prices[i + 1] > prices[i]) { + profit += prices[i + 1] - prices[i]; + } + } + + return profit; + } +} +``` + diff --git a/resource/markdown/algorithm/0.array.removeDuplicates.md b/resource/markdown/algorithm/0.array.removeDuplicates.md new file mode 100644 index 0000000..9a581a6 --- /dev/null +++ b/resource/markdown/algorithm/0.array.removeDuplicates.md @@ -0,0 +1,74 @@ +

Leetcode:从排序数组中删除重复项

+ +给定一个排序数组,你需要在**原地**删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。 + +不要使用额外的数组空间,你必须在**原地修改输入数组**并在使用 O(1) 额外空间的条件下完成。 + +**示例 1:** + +``` +给定数组 nums = [1,1,2], + +函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。 + +你不需要考虑数组中超出新长度后面的元素。 +``` + +**示例 2:** + +``` +给定 nums = [0,0,1,1,1,2,2,3,3,4], + +函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。 + +你不需要考虑数组中超出新长度后面的元素。 +``` + +**说明:** + +为什么返回数值是整数,但输出的答案是数组呢? + +请注意,输入数组是以**“引用”**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。 + +你可以想象内部操作如下: + +```java +// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝 +int len = removeDuplicates(nums); + +// 在函数里修改输入数组对于调用者是可见的。 +// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。 +for (int i = 0; i < len; i++) { + print(nums[i]); +} +``` + + + +**解题思路:** + +题目要求必须在原数组修改,那么元素只能在原数组移动,时间复杂度 O(1),那么只能遍历一次数组。为了保证数据靠近头部,从头部开始遍历。用 index 表示当前元素前的元素都没有重复的元素,以此向后遍历,保证出现 **新** 的元素(也就是前面未出现的元素)那么把它 `nums[i]` 放到 `nums[++index]` 位置。 + +总体思路:把后面第一次出现的新元素,往前放。 + +![过程说明图](https://i.loli.net/2019/01/24/5c49c5307b3be.png) + + + +**Java代码如下:** + +```java +class Solution { + public int removeDuplicates(int[] nums) { + int index = 0; + for (int i = 0; i < nums.length; i++) { + if (nums[i] != nums[index]) { + nums[++index] = nums[i]; + } + } + + return index + 1; + } +} +``` + diff --git a/resource/markdown/algorithm/0.array.rotate.md b/resource/markdown/algorithm/0.array.rotate.md new file mode 100644 index 0000000..f0506b7 --- /dev/null +++ b/resource/markdown/algorithm/0.array.rotate.md @@ -0,0 +1,46 @@ +

Leetcode:旋转数组

+ +给定一个数组,将数组中的元素向右移动 *k* 个位置,其中 *k* 是非负数。 + +**示例 1:** + +```java +输入: [1,2,3,4,5,6,7] 和 k = 3 +输出: [5,6,7,1,2,3,4] +解释: +向右旋转 1 步: [7,1,2,3,4,5,6] +向右旋转 2 步: [6,7,1,2,3,4,5] +向右旋转 3 步: [5,6,7,1,2,3,4] +``` + +**示例 2:** + +```java +输入: [-1,-100,3,99] 和 k = 2 +输出: [3,99,-1,-100] +解释: +向右旋转 1 步: [99,-1,-100,3] +向右旋转 2 步: [3,99,-1,-100] +``` + +**说明:** + +- 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。 +- 要求使用空间复杂度为 O(1) 的原地算法。 + + + +**解题思路:** + + + +![过程说明图]() + + + +**Java代码如下:** + +```java + +``` + From 9c8d85f42431d15fededce18ef9e0e0616f54e10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Sun, 27 Jan 2019 22:20:05 +0800 Subject: [PATCH 18/39] =?UTF-8?q?README=E6=B7=BB=E5=8A=A0=E4=BA=86Redis?= =?UTF-8?q?=E7=AB=A0=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 +++++++++++++++++--- resource/markdown/cache/BloomFilter.md | 2 ++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 resource/markdown/cache/BloomFilter.md diff --git a/README.md b/README.md index 915311c..7d51c13 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,22 @@ * [数据库 (第 05 篇)精讲: SQL优化](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/SQLOptimization.md) * [数据库 (第 06 篇)精讲: 数据库拆分](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/DBSplit.md) +### 八、:sunny::umbrella::zap:Redis 核心应用 + +- Redis(第01篇)核心:丰富的数据类型 +- Redis(第01篇)核心:读写分离与主从复制 +- Redis(第01篇)核心:Sentinel 哨兵 +- Redis(第01篇)核心:集群 +- Redis(第01篇)核心:事务 +- Redis(第01篇)核心:持久化--RDB与AOF +- Redis(第01篇)核心:缓存更新 +- Redis(第01篇)核心:布隆过滤 +- Redis(第01篇)核心:缓存击穿 +- Redis(第01篇)核心:缓存雪崩 +- Redis(第01篇)核心:热点key + +### 九、:telescope::tokyo_tower::satellite:分布式系统 -### 八、:telescope::tokyo_tower::satellite:分布式系统 * [分布式系统 (第 01 篇) 精讲:集群与分布式](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/WhatisDistributed.md) * [分布式系统 (第 02 篇) 精讲:CAP定理与BASE理论](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/CAPandBASE.md) * [分布式系统 (第 03 篇) 精讲:X/Open DTP 与 XA 事务](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/XA.md) @@ -130,7 +144,7 @@ * 分布式系统 (第 14 篇) 精讲:分布式支付框架的实现 -### 九、:microscope::microscope::microscope:微服务 +### 十、:microscope::microscope::microscope:微服务 * 微服务 (第 01 篇) 精讲:什么是微服务? * 微服务 (第 01 篇) 精讲:服务建模 * 微服务 (第 01 篇) 精讲:服务拆分 @@ -142,7 +156,7 @@ * 微服务 (第 01 篇) 精讲:SpringCloud -### 十、:bicyclist::horse_racing::snowboarder:高并发与高可用 +### 十一、:bicyclist::horse_racing::snowboarder:高并发与高可用 * 高并发与高可用 (第 01 篇) 精讲:全局id生成算法 * 高并发与高可用 (第 01 篇) 精讲:如何实现请求幂等性 * 高并发与高可用 (第 01 篇) 精讲:如何防止网络抖动产生的重复建单 diff --git a/resource/markdown/cache/BloomFilter.md b/resource/markdown/cache/BloomFilter.md new file mode 100644 index 0000000..2ba4390 --- /dev/null +++ b/resource/markdown/cache/BloomFilter.md @@ -0,0 +1,2 @@ +

布隆过滤

+ From 73f7b5f45d4c07540c582b21ffafd9251a244cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Tue, 29 Jan 2019 18:24:49 +0800 Subject: [PATCH 19/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86Redis=E5=8D=95?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=E5=BF=AB=E9=80=9F=E7=9A=84=E5=8E=9F=E5=9B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +++++--- resource/markdown/cache/RedisDataStructure.md | 4 ++++ resource/markdown/cache/SingleThreadModel.md | 19 +++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 resource/markdown/cache/RedisDataStructure.md create mode 100644 resource/markdown/cache/SingleThreadModel.md diff --git a/README.md b/README.md index 7d51c13..2afc092 100644 --- a/README.md +++ b/README.md @@ -114,16 +114,18 @@ ### 八、:sunny::umbrella::zap:Redis 核心应用 -- Redis(第01篇)核心:丰富的数据类型 +- Redis(第01篇)核心:Redis单线程模型为啥这么快? + - Redis(第01篇)核心:读写分离与主从复制 - Redis(第01篇)核心:Sentinel 哨兵 -- Redis(第01篇)核心:集群 +- Redis(第01篇)核心:集群与分布式 - Redis(第01篇)核心:事务 - Redis(第01篇)核心:持久化--RDB与AOF - Redis(第01篇)核心:缓存更新 - Redis(第01篇)核心:布隆过滤 -- Redis(第01篇)核心:缓存击穿 +- Redis(第01篇)核心:缓存击穿(缓存穿透) - Redis(第01篇)核心:缓存雪崩 +- Redis(第01篇)核心:缓存降级 - Redis(第01篇)核心:热点key ### 九、:telescope::tokyo_tower::satellite:分布式系统 diff --git a/resource/markdown/cache/RedisDataStructure.md b/resource/markdown/cache/RedisDataStructure.md new file mode 100644 index 0000000..a2588d0 --- /dev/null +++ b/resource/markdown/cache/RedisDataStructure.md @@ -0,0 +1,4 @@ +

Redis丰富的数据结构

+ +Redis是基于 **内存** 的 key-value 数据库,距离操作系统更近,存取性能更好。它不仅速度快,而且提供丰富的数据结构。 + diff --git a/resource/markdown/cache/SingleThreadModel.md b/resource/markdown/cache/SingleThreadModel.md new file mode 100644 index 0000000..d46ff77 --- /dev/null +++ b/resource/markdown/cache/SingleThreadModel.md @@ -0,0 +1,19 @@ +

Redis单线程模型为啥这么快?

+ +**1.基于内存操作:** Redis将所有需要存储的数据都存放在内存中,基于内存的随机访问速度是磁盘的10万倍左右,即使是SSD遥不可及。这是Redis操作快速的重要基础。 + + + +**2.C语言实现:** 相同逻辑下的C语言程序,执行效率要比其他语言的高很多。C语言与当前主流的操作系统之间有着独特的关系,抛开开发难度来看,执行速度还是蛮快的。 + + + +**3.独特的数据结构:** + + + +**4.I/O多路复用模型:** + + + +**5.单线程模型:** 线程不仅在启动和销毁的过程中消耗资源,而且多线程在上下文切换时也会消耗资源。大量的资源都消耗在不必要的“抢占”和“争夺”过程中,不利于高效率操作。单线程正是避免了这种情况 \ No newline at end of file From 7fbbfe533b07861572d1d4db0672d0c8613e7004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Mon, 11 Feb 2019 18:12:11 +0800 Subject: [PATCH 20/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86Redis=E4=B8=BB?= =?UTF-8?q?=E4=BB=8E=E5=A4=8D=E5=88=B6=E4=B8=8A=E5=8D=8A=E7=AF=87=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- resource/markdown/cache/DataStructure.md | 2 - resource/markdown/cache/RedisDataStructure.md | 4 -- resource/markdown/cache/Replication.md | 56 +++++++++++++++++++ resource/markdown/cache/SingleThreadModel.md | 10 ++-- 5 files changed, 64 insertions(+), 12 deletions(-) delete mode 100644 resource/markdown/cache/DataStructure.md delete mode 100644 resource/markdown/cache/RedisDataStructure.md create mode 100644 resource/markdown/cache/Replication.md diff --git a/README.md b/README.md index 2afc092..beb4ee1 100644 --- a/README.md +++ b/README.md @@ -114,9 +114,9 @@ ### 八、:sunny::umbrella::zap:Redis 核心应用 -- Redis(第01篇)核心:Redis单线程模型为啥这么快? +- [Redis(第01篇)核心:Redis单线程模型为啥这么快?](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/SingleThreadModel.md) -- Redis(第01篇)核心:读写分离与主从复制 +- [Redis(第01篇)核心:主从复制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) - Redis(第01篇)核心:Sentinel 哨兵 - Redis(第01篇)核心:集群与分布式 - Redis(第01篇)核心:事务 diff --git a/resource/markdown/cache/DataStructure.md b/resource/markdown/cache/DataStructure.md deleted file mode 100644 index 24ef512..0000000 --- a/resource/markdown/cache/DataStructure.md +++ /dev/null @@ -1,2 +0,0 @@ -

Redis基本数据结构

- diff --git a/resource/markdown/cache/RedisDataStructure.md b/resource/markdown/cache/RedisDataStructure.md deleted file mode 100644 index a2588d0..0000000 --- a/resource/markdown/cache/RedisDataStructure.md +++ /dev/null @@ -1,4 +0,0 @@ -

Redis丰富的数据结构

- -Redis是基于 **内存** 的 key-value 数据库,距离操作系统更近,存取性能更好。它不仅速度快,而且提供丰富的数据结构。 - diff --git a/resource/markdown/cache/Replication.md b/resource/markdown/cache/Replication.md new file mode 100644 index 0000000..6ec820c --- /dev/null +++ b/resource/markdown/cache/Replication.md @@ -0,0 +1,56 @@ +

主从复制

+ +**数据复制的意义:** 1、读写分离,降低单节点的读写压力;2、容灾转移,单机出现问题,从节点接替。 + +Redis 数据复制是单向的,而且一个节点从属一个master节点。 + + + +设置主从复制方式一: redis实例启动前,在配置文件中设置 `slaveof host port` + +设置主从复制方式二: redis实例启动时,使用 `redis-server` 加上 `--slaveof host port` + +设置主从复制方式三:redis实例启动后,在命令行设置 `slaveof host port` + +断开主从复制:在从节点命令行执行 `slaveof no one` + + + +#### 一主一从: + +![一主一从](https://i.loli.net/2019/02/11/5c612b5f58ffc.png) + +常规的主从配置,一个主节点配置一个从节点。 + +作主备容灾转移时,主节点负责读写,从节点负责复制数据,一旦主节点宕机,从节点晋升为主节点,接替读写工作; + +做读写分离时,主节点只写,从节点只读。 + +注意,节点最好要开启持久化,否则Redis实例宕机重启后,数据变空。 + + + +#### 一主多从: + +![一主多从](https://i.loli.net/2019/02/11/5c612ba97b585.png) + +根据“二八原则”,大部分场景下都是在读操作,通常给主节点配置多个从节点,主节点只写,从节点只读,从节点间分摊压力。 + + + +#### 树状主从: + +![树状主从](https://i.loli.net/2019/02/11/5c612bbf22aea.png) + +但是从节点过多,主节点的IO就越大(主要是IO中的O),这时就会出现IO堵塞。使用树状主从结构,每个主节点的从节点数量就变少,这样主节点的IO输出压力就会变小,非叶子节点的主节点层层分担压力,从而降低根节点的压力。 + +(为了能够校正数据,不推荐将从节点设置为只读模式 ~~slave-read-only=yes~~) + + + +--- + +

复制原理

+ +![Redis注册复制原理](https://i.loli.net/2019/02/11/5c6147b3de702.png) + diff --git a/resource/markdown/cache/SingleThreadModel.md b/resource/markdown/cache/SingleThreadModel.md index d46ff77..e235f69 100644 --- a/resource/markdown/cache/SingleThreadModel.md +++ b/resource/markdown/cache/SingleThreadModel.md @@ -1,6 +1,8 @@

Redis单线程模型为啥这么快?

-**1.基于内存操作:** Redis将所有需要存储的数据都存放在内存中,基于内存的随机访问速度是磁盘的10万倍左右,即使是SSD遥不可及。这是Redis操作快速的重要基础。 +![Single](https://i.loli.net/2019/02/11/5c610bc32d64e.jpeg) + +**1.基于内存操作:** Redis将所有需要存储的数据都存放在内存中,基于内存的随机访问速度是磁盘的10万倍左右,即使是SSD也遥不可及,这是Redis操作快速的重要物理基础。 @@ -8,12 +10,12 @@ -**3.独特的数据结构:** +**3.简单的数据结构:** 内存数据库的另一个优点是,基于内存的数据结构要比基于硬盘的数据结构更加简单,对数据的操作也更加简单,因此Redis可以做很多事情,内部复杂性很小。Redis占用的内存空间也是比较少。 -**4.I/O多路复用模型:** +**4.多路I/O复用模型:** **多路** 指的的是多个网络连接,**复用** 指的是重复使用一个线程。I/O多路复用技术可以使单个线程处理多个网络连接请求。 -**5.单线程模型:** 线程不仅在启动和销毁的过程中消耗资源,而且多线程在上下文切换时也会消耗资源。大量的资源都消耗在不必要的“抢占”和“争夺”过程中,不利于高效率操作。单线程正是避免了这种情况 \ No newline at end of file +**5.单线程模型:** 避免了多线程的启动、销毁、上下文切换、加锁、解锁等消耗资源的操作。单线程简单,不仅能避免上下文切换这种非常消耗资源的操作,而且可以避免死锁的情况。大大提升CPU的利用率。如果是多核服务器,可以通过启动多个Redis实例来利用多个CPU。 \ No newline at end of file From 6cbf37ef451a9bb3a6178d3752525a5463eda3aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Tue, 12 Feb 2019 18:00:16 +0800 Subject: [PATCH 21/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E6=8C=81?= =?UTF-8?q?=E4=B9=85=E5=8C=96=E7=9A=84=E6=8F=8F=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 23 ++++++++++---------- resource/markdown/cache/DataPersistence.md | 2 -- resource/markdown/cache/Persistence.md | 25 ++++++++++++++++++++++ resource/markdown/cache/Replication.md | 5 +++-- 4 files changed, 39 insertions(+), 16 deletions(-) delete mode 100644 resource/markdown/cache/DataPersistence.md create mode 100644 resource/markdown/cache/Persistence.md diff --git a/README.md b/README.md index beb4ee1..2d3f3b9 100644 --- a/README.md +++ b/README.md @@ -115,18 +115,17 @@ ### 八、:sunny::umbrella::zap:Redis 核心应用 - [Redis(第01篇)核心:Redis单线程模型为啥这么快?](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/SingleThreadModel.md) - -- [Redis(第01篇)核心:主从复制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) -- Redis(第01篇)核心:Sentinel 哨兵 -- Redis(第01篇)核心:集群与分布式 -- Redis(第01篇)核心:事务 -- Redis(第01篇)核心:持久化--RDB与AOF -- Redis(第01篇)核心:缓存更新 -- Redis(第01篇)核心:布隆过滤 -- Redis(第01篇)核心:缓存击穿(缓存穿透) -- Redis(第01篇)核心:缓存雪崩 -- Redis(第01篇)核心:缓存降级 -- Redis(第01篇)核心:热点key +- [Redis(第02篇)核心:主从复制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) +- Redis(第03篇)核心:Sentinel 哨兵 +- Redis(第04篇)核心:集群与分布式 +- Redis(第05篇)核心:事务 +- [Redis(第06篇)核心:持久化--RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) +- Redis(第07篇)核心:缓存更新 +- Redis(第08篇)核心:布隆过滤 +- Redis(第09篇)核心:缓存击穿(缓存穿透) +- Redis(第10篇)核心:缓存雪崩 +- Redis(第11篇)核心:缓存降级 +- Redis(第12篇)核心:热点key ### 九、:telescope::tokyo_tower::satellite:分布式系统 diff --git a/resource/markdown/cache/DataPersistence.md b/resource/markdown/cache/DataPersistence.md deleted file mode 100644 index 4340857..0000000 --- a/resource/markdown/cache/DataPersistence.md +++ /dev/null @@ -1,2 +0,0 @@ -

Redis持久化:RDB与AOF

- diff --git a/resource/markdown/cache/Persistence.md b/resource/markdown/cache/Persistence.md new file mode 100644 index 0000000..20d6816 --- /dev/null +++ b/resource/markdown/cache/Persistence.md @@ -0,0 +1,25 @@ +

Redis持久化

+ +Redis 是一种基于内存的数据库,一旦断电、重启,Redis 中的数据将不复存在。Redis 提供了 RDB 和 AOF 两个持久化的方式。当 Redis 实例重启时,可以使用已持久化的数据(文件)来还原内存数据集。 + + + +--- + +

RDB

+ +**RDB** 的全称是 *Redis DataBase* ,Redis 数据库。这种持久化方式是将当前Redis内存中的数据生成快照文件保存到硬盘,简单粗暴。 + + + +在之前的、已经废弃的 `save` 命令来实现RDB持久化数据,因为Redis是单线程工作的,`save` 命令持久化时会一直阻塞,直到持久化完成。数据量越大,持久化带来的阻塞时间就越长。 + +现在可以使用 `bgsave` 命令来代替 `save`。bg就是background,使用 `bgsave` 方式RDB持久化时,Redis工作进程会 fork 一个子进程,该子进程专门来负责RDB持久化,而工作进程会继续工作,阻塞时间只发生。 + + + + + +--- + +

AOF

\ No newline at end of file diff --git a/resource/markdown/cache/Replication.md b/resource/markdown/cache/Replication.md index 6ec820c..2929705 100644 --- a/resource/markdown/cache/Replication.md +++ b/resource/markdown/cache/Replication.md @@ -2,7 +2,7 @@ **数据复制的意义:** 1、读写分离,降低单节点的读写压力;2、容灾转移,单机出现问题,从节点接替。 -Redis 数据复制是单向的,而且一个节点从属一个master节点。 +Redis 数据复制是单向的,而且一个节点只能从属一个master节点。 @@ -52,5 +52,6 @@ Redis 数据复制是单向的,而且一个节点从属一个master节点。

复制原理

-![Redis注册复制原理](https://i.loli.net/2019/02/11/5c6147b3de702.png) +![Redis注册复制原理](https://i.loli.net/2019/02/12/5c62226abbf3d.png) +首先,通过上述配置主从复制,从节点保存主节点的信息,然后建立 socket 连接。Redis的ping命令是用于客户端检测服务端是否正常运作或消息延时的一种命令,是一种心跳机制,在这里从节点就是客户端,主节点就是服务端。从节点发送ping命令至主节点,如果主节点正常运作则返回pong,这样才能表示双方网络通达。紧接着,如果主节点开启 `requirepass foobared` 参数(foobared 可以认为是安全校验密码),主节点会对从节点进行权限校验。密码正确后,才进行数据同步,最后不断的、陆陆续续的、持续复制后面的数据,实现这一持续复制数据的实现方式就是复制其操作命令。 \ No newline at end of file From ec8062c905b0f9d008e584d4c4f2b816bf9dc364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 13 Feb 2019 19:47:16 +0800 Subject: [PATCH 22/39] =?UTF-8?q?Redis=E6=8C=81=E4=B9=85=E5=8C=96RDB?= =?UTF-8?q?=E4=B8=8EAOF=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +- resource/markdown/cache/Persistence.md | 136 ++++++++++++++++++++++++- 2 files changed, 134 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2d3f3b9..d1131ba 100644 --- a/README.md +++ b/README.md @@ -113,13 +113,12 @@ * [数据库 (第 06 篇)精讲: 数据库拆分](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/DBSplit.md) ### 八、:sunny::umbrella::zap:Redis 核心应用 - - [Redis(第01篇)核心:Redis单线程模型为啥这么快?](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/SingleThreadModel.md) - [Redis(第02篇)核心:主从复制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) -- Redis(第03篇)核心:Sentinel 哨兵 - Redis(第04篇)核心:集群与分布式 +- Redis(第03篇)核心:高可用 Sentinel 哨兵 - Redis(第05篇)核心:事务 -- [Redis(第06篇)核心:持久化--RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) +- [Redis(第06篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) - Redis(第07篇)核心:缓存更新 - Redis(第08篇)核心:布隆过滤 - Redis(第09篇)核心:缓存击穿(缓存穿透) diff --git a/resource/markdown/cache/Persistence.md b/resource/markdown/cache/Persistence.md index 20d6816..ac84ef1 100644 --- a/resource/markdown/cache/Persistence.md +++ b/resource/markdown/cache/Persistence.md @@ -1,5 +1,7 @@

Redis持久化

+![persistence](https://i.loli.net/2019/02/13/5c63bb6ce9d5f.jpeg) + Redis 是一种基于内存的数据库,一旦断电、重启,Redis 中的数据将不复存在。Redis 提供了 RDB 和 AOF 两个持久化的方式。当 Redis 实例重启时,可以使用已持久化的数据(文件)来还原内存数据集。 @@ -8,18 +10,144 @@ Redis 是一种基于内存的数据库,一旦断电、重启,Redis 中的

RDB

-**RDB** 的全称是 *Redis DataBase* ,Redis 数据库。这种持久化方式是将当前Redis内存中的数据生成快照文件保存到硬盘,简单粗暴。 +**RDB** 的全称是 *Redis DataBase* ,Redis 数据库。这种持久化方式是将当前Redis内存中的数据生成 **快照(Snapshot)** 文件保存到硬盘,简单粗暴。RDB也是Redis默认开启的持久化方式。 + + + +##### 触发持久化 + +![save](https://i.loli.net/2019/02/13/5c63c0174e045.png) + +在之前已经废弃的 `save` 命令来实现RDB持久化数据,`save` 命令持久化时会一直阻塞,直到持久化完成。数据量越大,持久化过程带来的阻塞时间就越长。 + +![bgsave](https://i.loli.net/2019/02/13/5c63c02cc0753.png) + +现在可以使用 `bgsave` 命令来代替 `save`。bg就是background,使用 `bgsave` 方式RDB持久化时,Redis工作进程会 fork 一个子进程,该子进程专门来负责耗时的RDB持久化,而工作进程会继续工作,阻塞时间只发生在短暂的 fork 阶段。 + + + +除了主动触发命令外,Redis还是提供了自动触发RDB持久化命令 `bgsave ` ,该命令表示在seconds秒内,Redis内存数据修改的次数达到changes次时,将触发RDB持久化。比如 `bgsave 7200 10` 表示在2个小时内,数据被更改10次时将自动触发RDB持久化。 + +(注意changes计数并不包含查询语句,当然被操作的key一定存在,否则也不会进行计数) + +作者现在测试使用的是 `4.0.9` 版本,Redis在此版本默认开启的参数是 `save 900 1` 、`save 300 10` 、`save 60 10000` ,更加实际情况可自定义更改。 + + + +##### 数据快照存储 + +首先,工作目录默认为当前Redis目录,那么持久化的RDB数据快照将存储在该目录,可通过 `dir ./` 参数进行修改。生成的数据存储快照名称默认为 `dump.rdb` ,可通过 `dbfilename dump.rdb` 参数进行修改。RDB持久化先生存一个临时快照,待数据持久化完成,这个临时快照会替换原快照,以防持久化过程出现问题。 + +RDB存储快照默认是使用LZF算法压缩功能,压缩后的文件体积将大大减少。压缩文件但并不是十全十美,RDB快照压缩时也会消耗CPU,数据量越大,消耗CPU资源就越多,但官网还是极力推荐我们开始此功能。 + +RDB持久化时可能出现权限问题、存储空间不够用等问题,Redis 默认开启`stop-writes-on-bgsave-error yes` 参数,意味着出现 error时,Redis将停止向快照文件写入数据。 + +在Redis保存/恢复数据时,并不是一股脑进行保存/恢复,默认会对RDB快照文件进行检查,性能会受到影响(大约10%),因此可以禁用它以获得最大的性能。(推荐开启) + + + +--- + +

AOF

+ +**AOF** 的全称是 *Append Only File* ,仅附加文件,类似于MySQL的binlog。这种持久化方式是通过保存写命令来记录操作日志,在数据恢复时,重新执行已记录的写命令,来达到数据恢复。 + + + +##### AOF原理 + +![AOF](https://i.loli.net/2019/02/13/5c63dd9d9f120.png) + +默认情况下,AOF是关闭的,可以通过修改 `appendonly no` 为 `appendonly yes` 来开启 AOF 持久化。内存的Output的带宽远大于磁盘Input的带宽,如果内存瞬时将大量的数据写入磁盘,必然发生IO堵塞,对于单线程工作的Redis也将会在性能上大打折扣。所以所有的写命令并不会直接写入AOF文件,而是先将写命令存储至AOF Buffer缓冲区,最后在同步至AOF文件。 + +生成的AOF文件名,默认为 `appendonly.aof` ,可以修改 `appendfilename "appendonly.aof"` 参数来更改生成的AOF文件名。 + +从缓冲区同步至磁盘,Redis提供了三种同步方式: -在之前的、已经废弃的 `save` 命令来实现RDB持久化数据,因为Redis是单线程工作的,`save` 命令持久化时会一直阻塞,直到持久化完成。数据量越大,持久化带来的阻塞时间就越长。 +**always:** 写命令被追加到AOF缓存后,调用系统fsync函数将缓冲区的数据同步至磁盘,最后返回 -现在可以使用 `bgsave` 命令来代替 `save`。bg就是background,使用 `bgsave` 方式RDB持久化时,Redis工作进程会 fork 一个子进程,该子进程专门来负责RDB持久化,而工作进程会继续工作,阻塞时间只发生。 +**everysec:** 写命令被追加到AOF缓存后,调用系统write函数,在write函数执行完成,然后返回,会触发系统将对缓冲区的数据进行同步至磁盘 +**no:** 写命令被追加到AOF缓存后,调用系统write函数,然后返回,后续的同步由系统负责 +其中 `everysec` 不仅是默认的方式,也是推荐的方式。 + + + +##### AOF重写机制 + +AOF持久化文件存储的是Redis写命令,这比仅仅是数据的存储文件要大了很多,而且像如下的命令就可以考虑做很多优化: + +```shell +127.0.0.1:6379> set user:10001 Kevin +OK +127.0.0.1:6379> del user:10001 +(integer) 1 +127.0.0.1:6379> set user:10002 Jack +OK +127.0.0.1:6379> del user:10002 +(integer) 1 +127.0.0.1:6379> set user:10003 Tony +OK +127.0.0.1:6379> del user:10003 +(integer) 1 +``` + +上述命令都是些命令,最终是空数据,如果都存储到AOF文件,不仅增大文件体积,而且在恢复数据时会有很多不必要的执行命令。AOF重写机制对持久化的命令做了优化。 + +在AOF重写机制中,首先遍历数据库,如果数据库为空库,则跳过该库。对于非空的数据库,则遍历所有的key,如果key过期则跳过该key,如果key有过期时间则设置key的过期时间。对于不同类型的数据,用不同的、最简单的写命令来保存,比如下面的命令: + +```shell +# 假设一开始 user 对应的 value 为空 +lpush user kevin + +lpush user jack + +lpush user tony + +rpop user + +lpop user +``` + +可以重写成: + +```shell +lpush user jack +``` + +这样大大减少了不必要的命令,在存储AOF文件时,文件的体积也会大大减少,而且恢复数据的效率也会大大提升。 + + + +AOF的重写机制并不是随机触发的,默认是当前写入AOF文件大小较上次重写后文件大小增长了 `100%` 时就触发重写机制。比如上次重写后的文件大小是1G,经过一段时间,AOF文件体积增长了 `1G` ,那么增长率就是 `100%` ,就会触发AOF重写。可以通过设置 `auto-aof-rewrite-percentage 100` 来调节文件增长率的触发阈值。 + +如果AOF的文件较少,不足以影响太大性能,所以没必要重写AOF文件。可以通过 `auto-aof-rewrite-min-size 64mb` 来设置AOF文件重写的最小阈值。 + + + +`aof-load-truncated yes` 参数表示,在Redis恢复数据时,最后一条命令可能不完整,开启则表示忽略最后一条不完整的命令。 --- -

AOF

\ No newline at end of file +

RDB和AOF对比

+ +RDB开始压缩后,生成一个体量更小、更紧凑的二进制文件,占有空间小。RDB不仅有着较快的数据恢复能力,而且可以直接复制RDB文件至其他Redis实例进行回复数据,有着良好的容灾体验。RDB适用于定时复制、全量复制、快速恢复的场景,由于RDB文件生成时间长,数据有存储延迟,不适用于近实时持久化的场景。 + + + +AOF以追加命令的方式,持续的、近实时的写入文件(秒级别)。而且,在一定程度时,AOF不断的重写持久化文件,不断的优化。特别是在数据完整性上要比RDB好很多,例如,使用默认的数据同步策略,Redis在发生服务器断电等重大事件时,可能只丢失一秒钟的写入时间,或者在Redis进程本身发生错误但操作系统仍在正常运行时丢失一次写入时间。所以AOF比较适合对数据实时性和数据完整性要求比较高的场景。AOF相较于RDB,缺点是文件相对较大,而且恢复数据时因为要执行全部的写命令,所以数据恢复比较慢,特别在是在被依赖的应用系统高负载的情况下,较长时间的数据恢复,对后端系统是不可容忍的。 + + + +但AOF和RDB持久性可以同时启用,不会出现问题。如果在启动时启用了AOF,redis将加载AOF,即具有更好的持久性保证的文件。而且在较新的版本还支持混合模式。 + + + +#### RDB-AOF混合持久化 + +Redis从 `4.0` 开始支持RDB-AOF混合持久化,默认是关闭状态,可以通过 `aof-use-rdb-preamble yes` 开启。AOF文件在重写之后,将生成一份记录已有数据的RDB快照文件,再生成一份记录最近写命令的AOF文件,作为对RDB快照的补充。这样的混合持久化模式,将兼具RDB和AOF的双优特性。 \ No newline at end of file From 1e40d048fda63281930f47549519faed42de48f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Fri, 15 Feb 2019 18:17:22 +0800 Subject: [PATCH 23/39] =?UTF-8?q?=E8=B0=83=E6=95=B4=E4=BA=86Redis=E7=9A=84?= =?UTF-8?q?=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d1131ba..6b1bb38 100644 --- a/README.md +++ b/README.md @@ -113,18 +113,18 @@ * [数据库 (第 06 篇)精讲: 数据库拆分](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/DBSplit.md) ### 八、:sunny::umbrella::zap:Redis 核心应用 -- [Redis(第01篇)核心:Redis单线程模型为啥这么快?](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/SingleThreadModel.md) -- [Redis(第02篇)核心:主从复制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) -- Redis(第04篇)核心:集群与分布式 -- Redis(第03篇)核心:高可用 Sentinel 哨兵 -- Redis(第05篇)核心:事务 -- [Redis(第06篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) -- Redis(第07篇)核心:缓存更新 -- Redis(第08篇)核心:布隆过滤 -- Redis(第09篇)核心:缓存击穿(缓存穿透) +- Redis(第01篇)核心:数据结构与底层数据结构 +- [Redis(第02篇)核心:Redis单线程模型为啥这么快?](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/SingleThreadModel.md) +- [Redis(第03篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) +- [Redis(第04篇)核心:主从复制与故障转移](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) +- Redis(第05篇)核心:高可用与 Sentinel 哨兵 +- Redis(第06篇)核心:一致性哈希算法 +- Redis(第07篇)核心:分布式缓存与 Redis Cluster +- Redis(第08篇)核心:缓存倾斜与热点key +- Redis(第09篇)核心:缓存击穿(缓存穿透) - Redis(第10篇)核心:缓存雪崩 -- Redis(第11篇)核心:缓存降级 -- Redis(第12篇)核心:热点key +- Redis(第11篇)核心:布隆过滤 +- Redis(第12篇)核心:缓存降级 ### 九、:telescope::tokyo_tower::satellite:分布式系统 @@ -138,10 +138,9 @@ * 分布式系统 (第 08 篇) 精讲:设计分布式锁 * 分布式系统 (第 09 篇) 精讲:分布式session/token一致性设计 * 分布式系统 (第 10 篇) 精讲:分布式事务 -* 分布式系统 (第 11 篇) 精讲:分布式缓存 -* 分布式系统 (第 12 篇) 精讲:分布式高并发的支持与控制 -* 分布式系统 (第 13 篇) 精讲:分布式下悲观锁和乐观锁的实现 -* 分布式系统 (第 14 篇) 精讲:分布式支付框架的实现 +* 分布式系统 (第 11 篇) 精讲:分布式高并发的支持与控制 +* 分布式系统 (第 12 篇) 精讲:分布式下悲观锁和乐观锁的实现 +* 分布式系统 (第 13 篇) 精讲:分布式支付框架的实现 ### 十、:microscope::microscope::microscope:微服务 @@ -162,7 +161,7 @@ * 高并发与高可用 (第 01 篇) 精讲:如何防止网络抖动产生的重复建单 * 高并发与高可用 (第 01 篇) 精讲:如何度过服务器重启后的连接风暴 * 高并发与高可用 (第 01 篇) 精讲:数据持久化 + ACK + 补偿重试+请求幂等性+防重设计 -* 高并发与高可用 (第 01 篇) 精讲: 抢红包系统实现 +* 高并发与高可用 (第 01 篇) 精讲: 抢红包系统实现(高并发系统三把利刃:缓存、限流、降级) ### :soon:未完,待续 ... From c046f33fb2dcd6d9b80be616905abc908b45aa1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Sat, 16 Feb 2019 16:20:12 +0800 Subject: [PATCH 24/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86Redis=E6=95=85?= =?UTF-8?q?=E9=9A=9C=E8=BD=AC=E7=A7=BB=E4=B8=8E=E9=AB=98=E5=8F=AF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/cache/Replication.md | 38 +++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/resource/markdown/cache/Replication.md b/resource/markdown/cache/Replication.md index 2929705..d47a681 100644 --- a/resource/markdown/cache/Replication.md +++ b/resource/markdown/cache/Replication.md @@ -54,4 +54,40 @@ Redis 数据复制是单向的,而且一个节点只能从属一个master节 ![Redis注册复制原理](https://i.loli.net/2019/02/12/5c62226abbf3d.png) -首先,通过上述配置主从复制,从节点保存主节点的信息,然后建立 socket 连接。Redis的ping命令是用于客户端检测服务端是否正常运作或消息延时的一种命令,是一种心跳机制,在这里从节点就是客户端,主节点就是服务端。从节点发送ping命令至主节点,如果主节点正常运作则返回pong,这样才能表示双方网络通达。紧接着,如果主节点开启 `requirepass foobared` 参数(foobared 可以认为是安全校验密码),主节点会对从节点进行权限校验。密码正确后,才进行数据同步,最后不断的、陆陆续续的、持续复制后面的数据,实现这一持续复制数据的实现方式就是复制其操作命令。 \ No newline at end of file +首先,通过上述配置主从复制,从节点保存主节点的信息,然后建立 socket 连接。Redis的ping命令是用于客户端检测服务端是否正常运作或消息延时的一种命令,是一种心跳机制,在这里从节点就是客户端,主节点就是服务端。从节点发送ping命令至主节点,如果主节点正常运作则返回pong,这样才能表示双方网络通达。紧接着,如果主节点开启 `requirepass foobared` 参数(foobared 可以认为是安全校验密码),主节点会对从节点进行权限校验。密码正确后,才进行数据同步,最后不断的、陆陆续续的、持续复制后面的数据,实现这一持续复制数据的实现方式就是复制其操作命令。 + + + +--- + +

故障转移

+ +对于单个节点而言,有可能出现故障(fail),为了保证该服务的可用性,需要使用其他冗余或备用节点来接替该节点工作,这种拯救方式就是 **故障转移(Failover)** 。 + +比如一个 **一主两从** 的高可用方案,主节点工作,从节点作备用: + +![一主两从](https://i.loli.net/2019/02/16/5c67baed29b4a.png) + +如果主节点(master节点)出现故障,那么服务不可用,从节点(slave节点)也无法从master节点持续复制数据,如何实现故障转移呢?下面是基于客户端的实现。 + +1. 客户端使用心跳机制,定时检测 master、slave节点活性,比如使用ping命令; + +2. 如果master节点在一定时间内无回复,则认为master节点此时不可用; + +3. 从slave节点中随机选择或选择一个ping-pong网络较好的一个节点晋升为master,比如6380节点; + +4. 6380节点和6381节点先与6379断开复制关系 `slaveof no one`; + +5. 然后以6380为master节点,6381为salve节点建立复制关系; + + ![建立新的主从复制](https://i.loli.net/2019/02/16/5c67c5a831a15.png) + +6. 通过心跳检测6379节点故障恢复后,作为salve节点与master节点建立主从复制关系; + + ![屏幕快照 2019-02-16 下午4.10.30.png](https://i.loli.net/2019/02/16/5c67c61676337.png) + +7. 故障转移完成。(其实到了第3步选取6380为主节点后,服务就可用了) + + + +客户端为了高可用,也可以做成多节点,在Redis的master节点出现故障时,客户端多节点通过选举方式来产生新的master节点。从Redis 2.8 版本开始,新加入了 Redis Sentinel 来实现高可用。 \ No newline at end of file From 4a53a303c4951f947a33d5482e701bc6cc874d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Sun, 17 Feb 2019 23:39:46 +0800 Subject: [PATCH 25/39] =?UTF-8?q?=E5=B0=86=E4=B8=80=E8=87=B4=E6=80=A7?= =?UTF-8?q?=E5=93=88=E5=B8=8C=E7=AE=97=E6=B3=95=E7=9B=AE=E5=BD=95=E4=BB=8E?= =?UTF-8?q?Redis=E7=A7=BB=E9=80=81=E8=87=B3=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +++++++------- resource/markdown/cache/ConsistentHashing.md | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6b1bb38..3522acd 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,6 @@ - [Redis(第03篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) - [Redis(第04篇)核心:主从复制与故障转移](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) - Redis(第05篇)核心:高可用与 Sentinel 哨兵 -- Redis(第06篇)核心:一致性哈希算法 - Redis(第07篇)核心:分布式缓存与 Redis Cluster - Redis(第08篇)核心:缓存倾斜与热点key - Redis(第09篇)核心:缓存击穿(缓存穿透) @@ -135,12 +134,13 @@ * [分布式系统 (第 05 篇) 精讲:TCC事务补偿机制(柔性事务方案)](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/TryConfirmCancel.md) * 分布式系统 (第 06 篇) 精讲:Paxos算法(强一致性算法) * 分布式系统 (第 07 篇) 精讲:Chubby 与 Zookeeper -* 分布式系统 (第 08 篇) 精讲:设计分布式锁 -* 分布式系统 (第 09 篇) 精讲:分布式session/token一致性设计 -* 分布式系统 (第 10 篇) 精讲:分布式事务 -* 分布式系统 (第 11 篇) 精讲:分布式高并发的支持与控制 -* 分布式系统 (第 12 篇) 精讲:分布式下悲观锁和乐观锁的实现 -* 分布式系统 (第 13 篇) 精讲:分布式支付框架的实现 +* 分布式系统 (第 08 篇) 精讲:一致性哈希算法 +* 分布式系统 (第 09 篇) 精讲:设计分布式锁 +* 分布式系统 (第 10 篇) 精讲:分布式session/token一致性设计 +* 分布式系统 (第 11 篇) 精讲:分布式事务 +* 分布式系统 (第 12 篇) 精讲:分布式高并发的支持与控制 +* 分布式系统 (第 13 篇) 精讲:分布式下悲观锁和乐观锁的实现 +* 分布式系统 (第 14 篇) 精讲:分布式支付框架的实现 ### 十、:microscope::microscope::microscope:微服务 diff --git a/resource/markdown/cache/ConsistentHashing.md b/resource/markdown/cache/ConsistentHashing.md index f47703b..18d05b4 100644 --- a/resource/markdown/cache/ConsistentHashing.md +++ b/resource/markdown/cache/ConsistentHashing.md @@ -1,2 +1,2 @@ -

Redis集群

+

一致性哈希:变化世界中的负载平衡

From 8f16175e349742c69307e2e66c9e3ee807f29177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Sun, 17 Feb 2019 23:43:25 +0800 Subject: [PATCH 26/39] add a Sentinel file --- resource/markdown/cache/Sentinel.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 resource/markdown/cache/Sentinel.md diff --git a/resource/markdown/cache/Sentinel.md b/resource/markdown/cache/Sentinel.md new file mode 100644 index 0000000..f5fc406 --- /dev/null +++ b/resource/markdown/cache/Sentinel.md @@ -0,0 +1,2 @@ +

Redis高可用之Sentinel

+ From 98f49dbd5e66a69a4f85826ddb21473171f67b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Mon, 18 Feb 2019 18:48:20 +0800 Subject: [PATCH 27/39] =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7=E5=93=88?= =?UTF-8?q?=E5=B8=8C=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/cache/ConsistentHashing.md | 44 ++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/resource/markdown/cache/ConsistentHashing.md b/resource/markdown/cache/ConsistentHashing.md index 18d05b4..b1b99ee 100644 --- a/resource/markdown/cache/ConsistentHashing.md +++ b/resource/markdown/cache/ConsistentHashing.md @@ -1,2 +1,46 @@

一致性哈希:变化世界中的负载平衡

+Teradata在1986年发布的分布式数据库中使用了这种技术,但他没有提出这个术语。直到1997年,由David Karger等人在应用于分布式缓存问题中提出了 **“一致性哈希”** ,作为一种在不断变化的Web服务器集群中分配请求的方法。一致性哈希算法也是负载均衡算法中的一种。常用的负载均衡算法适用于集群,但不一定适用于分布式。 + + + +#### 案例背景 + +在某大促活动中,我们需要将1亿用户数据缓存到1000个Redis实例中,每个Redis实例均摊10万用户数据。通常使用哈希算法(hash(K) mod N )来做映射,先计算用户id的哈希值,然后模运算计算出对应的服务器,最后把对应的数据缓存到对应的服务器即可。此种方法比较简单,但存在一个问题,它不适用于分布式下节点宕机、扩容和缩容的场景。 + +![未命名1550461043.png](https://i.loli.net/2019/02/18/5c6a287f117fc.png) + +假如1号Redis服务宕机,对应10万用户将无法命中缓存。当然1号Redis服务可以做成高可用集群,但又不适用于分布式缓存下节点的扩容与缩容。试想,大促活动结束后,服务器的压力也会减少,没必要使用这么多服务器,可以将1000个Redis实例缩容为100个。随着节点的宕机、扩容和缩容,势必会带来数据的再哈希与重分配。如此海量的数据,将导致缓存服务不可用。 + + + +#### 一致性哈希算法 + +为了解决上述问题,一致性哈希算法孕育而出 + +![图1](https://i.loli.net/2019/02/18/5c6a7ecbcab93.png) + +首先将分配 $2^{32}$ 个槽,也就是从 0 到 $2^{32}-1$ ,如上图,虚拟出环形。通过哈希算法 `hash(ip, port)` 计算出Redis实例对应的哈希值,然后对 $2^{32}$ 取模,得到映射到环上的值。对于请求来说,先计算出key的哈希值,同样对 $2^{32}$ 取模,得到映射到环上的值。沿着顺时针匹配第一个Redis服务节点。 + +当添加Redis节点时,也是通过哈希算法 `hash(ip, port)` 计算出Redis实例对应的哈希值,然后对 $2^{32}​$ 取模,得到映射到环上的值。假设如下图的X节点: + +![图2](https://i.loli.net/2019/02/18/5c6a855193525.png) + +在数据重分配上,仅仅需要将 B节点 和 C节点 之间的数据再哈希、重分配就可以了,B-X之间的数据映射到X节点,X-C之间的数据映射到C节点。这样就避免将全量的数据再哈希、重分配节点。 + + + +##### 数据倾斜 + +![图3](https://i.loli.net/2019/02/18/5c6a872f8a386.png) + +如果节点较少,就会造成节点分布不均衡(数据倾斜)的问题,如上图,B-A之间映射的请求远多于A-B之间。就会造成A节点压力增加,而B节点负载较轻。 + + + +#### 虚拟节点 + +![图4](https://i.loli.net/2019/02/18/5c6a8b15e7d81.png) + +为了解决上述的数据倾斜问题,在一致性哈希算法的基础上引入虚拟节点。其思想是,预先分配N个虚拟节点,然后这些虚拟节点再在映射实际节点,能保证虚拟节点映射的数据,不会过于倾斜。图4中,虚拟节点A1和A2对应的服务器为A、虚拟节点B1和B2对应的服务器为B,以此类推。假如A服务器宕机后,虚拟节点B1和B2则无法映射实际服务器,那么可以将数据按照瞬时间就近映射。D2-B1之间的数据映射到节点C1,实际映射的服务器是C;A1-B2之间的数据映射到节点A2,实际映射的服务器是A,这样就避免数据过度倾斜。 + From c9f5b187674279b39c6aed5c16d932a442c9a42f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Mon, 18 Feb 2019 18:58:39 +0800 Subject: [PATCH 28/39] =?UTF-8?q?=E4=B8=80=E8=87=B4=E6=80=A7=E5=93=88?= =?UTF-8?q?=E5=B8=8C=E7=AE=97=E6=B3=95=E4=B8=AD=E5=B0=86=E6=95=B0=E5=AD=A6?= =?UTF-8?q?=E8=A1=A8=E8=BE=BE=E5=BC=8F=E6=8D=A2=E6=88=90GIF=E5=9B=BE?= =?UTF-8?q?=E7=89=87=EF=BC=9B=E5=B9=B6=E8=87=B3=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E7=9B=AE=E5=BD=95=E4=B8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- .../markdown/{cache => distribution}/ConsistentHashing.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename resource/markdown/{cache => distribution}/ConsistentHashing.md (80%) diff --git a/README.md b/README.md index 3522acd..7bb5a76 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ * [分布式系统 (第 05 篇) 精讲:TCC事务补偿机制(柔性事务方案)](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/TryConfirmCancel.md) * 分布式系统 (第 06 篇) 精讲:Paxos算法(强一致性算法) * 分布式系统 (第 07 篇) 精讲:Chubby 与 Zookeeper -* 分布式系统 (第 08 篇) 精讲:一致性哈希算法 +* [分布式系统 (第 08 篇) 精讲:一致性哈希算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/ConsistentHashing.md) * 分布式系统 (第 09 篇) 精讲:设计分布式锁 * 分布式系统 (第 10 篇) 精讲:分布式session/token一致性设计 * 分布式系统 (第 11 篇) 精讲:分布式事务 diff --git a/resource/markdown/cache/ConsistentHashing.md b/resource/markdown/distribution/ConsistentHashing.md similarity index 80% rename from resource/markdown/cache/ConsistentHashing.md rename to resource/markdown/distribution/ConsistentHashing.md index b1b99ee..c8b3f56 100644 --- a/resource/markdown/cache/ConsistentHashing.md +++ b/resource/markdown/distribution/ConsistentHashing.md @@ -20,9 +20,9 @@ Teradata在1986年发布的分布式数据库中使用了这种技术,但他 ![图1](https://i.loli.net/2019/02/18/5c6a7ecbcab93.png) -首先将分配 $2^{32}$ 个槽,也就是从 0 到 $2^{32}-1$ ,如上图,虚拟出环形。通过哈希算法 `hash(ip, port)` 计算出Redis实例对应的哈希值,然后对 $2^{32}$ 取模,得到映射到环上的值。对于请求来说,先计算出key的哈希值,同样对 $2^{32}$ 取模,得到映射到环上的值。沿着顺时针匹配第一个Redis服务节点。 +首先将分配 ![2^32](https://i.loli.net/2019/02/18/5c6a8e51599c7.gif) 个槽,也就是从 0 到 ![CodeCogsEqn (1).gif](https://i.loli.net/2019/02/18/5c6a8ed1b3329.gif) ,如上图,虚拟出环形。通过哈希算法 `hash(ip, port)` 计算出Redis实例对应的哈希值,然后对 ![2^32](https://i.loli.net/2019/02/18/5c6a8e51599c7.gif)取模,得到映射到环上的值。对于请求来说,先计算出key的哈希值,同样对 ![2^32](https://i.loli.net/2019/02/18/5c6a8e51599c7.gif)取模,得到映射到环上的值。沿着顺时针匹配第一个Redis服务节点。 -当添加Redis节点时,也是通过哈希算法 `hash(ip, port)` 计算出Redis实例对应的哈希值,然后对 $2^{32}​$ 取模,得到映射到环上的值。假设如下图的X节点: +当添加Redis节点时,也是通过哈希算法 `hash(ip, port)` 计算出Redis实例对应的哈希值,然后对 ![2^32](https://i.loli.net/2019/02/18/5c6a8e51599c7.gif) 取模,得到映射到环上的值。假设如下图的X节点: ![图2](https://i.loli.net/2019/02/18/5c6a855193525.png) From 6d9344a58a712098bfcb90a765de0d1401d0cc76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Tue, 19 Feb 2019 19:31:42 +0800 Subject: [PATCH 29/39] =?UTF-8?q?=E7=BC=93=E5=AD=98=E7=A9=BF=E9=80=8F?= =?UTF-8?q?=E4=B8=8E=E7=BC=93=E5=AD=98=E9=9B=AA=E5=B4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +-- resource/markdown/cache/CachePenetration.md | 56 +++++++++++ resource/markdown/cache/HotData.md | 2 - resource/markdown/cache/HotKey.md | 11 +++ .../markdown/distribution/DistributedLock.md | 92 +++++++++++++++++++ 5 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 resource/markdown/cache/CachePenetration.md delete mode 100644 resource/markdown/cache/HotData.md create mode 100644 resource/markdown/cache/HotKey.md diff --git a/README.md b/README.md index 7bb5a76..c19e883 100644 --- a/README.md +++ b/README.md @@ -118,12 +118,10 @@ - [Redis(第03篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) - [Redis(第04篇)核心:主从复制与故障转移](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) - Redis(第05篇)核心:高可用与 Sentinel 哨兵 -- Redis(第07篇)核心:分布式缓存与 Redis Cluster -- Redis(第08篇)核心:缓存倾斜与热点key -- Redis(第09篇)核心:缓存击穿(缓存穿透) -- Redis(第10篇)核心:缓存雪崩 -- Redis(第11篇)核心:布隆过滤 -- Redis(第12篇)核心:缓存降级 +- Redis(第06篇)核心:分布式缓存与 Redis Cluster +- [Redis(第07篇)核心:缓存击穿(缓存穿透)、缓存雪崩](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/CachePenetration.md) +- [Redis(第08篇)核心:热点key](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/HotKey.md) +- Redis(第09篇)核心:布隆过滤 ### 九、:telescope::tokyo_tower::satellite:分布式系统 @@ -135,7 +133,7 @@ * 分布式系统 (第 06 篇) 精讲:Paxos算法(强一致性算法) * 分布式系统 (第 07 篇) 精讲:Chubby 与 Zookeeper * [分布式系统 (第 08 篇) 精讲:一致性哈希算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/ConsistentHashing.md) -* 分布式系统 (第 09 篇) 精讲:设计分布式锁 +* [分布式系统 (第 09 篇) 精讲:设计分布式锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/DistributedLock.md) * 分布式系统 (第 10 篇) 精讲:分布式session/token一致性设计 * 分布式系统 (第 11 篇) 精讲:分布式事务 * 分布式系统 (第 12 篇) 精讲:分布式高并发的支持与控制 diff --git a/resource/markdown/cache/CachePenetration.md b/resource/markdown/cache/CachePenetration.md new file mode 100644 index 0000000..0e7b578 --- /dev/null +++ b/resource/markdown/cache/CachePenetration.md @@ -0,0 +1,56 @@ +

缓存击穿(缓存穿透)

+ +![缓存穿透](https://i.loli.net/2019/02/19/5c6b968d1e946.png) + +**缓存(Cache)** 是分布式、高并发的场景下,为了保护后端数据库,降低数据库的压力,引入的一种基于内存的数据访问机制,能加快数据的读取与写入,进而提高系统负载能力。 + +**缓存击穿(缓存穿透)** 是访问不存在缓存中的数据,进而直接访问数据库。 + + + +**缓存击穿** 和 **缓存穿透** 更为详细的分类: + +| 名称 | 缓存是否存在被访问数据 | 数据库是否存在被访问数据 | +| -------- | ---------------------- | ------------------------ | +| 缓存击穿 | 否 | 是 | +| | 否 | 否 | + +在缓存穿透这种情形下,如果缓存和数据库都没有需要被访问的数据,那么访问缓存时没有数据则直接返回空,避免重复访问数据库,造成不必要的后端数据库压力。 + + + +总之,避免缓存击穿也好,避免缓存穿透也罢,核心思路是想法设法不要让请求怼在数据库上。 + + + +##### 解决方案 + +**1.缓存空值:** 如果第一次访问一个不存在的数据,那么将此key与value为空值的数据缓存起来,下次再有对应的key访问时,缓存直接返回空值,通常要设置key的过期时间,再次访问时更新过期时间; + +**2.布隆过滤:** 类似散列集合(hash set),判断key是否在这个集合中。实现机制在于比特位,一个key对应一个比特位,并且存储一个标识,如果key有对应的比特位,并且标识位表示存在,则表示有对应的数据。比如使用Redis 的 Bitmap实现。 + + + +--- + +

缓存雪崩

+ +**缓存雪崩:** 大量的缓存击穿意味着大量的请求怼在数据库上,轻者造成数据库响应巨慢,严重者造成数据库宕机。比如缓存内的数据集体定时刷新、服务器重启等。 + + + +##### 解决方案 + +1. 降低缓存刷新频率; +2. 部分缓存刷新,刷新数据时按照一定规则分组刷新; +3. 设置key永远不过期,如果需要刷新数据,则定时刷新; +4. 分片缓存,在分布式缓存下,将需要缓存的数据 散列 分布到多个节点,尽量将热点数据均匀分布到多节点。 + + + + + + + + + diff --git a/resource/markdown/cache/HotData.md b/resource/markdown/cache/HotData.md deleted file mode 100644 index 1f660b5..0000000 --- a/resource/markdown/cache/HotData.md +++ /dev/null @@ -1,2 +0,0 @@ -

Redis热点数据与数据淘汰机制

- diff --git a/resource/markdown/cache/HotKey.md b/resource/markdown/cache/HotKey.md new file mode 100644 index 0000000..745e34f --- /dev/null +++ b/resource/markdown/cache/HotKey.md @@ -0,0 +1,11 @@ +

Redis热点Key

+ +在一定时间内,被频繁访问的key称为 **热点key** 。比如突发性新闻,微博上常见的热搜新闻,引起千千万万人短时间内浏览;在线商城大促活动,消费者比较关注的商品突然降价,引起成千上万消费者点击、购买。热点key所在服务器的压力骤然上升,如果超出物理机器的承载能力,则缓存不可用,进而诱发缓存击穿、缓存雪崩的问题。 + +![缓存击穿](https://i.loli.net/2019/02/19/5c6b968d1e946.png) + + + +#### 解决方案 + +**1. 读写分离:** 读写分离比较适用于写少读多的情景, \ No newline at end of file diff --git a/resource/markdown/distribution/DistributedLock.md b/resource/markdown/distribution/DistributedLock.md index 8b13789..2e1d994 100644 --- a/resource/markdown/distribution/DistributedLock.md +++ b/resource/markdown/distribution/DistributedLock.md @@ -1 +1,93 @@ +比如某个应用需要定时跑一些任务,为了保证高可用,做了三个节点的集群,后来发现集群中每个节点都跑了重复的任务。 + +

一、基于数据库实现分布式锁

+ +#### 1.1 悲观锁 + +首先创建一张表,用于存储锁标识的记录: + +```mysql +CREATE TABLE IF NOT EXISTS distribution_lock( + id BIGINT(20) UNSIGNED NOT NULL COMMENT '分布式锁id', + lock_name VARCHAR(50) DEFAULT '' COMMENT '分布式锁的名称', + node_number TINYINT(1) NOT NULL COMMENT '集群下节点的编号,用于重入锁', + gmt_create DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '行记录创建时间', + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='存储分布式锁的表'; +``` + + + +**获取锁:** + +```mysql +INSERT INTO distribution_lock(id, lock_name, node_number) VALUES(1001, 'order_task', 1); +``` + + + +**获取重入锁:** + +```mysql +SELECT + t0.id, + t0.lock_name, + t0.gmt_create +FROM distribution_lock t0 +WHERE t0.id = 1001 AND t0.node_number = 1; +``` + + + +**锁超时:** + +```mysql +SELECT + t0.id, + t0.lock_name, + t0.gmt_create , + TIMESTAMPDIFF(SECOND, t0.gmt_create, NOW()) 'duration_time' +FROM distribution_lock t0 +WHERE t0.id = 1001 AND t0.node_number != 2; +``` + + + +**释放锁:** + +```mysql +DELETE FROM distribution_lock WHERE id = 1001; +``` + + + +> 上面是用 `INSERT` + `SELECT ` + `DELETE` 来实现的,当然也可以使用 `UPDATE` + `SELECT` 实现(注意初始化锁标识的行记录)。 + + + +#### 1.2 乐观锁 + + + +--- + +

二、基于Redis实现分布式锁

+ +##### 悲观锁 + + + +##### 乐观锁 + + + +--- + +

三、基于Zookeeper实现分布式锁

+ +##### 悲观锁 + + + +##### 乐观锁 From c2238a7abd54e6ff4cc427cb57bed0a30cf7442a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Tue, 19 Feb 2019 23:54:40 +0800 Subject: [PATCH 30/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E5=88=86=E5=B8=83=E5=BC=8F=E9=94=81=E7=9A=84=E4=B8=8A?= =?UTF-8?q?=E5=8D=8A=E7=AF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 +++--- .../markdown/distribution/DistributedLock.md | 66 ++++++++++++++++--- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index c19e883..d2427ef 100644 --- a/README.md +++ b/README.md @@ -130,15 +130,17 @@ * [分布式系统 (第 03 篇) 精讲:X/Open DTP 与 XA 事务](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/XA.md) * [分布式系统 (第 04 篇) 精讲:2PC协议和3PC协议](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/2PCand3PC.md) * [分布式系统 (第 05 篇) 精讲:TCC事务补偿机制(柔性事务方案)](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/TryConfirmCancel.md) -* 分布式系统 (第 06 篇) 精讲:Paxos算法(强一致性算法) -* 分布式系统 (第 07 篇) 精讲:Chubby 与 Zookeeper -* [分布式系统 (第 08 篇) 精讲:一致性哈希算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/ConsistentHashing.md) -* [分布式系统 (第 09 篇) 精讲:设计分布式锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/DistributedLock.md) -* 分布式系统 (第 10 篇) 精讲:分布式session/token一致性设计 -* 分布式系统 (第 11 篇) 精讲:分布式事务 -* 分布式系统 (第 12 篇) 精讲:分布式高并发的支持与控制 -* 分布式系统 (第 13 篇) 精讲:分布式下悲观锁和乐观锁的实现 -* 分布式系统 (第 14 篇) 精讲:分布式支付框架的实现 +* 分布式系统 (第 06 篇) 精讲:Paxos 协议(强一致性协议) +* 分布式系统 (第 07 篇) 精讲:ZAB 协议 +* 分布式系统 (第 08 篇) 精讲:Raft 协议 +* 分布式系统 (第 09 篇) 精讲:Chubby 与 Zookeeper +* [分布式系统 (第 10 篇) 精讲:一致性哈希算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/ConsistentHashing.md) +* [分布式系统 (第 11 篇) 精讲:设计分布式锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/DistributedLock.md) +* 分布式系统 (第 12 篇) 精讲:分布式session/token一致性设计 +* 分布式系统 (第 13 篇) 精讲:分布式事务 +* 分布式系统 (第 14 篇) 精讲:分布式高并发的支持与控制 +* 分布式系统 (第 15 篇) 精讲:分布式下悲观锁和乐观锁的实现 +* 分布式系统 (第 16 篇) 精讲:分布式支付框架的实现 ### 十、:microscope::microscope::microscope:微服务 diff --git a/resource/markdown/distribution/DistributedLock.md b/resource/markdown/distribution/DistributedLock.md index 2e1d994..9fad653 100644 --- a/resource/markdown/distribution/DistributedLock.md +++ b/resource/markdown/distribution/DistributedLock.md @@ -2,8 +2,6 @@

一、基于数据库实现分布式锁

-#### 1.1 悲观锁 - 首先创建一张表,用于存储锁标识的记录: ```mysql @@ -24,6 +22,8 @@ CREATE TABLE IF NOT EXISTS distribution_lock( INSERT INTO distribution_lock(id, lock_name, node_number) VALUES(1001, 'order_task', 1); ``` +插入成功表示获取锁,否是未获取锁; + **获取重入锁:** @@ -39,7 +39,7 @@ WHERE t0.id = 1001 AND t0.node_number = 1; -**锁超时:** +**检查锁超时:** ```mysql SELECT @@ -63,9 +63,17 @@ DELETE FROM distribution_lock WHERE id = 1001; > 上面是用 `INSERT` + `SELECT ` + `DELETE` 来实现的,当然也可以使用 `UPDATE` + `SELECT` 实现(注意初始化锁标识的行记录)。 +因为 `id` 设置为 主键,所以具有唯一约束属性,同一时刻只能被一个线程获取锁🔐。 + + +**注意问题:** -#### 1.2 乐观锁 +1. 单点故障: 数据库存在单点故障的风险,建议做成高可用版本; +2. 重入性: 也就是重入锁,可以精确到线程级别; +3. 锁失效机制: 如果一台服务获得锁后,再没有主动释放锁的情况下,其他服务也就无法获取锁。解决方法是获取锁时,判断锁是否超时; +4. 非阻塞性: 比如一个定时任务每月跑一次,恰巧此时被其他节点服务占用锁,导致该节点获取锁失败,解决方法是循环多次获取锁; +5. 性能: 基于数据库的性能比较低,不适合高并发场景。 @@ -73,11 +81,51 @@ DELETE FROM distribution_lock WHERE id = 1001;

二、基于Redis实现分布式锁

-##### 悲观锁 +**获取锁** + +```shell +127.0.0.1:6379> SETNX lock:1001 192.168.2.104 +``` + +巧用 `SETNX` 命令,其中 `value` 设置唯一标识,比如 `ip+线程id+时间戳` 、 `UUID`诸如此类,如果返回 `1` 表示获取锁成功,否则获取锁失败; + +**设置锁超时时间** + +```shell +127.0.0.1:6379> expire lock:1001 30 +``` + +如果返回 `1` 表示设置锁超时成功,否则失败; + + + +**锁过期检查** + +```shell +127.0.0.1:6379> GET lock:1001 +``` + +如果获得的 `value` 和之前设置的 `value` 说明锁有效,提交事务;否则锁过期,业务回滚; + + + +**释放锁** + +```shell +127.0.0.1:6379> DEL lock:1001 +``` + + + +注意: 防止误删 `key` ,使用以下伪代码演示: + +```java + +``` + -##### 乐观锁 @@ -85,9 +133,11 @@ DELETE FROM distribution_lock WHERE id = 1001;

三、基于Zookeeper实现分布式锁

-##### 悲观锁 +**获取锁** + + -##### 乐观锁 +**释放锁** From baccefc0a72a0574a0b9f0ccc7862364ba07e5e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 20 Feb 2019 17:53:23 +0800 Subject: [PATCH 31/39] =?UTF-8?q?1.=E5=AE=8C=E5=96=84=E4=BA=86=E5=9F=BA?= =?UTF-8?q?=E4=BA=8ERedis=E5=AE=9E=E7=8E=B0=E5=88=86=E5=B8=83=E5=BC=8F?= =?UTF-8?q?=E9=94=81=EF=BC=9B2.review=E7=9A=84=E9=83=A8=E5=88=86=E9=9B=86?= =?UTF-8?q?=E5=90=88=E6=BA=90=E7=A0=81=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 +++---- resource/markdown/collection/ArrayList.md | 13 +++- .../collection/HashConflictsAndResolve.md | 2 - resource/markdown/collection/HashMap1.7v.md | 2 +- resource/markdown/collection/LinkedList.md | 2 +- resource/markdown/collection/Map.Entry1.7v.md | 4 +- .../markdown/distribution/DistributedLock.md | 71 +++++++++++++++---- .../markdown/micro-service/MicroService.md | 4 -- .../microservice/MicroserviceAndSOA.md | 38 ++++++++++ 9 files changed, 125 insertions(+), 38 deletions(-) delete mode 100644 resource/markdown/micro-service/MicroService.md create mode 100644 resource/markdown/microservice/MicroserviceAndSOA.md diff --git a/README.md b/README.md index d2427ef..fe26fcb 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ### 一、:bullettrain_side::railway_car::railway_car::railway_car:集合框架源码分析 * [集合框架 (第 01 篇) 源码分析:Collection 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaCollections.md) * [集合框架 (第 02 篇) 源码分析:Map 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaMaps.md) -* [集合框架 (第 03 篇) 源码分析:ArrayList](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/ArrayList.md) +* [集合框架 (第 03 篇) 源码分析:ArrayList](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/ArrayList.md) * [集合框架 (第 04 篇) 源码分析:LinkedList](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/LinkedList.md) * [集合框架 (第 05 篇) 源码分析:Map接口与其内部接口Entry](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/Map.Entry1.7v.md) * [集合框架 (第 06 篇) 源码分析:哈希冲突(哈希碰撞)与解决算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/HashConflictsAndResolve.md) @@ -138,26 +138,29 @@ * [分布式系统 (第 11 篇) 精讲:设计分布式锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/DistributedLock.md) * 分布式系统 (第 12 篇) 精讲:分布式session/token一致性设计 * 分布式系统 (第 13 篇) 精讲:分布式事务 -* 分布式系统 (第 14 篇) 精讲:分布式高并发的支持与控制 -* 分布式系统 (第 15 篇) 精讲:分布式下悲观锁和乐观锁的实现 +* 分布式系统 (第 14 篇) 精讲:分布式任务调度 +* 分布式系统 (第 15 篇) 精讲:分布式高并发的支持与控制 * 分布式系统 (第 16 篇) 精讲:分布式支付框架的实现 ### 十、:microscope::microscope::microscope:微服务 -* 微服务 (第 01 篇) 精讲:什么是微服务? -* 微服务 (第 01 篇) 精讲:服务建模 -* 微服务 (第 01 篇) 精讲:服务拆分 -* 微服务 (第 01 篇) 精讲:微服务的核心 -- 服务治理(服务注册与发现中心) -* 微服务 (第 01 篇) 精讲:网关Gateway -* 微服务 (第 01 篇) 精讲:路由Routing -* 微服务 (第 01 篇) 精讲:负载均衡算法 -* 微服务 (第 01 篇) 精讲:服务熔断、服务隔离、服务限流、服务降级、快速失败 -* 微服务 (第 01 篇) 精讲:SpringCloud +* 微服务 (第 01 篇) 精讲:微服务与SOA +* 微服务 (第 02 篇) 精讲:服务建模 +* 微服务 (第 03 篇) 精讲:服务拆分 +* 微服务 (第 04 篇) 精讲:微服务的核心 -- 服务治理(服务注册与发现中心) +* 微服务 (第 05 篇) 精讲:服务网关Gateway +* 微服务 (第 05 篇) 精讲:配置中心 +* 微服务 (第 06 篇) 精讲:路由Routing +* 微服务 (第 07 篇) 精讲:负载均衡算法 +* 微服务 (第 08 篇) 精讲:服务限流、服务隔离、服务熔断、服务降级、快速失败 +* 微服务 (第 09 篇) 精讲:容器化 +* 微服务 (第 10 篇) 精讲:SpringCloud ### 十一、:bicyclist::horse_racing::snowboarder:高并发与高可用 * 高并发与高可用 (第 01 篇) 精讲:全局id生成算法 * 高并发与高可用 (第 01 篇) 精讲:如何实现请求幂等性 +* 高并发与高可用 (第 01 篇) 精讲:常见的负载均衡算法 * 高并发与高可用 (第 01 篇) 精讲:如何防止网络抖动产生的重复建单 * 高并发与高可用 (第 01 篇) 精讲:如何度过服务器重启后的连接风暴 * 高并发与高可用 (第 01 篇) 精讲:数据持久化 + ACK + 补偿重试+请求幂等性+防重设计 diff --git a/resource/markdown/collection/ArrayList.md b/resource/markdown/collection/ArrayList.md index 47c7f00..6e46734 100644 --- a/resource/markdown/collection/ArrayList.md +++ b/resource/markdown/collection/ArrayList.md @@ -7,7 +7,7 @@ ***

一、ArrayList

-> ArrayList是动态数组,其动态性体现在能够`动态扩容、缩容`。(其原理是构建一个新Object数组,将原数组复制进去。借助 `Arrays.copyOf(T[] original, int newLength))` 。 +> ArrayList是动态数组,其动态性体现在能够`动态扩容` 、`动态缩容`。(其原理是构建一个新Object数组,将原数组复制进去。借助 `Arrays.copyOf(T[] original, int newLength))` 。 > ![ArrayList继承关系](https://i.loli.net/2018/12/09/5c0cbffb79b69.png) @@ -73,6 +73,7 @@ private void grow(int minCapacity) { #### 1.2、在指定位子添加方法 public void `add(int index, E element)` ```java +// 注意:在指定 index 位置添加元素,并不会覆盖该处的元素,而是 index位置及其之后的元素后移 public void add(int index, E element) { // 只要有index,必定会检查range rangeCheckForAdd(index); @@ -127,7 +128,17 @@ private void fastRemove(int index) { } ``` +:bomb::bomb::bomb:注意: + +1. 如果存在多个对象o,仅仅删除距离 index = 0 处最近的一个元素; +2. 元素被删除后,该位置不会空出来,后面的元素会前移。 + +所以 `ArrayList` 适合读多删少场景。 + + + #### 3、重新设置指定index位置的元素值 public E `set(int index, E element)` + ```java public E set(int index, E element) { // 有index,必检查 diff --git a/resource/markdown/collection/HashConflictsAndResolve.md b/resource/markdown/collection/HashConflictsAndResolve.md index 71dc68c..1a79759 100644 --- a/resource/markdown/collection/HashConflictsAndResolve.md +++ b/resource/markdown/collection/HashConflictsAndResolve.md @@ -40,8 +40,6 @@ #### 2.3、:anchor:链地址法(链表法):heavy_check_mark: -![链表法](http://pgq1yfr0p.bkt.clouddn.com/image/java/collection/HashBucket.png) - >当多个元素产生哈希冲突时,它们的哈希码是一定相同,落脚的哈希槽也相同。将它们通过地址引用一一相连,也就是形成 **链表** 的形式。这样它们既可以存储,又不会占用其他哈希槽的位置。这种通过**链表**形式解决**哈希冲突**的算法称为**链表法**。由不同元素、相同哈希码经过组织形成的数据结构称为 **哈希桶**Bucket,或者说这个容器是 **哈希桶**。**哈希槽**的位置也就是**哈希桶号** 。 #### 2.4、建立公共溢出区 diff --git a/resource/markdown/collection/HashMap1.7v.md b/resource/markdown/collection/HashMap1.7v.md index f75d496..51b0779 100644 --- a/resource/markdown/collection/HashMap1.7v.md +++ b/resource/markdown/collection/HashMap1.7v.md @@ -2,7 +2,7 @@

一、继承关系

-![HashMap1.7vExtend](http://pgq1yfr0p.bkt.clouddn.com/image/java/collectionHashMap1.7vExtend.png) +![HashMap1.7vExtend](https://i.loli.net/2019/02/20/5c6d2069aef34.png)

二、数据结构

diff --git a/resource/markdown/collection/LinkedList.md b/resource/markdown/collection/LinkedList.md index c5ed05a..0152018 100644 --- a/resource/markdown/collection/LinkedList.md +++ b/resource/markdown/collection/LinkedList.md @@ -88,7 +88,7 @@ void linkLast(E e) { } ``` -添加元素到头部,操作于此类似。 +添加元素到头部,操作与此类似。 :star::star::star::star::star:

重点:添加元素到指定index位置

diff --git a/resource/markdown/collection/Map.Entry1.7v.md b/resource/markdown/collection/Map.Entry1.7v.md index 1c7f928..2294d9f 100644 --- a/resource/markdown/collection/Map.Entry1.7v.md +++ b/resource/markdown/collection/Map.Entry1.7v.md @@ -2,7 +2,7 @@

一、Map接口

-> **Map接口**是整个 **Key-Value** 存储容器的核心。它 **抽**(qú)(xíng)**象** 定义了 **Key-Value** 结构的容器框架。**Map**不会像 **数组** 中元素那样可以单独存放,到像似 `LinkedList` 中的节点自定义,那么特殊的节点一定需要特定的来描述。在 `jdk1.7` 及其之前的版本,Map的作者将其节点描述为 **Entry** ,Entry不是 ~~入口~~、~~大门~~,它在这里称为 **项目** 或 **条目** ,下文简称为 **项**。**Map** 中的每个节点称为 `一项`。 +> **Map接口**是整个 **Key-Value** 存储容器的核心。它 **抽**(qǔ)(xíng)**象** 定义了 **Key-Value** 结构的容器框架。**Map**不会像 **数组** 中元素那样可以单独存放,到像似 `LinkedList` 中的节点自定义,那么特殊的节点一定需要特殊的定义来描述。在 `jdk1.7` 及其之前的版本,Map的作者将其节点描述为 **Entry** ,Entry不是 ~~入口~~、~~大门~~,它在这里称为 **项目** 或 **条目** ,下文简称为 **项**。**Map** 中的每个节点称为 `一项`。

二、Map.Entry

@@ -27,7 +27,7 @@ int hashCode()

三、Map中的方法

-> **方法** 意味着 行为。下面Map的方法意味着整个Map框架具有最基本的什么样的 **功能**。明白这些方法,就掌握了Map框架核心操作功能。 +> **方法** 意味着 行为。下面Map的方法意味着整个Map框架具有最基本的、什么样的 **功能**。明白这些方法,就掌握了Map框架核心操作功能。 ```java // 返回 map 中键值对的数量 diff --git a/resource/markdown/distribution/DistributedLock.md b/resource/markdown/distribution/DistributedLock.md index 9fad653..0c94258 100644 --- a/resource/markdown/distribution/DistributedLock.md +++ b/resource/markdown/distribution/DistributedLock.md @@ -56,7 +56,7 @@ WHERE t0.id = 1001 AND t0.node_number != 2; **释放锁:** ```mysql -DELETE FROM distribution_lock WHERE id = 1001; +DELETE FROM distribution_lock WHERE id = 1001 AND t0.node_number = 1; ``` @@ -84,60 +84,101 @@ DELETE FROM distribution_lock WHERE id = 1001; **获取锁** ```shell -127.0.0.1:6379> SETNX lock:1001 192.168.2.104 +127.0.0.1:6379> SETNX lock:1001 uuid ``` -巧用 `SETNX` 命令,其中 `value` 设置唯一标识,比如 `ip+线程id+时间戳` 、 `UUID`诸如此类,如果返回 `1` 表示获取锁成功,否则获取锁失败; +巧用 `SETNX` 命令,其中 `value` 为唯一标识,比如 `ip+线程id+时间戳` 、 `UUID`诸如此类,如果返回 `1` 表示获取锁成功,否则获取锁失败; **设置锁超时时间** ```shell -127.0.0.1:6379> expire lock:1001 30 +127.0.0.1:6379> expire lock:1001 5 ``` 如果返回 `1` 表示设置锁超时成功,否则失败; -**锁过期检查** +##### 获取将直接使用以下命令代替上面两条命令: ```shell -127.0.0.1:6379> GET lock:1001 +127.0.0.1:6379> SET lock:1001 uuid EX 5 NX ``` -如果获得的 `value` 和之前设置的 `value` 说明锁有效,提交事务;否则锁过期,业务回滚; - -**释放锁** +**锁过期检查** ```shell -127.0.0.1:6379> DEL lock:1001 +127.0.0.1:6379> GET lock:1001 ``` +如果获得的 `value` 和之前设置的 `value` 一致,说明锁有效,业务提交;否则锁过期,业务回滚; -注意: 防止误删 `key` ,使用以下伪代码演示: -```java +**释放锁** Redis的事务不像MySQL的事务那样强大,所以释放锁时要防止误删 `key` ,使用Lua脚本来操作: +```java +/** + * 释放分布式锁 + * + * @param key key + * @param value value + * @return 主动成功释放锁返回 true,否则返回false + */ +public boolean release(String key, String value) { + // Null check + Assert.notNull(key, "The key is not allowed to be null"); + Assert.notNull(value, "The value is not allowed to be null"); + + // result 表示是否删除指定的 key,1表示是,0表示否 + long result = 0; + + try { + String lua = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then \n" + + " return redis.call(\"del\",KEYS[1])\n" + + " else \n" + + " return 0\n" + + " end"; + + result = (Long) jedis.evalsha(jedis.scriptLoad(lua), + Collections.singletonList(key), + Collections.singletonList(value)); + + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (jedis != null) { + try { + jedis.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + return result == 1; +} ``` - - ---

三、基于Zookeeper实现分布式锁

**获取锁** - +// TODO **释放锁** + + +// TODO + diff --git a/resource/markdown/micro-service/MicroService.md b/resource/markdown/micro-service/MicroService.md deleted file mode 100644 index ff8dcb2..0000000 --- a/resource/markdown/micro-service/MicroService.md +++ /dev/null @@ -1,4 +0,0 @@ -![微服务]() - -*微服务* 是一种架构风格,不一定是单机支撑不了整个应用,而更多强调的是业务上的拆分。 - diff --git a/resource/markdown/microservice/MicroserviceAndSOA.md b/resource/markdown/microservice/MicroserviceAndSOA.md new file mode 100644 index 0000000..7421c30 --- /dev/null +++ b/resource/markdown/microservice/MicroserviceAndSOA.md @@ -0,0 +1,38 @@ +

微服务

+ +![微服务]() + +*微服务* 是一种架构风格,全称是 **微服务架构** ,英文名 *Microservice Architecture* + + + +不一定是单机支撑不了整个应用,而更多强调的是业务上的拆分。 + + + + + +

SOA

+ +**SOA** 是 *Service-oriented architecture* 简写,中文名称 **面向服务的架构** ,一种软件设计风格,通过网络通信协议,应用程序组件向其他组件提供服务。面向服务的体系结构的基本原则是独立于供应商、产品和技术的。服务是一个独立的功能单元,可以远程访问、独立操作和更新,例如在线检索信用卡对账单。 + + + +根据SOA的许多定义之一,服务有四个属性: + +1. 它在逻辑上表示具有指定结果的业务活动; +2. 它是独立的; +3. 对于消费者来说,这是一个黑盒; +4. 它可能由其他基础服务组成。 + +可以结合使用不同的服务来提供大型软件应用程序的功能,[5]SOA与模块化编程共享的原则。面向服务的体系结构集成了分布式、单独维护和部署的软件组件。它是由技术和标准支持的,这些技术和标准促进了组件在网络上的通信和合作,特别是在IP网络上。 + + + +在SOA中,服务使用协议来描述如何使用描述元数据传递和解析消息。此元数据描述了服务的功能特性和服务质量特性。面向服务的体系结构旨在允许用户组合大量功能以形成纯粹从现有服务构建的应用程序,并以特殊方式组合它们。服务向请求者提供了一个简单的接口,它抽象出充当黑盒的底层复杂性。其他用户也可以在不了解其内部实现的情况下访问这些独立的服务。 + + + +参考资料: + +[Wikipedia-Service-oriented architecture](https://en.wikipedia.org/wiki/Service-oriented_architecture) \ No newline at end of file From 3f8949a9db573374a8500cc1113cf4c005a59f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 20 Feb 2019 23:08:48 +0800 Subject: [PATCH 32/39] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86MyBatis?= =?UTF-8?q?=E7=9A=84=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++++++++------ .../design-pattern/design-principles.md | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 resource/markdown/design-pattern/design-principles.md diff --git a/README.md b/README.md index fe26fcb..a662752 100644 --- a/README.md +++ b/README.md @@ -95,13 +95,19 @@ ### 六、:bird::bird::bird:MyBatis3 源码分析 * MyBatis3 (第 01 篇)源码分析:SqlSession * MyBatis3 (第 02 篇)源码分析:SqlSessionFactory -* MyBatis3 (第 03 篇)源码分析:SqlSessionManager -* MyBatis3 (第 04 篇)源码分析:DefaultSqlSessionFactory -* MyBatis3 (第 05 篇)源码分析:SqlSessionFactoryBuilder -* MyBatis3 (第 06 篇)源码分析:XmlConfigBuilder -* MyBatis3 (第 07 篇)源码分析:StatementHandler -* MyBatis3 (第 08 篇)源码分析:ResultSetHandler -* MyBatis3 (第 09 篇)源码分析:一级缓存和二级缓存 +* MyBatis3 (第 03 篇)源码分析:SqlSessionFactoryBuilder +* MyBatis3 (第 04 篇)源码分析:SqlSessionManager +* MyBatis3 (第 05 篇)源码分析:DefaultSqlSession +* MyBatis3 (第 06 篇)源码分析:DefaultSqlSessionFactory +* MyBatis3 (第 07 篇)源码分析:Configuration +* MyBatis3 (第 08 篇)源码分析:XmlConfigBuilder +* MyBatis3 (第 08 篇)源码分析:Executor +* MyBatis3 (第 09 篇)源码分析:StatementHandler +* MyBatis3 (第 10 篇)源码分析:ParameterHandler +* MyBatis3 (第 11 篇)源码分析:ResultSetHandler +* MyBatis3 (第 12 篇)源码分析:Reflector +* MyBatis3 (第 13 篇)源码分析:ReflectorFactory 与 DefaultReflectorFactory +* MyBatis3 (第 14 篇)源码分析:一级缓存和二级缓存 ### 七、:closed_book::green_book::blue_book::notebook_with_decorative_cover::books:高性能 MySQL diff --git a/resource/markdown/design-pattern/design-principles.md b/resource/markdown/design-pattern/design-principles.md new file mode 100644 index 0000000..8cba596 --- /dev/null +++ b/resource/markdown/design-pattern/design-principles.md @@ -0,0 +1,23 @@ +

6大设计原则

+ +1.单一职能原则 + + + +2.开闭原则 + + + +3.里氏置换原则 + + + +4.依赖倒置原则 + + + +5.接口隔离原则 + + + +6.迪米特法则(最少知道原则) \ No newline at end of file From c0815dc5bc43ed2b0b4be30219a4f3fa4b2252d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Thu, 21 Feb 2019 19:23:17 +0800 Subject: [PATCH 33/39] review --- .../markdown/collection/HashMapHashtableConcurrentHashMap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resource/markdown/collection/HashMapHashtableConcurrentHashMap.md b/resource/markdown/collection/HashMapHashtableConcurrentHashMap.md index 11e467b..9ce41f6 100644 --- a/resource/markdown/collection/HashMapHashtableConcurrentHashMap.md +++ b/resource/markdown/collection/HashMapHashtableConcurrentHashMap.md @@ -11,7 +11,7 @@ -

一、线程安全的Hashtable

+

二、线程安全的Hashtable

> `Hashtable` 是`HashMap` 线程安全的实现。它也起始于 **上古时期**,可追溯到` jdk1.0`。(:no_good:注意是 `Hashtable` 而非 ~~HashTable~~) @@ -35,7 +35,7 @@ public class Hashtable :suspension_railway::suspension_railway::suspension_railway: -

一、线程安全的ConcurrentHashMap

+

三、线程安全的ConcurrentHashMap

> 面对着 `Hashtable` 粗暴的大锁:lock:,`ConcurrentHashMap` 采用 **分段锁技术**,将一个大的Map分割成n个小的 **段segment**,对每段进行加锁,降低了容器加锁的粒子度,每段(segment)各自加锁,互不影响。分段锁使用的锁是 `ReentrantLock` 可重入锁。 > From 6881e52a4a2696385e9c347029b2cc8a621425d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Fri, 22 Feb 2019 17:36:23 +0800 Subject: [PATCH 34/39] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E7=83=AD=E7=82=B9key=E7=9A=84=E6=96=87=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 34 ++++++++++++++++++++++--------- resource/markdown/cache/HotKey.md | 32 ++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index a662752..c9d1107 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ * 集合框架 (第 17 篇) 源码分析:CopyOnWriteArrayList 与 CopyOnWriteArraySet + ### 二、:radio::radio::radio:JVM(Java虚拟机) * [Java虚拟机 (第 01 篇) 深入理解:JVM内存区域(运行时数据区)](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/jvm/RuntimeDataAreas.md) * [Java虚拟机 (第 03 篇) 深入理解:垃圾收集算法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/jvm/GarbageCollectionAlgorithm.md) @@ -34,6 +35,7 @@ * Java虚拟机 (第 08 篇) 深入理解:打破双亲委派模型 + ### 三、:closed_lock_with_key:Java多线程与并发框架:unlock: * [Java多线程与并发框 (第 01 篇) 深入理解:线程的状态](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/ThreadStatus.md) * [Java多线程与并发框 (第 02 篇) 深入理解:线程和进程的区别](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/ThreadAndProcess.md) @@ -52,6 +54,7 @@ * Java多线程与并发框 (第 15 篇) 深入理解:ThreadLocal + ### 四、:satellite::satellite::satellite:网络协议:globe_with_meridians::globe_with_meridians::globe_with_meridians: * [网络协议 (第 01 篇) 精讲:ISO的OSI七层参考模型与TCP/IP四层参考模型](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/NetworkModel.md) * [网络协议 (第 02 篇) 精讲:HTTP/1.1、HTTP/2.0、HTTPS的区别](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/HTTP1.1HTTP2.0HTTPS.md) @@ -65,6 +68,7 @@ * [网络协议 (第 10 篇) 精讲:常见HTTP状态码及含义](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/networking/StateCode.md) + ### 五、:leaves::four_leaf_clover::fountain:Spring 5.x 核心篇 * [Spring 5.x (第 01 篇) 核心篇:反射机制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/spring/Reflection.md) * [Spring 5.x (第 02 篇) 核心篇:静态代理、jdk动态代理与CGLib](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/spring/DynamicProxy.md) @@ -92,6 +96,7 @@ * [Spring 5.x (第 24 篇) 核心篇:SpringBoot 2.x 启动过程](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/spring/SpringBootStart-upProcedure.md) + ### 六、:bird::bird::bird:MyBatis3 源码分析 * MyBatis3 (第 01 篇)源码分析:SqlSession * MyBatis3 (第 02 篇)源码分析:SqlSessionFactory @@ -110,6 +115,7 @@ * MyBatis3 (第 14 篇)源码分析:一级缓存和二级缓存 + ### 七、:closed_book::green_book::blue_book::notebook_with_decorative_cover::books:高性能 MySQL * [数据库 (第 01 篇)精讲:数据库的三大范式与五大约束](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/NormalformAndConstraint.md) * [数据库 (第 02 篇)精讲:事务的ACID四大特性与四种隔离级别](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/ACIDAndIsolationLevel.md) @@ -118,19 +124,23 @@ * [数据库 (第 05 篇)精讲: SQL优化](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/SQLOptimization.md) * [数据库 (第 06 篇)精讲: 数据库拆分](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/database/DBSplit.md) + + ### 八、:sunny::umbrella::zap:Redis 核心应用 -- Redis(第01篇)核心:数据结构与底层数据结构 -- [Redis(第02篇)核心:Redis单线程模型为啥这么快?](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/SingleThreadModel.md) -- [Redis(第03篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) -- [Redis(第04篇)核心:主从复制与故障转移](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) -- Redis(第05篇)核心:高可用与 Sentinel 哨兵 -- Redis(第06篇)核心:分布式缓存与 Redis Cluster -- [Redis(第07篇)核心:缓存击穿(缓存穿透)、缓存雪崩](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/CachePenetration.md) -- [Redis(第08篇)核心:热点key](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/HotKey.md) -- Redis(第09篇)核心:布隆过滤 +- Redis(第 01 篇)核心:数据结构与底层数据结构 +- [Redis(第 02 篇)核心:Redis单线程模型为啥这么快?](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/SingleThreadModel.md) +- [Redis(第 03 篇)核心:持久化之RDB与AOF](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Persistence.md) +- [Redis(第 04 篇)核心:主从复制与故障转移](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/Replication.md) +- Redis(第 05 篇)核心:高可用与 Sentinel 哨兵 +- Redis(第 06 篇)核心:分布式缓存与 Redis Cluster +- [Redis(第 07 篇)核心:缓存击穿(缓存穿透)、缓存雪崩](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/CachePenetration.md) +- [Redis(第 08 篇)核心:热点key](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/cache/HotKey.md) +- Redis(第 09 篇)核心:布隆过滤 +- Redis(第 10 篇)核心:缓存数据一致性 + -### 九、:telescope::tokyo_tower::satellite:分布式系统 +### 九、:telescope::tokyo_tower::satellite:分布式系统 * [分布式系统 (第 01 篇) 精讲:集群与分布式](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/WhatisDistributed.md) * [分布式系统 (第 02 篇) 精讲:CAP定理与BASE理论](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/CAPandBASE.md) * [分布式系统 (第 03 篇) 精讲:X/Open DTP 与 XA 事务](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/distribution/XA.md) @@ -149,6 +159,7 @@ * 分布式系统 (第 16 篇) 精讲:分布式支付框架的实现 + ### 十、:microscope::microscope::microscope:微服务 * 微服务 (第 01 篇) 精讲:微服务与SOA * 微服务 (第 02 篇) 精讲:服务建模 @@ -163,6 +174,7 @@ * 微服务 (第 10 篇) 精讲:SpringCloud + ### 十一、:bicyclist::horse_racing::snowboarder:高并发与高可用 * 高并发与高可用 (第 01 篇) 精讲:全局id生成算法 * 高并发与高可用 (第 01 篇) 精讲:如何实现请求幂等性 @@ -172,6 +184,8 @@ * 高并发与高可用 (第 01 篇) 精讲:数据持久化 + ACK + 补偿重试+请求幂等性+防重设计 * 高并发与高可用 (第 01 篇) 精讲: 抢红包系统实现(高并发系统三把利刃:缓存、限流、降级) + + ### :soon:未完,待续 ... ### 联系作者:flags:: diff --git a/resource/markdown/cache/HotKey.md b/resource/markdown/cache/HotKey.md index 745e34f..4fda35e 100644 --- a/resource/markdown/cache/HotKey.md +++ b/resource/markdown/cache/HotKey.md @@ -1,11 +1,37 @@

Redis热点Key

-在一定时间内,被频繁访问的key称为 **热点key** 。比如突发性新闻,微博上常见的热搜新闻,引起千千万万人短时间内浏览;在线商城大促活动,消费者比较关注的商品突然降价,引起成千上万消费者点击、购买。热点key所在服务器的压力骤然上升,如果超出物理机器的承载能力,则缓存不可用,进而诱发缓存击穿、缓存雪崩的问题。 +在一定时间内,被频繁访问的key称为 **热点key** 。比如突发性新闻,微博上常见的热搜新闻,引起千千万万人短时间内浏览;在线商城大促活动,消费者比较关注的商品突然降价,引起成千上万消费者点击、购买。热点key会导致流量过于集中,缓存服务器的压力骤然上升,如果超出物理机器的承载能力,则缓存不可用,进而可能诱发缓存击穿、缓存雪崩的问题。 -![缓存击穿](https://i.loli.net/2019/02/19/5c6b968d1e946.png) +![缓存击穿](https://i.loli.net/2019/02/22/5c6f888d77c01.png) #### 解决方案 -**1. 读写分离:** 读写分离比较适用于写少读多的情景, \ No newline at end of file +--- + +##### 一、读写分离 + +![读写分离](https://i.loli.net/2019/02/22/5c6fbae592c5d.png) + +通过将数据的写入与读取分散去各个节点,通过数据复制到达各个节点数据一致性的目的。在写少的情形下,master节点写入数据,在读取请求压力大的情形下,配置多个slave节点,数据横向同步(拓扑结构)。结合Redis Sentinel (或其他高可用技术)实现缓存节点的高可用。 + + + +##### 二、阿里云云数据库 Redis 版解决方案 + +![阿里云云数据库 Redis 版解决方案](https://i.loli.net/2019/02/22/5c6f8da9c4e60.png) + +在热点 `Key` 的处理上主要分为写入跟读取两种形式,在数据写入过程当 `SLB` 收到数据 `key1` 并将其通过某一个 `Proxy` 写入一个 `Redis`,完成数据的写入。假若经过后端热点模块计算发现 `key1` 成为热点 `key` 后, `Proxy` 会将该热点进行缓存,当下次客户端再进行访问 `key1` 时,可以不经 `Redis`。最后由于` Proxy` 是可以水平扩充的,因此可以任意增强热点数据的访问能力。 + + + +##### 三、热点key不过期 + +如果key存在,那么不要设置key过期时间,如果key对应的数据不可用(比如删除了),那么从缓存中删除key。从请求来说,如果在缓存找到对应的key,表明该key及其value就是用户需要的数据。如果缓存中不存在对应的key,表明无对应的数据,返回空值。比如微博发来爆料某明星文章,短时间内访问量直接上升,如果key不过期,那么请求永远命中缓存。只有当文章被删除的时候,才从缓存中删除对应的key,如果此时还有请求访问,在缓存中查无数据时,直接返回空值,表明文章被删除。当然也可以更新key对应的value值,返回想要表达的value。 + + + +**参考资料:** + +[阿里云:热点 Key 问题的发现与解决](https://help.aliyun.com/document_detail/67252.html) \ No newline at end of file From e24a861c63aa1d18fdd3f75b9a87c787a7eb447d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 3 Apr 2019 21:09:37 +0800 Subject: [PATCH 35/39] =?UTF-8?q?=E6=9B=B4=E6=AD=A3=E4=BA=86java.lang.Iter?= =?UTF-8?q?able=E7=9A=84=E7=BB=A7=E6=89=BF=E5=85=B3=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/collection/JavaCollections.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resource/markdown/collection/JavaCollections.md b/resource/markdown/collection/JavaCollections.md index eb2f217..ff3c81e 100644 --- a/resource/markdown/collection/JavaCollections.md +++ b/resource/markdown/collection/JavaCollections.md @@ -4,4 +4,4 @@ > > (如果您正在使用电脑💻浏览本文,建议将图片保存下来、逆时针🔄旋转90°阅览,如果您正在使用手机📱,建议将手机逆时针🔄旋转90°,便于阅览) -![Collection集合框架图](https://i.loli.net/2018/12/13/5c11e071b73f0.png) \ No newline at end of file +![Collection集合框架图](https://i.loli.net/2019/04/03/5ca4aea6d4244.png) \ No newline at end of file From 3b551565a1e4325856c6a0881c2e8be9523d521e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Wed, 3 Apr 2019 21:19:46 +0800 Subject: [PATCH 36/39] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=E5=85=B3?= =?UTF-8?q?=E4=BA=8EMap=E7=9A=84=E7=BB=A7=E6=89=BF=E5=9B=BE=E8=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/collection/JavaMaps.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resource/markdown/collection/JavaMaps.md b/resource/markdown/collection/JavaMaps.md index 97fc8bf..01651ac 100644 --- a/resource/markdown/collection/JavaMaps.md +++ b/resource/markdown/collection/JavaMaps.md @@ -1,3 +1,4 @@ > Java除了按 **单个元素** 存储的 **Collection**,还有如下按 **Key-Value** 存储的 **Map**: -![JavaMap框架图](https://i.loli.net/2018/12/13/5c11e028daf32.png) \ No newline at end of file +![JavaMap框架图](https://i.loli.net/2019/04/03/5ca4b20dedfd4.png) + From c171854b46bb2e6301cea38da8f47aeaed10eeae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Tue, 11 Jun 2019 18:06:29 +0800 Subject: [PATCH 37/39] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=8E=92=E5=BA=8F=E7=AE=97=E6=B3=95=E5=92=8C=E4=BA=8C?= =?UTF-8?q?=E5=8F=89=E6=A0=91=E7=9A=84=E9=81=8D=E5=8E=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 +- resource/markdown/algorithm/BasicSorting.md | 245 ++++++++++++++++++ .../markdown/algorithm/BinaryTreeTraversal.md | 120 +++++++++ 3 files changed, 376 insertions(+), 3 deletions(-) create mode 100644 resource/markdown/algorithm/BasicSorting.md create mode 100644 resource/markdown/algorithm/BinaryTreeTraversal.md diff --git a/README.md b/README.md index c9d1107..cb171ed 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,16 @@ ### :lollipop::lollipop::lollipop:全文持续更新中 ... :recycle::recycle::recycle: + + +### 零、:rocket::rocket::rocket:数据结构与算法 + +* [数据结构与算法 (第 01 篇) Java版:常见基础排序算法与复杂度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BasicSorting.md) +* [数据结构与算法 (第 02 篇) Java版:二叉树遍历的n种方法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BinaryTreeTraversal.md) + + ### 一、:bullettrain_side::railway_car::railway_car::railway_car:集合框架源码分析 + * [集合框架 (第 01 篇) 源码分析:Collection 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaCollections.md) * [集合框架 (第 02 篇) 源码分析:Map 框架总览](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/JavaMaps.md) * [集合框架 (第 03 篇) 源码分析:ArrayList](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/collection/ArrayList.md) @@ -190,6 +199,5 @@ ### 联系作者:flags:: -> :postbox:aboutcloud@163.com -> -> :dizzy:[https://www.FooVv.com](https://www.foovv.com) \ No newline at end of file +> :postbox:linkedme@qq.com +> \ No newline at end of file diff --git a/resource/markdown/algorithm/BasicSorting.md b/resource/markdown/algorithm/BasicSorting.md new file mode 100644 index 0000000..ce1fae7 --- /dev/null +++ b/resource/markdown/algorithm/BasicSorting.md @@ -0,0 +1,245 @@ +### 常见基础排序算法 + + + +#### 排序算法分类 + +![排序算法分类](https://i.loli.net/2019/06/10/5cfe3bf15a32392750.png) + + +#### 时间复杂度 + +| 排序算法 | 最好(时间复杂度) | 平均(时间复杂度) | 最坏(时间复杂度) | 稳定性 | 空间复杂度 | +| ------------ | ------------------------- | ------------------------- | ------------------------- | ------ | -------------------------------- | +| 冒泡排序 | **O**(n) | **O**(n2) | **O**(n2) | 稳定 | **O**(1) | +| **快速排序** | **O**(n*log2n) | **O**(n*log2n) | **O**(n2) | 不稳定 | **O**(log2n)~**O**(n) | +| 直接插入排序 | **O**(n) | **O**(n2) | **O**(n2) | 稳定 | **O**(1) | +| **希尔排序** | **O**(n) | **O**(n1.3) | **O**(n2) | 不稳定 | **O**(1) | +| 简单选择排序 | **O**(n) | **O**(n2) | **O**(n2) | 不稳定 | **O**(1) | +| **堆排序** | **O**(n*log2n) | **O**(n*log2n) | **O**(n*log2n) | 不稳定 | **O**(1) | +| **归并排序** | **O**(n*log2n) | **O**(n*log2n) | **O**(n*log2n) | 稳定 | **O**(n) | +| 基数排序 | **O**(d*(r+n)) | **O**(d*(r+n)) | **O**(d*(r+n)) | 稳定 | **O**(r*d+n) | + + + +#### 各种复杂度效率比较图 + +```java +O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(2^n) < O(n^3) < O(n^n) +``` + +![各种时间复杂度效率比较图](https://i.loli.net/2019/06/11/5cff235ba9b9a93703.jpg) + +**说明:** n 越大,越能体现算法效率。当 n 比较小时,复杂度会有一波小交叉,上图不考虑 n 比较小的情况。 + + + +##### 1. 冒泡排序 ★☆☆☆☆ + +```java +public void bubbleSort(int[] array) { + int temp; + // 外层移动基准元素索引 + for (int i = 0; i < array.length - 1; i++) { + // 内层对基准元素与之后的每个做比较,冒泡排序 + for (int j = i + 1; j < array.length; j++) { + // 大的值,向上冒泡 + if ((temp = array[i]) > array[j]) { + array[i] = array[j]; + array[j] = temp; + } + } + } +} +``` + + + +##### 2. 快速排序 ★★★★★ + +```java +public void quickSort(int[] array, int left, int right) { + if (left < right) { + int i = left; + int j = right; + int temp = array[i]; + + while (i < j) { + while (i < j && array[j] >= temp) { + j--; + } + if (i < j) { + array[i++] = array[j]; + } + + while (i < j && array[i] <= temp) { + i++; + } + if (i < j) { + array[j--] = array[i]; + } + + array[i] = temp; + quickSort(array, left, i - 1); + quickSort(array, i + 1, right); + } + } +} +``` + + + +##### 3. 直接插入排序 ★★★☆☆ + +```java +public void insertionSort(int[] array) { + for (int i = 0; i < array.length; i++) { + int temp = array[i]; + int j; + for (j = i; j > 0 && array[j - 1] > temp; j--) { + array[j] = array[j - 1]; + } + if (array[j] > temp) { + array[j] = temp; + } + } +} +``` + + + +##### 4. 希尔排序 ★★★☆☆ + +```java +public void shellSort(int[] array) { + // 增量 + for (int d = array.length / 2; d > 0; d /= 2) { + // 分组 + for (int x = 0; x < d; x++) { + // 直接插入排序(第 x 组的第2个元素起步) + for (int i = x + d; i < array.length; i += d) { + int temp = array[i]; + int j = i; + for (; j > d && array[j - d] > temp; j -= d) { + array[j] = array[j - d]; + } + if (array[j] > temp) { + array[j] = temp; + } + } + } + } +} +``` + + + +##### 5. 简单选择排序 ★★☆☆☆ + +```java +public void selectionSort(int[] array) { + int minIndex; + int temp; + for (int i = 0; i < array.length - 1; i++) { + minIndex = i; + for (int j = i + 1; j < array.length; j ++) { + if (array[minIndex] > array[j]) { + minIndex = j; + } + } + if (minIndex > i) { + temp = array[i]; + array[i] = array[minIndex]; + array[minIndex] = temp; + } + } +} +``` + + + +##### 6. 堆排序 ★★★★☆ + +```java +public void heapSort(int[] array) { + for (int i = array.length / 2 - 1; i >= 0; i--) { + adjustHeap(array, i, array.length); + } + + for (int i = array.length - 1; i > 0; i--) { + swap(array, 0, i); + adjustHeap(array, 0, i); + } +} + +private void adjustHeap(int[] array, int i, int length) { + int temp = array[i]; + + for (int j = i * 2 + 1; j < length; j = j * 2 + 1) { + if (j + 1 < length && array[j + 1] > array[j]) { + j++; + } + if (array[j] > temp) { + array[i] = array[j]; + i = j; + } else { + break; + } + } + array[i] = temp; +} + +private void swap(int[] array, int a, int b) { + int temp = array[a]; + array[a] = array[b]; + array[b] = temp; +} +``` + + + +##### 7. 归并排序 ★★★★☆ + +```java +public void mergeSort(int[] array) { + int[] aux = new int[array.length]; + sort(array, 0, array.length - 1, aux); +} + +private void sort(int[] array, int left, int right,int[] aux) { + if (left < right) { + int mid = (left + right) / 2; + sort(array, left , mid, aux); + sort(array, mid + 1, right, aux); + merge(array, left, mid, right, aux); + } +} + +private void merge(int[] array, int left, int mid, int right, int[] aux){ + int l = left; + int m = mid + 1; + int t = 0; + + while (l <= mid && m <= right) { + if (array[l] <= array[m]) { + aux[t++] = array[l++]; + } else { + aux[t++] = array[m++]; + } + } + + // 填充剩余元素 + while (l <= mid) { + aux[t++] = array[l++]; + } + while (m <= right) { + aux[t++] = array[m++]; + } + + t = 0; + while (left <= right) { + array[left++] = aux[t++]; + } +} +``` + diff --git a/resource/markdown/algorithm/BinaryTreeTraversal.md b/resource/markdown/algorithm/BinaryTreeTraversal.md new file mode 100644 index 0000000..e73d9e1 --- /dev/null +++ b/resource/markdown/algorithm/BinaryTreeTraversal.md @@ -0,0 +1,120 @@ +### 二叉树的遍历 ★★★★★ + + + +##### 0. TreeNode 结点 + +```java +private static class TreeNode { + T data; + TreeNode left, right; + + TreeNode(T data) { + this.data = data; + } + TreeNode() { + } +} +``` + + + +##### 1. 递归调用 (深度优先遍历) ★☆☆☆☆ + +```java +public void preorderTraversal(TreeNode node) { + if (node == null) { + return; + } + // 可调整前、中、后序遍历 + System.out.print(node.data); + preorderTraversal(node.left); + preorderTraversal(node.right); +} +``` + + + +##### 2. 非递归遍历 - 基于栈 (深度优先遍历) ★★★☆☆ + +```java +public void preorderTraversalWithStack(TreeNode root) { + Stack stack = new Stack<>(); + TreeNode node = root; + while (node != null || !stack.isEmpty()) { + // 访问结点的数据,并且移动到左子结点 + while (node != null) { + System.out.println(node.data); + stack.push(node); + node = node.left; + } + // 回溯,遍历右子结点 + if (!stack.isEmpty()) { + node = stack.pop(); + node = node.right; + } + } +} +``` + + + +##### 3. 非递归遍历 - 基于队列 (层次遍历、广度优先遍历、O(n)) ★★★★☆ + +```java +public void levelorderTraversal(TreeNode root) { + if (root == null) { + return; + } + Queue queue = new LinkedList<>(); + queue.offer(root); + TreeNode node; + // 遍历队列 + while (!queue.isEmpty()) { + // 从队列头部取出一个结点 + node = queue.poll(); + System.out.println(node.data); + // 将左右子结点放入队列尾部 + if (node.left != null) { + queue.offer(node.left); + } + if (node.right != null) { + queue.offer(node.right); + } + } +} +``` + + + +##### 4. Morris Traversal(莫里斯遍历、O(n)) ★★★★☆ + +```java +public void morrisTraversal(TreeNode root) { + TreeNode curr = root; + TreeNode prev; + while (curr != null) { + // 向左子结点遍历 + if (curr.left != null) { + prev = curr.left; + while (prev.right != null && prev.right != curr) { + prev = prev.right; + } + // 右子结点的回溯指针绑定 + if (prev.right == null) { + prev.right = curr; + curr = curr.left; + } else { + System.out.println(curr.data); + prev.right = null; + curr = curr.right; + } + // 向右子结点遍历 + } else { + System.out.print(curr.data); + curr = curr.right; + } + } +} +``` + From 91dab8d412c8600b4e87e0bdf85012728f3ad85b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Sun, 1 Sep 2019 00:28:09 +0800 Subject: [PATCH 38/39] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E4=BA=86=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=8E=92=E5=BA=8F=E7=AE=97=E6=B3=95=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=EF=BC=9B2.=E6=B7=BB=E5=8A=A0=E4=BA=86=E4=BA=8C=E5=8F=89?= =?UTF-8?q?=E6=A0=91=E9=81=8D=E5=8E=86=E3=80=81=E4=BA=8C=E5=8F=89=E6=A0=91?= =?UTF-8?q?=E6=B7=B1=E5=BA=A6=E3=80=81LRU=E7=BC=93=E5=AD=98=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- resource/markdown/algorithm/BasicSorting.md | 151 +++++++++++------ .../markdown/algorithm/BinaryTreeDepth.md | 89 ++++++++++ .../markdown/algorithm/BinaryTreeTraversal.md | 130 ++++++++++----- resource/markdown/algorithm/LRUCache.md | 153 ++++++++++++++++++ 5 files changed, 436 insertions(+), 91 deletions(-) create mode 100644 resource/markdown/algorithm/BinaryTreeDepth.md create mode 100644 resource/markdown/algorithm/LRUCache.md diff --git a/README.md b/README.md index cb171ed..bc5223b 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,10 @@ ### 零、:rocket::rocket::rocket:数据结构与算法 -* [数据结构与算法 (第 01 篇) Java版:常见基础排序算法与复杂度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BasicSorting.md) +* [数据结构与算法 (第 01 篇) Java版:常见基础排序算法及复杂度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BasicSorting.md) * [数据结构与算法 (第 02 篇) Java版:二叉树遍历的n种方法](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BinaryTreeTraversal.md) +* [数据结构与算法 (第 03 篇) Java版:二叉树深度](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/BinaryTreeDepth.md) +* [数据结构与算法 (第 04 篇) Java版:LRU缓存机制](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/algorithm/LRUCache.md) ### 一、:bullettrain_side::railway_car::railway_car::railway_car:集合框架源码分析 diff --git a/resource/markdown/algorithm/BasicSorting.md b/resource/markdown/algorithm/BasicSorting.md index ce1fae7..c37e6a2 100644 --- a/resource/markdown/algorithm/BasicSorting.md +++ b/resource/markdown/algorithm/BasicSorting.md @@ -1,10 +1,10 @@ -### 常见基础排序算法 +### 2常见基础排序算法 #### 排序算法分类 -![排序算法分类](https://i.loli.net/2019/06/10/5cfe3bf15a32392750.png) +![排序算法分类](http://ww2.sinaimg.cn/large/006y8mN6ly1g68uopou69j30ko0dzwgb.jpg) #### 时间复杂度 @@ -38,18 +38,23 @@ O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(2^n) < O(n^3) < O(n^n) ```java public void bubbleSort(int[] array) { - int temp; - // 外层移动基准元素索引 - for (int i = 0; i < array.length - 1; i++) { - // 内层对基准元素与之后的每个做比较,冒泡排序 - for (int j = i + 1; j < array.length; j++) { - // 大的值,向上冒泡 - if ((temp = array[i]) > array[j]) { - array[i] = array[j]; - array[j] = temp; - } - } - } + if (array == null) { + return; + } + + int temp; + // 冒泡次数 + for (int i = array.length - 1; i > 0; i--) { + // 冒泡排序 + for (int j = 0; j < i; j++) { + // 将大值交换到后面 + if (array[j] > array[j + 1]) { + temp = array[j]; + array[j] = array[j + 1]; + array[j + 1] = temp; + } + } + } } ``` @@ -57,32 +62,49 @@ public void bubbleSort(int[] array) { ##### 2. 快速排序 ★★★★★ +基本思想:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 + ```java public void quickSort(int[] array, int left, int right) { + if (array == null) { + return; + } + if (left < right) { int i = left; int j = right; - int temp = array[i]; + int temp = array[i]; // 选取一端值为基准值 while (i < j) { + // 如果 j 处值大于等于基准值,那么不用交换数据,直接将 j 向前移动, + // 直到 i 等于 j 或者 j 处值比基准值小 while (i < j && array[j] >= temp) { j--; } + // 如果 i < j,说明 j 处值比基准值小(根据上面循环判断) if (i < j) { + // 交换 j 与 i 处的值,并将 i 向后移动 array[i++] = array[j]; } + + // 如果 i 处值小于等于基准值,那么将i向后移动就可以了 while (i < j && array[i] <= temp) { i++; } + // 如果 i < j,说明 i 处值比基准值大(根据上面循环判断) if (i < j) { + // 交换 i 与 j 处的值,并将 i 向前移动 array[j--] = array[i]; } + // 最后将临时基准值填充到 i 处 array[i] = temp; - quickSort(array, left, i - 1); - quickSort(array, i + 1, right); + // 对两段各自快速排序 } + + quickSort(array, left, i - 1); + quickSort(array, i + 1, right); } } ``` @@ -93,12 +115,18 @@ public void quickSort(int[] array, int left, int right) { ```java public void insertionSort(int[] array) { + if (array == null) { + return; + } + // 和冒泡排序有些类似,这里是遍历趟数 for (int i = 0; i < array.length; i++) { - int temp = array[i]; + // 精髓是从局部有序,到整体有序 + int temp = array[i]; // 当前基准元素 int j; for (j = i; j > 0 && array[j - 1] > temp; j--) { - array[j] = array[j - 1]; + array[j] = array[j - 1]; // 下一个元素比基准元素大,下一个元素向后移动 } + // 最后比较当前元素和基准元素大小 if (array[j] > temp) { array[j] = temp; } @@ -108,19 +136,22 @@ public void insertionSort(int[] array) { -##### 4. 希尔排序 ★★★☆☆ +##### 4. 希尔排序(缩写增量-直接插入排序) ★★★☆☆ ```java public void shellSort(int[] array) { - // 增量 + if (array == null) { + return; + } + // 计算增量 for (int d = array.length / 2; d > 0; d /= 2) { // 分组 - for (int x = 0; x < d; x++) { - // 直接插入排序(第 x 组的第2个元素起步) - for (int i = x + d; i < array.length; i += d) { + for (int g = 0; g < d; g++) { + // 插入排序(第 x 组的第 d 个增量元素起步)(直接插入排序的增量是 1,这里是 d,需注意下) + for (int i = g + d; i < array.length; i += d) { int temp = array[i]; - int j = i; - for (; j > d && array[j - d] > temp; j -= d) { + int j; + for (j = i; j > d && array[j - d] > temp; j -= d) { array[j] = array[j - d]; } if (array[j] > temp) { @@ -138,19 +169,26 @@ public void shellSort(int[] array) { ```java public void selectionSort(int[] array) { - int minIndex; + if (array == null) { + return; + } + + int index; int temp; - for (int i = 0; i < array.length - 1; i++) { - minIndex = i; - for (int j = i + 1; j < array.length; j ++) { - if (array[minIndex] > array[j]) { - minIndex = j; + // 做出的选择次数 + for (int i = array.length - 1; i > 0; i--) { + index = 0; + for (int j = 1; j < i; j++) { + // 选择一个最大的值(记录索引) + if (array[j] > array[index]) { + index = j; } } - if (minIndex > i) { - temp = array[i]; - array[i] = array[minIndex]; - array[minIndex] = temp; + // 将选出的最大值换到一端 + if (array[index] > array[i]) { + temp = array[index]; + array[index] = array[i]; + array[i] = temp; } } } @@ -162,31 +200,43 @@ public void selectionSort(int[] array) { ```java public void heapSort(int[] array) { + if (array == null) { + return; + } + for (int i = array.length / 2 - 1; i >= 0; i--) { + // 先调整堆(选择一个最大值放到堆顶) adjustHeap(array, i, array.length); } for (int i = array.length - 1; i > 0; i--) { + // 将堆顶的元素与其他元素比较并交换 swap(array, 0, i); + // 再调整堆 adjustHeap(array, 0, i); } } -private void adjustHeap(int[] array, int i, int length) { - int temp = array[i]; +// 调整堆,使得堆顶元素值大于等于其子节点值 +private void adjustHeap(int[] array, int top, int length) { + int temp = array[top]; - for (int j = i * 2 + 1; j < length; j = j * 2 + 1) { - if (j + 1 < length && array[j + 1] > array[j]) { - j++; + for (int i = top * 2 + 1; i < length; i = i * 2 + 1) { + // (如果存在的化)从左右子节点找出值最大的子节点 + if (i + 1 < length && array[i + 1] > array[i]) { + i++; } - if (array[j] > temp) { - array[i] = array[j]; - i = j; + if (array[i] > temp) { + array[top] = array[i]; + top = i; } else { break; } } - array[i] = temp; + + if (array[top] > temp) { + array[top] = temp; + } } private void swap(int[] array, int a, int b) { @@ -202,6 +252,10 @@ private void swap(int[] array, int a, int b) { ```java public void mergeSort(int[] array) { + if (array == null) { + return; + } + int[] aux = new int[array.length]; sort(array, 0, array.length - 1, aux); } @@ -209,6 +263,7 @@ public void mergeSort(int[] array) { private void sort(int[] array, int left, int right,int[] aux) { if (left < right) { int mid = (left + right) / 2; + // 先分后合 sort(array, left , mid, aux); sort(array, mid + 1, right, aux); merge(array, left, mid, right, aux); @@ -216,10 +271,11 @@ private void sort(int[] array, int left, int right,int[] aux) { } private void merge(int[] array, int left, int mid, int right, int[] aux){ + int t = 0; int l = left; int m = mid + 1; - int t = 0; + // 判断元素值大小,按大小排序到辅助数组上 while (l <= mid && m <= right) { if (array[l] <= array[m]) { aux[t++] = array[l++]; @@ -228,7 +284,7 @@ private void merge(int[] array, int left, int mid, int right, int[] aux){ } } - // 填充剩余元素 + // 把剩余元素填充到辅助数组上 while (l <= mid) { aux[t++] = array[l++]; } @@ -236,6 +292,7 @@ private void merge(int[] array, int left, int mid, int right, int[] aux){ aux[t++] = array[m++]; } + // 将辅助线数组上的元素复制到需要排序的数组上 t = 0; while (left <= right) { array[left++] = aux[t++]; diff --git a/resource/markdown/algorithm/BinaryTreeDepth.md b/resource/markdown/algorithm/BinaryTreeDepth.md new file mode 100644 index 0000000..38cef68 --- /dev/null +++ b/resource/markdown/algorithm/BinaryTreeDepth.md @@ -0,0 +1,89 @@ +### 二叉树的最小深度 + + + +> 给定一个二叉树,找出其最小深度。 +> +> 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 + +**说明:** 叶子节点是指没有子节点的节点。 + + + +##### 0. 定义一个二叉树节点 + +```java +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode(int x) { + val = x; + } +} +``` + + + +##### 1. 递归 + +```java +public class Solution { + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + // 递归计算 + int leftMinDepth = minDepth(root.left); + int rightMinDepth = minDepth(root.right); + + // 有子节点为空的,则返回另一个节点的深度。否则返回两则最小的深度。 + return leftMinDepth == 0 || rightMinDepth == 0 + ? leftMinDepth + rightMinDepth + 1 + : Math.min(leftMinDepth, rightMinDepth) + 1; + } +} +``` + + + +##### 2. 非递归(宽度优先搜索) + +> 出现第一个无子节点的节点,则该节点的深度为树的最小深度 + +```java +public class Solution { + public int minDepth(TreeNode root) { + if (root == null) { + return 0; + } + + Queue queue = new LinkedList<>(); + queue.offer(root); + + int minDepth = 0; + while (!queue.isEmpty()) { + ++minDepth; + + // 逐层遍历,判断一层是否存在没有子节点的节点,则该节点的深度为树的最小深度 + int size = queue.size(); + for (int i = 0; i < size; i++) { + root = queue.poll(); + if (root.left == null && root.right == null) { + return minDepth; + } + if (root.left != null) { + queue.offer(root.left); + } + if (root.right != null) { + queue.offer(root.right); + } + } + } + + return minDepth; + } +} +``` + diff --git a/resource/markdown/algorithm/BinaryTreeTraversal.md b/resource/markdown/algorithm/BinaryTreeTraversal.md index e73d9e1..d55a2bb 100644 --- a/resource/markdown/algorithm/BinaryTreeTraversal.md +++ b/resource/markdown/algorithm/BinaryTreeTraversal.md @@ -2,18 +2,18 @@ -##### 0. TreeNode 结点 +##### TreeNode 节点 ```java -private static class TreeNode { - T data; - TreeNode left, right; - - TreeNode(T data) { - this.data = data; - } - TreeNode() { - } +/* Definition for a binary tree node. */ +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode(int x) { + val = x; + } } ``` @@ -22,66 +22,106 @@ private static class TreeNode { ##### 1. 递归调用 (深度优先遍历) ★☆☆☆☆ ```java -public void preorderTraversal(TreeNode node) { - if (node == null) { +public void preorderTraversal(TreeNode root) { + if (root == null) { return; } // 可调整前、中、后序遍历 - System.out.print(node.data); - preorderTraversal(node.left); - preorderTraversal(node.right); + System.out.print(root.val); + preorderTraversal(root.left); + preorderTraversal(root.right); } ``` -##### 2. 非递归遍历 - 基于栈 (深度优先遍历) ★★★☆☆ +##### 2.1 非递归遍历 - 基于栈 (前序遍历、深度优先遍历) ★★★☆☆(不推荐) ```java -public void preorderTraversalWithStack(TreeNode root) { +public List preorderTraversalWithStack(TreeNode root) { + + LinkedList output = new LinkedList<>(); + + // 用于回溯的栈 Stack stack = new Stack<>(); - TreeNode node = root; - while (node != null || !stack.isEmpty()) { - // 访问结点的数据,并且移动到左子结点 - while (node != null) { - System.out.println(node.data); - stack.push(node); - node = node.left; + + while (root != null || !stack.isEmpty()) { + // 访问结点的数据,并且移动到左子结点,直到无左子结点 + while (root != null) { + output.add(root.val); + stack.push(root); // 将当前节点 push 到栈中,方便后续回溯 + root = root.left; // 遍历左子结点 } // 回溯,遍历右子结点 if (!stack.isEmpty()) { - node = stack.pop(); - node = node.right; + root = stack.pop().right; } } + + return output; } ``` +##### 2.2非递归遍历 - 基于栈改进版(基于队列的栈模式)(执行时间较上者提升1倍) ★★★★☆ + +```java +public List preorderTraversalWithStack(TreeNode root) { + + LinkedList output = new LinkedList<>(); + + // 用于回溯的栈(基于链表实现,不用担心栈的扩容问题) + LinkedList stack = new LinkedList<>(); + + while (root != null || !stack.isEmpty()) { + // 访问结点的数据,并且移动到左子结点,直到无左子结点 + while (root != null) { + output.add(root.val); + stack.add(root); // 将当前节点 add 到栈中,方便后续回溯 + root = root.left; // 遍历左子结点 + } + // 回溯,遍历右子结点 + if (!stack.isEmpty()) { + root = stack.pollLast().right; + } + } + + return output; +} +``` + + + + + ##### 3. 非递归遍历 - 基于队列 (层次遍历、广度优先遍历、O(n)) ★★★★☆ ```java -public void levelorderTraversal(TreeNode root) { +public List levelorderTraversal(TreeNode root) { + LinkedList output = new LinkedList<>(); if (root == null) { - return; + return output; } + Queue queue = new LinkedList<>(); - queue.offer(root); - TreeNode node; + queue.add(root); + // 遍历队列 while (!queue.isEmpty()) { // 从队列头部取出一个结点 - node = queue.poll(); - System.out.println(node.data); + root = queue.poll(); + output.add(root.val); // 将左右子结点放入队列尾部 - if (node.left != null) { - queue.offer(node.left); + if (root.left != null) { + queue.add(root.left); } - if (node.right != null) { - queue.offer(node.right); + if (root.right != null) { + queue.add(root.right); } } + + return output; } ``` @@ -90,31 +130,35 @@ public void levelorderTraversal(TreeNode root) { ##### 4. Morris Traversal(莫里斯遍历、O(n)) ★★★★☆ ```java -public void morrisTraversal(TreeNode root) { - TreeNode curr = root; +public List morrisTraversal(TreeNode root) { + LinkedList output = new LinkedList<>(); + TreeNode prev; + TreeNode curr = root; while (curr != null) { - // 向左子结点遍历 + // 向左子节点遍历 if (curr.left != null) { prev = curr.left; while (prev.right != null && prev.right != curr) { prev = prev.right; } - // 右子结点的回溯指针绑定 + // 右子节点的回溯指针绑定 if (prev.right == null) { prev.right = curr; curr = curr.left; } else { - System.out.println(curr.data); + output.add(curr.val); prev.right = null; curr = curr.right; } - // 向右子结点遍历 + // 向右子节点遍历 } else { - System.out.print(curr.data); + output.add(curr.val); curr = curr.right; } } + + return output; } ``` diff --git a/resource/markdown/algorithm/LRUCache.md b/resource/markdown/algorithm/LRUCache.md new file mode 100644 index 0000000..91f159c --- /dev/null +++ b/resource/markdown/algorithm/LRUCache.md @@ -0,0 +1,153 @@ +### LRUCache 缓存算法 + +##### 最少最近使用算法 + +```java +public class LRUCache { + /** + * 默认容量 + */ + private int DEFAULT_CAPACITY = 1024; + /** + * 缓存容量 + */ + private int capacity; + /** + * 实际存储/使用的元素大小 + */ + private int size = 0; + /** + * 高效访问的散列表 + */ + private Map> map; + + private Node head, tail; + + /** + * 自定义双向链表中的节点 + */ + private static class Node { + V value; + Node prev; + Node next; + + Node(Node prev, V value, Node next) { + this.value = value; + this.prev = prev; + this.next = next; + } + } + + public LRUCache() { + this.capacity = DEFAULT_CAPACITY; + this.map = new HashMap<>(DEFAULT_CAPACITY, 0.75F); + this.head = null; + this.tail = null; + } + + public LRUCache(int capacity) { + this.capacity = capacity; + this.map = new HashMap<>(capacity, 0.75F); + this.head = null; + this.tail = null; + } + + public V get(K key) { + Node node = this.map.get(key); + if (node != null) { + this.moveToHead(node); + return node.value; + } else { + return null; + } + } + + public V put(K key, V value) { + Node node = this.map.get(key); + if (node != null) { + node.value = value; + moveToHead(node); + return value; + } + + if (size == capacity) { + removeLast(); + map.remove(key); + } + + node = addFirst(value); + map.put(key, node); + + return value; + } + + /** + * 对于新添加的元素,应将新元素添加到链表的头部 + */ + private Node addFirst(V e) { + final Node h = head; + final Node newNode = new Node<>(null, e, h); + head = newNode; + if (h == null) { + tail = newNode; + } else { + h.prev = newNode; + } + size++; + + return newNode; + } + + /** + * 对于被访问的元素,将该元素移动到头部 + */ + private Node moveToHead(Node node) { + final Node prev = node.prev; + final Node next = node.next; + + if (prev == null) { // 如果是首节点,无需移动 + return node; + } + + prev.next = next; + if (next == null) { // 如果是尾节点,需要移动tail + tail = prev; + } else { + next.prev = prev; + } + + node.prev = null; + node.next = head; + head.prev = node; + head = node; + + return node; + } + + /** + * 缓存满时,应删除(淘汰)最后一个节点 + */ + private V removeLast() { + final Node t = tail; + if (t == null) { + return null; + } + + V element = t.value; + t.value = null; // help GC + Node prev = t.prev; + t.prev = null; // help GC + tail = prev; // 移动 tail + if (prev == null) { // 如果尾节点的前一个节点也为空,说明尾节点也是首节点 + head = null; + } else { + prev.next = null; + } + size--; + return element; + } +} +``` + + + From e0335368b7ffce0e30a170c12111d577f21440ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=B0=B4=E5=B8=88?= Date: Sun, 1 Sep 2019 13:44:46 +0800 Subject: [PATCH 39/39] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86map=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E5=88=A0=E9=99=A4=E5=85=83=E7=B4=A0=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- resource/markdown/algorithm/LRUCache.md | 51 ++++++++++++++----------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/resource/markdown/algorithm/LRUCache.md b/resource/markdown/algorithm/LRUCache.md index 91f159c..953911a 100644 --- a/resource/markdown/algorithm/LRUCache.md +++ b/resource/markdown/algorithm/LRUCache.md @@ -7,7 +7,7 @@ public class LRUCache { /** * 默认容量 */ - private int DEFAULT_CAPACITY = 1024; + private static final int DEFAULT_CAPACITY = 1024; /** * 缓存容量 */ @@ -19,19 +19,21 @@ public class LRUCache { /** * 高效访问的散列表 */ - private Map> map; + private Map> map; - private Node head, tail; + private Node head, tail; /** * 自定义双向链表中的节点 */ - private static class Node { + private static class Node { + K key; V value; - Node prev; - Node next; + Node prev; + Node next; - Node(Node prev, V value, Node next) { + Node(Node prev, K key, V value, Node next) { + this.key = key; this.value = value; this.prev = prev; this.next = next; @@ -46,6 +48,10 @@ public class LRUCache { } public LRUCache(int capacity) { + if (capacity <= 0) { + throw new IllegalArgumentException("Capacity must be positive integer"); + } + this.capacity = capacity; this.map = new HashMap<>(capacity, 0.75F); this.head = null; @@ -53,7 +59,7 @@ public class LRUCache { } public V get(K key) { - Node node = this.map.get(key); + Node node = this.map.get(key); if (node != null) { this.moveToHead(node); return node.value; @@ -63,7 +69,7 @@ public class LRUCache { } public V put(K key, V value) { - Node node = this.map.get(key); + Node node = this.map.get(key); if (node != null) { node.value = value; moveToHead(node); @@ -71,11 +77,11 @@ public class LRUCache { } if (size == capacity) { - removeLast(); - map.remove(key); + node = removeLast(); + map.remove(node.key); } - node = addFirst(value); + node = addFirst(key, value); map.put(key, node); return value; @@ -84,9 +90,9 @@ public class LRUCache { /** * 对于新添加的元素,应将新元素添加到链表的头部 */ - private Node addFirst(V e) { - final Node h = head; - final Node newNode = new Node<>(null, e, h); + private Node addFirst(K key, V value) { + final Node h = head; + final Node newNode = new Node<>(null, key, value, h); head = newNode; if (h == null) { tail = newNode; @@ -101,9 +107,9 @@ public class LRUCache { /** * 对于被访问的元素,将该元素移动到头部 */ - private Node moveToHead(Node node) { - final Node prev = node.prev; - final Node next = node.next; + private Node moveToHead(Node node) { + final Node prev = node.prev; + final Node next = node.next; if (prev == null) { // 如果是首节点,无需移动 return node; @@ -127,15 +133,14 @@ public class LRUCache { /** * 缓存满时,应删除(淘汰)最后一个节点 */ - private V removeLast() { - final Node t = tail; + private Node removeLast() { + final Node t = tail; if (t == null) { return null; } - V element = t.value; t.value = null; // help GC - Node prev = t.prev; + Node prev = t.prev; t.prev = null; // help GC tail = prev; // 移动 tail if (prev == null) { // 如果尾节点的前一个节点也为空,说明尾节点也是首节点 @@ -144,7 +149,7 @@ public class LRUCache { prev.next = null; } size--; - return element; + return t; } } ```