日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

分布式系统常见问题

發布時間:2023/12/2 windows 16 豆豆
生活随笔 收集整理的這篇文章主要介紹了 分布式系统常见问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

/? ?前言???/

目錄

  • 為什么要進行系統拆分

  • 如果進行系統拆分

  • 分布式服務框架

    • Dubbo工作原理

    • Dubbo支持的序列化協議

    • Hessian數據結構

    • 幾個常用的CAP框架對比

  • 分布式鎖

    • Redis分布式鎖

    • zk分布式鎖

  • 分布式事務

    • 兩階段提交方案/XA方案

  • 分布式會話

/? ?正文? ?/

為什么要進行系統拆分?

先想象一下不拆分是什么樣子:

  • 要是不拆分,一個大系統幾十萬行代碼,一堆人維護同一個項目、一份代碼,改一處就要整體重新上線,代碼也得重新測試,上線發布都是幾十萬行代碼的系統一起發布,可能每次上線都要做很多的檢查,風險性極高。

  • 多分支、多版本開發也是問題,接口版本不兼容,業務沖突、代碼沖突和合并要處理,非常耗費時間;

  • 技術升級幾乎無法升級,一旦升級,可能很多地方出問題,排查。

  • 開發效率極其低下,歷史遺留問題很多,先行者到處埋坑,后來者要承受巨大的技術債務

拆分了以后:

  • 系統耦合度降低,系統性風險降低由多個服務組成,開發、部署、測試互相之間不影響,測試單系統代碼就可以,每次發布單系統服務,一切以微服務接口、mq等關聯。

  • 多工程,多 git 代碼倉庫,開發人員各自維護自己的服務,減少版本沖突、代碼沖突了。

  • 技術升級負債低,不影響其他系統,保持接口不變就可以了,各業務技術棧可百花齊放

  • 服務擴容也很簡單,新業務新增服務即可

也要提醒的一點是,系統拆分成分布式系統之后,大量的分布式系統面臨的問題也是接踵而來,所以后面的問題都是在圍繞分布式系統帶來的復雜技術挑戰在說。

如何進行系統拆分?

系統拆分為分布式系統,拆成多個服務,拆成微服務的架構,是需要拆很多輪的。并不是說上來一個架構師一次就給拆好了,而以后都不用拆。

第一輪;團隊繼續擴大,拆好的某個服務,剛開始是 1 個人維護 1 萬行代碼,后來業務系統越來越復雜,這個服務是 10 萬行代碼,5 個人;第二輪,1 個服務 -> 5 個服務,每個服務 2 萬行代碼,每人負責一個服務。

如果是多人維護一個服務,最理想的情況下,幾十個人,1 個人負責 1 個或 2~3 個服務;某個服務工作量變大了,代碼量越來越多,某個同學,負責一個服務,代碼量變成了 10 萬行了,他自己不堪重負,他現在一個人拆開,5 個服務,1 個人頂著,負責 5 個人,接著招人,2 個人,給那個同學帶著,3 個人負責 5 個服務,其中 2 個人每個人負責 2 個服務,1 個人負責 1 個服務。

個人建議,一個服務的代碼不要太多,1 萬行左右,兩三萬撐死了吧。

大部分的系統,是要進行多輪拆分的,第一次拆分,可能就是將以前的多個模塊該拆分開來了,比如說將電商系統拆分成訂單系統、商品系統、采購系統、倉儲系統、用戶系統等。

但是后面可能每個系統又變得越來越復雜了,比如說采購系統里面又分成了供應商管理系統、采購單管理系統,訂單系統又拆分成了購物車系統、價格系統、訂單管理系統。

核心意思就是根據情況,先拆分一輪,后面如果系統更復雜了,可以繼續分拆。你根據自己負責系統的實際情況來考慮。


分布式服務框架

什么是微服務

通過將功能分解到各個離散的服務中以實現對解決方案的解耦

Dubbo工作原理

dubbo 工作原理

  • 第一層:service 層,接口層,給服務提供者和消費者來實現的

  • 第二層:config 層,配置層,主要是對 dubbo 進行各種配置的

  • 第三層:proxy 層,服務代理層,無論是 consumer 還是 provider,dubbo 都會給你生成代理,代理之間進行網絡通信

  • 第四層:registry 層,服務注冊層,負責服務的注冊與發現

  • 第五層:cluster 層,集群層,封裝多個服務提供者的路由以及負載均衡,將多個實例組合成一個服務

  • 第六層:monitor 層,監控層,對 rpc 接口的調用次數和調用時間進行監控

  • 第七層:protocal 層,遠程調用層,封裝 rpc 調用

  • 第八層:exchange 層,信息交換層,封裝請求響應模式,同步轉異步

  • 第九層:transport 層,網絡傳輸層,抽象 mina 和 netty 為統一接口

  • 第十層:serialize 層,數據序列化層

工作流程

  • 第一步:provider 向注冊中心去注冊

  • 第二步:consumer 從注冊中心訂閱服務,注冊中心會通知 consumer 注冊好的服務

  • 第三步:consumer 調用 provider

  • 第四步:consumer 和 provider 都異步通知監控中心

注冊中心掛了可以繼續通信嗎?注冊中心掛了可以繼續通信嗎?

  • 可以,因為剛開始初始化的時候,消費者會將提供者的地址等信息拉取到本地緩存,所以注冊中心掛了可以繼續通信。

Dubbo 支持的序列化協議

序列化,就是把數據結構或者是一些對象,轉換為二進制串的過程,而反序列化是將在序列化過程中所生成的二進制串轉換成數據結構或者對象的過程。

dubbo 支持 hession、Java 二進制序列化、json、SOAP 文本序列化多種序列化協議。但是 hessian 是其默認的序列化協議。

  • dubbo 協議

    • 默認就是走 dubbo 協議,單一長連接,進行的是 NIO 異步通信,基于 hessian 作為序列化協議。使用的場景是:傳輸數據量小(每次請求在 100kb 以內),但是并發量很高,以及服務消費者機器數遠大于服務提供者機器數的情況。

    • 為了要支持高并發場景,一般是服務提供者就幾臺機器,但是服務消費者有上百臺,可能每天調用量達到上億次!此時用長連接是最合適的,就是跟每個服務消費者維持一個長連接就可以,可能總共就 100 個連接。然后后面直接基于長連接 NIO 異步通信,可以支撐高并發請求。

    • 長連接,通俗點說,就是建立連接過后可以持續發送請求,無須再建立連接。

  • rmi 協議

    • RMI 協議采用 JDK 標準的 java.rmi.* 實現,采用阻塞式短連接和 JDK 標準序列化方式。多個短連接,適合消費者和提供者數量差不多的情況,適用于文件的傳輸,一般較少用。

  • hessian 協議

    • Hessian 1 協議用于集成 Hessian 的服務,Hessian 底層采用 Http 通訊,采用 Servlet 暴露服務,Dubbo 缺省內嵌 Jetty 作為服務器實現。走 hessian 序列化協議,多個短連接,適用于提供者數量比消費者數量還多的情況,適用于文件的傳輸,一般較少用。

  • http 協議

    • 基于 HTTP 表單的遠程調用協議,采用 Spring 的 HttpInvoker 實現。走表單序列化。

  • thrift 協議

    • 當前 dubbo 支持的 thrift 協議是對 thrift 原生協議的擴展,在原生協議的基礎上添加了一些額外的頭信息,比如 service name,magic number 等。

  • webservice

    • 基于 WebService 的遠程調用協議,基于 Apache CXF 的 frontend-simple 和 transports-http 實現。走 SOAP 文本序列化。

  • memcached 協議

    • 基于 memcached 實現的 RPC 協議。

  • redis 協議

    • 基于 Redis 實現的 RPC 協議。

  • rest 協議

    • 基于標準的 Java REST API——JAX-RS 2.0(Java API for RESTful Web Services 的簡寫)實現的 REST 調用支持。

  • gPRC 協議

    • Dubbo 自 2.7.5 版本開始支持 gRPC 協議,對于計劃使用 HTTP/2 通信,或者想利用 gRPC 帶來的 Stream、反壓、Reactive 編程等能力的開發者來說, 都可以考慮啟用 gRPC 協議。

Hessian 的數據結構

Hessian 的對象序列化機制有 8 種原始類型:

  • 原始二進制數據

  • boolean

  • 64-bit date(64 位毫秒值的日期)

  • 64-bit double

  • 32-bit int

  • 64-bit long

  • null

  • UTF-8 編碼的 string

另外還包括 3 種遞歸類型:

  • list for lists and arrays

  • map for maps and dictionaries

  • object for objects

還有一種特殊的類型:

  • ref:用來表示對共享對象的引用。

Protocol Buffer為什么效率高

常見數據存儲格式JSON or XML,對于 Protocol Buffer 還比較陌生。Protocol Buffer 其實是 Google 出品的一種輕量并且高效的結構化數據存儲格式,性能比 JSON、XML 要高很多。

其實 PB 之所以性能如此好,主要得益于兩個:第一,它使用 proto 編譯器,自動進行序列化和反序列化,速度非常快,應該比?XML?和?JSON?快上了?20~100?倍;第二,它的數據壓縮效果好,就是說它序列化后的數據量體積小。因為體積小,傳輸起來帶寬和速度上會有優化。

dubbo與http區別

  • Dubbo 接口:

    • Dubbo 接口是阿里巴巴開源的致力于提供高性能和透明化的RPC遠程服務調用方案,以及SOA服務治理方案。

    • dubbo框架告別了傳統的web service的服務模式,進而改用provider和consumer模式進行服務。

  • 為什么是高性能的呢?

    • 可以在某個服務器集群中提供單一專注的服務,這樣不與其他服務混雜,同時dubbo接口有SOA調度通過監控每臺服務器而實現負載均衡。

    • consumer端無需關注provider端如何實現,只需在注冊中心訂閱即可到相應服務器請求服務,這樣就實現了高性能和透明化。

    • 說到底,Dubbo接口就是一個分布式服務框架。

  • 為什么要用Dubbo 接口:

    • 互聯網應用及用戶數據規模變大,傳統的垂直架構滿足不了需求,因此急需分布式服務架構以及流動計算架構。

區別:

  • 協議層區別

    • HTTP ,HTTPS 使用的是 應用層協議 應用層協議:定義了用于在網絡中進行通信和傳輸數據的接口

    • DUBBO接口使用的是 TCP/IP是傳輸層協議 傳輸層協議:管理著網絡中的端到端的數據傳輸;因此要比 HTTP協議快

  • socket 層區別

    • dubbo默認使用socket長連接,即首次訪問建立連接以后,后續網絡請求使用相同的網絡通道

    • http1.1協議默認使用短連接,每次請求均需要進行三次握手,而http2.0協議開始將默認socket連接改為了長連接(keep-alive)

  • rpc與http區別

    • rpc是一種網絡請求通信方式

    • http是應用層協議

    • 常見Controller層接口基本是基于http協議的rpc通信

    • socket API是基于TCP協議的rpc通信

    附:RMI其實就是一種RPC的實現, 很早在jdk1.1實現。RMI的通信方式是把Java對象序列化為二進制格式,接收方收到以后再進行反序列化,局限java編程。

    Dubbo 負載均衡策略和高可用策略都有哪些?動態代理策略呢?

    • dubbo 工作原理:服務注冊、注冊中心、消費者、代理通信、負載均衡;

    • 網絡通信、序列化:dubbo 協議、長連接、NIO、hessian 序列化協議;

    • 負載均衡策略、集群容錯策略、動態代理策略

    • dubbo SPI 機制:你了解不了解 dubbo 的 SPI 機制?如何基于 SPI 機制對 dubbo 進行擴展?

    dubbo 負載均衡策略

    • RandomLoadBalance :默認情況下,dubbo 是 RandomLoadBalance ,即隨機調用實現負載均衡,可以對 provider 不同實例設置不同的權重,會按照權重來負載均衡,權重越大分配流量越高,一般就用這個默認的就可以了。

    • RoundRobinLoadBalance :這個的話默認就是均勻地將流量打到各個機器上去,但是如果各個機器的性能不一樣,容易導致性能差的機器負載過高。所以此時需要調整權重,讓性能差的機器承載權重小一些,流量少一些。

    • LeastActiveLoadBalance :官網對?LeastActiveLoadBalance?的解釋是“最小活躍數負載均衡”,活躍調用數越小,表明該服務提供者效率越高,單位時間內可處理更多的請求,那么此時請求會優先分配給該服務提供者。

    • ConsistentHashLoadBalance :一致性 Hash 算法,相同參數的請求一定分發到一個 provider 上去,provider 掛掉的時候,會基于虛擬節點均勻分配剩余的流量,抖動不會太大。如果你需要的不是隨機負載均衡,是要一類請求都到一個節點,那就走這個一致性 Hash 策略。

    dubbo 集群容錯策略

    • Failover Cluster 模式 :失敗自動切換,自動重試其他機器,默認就是這個,常見于讀操作。(失敗重試其它機器)

    • Failfast Cluster 模式:一次調用失敗就立即失敗,常見于非冪等性的寫操作,比如新增一條記錄(調用失敗就立即失敗)

    • Failsafe Cluster 模式 :出現異常時忽略掉,常用于不重要的接口調用,比如記錄日志。

    • Failback Cluster 模式 :失敗了后臺自動記錄請求,然后定時重發,比較適合于寫消息隊列這種。

    • Forking Cluster 模式:并行調用多個 provider,只要一個成功就立即返回。常用于實時性要求比較高的讀操作,但是會浪費更多的服務資源,可通過?forks="2"?來設置最大并行數。

    • Broadcast Cluster 模式 :逐個調用所有的 provider。任何一個 provider 出錯則報錯(從?2.1.0?版本開始支持)。通常用于通知所有提供者更新緩存或日志等本地資源信息。

    • dubbo 動態代理策略 :默認使用 javassist 動態字節碼生成,創建代理類。但是可以通過 spi 擴展機制配置自己的動態代理策略。

    Dubbo 的 SPI 思想是什么?

    spi,簡單來說,就是?service provider interface?,說白了是什么意思呢,比如你有個接口,現在這個接口有 3 個實現類,那么在系統運行的時候對這個接口到底選擇哪個實現類呢?這就需要 spi 了,需要根據指定的配置或者是默認的配置,去找到對應的實現類加載進來,然后用這個實現類的實例對象。

    舉個栗子。

    你有一個接口 A。A1/A2/A3 分別是接口 A 的不同實現。你通過配置?接口 A = 實現 A2?,那么在系統實際運行的時候,會加載你的配置,用實現 A2 實例化一個對象來提供服務。

    spi 機制一般用在哪兒?插件擴展的場景,比如說你開發了一個給別人使用的開源框架,如果你想讓別人自己寫個插件,插到你的開源框架里面,從而擴展某個功能,這個時候 spi 思想就用上了。

    Java spi 思想的體現 spi 經典的思想體現,大家平時都在用,比如說 jdbc。Java 定義了一套 jdbc 的接口,但是 Java 并沒有提供 jdbc 的實現類。

    但是實際上項目跑的時候,要使用 jdbc 接口的哪些實現類呢?

    一般來說,我們要根據自己使用的數據庫,比如 mysql,你就將?mysql-jdbc-connector.jar?引入進來;oracle,你就將?oracle-jdbc-connector.jar?引入進來。在系統跑的時候,碰到你使用 jdbc 的接口,他會在底層使用你引入的那個 jar 中提供的實現類。

    dubbo 的 spi 思想?

    dubbo 也用了 spi 思想,不過沒有用 jdk 的 spi 機制,是自己實現的一套 spi 機制。

    Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

    Protocol 接口,在系統運行的時候,,dubbo 會判斷一下應該選用這個 Protocol 接口的哪個實現類來實例化對象來使用。它會去找一個你配置的 Protocol,將你配置的 Protocol 實現類,加載到 jvm 中來,然后實例化對象,就用你的那個 Protocol 實現類就可以了。

    上面那行代碼就是 dubbo 里大量使用的,就是對很多組件,都是保留一個接口和多個實現,然后在系統運行的時候動態根據配置去找到對應的實現類。如果你沒配置,那就走默認的實現好了,沒問題。

    如何基于 Dubbo 進行服務治理、服務降級、失敗重試以及超時重試?

    服務降級,這個是涉及到復雜分布式系統中必備的一個話題,因為分布式系統互相來回調用,任何一個系統故障了,你不降級,直接就全盤崩潰?那就太坑爹了吧。

    失敗重試,分布式系統中網絡請求如此頻繁,要是因為網絡問題不小心失敗了一次,是不是要重試?

    超時重試,跟上面一樣,如果不小心網絡慢一點,超時了,如何重試?

    分布式服務接口的冪等性如何設計(比如不能重復扣款)?

    這個沒有通用的一個方法,這個應該結合業務來保證冪等性。所謂冪等性,就是說一個接口,多次發起同一個請求,你這個接口得保證結果是準確的,比如不能多扣款、不能多插入一條數據、不能將統計值多加了 1。這就是冪等性。

    其實保證冪等性主要是三點:

    • 對于每個請求必須有一個唯一的標識,舉個栗子:訂單支付請求,肯定得包含訂單 id,一個訂單 id 最多支付一次,對吧。

    • 每次處理完請求之后,必須有一個記錄標識這個請求處理過了。常見的方案是在 mysql 中記錄個狀態啥的,比如支付之前記錄一條這個訂單的支付流水。

    • 每次接收請求需要進行判斷,判斷之前是否處理過。比如說,如果有一個訂單已經支付了,就已經有了一條支付流水,那么如果重復發送這個請求,則此時先插入支付流水,orderId 已經存在了,唯一鍵約束生效,報錯插入不進去的。然后你就不用再扣款了。

    實際運作過程中,你要結合自己的業務來,比如說利用 Redis,用 orderId 作為唯一鍵。只有成功插入這個支付流水,才可以執行實際的支付扣款。

    要求是支付一個訂單,必須插入一條支付流水,order_id 建一個唯一鍵?unique key?。你在支付一個訂單之前,先插入一條支付流水,order_id 就已經進去了。你就可以寫一個標識到 Redis 里面去,?set order_id payed?,下一次重復請求過來了,先查 Redis 的 order_id 對應的 value,如果是?payed?就說明已經支付過了,你就別重復支付了。

    分布式服務接口請求的順序性如何保證?

    一般來說,個人建議是,你們從業務邏輯上設計的這個系統最好是不需要這種順序性的保證,因為一旦引入順序性保障,比如使用分布式鎖,會導致系統復雜度上升,而且會帶來效率低下,熱點數據壓力過大等問題。

    簡單來說,首先你得用 Dubbo 的一致性 hash 負載均衡策略,將比如某一個訂單 id 對應的請求都給分發到某個機器上去,接著就是在那個機器上,因為可能還是多線程并發執行的,你可能得立即將某個訂單 id 對應的請求扔一個內存隊列里去,強制排隊,這樣來確保他們的順序性。

    但是這樣引發的后續問題就很多,比如說要是某個訂單對應的請求特別多,造成某臺機器成熱點怎么辦?解決這些問題又要開啟后續一連串的復雜技術方案...... 曾經這類問題弄的我們頭疼不已,所以,還是建議什么呢?

    最好是比如說剛才那種,一個訂單的插入和刪除操作,能不能合并成一個操作,就是一個刪除,或者是其它什么,避免這種問題的產生。

    如何自己設計一個類似 Dubbo 的 RPC 框架?

    最基本的結構:

    • 注冊中心,得有個注冊中心,保留各個服務的信息,可以用 zookeeper 來做,對吧。

    • 然后你的消費者需要去注冊中心拿對應的服務信息吧,對吧,而且每個服務可能會存在于多臺機器上。

    • 接著你就該發起一次請求了,咋發起?當然是基于動態代理了,你面向接口獲取到一個動態代理,這個動態代理就是接口在本地的一個代理,然后這個代理會找到服務對應的機器地址。

    • 然后找哪個機器發送請求?那肯定得有個負載均衡算法了,比如最簡單的可以隨機輪詢是不是。

    • 接著找到一臺機器,就可以跟它發送請求了,第一個問題咋發送?你可以說用 netty 了,nio 方式;第二個問題發送啥格式數據?你可以說用 hessian 序列化協議了,或者是別的,對吧。然后請求過去了。

    • 服務器那邊一樣的,需要針對你自己的服務生成一個動態代理,監聽某個網絡端口了,然后代理你本地的服務代碼。接收到請求的時候,就調用對應的服務代碼,對吧。

    CAP 定理

    在理論計算機科學中,CAP 定理(CAP theorem),又被稱作布魯爾定理(Brewer's theorem),它指出對于一個分布式計算系統來說,不可能同時滿足以下三點:

    • 一致性(Consistency) (等同于所有節點訪問同一份最新的數據副本)

    • 可用性(Availability)(每次請求都能獲取到非錯的響應——但是不保證獲取的數據為最新數據)

    • 分區容錯性(Partition tolerance)(以實際效果而言,分區相當于對通信的時限要求。系統如果不能在時限內達成數據一致性,就意味著發生了分區的情況,必須就當前操作在 C 和 A 之間做出選擇。)

    幾個常用的CAP框架對比

    框架所屬
    EurekaAP
    ZookeeperCP
    ConsulCP

    Eureka

    Eureka 保證了可用性,實現最終一致性。

    Eureka 所有節點都是平等的所有數據都是相同的,且 Eureka 可以相互交叉注冊。


    Eureka client 使用內置輪詢負載均衡器去注冊,有一個檢測間隔時間,如果在一定時間內沒有收到心跳,才會移除該節點注冊信息;如果客戶端發現當前 Eureka 不可用,會切換到其他的節點,如果所有的 Eureka 都跪了,Eureka client 會使用最后一次數據作為本地緩存;所以以上的每種設計都是他不具備一致性的特性。

    注意:因為 EurekaAP 的特性和請求間隔同步機制,在服務更新時候一般會手動通過 Eureka 的 api 把當前服務狀態設置為offline,并等待 2 個同步間隔后重新啟動,這樣就能保證服務更新節點對整體系統的影響

    Zookeeper

    強一致性

    Zookeeper 在選舉 leader 時會停止服務,只有成功選舉 leader 成功后才能提供服務,選舉時間較長;

    內部使用 paxos 選舉投票機制,只有獲取半數以上的投票才能成為 leader,否則重新投票,所以部署的時候最好集群節點不小于 3 的奇數個(但是誰能保證跪掉后節點也是基數個呢);

    Zookeeper 健康檢查一般是使用 tcp 長鏈接,在內部網絡抖動時或者對應節點阻塞時候都會變成不可用,這里還是比較危險的;

    Consul

    和 Zookeeper 一樣數據 CP

    Consul 注冊時候只有過半的節點都寫入成功才認為注冊成功;leader 掛掉時,重新選舉期間整個 Consul 不可用,保證了強一致性但犧牲了可用性
    有很多 blog 說 Consul 屬于 ap,官方已經確認他為 CP 機制


    分布式鎖

    • 使用 Redis 如何設計分布式鎖?使用 zk 來設計分布式鎖可以嗎?這兩種分布式鎖的實現方式哪種效率比較高?

    Redis分布式鎖

    官方叫做?RedLock?算法,是 Redis 官方支持的分布式鎖算法。

    這個分布式鎖有 3 個重要的考量點:

    • 互斥(只能有一個客戶端獲取鎖)

    • 不能死鎖

    • 容錯(只要大部分 Redis 節點創建了這把鎖就可以)

    Redis最普通的分布式鎖

    第一個最普通的實現方式,就是在 Redis 里使用?SET key value [EX seconds] [PX milliseconds] NX?創建一個 key,這樣就算加鎖。其中:

    • NX:表示只有?key?不存在的時候才會設置成功,如果此時 redis 中存在這個?key,那么設置失敗,返回?nil。

    • EX seconds:設置?key?的過期時間,精確到秒級。意思是?seconds?秒后鎖自動釋放,別人創建的時候如果發現已經有了就不能加鎖了。

    • PX milliseconds:同樣是設置?key?的過期時間,精確到毫秒級。

    比如執行以下命令:

    SET resource_name my_random_value PX 30000 NX

    釋放鎖就是刪除 key ,但是一般可以用?lua?腳本刪除,判斷 value 一樣才刪除

    為啥要用?random_value?隨機值呢?因為如果某個客戶端獲取到了鎖,但是阻塞了很長時間才執行完,比如說超過了 30s,此時可能已經自動釋放鎖了,此時可能別的客戶端已經獲取到了這個鎖,要是你這個時候直接刪除 key 的話會有問題,所以得用隨機值加上面的?lua?腳本來釋放鎖。

    但是這樣是肯定不行的。因為如果是普通的 Redis 單實例,那就是單點故障。或者是 Redis 普通主從,那 Redis 主從異步復制,如果主節點掛了(key 就沒有了),key 還沒同步到從節點,此時從節點切換為主節點,別人就可以 set key,從而拿到鎖。

    RedLock 算法

    這個場景是假設有一個 Redis cluster,有 5 個 Redis master 實例。然后執行如下步驟獲取一把鎖:

  • 獲取當前時間戳,單位是毫秒;

  • 跟上面類似,輪流嘗試在每個 master 節點上創建鎖,超時時間較短,一般就幾十毫秒(客戶端為了獲取鎖而使用的超時時間比自動釋放鎖的總時間要小。例如,如果自動釋放時間是 10 秒,那么超時時間可能在?5~50?毫秒范圍內);

  • 嘗試在大多數節點上建立一個鎖,比如 5 個節點就要求是 3 個節點?n / 2 + 1?;

  • 客戶端計算建立好鎖的時間,如果建立鎖的時間小于超時時間,就算建立成功了;

  • 要是鎖建立失敗了,那么就依次之前建立過的鎖刪除;

  • 只要別人建立了一把分布式鎖,你就得不斷輪詢去嘗試獲取鎖

  • zk分布式鎖

    zk 分布式鎖,其實可以做的比較簡單,就是某個節點嘗試創建臨時 znode,此時創建成功了就獲取了這個鎖;這個時候別的客戶端來創建鎖會失敗,只能注冊個監聽器監聽這個鎖。釋放鎖就是刪除這個 znode,一旦釋放掉就會通知客戶端,然后有一個等待著的客戶端就可以再次重新加鎖。

    redis 分布式鎖和 zk 分布式鎖的對比

    • redis 分布式鎖,其實需要自己不斷去嘗試獲取鎖,比較消耗性能。

    • zk 分布式鎖,獲取不到鎖,注冊個監聽器即可,不需要不斷主動嘗試獲取鎖,性能開銷較小。

    另外一點就是,如果是 Redis 獲取鎖的那個客戶端 出現 bug 掛了,那么只能等待超時時間之后才能釋放鎖;而 zk 的話,因為創建的是臨時 znode,只要客戶端掛了,znode 就沒了,此時就自動釋放鎖。

    Redis 分布式鎖大家沒發現好麻煩嗎?遍歷上鎖,計算時間等等......zk 的分布式鎖語義清晰實現簡單。

    所以先不分析太多的東西,就說這兩點,我個人實踐認為 zk 的分布式鎖比 Redis 的分布式鎖牢靠、而且模型簡單易用。


    分布式事務

    • 分布式事務了解嗎?你們如何解決分布式事務問題的?TCC 如果出現網絡連不通怎么辦?XA 的一致性如何保證?

    分布式事務的實現主要有以下 6 種方案:

    • XA 方案

    • TCC 方案

    • SAGA 方案

    • 本地消息表

    • 可靠消息最終一致性方案

    • 最大努力通知方案

    兩階段提交方案/XA方案

    所謂的 XA 方案,即:兩階段提交,有一個事務管理器的概念,負責協調多個數據庫(資源管理器)的事務,事務管理器先問問各個數據庫你準備好了嗎?如果每個數據庫都回復 ok,那么就正式提交事務,在各個數據庫上執行操作;如果任何其中一個數據庫回答不 ok,那么就回滾事務。

    這種分布式事務方案,比較適合單塊應用里,跨多個庫的分布式事務,而且因為嚴重依賴于數據庫層面來搞定復雜的事務,效率很低,絕對不適合高并發的場景。如果要玩兒,那么基于?Spring + JTA?就可以搞定,自己隨便搜個 demo 看看就知道了。

    這個方案,我們很少用,一般來說某個系統內部如果出現跨多個庫的這么一個操作,是不合規的。我可以給大家介紹一下, 現在微服務,一個大的系統分成幾十個甚至幾百個服務。一般來說,我們的規定和規范,是要求每個服務只能操作自己對應的一個數據庫

    如果你要操作別的服務對應的庫,不允許直連別的服務的庫,違反微服務架構的規范,你隨便交叉胡亂訪問,幾百個服務的話,全體亂套,這樣的一套服務是沒法管理的,沒法治理的,可能會出現數據被別人改錯,自己的庫被別人寫掛等情況。

    如果你要操作別人的服務的庫,你必須是通過調用別的服務的接口來實現,絕對不允許交叉訪問別人的數據庫。

    TCC 方案

    TCC 的全稱是:Try?、?Confirm?、?Cancel?。

    • Try 階段:這個階段說的是對各個服務的資源做檢測以及對資源進行鎖定或者預留

    • Confirm 階段:這個階段說的是在各個服務中執行實際的操作

    • Cancel 階段:如果任何一個服務的業務方法執行出錯,那么這里就需要進行補償,就是執行已經執行成功的業務邏輯的回滾操作。(把那些執行成功的回滾)

    這種方案說實話幾乎很少人使用,我們用的也比較少,但是也有使用的場景。因為這個事務回滾實際上是嚴重依賴于你自己寫代碼來回滾和補償了,會造成補償代碼巨大,非常之惡心。

    比如說我們,一般來說跟相關的,跟錢打交道的,支付交易相關的場景,我們會用 TCC,嚴格保證分布式事務要么全部成功,要么全部自動回滾,嚴格保證資金的正確性,保證在資金上不會出現問題。

    而且最好是你的各個業務執行的時間都比較短。

    但是說實話,一般盡量別這么搞,自己手寫回滾邏輯,或者是補償邏輯,實在太惡心了,那個業務代碼是很難維護的。

    Saga 方案

    金融核心等業務可能會選擇 TCC 方案,以追求強一致性和更高的并發量,而對于更多的金融核心以上的業務系統 往往會選擇補償事務,補償事務處理在 30 多年前就提出了 Saga 理論,隨著微服務的發展,近些年才逐步受到大家的關注。目前業界比較公認的是采用 Saga 作為長事務的解決方案。

    本地消息表

    本地消息表其實是國外的 ebay 搞出來的這么一套思想。

    這個大概意思是這樣的:

  • A 系統在自己本地一個事務里操作同時,插入一條數據到消息表;

  • 接著 A 系統將這個消息發送到 MQ 中去;

  • B 系統接收到消息之后,在一個事務里,往自己本地消息表里插入一條數據,同時執行其他的業務操作,如果這個消息已經被處理過了,那么此時這個事務會回滾,這樣保證不會重復處理消息

  • B 系統執行成功之后,就會更新自己本地消息表的狀態以及 A 系統消息表的狀態;

  • 如果 B 系統處理失敗了,那么就不會更新消息表狀態,那么此時 A 系統會定時掃描自己的消息表,如果有未處理的消息,會再次發送到 MQ 中去,讓 B 再次處理;

  • 這個方案保證了最終一致性,哪怕 B 事務失敗了,但是 A 會不斷重發消息,直到 B 那邊成功為止。

  • 這個方案說實話最大的問題就在于嚴重依賴于數據庫的消息表來管理事務啥的,如果是高并發場景咋辦呢?咋擴展呢?所以一般確實很少用。

    可靠消息最終一致性方案

    這個的意思,就是干脆不要用本地的消息表了,直接基于 MQ 來實現事務。比如阿里的 RocketMQ 就支持消息事務。

    大概的意思就是:

  • A 系統先發送一個 prepared 消息到 mq,如果這個 prepared 消息發送失敗那么就直接取消操作別執行了;

  • 如果這個消息發送成功過了,那么接著執行本地事務,如果成功就告訴 mq 發送確認消息,如果失敗就告訴 mq 回滾消息;

  • 如果發送了確認消息,那么此時 B 系統會接收到確認消息,然后執行本地的事務;

  • mq 會自動定時輪詢所有 prepared 消息回調你的接口,問你,這個消息是不是本地事務處理失敗了,所有沒發送確認的消息,是繼續重試還是回滾?一般來說這里你就可以查下數據庫看之前本地事務是否執行,如果回滾了,那么這里也回滾吧。這個就是避免可能本地事務執行成功了,而確認消息卻發送失敗了。

  • 這個方案里,要是系統 B 的事務失敗了咋辦?重試咯,自動不斷重試直到成功,如果實在是不行,要么就是針對重要的資金類業務進行回滾,比如 B 系統本地回滾后,想辦法通知系統 A 也回滾;或者是發送報警由人工來手工回滾和補償。

  • 這個還是比較合適的,目前國內互聯網公司大都是這么玩兒的,要不你就用 RocketMQ 支持的,要不你就自己基于類似 ActiveMQ?RabbitMQ?自己封裝一套類似的邏輯出來,總之思路就是這樣子的。

  • 最大努力通知方案

    這個方案的大致意思就是:

  • 系統 A 本地事務執行完之后,發送個消息到 MQ;

  • 這里會有個專門消費 MQ 的最大努力通知服務,這個服務會消費 MQ 然后寫入數據庫中記錄下來,或者是放入個內存隊列也可以,接著調用系統 B 的接口;

  • 要是系統 B 執行成功就 ok 了;要是系統 B 執行失敗了,那么最大努力通知服務就定時嘗試重新調用系統 B,反復 N 次,最后還是不行就放棄。


  • 分布式會話

    • 集群部署時的分布式 Session 如何實現?

    Session 是啥?瀏覽器有個 Cookie,在一段時間內這個 Cookie 都存在,然后每次發請求過來都帶上一個特殊的?jsessionid cookie?,就根據這個東西,在服務端可以維護一個對應的 Session 域,里面可以放點數據。

    一般的話只要你沒關掉瀏覽器,Cookie 還在,那么對應的那個 Session 就在,但是如果 Cookie 沒了,Session 也就沒了。常見于什么購物車之類的東西,還有登錄狀態保存之類的。

    這個不多說了,懂 Java 的都該知道這個。

    單塊系統的時候這么玩兒 Session 沒問題,但是你要是分布式系統呢,那么多的服務,Session 狀態在哪兒維護啊?

    其實方法很多,但是常見常用的是以下幾種:

    完全不用 Session

    使用 JWT Token 儲存用戶身份,然后再從數據庫或者 cache 中獲取其他的信息。這樣無論請求分配到哪個服務器都無所謂。

    Tomcat + Redis

    這個其實還挺方便的,就是使用 Session 的代碼,跟以前一樣,還是基于 Tomcat 原生的 Session 支持即可,然后就是用一個叫做?Tomcat RedisSessionManager?的東西,讓所有我們部署的 Tomcat 都將 Session 數據存儲到 Redis 即可。

    Spring Session + Redis

    上面所說的第二種方式會與 Tomcat 容器重耦合,如果我要將 Web 容器遷移成 Jetty,難道還要重新把 Jetty 都配置一遍?

    因為上面那種 Tomcat + Redis 的方式好用,但是會嚴重依賴于 Web 容器,不好將代碼移植到其他 Web 容器上去,尤其是你要是換了技術棧咋整?比如換成了 Spring Cloud 或者是 Spring Boot 之類的呢?

    所以現在比較好的還是基于 Java 一站式解決方案,也就是 Spring。人家 Spring 基本上承包了大部分我們需要使用的框架,Spirng Cloud 做微服務,Spring Boot 做腳手架,所以用 Spring Session 是一個很好的選擇。

    關注我的公眾號,學習技術或投稿

    長按上圖,識別圖中二維碼即可關注

    總結

    以上是生活随笔為你收集整理的分布式系统常见问题的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。