分散式系統架構,回顧2020年常見面試知識點梳理(每次面試都會問到其中某一塊知識點)

Java白楠楠發表於2020-12-30

分散式分為分散式快取(Redis)、分散式鎖(Redis 或 Zookeeper)、分散式服務(Dubbo 或 SpringCloud)、分散式服務協調(Zookeeper)、分散式訊息佇列(Kafka 、RabbitMq)、分散式 Session 、分散式事務、分散式搜尋(Elasticsearch)等。不可能所有分散式內容都熟悉,一定要在某個領域有所專長。

分散式理論

問:分散式有哪些理論?

CAP 、BASE。分散式 CAP 理論,任何一個分散式系統都無法同時滿足 Consistency(一致性)、Availability(可用性)、Partition tolerance(分割槽容錯性) 這三個基本需求。最多隻能滿足其中兩項。而 Partition tolerance(分割槽容錯性) 是必須的,因此一般是 CP ,或者 AP。

問:你怎麼理解分散式一致性?

資料一致性通常指關聯資料之間的邏輯關係是否正確和完整。在分散式系統中,資料一致性往往指的是由於資料的複製,不同資料節點中的資料內容是否完整並且相同。

一致性還分為強一致性,弱一致性,還有最終一致性。強一致性就是馬上就保持一致。 最終一致性是指經過一段時間後,可以保持一致。

分散式事務

問:你怎麼理解分散式事務?分散式事務的協議有哪些?

分散式事務是指會涉及到操作多個資料庫的事務。目的是為了保證分散式系統中的資料一致性。分散式事務型別:二階段提交 2PC ,三階段提交 3PC。

  • 2PC :第一階段:準備階段(投票階段)和第二階段:提交階段(執行階段)。

  • 3PC :三個階段:CanCommit 、PreCommit 、DoCommit。

問:分散式事務的解決方案有哪些?

分散式事務解決方案:補償機制 TCC 、XA 、訊息佇列 MQ。

問:講一下 TCC。

T(Try)鎖資源:鎖定某個資源,設定一個預備類的狀態,凍結部分資料。

  • 比如,訂單的支付狀態,先把狀態修改為"支付中(PAYING)"。

  • 比如,本來庫存數量是 100 ,現在賣出了 2 個,不要直接扣減這個庫存。在一個單獨的凍結庫存的欄位,比如 prepare _ remove _ stock 欄位,設定一個 2。也就是說,有 2 個庫存是給凍結了。

  • 積分服務的也是同理,別直接給使用者增加會員積分。你可以先在積分表裡的一個預增加積分欄位加入積分。

  • 比如:使用者積分原本是 1190 ,現在要增加 10 個積分,別直接 1190 + 10 = 1200 個積分啊!你可以保持積分為 1190 不變,在一個預增加欄位裡,比如說 prepare _ add _ credit 欄位,設定一個 10 ,表示有 10 個積分準備增加。

C(Confirm):在各個服務裡引入了一個 TCC 分散式事務的框架,事務管理器可以感知到各個服務的 Try 操作是否都成功了。假如都成功了, TCC 分散式事務框架會控制進入 TCC 下一個階段,第一個 C 階段,也就是 Confirm 階段。此時,需要把 Try 階段鎖住的資源進行處理。

  • 比如,把訂單的狀態設定為“已支付(Payed)”。

  • 比如,扣除掉相應的庫存。

  • 比如,增加使用者積分。

C(Cancel):在 Try 階段,假如某個服務執行出錯,比如積分服務執行出錯了,那麼服務內的 TCC 事務框架是可以感知到的,然後它會決定對整個 TCC 分散式事務進行回滾。

TCC 分散式事務框架只要感知到了任何一個服務的 Try 邏輯失敗了,就會跟各個服務內的 TCC 分散式事務框架進行通訊,然後呼叫各個服務的 Cancel 邏輯。也就是說,會執行各個服務的第二個 C 階段, Cancel 階段。

  • 比如,訂單的支付狀態,先把狀態修改為" closed "狀態。

  • 比如,凍結庫存的欄位, prepare _ remove _ stock 欄位,將凍結的庫存 2 清零。

  • 比如,預增加積分的欄位, prepare _ add _ credit 欄位,將準備增加的積分 10 清零。

問:事務管理器宕掉了,怎麼辦?

做冗餘,設定多個事務管理器,一個宕掉了,其他的還可以用。

問:怎麼保證分散式系統的冪等性?

狀態機制。版本號機制。

Redis

問:Redis 有哪些優勢?

  1. 速度快,因為資料存在記憶體中。

  2. 支援豐富資料型別,支援 string、list、set 、sorted set、hash。

  3. 支援事務,操作都是原子性,所謂的原子性就是對資料的更改要麼全部執行,要麼全部不執行。

  4. 豐富的特性:可用於快取,訊息,按 key 設定過期時間,過期後將會自動刪除。

  5. 單執行緒,單程式,採用 IO 多路複用技術。

問:Redis 的儲存結構是怎樣的?

key-value 鍵值對。

問:Redis 支援哪些資料結構?

string(字串), hash(雜湊), list(佇列), set(集合)及 zset(sorted set 有序集合)。

問:Redis 的資料結構,有哪些應用場景?

  • string:簡單地 get / set 快取。

  • hash:可以快取使用者資料。比如命令:hmset user1 name "lin" sex "male" age "25" ,快取使用者 user1 的資料,姓名為 lin ,性別為男,年齡 25。

  • list:可以做佇列。往 list 佇列裡面 push 資料,然後再 pop 出來。

  • zset:可以用來做排行榜。

問:Redis 的資料結構,底層分別是由什麼實現的?

  • Redis 字串,卻不是 C 語言中的字串(即以空字元 ’\0’ 結尾的字元陣列),它是自己構建了一種名為 簡單動態字串(simple dynamic string , SDS)的抽象型別,並將 SDS 作為 Redis 的預設字串表示。

  • Redi List ,底層是 ZipList ,不滿足 ZipList 就使用雙向連結串列。ZipList 是為了節約記憶體而開發的。和各種語言的陣列類似,它是由連續的記憶體塊組成的,這樣一來,由於記憶體是連續的,就減少了很多記憶體碎片和指標的記憶體佔用,進而節約了記憶體。

問:Redis 怎麼保證可靠性?Redis 的持久化方式有哪些?有哪些優缺點?

一個可靠安全的系統,肯定要考慮資料的可靠性,尤其對於記憶體為主的 Redis ,就要考慮一旦伺服器掛掉,啟動之後,如何恢復資料的問題,也就是說資料如何持久化的問題。 AOF 就是備份操作記錄。AOF 由於是備份操作命令,備份快、恢復慢。 AOF 的優點:AOF 更好保證資料不會被丟失,最多隻丟失一秒內的資料。另外重寫操作保證了資料的有效性,即使日誌檔案過大也會進行重寫。AOF 的日誌檔案的記錄可讀性非常的高。 AOF 的缺點:對於相同數量的資料集而言, AOF 檔案通常要大於 RDB 檔案。 RDB 就是備份所有資料,使用了快照。RDB 恢復資料比較快。

問:AOF 檔案過大,怎麼處理?

會進行 AOF 檔案重寫。

  • 隨著 AOF 檔案越來越大,裡面會有大部分是重複命令或者可以合併的命令。

  • 重寫的好處:減少 AOF 日誌尺寸,減少記憶體佔用,加快資料庫恢復時間。

執行一個 AOF 檔案重寫操作,重寫會建立一個當前 AOF 檔案的體積優化版本。

問:講一下 Redis 的事務。

先以 MULTI 開始一個事務, 然後將多個命令入隊到事務中, 最後由 EXEC 命令觸發事務, 一併執行事務中的所有命令。如果想放棄這個事務,可以使用 DISCARD 命令。

問:Redis 事務無法回滾,那怎麼處理? 問:怎麼設定 Redis 的 key 過期時間?

key 的的過期時間通過 EXPIRE key seconds 命令來設定資料的過期時間。返回 1 表明設定成功,返回 0 表明 key 不存在或者不能成功設定過期時間。

問:Redis 的過期策略有哪些?

惰性刪除:當讀/寫一個已經過期的 key 時,會觸發惰性刪除策略,直接刪除掉這個過期 key ,並按照 key 不存在去處理。惰性刪除,對記憶體不太好,已經過期的 key 會佔用太多的記憶體。 定期刪除:每隔一段時間,就會對 Redis 進行檢查,主動刪除一批已過期的 key。

問:為什麼 Redis 不使用定時刪除?

定時刪除,就是在設定 key 的過期時間的同時,建立一個定時器,讓定時器在過期時間來臨時,立即執行對 key 的刪除操作。 定時刪會佔用 CPU ,影響伺服器的響應時間和效能。

問:Redis 的記憶體回收機制都有哪些?

當前已用記憶體超過 maxmemory 限定時,會觸發主動清理策略,也就是 Redis 的記憶體回收策略。 LRU 、TTL。 noeviction :預設策略,不會刪除任何資料,拒絕所有寫入操作並返回客戶端錯誤資訊,此時 Redis 只響應讀操作。

  • volatitle - lru :根據 LRU 演算法刪除設定了超時屬性的鍵,知道騰出足夠空間為止。如果沒有可刪除的鍵物件,回退到 noeviction 策略。

  • allkeys - lru :根據 LRU 演算法刪除鍵,不管資料有沒有設定超時屬性,直到騰出足夠空間為止。

  • allkeys - random :隨機刪除所有鍵,知道騰出足夠空間為止。

  • volatitle - random :隨機刪除過期鍵,知道騰出足夠空間為止。

  • volatitle - ttl :根據鍵值物件的 ttl 屬性,刪除最近將要過期資料。如果沒有,回退到 noeviction 策略。

問:手寫一下 LRU 演算法。 問:Redis 的搭建有哪些模式?

主從模式、哨兵模式、Cluster(叢集)模式。最好是用叢集模式。

問:你用過的 Redis 是多主多從的,還是一主多從的?叢集用到了多少節點?用到了多少個哨兵?

叢集模式。三主三從。

問:Redis 採用多主多從的叢集模式,各個主節點的資料是否一致?

問:Redis 叢集有哪些特性

master 和 slaver。主從複製。讀寫分離。哨兵模式。

問:Redis 是怎麼進行水平擴容的? 問:Redis 叢集資料分片的原理是什麼?

Redis 資料分片原理是雜湊槽(hash slot)。

Redis 叢集有 16384 個雜湊槽。每一個 Redis 叢集中的節點都承擔一個雜湊槽的子集。

雜湊槽讓在叢集中新增和移除節點非常容易。例如,如果我想新增一個新節點 D ,我需要從節點 A 、B、C 移動一些雜湊槽到節點 D。同樣地,如果我想從叢集中移除節點 A ,我只需要移動 A 的雜湊槽到 B 和 C。當節點 A 變成空的以後,我就可以從叢集中徹底刪除它。因為從一個節點向另一個節點移動雜湊槽並不需要停止操作,所以新增和移除節點,或者改變節點持有的雜湊槽百分比,都不需要任何停機時間(downtime)。

問:講一下一致性 Hash 演算法。

一致性 Hash 演算法將整個雜湊值空間組織成一個虛擬的圓環, 我們對 key 進行雜湊計算,使用雜湊後的結果對 2 ^ 32 取模,hash 環上必定有一個點與這個整數對應。依此確定此資料在環上的位置,從此位置沿環順時針“行走”,第一臺遇到的伺服器就是其應該定位到的伺服器。 一致性 Hash 演算法對於節點的增減都只需重定位環空間中的一小部分資料,具有較好的容錯性和可擴充套件性。 比如,叢集有四個節點 Node A 、B 、C 、D ,增加一臺節點 Node X。Node X 的位置在 Node B 到 Node C 直接,那麼受到影響的僅僅是 Node B 到 Node X 間的資料,它們要重新落到 Node X 上。 所以一致性雜湊演算法對於容錯性和擴充套件性有非常好的支援。

問:為什麼 Redis Cluster 分片不使用 Redis 一致性 Hash 演算法?

一致性雜湊演算法也有一個嚴重的問題,就是資料傾斜。 如果在分片的叢集中,節點太少,並且分佈不均,一致性雜湊演算法就會出現部分節點資料太多,部分節點資料太少。也就是說無法控制節點儲存資料的分配。

問:叢集的拓撲結構有沒有了解過?叢集是怎麼連線的?

無中心結構。Redis-Cluster 採用無中心結構,每個節點儲存資料和整個叢集狀態,每個節點都和其他所有節點連線。

問:講一下 Redis 主從複製的過程。

從機傳送 SYNC(同步)命令,主機接收後會執行 BGSAVE(非同步儲存)命令備份資料。

主機備份後,就會向從機傳送備份檔案。主機之後還會傳送緩衝區內的寫命令給從機。 當緩衝區命令傳送完成後,主機執行一條寫命令,就會往從機傳送同步寫入命令。

問:講一下 Redis 哨兵機制。

下面是 Redis 官方文件對於哨兵功能的描述:

  • 監控(Monitoring):哨兵會不斷地檢查主節點和從節點是否運作正常。

  • 自動故障轉移(Automatic Failover):當主節點不能正常工作時,哨兵會開始自動故障轉移操作,它會將失效主節點的其中一個從節點升級為新的主節點,並讓其他從節點改為複製新的主節點。

  • 配置提供者(Configuration Provider):客戶端在初始化時,通過連線哨兵來獲得當前 Redis 服務的主節點地址。

  • 通知(Notification):哨兵可以將故障轉移的結果傳送給客戶端。

問:講一下布隆過濾器。

布隆過濾器的主要是由一個很長的二進位制向量和若干個(k 個)雜湊對映函式組成。因為每個後設資料的儲存資訊值固定,而且總的二進位制向量固定。所以在記憶體佔用和查詢時間上都遠遠超過一般的演算法。當然存在一定的不準確率(可以控制)和不容易刪除樣本資料。 布隆過濾器的優點:大批量資料去重,特別的佔用記憶體。但是用布隆過濾器(Bloom Filter)會非常的省記憶體。 布隆過濾器的特點:當布隆過濾器說某個值存在時,那可能就不存在,如果說某個值不存在時,那肯定就是不存在了。 布隆過濾器的應用場景:新聞推送(不重複推送)。解決快取穿透的問題。

快取

問:快取雪崩是什麼?

如果快取資料設定的過期時間是相同的,並且 Redis 恰好將這部分資料全部刪光了。這就會導致在這段時間內,這些快取同時失效,全部請求到資料庫中。這就是快取雪崩。

問:怎麼解決快取雪崩?

解決方法:在快取的時候給過期時間加上一個隨機值,這樣就會大幅度的減少快取在同一時間過期。

問:快取穿透是什麼?

快取穿透是指查詢一個一定不存在的資料。由於快取不命中,並且出於容錯考慮,如果從資料庫查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到資料庫去查詢,失去了快取的意義。

問:怎麼解決快取穿透?

問:什麼是快取與資料庫雙寫一致問題?

問:如何保證快取與資料庫的一致性?

讀的時候,先讀快取,快取沒有的話,就讀資料庫,然後取出資料後放入快取,同時返回響應。

先刪除快取,再更新資料庫。

問:為什麼是先刪除快取,而不是先更新快取?

問:先更新資料庫,再刪除快取,會有什麼問題?

先更新資料庫,再刪除快取。可能出現以下情況:

  • 如果更新完資料庫, Java 服務提交了事務,然後掛掉了,那 Redis 還是會執行,這樣也會不一致。

  • 如果更新資料庫成功,刪除快取失敗了,那麼會導致資料庫中是新資料,快取中是舊資料,資料就出現了不一致。

先刪除快取,再更新資料庫。

  • 如果刪除快取失敗,那就不更新資料庫,快取和資料庫的資料都是舊資料,資料是一致的。

  • 如果刪除快取成功,而資料庫更新失敗了,那麼資料庫中是舊資料,快取中是空的,資料不會不一致。因為讀的時候快取沒有,所以去讀了資料庫中的舊資料,然後更新到快取中。

問:先刪除快取,在寫資料庫成功之前,如果有讀請求發生,可能導致舊資料入快取,引發資料不一致,怎麼處理?

分散式鎖

問:Redis 如何實現分散式鎖?

使用 set key value ex nx 命令。

  • 當 key 不存在時,將 key 的值設為 value ,返回 1。若給定的 key 已經存在,則 setnx 不做任何動作,返回 0。

  • 當 setnx 返回 1 時,表示獲取鎖,做完操作以後 del key ,表示釋放鎖,如果 setnx 返回 0 表示獲取鎖失敗。

詳細的命令如下:

set key value [EX seconds] [PX milliseconds] [NX|XX]EX seconds:設定失效時長,單位秒PX milliseconds:設定失效時長,單位毫秒NX:key不存在時設定value,成功返回OK,失敗返回(nil)XX:key存在時設定value,成功返回OK,失敗返回(nil)。
複製程式碼

示例如下:

set name fenglin ex 100 nx
複製程式碼

問:為什麼不先 set nx ,然後再使用 expire 設定超時時間?

我們需要保證 setnx 命令和 expire 命令以原子的方式執行,否則如果客戶端執行 setnx 獲得鎖後,這時客戶端當機了,那麼這把鎖沒有設定過期時間,導致其他客戶端永遠無法獲得鎖了。

問:使用 Redis 分散式鎖, key 和 value 分別設定成什麼?

value 可以使用 json 格式的字串,示例:

{    "count":1,    "expireAt":147506817232,    "jvmPid":22224,    "mac":"28-D2-44-0E-0D-9A",    "threadId":14}
複製程式碼

問:Redis 實現的分散式鎖,如果某個系統獲取鎖後,當機了怎麼辦?

系統模組當機的話,可以通過設定過期時間(就是設定快取失效時間)解決。系統當機時鎖阻塞,過期後鎖釋放。 問:設定快取失效時間,那如果前一個執行緒把這個鎖給刪除了呢?

問:如果加鎖和解鎖之間的業務邏輯執行的時間比較長,超過了鎖過期的時間,執行完了,又刪除了鎖,就會把別人的鎖給刪了。怎麼辦?

這兩個屬於鎖超時的問題。

可以將鎖的 value 設定為 Json 字串,在其中加入執行緒的 id 或者請求的 id ,在刪除之前, get 一下這個 key ,判斷 key 對應的 value 是不是當前執行緒的。只有是當前執行緒獲取的鎖,當前執行緒才可以刪除。

問:Redis 分散式鎖,怎麼保證可重入性?

可以將鎖的 value 設定為 Json 字串,在其中加入執行緒的 id 和 count 變數。

  • 當 count 變數的值為 0 時,表示當前分散式鎖沒有被執行緒佔用。

  • 如果 count 變數的值大於 0 ,執行緒 id 不是當前執行緒,表示當前分散式鎖已經被其他執行緒佔用。

  • 如果 count 變數的值大於 0 ,執行緒 id 是當前執行緒的 id ,表示當前執行緒已經拿到了鎖,不必阻塞,可以直接重入,並將 count 變數的值加一即可。

這種思路,其實就是參考了 ReentrantLock 可重入鎖的機制。

問:Redis 做分散式鎖, Redis 做了主從,如果設定鎖之後,主機在傳輸到從機的時候掛掉了,從機還沒有加鎖資訊,如何處理?

可以使用開源框架 Redisson ,採用了 redLock。

問:講一下 Redis 的 redLock。 問:Zookeeper 是怎麼實現分散式鎖的?

分散式鎖:基於 Zookeeper 一致性檔案系統,實現鎖服務。鎖服務分為儲存獨佔及時序控制兩類。

  • 儲存獨佔:將 Zookeeper 上的一個 znode 看作是一把鎖,通過 createznode 的方式來實現。所有客戶端都去建立 / distribute _ lock 節點,最終成功建立的那個客戶端也即擁有了這把鎖。用完刪除自己建立的 distribute _ lock 節點就釋放鎖。

  • 時序控制:基於/ distribute _ lock 鎖,所有客戶端在它下面建立臨時順序編號目錄節點,和選 master 一樣,編號最小的獲得鎖,用完刪除,依次方便。

更詳細的回答如下:

其實基於 Zookeeper ,就是使用它的臨時有序節點來實現的分散式鎖。

原理就是:當某客戶端要進行邏輯的加鎖時,就在 Zookeeper 上的某個指定節點的目錄下,去生成一個唯一的臨時有序節點, 然後判斷自己是否是這些有序節點中序號最小的一個,如果是,則算是獲取了鎖。如果不是,則說明沒有獲取到鎖,那麼就需要在序列中找到比自己小的那個節點,並對其呼叫 exist() 方法,對其註冊事件監聽,當監聽到這個節點被刪除了,那就再去判斷一次自己當初建立的節點是否變成了序列中最小的。如果是,則獲取鎖,如果不是,則重複上述步驟。

當釋放鎖的時候,只需將這個臨時節點刪除即可。

Zookeeper

問:Zookeeper 的原理是什麼?

問:Zookeeper 是怎麼保證一致性的?

zab 協議。

zab 協議有兩種模式,它們分別是恢復模式(選主)和廣播模式(同步)。當服務啟動或者在領導者崩潰後, zab 就進入了恢復模式,當領導者被選舉出來,且大多數 server 完成了和 leader 的狀態同步以後,恢復模式就結束了。狀態同步保證了 leader 和 server 具有相同的系統狀態。

問:Zookeeper 有哪些應用場景?

Zookeeper 可以作為服務協調的註冊中心。還可以做分散式鎖(如果沒有用過分散式鎖就不要說)。

問:Zookeeper 為什麼能做註冊中心?

Zookeeper 的資料模型是樹型結構,由很多資料節點組成, zk 將全量資料儲存在記憶體中,可謂是高效能,而且支援叢集,可謂高可用。另外支援事件監聽(watch 命令)。

Zookeeper 可以作為一個資料釋出/訂閱系統。

問:Zookeeper 的節點有哪些型別?有什麼區別?

臨時節點,永久節點。更加細分就是臨時有序節點、臨時無序節點、永久有序節點、永久無序節點。  

臨時節點:當建立臨時節點的程式停掉之後,這個臨時節點就會消失,儲存的資料也沒有了。

問:Zookeeper 做為註冊中心,主要儲存哪些資料?儲存在哪裡?

IP、埠、還有心跳機制。資料儲存在 Zookeeper 的節點上面。

問:心跳機制有什麼用? 問:Zookeeper 的廣播模式有什麼缺陷?

廣播風暴。

問:講一下 Zookeeper 的讀寫機制。

  • Leader 主機負責讀和寫。

  • Follower 負責讀,並將寫操作轉發給 Leader。Follower 還參與 Leader 選舉投票,參與事務請求 Proposal 投票。

  • Observer 充當觀察者的角色。Observer 和 Follower 的唯一區別在於:Observer 不參與任何投票。

問:講一下 Zookeeper 的選舉機制。

Leader 不可用時,會重新選舉 Leader。超過半數的 Follower 選舉投票即可,Observer 不參與投票。

問:你們的 Zookeeper 叢集配置了幾個節點?

3 個節點。注意:Zookeeper 叢集節點,最好是奇數個的。

叢集中的 Zookeeper 節點需要超過半數,整個叢集對外才可用。

這裡所謂的整個叢集對外才可用,是指整個叢集還能選出一個 Leader 來, Zookeeper 預設採用 quorums 來支援 Leader 的選舉。

如果有 2 個 Zookeeper,那麼只要有 1 個死了 Zookeeper 就不能用了,因為 1 沒有過半,所以 2 個 Zookeeper 的死亡容忍度為 0 ;同理,要是有 3 個 Zookeeper,一個死了,還剩下 2 個正常的,過半了,所以 3 個 Zookeeper 的容忍度為 1 ;同理你多列舉幾個:2 -> 0 ; 3 -> 1 ; 4 -> 1 ; 5 -> 2 ; 6 -> 2 會發現一個規律, 2n 和 2n - 1 的容忍度是一樣的,都是 n - 1 ,所以為了更加高效,何必增加那一個不必要的 Zookeeper 呢。

問:Zookeeper 的叢集節點,如果不是奇數可能會出現什麼問題?

可能會出現腦裂。

  • 假死:由於心跳超時(網路原因導致的)認為 master 死了,但其實 master 還存活著。

  • 腦裂:由於假死會發起新的 master 選舉,選舉出一個新的 master ,但舊的 master 網路又通了,導致出現了兩個 master ,有的客戶端連線到老的 master 有的客戶端連結到新的 master。

訊息佇列

問:為什麼使用訊息佇列?訊息佇列有什麼優點和缺點?Kafka 、ActiveMQ 、RabbitMq 、RocketMQ 都有什麼優點和缺點?

訊息佇列解耦,削峰,限流。

問:如何保證訊息佇列的高可用?(多副本)

問:如何保證訊息不被重複消費?(如何保證訊息消費的冪等性)

問:如何保證訊息的可靠性傳輸?(如何處理訊息丟失的問題)

問:如何保證訊息的順序性?

問:如何解決訊息佇列的延時以及過期失效問題?訊息佇列滿了以後該怎麼處理?有幾百萬訊息持續積壓幾小時,說說怎麼解決?

問:如果讓你寫一個訊息佇列,該如何進行架構設計啊?說一下你的思路。

答案

Kafka

問:講一下 Kafka。

Kafka 的簡單理解

問:Kafka 相對其他訊息佇列,有什麼特點?

  • 持久化:Kafka 的持久化能力比較好,通過磁碟持久化。而 RabbitMQ 是通過記憶體持久化的。

  • 吞吐量:Rocket 的併發量非常高。

  • 訊息處理:RabbitMQ 的訊息不支援批量處理,而 RocketMQ 和 Kafka 支援批量處理。

  • 高可用:RabbitMQ 採用主從模式。Kafka 也是主從模式,通過 Zookeeper 管理,選舉 Leader ,還有 Replication 副本。

  • 事務:RocketMQ 支援事務,而 Kafka 和 RabbitMQ 不支援。

問:Kafka 有哪些模式?

如果一個生產者或者多個生產者產生的訊息能夠被多個消費者同時消費的情況,這樣的訊息佇列稱為"釋出訂閱模式"的訊息佇列。

問:Kafka 作為訊息佇列,有哪些優勢?

分散式的訊息系統。

高吞吐量。即使儲存了許多 TB 的訊息,它也保持穩定的效能。 資料保留在磁碟上,因此它是持久的。

問:Kafka 為什麼處理速度會很快?kafka 的吞吐量為什麼高?

  • 零拷貝:Kafka 實現了"零拷貝"原理來快速移動資料,避免了核心之間的切換。

  • 訊息壓縮、分批傳送:Kafka 可以將資料記錄分批傳送,從生產者到檔案系統(Kafka 主題日誌)到消費者,可以端到端的檢視這些批次的資料。

  • 批處理能夠進行更有效的資料壓縮並減少 I / O 延遲。

  • 順序讀寫:Kafka 採取順序寫入磁碟的方式,避免了隨機磁碟定址的浪費。

問:講一下 Kafka 中的零拷貝。

資料的拷貝從記憶體拷貝到 kafka 服務程式那塊,又拷貝到 socket 快取那塊,整個過程耗費的時間比較高, kafka 利用了 Linux 的 sendFile 技術(NIO),省去了程式切換和一次資料拷貝,讓效能變得更好。

問:Kafka 的偏移量是什麼?

消費者每次消費資料的時候,消費者都會記錄消費的物理偏移量(offset)的位置。等到下次消費時,他會接著上次位置繼續消費

問:Kafka 的生產者,是如何傳送訊息的?

  • 生產者的訊息是先被寫入分割槽中的緩衝區中,然後分批次傳送給 Kafka Broker。

  • 生產者的訊息傳送機制,有同步傳送和非同步傳送。

  • 同步傳送訊息都有個問題,那就是同一時間只能有一個訊息在傳送,這會造成許多訊息。

  • 無法直接傳送,造成訊息滯後,無法發揮效益最大化。

  • 非同步傳送訊息的同時能夠對異常情況進行處理,生產者提供了 Callback 回撥。

問:Kafka 生產者傳送訊息,有哪些分割槽策略?

Kafka 的分割槽策略指的就是將生產者傳送到哪個分割槽的演算法。有順序輪詢、隨機輪詢、key - ordering 策略。

key - ordering 策略:Kafka 中每條訊息都會有自己的 key ,一旦訊息被定義了 Key ,那麼你就可以保證同一個 Key 的所有訊息都進入到相同的分割槽裡面,由於每個分割槽下的訊息處理都是有順序的,故這個策略被稱為按訊息鍵保序策略。

問:Kafka 為什麼要分割槽?

實現負載均衡和水平擴充套件。Kafka 可以將主題(Topic)劃分為多個分割槽(Partition),會根據分割槽規則選擇把訊息儲存到哪個分割槽中,只要如果分割槽規則設定的合理,那麼所有的訊息將會被均勻的分佈到不同的分割槽中,這樣就實現了負載均衡和水平擴充套件。另外,多個訂閱者可以從一個或者多個分割槽中同時消費資料,以支撐海量資料處理能力。

問:Kafka 是如何在 Broker 間分配分割槽的?

在 broker 間平均分佈分割槽副本。

假設有 6 個 broker ,打算建立一個包含 10 個分割槽的 Topic ,複製係數為 3 ,那麼 Kafka 就會有 30 個分割槽副本,它可以被分配給這 6 個 broker ,這樣的話,每個 broker 可以有 5 個副本。

要確保每個分割槽的每個副本分佈在不同的 broker 上面:

假設 Leader 分割槽 0 會在 broker1 上面, Leader 分割槽 1 會在 broker2 上面, Leder 分割槽 2 會在 broker3 上面。

接下來會分配跟隨者副本。如果分割槽 0 的第一個 Follower 在 broker2 上面,第二個 Follower 在 broker3 上面。分割槽 1 的第一個 Follower 在 broker3 上面,第二個 Follower 在 broker4 上面。

問:Kafka 如何保證訊息的順序性?

Kafka 可以保證同一個分割槽裡的訊息是有序的。也就是說訊息傳送到一個 Partition 是有順序的。

問:Kafka 的消費者群組 Consumer Group 訂閱了某個 Topic ,假如這個 Topic 接收到訊息並推送,那整個消費者群組能收到訊息嗎?

Kafka 官網中有這樣一句" Consumers label themselves with a consumer group name , and each record published to a topic is delivered to one consumer instance within each subscribing consumer group . "

表示推送到 topic 上的 record ,會被傳遞到已訂閱的消費者群組裡面的一個消費者例項。

問:如何提高 Kafka 的消費速度?

問:Kafka 出現訊息積壓,有哪些原因?怎麼解決?

出現訊息積壓,可能是因為消費的速度太慢。

擴容消費者。之所以消費延遲大,就是消費者處理能力有限,可以增加消費者的數量。 擴大分割槽。一個分割槽只能被消費者群組中的一個消費者消費。消費者擴大,分割槽最好多隨之擴大。

問:Kafka 訊息消費者當機了,怎麼確認有沒有收到訊息?

ACK 機制,如果接收方收到訊息後,會返回一個確認字元。

問:講一下 Kafka 的 ACK 機制。

acks 引數指定了要有多少個分割槽副本接收訊息,生產者才認為訊息是寫入成功的。此引數對訊息丟失的影響較大。

如果 acks = 0 ,就表示生產者也不知道自己產生的訊息是否被伺服器接收了,它才知道它寫成功了。如果傳送的途中產生了錯誤,生產者也不知道,它也比較懵逼,因為沒有返回任何訊息。這就類似於 UDP 的運輸層協議,只管發,伺服器接受不接受它也不關心。

如果 acks = 1 ,只要叢集的 Leader 接收到訊息,就會給生產者返回一條訊息,告訴它寫入成功。如果傳送途中造成了網路異常或者 Leader 還沒選舉出來等其他情況導致訊息寫入失敗,生產者會受到錯誤訊息,這時候生產者往往會再次重發資料。因為訊息的傳送也分為 同步 和 非同步, Kafka 為了保證訊息的高效傳輸會決定是同步傳送還是非同步傳送。如果讓客戶端等待伺服器的響應(通過呼叫 Future 中的 get() 方法),顯然會增加延遲,如果客戶端使用回撥,就會解決這個問題。

如果 acks = all ,這種情況下是隻有當所有參與複製的節點都收到訊息時,生產者才會接收到一個來自伺服器的訊息。不過,它的延遲比 acks = 1 時更高,因為我們要等待不只一個伺服器節點接收訊息。

問:Kafka 如何避免訊息丟失?

1、生產者丟失訊息的情況

生產者(Producer) 呼叫 send 方法傳送訊息之後,訊息可能因為網路問題並沒有傳送過去。

所以,我們不能預設在呼叫 send 方法傳送訊息之後訊息訊息傳送成功了。為了確定訊息是傳送成功,我們要判斷訊息傳送的結果。可以採用為其新增回撥函式的形式,獲取回撥結果。

如果訊息傳送失敗的話,我們檢查失敗的原因之後重新傳送即可!可以設定 Producer 的 retries(重試次數)為一個比較合理的值,一般是 3 ,但是為了保證訊息不丟失的話一般會設定比較大一點。

設定完成之後,當出現網路問題之後能夠自動重試訊息傳送,避免訊息丟失。

2、消費者丟失訊息的情況

當消費者拉取到了分割槽的某個訊息之後,消費者會自動提交了 offset。自動提交的話會有一個問題,試想一下,當消費者剛拿到這個訊息準備進行真正消費的時候,突然掛掉了,訊息實際上並沒有被消費,但是 offset 卻被自動提交了。手動關閉閉自動提交 offset ,每次在真正消費完訊息之後之後再自己手動提交 offset 。

3 、Kafka 丟失訊息

a、假如 leader 副本所在的 broker 突然掛掉,那麼就要從 follower 副本重新選出一個 leader ,但是 leader 的資料還有一些沒有被 follower 副本的同步的話,就會造成訊息丟失。因此可以設定 ack = all。

b、設定 replication . factor >= 3 。為了保證 leader 副本能有 follower 副本能同步訊息,我們一般會為 topic 設定 replication . factor >= 3。這樣就可以保證每個 分割槽(partition) 至少有 3 個副本。雖然造成了資料冗餘,但是帶來了資料的安全性。

問:Kafka 怎麼保證可靠性?

多副本以及 ISR 機制。

在 Kafka 中主要通過 ISR 機制來保證訊息的可靠性。

ISR(in sync replica):是 Kafka 動態維護的一組同步副本,在 ISR 中有成員存活時,只有這個組的成員才可以成為 leader ,內部儲存的為每次提交資訊時必須同步的副本(acks = all 時),每當 leader 掛掉時,在 ISR 集合中選舉出一個 follower 作為 leader 提供服務,當 ISR 中的副本被認為壞掉的時候,會被踢出 ISR ,當重新跟上 leader 的訊息資料時,重新進入 ISR。

問:什麼是 HW ?

HW(high watermark):副本的高水印值, replica 中 leader 副本和 follower 副本都會有這個值,通過它可以得知副本中已提交或已備份訊息的範圍, leader 副本中的 HW ,決定了消費者能消費的最新訊息能到哪個 offset。

問:什麼是 LEO ?

LEO(log end offset):日誌末端位移,代表日誌檔案中下一條待寫入訊息的 offset ,這個 offset 上實際是沒有訊息的。不管是 leader 副本還是 follower 副本,都有這個值。

問:Kafka 怎麼保證一致性?(存疑)

一致性定義:若某條訊息對 client 可見,那麼即使 Leader 掛了,在新 Leader 上資料依然可以被讀到。 HW - HighWaterMark : client 可以從 Leader 讀到的最大 msg offset ,即對外可見的最大 offset , HW = max(replica . offset)

對於 Leader 新收到的 msg , client 不能立刻消費, Leader 會等待該訊息被所有 ISR 中的 replica 同步後,更新 HW ,此時該訊息才能被 client 消費,這樣就保證瞭如果 Leader fail ,該訊息仍然可以從新選舉的 Leader 中獲取。 對於來自內部 Broker 的讀取請求,沒有 HW 的限制。同時, Follower 也會維護一份自己的 HW , Folloer . HW = min(Leader . HW , Follower . offset).

問:Kafka 怎麼處理重複訊息?怎麼避免重複消費?

偏移量 offset :消費者每次消費資料的時候,消費者都會記錄消費的物理偏移量(offset)的位置。等到下次消費時,他會接著上次位置繼續消費。 一般情況下, Kafka 重複消費都是由於未正常提交 offset 造成的,比如網路異常,消費者當機之類的。 使用的是 spring-Kafka ,所以把 Kafka 消費者的配置 enable.auto. commit 設為 false ,禁止 Kafka 自動提交 offset ,從而使用 spring-Kafka 提供的 offset 提交策略。

sprin-Kafka 中的 offset 提交策略可以保證一批訊息資料沒有完成消費的情況下,也能提交 offset ,從而避免了提交失敗而導致永遠重複消費的問題。

問:怎麼避免重複消費?

將訊息的唯一標識儲存起來,每次消費時判斷是否處理過即可。

問:如何保證訊息不被重複消費?(如何保證訊息消費的冪等性)

怎麼保證訊息佇列消費的冪等性?其實還是得結合業務來思考,有幾個思路: 比如你拿個資料要寫庫,你先根據主鍵查一下,如果這資料都有了,你就別插入了, update 一下好吧。 比如你是寫 Redis ,那沒問題了,反正每次都是 set ,天然冪等性。 如果是複雜一點的業務,那麼每條訊息加一個全域性唯一的 id ,類似訂單 id 之類的東西,然後消費到了之後,先根據這個 id 去比如 Redis 裡查一下,之前消費過嗎? 如果沒有消費過,你就處理,然後這個 id 寫 Redis。如果消費過了,那你就別處理了,保證別重複處理相同的訊息即可。

問:Kafka 訊息是採用 pull 模式,還是 push 模式?

pull 模式。

問:pull 模式和 push 模式,各有哪些特點?

pull 模式,準確性?可以較大保證消費者能獲取到訊息。

push 模式,即時性?可以在 broker 獲取訊息後馬上送達消費者。

問:Kafka 是如何儲存訊息的?

Kafka 使用日誌檔案的方式來儲存生產者和傳送者的訊息,每條訊息都有一個 offset 值來表示它在分割槽中的偏移量。

Kafka 中儲存的一般都是海量的訊息資料,為了避免日誌檔案過大, 一個分片並不是直接對應在一個磁碟上的日誌檔案,而是對應磁碟上的一個目錄。 資料儲存設計的特點在於以下幾點:

  1. Kafka 把主題中一個分割槽劃分成多個分段的小檔案段,通過多個小檔案段,就容易根據偏移量查詢訊息、定期清除和刪除已經消費完成的資料檔案,減少磁碟容量的佔用;

  2. 採用稀疏索引儲存的方式構建日誌的偏移量索引檔案,並將其對映至記憶體中,提高查詢訊息的效率,同時減少磁碟 IO 操作;

  3. Kafka 將訊息追加的操作邏輯變成為日誌資料檔案的順序寫入,極大的提高了磁碟 IO 的效能;

問:講一下 Kafka 叢集的 Leader 選舉機制。

Kafka 在 Zookeeper 上針對每個 Topic 都維護了一個 ISR(in - sync replica ---已同步的副本)的集合,集合的增減 Kafka 都會更新該記錄。如果某分割槽的 Leader 不可用, Kafka 就從 ISR 集合中選擇一個副本作為新的 Leader。

分庫分表

問:資料庫如何處理海量資料?

分庫分表,主從架構,讀寫分離。

問:資料庫分庫分表,何時分?怎麼分?

水平分庫/分表,垂直分庫/分表。

  • 水平分庫/表,各個庫和表的結構一模一樣。

  • 垂直分庫/表,各個庫和表的結構不一樣。

問:讀寫分離怎麼做?

主機負責寫,從機負責讀。

系統設計

1、分散式、高併發場景

遇到高併發場景,可以使用 Redis 快取、Redis 限流、MQ 非同步、MQ 削峰等。

問:在實踐中,遇到過哪些併發的業務場景?

秒殺。比如搶商品,搶紅包。

2、秒殺

問:如何設計一個秒殺/搶券系統?

  • 可以通過佇列配合非同步處理實現秒殺。

  • 使用 redis 的 list ,將商品 push 進佇列, pop 出佇列。

  • 非同步操作不會阻塞,不會消耗太多時間。

問:如何提高搶券系統的效能?

  • 使用多個 list。

  • 使用多執行緒從佇列中拉取資料。

  • 叢集提高可用性。

  • MQ 非同步處理,削峰。

問:秒殺怎麼避免少賣或超賣?

redis 是單程式單執行緒的,操作具有原子性,不會導致少賣或者超賣。另外,也可以設定一個版本號 version ,樂觀鎖機制。

問:考勤打卡,假如高峰期有幾萬人同時打卡,那麼怎麼應對這種高併發?

使用 Redis 快取。員工點選簽到,可以在快取中 set 狀態。將工號作為 key ,打卡狀態作為 value ,打卡成功為 01 ,未打卡或者打卡失敗為 00 ,然後再將資料非同步地寫入到資料庫裡面就可以了。

問:如何應對高峰期的超高併發量?

Redis 限流。Redis 可以用計數器限流。使用 INCR 命令,每次都加一,處理完業務邏輯就減一。然後設定一個最大值,當達到最大值後就直接返回,不處理後續的邏輯。

Redis 還可以用令牌桶限流。使用 Redis 佇列,每十個資料中 push 一個令牌桶,每個請求進入後會先從佇列中 pop 資料,如果是令牌就可以通行,不是令牌就直接返回。

3、短連結 問:如何將長連結轉換成短連結,併傳送簡訊?

短 URL 從生成到使用分為以下幾步:

  • 有一個服務,將要傳送給你的長 URL 對應到一個短 URL 上.例如 www.baidu.com -> www.t.cn/1。

  • 把短 url 拼接到簡訊等的內容上傳送。

  • 使用者點選短 URL ,瀏覽器用 301 / 302 進行重定向,訪問到對應的長 URL。

  • 展示對應的內容。

問:長連結和短連結如何互相轉換?

思路是建立一個發號器。每次有一個新的長 URL 進來,我們就增加一。並且將新的數值返回.第一個來的 url 返回"www.x.cn/0",第二個返回"www.x.cn/1". 問:長連結和短連結的對應關係如何儲存?

如果資料量小且 QPS 低,直接使用資料庫的自增主鍵就可以實現。 還可以將最近/最熱門的對應關係儲存在 K-V 資料庫中,這樣子可以節省空間的同時,加快響應速度。

系統架構與設計

問:如何提高系統的併發能力?

  • 使用分散式系統。

  • 部署多臺伺服器,並做負載均衡。

  • 使用快取(Redis)叢集。

  • 資料庫分庫分表 + 讀寫分離。

  • 引入訊息中介軟體叢集。

問:設計一個紅包系統,需要考慮哪些問題,如何解決?(本質上也是秒殺系統)

問:如果讓你設計一個訊息佇列,你會怎麼設計?

專案經驗及資料量 問:這個專案的亮點、難點在哪裡?

問:如果這個模組掛掉了怎麼辦? 問:你們的專案有多少臺機器?

問:你們的專案有多少個例項?

  4 個例項。

問:你們的系統 QPS(TPS)是多少?

QPS ,每秒查詢量。QPS 為幾百/幾千,已經算是比較高的了。 TPS ,每秒處理事務數。TPS 即每秒處理事務數,包括:”使用者請求伺服器”、”伺服器自己的內部處理”、”伺服器返回給使用者”,這三個過程,每秒能夠完成 N 個這三個過程, TPS 也就是 3。

問:一個介面,多少秒響應才正常?

快的話幾毫秒。慢的話 1-2 秒。異常情況可能會 10 幾秒;最好保證 99 %以上的請求是正常的。

問:這個介面的請求時間,大概多久?主要耗時在哪裡? 問:系統的資料量多少?有沒有分庫分表?

正常情況下,幾百萬的資料量沒有必要分庫分表。只有超過幾千萬才需要分庫分表。

問:插入/更新一條資料要多久?更新十萬/百萬條資料要多久?

插入/更新一條資料一般要幾毫秒;更新十萬條資料最好在 10 秒以內; 百萬條資料最好在 50-100 秒以內。


 

相關文章