本篇文章跟大家分享一些Redis高頻面試題。有一定的參考價值,有需要的朋友可以參考一下,希望對大家有幫助。
Redis(Remote Dictionary Server) 是使用C 語言寫的,開源的( BSD許可)高效能非關係型(NoSQL)的鍵值對資料庫。
Redis 可以儲存鍵和五種不同類型的值之間的對應。鍵的類型只能為字串,值支援五種資料類型:字串、列表、集合、散列表、有序集合。
與傳統資料庫不同的是Redis 的資料是存在記憶體中的,所以讀寫速度非常快,因此redis 被廣泛應用於快取方向,每秒可以處理超過10萬次讀寫操作,是已知效能最快的Key-Value DB。另外,Redis 也常用來做分散式鎖。除此之外,Redis 支援交易 、持久化、LUA腳本、LRU驅動事件、多種叢集方案。
優點
讀寫效能優異, Redis能讀的速度是110000次/s,寫的速度是81000次/s。
支持資料持久化,支援AOF和RDB兩種持久化方式。
支援事務,Redis的所有操作都是原子性的,同時Redis也支援對幾個操作合併後的原子性執行。
資料結構豐富,除了支援string類型的value外還支援hash、set、zset、list等資料結構。
支援主從複製,主機會自動將資料同步到從機,可以進行讀寫分離。
缺點
資料庫容量受到實體記憶體的限制,不能用作海量資料的高效能讀寫,因此Redis適合的場景主要限制在較小資料量的高性能操作和運算上。
Redis 不具備自動容錯和復原功能,主機從機的宕機都會導致前端部分讀寫請求失敗,需要等待機器重新啟動或手動切換前端的IP才能恢復。
主機宕機,當機前有部分資料未能及時同步到從機,切換IP後還會引入資料不一致的問題,降低了系統的可用性。
Redis 較難支援線上擴容,在叢集容量達到上限時線上擴容會變得很複雜。為避免這個問題,維運人員在系統上線時必須確保有足夠的空間,這對資源造成了極大的浪費。
主要從「高效能」和「高並發」這兩點來看待這個問題。
高效能:
假如使用者第一次存取資料庫中的某些資料。這個過程會比較慢,因為是從硬碟上讀取的。將該用戶存取的資料存在數快取中,這樣下一次再存取這些資料的時候就可以直接從快取中取得了。操作快取就是直接操作內存,所以速度相當快。如果資料庫中的對應資料改變的之後,同步改變快取中對應的資料即可!
高並發:
直接操作快取能夠承受的請求是遠大於直接存取資料庫的,所以我們可以考慮把資料庫中的部分資料轉移到快取中去,這樣使用者的一部分請求會直接到快取這裡而不用經過資料庫。
快取分為本地快取和分散式快取.以Java 為例,使用自帶的map 或者guava 實現的是本地緩存,最主要的特點是輕量級以及快速,生命週期隨著jvm 的銷毀而結束,並且在多實例的情況下,每個實例都需要各自保存一份緩存,緩存不具一致性。
使用 redis 或 memcached 之類的稱為分散式緩存,在多實例的情況下,各實例共用一份緩存數據,緩存具有一致性。缺點是需要保持 redis 或 memcached服務的高可用,整個程式架構上較為複雜。
1、完全基於內存,絕大部分請求是純粹的記憶體操作,非常快速。資料存在記憶體中,類似HashMap,HashMap 的優勢就是查找和操作的時間複雜度都是O(1);
2、資料結構簡單,對資料操作也簡單,Redis 中的資料結構是專門進行設計的;
3、採用單線程,避免了不必要的上下文切換和競爭條件,也不存在多進程或者多線程導致的切換而消耗CPU,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有因為可能出現死鎖而導致的效能消耗;
4、使用多路I/O 復用模型,非阻塞IO;
5、使用底層模型不同,它們之間底層實作方式以及與客戶端之間通訊的應用協定不一樣,Redis 直接自己建構了VM 機制,因為一般的系統呼叫系統函數的話,會浪費一定的時間去移動和請求;
Redis主要有5種資料型,包括String,List,Set,Zset,Hash,滿足大部分的使用要求
類型 |
儲存值 |
操作 |
##應用場景 |
對整個字串或字串的其中一部分執行操作; | 對整數和浮點數執行自增或自減操作 | 做簡單的鍵值對快取||
從兩端壓入或彈出元素; | 對單一或多個元素進行修剪,只保留一個範圍內的元素 | 儲存一些清單型的資料結構,類似粉絲清單、文章的評論清單之類的資料||
SET #無序集合 |
##」新增、取得、移除單一元素; | 檢查一個元素是否存在於集合中;||
交集、並集、差集的操作,例如交集,可以把兩個人的粉絲列表整一個交集 |
哈希 |
新增、取得、移除單一鍵值對; | |
結構化的數據,例如一個物件 | #ZSET 有序集合 |
新增、取得、刪除元素; |
總結一
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 可以實現有序性操作,從而實現排行榜等功能。
總結二
Redis相比其他緩存,有一個非常大的優勢,就是支援多種資料類型。
資料型態說明string字串,最簡單的k-v儲存hashhash格式,value為field和value,適合ID-Detail這樣的場景。 list簡單的list,順序列表,支援首位或結尾插入資料set無序list,查找速度快,適合交集、並集、差集處理sorted set有序的set
其實,透過上面的數據類型的特性,基本就能想到合適的應用場景了。
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 提供兩種持久化機制RDB(預設) 和AOF 機制;
優點:
1、只有一個檔案 dump.rdb,方便持久化。 2、容災性好,一個檔案可以儲存到安全的磁碟。 3、效能最大化,fork 子程序來完成寫入操作,讓主程序繼續處理指令,所以是 IO 最大化。使用單獨子程序來進行持久化,主進程不會進行任何 IO 操作,保證了 redis 的高效能。4、相對於資料集大時,比 AOF 的啟動效率更高。
缺點:
1、資料安全性低。 RDB 是間隔一段時間進行持久化,如果持久化之間 redis 發生故障,會發生資料遺失。所以這種方式更適合資料要求不嚴謹的時候)
2、AOF(Append-only file)持久化方式:是指所有的命令列記錄以redis 命令請求協議的格式完全持久化存儲)儲存為aof 檔案。
AOF持久化(即Append Only File持久化),則是將Redis執行的每次寫指令記錄到單獨的日誌檔案中,當重啟Redis會重新將持久化的日誌中檔案還原資料。
當兩種方式同時開啟時,資料恢復Redis會優先選擇AOF恢復。
優點:
1、資料安全,aof 持久化可以設定appendfsync 屬性,有always,每進行一次指令操作就記錄到aof 文件中一次。
2、透過 append 模式寫入文件,即使中途伺服器當機,可以透過 redis-check-aof 工具解決資料一致性問題。
3、AOF 機制的 rewrite 模式。 AOF 檔案沒被rewrite 之前(檔案過大時會對指令合併重寫),可以刪除其中的某些指令(例如誤操作的flushall)
缺點:
1、AOF 檔案比RDB 檔案大,且恢復速度慢。
2、資料集大的時候,比 rdb 啟動效率低。
優缺點是什麼?
AOF檔比RDB更新頻率高,優先使用AOF還原資料
AOF比RDB更安全也更大
RDB效能比AOF好
如果兩個都配了優先載入AOF
一般來說, 如果你想達到足以媲美PostgreSQL的資料安全性,你應該同時使用兩種持久化功能。在這種情況下,當 Redis 重新啟動的時候會優先載入AOF檔案來恢復原始的數據,因為在通常情況下AOF檔案保存的資料集比RDB檔案保存的資料集要完整。
如果你非常關心你的數據, 但仍然可以承受數分鐘以內的資料遺失,那麼你可以只使用RDB持久化。
有許多使用者都只使用AOF持久化,但不建議使用這種方式,因為定時產生RDB快照(snapshot)非常便於進行資料庫備份,且RDB 復原資料集的速度也要比AOF恢復的速度要快,除此之外,使用RDB還可以避免AOF程式的bug。
如果你只希望你的資料在伺服器運作的時候存在,你也可以不使用任何持久化方式。
如果Redis被當作快取使用,使用一致性雜湊實作動態擴容縮容。
如果Redis被當作一個持久化儲存使用,必須使用固定的keys-to-nodes映射關係,節點的數量一旦確定不能變化。否則的話(即Redis節點需要動態變化的情況),必須使用可以在運行時進行資料再平衡的一套系統,而當前只有Redis叢集可以做到這樣。
我們都知道,Redis是key-value資料庫,我們可以設定Redis中快取的key的過期時間。 Redis的過期策略就是指當Redis中快取的key過期了,Redis如何處理。
過期策略通常有以下三種:
定時過期:每個設定過期時間的key都需要建立一個定時器,到過期時間就會立即清除。此策略可以立即清除過期的數據,對記憶體很友善;但是會佔用大量的CPU資源去處理過期的數據,從而影響快取的回應時間和吞吐量。
惰性過期:只有當存取一個key時,才會判斷該key是否已過期,過期則清除。此策略可以最大化地節省CPU資源,卻對記憶體非常不友善。極端情況可能出現大量的過期key沒有再次被訪問,從而不會被清除,佔用大量內存。
定期過期:每隔一定的時間,會掃描一定數量的資料庫的expires字典中一定數量的key,並清除其中已過期的key。該策略是前兩者的一個折衷方案。透過調整定時掃描的時間間隔和每次掃描的限定耗時,可以在不同情況下使得CPU和記憶體資源達到最優的平衡效果。
(expires字典會保存所有設定了過期時間的key的過期時間數據,其中,key是指向鍵空間中的某個鍵的指針,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間。鍵空間是指該Redis集群中保存的所有鍵。)
Redis中同時使用了惰性過期和定期過期兩種過期策略。
EXPIRE和PERSIST指令。
除了快取伺服器自帶的快取失效策略之外(Redis預設的有6中策略可供選擇),我們還可以根據具體的業務需求進行自訂的快取淘汰,常見的策略有兩種:
1、定時去清理過期的快取;
2、當有使用者請求過來時,再判斷這個請求所用到的快取是否過期,過期的話就去底層系統得到新資料並更新快取。
兩者各有優劣,第一種的缺點是維護大量快取的key是比較麻煩的,第二種的缺點就是每次使用者請求過來都要判斷快取失效,邏輯相對比較複雜!具體用哪一種方案,大家可以根據自己的應用場景來權衡。
redis記憶體資料集大小上升到一定大小的時候,就會施加資料淘汰策略。
#Redis的記憶體淘汰策略是指在Redis的用於快取的記憶體不足時,怎麼處理需要新寫入且需要申請額外空間的資料。
1、全域的鍵空間選擇性移除
#noeviction:當記憶體不足以容納新寫入資料時,新寫入操作會報錯。
allkeys-lru:當記憶體不足以容納新寫入資料時,在鍵空間中,移除最近最少使用的key。 (這個是最常用的)
allkeys-random:當記憶體不足以容納新寫入資料時,在鍵空間中,隨機移除某個key。
2、設定過期時間的鍵空間選擇性移除
volatile-lru:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,移除最近最少使用的key。
volatile-random:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,隨機移除某個key。
volatile-ttl:當記憶體不足以容納新寫入資料時,在設定了過期時間的鍵空間中,有更早過期時間的key優先移除。
總結
Redis的記憶體淘汰策略的選取並不會影響過期的key的處理。記憶體淘汰策略用於處理記憶體不足時的需要申請額外空間的資料;過期策略用於處理過期的快取資料。
記憶體。
如果達到設定的上限,Redis的寫入命令會回傳錯誤訊息(但是讀取指令還可以正常回傳。)或者你可以設定記憶體淘汰機制,當Redis達到記憶體上限時會沖刷掉舊的內容。
可以好好利用Hash,list,sorted set,set等集合類型數據,因為通常情況下很多小的Key-Value可以用更緊湊的方式存放到一起。盡可能使用散列表(hashes),散列表(是說散列表裡面儲存的數少)使用的記憶體非常小,所以你應該盡可能的將你的資料模型抽象化到一個散列表裡面。例如你的web系統中有一個用戶對象,不要為這個用戶的名稱,姓氏,郵箱,密碼設定單獨的key,而是應該把這個用戶的所有資訊儲存到一張散列表裡面。
Redis基於Reactor模式開發了網路事件處理器,這個處理器被稱為檔案事件處理器(file event handler) 。它的組成結構為4部分:多個套接字、IO多工程式、檔案事件分派器、事件處理器。因為檔案事件分派器佇列的消費是單線程的,所以Redis才叫單線程模型。
1、檔案事件處理器使用I/O 多重化(multiplexing)程式來同時監聽多個套接字, 並根據套接字目前執行的任務來為套接字關聯不同的事件處理器。
2、當被監聽的套接字準備好執行連接應答(accept)、讀取(read)、寫入(write)、關閉(close)等操作時, 與操作相對應的文件事件就會產生, 這時檔案事件處理器就會呼叫套接字之前關聯好的事件處理器來處理這些事件。
雖然檔案事件處理器以單執行緒方式運行, 但透過使用I/O 多路復用程式來監聽多個套接字, 檔案事件處理器既實現了高效能的網路通訊模型,又可以很好地與redis 伺服器中其他同樣以單執行緒方式運行的模組進行對接, 這保持了Redis 內部單執行緒設計的簡單性。
事務是一個單獨的隔離操作:事務中的所有命令都會序列化、依序執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
事務是一個原子操作:事務中的指令要麼全部被執行,要麼全部都不執行。
Redis交易的概念
Redis 交易的本質是透過MULTI、EXEC、WATCH等一組指令的集合。事務支援一次執行多個命令,一個事務中所有命令都會被序列化。在事務執行過程,會依照順序串列化執行佇列中的命令,其他客戶端提交的命令請求不會插入到交易執行命令序列中。
總結說:redis事務就是一次性、順序性、排他性的執行一個佇列中的一系列指令。
Redis交易的三個階段
1、交易開始MULTI
2、命令入隊
3、事務執行EXEC
事務執行過程中,如果服務端收到有EXEC、DISCARD、WATCH、MULTI以外的請求,將會把請求放入佇列中排隊
Redis交易相關指令
Redis交易功能是透過MULTI、EXEC、DISCARD和WATCH 四個原語實現的
Redis會將一個事務中的所有命令序列化,然後按順序執行。
1、redis 不支援回滾,“Redis 在交易失敗時不進行回滾,而是繼續執行餘下的命令”, 所以 Redis 的內部可以保持簡單且快速。
2、如果在一個交易中的指令出現錯誤,那麼所有的指令都不會執行;
3、如果在一個交易中出現運行錯誤,那麼正確的指令會被執行。
WATCH 指令是一個樂觀鎖,可以為 Redis 交易提供 check-and-set (CAS)行為。可以監控一個或多個鍵,一旦其中有一個鍵被修改(或刪除),之後的事務就不會執行,監控一直持續到EXEC指令。
MULTI指令用來開啟一個事務,它總是回傳OK。 MULTI執行之後,客戶端可以繼續向伺服器發送任意多條命令,這些命令不會立即被執行,而是被放到一個佇列中,當EXEC命令被呼叫時,所有佇列中的命令才會被執行。
EXEC:執行所有事務區塊內的命令。傳回事務區塊內所有指令的回傳值,依指令執行的先後順序排列。當操作被打斷時,傳回空值 nil 。
透過呼叫DISCARD,客戶端可以清空交易佇列,並放棄執行事務, 且客戶端會從交易狀態中退出。
UNWATCH指令可以取消watch對所有key的監控。
事務管理(ACID)概述
原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位,事務中的操作要麼都發生,要麼都不發生。
一致性(Consistency)
#交易前後資料的完整性必須保持一致。
隔離性(Isolation)
多個交易並發執行時,一個交易的執行不應影響其他交易的執行。
持久性(Durability)
持久性是指一個交易一旦被提交,它對資料庫中資料的改變就是永久性的,接下來即使資料庫發生故障也不應該對其有任何影響。
Redis的事務總是具有ACID中的一致性和隔離性,其他特性是不支援的。當伺服器運行在AOF持久化模式下,且appendfsync選項的值為always時,交易也具有耐久性。
Redis 是單一進程程序,並且它保證在執行事務時,不會對事務進行中斷,事務可以運行直到執行完所有事務隊列中的命令為止。因此,Redis 的事務是總是帶有隔離性的。
Redis中,單一指令是原子性執行的,但交易不保證原子性,且沒有回滾。事務中任意指令執行失敗,其餘的指令仍會被執行。
#基於Lua腳本,Redis可以保證腳本內的指令一次性、依序地執行,其同時也不提供事務運行錯誤的回滾,執行過程中如果部分命令運行錯誤,剩下的命令還是會繼續運。
基於中間標記變量,透過另外的標記變數來識別事務是否執行完成,讀取資料時先讀取該標記變數判斷是否事務執行完成。但這會需要額外寫程式碼實現,比較繁瑣。
#sentinel,中文名是哨兵。哨兵是 redis 叢集機構中非常重要的一個元件,主要有以下功能:
叢集監控:負責監控 redis master 和 slave 流程是否正常運作。
訊息通知:如果某個 redis 實例有故障,那麼哨兵負責發送訊息作為警報通知給管理員。
故障轉移:如果 master node 掛掉了,就會自動轉移到 slave node 上。
設定中心:如果故障轉移發生了,通知 client 用戶端新的 master 位址。
哨兵用來實作 redis 叢集的高可用
,本身也是分散式的,作為一個哨兵叢集去運行,互相協同工作。 1、故障轉移時,判斷一個 master node 是否宕機了,需要大部分的哨兵都同意才行,涉及到了分散式選舉的問題。2、即使部分哨兵節點掛掉了,哨兵集群還是能正常工作的,因為如果一個作為高可用機制重要組成部分的故障轉移系統本身是單點的,那就很坑爹了。
哨兵的核心知識
1、哨兵至少需要 3 個實例,來確保自己的健全性。 2、哨兵 redis 主從的部署架構,是不保證資料零遺失的,只能保證 redis 叢集的高可用性。 3、對於哨兵 redis 主從這種複雜的部署架構,盡量在測試環境和生產環境,都進行充足的測試和演練。官方Redis Cluster 方案(服務端路由查詢)
#redis 叢集模式的工作原理能說一下麼?在叢集模式下,redis 的 key 是如何定址的?分散式尋址都有哪些演算法?了解一致性 hash 演算法嗎?
簡介
Redis Cluster是一種服務端Sharding技術,3.0版本開始正式提供。 Redis Cluster並沒有使用一致性hash,而是採用slot(槽)的概念,總共分成16384個槽。將請求傳送到任意節點,接收到請求的節點會將查詢請求傳送到正確的節點上執行
#方案說明
1、透過雜湊的方式,將數據分片,每個節點均分儲存一定哈希槽(哈希值)區間的數據,預設分配了16384 個槽位2、每份數據分片會儲存在多個互為主從的多節點上3、資料寫入先寫主節點,再同步到從節點(支援配置為阻塞同步)4、同一分片多個節點間的資料不保持一致性5、讀取資料時,當客戶端操作的key沒有被指派在該節點上時,redis會回傳轉向指令,指向正確的節點# 6.擴容時時需要需要把舊節點的資料遷移一部分到新節點在redis cluster 架構下,每個redis 要放開兩個埠號,例如一個是6379,另外一個就是加1w 的連接埠號,例如16379。
16379 埠號是用來進行節點間通訊的,也就是 cluster bus 的東西,cluster bus 的通信,用來進行故障偵測、設定更新、故障轉移授權。 cluster bus 用了另外一種二進位的協議,gossip 協議,用於節點間進行高效的資料交換,佔用更少的網路頻寬和處理時間。
節點間的內部通訊機制
#基本通訊原理叢集元資料的維護有兩種方式:集中式、Gossip 協定。 redis cluster 節點間採用 gossip 協定進行通訊。
#1、無中心架構,支援動態擴容,對業務透明
3、客戶端不需要連接叢集所有節點,連接叢集中任何一個可用節點即可
4、高效能,客戶端直連redis服務,免去了proxy代理程式的損耗#########缺點#########1、維運也很複雜,資料遷移需要人工幹預######2、只能使用0號資料庫######3、不支援批次運算( pipeline管道操作)######4、分散式邏輯和儲存模組耦合等######基於客戶端分配#################### ####簡介######Redis Sharding是Redis Cluster出來之前,業界普遍使用的多Redis實例叢集方法。其主要想法是採用雜湊演算法將Redis資料的key進行雜湊,透過hash函數,特定的key會映射到特定的Redis節點上。 Java redis客戶端驅動jedis,支援Redis Sharding功能,即ShardedJedis以及結合快取池的ShardedJedisPool
優點
#優點在於非常簡單,服務端的Redis實例彼此獨立,相互無關聯,每個Redis實例像單一伺服器一樣運行,非常容易線性擴展,系統的靈活性很強
缺點
1、由於sharding處理放到客戶端,規模進一步擴大時為運維帶來挑戰。
2、客戶端sharding不支援動態增刪節點。服務端Redis實例群拓樸結構有變化時,每個客戶端都需要更新調整。連線不能共享,當應用程式規模增加時,資源浪費限制優化
簡介
客戶端發送請求到一個代理元件,代理程式解析客戶端的數據,並將請求轉發至正確的節點,最後將結果回覆給客戶端2、Proxy 的邏輯和儲存的邏輯是隔離的
1、Twtter開源的Twemproxy#2、豌豆莢開源的Codis
Redis 主從架構 #單機的redis,能夠承載的QPS 大概就在上萬到幾萬不等。對於快取來說,一般都是用來支撐讀高並發的。因此架構做成主從(master-slave)架構,一主多從,主負責寫,並且將資料複製到其它的 slave 節點,從節點負責讀取。所有的讀取請求全部走從節點。這樣也可以很輕鬆達到水平擴容,支撐讀高並發。 redis replication -> 主從架構-> 讀寫分離-> 水平擴容支撐讀高並發redis replication 的核心機制
1、redis 採用非同步方式複製資料到slave 節點,不過redis2.8 開始,slave node 會週期性地確認自己每次複製的資料量;
2、一個master node 是可以配置多個slave node 的;3、slave node 也可以連接其他的slave node;#4、slave node 做複製的時候,不會block master node 的正常工作;5、slave node 在做複製的時候,也不會block 對自己的查詢操作,它會用舊的資料集來提供服務;但是複製完成的時候,需要刪除舊資料集,載入新資料集,這個時候就會暫停對外服務了;6、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 資料被清空。
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、從庫和主庫建立MS關係後,會傳送SYNC指令
2、主庫接收到SYNC指令後會開始在背景儲存快照(RDB持久化過程) ,並將期間接收到的寫入命令快取起來
3、當快照完成後,主Redis會將快照檔案和所有快取的寫入命令傳送給從Redis
4、從Redis接收到後,會載入快照檔案並且執行收到的快取的命令
5、之後,主Redis每當接收到寫入命令時就會將命令發送從Redis,從而保證資料的一致
#所有的slave節點資料的複製和同步都由master節點來處理,會照成master節點壓力太大,使用主從結構來解決
Redis叢集的主從複製模型是怎樣的?
為了使在部分節點失敗或大部分節點無法通訊的情況下叢集仍然可用,所以叢集使用了主從複製模型,每個節點都會有N-1個複製品
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 負責快取叢集的運維。
Redis集群並沒有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384個哈希槽,每個key透過CRC16校驗後對16384取模來決定放置哪個槽,集群的每個節點負責一部分hash槽。
Redis並不能保證資料的強一致性,這意味著這在實際中集群在特定的條件下可能會遺失寫入操作。
非同步複製
16384個
Redis叢集目前無法做資料庫選擇,預設在0資料庫。
可以在同一個伺服器部署多個Redis的實例,並把他們當作不同的伺服器來使用,在某些時候,無論如何一個伺服器是不夠的, 所以,如果你想使用多個CPU,你可以考慮分片(shard)。
分割區可以讓Redis管理更大的內存,Redis將可以使用所有機器的記憶體。如果沒有分區,你最多只能使用一台機器的記憶體。分區使Redis的運算能力透過簡單地增加電腦而成倍提升,Redis的網路頻寬也會隨著電腦和網路卡的增加而成倍增長。
1、客戶端分區就是在客戶端就已經決定資料會被儲存到哪個redis節點或從哪個redis節點讀取。大多數客戶端已經實現了客戶端分區。
2、代理分區 意味著客戶端將請求傳送給代理,然後代理決定去哪個節點寫資料或讀取資料。代理根據分區規則決定要要求哪些Redis實例,然後根據Redis的回應結果傳回給客戶端。 redis和memcached的一種代理實作就是Twemproxy。
3、查詢路由(Query routing) 的意思是客戶端隨機地請求任一個redis實例,然後由Redis將請求轉送給正確的Redis節點。 Redis Cluster實作了一種混合形式的查詢路由,但並不是直接將請求從一個redis節點轉送到另一個redis節點,而是在客戶端的幫助下直接redirected到正確的redis節點。
1、涉及多個key的操作通常不會被支援。例如你不能對兩個集合求交集,因為他們可能被儲存到不同的Redis實例(實際上這種情況也有辦法,但是不能直接使用交集指令)。
2、同時操作多個key,則不能使用Redis交易。
3、分區使用的粒度是key,不能使用一個非常長的排序key儲存一個資料集(The partitioning granularity is the key, so it is not possible to shard a dataset with a single huge key like a very big sorted set)
4、當使用分割區的時候,資料處理會非常複雜,例如為了備份你必須從不同的Redis實例和主機同時收集RDB / AOF檔。
5、分割時動態擴容或縮容可能非常複雜。 Redis叢集在執行時增加或刪除Redis節點,能做到最大程度對使用者透明地資料再平衡,但其他一些客戶端分割區或代理分割區方法則不支援此特性。然而,有一種預分片的技術也可以較好的解決這個問題。
Redis為單一進程單執行緒模式,採用佇列模式將並發存取變成串列訪問,且多客戶端對Redis的連線並不存在競爭關係Redis中可以使用SETNX指令實現分散式鎖定。
當且僅當 key 不存在,將 key 的值設為 value。若給定的 key 已經存在,則 SETNX 不做任何動作
SETNX 是‘SET if Not eXists’(如果不存在,則 SET)的簡寫。
傳回值:設定成功,回傳 1 。設定失敗,返回 0 。
使用SETNX完成同步鎖定的流程及事項如下:
使用SETNX指令取得鎖定,若傳回0(key已存在,鎖定已存在)則取得失敗,反之取得成功
為了防止取得鎖定後程式出現異常,導致其他執行緒/進程呼叫SETNX指令總是回傳0而進入死鎖狀態,需要為該key設定一個「合理」的過期時間
#釋放鎖,使用DEL指令將鎖定資料刪除
所謂Redis 的並發競爭Key 的問題也就是多個系統同時對一個key 進行操作,但是最後執行的順序和我們期望的順序不同,這樣也就導致了結果的不同!
推薦一種方案:分散式鎖定(zookeeper 和 redis 都可以實作分散式鎖定)。 (如果不存在 Redis 的並發競爭 Key 問題,不要使用分散式鎖,這樣會影響效能)
基於zookeeper臨時有序節點可以實現的分散式鎖定。大致觀念為:每個客戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節點的目錄下,產生一個唯一的瞬時有序節點。判斷是否取得鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。當釋放鎖的時候,只要將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,而產生的死鎖問題。完成業務流程後,刪除對應的子節點釋放鎖定。
在實踐中,當然是從以可靠性為主。所以首推Zookeeper。
既然Redis是如此的輕量(單一實例只使用1M記憶體),為防止以後的擴容,最好的方法就是一開始就啟動較多實例。即便你只有一台伺服器,你也可以一開始就讓Redis以分散式的方式運行,使用分區,在同一台伺服器上啟動多個實例。
一開始就多設定幾個Redis實例,例如32或64個實例,對大多數使用者來說這操作起來可能比較麻煩,但是從長久來看做這點犧牲是值得的。
這樣的話,當你的資料不斷成長,需要更多的Redis伺服器時,你需要做的就是僅僅將Redis實例從一個服務遷移到另外一個伺服器而已(而不用考慮重新分區的問題)。一旦你增加了另一台伺服器,你需要將你一半的Redis實例從第一台機器遷移到第二台機器。
Redis 官方站提出了一種權威的基於Redis 實現分散式鎖定的方式名叫Redlock,此種方式比原先的單節點的方法比較安全。它可以確保以下特性:
1、安全特性:互斥訪問,即永遠只有一個client 能拿到鎖
2、避免死鎖:最終client 都可能拿到鎖,不會出現死鎖的情況,即使原本鎖住某資源的client crash 了或者出現了網絡分區
3、容錯性:只要大部分Redis 節點存活就可以正常提供服務
快取雪崩
快取雪崩是指快取同一時間大面積的失效,所以,後面的請求都會落到資料庫上,造成資料庫短時間內承受大量請求而崩掉。
解決方案
1、快取資料的過期時間設定隨機,防止同一時間大量資料過期現象發生。
2、一般並發量不是特別多的時候,使用最多的解決方案是加鎖排隊。
3、增加每一個快取資料對應的快取標記,記錄快取的是否失效,如果快取標記失效,則更新資料快取。
快取穿透
快取穿透是指快取和資料庫中都沒有的數據,導致所有的請求都落在資料庫上,造成資料庫短時間內承受大量請求而崩掉。
解決方案
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一般用於在大資料量的集合中判定某元素是否存在。
快取擊穿
快取擊穿是指快取中沒有但資料庫中有的資料(一般是快取時間到期),這時由於並髮用戶特別多,同時讀取快取沒讀到數據,又同時去資料庫去取數據,造成資料庫壓力瞬間增大,造成過大壓力。和快取雪崩不同的是,快取擊穿指並發查同一條數據,快取雪崩是不同數據都過期了,很多數據都查不到從而查資料庫。
解決方案
1、設定熱點資料永遠不會過期
2、加互斥鎖,互斥鎖
快取預熱
快取預熱就是系統上線後,將相關的快取資料直接載入到快取系統。這樣就可以避免在用戶請求的時候,先查詢資料庫,然後再將資料快取的問題!使用者直接查詢事先被預熱的快取資料!
解決方案
1、直接寫個快取刷新頁面,上線時手動操作一下;
2、資料量不大,可以在專案啟動的時候自動進行載入;
3、定時刷新快取;
#快取降級
##當訪問量劇增、服務出現問題(如回應時間慢或不回應)或非核心服務影響核心流程的效能時,仍需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵資料進行自動降級,也可以配置開關來實現人工降級。 快取降級的最終目的是確保核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物車、結算)。 在進行降級之前要對系統進行梳理,看看系統是否可以丟卒保帥;從而梳理出哪些必須誓死保護,哪些可降級;比如可以參考日誌級別設置預案:1、一般:例如有些服務偶爾因為網路抖動或服務正在上線而超時,可以自動降級;2、警告:有些服務在一段時間內成功率有波動(如在95~ 100%之間),可以自動降級或人工降級,並發送告警;3、錯誤:例如可用率低於90%,或者資料庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或人工降級;
4、嚴重錯誤:例如因為特殊原因資料錯誤了,此時需要緊急人工降級。
服務降級的目的,是為了防止Redis服務故障,導致資料庫跟著一起發生雪崩問題。因此,對於不重要的快取數據,可以採取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去資料庫查詢,而是直接傳回預設值給使用者。
熱點數據和冷數據
熱點數據,快取才有價值
對於冷數據而言,大部分資料可能還沒有再次訪問到就已經被擠出內存,不僅佔用內存,而且價值不大。頻繁修改的數據,看情況考慮使用緩存
對於熱點數據,例如我們的某IM產品,生日祝福模組,當天的壽星列表,緩存以後可能讀取數十萬次。再舉個例子,某導航產品,我們將導航訊息,快取以後可能讀取數百萬次。
資料更新前至少讀取兩次,快取才有意義。這個是最基本的策略,如果快取還沒起作用就失效了,那就沒有太大價值了。
那存不存在,修改頻率很高,但又得考慮快取的場景呢?有!例如,這個讀取介面對資料庫的壓力很大,但是又是熱點數據,這個時候就需要考慮透過快取手段,減少資料庫的壓力,例如我們的某助手產品的,點讚數,收藏數,分享數等是非常典型的熱點數據,但又不斷變化,此時就需要將數據同步保存到Redis緩存,減少資料庫壓力。
快取熱點key
快取中的一個Key(例如促銷商品),在某個時間點過期的時候,剛好在這個時間點對這個Key有大量的並發請求過來,這些請求發現緩存過期一般都會從後端DB加載資料並回設到緩存,這個時候大並發的請求可能會瞬間把後端DB壓垮。
解決方案
對快取查詢加鎖,如果KEY不存在,就加鎖,然後查DB入緩存,然後解鎖;其他行程如果發現有鎖就等待,然後等解鎖後返回資料或進入DB查詢
Redis支援的Java客戶端都有哪些?官方推薦用哪一個?
Redisson、Jedis、lettuce等等,官方推薦使用Redisson。
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)。
Jedis與Redisson對比有什麼優缺點?
Jedis是Redis的Java實現的客戶端,其API提供了比較全面的Redis指令的支援;Redisson實作了分散式和可擴展的Java資料結構,和Jedis相比,功能較為簡單,不支援字串操作,不支援排序、事務、管道、分區等Redis特性。 Redisson的宗旨是促進使用者對Redis的關注分離,從而讓使用者能夠將精力更集中地放在處理業務邏輯上。
Redis與Memcached的差異
兩者都是非關聯式記憶體鍵值資料庫,現在公司通常都用Redis 來實現緩存,而且Redis 本身也越來越強大了! Redis 與 Memcached 主要有以下不同:
對比參數 |
#redis | memcached |
類型 | 1. 支援記憶體2.非關係型資料庫 | 1. 支援記憶體2.鍵值對式 3.快取形式 |
資料儲存類型 | 1. String 2. List 3. Set 4. Hash 5. Sort Set 【俗稱ZSet】 | #1. 文型2 .二進位類型 |
查詢【操作】類型 | 1. 批次操作2. 交易支援3. 每個類型不同的CRUD | ##1.常用的CRUD 2. 少量的其他指令|
#1. 發布/訂閱模式2. 主從分割區3.序列化支援4.腳本支援【 Lua腳本】 | 1. 多執行緒服務支援 | |
單執行緒的多路IO複用模型 | ||
##LibEvent | ||
1. RDB 2. AOF |
#不支援 | |
原生支援cluster模式,可以實現主從複製,讀寫分離 |
#沒有原生的叢集模式,需要依賴客戶端來實現往叢集中分片寫入資料 |
記憶體管理機制 |
在Redis 中,並不是所有資料都一直儲存在記憶體中,可以將一些很久沒用的value 交換到磁碟 | Memcached 的資料則會一直在記憶體中,Memcached 將記憶體分割成特定長度的區塊來儲存數據,以完全解決記憶體碎片的問題。但這種方式會使得記憶體的使用率不高,例如區塊的大小為 128 bytes,只儲存 100 bytes 的數據,那麼剩下的 28 bytes 就浪費掉了。 適用場景 | |
複雜資料結構,有持久化,高可用需求,value儲存內容較大 |
純key-value,資料量非常大,並發量非常大的業務 |
如何保證緩存與資料庫雙寫時的資料一致性?
你只要用緩存,就可能會牽涉到快取與資料庫雙儲存雙寫,你只要是雙寫,就一定會有資料一致性的問題,那麼你如何解決一致性問題?
一般來說,就是如果你的系統不是嚴格要求快取資料庫必須一致性的話,快取可以稍微的跟資料庫偶爾有不一致的情況,最好不要做這個方案,讀請求和寫請求串行化,串到一個記憶體佇列裡去,這樣就可以保證一定不會出現不一致的情況串行化之後,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。 還有一種方式就是可能會暫時產生不一致的情況,但是發生的幾率特別小,就是先更新資料庫,然後再刪除快取
。
#解決 | 先寫緩存,再寫資料庫,快取寫成功,資料庫寫入失敗 | |
這個寫快取的方式,本身就是錯誤的,需要改為先寫資料庫,把舊快取置為失效;讀取資料的時候,如果快取不存在,則讀取資料庫再寫快取 | 先寫資料庫,再寫緩存,資料庫寫成功,快取寫失敗 | |
快取使用時,假如讀快取失敗,先讀資料庫,再回寫快取的方式實作 | 需要快取非同步刷新 | |
確定哪些資料適合此類場景,根據經驗值決定合理的資料不一致時間,使用者資料刷新的時間間隔 |
以上是Redis高頻面試題目(附答案分析)的詳細內容。更多資訊請關注PHP中文網其他相關文章!