這些年背過的面試題——Redis篇

資料庫工作筆記發表於2024-02-04

這些年背過的面試題——Redis篇

來源:阿里雲開發者

阿里妹導讀

本文是技術人面試系列Redis篇,面試中關於Redis都需要了解哪些基礎?一文帶你詳細瞭解,歡迎收藏!

WhyRedis

速度快,完全基於記憶體,使用C語言實現,網路層使用epoll解決高併發問題,單執行緒模型避免了不必要的上下文切換及競爭條件;

這些年背過的面試題——Redis篇

與傳統資料庫不同的是 Redis 的資料是存在記憶體中的,所以讀寫速度非常快,因此 Redis 被廣泛應用於快取方向,每秒可以處理超過 10萬次讀寫操作,是已知效能最快的Key-Value DB。另外,Redis 也經常用來做分散式鎖。除此之外,Redis 支援事務 、持久化、LUA指令碼、LRU驅動事件、多種叢集方案。


1、簡單高效

1)完全基於記憶體,絕大部分請求是純粹的記憶體操作。資料存在記憶體中,類似於 HashMap,查詢和操作的時間複雜度都是O(1);

2)資料結構簡單,對資料操作也簡單,Redis 中的資料結構是專門進行設計的;

3)採用單執行緒,避免了多執行緒不必要的上下文切換和競爭條件,不存在加鎖釋放鎖操作,減少了因為鎖競爭導致的效能消耗;(6.0以後多執行緒)

4)使用EPOLL多路 I/O 複用模型,非阻塞 IO;

5)使用底層模型不同,它們之間底層實現方式以及與客戶端之間通訊的應用協議不一樣,Redis 直接自己構建了 VM 機制 ,因為一般的系統呼叫系統函式的話,會浪費一定的時間去移動和請求;

2、Memcache

這些年背過的面試題——Redis篇

使用場景:

1、如果有持久方面的需求或對資料型別和處理有要求的應該選擇redis。

2、如果簡單的key/value 儲存應該選擇memcached。 

3、Tair

Tair(Taobao Pair)是淘寶開發的分散式Key-Value儲存引擎,既可以做快取也可以做資料來源(三種引擎切換)

  • MDB(Memcache)屬於記憶體型產品,支援kv和類hashMap結構,效能最優;
  • RDB(Redis)支援List.Set.Zset等複雜的資料結構,效能次之,可提供快取和持久化儲存兩種模式;
  • LDB(levelDB)屬於持久化產品,支援kv和類hashmap結構,效能較前兩者稍低,但持久化可靠性最高;

分散式快取

大訪問少量臨時資料的儲存(kb左右)

用於快取,降低對後端資料庫的訪問壓力

session場景

高速訪問某些資料結構的應用和計算(rdb)

資料來源儲存

快速讀取資料(fdb)

持續大資料量的存入讀取(ldb),交易快照

高頻度的更新讀取(ldb),庫存

痛點:redis叢集中,想借用快取資源必須得指明redis伺服器地址去要。這就增加了程式的維護複雜度。因為redis伺服器很可能是需要頻繁變動的。所以人家淘寶就想啊,為什麼不能像操作分散式資料庫或者hadoop那樣。增加一箇中央節點,讓他去代理所有事情。在tair中程式只要跟tair中心節點互動就OK了。同時tair裡還有配置伺服器概念。又免去了像操作hadoop那樣,還得每臺hadoop一套一模一樣配置檔案。改配置檔案得整個叢集都跟著改。

4、Guava

分散式快取一致性更好一點,用於叢集環境下多節點使用同一份快取的情況;有網路IO,吞吐率與快取的資料大小有較大關係;

本地快取非常高效,本地快取會佔用堆記憶體,影響垃圾回收、影響系統效能。

本地快取設計:

以 Java 為例,使用自帶的 map 或者 guava 實現的是本地快取,最主要的特點是輕量以及快速,生命週期隨著 jvm 的銷燬而結束,並且在多例項的情況,每個例項都需要各自儲存一份快取,快取不具有一致性。

解決快取過期:

1、將快取過期時間調為永久;

2、將快取失效時間分散開,不要將快取時間長度都設定成一樣;比如我們可以在原有的失效時間基礎上增加一個隨機值,這樣每一個快取的過期時間的重複率就會降低,就很難引發集體失效的事件。

解決記憶體溢位:

第一步,修改JVM啟動引數,直接增加記憶體。(-Xms,-Xmx引數一定不要忘記加。)

第二步,檢查錯誤日誌,檢視“OutOfMemory”錯誤前是否有其它異常或錯誤。

第三步,對程式碼進行走查和分析,找出可能發生記憶體溢位的位置。

Google Guava Cache

自己設計本地快取痛點:

  • 不能按照一定的規則淘汰資料,如 LRU,LFU,FIFO 等;
  • 清除資料時的回撥通知;
  • 併發處理能力差,針對併發可以使用CurrentHashMap,但快取的其他功能需要自行實現;
  • 快取過期處理,快取資料載入重新整理等都需要手工實現;

Guava Cache 的場景:

  • 對效能有非常高的要求

  • 不經常變化,佔用記憶體不大

  • 有訪問整個集合的需求

  • 資料允許不實時一致

Guava Cache 的優勢:

  • 快取過期和淘汰機制

在GuavaCache中可以設定Key的過期時間,包括訪問過期和建立過期。GuavaCache在快取容量達到指定大小時,採用LRU的方式,將不常使用的鍵值從Cache中刪除。

  • 併發處理能力

GuavaCache類似CurrentHashMap,是執行緒安全的。提供了設定併發級別的api,使得快取支援併發的寫入和讀取,採用分離鎖機制,分離鎖能夠減小鎖力度,提升併發能力,分離鎖是分拆鎖定,把一個集合看分成若干partition, 每個partiton一把鎖。更新鎖定

  • 防止快取擊穿

一般情況下,在快取中查詢某個key,如果不存在,則查源資料,並回填快取。(Cache Aside Pattern)在高併發下會出現,多次查源並重復回填快取,可能會造成源的當機(DB),效能下降 GuavaCache可以在CacheLoader的load方法中加以控制,對同一個key,只讓一個請求去讀源並回填快取,其他請求阻塞等待。(相當於整合資料來源,方便使用者使用)

  • 監控快取載入/命中情況

統計

問題:

OOM->設定過期時間、使用弱引用、配置過期策略

5、EVCache

EVCache是一個Netflflix(網飛)公司開源、快速的分散式快取,是基於Memcached的記憶體儲存實現的,用以構建超大容量、高效能、低延時、跨區域的全球可用的快取資料層。

E:Ephemeral:資料儲存是短暫的,有自身的存活時間

V:Volatile:資料可以在任何時候消失

EVCache典型地適合對強一致性沒有必須要求的場合

典型用例:Netflflix向使用者推薦使用者感興趣的電影

這些年背過的面試題——Redis篇

EVCache叢集在峰值每秒可以處理200kb的請求,

Netflflix生產系統中部署的EVCache經常要處理超過每秒3000萬個請求,儲存數十億個物件,

跨數千臺memcached伺服器。整個EVCache叢集每天處理近2萬億個請求。

EVCache叢集響應平均延時大約是1-5毫秒,最多不會超過20毫秒。

EVCache叢集的快取命中率在99%左右。

典型部署

EVCache 是線性擴充套件的,可以在一分鐘之內完成擴容,在幾分鐘之內完成負載均衡和快取預熱。

這些年背過的面試題——Redis篇

1、叢集啟動時,EVCache向服務註冊中心(Zookeeper、Eureka)註冊各個例項
2、在web應用啟動時,查詢命名服務中的EVCache伺服器列表,並建立連線。

3、客戶端透過key使用一致性hash演算法,將資料分片到叢集上。

6、ETCD

和Zookeeper一樣,CP模型追求資料一致性,越來越多的系統開始用它儲存關鍵資料。比如,秒殺系統經常用它儲存各節點資訊,以便控制消費 MQ 的服務數量。還有些業務系統的配置資料,也會透過 etcd 實時同步給業務系統的各節點,比如,秒殺管理後臺會使用 etcd 將秒殺活動的配置資料實時同步給秒殺 API 服務各節點

這些年背過的面試題——Redis篇

Redis底層

1、redis資料型別

這些年背過的面試題——Redis篇

2、相關API

這些年背過的面試題——Redis篇

3、redis底層結構

SDS陣列結構,用於儲存字串和整型資料及輸入緩衝。


struct sdshdr{   int len;//記錄buf陣列中已使用位元組的數量   int free; //記錄 buf 陣列中未使用位元組的數量   char buf[];//字元陣列,用於儲存字串}


跳躍表:將有序連結串列中的部分節點分層,每一層都是一個有序連結串列。

1、可以快速查詢到需要的節點 O(logn) ,額外儲存了一倍的空間

2、可以在O(1)的時間複雜度下,快速獲得跳躍表的頭節點、尾結點、長度和高度。 

字典dict: 又稱雜湊表(hash),是用來儲存鍵值對的一種資料結構。 

Redis整個資料庫是用字典來儲存的(K-V結構) —Hash+陣列+連結串列

Redis字典實現包括:**字典(dict)、Hash表(dictht)、Hash表節點(dictEntry)**。

字典達到儲存上限(閾值 0.75),需要rehash(擴容)

1、初次申請預設容量為4個dictEntry,非初次申請為當前hash表容量的一倍。

2、rehashidx=0表示要進行rehash操作。

3、新增加的資料在新的hash表h[1] 、修改、刪除、查詢在老hash表h[0]

4、將老的hash表h[0]的資料重新計算索引值後全部遷移到新的hash表h[1]中,這個過程稱為 rehash。

漸進式rehash


 由於當資料量巨大時rehash的過程是非常緩慢的,所以需要進行最佳化。 可根據伺服器空閒程度批次rehash部分節點


壓縮列表zipList

壓縮列表(ziplist)是由一系列特殊編碼的連續記憶體塊組成的順序型資料結構,節省內容

sorted-sethash元素個數少且是小整數或短字串(直接使用) 

list用快速連結串列(quicklist)資料結構儲存,而快速連結串列是雙向列表與壓縮列表的組合。(間接使用)

整數集合intSet

整數集合(intset)是一個有序的(整數升序)、儲存整數的連續儲存結構。 

當Redis集合型別的元素都是整數並且都處在64位有符號整數範圍內(2^64),使用該結構體儲存。

快速列表quickList

快速列表(quicklist)是Redis底層重要的資料結構。是Redis3.2列表的底層實現。

(在Redis3.2之前,Redis採 用雙向連結串列(adlist)和壓縮列表(ziplist)實現。)

Redis Stream的底層主要使用了listpack(緊湊列表)和Rax樹(基數樹)。

listpack表示一個字串列表的序列化,listpack可用於儲存字串或整數。用於儲存stream的訊息內容。

Rax樹是一個有序字典樹 (基數樹 Radix Tree),按照 key 的字典序排列,支援快速地定位、插入和刪除操作。


4、Zset底層實現

跳錶(skip List)是一種隨機化的資料結構,基於並聯的連結串列,實現簡單,插入、刪除、查詢的複雜度均為O(logN)。簡單說來跳錶也是連結串列的一種,只不過它在連結串列的基礎上增加了跳躍功能,正是這個跳躍的功能,使得在查詢元素時,跳錶能夠提供O(logN)的時間複雜度。

Zset資料量少的時候使用壓縮連結串列ziplist實現,有序集合使用緊挨在一起的壓縮列表節點來儲存,第一個節點儲存member,第二個儲存score。ziplist內的集合元素按score從小到大排序,score較小的排在表頭位置。 資料量大的時候使用跳躍列表skiplist和雜湊表hash_map結合實現,查詢刪除插入的時間複雜度都是O(longN)。

Redis使用跳錶而不使用紅黑樹,是因為跳錶的索引結構序列化和反序列化更加快速,方便持久化。

搜尋

跳躍表按 score 從小到大儲存所有集合元素,查詢時間複雜度為平均 O(logN),最壞 O(N) 。

插入

選用連結串列作為底層結構支援,為了高效地動態增刪。因為跳錶底層的單連結串列是有序的,為了維護這種有序性,在插入前需要遍歷連結串列,找到該插入的位置,單連結串列遍歷查詢的時間複雜度是O(n),同理可得,跳錶的遍歷也是需要遍歷索引數,所以是O(logn)。

刪除

如果該節點還在索引中,刪除時不僅要刪除單連結串列中的節點,還要刪除索引中的節點;單連結串列在知道刪除的節點是誰時,時間複雜度為O(1),但針對單連結串列來說,刪除時都需要拿到前驅節點O(logN)才可改變引用關係從而刪除目標節點。

Redis可用性


1、redis持久化

持久化就是把記憶體中的資料持久化到本地磁碟,防止伺服器當機了記憶體資料丟失。

Redis 提供兩種持久化機制 RDB(預設) 和 AOF 機制,Redis4.0以後採用混合持久化,用 AOF 來保證資料不丟失,作為資料恢復的第一選擇; 用 RDB 來做不同程度的冷備。

RDB:是Redis DataBase縮寫快照

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

優點:

1)只有一個檔案 dump.rdb,方便持久化;
2)容災性好,一個檔案可以儲存到安全的磁碟。
3)效能最大化,fork 子程式來進行持久化寫操作,讓主程式繼續處理命令,只存在毫秒級不響應請求。

4)相對於資料集大時,比 AOF 的啟動效率更高。

缺點:

資料安全性低,RDB 是間隔一段時間進行持久化,如果持久化之間 redis 發生故障,會發生資料丟失。

AOF:持久化

AOF持久化(即Append Only File持久化),則是將Redis執行的每次寫命令記錄到單獨的日誌檔案中,當重啟Redis會重新將持久化的日誌中檔案恢復資料。

優點:

1)資料安全,aof 持久化可以配置 appendfsync 屬性,有 always,每進行一次 命令操作就記錄到 aof 檔案中一次。

2)透過 append 模式寫檔案,即使中途伺服器當機,可以透過 redis-check-aof 工具解決資料一致性問題。

缺點:

1)AOF 檔案比 RDB 檔案大,且恢復速度慢。

2)資料集大的時候,比 rdb 啟動效率低。

2、redis事務

事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端傳送來的命令請求所打斷。事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。

Redis事務的概念

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

Redis的事務總是具有ACID中的一致性和隔離性,其他特性是不支援的。當伺服器執行在AOF持久化模式下,並且appendfsync選項的值為always時,事務也具有耐久性。

Redis事務功能是透過MULTI、EXEC、DISCARD和WATCH 四個原語實現的。

事務命令:

MULTI:用於開啟一個事務,它總是返回OK。MULTI執行之後,客戶端可以繼續向伺服器傳送任意多條命令,這些命令不會立即被執行,而是被放到一個佇列中,當EXEC命令被呼叫時,所有佇列中的命令才會被執行。

EXEC:執行所有事務塊內的命令。返回事務塊內所有命令的返回值,按命令執行的先後順序排列。當操作被打斷時,返回空值 nil 。

WATCH :是一個樂觀鎖,可以為 Redis 事務提供 check-and-set (CAS)行為。可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行,監控一直持續到EXEC命令。(秒殺場景)

DISCARD:呼叫該命令,客戶端可以清空事務佇列,並放棄執行事務,且客戶端會從事務狀態中退出。

UNWATCH:命令可以取消watch對所有key的監控。

3、redis失效策略

記憶體淘汰策略

1)全域性的鍵空間選擇性移除

noeviction:當記憶體不足以容納新寫入資料時,新寫入操作會報錯。(字典庫常用)

allkeys-lru:在鍵空間中,移除最近最少使用的key。(快取常用)

allkeys-random:在鍵空間中,隨機移除某個key。

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

volatile-lru:在設定了過期時間的鍵空間中,移除最近最少使用的key。

volatile-random:在設定了過期時間的鍵空間中,隨機移除某個key。

volatile-ttl:在設定了過期時間的鍵空間中,有更早過期時間的key優先移除。

快取失效策略

定時清除:針對每個設定過期時間的key都建立指定定時器

惰性清除:訪問時判斷,對記憶體不友好

定時掃描清除:定時100ms隨機20個檢查過期的字典,若存在25%以上則繼續迴圈刪除。

4、redis讀寫模式

CacheAside旁路快取

寫請求更新資料庫後刪除快取資料。讀請求不命中查詢資料庫,查詢完成寫入快取

這些年背過的面試題——Redis篇

業務端處理所有資料訪問細節,同時利用 Lazy 計算的思想,更新 DB 後,直接刪除 cache 並透過 DB 更新,確保資料以 DB 結果為準,則可以大幅降低 cache 和 DB 中資料不一致的機率

如果沒有專門的儲存服務,同時是對資料一致性要求比較高的業務或者是快取資料更新比較複雜的業務,適合使用 Cache Aside 模式。如微博發展初期,不少業務採用這種模式


// 延遲雙刪,用以保證最終一致性,防止小機率舊資料讀請求在第一次刪除後更新資料庫public void write(String key,Object data){    redis.delKey(key);    db.updateData(data);    Thread.sleep(1000);    redis.delKey(key);}


高併發下保證絕對的一致,先刪快取再更新資料,需要用到記憶體佇列做非同步序列化。非高併發場景,先更新資料再刪除快取,延遲雙刪策略基本滿足了

  • 先更新db後刪除redis:刪除redis失敗則出現問題
  • 先刪redis後更新db:刪除redis瞬間,舊資料被回填redis
  • 先刪redis後更新db休眠後刪redis:同第二點,休眠後刪除redis 可能當機
  • java內部jvm佇列:不適用分散式場景且降低併發

Read/Write Though(讀寫穿透)

先查詢快取中資料是否存在,如果存在則直接返回,如果不存在,則由快取元件負責從資料庫中同步載入資料。 

這些年背過的面試題——Redis篇

先查詢要寫入的資料在快取中是否已經存在,如果已經存在,則更新快取中的資料,並且由快取元件同步更新到資料庫中。

這些年背過的面試題——Redis篇

使用者讀操作較多.相較於Cache aside而言更適合快取一致的場景。使用簡單遮蔽了底層資料庫的操作,只是操作快取。

場景:

微博 Feed 的 Outbox Vector(即使用者最新微博列表)就採用這種模式。一些粉絲較少且不活躍的使用者發表微博後,Vector 服務會首先查詢 Vector Cache,如果 cache 中沒有該使用者的 Outbox 記錄,則不寫該使用者的 cache 資料,直接更新 DB 後就返回,只有 cache 中存在才會透過 CAS 指令進行更新。

Write Behind Caching(非同步快取寫入)

這些年背過的面試題——Redis篇

比如對一些計數業務,一條 Feed 被點贊 1萬 次,如果更新 1萬 次 DB 代價很大,而合併成一次請求直接加 1萬,則是一個非常輕量的操作。但這種模型有個顯著的缺點,即資料的一致性變差,甚至在一些極端場景下可能會丟失資料。

5、多級快取

瀏覽器本地記憶體快取:專題活動,一旦上線,在活動期間是不會隨意變更的。

瀏覽器本地磁碟快取:Logo快取,大圖片懶載入

服務端本地記憶體快取:由於沒有持久化,重啟時必定會被穿透

服務端網路記憶體快取:Redis等,針對穿透的情況下可以繼續分層,必須保證資料庫不被壓垮

為什麼不是使用伺服器本地磁碟做快取?

當系統處理大量磁碟 IO 操作的時候,由於 CPU 和記憶體的速度遠高於磁碟,可能導致 CPU 耗費太多時間等待磁碟返回處理的結果。對於這部分 CPU 在 IO 上的開銷,我們稱為 iowait。

Redis七大經典問題

1、快取雪崩

指快取同一時間大面積的失效,所以,後面的請求都會落到資料庫上,造成資料庫短時間內承受大量請求而崩掉。

  解決方案:

  • Redis 高可用,主從+哨兵,Redis cluster,避免全盤崩潰;
  • 本地 ehcache 快取 + hystrix 限流&降級,避免 MySQL 被打死;
  • 快取資料的過期時間設定隨機,防止同一時間大量資料過期現象發生;
  • 邏輯上永不過期給每一個快取資料增加相應的快取標記,快取標記失效則更新資料快取;
  • 多級快取,失效時透過二級更新一級,由第三方外掛更新二級快取;

2、快取穿透

https://blog.csdn.net/lin777lin/article/details/105666839

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

解決方案:

1)介面層增加校驗,如使用者鑑權校驗,id做基礎校驗,id<=0的直接攔截;

2)從快取取不到的資料,在資料庫中也沒有取到,這時也可以將key-value對寫為key-null,快取有效時間可以設定短點,如30秒。這樣可以防止攻擊使用者反覆用同一個id暴力攻擊;

3)採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的 bitmap 中,一個一定不存在的資料會被這個 bitmap 攔截掉,從而避免了對底層儲存系統的查詢壓力。(寧可錯殺一千不可放過一人)

3、快取擊穿

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

解決方案:

1)設定熱點資料永遠不過期,非同步執行緒處理。
2)加寫回操作加互斥鎖,查詢失敗預設值快速返回。

3)快取預熱

系統上線後,將相關可預期(例如排行榜)熱點資料直接載入到快取。

寫一個快取重新整理頁面,手動操作熱點資料(例如廣告推廣)上下線。

4、資料不一致

在快取機器的頻寬被打滿,或者機房網路出現波動時,快取更新失敗,新資料沒有寫入快取,就會導致快取和 DB 的資料不一致。快取 rehash 時,某個快取機器反覆異常,多次上下線,更新請求多次 rehash。這樣,一份資料存在多個節點,且每次 rehash 只更新某個節點,導致一些快取節點產生髒資料。

  • Cache 更新失敗後,可以進行重試,則將重試失敗的 key 寫入mq,待快取訪問恢復後,將這些 key 從快取刪除。這些 key 在再次被查詢時,重新從 DB 載入,從而保證資料的一致性
  • 快取時間適當調短,讓快取資料及早過期後,然後從 DB 重新載入,確保資料的最終一致性。
  • 不採用 rehash 漂移策略,而採用快取分層策略,儘量避免髒資料產生。

5、資料併發競爭

資料併發競爭在大流量系統也比較常見,比如車票系統,如果某個火車車次快取資訊過期,但仍然有大量使用者在查詢該車次資訊。又比如微博系統中,如果某條微博正好被快取淘汰,但這條微博仍然有大量的轉發、評論、贊。上述情況都會造成併發競爭讀取的問題。

  • 寫回操作加互斥鎖,查詢失敗預設值快速返回。
  • 對快取資料保持多個備份,減少併發競爭的機率

6、熱點key問題

明星結婚、離婚、出軌這種特殊突發事件,比如奧運、春節這些重大活動或節日,還比如秒殺、雙12、618 等線上促銷活動,都很容易出現 Hot key 的情況。

如何提前發現HotKey?

  • 對於重要節假日、線上促銷活動這些提前已知的事情,可以提前評估出可能的熱 key 來。
  • 而對於突發事件,無法提前評估,可以透過 Spark,對應流任務進行實時分析,及時發現新發布的熱點 key。而對於之前已發出的事情,逐步發酵成為熱 key 的,則可以透過 Hadoop 對批處理任務離線計算,找出最近歷史資料中的高頻熱 key。

解決方案:

  • 這 n 個 key 分散存在多個快取節點,然後 client 端請求時,隨機訪問其中某個字尾的 hotkey,這樣就可以把熱 key 的請求打散,避免一個快取節點過載;
  • 快取叢集可以單節點進行主從複製和垂直擴容;
  • 利用應用內的前置快取,但是需注意需要設定上限;
  • 延遲不敏感,定時重新整理,實時感知用主動重新整理;
  • 和快取穿透一樣,限制逃逸流量,單請求進行資料回源並重新整理前置;
  • 無論如何設計,最後都要寫一個兜底邏輯,千萬級流量說來就來;

7、BigKey問題

比如網際網路系統中需要儲存使用者最新 1萬 個粉絲的業務,比如一個使用者個人資訊快取,包括基本資料、關係圖譜計數、發 feed 統計等。微博的 feed 內容快取也很容易出現,一般使用者微博在 140 字以內,但很多使用者也會發表 1千 字甚至更長的微博內容,這些長微博也就成了大 key

  • 首先Redis底層資料結構裡,根據Value的不同,會進行資料結構的重新選擇;
  • 可以擴充套件新的資料結構,進行序列化構建,然後透過 restore 一次性寫入;
  • 將大 key 分拆為多個 key,設定較長的過期時間;

Redis分割槽容錯

1、redis資料分割槽

Hash:(不穩定)

客戶端分片:雜湊+取餘

節點伸縮:資料節點關係變化,導致資料遷移

遷移數量和新增節點數量有關:建議翻倍擴容

一個簡單直觀的想法是直接用Hash來計算,以Key做雜湊後對節點數取模。可以看出,在key足夠分散的情況下,均勻性可以獲得,但一旦有節點加入或退出,所有的原有節點都會受到影響,穩定性無從談起。

一致性Hash:(不均衡)

客戶端分片:雜湊+順時針(最佳化取餘)

節點伸縮:隻影響鄰近節點,但是還是有資料遷移

翻倍伸縮:保證最小遷移資料和負載均衡

一致性Hash可以很好的解決穩定問題,可以將所有的儲存節點排列在收尾相接的Hash環上,每個key在計算Hash後會順時針找到先遇到的一組儲存節點存放。而當有節點加入或退出時,僅影響該節點在Hash環上順時針相鄰的後續節點,將資料從該節點接收或者給予。但這又帶來均勻性的問題,即使可以將儲存節點等距排列,也會在儲存節點個數變化時帶來資料的不均勻。

Codis的Hash槽

Codis 將所有的 key 預設劃分為 1024 個槽位(slot),它首先對客戶端傳過來的 key 進行 crc32 運算計算 雜湊值,再將 hash 後的整數值對 1024 這個整數進行取模得到一個餘數,這個餘數就是對應 key 的槽位。

RedisCluster

Redis-cluster把所有的物理節點對映到[0-16383]個slot上,對key採用crc16演算法得到hash值後對16384取模,基本上採用平均分配和連續分配的方式。

2、主從模式=簡單

主從模式最大的優點是部署簡單,最少兩個節點便可以構成主從模式,並且可以透過讀寫分離避免讀和寫同時不可用。不過,一旦 Master 節點出現故障,主從節點就無法自動切換,直接導致 SLA 下降。所以,主從模式一般適合業務發展初期,併發量低,運維成本低的情況

這些年背過的面試題——Redis篇

主從複製原理:

①透過從伺服器傳送到PSYNC命令給主伺服器;

②如果是首次連線,觸發一次全量複製。此時主節點會啟動一個後臺執行緒,生成 RDB 快照檔案;

③主節點會將這個 RDB 傳送給從節點,slave 會先寫入本地磁碟,再從本地磁碟載入到記憶體中;

④master會將此過程中的寫命令寫入快取,從節點實時同步這些資料;

⑤如果網路斷開了連線,自動重連後主節點透過命令傳播增量複製給從節點部分缺少的資料;

缺點

所有的slave節點資料的複製和同步都由master節點來處理,會照成master節點壓力太大,使用主從從結構來解決,redis4.0中引入psync2 解決了slave重啟後仍然可以增量同步。

3、哨兵模式=讀多

由一個或多個sentinel例項組成sentinel叢集可以監視一個或多個主伺服器和多個從伺服器。哨兵模式適合讀請求遠多於寫請求的業務場景,比如在秒殺系統中用來快取活動資訊。如果寫請求較多,當叢集 Slave 節點數量多了後,Master 節點同步資料的壓力會非常大。

這些年背過的面試題——Redis篇

當主伺服器進入下線狀態時,sentinel可以將該主伺服器下的某一從伺服器升級為主伺服器繼續提供服務,從而保證redis的高可用性。

檢測主觀下線狀態

Sentinel每秒一次向所有與它建立了命令連線的例項(主伺服器、從伺服器和其他Sentinel)傳送PING命 令

例項在down-after-milliseconds毫秒內返回無效回覆Sentinel就會認為該例項主觀下線(SDown)

檢查客觀下線狀態

當一個Sentinel將一個主伺服器判斷為主觀下線後 ,Sentinel會向監控這個主伺服器的所有其他Sentinel傳送查詢主機狀態的命令

如果達到Sentinel配置中的quorum數量的Sentinel例項都判斷主伺服器為主觀下線,則該主伺服器就會被判定為客觀下線(ODown)。

選舉Leader Sentinel

當一個主伺服器被判定為客觀下線後,監視這個主伺服器的所有Sentinel會透過選舉演算法(raft),選出一個Leader Sentinel去執行**failover(故障轉移)**操作。

Raft演算法

Raft協議是用來解決分散式系統一致性問題的協議。Raft協議描述的節點共有三種狀態:Leader, Follower, Candidate。Raft協議將時間切分為一個個的Term(任期),可以認為是一種“邏輯時間”。選舉流程:

①Raft採用心跳機制觸發Leader選舉系統啟動後,全部節點初始化為Follower,term為0

②節點如果收到了RequestVote或者AppendEntries,就會保持自己的Follower身份 
③節點如果一段時間內沒收到AppendEntries訊息,在該節點的超時時間內還沒發現Leader,Follower就會轉換成Candidate,自己開始競選Leader。一旦轉化為Candidate,該節點立即開始下面幾件事情:

--增加自己的term,啟動一個新的定時器;

--給自己投一票,向所有其他節點傳送RequestVote,並等待其他節點的回覆。
④如果在計時器超時前,節點收到多數節點的同意投票,就轉換成Leader。同時透過 AppendEntries,向其他節點傳送通知。
⑤每個節點在一個term內只能投一票,採取先到先得的策略,Candidate投自己, Follower會投給第一個收到RequestVote的節點。

⑥Raft協議的定時器採取隨機超時時間(選舉的關鍵),先轉為Candidate的節點會先發起投票,從而獲得多數票。

主伺服器的選擇

當選舉出Leader Sentinel後,Leader Sentinel會根據以下規則去從伺服器中選擇出新的主伺服器。

  1. 過濾掉主觀、客觀下線的節點
  2. 選擇配置slave-priority最高的節點,如果有則返回沒有就繼續選擇
  3. 選擇出複製偏移量最大的系節點,因為複製偏移量越大則資料複製的越完整
  4. 選擇run_id最小的節點,因為run_id越小說明重啟次數越少

故障轉移

當Leader Sentinel完成新的主伺服器選擇後,Leader Sentinel會對下線的主伺服器執行故障轉移操作,主要有三個步驟:

1、它會將失效 Master 的其中一個 Slave 升級為新的 Master , 並讓失效 Master 的其他 Slave 改為複製新的 Master ;

2、當客戶端試圖連線失效的 Master 時,叢集會向客戶端返回新 Master 的地址,使得叢集當前狀態只有一個Master。

3、Master 和 Slave 伺服器切換後, Master 的 redis.conf 、 Slave 的 redis.conf 和 sentinel.conf 的配置檔案的內容都會發生相應的改變,即 Master 主伺服器的 redis.conf配置檔案中會多一行 replicaof 的配置, sentinel.conf 的監控目標會隨之調換。

4、叢集模式=寫多

為了避免單一節點負載過高導致不穩定,叢集模式採用一致性雜湊演算法或者雜湊槽的方法將 Key 分佈到各個節點上。其中,每個 Master 節點後跟若干個 Slave 節點,用於出現故障時做主備切換,客戶端可以連線任意 Master 節點,叢集內部會按照不同 key 將請求轉發到不同的 Master 節點

叢集模式是如何實現高可用的呢?叢集內部節點之間會互相定時探測對方是否存活,如果多數節點判斷某個節點掛了,則會將其踢出叢集,然後從 Slave 節點中選舉出一個節點替補掛掉的 Master 節點。整個原理基本和哨兵模式一致。

雖然叢集模式避免了 Master 單節點的問題,但叢集內同步資料時會佔用一定的頻寬。所以,只有在寫操作比較多的情況下人們才使用叢集模式,其他大多數情況,使用哨兵模式都能滿足需求

5、分散式鎖

利用Watch實現Redis樂觀鎖

樂觀鎖基於CAS(Compare And Swap)比較並替換思想,不會產生鎖等待而消耗資源,但是需要反覆的重試,但也是因為重試的機制,能比較快的響應。因此我們可以利用redis來實現樂觀鎖(秒殺)。具體思路如下:

1、利用redis的watch功能,監控這個redisKey的狀態值
2、獲取redisKey的值,建立redis事務,給這個key的值+1
3、執行這個事務,如果key的值被修改過則回滾,key不加1

利用setnx防止庫存超賣
分散式鎖是控制分散式系統之間同步訪問共享資源的一種方式。 利用Redis的單執行緒特性對共享資源進行序列化處理

// 獲取鎖推薦使用set的方式String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);String result = jedis.setnx(lockKey, requestId); //如執行緒死掉,其他執行緒無法獲取到鎖

// 釋放鎖,非原子操作,可能會釋放其他執行緒剛加上的鎖if (requestId.equals(jedis.get(lockKey))) {   jedis.del(lockKey);}// 推薦使用redis+lua指令碼String lua = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";Object result = jedis.eval(lua, Collections.singletonList(lockKey),
分散式鎖存在的問題:

  • 客戶端長時間阻塞導致鎖失效問題

計算時間內非同步啟動另外一個執行緒去檢查的問題,這個key是否超時,當鎖超時時間快到期且邏輯未執行完,延長鎖超時時間。

  • **Redis伺服器時鐘漂移問題導致同時加鎖

redis的過期時間是依賴系統時鐘的,如果時鐘漂移過大時 理論上是可能出現的 **會影響到過期時間的計算。

  • 單點例項故障,鎖未及時同步導致丟失

RedLock演算法

  1. 獲取當前時間戳T0,配置時鐘漂移誤差T1
  2. 短時間內逐個獲取全部N/2+1個鎖,結束時間點T2
  3. 實際鎖能使用的處理時長變為:TTL - (T2 - T0)- T1
    該方案透過多節點來防止Redis的單點故障,效果一般,也無法防止:
  • 主從切換導致的兩個客戶端同時持有鎖

    大部分情況下持續時間極短,而且使用Redlock在切換的瞬間獲取到節點的鎖,也存在問題。已經是極低機率的時間,無法避免。Redis分散式鎖適合冪等性事務,如果一定要保證安全,應該使用Zookeeper或者DB,但是,效能會急劇下降

與zookeeper分散式鎖對比

  • redis 分散式鎖,其實需要自己不斷去嘗試獲取鎖,比較消耗效能。
  • zk 分散式鎖,註冊個監聽器即可,不需要不斷主動嘗試獲取鎖,ZK獲取鎖會按照加鎖的順序,所以是公平鎖,效能和mysql差不多,和redis差別大

Redission生產環境的分散式鎖

Redisson是基於NIO的Netty框架上的一個Java駐記憶體資料網格(In-Memory Data Grid)分散式鎖開源元件。 

這些年背過的面試題——Redis篇

但當業務必須要資料的強一致性,即不允許重複獲得鎖,比如金融場景(重複下單,重複轉賬),請不要使用redis分散式鎖。可以使用CP模型實現,比如:zookeeper和etcd

這些年背過的面試題——Redis篇

6、redis心跳檢測

在命令傳播階段,從伺服器預設會以每秒一次的頻率向主伺服器傳送ACK命令:

1、檢測主從的連線狀態 檢測主從伺服器的網路連線狀態

lag的值應該在0或1之間跳動,如果超過1則說明主從之間的連線有 故障。

2、輔助實現min-slaves,Redis可以透過配置防止主伺服器在不安全的情況下執行寫命令


min-slaves-to-write 3 (min-replicas-to-write 3 )min-slaves-max-lag 10 (min-replicas-max-lag 10)


上面的配置表示:從伺服器的數量少於3個,或者三個從伺服器的延遲(lag)值都大於或等於10 秒時,主伺服器將拒絕執行寫命令。

3、檢測命令丟失,增加重傳機制

如果因為網路故障,主伺服器傳播給從伺服器的寫命令在半路丟失,那麼當從伺服器向主伺服器發 送REPLCONF ACK命令時,主伺服器將發覺從伺服器當前的複製偏移量少於自己的複製偏移量, 然後主伺服器就會根據從伺服器提交的複製偏移量,在複製積壓緩衝區裡面找到從伺服器缺少的資料,並將這些資料重新傳送給從伺服器。

Redis實戰

1、Redis最佳化

這些年背過的面試題——Redis篇

讀寫方式
簡單來說就是不用keys等,用range、contains之類。比如,使用者粉絲數,大 V 的粉絲更是高達幾千萬甚至過億,因此,獲取粉絲列表只能部分獲取。另外在判斷某使用者是否關注了另外一個使用者時,也只需要關注列表上進行檢查判斷,然後返回 True/False 或 0/1 的方式更為高效。
KV size
如果單個業務的 KV size 過大,需要分拆成多個 KV 來快取。拆分時應考慮訪問頻率。
key 的數量
如果資料量巨大,則在快取中儘可能只保留頻繁訪問的熱資料,對於冷資料直接訪問 DB。

讀寫峰值

如果小於 10萬 級別,簡單分拆到獨立 Cache 池即可
如果達到 100萬 級的QPS,則需要對 Cache 進行分層處理,可以同時使用 Local-Cache 配合遠端 cache,甚至遠端快取內部繼續分層疊加分池進行處理。(多級快取)

命中率
快取的命中率對整個服務體系的效能影響甚大。對於核心高併發訪問的業務,需要預留足夠的容量,確保核心業務快取維持較高的命中率。比如微博中的 Feed Vector Cache(熱點資訊),常年的命中率高達 99.5% 以上。為了持續保持快取的命中率,快取體系需要持續監控,及時進行故障處理或故障轉移。同時在部分快取節點異常、命中率下降時,故障轉移方案,需要考慮是採用一致性 Hash 分佈的訪問漂移策略,還是採用資料多層備份策略。

過期策略

可以設定較短的過期時間,讓冷 key 自動過期;也可以讓 key 帶上時間戳,同時設定較長的過期時間,比如很多業務系統內部有這樣一些 key:key_20190801。

快取穿透時間
平均快取穿透載入時間在某些業務場景下也很重要,對於一些快取穿透後,載入時間特別長或者需要複雜計算的資料,而且訪問量還比較大的業務資料,要配置更多容量,維持更高的命中率,從而減少穿透到 DB 的機率,來確保整個系統的訪問效能。

快取可運維性

對於快取的可運維性考慮,則需要考慮快取體系的叢集管理,如何進行一鍵擴縮容,如何進行快取元件的升級和變更,如何快速發現並定位問題,如何持續監控報警,最好有一個完善的運維平臺,將各種運維工具進行整合。

快取安全性

對於快取的安全性考慮,一方面可以限制來源 IP,只允許內網訪問,同時加密鑑權訪問。

2、Redis熱升級

在 Redis 需要升級版本或修復 bug 時,如果直接重啟變更,由於需要資料恢復,這個過程需要近 10 分鐘的時間,時間過長,會嚴重影響系統的可用性。面對這種問題,可以對 Redis 擴充套件熱升級功能,從而在毫秒級完成升級操作,完全不影響業務訪問。

熱升級方案如下,首先構建一個 Redis 殼程式,將 redisServer 的所有屬性(包括redisDb、client等)儲存為全域性變數。然後將 Redis 的處理邏輯程式碼全部封裝到動態連線庫 so 檔案中。Redis 第一次啟動,從磁碟載入恢復資料,在後續升級時,透過指令,殼程式重新載入 Redis 新的 redis-4.so 到 redis-5.so 檔案,即可完成功能升級,毫秒級完成 Redis 的版本升級。而且整個過程中,所有 Client 連線仍然保留,在升級成功後,原有 Client 可以繼續進行讀寫操作,整個過程對業務完全透明。

來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70027826/viewspace-3006081/,如需轉載,請註明出處,否則將追究法律責任。

相關文章