這些年背過的面試題——Redis篇
來源:阿里雲開發者
阿里妹導讀
WhyRedis
速度快,完全基於記憶體,使用C語言實現,網路層使用epoll解決高併發問題,單執行緒模型避免了不必要的上下文切換及競爭條件;
與傳統資料庫不同的是 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
使用場景:
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 的場景:
對效能有非常高的要求
不經常變化,佔用記憶體不大
有訪問整個集合的需求
資料允許不實時一致
快取過期和淘汰機制
在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向使用者推薦使用者感興趣的電影
EVCache叢集在峰值每秒可以處理200kb的請求,
Netflflix生產系統中部署的EVCache經常要處理超過每秒3000萬個請求,儲存數十億個物件,
跨數千臺memcached伺服器。整個EVCache叢集每天處理近2萬億個請求。
EVCache叢集響應平均延時大約是1-5毫秒,最多不會超過20毫秒。
EVCache叢集的快取命中率在99%左右。
典型部署
EVCache 是線性擴充套件的,可以在一分鐘之內完成擴容,在幾分鐘之內完成負載均衡和快取預熱。
3、客戶端透過key使用一致性hash演算法,將資料分片到叢集上。
6、ETCD
和Zookeeper一樣,CP模型追求資料一致性,越來越多的系統開始用它儲存關鍵資料。比如,秒殺系統經常用它儲存各節點資訊,以便控制消費 MQ 的服務數量。還有些業務系統的配置資料,也會透過 etcd 實時同步給業務系統的各節點,比如,秒殺管理後臺會使用 etcd 將秒殺活動的配置資料實時同步給秒殺 API 服務各節點。
Redis底層
1、redis資料型別
2、相關API
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)是由一系列特殊編碼的連續記憶體塊組成的順序型資料結構,節省內容
sorted-set和hash元素個數少且是小整數或短字串(直接使用)
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引數來定義快照的週期。
優點:
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旁路快取
寫請求更新資料庫後刪除快取資料。讀請求不命中查詢資料庫,查詢完成寫入快取
業務端處理所有資料訪問細節,同時利用 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(讀寫穿透)
先查詢快取中資料是否存在,如果存在則直接返回,如果不存在,則由快取元件負責從資料庫中同步載入資料。
先查詢要寫入的資料在快取中是否已經存在,如果已經存在,則更新快取中的資料,並且由快取元件同步更新到資料庫中。
使用者讀操作較多.相較於Cache aside而言更適合快取一致的場景。使用簡單遮蔽了底層資料庫的操作,只是操作快取。
場景:
微博 Feed 的 Outbox Vector(即使用者最新微博列表)就採用這種模式。一些粉絲較少且不活躍的使用者發表微博後,Vector 服務會首先查詢 Vector Cache,如果 cache 中沒有該使用者的 Outbox 記錄,則不寫該使用者的 cache 資料,直接更新 DB 後就返回,只有 cache 中存在才會透過 CAS 指令進行更新。
Write Behind Caching(非同步快取寫入)
比如對一些計數業務,一條 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、快取擊穿
這時由於併發使用者特別多,同時讀快取沒讀到資料,又同時去資料庫去取資料,引起資料庫壓力瞬間增大,造成過大壓力。和快取雪崩不同的是,快取擊穿指併發查同一條資料,快取雪崩是不同資料都過期了,很多資料都查不到從而查資料庫。
解決方案:
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 下降。所以,主從模式一般適合業務發展初期,併發量低,運維成本低的情況
主從複製原理:
①透過從伺服器傳送到PSYNC命令給主伺服器;
②如果是首次連線,觸發一次全量複製。此時主節點會啟動一個後臺執行緒,生成 RDB 快照檔案;
③主節點會將這個 RDB 傳送給從節點,slave 會先寫入本地磁碟,再從本地磁碟載入到記憶體中;
④master會將此過程中的寫命令寫入快取,從節點實時同步這些資料;
⑤如果網路斷開了連線,自動重連後主節點透過命令傳播增量複製給從節點部分缺少的資料;
缺點
所有的slave節點資料的複製和同步都由master節點來處理,會照成master節點壓力太大,使用主從從結構來解決,redis4.0中引入psync2 解決了slave重啟後仍然可以增量同步。
3、哨兵模式=讀多
由一個或多個sentinel例項組成sentinel叢集可以監視一個或多個主伺服器和多個從伺服器。哨兵模式適合讀請求遠多於寫請求的業務場景,比如在秒殺系統中用來快取活動資訊。如果寫請求較多,當叢集 Slave 節點數量多了後,Master 節點同步資料的壓力會非常大。
當主伺服器進入下線狀態時,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採用心跳機制觸發Leader選舉系統啟動後,全部節點初始化為Follower,term為0
--增加自己的term,啟動一個新的定時器;
⑥Raft協議的定時器採取隨機超時時間(選舉的關鍵),先轉為Candidate的節點會先發起投票,從而獲得多數票。
主伺服器的選擇
當選舉出Leader Sentinel後,Leader Sentinel會根據以下規則去從伺服器中選擇出新的主伺服器。
過濾掉主觀、客觀下線的節點 選擇配置slave-priority最高的節點,如果有則返回沒有就繼續選擇 選擇出複製偏移量最大的系節點,因為複製偏移量越大則資料複製的越完整 選擇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
// 獲取鎖推薦使用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演算法
獲取當前時間戳T0,配置時鐘漂移誤差T1 短時間內逐個獲取全部N/2+1個鎖,結束時間點T2 實際鎖能使用的處理時長變為: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分散式鎖。可以使用CP模型實現,比如:zookeeper和etcd。
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、檢測命令丟失,增加重傳機制
如果因為網路故障,主伺服器傳播給從伺服器的寫命令在半路丟失,那麼當從伺服器向主伺服器發 送REPLCONF ACK命令時,主伺服器將發覺從伺服器當前的複製偏移量少於自己的複製偏移量, 然後主伺服器就會根據從伺服器提交的複製偏移量,在複製積壓緩衝區裡面找到從伺服器缺少的資料,並將這些資料重新傳送給從伺服器。
Redis實戰
1、Redis最佳化
讀寫峰值
如果小於 10萬 級別,簡單分拆到獨立 Cache 池即可
如果達到 100萬 級的QPS,則需要對 Cache 進行分層處理,可以同時使用 Local-Cache 配合遠端 cache,甚至遠端快取內部繼續分層疊加分池進行處理。(多級快取)
過期策略
可以設定較短的過期時間,讓冷 key 自動過期;也可以讓 key 帶上時間戳,同時設定較長的過期時間,比如很多業務系統內部有這樣一些 key:key_20190801。
快取可運維性
對於快取的可運維性考慮,則需要考慮快取體系的叢集管理,如何進行一鍵擴縮容,如何進行快取元件的升級和變更,如何快速發現並定位問題,如何持續監控報警,最好有一個完善的運維平臺,將各種運維工具進行整合。
快取安全性
對於快取的安全性考慮,一方面可以限制來源 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-3006219/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 這些年背過的面試題——MySQL篇面試題MySql
- 這些年背過的面試題——Kafka篇面試題Kafka
- 這些年背過的面試題——SpringCloud篇面試題SpringGCCloud
- 轉載 -這些年背過的面試題——架構設計篇面試題架構
- 面試中關於Redis的問題看這篇就夠了面試Redis
- 面試雲端計算崗位時這些面試題不能錯過面試題
- 【面試篇】金九銀十面試季,這些面試題你都會了嗎?面試題
- 最新阿里Java面試題,這些面試題你會嗎?阿里Java面試題
- 面試中的這些坑,你踩過幾個?面試
- 面試 HTTP ,99% 的面試官都愛問這些問題面試HTTP
- 那些年,碰上過的面試題面試題
- 這幾道Redis面試題都不懂,offer肯定與你擦肩而過Redis面試題
- Redis過期策略及實現原理-Redis面試題Redis面試題
- MyBatis面試題集合,90%會遇到這些問題MyBatis面試題
- 熟悉這幾道 Redis 高頻面試題,面試不用愁Redis面試題
- 乾貨分享,值得收藏:搞懂這些redis知識點,還怕幹不過面試官?Redis面試
- 征服面試官:OkHttp 原理篇 掌握這篇面試題彙總,吊打面試官!HTTP面試題
- 面試現場:這些常問的面試題你都會了嗎面試題
- 這些 SpringBoot 面試題你會嗎?Spring Boot面試題
- 這些 iOS 面試基礎題,你會麼?iOS面試
- 有了這些java面試題目和答案,你還有什麼過不去的梗Java面試題
- redis面試題Redis面試題
- 3年Java工程師面試必問!這些題一定要會!Java工程師面試
- 做到這些面試事半功倍面試
- 08年出的一些前端面試題前端面試題
- 從阿里、騰訊的面試真題中總結了這11個Redis高頻面試題阿里Redis面試題
- Linux常見面試題,這些你知道多少?Linux面試題
- CEO面試你時喜歡問這些問題面試
- 這些javascript面試題,你做對了幾道?JavaScript面試題
- 99面試常問:中高階開發面試必問的Redis,看這篇就夠了!面試Redis
- 講課這些天(二):那些年踩過的坑
- C,java,Python,這些名字背後的江湖!JavaPython
- 看了這篇Dubbo RPC面試題,讓天下沒有難面的面試題!RPC面試題
- 前端開發面試題——HTML篇(你想要的,都在這裡)前端面試題HTML
- 大廠Android面試,居然還問這些問題!Android面試
- 學習Python這些面試題你都知道嗎?Python面試題
- 跳槽時,這些Java面試題99%會被問到Java面試題
- 你可能也罵過這兩個面試題!面試題