java程序員進階:Redis分佈式技術問題集錦

Redis 簡介

Redis 是徹底開源免費的,遵照BSD協議,是一個高性能的key-value數據庫。node

Redis 與其餘 key - value 緩存產品有如下三個特色:redis

  • 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 有什麼數據類型?分別用於什麼場景?數據庫

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)將會在下次繼續分享,喜歡的朋友能夠關注一下,感謝您的支持

相關文章
相關標籤/搜索