分佈式分為分佈式緩存(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 有哪些優勢?
- 速度快,因為數據存在內存中。
- 支持豐富數據類型,支持 string、list、set 、sorted set、hash。
- 支持事務,操作都是原子性,所謂的原子性就是對數據的更改要麼全部執行,要麼全部不執行。
- 豐富的特性:可用於緩存,消息,按 key 設置過期時間,過期後將會自動刪除。
- 單線程,單進程,採用 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 模式和 push 模式,各有哪些特點?
pull 模式,準確性?可以較大保證消費者能獲取到消息。
push 模式,即時性?可以在 broker 獲取消息後馬上送達消費者。
問:Kafka 是如何存儲消息的?
Kafka 使用日誌文件的方式來保存生產者和發送者的消息,每條消息都有一個 offset 值來表示它在分區中的偏移量。
Kafka 中存儲的一般都是海量的消息數據,為了避免日誌文件過大, 一個分片並不是直接對應在一個磁盤上的日誌文件,而是對應磁盤上的一個目錄。 數據存儲設計的特點在於以下幾點:
- Kafka 把主題中一個分區劃分成多個分段的小文件段,通過多個小文件段,就容易根據偏移量查找消息、定期清除和刪除已經消費完成的數據文件,減少磁盤容量的佔用;
- 採用稀疏索引存儲的方式構建日誌的偏移量索引文件,並將其映射至內存中,提高查找消息的效率,同時減少磁盤 IO 操作;
- 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)集群。
- 數據庫分庫分表 + 讀寫分離。
- 引入消息中間件集群。
問:設計一個紅包系統,需要考慮哪些問題,如何解決?(本質上也是秒殺系統)
問:如果讓你設計一個消息隊列,你會怎麼設計?
項目經驗及數據量 問:這個項目的亮點、難點在哪裡?
問:如果這個模塊掛掉了怎麼辦? 問:你們的項目有多少臺機器?
問:你們的項目有多少個實例?
問:你們的系統 QPS(TPS)是多少?
QPS ,每秒查詢量。QPS 為幾百/幾千,已經算是比較高的了。 TPS ,每秒處理事務數。TPS 即每秒處理事務數,包括:”用戶請求服務器”、”服務器自己的內部處理”、”服務器返回給用戶”,這三個過程,每秒能夠完成 N 個這三個過程, TPS 也就是 3。
問:一個接口,多少秒響應才正常?
快的話幾毫秒。慢的話 1-2 秒。異常情況可能會 10 幾秒;最好保證 99 %以上的請求是正常的。
問:這個接口的請求時間,大概多久?主要耗時在哪裡? 問:系統的數據量多少?有沒有分庫分表?
正常情況下,幾百萬的數據量沒有必要分庫分表。只有超過幾千萬才需要分庫分表。
問:插入/更新一條數據要多久?更新十萬/百萬條數據要多久?
插入/更新一條數據一般要幾毫秒;更新十萬條數據最好在 10 秒以內; 百萬條數據最好在 50-100 秒以內。