T5大牛帶你解析:如何實現分散式技術

lihong發表於2019-04-24

1.分散式事務

2. 分散式鎖

Java 原生 API 雖然有併發鎖,但並沒有提供分散式鎖的能力,所以針對分散式場景中的鎖需要解決的方案。

分散式鎖的解決方案大致有以下幾種:

  • 基於資料庫實現
  • 基於快取(redis,memcached 等)實現
  • 基於 Zookeeper 實現

2.1. 基於資料庫實現分散式鎖

實現

1. 建立表

CREATE TABLE `methodLock` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '鎖定的方法名',
  `desc` varchar(1024) NOT NULL DEFAULT '備註資訊',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '儲存資料時間,自動生成',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='鎖定中的方法';複製程式碼

2. 獲取鎖

想要鎖住某個方法時,執行以下 SQL:

insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)複製程式碼

因為我們對 method_name 做了唯一性約束,這裡如果有多個請求同時提交到資料庫的話,資料庫會保證只有一個操作可以成功,那麼我們就可以認為操作成功的那個執行緒獲得了該方法的鎖,可以執行方法體內容。

成功插入則獲取鎖。

3. 釋放鎖

當方法執行完畢之後,想要釋放鎖的話,需要執行以下 Sql:

delete from methodLock where method_name ='method_name'複製程式碼

問題

  1. 這把鎖強依賴資料庫的可用性。如果資料庫是一個單點,一旦資料庫掛掉,會導致業務系統不可用。
  2. 這把鎖沒有失效時間,一旦解鎖操作失敗,就會導致鎖記錄一直在資料庫中,其他執行緒無法再獲得到鎖。
  3. 這把鎖只能是非阻塞的,因為資料的 insert 操作,一旦插入失敗就會直接報錯。沒有獲得鎖的執行緒並不會進入排隊佇列,要想再次獲得鎖就要再次觸發獲得鎖操作。
  4. 這把鎖是非重入的,同一個執行緒在沒有釋放鎖之前無法再次獲得該鎖。因為資料中資料已經存在了。

解決辦法

  1. 單點問題可以用多資料庫例項,同時塞 N 個表,N/2+1 個成功就任務鎖定成功
  2. 寫一個定時任務,隔一段時間清除一次過期的資料。
  3. 寫一個 while 迴圈,不斷的重試插入,直到成功。
  4. 在資料庫表中加個欄位,記錄當前獲得鎖的機器的主機資訊和執行緒資訊,那麼下次再獲取鎖的時候先查詢資料庫,如果當前機器的主機資訊和執行緒資訊在資料庫可以查到的話,直接把鎖分配給他就可以了。

小結

  • 優點: 直接藉助資料庫,容易理解。
  • 缺點: 會有各種各樣的問題,在解決問題的過程中會使整個方案變得越來越複雜。運算元據庫需要一定的開銷,效能問題需要考慮。

2.2. 基於 Redis 實現分散式鎖

相比於用資料庫來實現分散式鎖,基於快取實現的分散式鎖的效能會更好一些。目前有很多成熟的分散式產品,包括 Redis、memcache、Tair 等。這裡以 Redis 舉例。

Redis 命令

  • setnx - setnx key val:當且僅當 key 不存在時,set 一個 key 為 val 的字串,返回 1;若 key 存在,則什麼都不做,返回 0。
  • expire - expire key timeout:為 key 設定一個超時時間,單位為 second,超過這個時間鎖會自動釋放,避免死鎖。
  • delete - delete key:刪除 key

實現

單點實現步驟:

  1. 獲取鎖的使用,使用 setnx 加鎖,鎖的 value 值為一個隨機生成的 UUID,再使用 expire 設定一個過期值。
  2. 獲取鎖的時候還設定一個獲取的超時時間,若超過這個時間則放棄獲取鎖。
  3. 釋放鎖的時候,通過 UUID 判斷是不是該鎖,若是該鎖,則執行 delete 進行鎖釋放。

問題

  • 單點問題。如果單機 redis 掛掉了,那麼程式會跟著出錯。
  • 如果轉移使用 slave 節點,複製不是同步複製,會出現多個程式獲取鎖的情況

小結

可以考慮使用 redisson 的解決方案。

2.3. 基於 ZooKeeper 實現分散式鎖

實現

這也是 ZooKeeper 客戶端 curator 的分散式鎖實現。

  1. 建立一個目錄 mylock;
  2. 執行緒 A 想獲取鎖就在 mylock 目錄下建立臨時順序節點;
  3. 獲取 mylock 目錄下所有的子節點,然後獲取比自己小的兄弟節點,如果不存在,則說明當前執行緒順序號最小,獲得鎖;
  4. 執行緒 B 獲取所有節點,判斷自己不是最小節點,設定監聽比自己次小的節點;
  5. 執行緒 A 處理完,刪除自己的節點,執行緒 B 監聽到變更事件,判斷自己是不是最小的節點,如果是則獲得鎖。

小結

ZooKeeper 版本的分散式鎖問題相對比較來說少。

  • 鎖的佔用時間限制:redis 就有佔用時間限制,而 ZooKeeper 則沒有,最主要的原因是 redis 目前沒有辦法知道已經獲取鎖的客戶端的狀態,是已經掛了呢還是正在執行耗時較長的業務邏輯。而 ZooKeeper 通過臨時節點就能清晰知道,如果臨時節點存在說明還在執行業務邏輯,如果臨時節點不存在說明已經執行完畢釋放鎖或者是掛了。由此看來 redis 如果能像 ZooKeeper 一樣新增一些與客戶端繫結的臨時鍵,也是一大好事。
  • 是否單點故障:redis 本身有很多中玩法,如客戶端一致性 hash,伺服器端 sentinel 方案或者 cluster 方案,很難做到一種分散式鎖方式能應對所有這些方案。而 ZooKeeper 只有一種玩法,多臺機器的節點資料是一致的,沒有 redis 的那麼多的麻煩因素要考慮。

總體上來說 ZooKeeper 實現分散式鎖更加的簡單,可靠性更高。但 ZooKeeper 因為需要頻繁的建立和刪除節點,效能上不如 Redis 方式。

3. 分散式 Session

在分散式場景下,一個使用者的 Session 如果只儲存在一個伺服器上,那麼當負載均衡器把使用者的下一個請求轉發到另一個伺服器上,該伺服器沒有使用者的 Session,就可能導致使用者需要重新進行登入等操作。

分散式 Session 的幾種實現策略:

  1. 粘性 session
  2. 應用伺服器間的 session 複製共享
  3. 基於 cache DB 快取的 session 共享

3.1. Sticky Sessions

需要配置負載均衡器,使得一個使用者的所有請求都路由到一個伺服器節點上,這樣就可以把使用者的 Session 存放在該伺服器節點中。

缺點:當伺服器節點當機時,將丟失該伺服器節點上的所有 Session。

T5大牛帶你解析:如何實現分散式技術

3.2. Session Replication

在伺服器節點之間進行 Session 同步操作,這樣的話使用者可以訪問任何一個伺服器節點。

缺點:佔用過多記憶體;同步過程佔用網路頻寬以及伺服器處理器時間。

T5大牛帶你解析:如何實現分散式技術

3.3. Session Server

使用一個單獨的伺服器儲存 Session 資料,可以存在 MySQL 資料庫上,也可以存在 Redis 或者 Memcached 這種記憶體型資料庫。

缺點:需要去實現存取 Session 的程式碼。

T5大牛帶你解析:如何實現分散式技術

4. 分散式儲存

通常有兩種解決方案:

  1. 資料分佈:就是把資料分塊存在不同的伺服器上(分庫分表)。
  2. 資料複製:讓所有的伺服器都有相同的資料,提供相當的服務。

5. 分散式快取

使用快取的好處:

  • 提升資料讀取速度
  • 提升系統擴充套件能力,通過擴充套件快取,提升系統承載能力
  • 降低儲存成本,Cache+DB 的方式可以承擔原有需要多臺 DB 才能承擔的請求量,節省機器成本

根據業務場景,通常快取有以下幾種使用方式

  • 懶漢式(讀時觸發):寫入 DB 後, 然後把相關的資料也寫入 Cache
  • 飢餓式(寫時觸發):先查詢 DB 裡的資料, 然後把相關的資料寫入 Cache
  • 定期重新整理:適合週期性的跑資料的任務,或者列表型的資料,而且不要求絕對實時性

快取分類:

  • 應用內快取:如:EHCache
  • 分散式快取:如:Memached、Redis

6. 分散式計算

7. 負載均衡

7.1. 演算法

輪詢(Round Robin)

輪詢演算法把每個請求輪流傳送到每個伺服器上。下圖中,一共有 6 個客戶端產生了 6 個請求,這 6 個請求按 (1, 2, 3, 4, 5, 6) 的順序傳送。最後,(1, 3, 5) 的請求會被髮送到伺服器 1,(2, 4, 6) 的請求會被髮送到伺服器 2。

T5大牛帶你解析:如何實現分散式技術

該演算法比較適合每個伺服器的效能差不多的場景,如果有效能存在差異的情況下,那麼效能較差的伺服器可能無法承擔過大的負載(下圖的 Server 2)。

T5大牛帶你解析:如何實現分散式技術

加權輪詢(Weighted Round Robbin)

加權輪詢是在輪詢的基礎上,根據伺服器的效能差異,為伺服器賦予一定的權值。例如下圖中,伺服器 1 被賦予的權值為 5,伺服器 2 被賦予的權值為 1,那麼 (1, 2, 3, 4, 5) 請求會被髮送到伺服器 1,(6) 請求會被髮送到伺服器 2。

T5大牛帶你解析:如何實現分散式技術

最少連線(least Connections)

由於每個請求的連線時間不一樣,使用輪詢或者加權輪詢演算法的話,可能會讓一臺伺服器當前連線數過大,而另一臺伺服器的連線過小,造成負載不均衡。例如下圖中,(1, 3, 5) 請求會被髮送到伺服器 1,但是 (1, 3) 很快就斷開連線,此時只有 (5) 請求連線伺服器 1;(2, 4, 6) 請求被髮送到伺服器 2,只有 (2) 的連線斷開。該系統繼續執行時,伺服器 2 會承擔過大的負載。

T5大牛帶你解析:如何實現分散式技術

最少連線演算法就是將請求傳送給當前最少連線數的伺服器上。例如下圖中,伺服器 1 當前連線數最小,那麼新到來的請求 6 就會被髮送到伺服器 1 上。

T5大牛帶你解析:如何實現分散式技術

加權最少連線(Weighted Least Connection)

在最少連線的基礎上,根據伺服器的效能為每臺伺服器分配權重,再根據權重計算出每臺伺服器能處理的連線數。

T5大牛帶你解析:如何實現分散式技術

隨機演算法(Random)

把請求隨機傳送到伺服器上。和輪詢演算法類似,該演算法比較適合伺服器效能差不多的場景。

T5大牛帶你解析:如何實現分散式技術

源地址雜湊法 (IP Hash)

源地址雜湊通過對客戶端 IP 雜湊計算得到的一個數值,用該數值對伺服器數量進行取模運算,取模結果便是目標伺服器的序號。

  • 優點:保證同一 IP 的客戶端都會被 hash 到同一臺伺服器上。
  • 缺點:不利於叢集擴充套件,後臺伺服器數量變更都會影響 hash 結果。可以採用一致性 Hash 改進。

T5大牛帶你解析:如何實現分散式技術

7.2. 實現

HTTP 重定向

HTTP 重定向負載均衡伺服器收到 HTTP 請求之後會返回伺服器的地址,並將該地址寫入 HTTP 重定向響應中返回給瀏覽器,瀏覽器收到後需要再次傳送請求。

缺點:

  • 使用者訪問的延遲會增加;
  • 如果負載均衡器當機,就無法訪問該站點。

T5大牛帶你解析:如何實現分散式技術

DNS 重定向

使用 DNS 作為負載均衡器,根據負載情況返回不同伺服器的 IP 地址。大型網站基本使用了這種方式做為第一級負載均衡手段,然後在內部使用其它方式做第二級負載均衡。

缺點:

  • DNS 查詢表可能會被客戶端快取起來,那麼之後的所有請求都會被重定向到同一個伺服器。

T5大牛帶你解析:如何實現分散式技術

修改 MAC 地址

使用 LVS(Linux Virtual Server)這種鏈路層負載均衡器,根據負載情況修改請求的 MAC 地址。

T5大牛帶你解析:如何實現分散式技術

修改 IP 地址

在網路層修改請求的目的 IP 地址。

T5大牛帶你解析:如何實現分散式技術

代理自動配置

正向代理與反向代理的區別:

  • 正向代理:發生在客戶端,是由使用者主動發起的。比如翻牆,客戶端通過主動訪問代理伺服器,讓代理伺服器獲得需要的外網資料,然後轉發回客戶端。
  • 反向代理:發生在伺服器端,使用者不知道代理的存在。

PAC 伺服器是用來判斷一個請求是否要經過代理。

T5大牛帶你解析:如何實現分散式技術

免費Java資料需要自己領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分散式等教程,一共30G。
傳送門: mp.weixin.qq.com/s/JzddfH-7y…

T5大牛帶你解析:如何實現分散式技術

相關文章