1. 程式人生 > 資料庫 >後端開發面試題(五)Redis篇

後端開發面試題(五)Redis篇

文章目錄

一、概述

1.1 什麼是Redis

  Redis(Remote Dictionary Server) 是一個使用 C 語言編寫的,開源的(BSD許可)高效能非關係型(NoSQL)的鍵值對資料庫
  Redis 可以儲存鍵和五種不同型別的值之間的對映。鍵的型別只能為字串,值支援五種資料型別:字串、列表、集合、散列表、有序集合
  與傳統資料庫不同的是 Redis 的資料是存在記憶體中的,所以讀寫速度非常快,因此 redis 被廣泛應用於快取方向,每秒可以處理超過 10萬次讀寫操作,是已知效能最快的Key-Value DB

。另外,Redis 也經常用來做分散式鎖。除此之外,Redis 支援事務 、持久化、LUA指令碼、LRU驅動事件、多種叢集方案。

1.2 Redis有哪些優缺點

  Redis的優點:
   1、讀寫效能優異, Redis能讀的速度是110000次/s,寫的速度是81000次/s。
   2、支援資料持久化,支援AOF和RDB兩種持久化方式。
   3、支援事務,Redis的所有操作都是原子性的,同時Redis還支援對幾個操作合併後的原子性執行。
   4、資料結構豐富,除了支援string型別的value外還支援hash、set、zset、list等資料結構。
   5、支援主從複製,主機會自動將資料同步到從機,可以進行讀寫分離。

  
  Redis的缺點:
   1、資料庫容量受到實體記憶體的限制,不能用作海量資料的高效能讀寫,因此Redis適合的場景主要侷限在較小資料量的高效能操作和運算上。
   2、Redis 不具備自動容錯和恢復功能,主機從機的宕機都會導致前端部分讀寫請求失敗,需要等待機器重啟或者手動切換前端的IP才能恢復。
   3、主機宕機,宕機前有部分資料未能及時同步到從機,切換IP後還會引入資料不一致的問題,降低了系統的可用性。
   4、Redis較難支援線上擴容,在叢集容量達到上限時線上擴容會變得很複雜。為避免這一問題,運維人員在系統上線時必須確保有足夠的空間,這對資源造成了很大的浪費。

1.3 為什麼要用 Redis /為什麼要用快取

  主要從“高效能”和“高併發”這兩點來看待這個問題:

  • 1、高效能
      假如使用者第一次訪問資料庫中的某些資料。這個過程會比較慢,因為是從硬碟上讀取的。將該使用者訪問的資料存在數快取中,這樣下一次再訪問這些資料的時候就可以直接從快取中獲取了。操作快取就是直接操作記憶體,所以速度相當快。如果資料庫中的對應資料改變的之後,同步改變快取中相應的資料即可。
  • 2、高併發
      直接操作快取能夠承受的請求是遠遠大於直接訪問資料庫的,所以我們可以考慮把資料庫中的部分資料轉移到快取中去,這樣使用者的一部分請求會直接到快取這裡而不用經過資料庫。

1.4 為什麼要用 Redis 而不用 map/guava 做快取?

  快取分為本地快取和分散式快取。以 Java 為例,使用自帶的 map 或者 guava 實現的是本地快取,最主要的特點是輕量以及快速,生命週期隨著 jvm 的銷燬而結束,並且在多例項的情況下,每個例項都需要各自儲存一份快取,快取不具有一致性。
  此處提一下Guava,Guava是Google開源的Java庫,其中有很多庫,如:集合、快取、原生型別支援、併發庫、通用註解、字串處理、I/O。
  使用 redis 或 memcached 之類的稱為分散式快取,在多例項的情況下,各例項共用一份快取資料,快取具有一致性。缺點是需要保持 redis 或 memcached服務的高可用,整個程式架構上較為複雜。

1.4.1 Redis作為分散式快取的優點

  作為分散式快取,Redis有以下優點:
   1>redis可以獨立部署,這樣網站程式碼更新後redis快取的資料還在,本地記憶體每次網站更新都會釋放掉;
   2>資料存到redis,多個專案間可以共享快取資料,如果是本地記憶體是無法跨專案共享的;
   3>本地快取不方便檢視及修改,redis有豐富工具管理快取資料。

1.4.2 Redis和Map的比較

  • 1、Redis 可以用幾十 G 記憶體來做快取,Map 不行,一般 JVM 也就分幾個 G 資料就夠大了
  • 2、Redis 的快取可以持久化,Map 是記憶體物件,程式一重啟資料就沒了
  • 3、Redis 可以實現分散式的快取,Map 只能存在建立它的程式裡
  • 4、Redis 可以處理每秒百萬級的併發,是專業的快取服務,Map 只是一個普通的物件
  • 5、Redis 快取有過期機制,Map 本身無此功能
  • 6、Redis 有豐富的 API,Map 就簡單太多了

1.5 Redis為什麼這麼快

  • 1、完全基於記憶體,絕大部分請求是純粹的記憶體操作,非常快速。資料存在記憶體中,類似於 HashMap,HashMap 的優勢就是查詢和操作的時間複雜度都是O(1);
      像mysql這樣的成傳統關係型資料庫是索引檔案儲存來記憶體,資料檔案儲存在硬碟的,那麼硬碟的效能和瓶頸將會影響到資料庫。硬碟型資料庫工作模式:

      記憶體型資料庫工作模式:

      本身記憶體和硬碟的讀寫方式不相同導致了它們在讀寫效能上的巨大差異。
  • 2、資料結構簡單,對資料操作也簡單,Redis 中的資料結構是專門進行設計的;
  • 3、採用單執行緒,避免了不必要的上下文切換和競爭條件,也不存在多程序或者多執行緒導致的切換而消耗 CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的效能消耗;
      這裡的單執行緒指的是,Redis處理網路請求的時候只有一個執行緒,而不是整個Redis服務是單執行緒的
  • 4、使用多路 I/O 複用模型,非阻塞 IO;
       1>傳統多程序併發模型: 每監聽到一個Socket連線就會分配一個執行緒處理
       2>多路複用模型: 單個執行緒,通過記錄跟蹤每一個Socket連線的I/O的狀態來同時管理多個I/O流。
     &emsp這裡的I/O指的是網路I/O,多路指的是多個網路連線,複用指的是複用一個執行緒。Redis使用多路 I/O 複用模型的大致流程如下:
       1>在Redis中的I/O多路複用程式會監聽多個客戶端連線的Socket
       2>每當有客戶端通過Socket流向Redis傳送請求進行操作時,I/O多路複用程式會將其放入一個佇列中。
       3>同時I/O多路複用程式會同步、有序、每次傳送一個任務給處理器處理。
       4>I/O多路複用程式會在上一個請求處理完畢後再繼續分派下一個任務。
      用圖表示的話,如下:
  • 5、使用底層模型不同,它們之間底層實現方式以及與客戶端之間通訊的應用協議不一樣,Redis 直接自己構建了 VM 機制 ,因為一般的系統呼叫系統函式的話,會浪費一定的時間去移動和請求。

二、資料型別

2.1 Redis有哪些資料型別

  Redis主要有5種資料型別,包括String,List,Set,Zset,Hash,滿足大部分的使用要求:

資料型別可以儲存的值操作應用場景
STRING字串、整數或者浮點數對整個字串或者字串的其中一部分執行操作
對整數和浮點數執行自增或者自減操作
做簡單的鍵值對快取
LIST列表從兩端壓入或者彈出元素
對單個或者多個元素進行修剪,只保留一個範圍內的元素
儲存一些列表型的資料結構,類似粉絲列表、文章的評論列表之類的資料
SET無序集合新增、獲取、移除單個元素
檢查一個元素是否存在於集合中
計算交集、並集、差集
從集合裡面隨機獲取元素
交集、並集、差集的操作,比如交集,可以把兩個人的粉絲列表整一個交集
HASH包含鍵值對的無序散列表新增、獲取、移除單個鍵值對
獲取所有鍵值對
檢查某個鍵是否存在
結構化的資料,比如一個物件
ZSET有序集合新增、獲取、刪除元素
根據分值範圍或者成員來獲取元素
計算一個鍵的排名
去重但可以排序,如獲取排名前幾名的使用者

2.2 Redis的應用場景

  • 總結一
    1、計數器
      可以對 String 進行自增自減運算,從而實現計數器功能。Redis 這種記憶體型資料庫的讀寫效能非常高,很適合儲存頻繁讀寫的計數量。
    2、快取
      將熱點資料放到記憶體中,設定記憶體的最大使用量以及淘汰策略來保證快取的命中率。
    3、會話快取
      可以使用 Redis 來統一儲存多臺應用伺服器的會話資訊。當應用伺服器不再儲存使用者的會話資訊,也就不再具有狀態,一個使用者可以請求任意一個應用伺服器,從而更容易實現高可用性以及可伸縮性。
    4、全頁快取(FPC)
      除基本的會話token之外,Redis還提供很簡便的FPC平臺。以Magento為例,Magento提供一個外掛來使用Redis作為全頁快取後端。此外,對WordPress的使用者來說,Pantheon有一個非常好的外掛 wp-redis,這個外掛能幫助你以最快速度載入你曾瀏覽過的頁面。
    5、查詢表
      例如 DNS 記錄就很適合使用 Redis 進行儲存。查詢表和快取類似,也是利用了 Redis 快速的查詢特性。但是查詢表的內容不能失效,而快取的內容可以失效,因為快取不作為可靠的資料來源。
    6、訊息佇列(釋出/訂閱功能)
      List 是一個雙向連結串列,可以通過 lpush 和 rpop 寫入和讀取訊息。不過最好使用 Kafka、RabbitMQ 等訊息中介軟體。
    7、分散式鎖實現
      在分散式場景下,無法使用單機環境下的鎖來對多個節點上的程序進行同步。可以使用 Redis 自帶的 SETNX 命令實現分散式鎖,除此之外,還可以使用官方提供的 RedLock 分散式鎖實現。
    8、其它
      Set 可以實現交集、並集等操作,從而實現共同好友等功能。ZSet 可以實現有序性操作,從而實現排行榜等功能。
  • 總結二
      string——適合最簡單的k-v儲存,類似於memcached的儲存結構,簡訊驗證碼,配置資訊等,就用這種型別來儲存。
      hash——一般key為ID或者唯一標示,value對應的就是詳情了。如商品詳情,個人資訊詳情,新聞詳情等。
      list——因為list是有序的,比較適合儲存一些有序且資料相對固定的資料。如省市區表、字典表等。因為list是有序的,適合根據寫入的時間來排序,如:最新的***,訊息佇列等。
      set——可以簡單的理解為ID-List的模式,如微博中一個人有哪些好友,set最牛的地方在於,可以對兩個set提供交集、並集、差集操作。例如:查詢兩個人共同的好友等。
      Sorted Set——是set的增強版本,增加了一個score引數,自動會根據score的值進行排序。比較適合類似於top 10等不根據插入的時間來排序的資料
      如上所述,雖然Redis不像關係資料庫那麼複雜的資料結構,但是,也能適合很多場景,比一般的快取資料結構要多。瞭解每種資料結構適合的業務場景,不僅有利於提升開發效率,也能有效利用Redis的效能。
  • 總結三
      Redis在網際網路公司一般有以下應用:
        String:快取、限流、計數器、分散式鎖、分散式Session
        Hash:儲存使用者資訊、使用者主頁訪問量、組合查詢
        List:微博關注人時間軸列表、簡單佇列
        Set:贊、踩、標籤、好友關係
        Zset:排行榜
  • 總結示例
      如果現在做一個秒殺,那麼,Redis應該如何結合進行使用?
       1>提前預熱資料,放入Redis
       2>商品列表放入Redis List
       3>商品的詳情資料 Redis hash儲存,設定過期時間
       4>商品的庫存資料Redis sorted set儲存
       5>使用者的地址資訊Redis set儲存
       6>訂單產生扣庫存通過Redis製造分散式鎖,庫存同步扣除
       7>訂單產生後發貨的資料,產生Redis list,通過訊息佇列處理
       8>秒殺結束後,再把Redis資料和資料庫進行同步
      以上只是一個簡略的秒殺系統和Redis結合的方案。

三、持久化

3.1 什麼是Redis持久化?

  持久化就是把記憶體的資料寫到磁碟中去,防止服務宕機了記憶體資料丟失。

3.2 Redis 的持久化機制是什麼?各自的優缺點?

  Redis 提供兩種持久化機制 RDB(預設) 和 AOF 機制。

3.2.1 RDB

  RDB:是Redis DataBase縮寫快照,RDB是Redis預設的持久化方式。按照一定的時間將記憶體的資料以快照的形式儲存到硬碟中,對應產生的資料檔案為dump.rdb(二進位制檔案)。通過配置檔案中的save引數來定義快照的週期。

  當 Redis 需要儲存 dump.rdb 檔案時, 伺服器執行以下操作:
   1、Redis 呼叫forks。同時擁有父程序和子程序。
   2、子程序將資料集寫入到一個臨時 RDB 檔案中。
   3、當子程序完成對新 RDB 檔案的寫入時,Redis 用新 RDB 檔案替換原來的 RDB 檔案,並刪除舊的 RDB 檔案。
  RDB的三種主要觸發機制:

  • 1、save命令(同步資料到磁碟上)
      由於 save 命令是同步命令,會佔用Redis的主程序。若Redis資料非常多時,save命令執行速度會非常慢,阻塞所有客戶端的請求。因此很少在生產環境直接使用SAVE 命令,可以使用BGSAVE 命令代替。如果在BGSAVE命令的儲存資料的子程序發生錯誤的時,用 SAVE命令儲存最新的資料是最後的手段。
  • 2、bgsave命令(非同步儲存資料到磁碟上)
      Redis使用Linux系統的fock()生成一個子程序來將DB資料儲存到磁碟,主程序繼續提供服務以供客戶端呼叫。如果操作成功,可以通過客戶端命令LASTSAVE來檢查操作結果。
  • 3、自動生成RDB
      除了手動執行 save 和 bgsave 命令實現RDB持久化以外,Redis還提供了自動自動生成RDB的方式。
      你可以通過配置檔案對 Redis 進行設定, 讓它在“ N 秒內資料集至少有 M 個改動”這一條件被滿足時, 自動進行資料集儲存操作。

  RDB的優點
   1、只有一個檔案 dump.rdb,方便持久化。
   2、容災性好,一個檔案可以儲存到安全的磁碟。
   3、效能最大化,fork 子程序來完成寫操作,讓主程序繼續處理命令,所以是 IO 最大化。使用單獨子程序來進行持久化,主程序不會進行任何 IO 操作,保證了 redis 的高效能。
   4.相對於資料集大時,比 AOF 的啟動效率更高。
  RDB的缺點:資料安全性低。RDB 是間隔一段時間進行持久化,如果持久化之間 redis 發生故障,會發生資料丟失。所以這種方式更適合資料要求不嚴謹的時候)。

3.2.2 AOF

  AOF持久化(即Append Only File持久化),則是將Redis執行的每次寫命令記錄到單獨的日誌檔案中,當重啟Redis會重新將持久化的日誌中檔案恢復資料。
  當兩種方式同時開啟時,資料恢復Redis會優先選擇AOF恢復。

  AOF持久化的三種策略

  • 1、always
      每次有新命令追加到 AOF 檔案時就執行一次 fsync :非常慢,也非常安全。
  • 2、everysec
      每秒 fsync 一次:足夠快(和使用 RDB 持久化差不多),並且在故障時只會丟失 1 秒鐘的資料。推薦(並且也是預設)的措施為每秒 fsync 一次, 這種 fsync 策略可以兼顧速度和安全性。
  • 3、no
      從不 fsync :將資料交給作業系統來處理,由作業系統來決定什麼時候同步資料。更快,也更不安全的選擇。

  AOF的優點
   1、資料安全,aof 持久化可以配置 appendfsync 屬性,如:always,每進行一次 命令操作就記錄到 aof 檔案中一次。
   2、通過 append 模式寫檔案,即使中途伺服器宕機,可以通過 redis-check-aof 工具解決資料一致性問題。
   3、AOF 機制的 rewrite 模式。AOF 檔案沒被 rewrite 之前(檔案過大時會對命令 進行合併重寫),可以刪除其中的某些命令(比如誤操作的 flushall))
  AOF的缺點
   1、AOF 檔案比 RDB 檔案大,且恢復速度慢。
   2、資料集大的時候,比 rdb 啟動效率低。

3.2.3 兩者的簡單對比

3.3 如何選擇合適的持久化方式

  一般來說, 如果想達到足以媲美PostgreSQL的資料安全性,你應該同時使用兩種持久化功能。在這種情況下,當 Redis 重啟的時候會優先載入AOF檔案來恢復原始的資料,因為在通常情況下AOF檔案儲存的資料集要比RDB檔案儲存的資料集要完整。
  如果你非常關心你的資料, 但仍然可以承受數分鐘以內的資料丟失,那麼你可以只使用RDB持久化。
  有很多使用者都只使用AOF持久化,但並不推薦這種方式,因為定時生成RDB快照(snapshot)非常便於進行資料庫備份, 並且 RDB 恢復資料集的速度也要比AOF恢復的速度要快,除此之外,使用RDB還可以避免AOF程式的bug。
  如果你只希望你的資料在伺服器執行的時候存在,你也可以不使用任何持久化方式。

3.4 Redis持久化資料和快取怎麼做擴容?

  如果Redis被當做快取使用,使用一致性雜湊實現動態擴容縮容。
  如果Redis被當做一個持久化儲存使用,必須使用固定的keys-to-nodes對映關係,節點的數量一旦確定不能變化。否則的話(即Redis節點需要動態變化的情況),必須使用可以在執行時進行資料再平衡的一套系統,而當前只有Redis叢集可以做到這樣。

3.4.1 一致性雜湊原理

  此處簡單提一下一致性雜湊,一致性Hash演算法是對 2^32 取模,簡單來說,一致性Hash演算法將整個雜湊值空間組織成一個虛擬的圓環,如假設某雜湊函式H的值空間為0-2 ^ 32-1(即雜湊值是一個32位無符號整形),整個雜湊環如下,從 0 ~ 2 ^ 32-1 代表的分別是一個個的節點,這個環也叫雜湊環:

  然後我們將我們的節點進行一次雜湊,按照一定的規則,比如按照 ip 地址的雜湊值,讓節點落在雜湊環上。比如此時我們可能得到了如下圖的環:

  然後就是需要通過資料 key 找到對應的伺服器然後儲存了,我們約定,通過資料 key 的雜湊值落在雜湊環上的節點,如果命中了機器節點就落在這個機器上,否則落在順時針直到碰到第一個機器。如下圖所示 : A 的雜湊值落在了 D2 節點的前面,往下找落在了 D2 機器上,D的雜湊值 在 D1 節點的前面,往下找到了 D1 機器,B的雜湊值剛好落在了D1 節點上,依次~~~

  一致性雜湊主要就是解決當機器減少或增加的時候,大面積的資料重新雜湊的問題,主要從下面 2 個方向去考慮的,當節點宕機時,資料記錄會被定位到下一個節點上,當新增節點的時候 ,相關區間內的資料記錄就需要重新雜湊。

3.4.2 一致性雜湊的問題及解決

  一致性Hash演算法在服務節點太少時,容易因為節點分部不均勻而造成資料傾斜(被快取的物件大部分集中快取在某一臺伺服器上)問題。比如只有 2 臺機器,這 2 臺機器離的很近,那麼順時針第一個機器節點上將存在大量的資料,第二個機器節點上資料會很少。如下圖所示,D0 機器承載了絕大多數的資料:

  為了避免出現數據傾斜問題,一致性 Hash 演算法引入了虛擬節點的機制,也就是每個機器節點會進行多次雜湊,最終每個機器節點在雜湊環上會有多個虛擬節點存在,使用這種方式來大大削弱甚至避免資料傾斜問題。同時資料定位演算法不變,只是多了一步虛擬節點到實際節點的對映,例如定位到“D1#1”、“D1#2”、“D1#3”三個虛擬節點的資料均定位到 D1 上。這樣就解決了服務節點少時資料傾斜的問題。在實際應用中,通常將虛擬節點數設定為32甚至更大,因此即使很少的服務節點也能做到相對均勻的資料分佈。這也是 Dubbo 負載均衡中有一種一致性雜湊負載均衡的實現思想。

四、過期鍵的刪除策略

4.1 Redis的過期鍵的刪除策略

  Redis是key-value資料庫,我們可以設定Redis中快取的key的過期時間。Redis的過期策略就是指當Redis中快取的key過期了,Redis如何處理。過期策略通常有以下三種:定時過期、惰性過期和定期過期。

  • 1、定時刪除:在設定鍵的過期時間的同時,建立一個定時器(timer),讓定時器在鍵的過期時間來臨時,立即執行對鍵的刪除操作;
  • 2、惰性刪除:放任鍵過期不管,但是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過期,如果過期的話,就刪除該鍵;如果沒有過期,那就返回該鍵;
  • 3、定期刪除:每隔一段時間,程式就對資料庫進行一次檢查,刪除裡面的過期鍵。至於刪除多少過期鍵,以及要檢查多少個數據庫,則由演算法決定。

  下面我們來看看三種策略的優缺比較:
   定時刪除策略對記憶體是最友好的:通過使用定時器,定時刪除策略可以保證過期鍵會盡可能快地被刪除,並釋放過期鍵所佔用的記憶體;但另一方面,定時刪除策略的缺點是,他對CPU是最不友好的:在過期鍵比較多的情況下,刪除過期鍵這一行為可能會佔用相當一部分CPU時間,在記憶體不緊張但是CPU時間非常緊張的情況下,將CPU時間用在刪除和當前任務無關的過期鍵上,無疑會對伺服器的響應時間和吞吐量造成影響;
   惰性刪除策略對CPU時間來說是最友好的:程式只會在取出鍵時才對鍵進行過期檢查,這可以保證刪除過期鍵的操作只會在非做不可的情況下進行;惰性刪除策略的缺點是,它對記憶體是最不友好的:如果一個鍵已經過期,而這個鍵又仍然保留在資料庫中,那麼只要這個過期鍵不被刪除,它所佔用的記憶體就不會釋放;
   定時刪除佔用太多CPU時間,影響伺服器的響應時間和吞吐量;惰性刪除浪費太多記憶體,有記憶體洩漏的危險。定期刪除策略是前兩種策略的一種整合和折中:
   定期刪除策略每隔一段時間執行一次刪除過期鍵操作,並通過限制刪除操作執行的時長和頻率來減少刪除操作對CPU時間的影響;通過定期刪除過期鍵,定期刪除策略有效地減少了因為過期鍵而帶來的記憶體浪費;定期刪除策略的難點是確定刪除操作執行的時長和頻率。
  Redis中同時使用了惰性過期和定期過期兩種過期策略。

4.2 Redis key的過期時間和永久有效分別怎麼設定?

  EXPIRE和PERSIST命令。

4.3 對過期的資料怎麼處理?

  在 redis 中,對於已經過期的資料,Redis 採用兩種策略來處理這些資料,分別是惰性刪除和定期刪除。

  • 1、惰性刪除
      惰性刪除不會去主動刪除資料,而是在訪問資料的時候,再檢查當前鍵值是否過期,如果過期則執行刪除並返回 null 給客戶端,如果沒有過期則返回正常資訊給客戶端。
      它的優點是簡單,不需要對過期的資料做額外的處理,只有在每次訪問的時候才會檢查鍵值是否過期,缺點是刪除過期鍵不及時,造成了一定的空間浪費。
  • 2、定期刪除
      Redis會週期性的隨機測試一批設定了過期時間的key並進行處理。測試到的已過期的key將被刪除。
      具體的演算法如下:
       1>Redis配置項hz定義了serverCron任務的執行週期,預設為10,代表了每秒執行10次;
       2>每次過期key清理的時間不超過CPU時間的25%,比如hz預設為10,則一次清理時間最大為25ms;
       3>清理時依次遍歷所有的db;
       4>從db中隨機取20個key,判斷是否過期,若過期,則逐出;
       5>若有5個以上key過期,則重複步驟4,否則遍歷下一個db;
       6>在清理過程中,若達到了25%CPU時間,退出清理過程;
      雖然redis的確是不斷的刪除一些過期資料,但是很多沒有設定過期時間的資料也會越來越多,那麼redis記憶體不夠用的時候是怎麼處理的呢?這裡我們就會談到淘汰策略。

4.3.1 Redis記憶體淘汰策略

  當redis的記憶體超過最大允許的記憶體之後,Redis會觸發記憶體淘汰策略,刪除一些不常用的資料,以保證redis伺服器的正常執行。
  在redis 4.0以前,redis的記憶體淘汰策略有以下6種:
   noeviction:當記憶體使用超過配置的時候會返回錯誤,不會驅逐任何鍵
   allkeys-lru:加入鍵的時候,如果過限,首先通過LRU演算法驅逐最久沒有使用的鍵
   volatile-lru:加入鍵的時候如果過限,首先從設定了過期時間的鍵集合中驅逐最久沒有使用的鍵
   allkeys-random:加入鍵的時候如果過限,從所有key隨機刪除
volatile-random:加入鍵的時候如果過限,從過期鍵的集合中隨機驅逐
   volatile-ttl:從配置了過期時間的鍵中驅逐馬上就要過期的鍵
  在redis 4.0以後,又增加了以下兩種:
   volatile-lfu:從所有配置了過期時間的鍵中驅逐使用頻率最少的鍵
   allkeys-lfu:從所有鍵中驅逐使用頻率最少的鍵
  記憶體淘汰策略可以通過配置檔案來修改,redis.conf對應的配置項是maxmemory-policy 修改對應的值就行,預設是noeviction。

五、記憶體相關

5.1 MySQL裡有2000w資料,redis中只存20w的資料,如何保證redis中的資料都是熱點資料

  要考慮兩方面內容:1.Redis的記憶體淘汰策略。2.Redis的最大記憶體設定。
  思路:首先計算出20w資料所需的記憶體空間,設定最大記憶體,然後選擇合適的記憶體淘汰策略。當redis記憶體資料集大小上升到一定大小的時候,就會施行資料淘汰策略。
  記憶體淘汰策略有八種,上個小節已介紹。
  至於最大記憶體設定,在redis.conf配置檔案中,可以對最大記憶體進行設定,單位為bytes:當使用的記憶體到達指定的限制時,Redis會根據記憶體淘汰策略刪除鍵,以釋放空間。

5.2 Redis的記憶體淘汰策略有哪些

  Redis的記憶體淘汰策略是指在Redis的用於快取的記憶體不足時,怎麼處理需要新寫入且需要申請額外空間的資料。
全域性的鍵空間選擇性移除

  • noeviction:當記憶體不足以容納新寫入資料時,新寫入操作會報錯。
  • allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的key。(這個是最常用的)
  • allkeys-random:當記憶體不足以容納新寫入資料時,在鍵空間中,隨機移除某個key。

設定過期時間的鍵空間選擇性移除

  • volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的key。
  • volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個key。
  • volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的key優先移除。

  Redis的記憶體淘汰策略的選取並不會影響過期的key的處理。記憶體淘汰策略用於處理記憶體不足時的需要申請額外空間的資料;過期策略用於處理過期的快取資料。

5.3 Redis主要消耗什麼物理資源?

  記憶體。redis的資料都是儲存在記憶體當中。記憶體資料庫相比一般的關係型資料庫,讀取速度要更快,但是消耗的記憶體資源會更多。

5.4 Redis的記憶體用完了會發生什麼?

  這個跟 Redis 的記憶體回收策略有關。Redis 的預設回收策略是 noenviction,當記憶體用完之後,寫資料會報錯。或者可以配置記憶體淘汰機制,當Redis達到記憶體上限時會沖刷掉舊的內容。

5.5 Redis如何做記憶體優化?

  • 1、縮減鍵值物件
      縮減鍵(key)和值(value)的長度:
       key長度:如在設計鍵時,在完整描述業務情況下,鍵值越短越好。
       value長度:值物件縮減比較複雜,常見需求是把業務物件序列化成二進位制陣列放入Redis。首先應該在業務上精簡業務物件,去掉不必要的屬性避免儲存無效資料。其次在序列化工具選擇上,應該選擇更高效的序列化工具來降低位元組陣列大小。以JAVA為例,內建的序列化方式無論從速度還是壓縮比都不盡如人意,這時可以選擇更高效的序列化工具,如: protostuff,kryo等,下圖是JAVA常見序列化工具空間壓縮對比。
  • 2、共享物件池
      物件共享池指Redis內部維護[0-9999]的整數物件池。建立大量的整數型別redisObject存在記憶體開銷,每個redisObject內部結構至少佔16位元組,甚至超過了整數自身空間消耗。所以Redis記憶體維護一個[0-9999]的整數物件池,用於節約記憶體。 除了整數值物件,其他型別如list,hash,set,zset內部元素也可以使用整數物件池。因此開發中在滿足需求的前提下,儘量使用整數物件以節省記憶體。
  • 3、字串優化
  • 4、編碼優化
  • 5、控制key的數量

六、執行緒模型

6.1 Redis執行緒模型

6.1.1 Redis執行緒模型特點

  • 1、Redis基於Reactor模式開發了網路事件處理器,這個處理器被稱為檔案事件處理器(file event handler)。它的組成結構為4部分:多個套接字、IO多路複用程式、檔案事件分派器、事件處理器。因為檔案事件分派器佇列的消費是單執行緒的,所以Redis才叫單執行緒模型
  • 2、檔案事件處理器使用 I/O 多路複用(multiplexing)程式來同時監聽多個套接字, 並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。
  • 3、當被監聽的套接字準備好執行連線應答(accept)、讀取(read)、寫入(write)、關閉(close)等操作時, 與操作相對應的檔案事件就會產生, 這時檔案事件處理器就會呼叫套接字之前關聯好的事件處理器來處理這些事件。
  • 4、雖然檔案事件處理器以單執行緒方式執行, 但通過使用 I/O 多路複用程式來監聽多個套接字, 檔案事件處理器既實現了高效能的網路通訊模型, 又可以很好地與 redis 伺服器中其他同樣以單執行緒方式執行的模組進行對接, 這保持了 Redis 內部單執行緒設計的簡單性。

6.1.2 Redis執行緒模型組成

6.1.3 Redis的處理流程


  Redis客戶端對服務端的每次呼叫都經歷了傳送命令,執行命令,返回結果三個過程。其中執行命令階段,由於Redis是單執行緒來處理命令的,所有每一條到達服務端的命令不會立刻執行,所有的命令都會進入一個佇列中,然後逐個被執行。並且多個客戶端傳送的命令的執行順序是不確定的。但是可以確定的是不會有兩條命令被同時執行,不會產生併發問題,這就是Redis的單執行緒基本模型。

6.1.4 Redis是單執行緒模型為什麼效率還這麼高

  • 1、純記憶體訪問:資料存放在記憶體中,記憶體的響應時間大約是100納秒,這是Redis每秒萬億級別訪問的重要基礎。
  • 2、非阻塞I/O:Redis採用epoll做為I/O多路複用技術的實現,再加上Redis自身的事件處理模型將epoll中的連線,讀寫,關閉都轉換為了時間,不在I/O上浪費過多的時間。
  • 3、單執行緒避免了執行緒切換和競態產生的消耗。
  • 4、Redis採用單執行緒模型,每條命令執行如果佔用大量時間,會造成其他執行緒阻塞,對於Redis這種高效能服務是致命的,所以Redis是面向高速執行的資料庫。

七、事務

7.1 Redis事務的概念

  Redis 事務的本質是通過MULTI、EXEC、WATCH等一組命令的集合。事務支援一次執行多個命令,一個事務中所有命令都會被序列化。在事務執行過程,會按照順序序列化執行佇列中的命令,其他客戶端提交的命令請求不會插入到事務執行命令序列中。
  總結說:redis事務就是一次性、順序性、排他性的執行一個佇列中的一系列命令
  Redis 事務可以一次執行多個命令, 並且帶有以下三個重要的保證:

  • 批量操作在傳送 EXEC 命令前被放入佇列快取。
  • 收到 EXEC 命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行。
  • 在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。

7.2 Redis事務的三個階段

  • 1、事務開始 MULTI
  • 2、命令入隊
  • 3、事務執行 EXEC

  事務執行過程中,如果服務端收到有EXEC、DISCARD、WATCH、MULTI之外的請求,將會把請求放入佇列中排隊。

7.3 Redis事務相關命令

  Redis事務功能是通過MULTI、EXEC、DISCARD和WATCH 四個原語實現的。事務功能有以下特點:
   1、Redis會將一個事務中的所有命令序列化,然後按順序執行。
   2、redis 不支援回滾,“Redis 在事務失敗時不進行回滾,而是繼續執行餘下的命令”, 所以 Redis 的內部可以保持簡單且快速。
   3、如果在一個事務中的命令出現錯誤,那麼所有的命令都不會執行;
   4、如果在一個事務中出現執行錯誤,那麼正確的命令會被執行。
  Redis中的命令如下:

  • WATCH :WATCH命令是一個樂觀鎖,可以為 Redis 事務提供 check-and-set (CAS)行為。其功能是:可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行,監控一直持續到EXEC命令。
  • MULTI:MULTI命令用於開啟一個事務,它總是返回OK。 MULTI執行之後,客戶端可以繼續向伺服器傳送任意多條命令,這些命令不會立即被執行,而是被放到一個佇列中,當EXEC命令被呼叫時,所有佇列中的命令才會被執行。
  • EXEC:執行所有事務塊內的命令。返回事務塊內所有命令的返回值,按命令執行的先後順序排列。 當操作被打斷時,返回空值 nil 。
  • DISCARD:通過呼叫DISCARD,客戶端可以清空事務佇列,並放棄執行事務, 並且客戶端會從事務狀態中退出。
  • UNWATCH:UNWATCH命令可以取消watch對所有key的監控。

7.4 事務管理

 1、一致性(Consistency)
  事務前後資料的完整性必須保持一致。
 2、隔離性(Isolation)
  多個事務併發執行時,一個事務的執行不應影響其他事務的執行。

  Redis的事務總是具有一致性和隔離性,當伺服器執行在AOF持久化模式下,並且appendfsync選項的值為always時,事務也具有永續性(永續性是指一個事務一旦被提交,它對資料庫中資料的改變就是永久性的,接下來即使資料庫發生故障也不應該對其有任何影響)。

7.5 Redis事務支援隔離性嗎

  Redis 是單程序程式,並且它保證在執行事務時,不會對事務進行中斷,事務可以執行直到執行完所有事務佇列中的命令為止。因此,Redis 的事務是總是帶有隔離性的。

7.6 Redis事務保證原子性嗎,支援回滾嗎

  Redis中,單條命令是原子性執行的,但事務不保證原子性,且沒有回滾。事務中任意命令執行失敗,其餘的命令仍會被執行。
  如果想實現回滾,就需要用WATCH命令,具體的做法是:需要在MULTI之前使用WATCH來監控某些鍵值對,然後使用MULTI命令來開啟事務,執行對資料結構操作的各種命令,此時這些命令入佇列。
  當使用EXEC執行事務時,首先會比對WATCH所監控的鍵值對,如果沒發生改變,它會執行事務佇列中的命令,提交事務;如果發生變化,將不會執行事務中的任何命令,同時事務回滾。當然無論是否回滾,Redis都會取消執行事務前的WATCH命令。

7.7 Redis事務其他實現

  基於Lua指令碼,Redis可以保證指令碼內的命令一次性、按順序地執行,其同時也不提供事務執行錯誤的回滾,執行過程中如果部分命令執行錯誤,剩下的命令還是會繼續執行完。
  基於中間標記變數,通過另外的標記變數來標識事務是否執行完成,讀取資料時先讀取該標記變數判斷是否事務執行完成。但這樣會需要額外寫程式碼實現,比較繁瑣。

八、叢集方案

8.1 哨兵模式

8.1.1 哨兵模式簡介

  sentinel,中文名是哨兵。哨兵是 redis 叢集機構中非常重要的一個元件,主要有以下功能:

  • 1、叢集監控:負責監控 redis master 和 slave 程序是否正常工作。
  • 2、訊息通知:當被監控的某個 Redis出現問題時, 哨兵(sentinel) 可以通過 API 向管理員或者其他應用程式傳送通知。
  • 3、故障轉移:當一個Master不能正常工作時,哨兵(sentinel) 會開始一次自動故障遷移操作,它會將失效Master的其中一個Slave升級為新的Master, 並讓失效Master的其他Slave改為複製新的Master; 當客戶端試圖連線失效的Master時,叢集也會向客戶端返回新Master的地址,使得叢集可以使用Master代替失效Master。

  哨兵用於實現 redis 叢集的高可用,本身也是分散式的,作為一個哨兵叢集去執行,互相協同工作。

  • 故障轉移時,判斷一個 master node 是否宕機了,需要大部分的哨兵都同意才行,涉及到了分散式選舉的問題。
  • 即使部分哨兵節點掛掉了,哨兵叢集還是能正常工作的。

  Sentinel的工作方式:
   1):每個Sentinel以每秒鐘一次的頻率向它所知的Master,Slave以及其他 Sentinel 例項傳送一個 PING 命令。
   2):如果一個例項(instance)距離最後一次有效回覆 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個例項會被 Sentinel 標記為主觀下線。
   3):如果一個Master被標記為主觀下線,則正在監視這個Master的所有 Sentinel 要以每秒一次的頻率確認Master的確進入了主觀下線狀態。
   4):當有足夠數量的 Sentinel(大於等於配置檔案指定的值)在指定的時間範圍內確認Master的確進入了主觀下線狀態, 則Master會被標記為客觀下線 。
   5):在一般情況下, 每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有Master,Slave傳送 INFO 命令 。
   6):當Master被 Sentinel 標記為客觀下線時,Sentinel 向下線的 Master 的所有 Slave 傳送 INFO 命令的頻率會從 10 秒一次改為每秒一次 。
   7):若沒有足夠數量的 Sentinel 同意 Master 已經下線, Master 的客觀下線狀態就會被移除。 若 Master 重新向 Sentinel 的 PING 命令返回有效回覆, Master 的主觀下線狀態就會被移除。

  哨兵的核心知識:

  • 哨兵至少需要 3 個例項,來保證自己的健壯性。
  • 哨兵 + redis 主從的部署架構,是不保證資料零丟失的,只能保證 redis 叢集的高可用性。
  • 對於哨兵 + redis 主從這種複雜的部署架構,儘量在測試環境和生產環境,都進行充足的測試和演練。

8.1.2 哨兵模式中的細節

  • 1、主從複製
      sentinel可以讓redis實現主從複製,當一個叢集中的master失效之後,sentinel可以選舉出一個新的master用於自動接替master的工作,叢集中的其他redis伺服器自動指向新的master同步資料。一般建議sentinel採取奇數臺,防止某一臺sentinel無法連線到master導致誤切換。其結構如下:
  • 2、三個定時任務
      sentinel在內部有3個定時任務:
       1)每10秒每個sentinel會對master和slave執行info命令,這個任務達到兩個目的:
        a>發現slave節點
        b>確認主從關係
       2)每2秒每個sentinel通過master節點的channel交換資訊(pub/sub)。master節點上有一個釋出訂閱的頻道(sentinel:hello)。sentinel節點通過__sentinel__:hello頻道進行資訊交換(對節點的"看法"和自身的資訊),達成共識。
       3)每1秒每個sentinel對其他sentinel和redis節點執行ping操作(相互監控),這個其實是一個心跳檢測,是失敗判定的依據。
  • 3、主觀下線
      所謂主觀下線(Subjectively Down, 簡稱 SDOWN)指的是單個Sentinel例項對伺服器做出的下線判斷,即單個sentinel認為某個服務下線(有可能是接收不到訂閱,之間的網路不通等等原因)。
      主觀下線就是說如果伺服器在down-after-milliseconds給定的毫秒數之內, 沒有返回 Sentinel 傳送的 PING 命令的回覆, 或者返回一個錯誤, 那麼 Sentinel 將這個伺服器標記為主觀下線(SDOWN )。
      sentinel會以每秒一次的頻率向所有與其建立了命令連線的例項(master,從服務,其他sentinel)發ping命令,通過判斷ping回覆是有效回覆,還是無效回覆來判斷例項時候線上(對該sentinel來說是“主觀線上”)。
  • 4、客觀下線
      客觀下線(Objectively Down, 簡稱 ODOWN)指的是多個 Sentinel 例項在對同一個伺服器做出 SDOWN 判斷, 並且通過 SENTINEL is-master-down-by-addr 命令互相交流之後, 得出的伺服器下線判斷,然後開啟failover。
      客觀下線就是說只有在足夠數量的 Sentinel 都將一個伺服器標記為主觀下線之後, 伺服器才會被標記為客觀下線(ODOWN)。
    只有當master被認定為客觀下線時,才會發生故障遷移。
      當sentinel監視的某個服務主觀下線後,sentinel會詢問其它監視該服務的sentinel,看它們是否也認為該服務主觀下線,接收到足夠數量(這個值可以配置)的sentinel判斷為主觀下線,即認為該服務客觀下線,並對其做故障轉移操作。

8.2 官方Redis Cluster 方案

  Redis Cluster是Redis官方提供的Redis叢集功能。

8.2.1 為什麼要實現Redis Cluster

  1. 主從複製不能實現高可用
  2. 隨著公司發展,使用者數量增多,併發越來越多,業務需要更高的QPS(每秒查詢率),而主從複製中單機的QPS可能無法滿足業務需求
  3. 資料量的考慮,現有伺服器記憶體不能滿足業務資料的需要時,單純向伺服器新增記憶體不能達到要求,此時需要考慮分散式需求,把資料分佈到不同伺服器上
  4. 網路流量需求:業務的流量已經超過伺服器的網絡卡的上限值,可以考慮使用分散式來進行分流
  5. 離線計算,需要中間環節緩衝等別的需求

8.2.2 資料分佈

  全量資料,單機Redis節點無法滿足要求,所以需要按照分割槽規則把資料分到若干個子集當中。
  常用的資料分佈方式如下:

  • 1、順序分佈
      比如:1到100個數字,要儲存在3個節點上,按照順序分割槽,把資料平均分配三個節點上:1號到33號資料儲存到節點1上,34號到66號資料儲存到節點2上,67號到100號資料儲存到節點3上。順序分割槽常用在關係型資料庫的設計。
  • 2、雜湊分佈
      例如1到100個數字,對每個數字進行雜湊運算,然後對每個數的雜湊結果除以節點數進行取餘,餘數為1則儲存在第1個節點上,餘數為2則儲存在第2個節點上,餘數為0則儲存在第3個節點,這樣可以保證資料被打散,同時保證資料分佈的比較均勻。雜湊分佈方式又可以分為三個分割槽方式:
      1)節點取餘分割槽
       比如有100個數據,對每個資料進行hash運算之後,與節點數進行取餘運算,根據餘數不同儲存在不同的節點上。
       節點取餘方式是非常簡單的一種分割槽方式,但是設計到資料遷移時,可能工作量較大。
       節點取餘分割槽的優點:1>客戶端分片;2>配置簡單:對資料進行雜湊,然後取餘。
       節點取餘分割槽的缺點:1>資料節點伸縮時,導致資料遷移;2>遷移數量和新增節點資料有關,建議翻倍擴容。
      2)一致性雜湊分割槽
       前面已介紹,不再贅述。
      3)虛擬槽分割槽
       虛擬槽分割槽是Redis Cluster採用的分割槽方式。預設虛擬槽,每個槽就相當於一個數字,有一定範圍。每個槽對映一個數據子集,一般比節點數大。Redis Cluster中預設虛擬槽的範圍為0到16383。

       虛擬槽分割槽步驟:
        1.把16384槽按照節點數量進行平均分配,由節點進行管理
        2.對每個key按照CRC16規則進行hash運算
        3.把hash結果對16383進行取餘
        4.把餘數傳送給Redis節點
        5.節點接收到資料,驗證是否在自己管理的槽編號的範圍
          如果在自己管理的槽編號範圍內,則把資料儲存到資料槽中,然後返回執行結果;
          如果在自己管理的槽編號範圍外,則會把資料傳送給正確的節點,由正確的節點來把資料儲存在對應的槽中;
       需要注意的是:Redis Cluster的節點之間會共享訊息,每個節點都會知道是哪個節點負責哪個範圍內的資料槽。
       虛擬槽分佈方式中,由於每個節點管理一部分資料槽,資料儲存到資料槽中。當節點擴容或者縮容時,對資料槽進行重新分配遷移即可,資料不會丟失。
       虛擬槽分割槽特點:
        使用服務端管理節點,槽,資料:例如Redis Cluster
        可以對資料打散,又可以保證資料分佈均勻

  順序分佈與雜湊分佈的對比:

8.3 基於客戶端分配


  Redis Sharding是Redis Cluster出來之前,業界普遍使用的多Redis例項叢集方法。其主要思想是採用雜湊演算法將Redis資料的key進行雜湊,通過hash函式,特定的key會對映到特定的Redis節點上。Java redis客戶端驅動jedis,支援Redis Sharding功能,即ShardedJedis以及結合快取池的ShardedJedisPool。
  Redis Sharding的優點:優勢在於非常簡單,服務端的Redis例項彼此獨立,相互無關聯,每個Redis例項像單伺服器一樣執行,非常容易線性擴充套件,系統的靈活性很強。
  Redis Sharding的缺點:

  • 由於sharding處理放到客戶端,規模進一步擴大時給運維帶來挑戰。
  • 客戶端sharding不支援動態增刪節點。服務端Redis例項群拓撲結構有變化時,每個客戶端都需要更新調整。連線不能共享,當應用規模增大時,資源浪費制約優化 。

8.4 基於代理伺服器分片


  客戶端傳送請求到一個代理元件,代理解析客戶端的資料,並將請求轉發至正確的節點,最後將結果回覆給客戶端。
  特徵:

  • 透明接入,業務程式不用關心後端Redis例項,切換成本低
  • Proxy 的邏輯和儲存的邏輯是隔離的
  • 代理層多了一次轉發,效能有所損耗

8.5 Redis 主從架構

8.5.1 簡單介紹

  單機的 redis,能夠承載的 QPS 大概就在上萬到幾萬不等。對於快取來說,一般都是用來支撐讀高併發的。因此架構做成主從(master-slave)架構,一主多從,主負責寫,並且將資料複製到其它的 slave 節點,從節點負責讀。所有的讀請求全部走從節點。這樣也可以很輕鬆實現水平擴容,支撐讀高併發。

  redis replication -> 主從架構 -> 讀寫分離 -> 水平擴容支撐讀高併發。

8.5.2 redis replication 的核心機制

  • redis 採用非同步方式複製資料到 slave 節點,不過 redis2.8 開始,slave node 會週期性地確認自己每次複製的資料量;
  • 一個 master node 是可以配置多個 slave node 的;
  • slave node 也可以連線其他的 slave node;
  • slave node 做複製的時候,不會 block master node 的正常工作;
  • slave node 在做複製的時候,也不會 block 對自己的查詢操作,它會用舊的資料集來提供服務;但是複製完成的時候,需要刪除舊資料集,載入新資料集,這個時候就會暫停對外服務了;
  • slave node 主要用來進行橫向擴容,做讀寫分離,擴容的 slave node 可以提高讀的吞吐量。
      
      注意,如果採用了主從架構,那麼建議必須開啟 master node 的持久化,不建議用 slave node 作為 master node 的資料熱備,因為那樣的話,如果你關掉 master 的持久化,可能在 master 宕機重啟的時候資料是空的,然後可能一經過複製, slave node 的資料也丟了。
      另外,master 的各種備份方案,也需要做。萬一本地的所有檔案丟失了,從備份中挑選一份 rdb 去恢復 master,這樣才能確保啟動的時候,是有資料的,即使採用了後續講解的高可用機制,slave node 可以自動接管 master node,但也可能 sentinel 還沒檢測到 master failure,master node 就自動重啟了,還是可能導致上面所有的 slave node 資料被清空。

8.5.3 redis 主從複製的核心原理

  當啟動一個 slave node 的時候,它會發送一個 PSYNC 命令給 master node。
  如果這是 slave node 初次連線到 master node,那麼會觸發一次 full resynchronization 全量複製。此時 master 會啟動一個後臺執行緒,開始生成一份 RDB 快照檔案,同時還會將從客戶端 client 新收到的所有寫命令快取在記憶體中。RDB 檔案生成完畢後, master 會將這個 RDB 傳送給 slave,slave 會先寫入本地磁碟,然後再從本地磁碟載入到記憶體中,接著 master 會將記憶體中快取的寫命令傳送到 slave,slave 也會同步這些資料。slave node 如果跟 master node 有網路故障,斷開了連線,會自動重連,連線之後 master node 僅會複製給 slave 部分缺少的資料。

  • 1、主從複製的斷點續傳
      從 redis2.8 開始,就支援主從複製的斷點續傳,如果主從複製過程中,網路連線斷掉了,那麼可以接著上次複製的地方,繼續複製下去,而不是從頭開始複製一份。
      master node 會在記憶體中維護一個 backlog,master 和 slave 都會儲存一個 replica offset 還有一個 master run id,offset 就是儲存在 backlog 中的。如果 master 和 slave 網路連線斷掉了,slave 會讓 master 從上次 replica offset 開始繼續複製,如果沒有找到對應的 offset,那麼就會執行一次 resynchronization。
  • 2、無磁碟化複製
      master 在記憶體中直接建立 RDB,然後傳送給 slave,不會在自己本地落地磁碟了。只需要在配置檔案中開啟 repl-diskless-sync yes 即可。
  • 3、過期 key 處理
      slave 不會過期 key,只會等待 master 過期 key。如果 master 過期了一個 key,或者通過 LRU 淘汰了一個 key,那麼會模擬一條 del 命令傳送給 slave。

8.5.4 複製的完整流程

  slave node 啟動時,會在自己本地儲存 master node 的資訊,包括 master node 的host和ip,但是複製流程沒開始。slave node 內部有個定時任務,每秒檢查是否有新的 master node 要連線和複製,如果發現,就跟 master node 建立 socket 網路連線。然後 slave node 傳送 ping 命令給 master node。如果 master 設定了 requirepass,那麼 slave node 必須傳送 masterauth 的口令過去進行認證。master node 第一次執行全量複製,將所有資料發給 slave node。而在後續,master node 持續將寫命令,非同步複製給 slave node。

  • 1、 全量複製
      master 執行 bgsave ,在本地生成一份 rdb 快照檔案。
      master node 將 rdb 快照檔案傳送給 slave node,如果 rdb 複製時間超過 60秒(repl-timeout),那麼 slave node 就會認為複製失敗,可以適當調大這個引數(對於千兆網絡卡的機器,一般每秒傳輸 100MB,6G 檔案,很可能超過 60s)
      master node 在生成 rdb 時,會將所有新的寫命令快取在記憶體中,在 slave node 儲存了 rdb 之後,再將新的寫命令複製給 slave node。
      如果在複製期間,記憶體緩衝區持續消耗超過 64MB,或者一次性超過 256MB,那麼停止複製,複製失敗。

client-output-buffer-limit slave 256MB 64MB 60

  slave node 接收到 rdb 之後,清空自己的舊資料,然後重新載入 rdb 到自己的記憶體中,同時基於舊的資料版本對外提供服務。
  如果 slave node 開啟了 AOF,那麼會立即執行 BGREWRITEAOF,重寫 AOF。

  • 2、增量複製
      如果全量複製過程中,master-slave 網路連線斷掉,那麼 slave 重新連線 master 時,會觸發增量複製。
      master 直接從自己的 backlog 中獲取部分丟失的資料,傳送給 slave node,預設 backlog 就是 1MB。
      master 就是根據 slave 傳送的 psync 中的 offset 來從 backlog 中獲取資料的。
  • 3、heartbeat
      主從節點互相都會發送 heartbeat 資訊。master 預設每隔 10秒 傳送一次 heartbeat,slave node 每隔 1秒 傳送一個 heartbeat。
  • 4、 非同步複製
      master 每次接收到寫命令之後,先在內部寫入資料,然後非同步傳送給 slave node。

8.6 Redis叢集的主從複製模型是怎樣的?

  為了使在部分節點失敗或者大部分節點無法通訊的情況下叢集仍然可用,所以叢集使用了主從複製模型,每個節點都會有N-1個複製品。

8.7 生產環境中的 redis 是怎麼部署的?

  redis cluster,10 臺機器,5 臺機器部署了 redis 主例項,另外 5 臺機器部署了 redis 的從例項,每個主例項掛了一個從例項,5 個節點對外提供讀寫服務,每個節點的讀寫高峰qps可能可以達到每秒 5 萬,5 臺機器最多是 25 萬讀寫請求/s。
  機器是什麼配置?32G 記憶體+ 8 核 CPU + 1T 磁碟,但是分配給 redis 程序的是10g記憶體,一般線上生產環境,redis 的記憶體儘量不要超過 10g,超過 10g 可能會有問題。
  5 臺機器對外提供讀寫,一共有 50g 記憶體。
  因為每個主例項都掛了一個從例項,所以是高可用的,任何一個主例項宕機,都會自動故障遷移,redis 從例項會自動變成主例項繼續提供讀寫服務。
  你往記憶體裡寫的是什麼資料?每條資料的大小是多少?商品資料,每條資料是 10kb。100 條資料是 1mb,10 萬條資料是 1g。常駐記憶體的是 200 萬條商品資料,佔用記憶體是 20g,僅僅不到總記憶體的 50%。目前高峰期每秒就是 3500 左右的請求量。
  其實大型的公司,會有基礎架構的 team 負責快取叢集的運維。

8.8 說說Redis雜湊槽的概念?

  Redis叢集沒有使用一致性hash,而是引入了雜湊槽的概念,Redis叢集有16384個雜湊槽,每個key通過CRC16校驗後對16384取模來決定放置哪個槽,叢集的每個節點負責一部分hash槽。
  "用了雜湊槽的概念,而沒有用一致性雜湊演算法,不都是雜湊麼?這樣做的原因是為什麼呢?"Redis Cluster是自己做的crc16的簡單hash演算法,沒有用一致性hash。Redis的作者認為它的crc16(key) mod 16384的效果已經不錯了,雖然沒有一致性hash靈活,但實現很簡單,節點增刪時處理起來也很方便。

8.9 Redis叢集會有寫操作丟失嗎?為什麼?

  Redis並不能保證資料的強一致性,這意味這在實際中叢集在特定的條件下可能會丟失寫操作。
  以下情況可能導致寫操作丟失:

  • 過期 key 被清理
  • 最大記憶體不足,導致 Redis 自動清理部分 key 以節省空間
  • 主庫故障後自動重啟,從庫自動同步
  • 單獨的主備方案,網路不穩定觸發哨兵的自動切換主從節點,切換期間會有資料丟失

8.10 Redis叢集之間是如何複製的?

  非同步複製,具體的複製過程見於上文。

8.11 Redis叢集最大節點個數是多少?

  16384個。
  Redis 叢集有 16384 個雜湊槽,每個 key 通過 CRC16 演算法計算的結果,對 16384 取模後放到對應的編號在 0-16383 之間的雜湊槽,叢集的每個節點負責一部分雜湊槽。

8.11.1 為什麼redis叢集的最大槽數是16384個?

  在redis節點發送心跳包時需要把所有的槽放到這個心跳包裡,以便讓節點知道當前叢集資訊,16384=16k,在傳送心跳包時使用char進行bitmap壓縮後是2k(2 * 8 (8 bit) * 1024(1k) = 2K),也就是說使用2k的空間建立了16k的槽數。
  雖然使用CRC16演算法最多可以分配65535(2^16-1)個槽位,65535=65k,壓縮後就是8k(8 * 8 (8 bit) * 1024(1k) = 8K),也就是說需要需要8k的心跳包,作者認為這樣做不太值得;並且一般情況下一個redis叢集不會有超過1000個master節點,所以16k的槽位是個比較合適的選擇。

8.12 Redis叢集如何選擇資料庫?

  Redis叢集目前無法做資料庫選擇,預設在0資料庫。

九、分割槽

  分割槽是將你的資料分發到不同redis例項上的一個過程,每個redis例項只是你所有key的一個子集。

9.1 Redis是單執行緒的,如何提高多核CPU的利用率?

  可以在同一個伺服器部署多個Redis的例項,並把他們當作不同的伺服器來使用,在某些時候,無論如何一個伺服器是不夠的, 所以,如果你想使用多個CPU,你可以考慮一下分片(shard)。

9.2 為什麼要做Redis分割槽?

  分割槽可以讓Redis管理更大的記憶體,Redis將可以使用所有機器的記憶體。如果沒有分割槽,你最多隻能使用一臺機器的記憶體。分割槽使Redis的計算能力通過簡單地增加計算機得到成倍提升,Redis的網路頻寬也會隨著計算機和網絡卡的增加而成倍增長。

9.3 你知道有哪些Redis分割槽實現方案?

  客戶端分割槽就是在客戶端就已經決定資料會被儲存到哪個redis節點或者從哪個redis節點讀取。大多數客戶端已經實現了客戶端分割槽。
  代理分割槽意味著客戶端將請求傳送給代理,然後代理決定去哪個節點寫資料或者讀資料。代理根據分割槽規則決定請求哪些Redis例項,然後根據Redis的響應結果返回給客戶端。redis和memcached的一種代理實現就是Twemproxy
  查詢路由的意思是客戶端隨機地請求任意一個redis例項,然後由Redis將請求轉發給正確的Redis節點。Redis Cluster實現了一種混合形式的查詢路由,但並不是直接將請求從一個redis節點轉發到另一個redis節點,而是在客戶端的幫助下直接redirected到正確的redis節點。

9.4 Redis分割槽有什麼缺點?

  • 涉及多個key的操作通常不會被支援。例如你不能對兩個集合求交集,因為他們可能被儲存到不同的Redis例項(實際上這種情況也有辦法,但是不能直接使用交集指令)。
  • 同時操作多個key,則不能使用Redis事務.
  • 分割槽使用的粒度是key,不能使用一個非常長的排序key儲存一個數據集
  • 當使用分割槽的時候,資料處理會非常複雜,例如為了備份你必須從不同的Redis例項和主機同時收集RDB / AOF檔案。
  • 分割槽時動態擴容或縮容可能非常複雜。Redis叢集在執行時增加或者刪除Redis節點,能做到最大程度對使用者透明地資料再平衡,但其他一些客戶端分割槽或者代理分割槽方法則不支援這種特性。然而,有一種預分片的技術也可以較好的解決這個問題。

十、分散式問題

10.1 Redis分散式鎖的實現原理

  Redisson實現Redis分散式鎖的底層原理:

  • 1、加鎖機制
      如果該客戶端面對的是一個redis cluster叢集,他首先會根據hash節點選擇一臺機器。緊接著,就會發送一段lua指令碼到redis上。
  • 2、鎖互斥機制
      在這個時候,如果客戶端2來嘗試加鎖,執行了同樣的一段lua指令碼,獲取到這個鎖key的剩餘生存時間。比如還剩15000毫秒的生存時間。此時客戶端2會進入一個while迴圈,不停的嘗試加鎖。
  • 3、watch dog自動延期機制
      客戶端1加鎖的鎖key預設生存時間才30秒,如果超過了30秒,客戶端1還想一直持有這把鎖,怎麼辦呢?只要客戶端1一旦加鎖成功,就會啟動一個watch dog看門狗,他是一個後臺執行緒,會每隔10秒檢查一下,如果客戶端1還持有鎖key,那麼就會不斷的延長鎖key的生存時間。
  • 4、釋放鎖機制
      如果執行lock.unlock(),就可以釋放分散式鎖,客戶端2就可以嘗試完成加鎖了。般我們在生產系統中,可以用Redisson框架提供的這個類庫來基於redis進行分散式鎖的加鎖與釋放鎖。

上述Redis分散式鎖的缺點:
  其實上面那種方案最大的問題,就是如果你對某個redis master例項,寫入了myLock這種鎖key的value,此時會非同步複製給對應的master slave例項。
  但是這個過程中一旦發生redis master宕機,主備切換,redis slave變為了redis master。接著就會導致,客戶端2來嘗試加鎖的時候,在新的redis master上完成了加鎖,而客戶端1也以為自己成功加了鎖。此時就會導致多個客戶端對一個分散式鎖完成了加鎖。這時系統在業務語義上一定會出現問題,導致各種髒資料的產生。
  所以這個就是redis cluster,或者是redis master-slave架構的主從非同步複製導致的redis分散式鎖的最大缺陷:在redis master例項宕機的時候,可能導致多個客戶端同時完成加鎖。

10.2 Redis實現分散式鎖

  Redis為單程序單執行緒模式,採用佇列模式將併發訪問變成序列訪問,且多客戶端對Redis的連線並不存在競爭關係Redis中可以使用SETNX命令實現分散式鎖。
  當且僅當 key 不存在,將 key 的值設為 value。 若給定的 key 已經存在,則 SETNX 不做任何動作
  SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡寫。
  返回值:設定成功,返回 1 。設定失敗,返回 0 。

  使用SETNX完成同步鎖的流程及事項如下:
    1>使用SETNX命令獲取鎖,若返回0(key已存在,鎖已存在)則獲取失敗,反之獲取成功
    2>為了防止獲取鎖後程序出現異常,導致其他執行緒/程序呼叫SETNX命令總是返回0而進入死鎖狀態,需要為該key設定一個“合理”的過期時間
    3>釋放鎖,使用DEL命令將鎖資料刪除

10.3 如何解決 Redis 的併發競爭 Key 問題

  併發競爭key這個問題簡單講就是:同時有多個客戶端去set一個key。
  解決方法常見的有四種:

  • 1、樂觀鎖
      樂觀鎖適用於大家一起搶著改同一個key,對修改順序沒有要求的場景。watch 命令可以方便的實現樂觀鎖。watch 命令會監視給定的每一個key,當 exec 時如果監視的任一個key自從呼叫watch後發生過變化,則整個事務會回滾,不執行任何動作。

需要注意的是,如果你的 redis 使用了資料分片的方式,那麼這個方法就不適用了。

  • 2、分散式鎖
      適合分散式環境,不用關心 redis 是否為分片叢集模式。在業務層進行控制,操作 redis 之前,先去申請一個分散式鎖,拿到鎖的才能操作。分散式鎖的實現方式很多,比如 ZooKeeper、Redis 等。
  • 3、時間戳
      適合有序需求場景。
  • 4、訊息佇列
      在併發量很大的情況下,可以通過訊息佇列進行序列化處理。這在高併發場景中是一種很常見的解決方案。

  總結這幾種方案的話,適用場景:

  • 樂觀鎖,注意不要在分片叢集中使用
  • 分散式鎖,適合分散式系統環境
  • 時間戳,適合有序場景
  • 訊息佇列,序列化處理

10.4 分散式Redis是前期做還是後期規模上來了再做好?為什麼?

  既然Redis是如此的輕量(單例項只使用1M記憶體),為防止以後的擴容,最好的辦法就是一開始就啟動較多例項。即便你只有一臺伺服器,你也可以一開始就讓Redis以分散式的方式執行,使用分割槽,在同一臺伺服器上啟動多個例項。
  一開始就多設定幾個Redis例項,例如32或者64個例項,對大多數使用者來說這操作起來可能比較麻煩,但是從長久來看做這點犧牲是值得的。
  這樣的話,當你的資料不斷增長,需要更多的Redis伺服器時,你需要做的就是僅僅將Redis例項從一臺服務遷移到另外一臺伺服器而已(而不用考慮重新分割槽的問題)。一旦你添加了另一臺伺服器,你需要將你一半的Redis例項從第一臺機器遷移到第二臺機器。

10.5 什麼是 RedLock

  Redlock:全名叫做 Redis Distributed Lock;即使用redis實現的分散式鎖;使用場景:多個服務間保證同一時刻同一時間段內同一使用者只能有一個請求(防止關鍵業務出現併發攻擊)。
  它可以保證以下特性:
   1>安全特性:互斥訪問,即永遠只有一個 client 能拿到鎖
   2>避免死鎖:最終 client 都可能拿到鎖,不會出現死鎖的情況,即使原本鎖住某資源的 client crash 了或者出現了網路分割槽
   3>容錯性:只要大部分 Redis 節點存活就可以正常提供服務
  多節點redis實現的分散式鎖演算法(假設有5個完全獨立的redis主伺服器),步驟為:

  1. 獲取當前時間戳
  2. client嘗試按照順序使用相同的key,value獲取所有redis服務的鎖,在獲取鎖的過程中的獲取時間比鎖過期時間短很多,這是為了不要過長時間等待已經關閉的redis服務。並且試著獲取下一個redis例項。
      比如:TTL為5s,設定獲取鎖最多用1s,所以如果一秒內無法獲取鎖,就放棄獲取這個鎖,從而嘗試獲取下個鎖
  3. client通過獲取所有能獲取的鎖後的時間減去第一步的時間,這個時間差要小於TTL時間並且至少有3個redis例項成功獲取鎖,才算真正的獲取鎖成功
  4. 如果成功獲取鎖,則鎖的真正有效時間是 TTL減去第三步的時間差 的時間;比如:TTL 是5s,獲取所有鎖用了2s,則真正鎖有效時間為3s(其實應該再減去時鐘漂移);
  5. 如果客戶端由於某些原因獲取鎖失敗,便會開始解鎖所有redis例項;因為可能已經獲取了小於3個鎖,必須釋放,否則影響其他client獲取鎖

  演算法示意圖如下:

  RedLock注意點(Safety arguments):

  1. 先假設client獲取所有例項,所有例項包含相同的key和過期時間(TTL) ,但每個例項set命令時間不同導致不能同時過期,第一個set命令之前是T1,最後一個set命令後為T2,則此client有效獲取鎖的最小時間為TTL-(T2-T1)-時鐘漂移;
  2. 對於以N/2+ 1(也就是一半以 上)的方式判斷獲取鎖成功,是因為如果小於一半判斷為成功的話,有可能出現多個client都成功獲取鎖的情況, 從而使鎖失效;
  3. 一個client鎖定大多數事例耗費的時間大於或接近鎖的過期時間,就認為鎖無效,並且解鎖這個redis例項(不執行業務) ;只要在TTL時間內成功獲取一半以上的鎖便是有效鎖;否則無效。

十一、快取異常

11.1 快取雪崩

  快取雪崩是指快取同一時間大面積的失效,所以,後面的請求都會落到資料庫上,造成資料庫短時間內承受大量請求而崩掉。
  快取失效的幾種情況:
    1、快取伺服器掛了
    2、高峰期快取區域性失效
    3、熱點快取失效
  解決方案:

  • 1、快取資料的過期時間設定隨機,防止同一時間大量資料過期現象發生。
  • 2、增加互斥鎖,控制資料庫請求,重建快取。
  • 3、提高快取的HA,如:redis叢集。

11.2 快取穿透

  快取穿透是指快取和資料庫中都沒有的資料,導致所有的請求都落到資料庫上,造成資料庫短時間內承受大量請求而崩掉。
  解決方案

  • 1、介面層增加校驗,如使用者鑑權校驗,id做基礎校驗,id<=0的直接攔截;
  • 2、快取空物件從快取取不到的資料,在資料庫中也沒有取到,這時也可以將key-value對寫為key-null,快取有效時間可以設定短點,如30秒(設定太長會導致正常情況也沒法使用)。這樣可以防止攻擊使用者反覆用同一個id暴力攻擊
  • 3、採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的 bitmap 中,一個一定不存在的資料會被這個 bitmap 攔截掉,從而避免了對底層儲存系統的查詢壓力

  對於空間的利用到達了一種極致,那就是Bitmap和布隆過濾器(Bloom Filter)。
  Bitmap: 典型的就是雜湊表。缺點是,Bitmap對於每個元素只能記錄1bit資訊,如果還想完成額外的功能,恐怕只能靠犧牲更多的空間、時間來完成了。
  布隆過濾器(推薦)
   就是引入了k(k>1)k(k>1)個相互獨立的雜湊函式,保證在給定的空間、誤判率下,完成元素判重的過程。
   它的優點是空間效率和查詢時間都遠遠超過一般的演算法,缺點是有一定的誤識別率和刪除困難。
   Bloom-Filter演算法的核心思想就是利用多個不同的Hash函式來解決“衝突”。
   Hash存在一個衝突(碰撞)的問題,用同一個Hash得到的兩個URL的值有可能相同。為了減少衝突,我們可以多引入幾個Hash,如果通過其中的一個Hash值我們得出某元素不在集合中,那麼該元素肯定不在集合中。只有在所有的Hash函式告訴我們該元素在集合中時,才能確定該元素存在於集合中。這便是Bloom-Filter的基本思想。
   Bloom-Filter一般用於在大資料量的集合中判定某元素是否存在

11.3 快取擊穿

  快取擊穿是指快取中沒有但資料庫中有的資料(一般是快取時間到期),這時由於併發使用者特別多,同時讀快取沒讀到資料,又同時去資料庫去取資料,引起資料庫壓力瞬間增大,造成過大壓力。和快取雪崩不同的是,快取擊穿指併發查同一條資料,快取雪崩是不同資料都過期了,很多資料都查不到從而查資料庫。
  解決方案

  • 1、設定熱點資料永遠不過期。
  • 2、加互斥鎖,互斥鎖。

11.4 快取預熱

  快取預熱就是系統上線後,將相關的快取資料直接載入到快取系統。這樣就可以避免在使用者請求的時候,先查詢資料庫,然後再將資料快取的問題!使用者直接查詢事先被預熱的快取資料!
  解決方案

  • 1、直接寫個快取重新整理頁面,上線時手工操作一下;
  • 2、資料量不大,可以在專案啟動的時候自動進行載入;
  • 3、定時重新整理快取;

11.5 快取降級

  當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的效能時,仍然需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵資料進行自動降級,也可以配置開關實現人工降級。
  快取降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。
  在進行降級之前要對系統進行梳理,看看系統是不是可以丟卒保帥;從而梳理出哪些必須誓死保護,哪些可降級;比如可以參考日誌級別設定預案:

  • 1、一般:比如有些服務偶爾因為網路抖動或者服務正在上線而超時,可以自動降級;
  • 2、警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,併發送告警;
  • 3、錯誤:比如可用率低於90%,或者資料庫連線池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;
  • 4、嚴重錯誤:比如因為特殊原因資料錯誤了,此時需要緊急人工降級。

  服務降級的目的,是為了防止Redis服務故障,導致資料庫跟著一起發生雪崩問題。因此,對於不重要的快取資料,可以採取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去資料庫查詢,而是直接返回預設值給使用者。

11.6 熱點資料和冷資料

  熱點資料,快取才有價值
  對於冷資料而言,大部分資料可能還沒有再次訪問到就已經被擠出記憶體,不僅佔用記憶體,而且價值不大。頻繁修改的資料,看情況考慮使用快取
  對於熱點資料,比如我們的某IM產品,生日祝福模組,當天的壽星列表,快取以後可能讀取數十萬次。再舉個例子,某導航產品,我們將導航資訊,快取以後可能讀取數百萬次。
  資料更新前至少讀取兩次,快取才有意義。這個是最基本的策略,如果快取還沒有起作用就失效了,那就沒有太大價值了。
  那存不存在,修改頻率很高,但是又不得不考慮快取的場景呢?有!比如,這個讀取介面對資料庫的壓力很大,但是又是熱點資料,這個時候就需要考慮通過快取手段,減少資料庫的壓力,比如我們的某助手產品的,點贊數,收藏數,分享數等是非常典型的熱點資料,但是又不斷變化,此時就需要將資料同步儲存到Redis快取,減少資料庫壓力。

11.7 快取熱點key

  快取中的一個Key(比如一個促銷商品),在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的併發請求過來,這些請求發現快取過期一般都會從後端DB載入資料並回設到快取,這個時候大併發的請求可能會瞬間把後端DB壓垮。
  解決方案:
    對快取查詢加鎖,如果KEY不存在,就加鎖,然後查DB入快取,然後解鎖;其他程序如果發現有鎖就等待,然後等解鎖後返回資料或者進入DB查詢

十二、常用工具

12.1 Redis支援的Java客戶端都有哪些?官方推薦用哪個?

  Redisson、Jedis、lettuce等等,官方推薦使用Redisson。

12.2 Redis和Redisson有什麼關係?

  Redisson是一個高階的分散式協調Redis客服端,能幫助使用者在分散式環境中輕鬆實現一些Java的物件 (Bloom filter, BitSet, Set, SetMultimap, ScoredSortedSet, SortedSet, Map, ConcurrentMap, List, ListMultimap, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, ReadWriteLock, AtomicLong, CountDownLatch, Publish / Subscribe, HyperLogLog)。

12.3 Jedis與Redisson對比有什麼優缺點?

  Jedis是Redis的Java實現的客戶端,其API提供了比較全面的Redis命令的支援;Redisson實現了分散式和可擴充套件的Java資料結構,和Jedis相比,功能較為簡單,不支援字串操作,不支援排序、事務、管道、分割槽等Redis特性。Redisson的宗旨是促進使用者對Redis的關注分離,從而讓使用者能夠將精力更集中地放在處理業務邏輯上。

十三、其他問題

13.1 Redis與Memcached的區別

  兩者都是非關係型記憶體鍵值資料庫,Redis 與 Memcached 主要有以下不同:
總結一

對比引數RedisMemcached
型別1. 支援記憶體 2. 非關係型資料庫1. 支援記憶體 2. 鍵值對形式 3. 快取形式
資料儲存型別1. String 2. List 3. Set 4. Hash 5. Sort Set 【俗稱ZSet】1. 文字型 2. 二進位制型別
查詢【操作】型別1. 批量操作 2. 事務支援 3. 每個型別不同的CRUD1.常用的CRUD 2. 少量的其他命令
附加功能1. 釋出/訂閱模式 2. 主從分割槽 3. 序列化支援 4. 指令碼支援【Lua指令碼】1. 多執行緒服務支援
網路IO模型1. 單執行緒的多路 IO 複用模型1. 多執行緒,非阻塞IO模式
事件庫自封轉簡易事件庫AeEvent貴族血統的LibEvent事件庫
持久化支援1. RDB 2. AOF不支援
叢集模式原生支援 cluster 模式,可以實現主從複製,讀寫分離沒有原生的叢集模式,需要依靠客戶端來實現往叢集中分片寫入資料
記憶體管理機制在 Redis 中,並不是所有資料都一直儲存在記憶體中,可以將一些很久沒用的 value 交換到磁碟Memcached 的資料則會一直在記憶體中,Memcached 將記憶體分割成特定長度的塊來儲存資料,以完全解決記憶體碎片的問題。但是這種方式會使得記憶體的利用率不高,例如塊的大小為 128 bytes,只儲存 100 bytes 的資料,那麼剩下的 28 bytes 就浪費掉了。
適用場景複雜資料結構,有持久化,高可用需求,value儲存內容較大純key-value,資料量非常大,併發量非常大的業務

  1、memcached所有的值均是簡單的字串,redis作為其替代者,支援更為豐富的資料型別
  2、redis的速度比memcached快很多
  3、redis可以持久化其資料
總結二

  • 1、型別
      Redis是一個開源的記憶體資料結構儲存系統,用作資料庫,快取和訊息代理。Memcached是一個免費的開源高效能分散式記憶體物件快取系統,它通過減少資料庫負載來加速動態Web應用程式。
  • 2、資料結構
      Redis支援字串,雜湊,列表,集合,有序集,點陣圖,超級日誌和空間索引;而Memcached支援字串和整數。
  • 3、執行速度
      Memcached的讀寫速度高於Redis。
  • 4、複製
      Memcached不支援複製。而,Redis支援主從複製,允許從屬Redis伺服器成為主伺服器的精確副本;來自任何Redis伺服器的資料都可以複製到任意數量的從屬伺服器。
  • 5、金鑰長度
      Redis的金鑰長度最大為2GB,而Memcached的金鑰長度最大為250位元組。
  • 6、執行緒
      Redis是單執行緒的;而,Memcached是多執行緒的。

13.2 如何保證快取與資料庫雙寫時的資料一致性?

  你只要用快取,就可能會涉及到快取與資料庫雙儲存雙寫,你只要是雙寫,就一定會有資料一致性的問題,那麼你如何解決一致性問題?
  一般來說,就是如果你的系統不是嚴格要求快取+資料庫必須一致性的話,快取可以稍微的跟資料庫偶爾有不一致的情況,最好不要做這個方案,讀請求和寫請求序列化,串到一個記憶體佇列裡去,這樣就可以保證一定不會出現不一致的情況
  序列化之後,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。
  最經典的快取+資料庫讀寫的模式,就是 Cache Aside Pattern。

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

  **為什麼是刪除快取,而不是更新快取?**原因很簡單,很多時候,在複雜點的快取場景,快取不單單是資料庫中直接取出來的值。另外更新快取的代價有時候是很高的。
  問題1:先修改資料庫,再刪除快取。如果刪除快取失敗了,那麼會導致資料庫中是新資料,快取中是舊資料,資料就出現了不一致。
  解決思路1:先刪除快取,再修改資料庫。如果資料庫修改失敗了,那麼資料庫中是舊資料,快取中是空的,那麼資料不會不一致。因為讀的時候快取沒有,則讀資料庫中舊資料,然後更新到快取中。
  問題2:資料發生了變更,先刪除了快取,然後要去修改資料庫,此時還沒修改。一個請求過來,去讀快取,發現快取空了,去查詢資料庫,查到了修改前的舊資料,放到了快取中。隨後資料變更的程式完成了資料庫的修改。完了,資料庫和快取中的資料不一樣了。
  解決思路2:更新資料的時候,根據資料的唯一標識,將操作路由之後,傳送到一個 jvm 內部佇列中。讀取資料的時候,如果發現數據不在快取中,那麼將重新讀取資料+更新快取的操作,根據唯一標識路由之後,也傳送同一個 jvm 內部佇列中。一個佇列對應一個工作執行緒,每個工作執行緒序列拿到對應的操作,然後一條一條的執行。這樣的話,一個數據變更的操作,先刪除快取,然後再去更新資料庫,但是還沒完成更新。此時如果一個讀請求過來,讀到了空的快取,那麼可以先將快取更新的請求傳送到佇列中,此時會在佇列中積壓,然後同步等待快取更新完成。

13.3 Redis常見效能問題和解決方案?

  • 1、Master最好不要做任何持久化工作,包括記憶體快照和AOF日誌檔案,特別是不要啟用記憶體快照做持久化。
  • 2、如果資料比較關鍵,某個Slave開啟AOF備份資料,策略為每秒同步一次。
  • 3、為了主從複製的速度和連線的穩定性,Slave和Master最好在同一個區域網內。
  • 4、儘量避免在壓力較大的主庫上增加從庫
  • 5、為了Master的穩定性,主從複製不要用圖狀結構,用單向連結串列結構更穩定,即主從關係為:Master<–Slave1<–Slave2<–Slave3…,這樣的結構也方便解決單點故障問題,實現Slave對Master的替換,也即,如果Master掛了,可以立馬啟用Slave1做Master,其他不變。

13.4 一個字串型別的值能儲存最大容量是多少?

  512M

13.5 Redis如何做大量資料插入?

  Redis2.6開始,redis-cli支援一種新的被稱之為pipe mode的新模式用於執行大量資料插入工作。
  使用pipe mode模式的執行命令如下:

cat data.txt | redis-cli --pipe  

pipe mode的工作原理:

  • redis-cli –pipe試著儘可能快的傳送資料到伺服器
  • 讀取資料的同時,解析它
  • 一旦沒有更多的資料輸入,它就會發送一個特殊的echo命令,後面跟著20個隨機的字元。我們相信可以通過匹配回覆相同的20個字元是同一個命令的行為
  • 一旦這個特殊命令發出,收到的答覆就開始匹配這20個字元,當匹配時,就可以成功退出了

13.6 假如Redis裡面有1億個key,其中有10w個key是以某個固定的已知的字首開頭的,如果將它們全部找出來?

  使用keys指令可以掃出指定模式的key列表。
  對方接著追問:如果這個redis正在給線上的業務提供服務,那使用keys指令會有什麼問題?
  這個時候你要回答redis關鍵的一個特性:redis的單執行緒的。keys指令會導致執行緒阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時候可以使用scan指令,scan指令可以無阻塞的提取出指定模式的key列表,但是會有一定的重複概率,在客戶端做一次去重就可以了,但是整體所花費的時間會比直接用keys指令長。

13.7 使用Redis做過非同步佇列嗎,是如何實現的

  一般使用 list 結構作為佇列,rpush 生產訊息,lpop 消費訊息。當 lpop 沒有訊息的時候,要適當 sleep 一會再重試。
  如果對方追問可不可以不用 sleep 呢?list 還有個指令叫 blpop,在沒有訊息的時候,它會阻塞住直到訊息到來。
  如果對方追問能不能生產一次消費多次呢?使用 pub/sub 主題訂閱者模式,可以實現1:N 的訊息佇列。
  如果對方追問 pub/sub 有什麼缺點?在消費者下線的情況下,生產的訊息會丟失,得使用專業的訊息佇列如 RabbitMQ等。
  如果對方追問** redis 如何實現延時佇列**?使用 sortedset,拿時間戳作為score,訊息內容作為 key 呼叫 zadd 來生產訊息,消費者用 zrangebyscore 指令獲取 N 秒之前的資料輪詢進行處理。

13.8 Redis回收程序如何工作的?

  一個客戶端運行了新的命令,添加了新的資料。
  Redis檢查記憶體使用情況,如果大於maxmemory的限制, 則根據設定好的策略進行回收。一個新的命令被執行,等等。
  所以我們不斷地穿越記憶體限制的邊界,通過不斷達到邊界然後不斷地回收回到邊界以下。如果一個命令的結果導致大量記憶體被使用(例如很大的集合的交集儲存到一個新的鍵),不用多久記憶體限制就會被這個記憶體使用量超越。

13.9 Redis回收使用的是什麼演算法?

  LRU演算法。LRU全稱是Least Recently Used,即最近最久未使用的意思。
  LRU演算法的設計原則是:如果一個數據在最近一段時間沒有被訪問到,那麼在將來它被訪問的可能性也很小。也就是說,當限定的空間已存滿資料時,應當把最久沒有被訪問到的資料淘汰。

13.10 什麼樣的資料適合儲存在非關係型資料庫

  1、關係不是很密切的的資料,比如使用者資訊,班級資訊,評論數量等等。
  2、量比較大的資料,如訪問記錄等。
  3、訪問比較頻繁的資料,如使用者資訊,訪問數量,最新微博等 。

  
  參考連結: