java程式設計師進階:Redis分散式技術問題集錦

java閘瓦發表於2019-04-29

Redis 簡介

java程式設計師進階:Redis分散式技術問題集錦

Redis 是完全開源免費的,遵守BSD協議,是一個高效能的key-value資料庫。

Redis 與其他 key - value 快取產品有以下三個特點:

  • Redis支援資料的持久化,可以將記憶體中的資料儲存在磁碟中,重啟的時候可以再次載入進行使用。
  • Redis不僅僅支援簡單的key-value型別的資料,同時還提供list,set,zset,hash等資料結構的儲存。
  • Redis支援資料的備份,即master-slave模式的資料備份。

Redis 優勢

  • 效能極高 – Redis能讀的速度是110000次/s,寫的速度是81000次/s 。
  • 豐富的資料型別 – Redis支援二進位制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 資料型別操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要麼成功執行要麼失敗完全不執行。單個操作是原子性的。多個操作也支援事務,即原子性,通過MULTI和EXEC指令包起來。
  • 豐富的特性 – Redis還支援 publish/subscribe, 通知, key 過期等等特性。

分散式快取

Redis 有什麼資料型別?分別用於什麼場景?

java程式設計師進階:Redis分散式技術問題集錦

Redis 的主從複製是如何實現的?

  • 從伺服器連線主伺服器,傳送 SYNC 命令;
  • 主伺服器接收到 SYNC 命名後,開始執行 BGSAVE 命令生成 RDB 檔案並使用緩衝區記錄此後執行的所有寫命令;
  • 主伺服器 BGSAVE 執行完後,向所有從伺服器傳送快照檔案,並在傳送期間繼續記錄被執行的寫命令;
  • 從伺服器收到快照檔案後丟棄所有舊資料,載入收到的快照;
  • 主伺服器快照傳送完畢後開始向從伺服器傳送緩衝區中的寫命令;
  • 從伺服器完成對快照的載入,開始接收命令請求,並執行來自主伺服器緩衝區的寫命令;

Redis 的 key 是如何定址的?

背景

(1)redis 中的每一個資料庫,都由一個 redisDb 的結構儲存。其中:

  • redisDb.id 儲存著 redis 資料庫以整數表示的號碼。
  • redisDb.dict 儲存著該庫所有的鍵值對資料。
  • redisDb.expires 儲存著每一個鍵的過期時間。

(2)當 redis 伺服器初始化時,會預先分配 16 個資料庫(該數量可以通過配置檔案配置),所有資料庫儲存到結構 redisServer 的一個成員 redisServer.db 陣列中。當我們選擇資料庫 select number 時,程式直接通過 redisServer.db[number] 來切換資料庫。有時候當程式需要知道自己是在哪個資料庫時,直接讀取 redisDb.id 即可。

(3)redis 的字典使用雜湊表作為其底層實現。dict 型別使用的兩個指向雜湊表的指標,其中 0 號雜湊表(ht[0])主要用於儲存資料庫的所有鍵值,而 1 號雜湊表主要用於程式對 0 號雜湊表進行 rehash 時使用,rehash 一般是在新增新值時會觸發,這裡不做過多的贅述。所以 redis 中查詢一個 key,其實就是對進行該 dict 結構中的 ht[0] 進行查詢操作。

(4)既然是雜湊,那麼我們知道就會有雜湊碰撞,那麼當多個鍵雜湊之後為同一個值怎麼辦呢?redis 採取連結串列的方式來儲存多個雜湊碰撞的鍵。也就是說,當根據 key 的雜湊值找到該列表後,如果列表的長度大於 1,那麼我們需要遍歷該連結串列來找到我們所查詢的 key。當然,一般情況下連結串列長度都為是 1,所以時間複雜度可看作 o(1)。

定址 key 的步驟

  • 當拿到一個 key 後,redis 先判斷當前庫的 0 號雜湊表是否為空,即:if (dict->ht[0].size == 0)。如果為 true 直接返回 NULL。
  • 判斷該 0 號雜湊表是否需要 rehash,因為如果在進行 rehash,那麼兩個表中者有可能儲存該 key。如果正在進行 rehash,將呼叫一次_dictRehashStep 方法,_dictRehashStep 用於對資料庫字典、以及雜湊鍵的字典進行被動 rehash,這裡不作贅述。
  • 計算雜湊表,根據當前字典與 key 進行雜湊值的計算。
  • 根據雜湊值與當前字典計算雜湊表的索引值。
  • 根據索引值在雜湊表中取出連結串列,遍歷該連結串列找到 key 的位置。一般情況,該連結串列長度為 1。
  • 當 ht[0] 查詢完了之後,再進行了次 rehash 判斷,如果未在 rehashing,則直接結束,否則對 ht[1]重複 345 步驟。

Redis 的叢集模式是如何實現的?

Redis Cluster 是 Redis 的分散式解決方案,在 Redis 3.0 版本正式推出的。

Redis Cluster 去中心化,每個節點儲存資料和整個叢集狀態,每個節點都和其他所有節點連線。

Redis Cluster 節點分配

特點:

  1. 所有的 redis 節點彼此互聯(PING-PONG 機制),內部使用二進位制協議優化傳輸速度和頻寬。
  2. 節點的 fail 是通過叢集中超過半數的節點檢測失效時才生效。
  3. 客戶端與 redis 節點直連,不需要中間proxy層。客戶端不需要連線叢集所有節點,連線叢集中任何一個可用節點即可。
  4. redis-cluster 把所有的物理節點對映到[0-16383] 雜湊槽 (hash slot)上(不一定是平均分配),cluster 負責維護 node、slot、value。
  5. Redis 叢集預分好 16384 個桶,當需要在 Redis 叢集中放置一個 key-value 時,根據 CRC16(key) mod 16384 的值,決定將一個 key 放到哪個桶中。

Redis Cluster 主從模式

Redis Cluster 為了保證資料的高可用性,加入了主從模式。

一個主節點對應一個或多個從節點,主節點提供資料存取,從節點則是從主節點拉取資料備份。當這個主節點掛掉後,就會有這個從節點選取一個來充當主節點,從而保證叢集不會掛掉。所以,在叢集建立的時候,一定要為每個主節點都新增了從節點。

Redis Sentinel

Redis Sentinel 用於管理多個 Redis 伺服器,它有三個功能:

  • 監控(Monitoring) - Sentinel 會不斷地檢查你的主伺服器和從伺服器是否運作正常。
  • 提醒(Notification) - 當被監控的某個 Redis 伺服器出現問題時, Sentinel 可以通過 API 向管理員或者其他應用程式傳送通知。
  • 自動故障遷移(Automatic failover) - 當一個主伺服器不能正常工作時, Sentinel 會開始一次自動故障遷移操作, 它會將失效主伺服器的其中一個從伺服器升級為新的主伺服器, 並讓失效主伺服器的其他從伺服器改為複製新的主伺服器;當客戶端試圖連線失效的主伺服器時, 叢集也會向客戶端返回新主伺服器的地址, 使得叢集可以使用新主伺服器代替失效伺服器。

Redis 如何實現分散式鎖?ZooKeeper 如何實現分散式鎖?比較二者優劣?

分散式鎖的三種實現:

  • 基於資料庫實現分散式鎖;
  • 基於快取(Redis 等)實現分散式鎖;
  • 基於 Zookeeper 實現分散式鎖;

Redis 實現

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

ZooKeeper 實現

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

對比

ZooKeeper 具備高可用、可重入、阻塞鎖特性,可解決失效死鎖問題。但 ZooKeeper 因為需要頻繁的建立和刪除節點,效能上不如 Redis 方式。

Redis 的持久化方式?有什麼優缺點?持久化實現原理?

RDB 快照(snapshot)

將存在於某一時刻的所有資料都寫入到硬碟中。

快照的原理

在預設情況下,Redis 將資料庫快照儲存在名字為 dump.rdb 的二進位制檔案中。你可以對 Redis 進行設定, 讓它在“N 秒內資料集至少有 M 個改動”這一條件被滿足時, 自動儲存一次資料集。你也可以通過呼叫 SAVE 或者 BGSAVE,手動讓 Redis 進行資料集儲存操作。這種持久化方式被稱為快照。

當 Redis 需要儲存 dump.rdb 檔案時, 伺服器執行以下操作:

  • Redis 建立一個子程式。
  • 子程式將資料集寫入到一個臨時快照檔案中。
  • 當子程式完成對新快照檔案的寫入時,Redis 用新快照檔案替換原來的快照檔案,並刪除舊的快照檔案。 這種工作方式使得 Redis 可以從寫時複製(copy-on-write)機制中獲益。

快照的優點

  1. 它儲存了某個時間點的資料集,非常適用於資料集的備份。
  2. 很方便傳送到另一個遠端資料中心或者亞馬遜的 S3(可能加密),非常適用於災難恢復。
  3. 快照在儲存 RDB 檔案時父程式唯一需要做的就是fork出一個子程式,接下來的工作全部由子程式來做,父程式不需要再做其他 IO 操作,所以快照持久化方式可以最大化 redis 的效能。
  4. 與 AOF 相比,在恢復大的資料集的時候,DB 方式會更快一些。

快照的缺點

  • 如果你希望在 redis 意外停止工作(例如電源中斷)的情況下丟失的資料最少的話,那麼快照不適合你。
  • 快照需要經常 fork 子程式來儲存資料集到硬碟上。當資料集比較大的時候,fork的過程是非常耗時的,可能會導致 Redis 在一些毫秒級內不能響應客戶端的請求。

AOF

AOF 持久化方式記錄每次對伺服器執行的寫操作。當伺服器重啟的時候會重新執行這些命令來恢復原始的資料。

AOF 的原理

  • Redis 建立一個子程式。
  • 子程式開始將新 AOF 檔案的內容寫入到臨時檔案。
  • 對於所有新執行的寫入命令,父程式一邊將它們累積到一個記憶體快取中,一邊將這些改動追加到現有 AOF 檔案的末尾,這樣樣即使在重寫的中途發生停機,現有的 AOF 檔案也還是安全的。
  • 當子程式完成重寫工作時,它給父程式傳送一個訊號,父程式在接收到訊號之後,將記憶體快取中的所有資料追加到新 AOF 檔案的末尾。
  • 搞定!現在 Redis 原子地用新檔案替換舊檔案,之後所有命令都會直接追加到新 AOF 檔案的末尾。

AOF的優點

  • 使用預設的每秒 fsync 策略,Redis 的效能依然很好(fsync 是由後臺執行緒進行處理的,主執行緒會盡力處理客戶端請求),一旦出現故障,使用 AOF ,你最多丟失 1 秒的資料。
  • AOF 檔案是一個只進行追加的日誌檔案,所以不需要寫入 seek,即使由於某些原因(磁碟空間已滿,寫的過程中當機等等)未執行完整的寫入命令,你也也可使用 redis-check-aof 工具修復這些問題。
  • Redis 可以在 AOF 檔案體積變得過大時,自動地在後臺對 AOF 進行重寫:重寫後的新 AOF 檔案包含了恢復當前資料集所需的最小命令集合。整個重寫操作是絕對安全的。
  • AOF 檔案有序地儲存了對資料庫執行的所有寫入操作,這些寫入操作以 Redis 協議的格式儲存。因此 AOF 檔案的內容非常容易被人讀懂,對檔案進行分析(parse)也很輕鬆。

AOF 的缺點

  • 對於相同的資料集來說,AOF 檔案的體積通常要大於 RDB 檔案的體積。
  • 根據所使用的 fsync 策略,AOF 的速度可能會慢於快照。在一般情況下,每秒 fsync 的效能依然非常高,而關閉 fsync 可以讓 AOF 的速度和快照一樣快,即使在高負荷之下也是如此。不過在處理巨大的寫入載入時,快照可以提供更有保證的最大延遲時間(latency)。

Redis 過期策略有哪些?

  • noeviction- 當記憶體使用達到閾值的時候,所有引起申請記憶體的命令會報錯。
  • allkeys-lru- 在主鍵空間中,優先移除最近未使用的 key。
  • allkeys-random- 在主鍵空間中,隨機移除某個 key。
  • volatile-lru- 在設定了過期時間的鍵空間中,優先移除最近未使用的 key。
  • volatile-random- 在設定了過期時間的鍵空間中,隨機移除某個 key。
  • volatile-ttl- 在設定了過期時間的鍵空間中,具有更早過期時間的 key 優先移除。

Redis 和 Memcached 有什麼區別?

兩者都是非關係型記憶體鍵值資料庫。有以下主要不同:

資料型別

  • Memcached 僅支援字串型別;
  • 而 Redis 支援五種不同種類的資料型別,使得它可以更靈活地解決問題。

資料持久化

  • Memcached 不支援持久化;
  • Redis 支援兩種持久化策略:RDB 快照和 AOF 日誌。

分散式

  • Memcached 不支援分散式,只能通過在客戶端使用像一致性雜湊這樣的分散式演算法來實現分散式儲存,這種方式在儲存和查詢時都需要先在客戶端計算一次資料所在的節點。
  • Redis Cluster 實現了分散式的支援。

記憶體管理機制

  • Memcached 將記憶體分割成特定長度的塊來儲存資料,以完全解決記憶體碎片的問題,但是這種方式會使得記憶體的利用率不高,例如塊的大小為 128 bytes,只儲存 100 bytes 的資料,那麼剩下的 28 bytes 就浪費掉了。
  • 在 Redis 中,並不是所有資料都一直儲存在記憶體中,可以將一些很久沒用的 value 交換到磁碟。而 Memcached 的資料則會一直在記憶體中。

為什麼單執行緒的 Redis 效能反而優於多執行緒的 Memcached?

Redis 快速的原因:

  • 絕大部分請求是純粹的記憶體操作(非常快速)
  • 採用單執行緒,避免了不必要的上下文切換和競爭條件

非阻塞 IO

  • 內部實現採用 epoll,採用了 epoll+自己實現的簡單的事件框架。epoll * 中的讀、寫、關閉、連線都轉化成了事件,然後利用 epoll 的多路複用特性,絕不在 io 上浪費一點時間。

由於篇幅原因關於分散式服務(RPC)、分散式訊息佇列(MQ)將會在下次繼續分享,喜歡的朋友可以關注一下,感謝您的支援

相關文章