Redis面试题详解
什么是 Redis ?
Redis ,全稱 Remote Dictionary Server ,是一個基于內存的高性能 Key-Value 數據庫。
另外,Redis 已經成為互聯網公司在緩存組件選擇的唯一,更多的關注點是,如何使用好 Redis 。
Redis 有什么優點?
🦅 1. 速度快
因為數據存在內存中,類似于 HashMap ,HashMap 的優勢就是查找和操作的時間復雜度都是O (1) 。
Redis 本質上是一個 Key-Value 類型的內存數據庫,很像Memcached ,整個數據庫統統加載在內存當中進行操作,定期通過異步操作把數據庫數據 flush 到硬盤上進行保存。
因為是純內存操作,Redis 的性能非常出色,每秒可以處理超過 10 萬次讀寫操作,是已知性能最快的 Key-Value 數據庫。
-
如果我們查看在阿里云銷售的 Redis 規格,最低的也是 8W QPS 。
🦅 2. 支持豐富數據類型
支持 String ,List,Set,Sorted Set,Hash 。
Redis 的出色之處不僅僅是性能,Redis 最大的魅力是支持保存多種數據結構,此外單個 Value 的最大限制是1GB,不像 Memcached只能保存1MB的數據,因此Redis可以用來實現很多有用的功能。比方說:
-
用他的 List 來做 FIFO 雙向鏈表,實現一個輕量級的高性能消息隊列服務。
-
用他的 Set 可以做高性能的 tag 系統等等。
🦅 3. 豐富的特性
-
訂閱發布 Pub / Sub 功能
-
Key 過期策略
-
事務
-
支持多個 DB
-
計數
-
…
并且在 Redis 5.0 增加了 Stream 功能,一個新的強大的支持多播的可持久化的消息隊列,提供類似 Kafka 的功能。
🦅 4. 持久化存儲
Redis 提供 RDB 和 AOF 兩種數據的持久化存儲方案,解決內存數據庫最擔心的萬一 Redis 掛掉,數據會消失掉。
Redis 有什么缺點?
-
1、由于 Redis 是內存數據庫,所以,單臺機器,存儲的數據量,跟機器本身的內存大小。雖然 Redis 本身有 Key 過期策略,但是還是需要提前預估和節約內存。如果內存增長過快,需要定期刪除數據。
另外,可使用 Redis Cluster、Codis 等方案,對 Redis 進行分區,從單機 Redis 變成集群 Redis 。
-
2、如果進行完整重同步,由于需要生成 RDB 文件,并進行傳輸,會占用主機的 CPU ,并會消耗現網的帶寬。不過 Redis2.8 版本,已經有部分重同步的功能,但是還是有可能有完整重同步的。比如,新上線的備機。
-
3、修改配置文件,進行重啟,將硬盤中的數據加載進內存,時間比較久。在這個過程中,Redis 不能提供服務。
Redis 和 Memcached 的區別有哪些?
🦅 1. Redis 支持復雜的數據結構
-
Memcached 僅提供簡單的字符串。
-
Redis 提供復雜的數據結構,豐富的數據操作。
也因為 Redis 支持復雜的數據結構,Redis 即使往于 Memcached 推出,卻獲得更多開發者的青睞。
Redis 相比 Memcached 來說,擁有更多的數據結構,能支持更豐富的數據操作。如果需要緩存能夠支持更復雜的結構和操作,Redis 會是不錯的選擇。
🦅 2. Redis 原生支持集群模式
-
在 Redis3.x 版本中,官方便能支持 Cluster 模式。
-
Memcached 沒有原生的集群模式,需要依靠客戶端來實現往集群中分片寫入數據。
🦅 3. 性能對比
-
Redis 只使用單核,而 Memcached 可以使用多核,所以平均每一個核上 Redis在存儲小數據時比 Memcached 性能更高。
-
在 100k 以上的數據中,Memcached 性能要高于 Redis 。雖然 Redis 最近也在存儲大數據的性能上進行優化,但是比起 Memcached,還是稍有遜色。
更多關于性能的對比,可以看看 《Memcached 與 Redis 的關鍵性能指標比較》 。
4. 內存使用效率對比
-
簡單的 Key-Value 存儲的話,Memcached 的內存利用率更高,可以使用類似內存池。
-
如果 Redis 采用 hash 結構來做 key-value 存儲,由于其組合式的壓縮, 其內存利用率會高于 Memcached 。
另外,Redis 和 Memcached 的內存管理方法不同。
-
Redis 采用的是包裝的 malloc/free , 相較于 Memcached 的內存管理方法 tcmalloc / jmalloc 來說,要簡單很多 。
5. 網絡 IO 模型
-
Memcached 是多線程,非阻塞 IO 復用的網絡模型,原型上接近 Nignx 。
-
Redis 使用單線程的 IO 復用模型,自己封裝了一個簡單的 AeEvent 事件處理框架,主要實現了 epoll, kqueue 和 select ,更接近 Apache 早期的模式。
TODO 有點看不懂,找亞普表弟確認中。
6. 持久化存儲
-
Memcached 不支持持久化存儲,重啟時,數據被清空。
-
Redis 支持持久化存儲,重啟時,可以恢復已持久化的數據。
也推薦閱讀下 《腳踏兩只船的困惑 - Memcached 與 Redis》 。
請說說 Redis 的線程模型?
:這個是我從網絡上找的資料,講的灰常不錯。
redis 內部使用文件事件處理器 file event handler,這個文件事件處理器是單線程的,所以 redis 才叫做單線程的模型。它采用 IO 多路復用機制同時監聽多個 socket,根據 socket 上的事件來選擇對應的事件處理器進行處理。
文件事件處理器的結構包含 4 個部分:
-
多個 socket
-
IO 多路復用程序
-
文件事件分派器
-
事件處理器(連接應答處理器、命令請求處理器、命令回復處理器)
多個 socket 可能會并發產生不同的操作,每個操作對應不同的文件事件,但是 IO 多路復用程序會監聽多個 socket,會將 socket 產生的事件放入隊列中排隊,事件分派器每次從隊列中取出一個事件,把該事件交給對應的事件處理器進行處理。
來看客戶端與 redis 的一次通信過程:
-
客戶端 socket01 向 redis 的 server socket 請求建立連接,此時 server socket 會產生一個 AE_READABLE 事件,IO 多路復用程序監聽到 server socket 產生的事件后,將該事件壓入隊列中。文件事件分派器從隊列中獲取該事件,交給連接應答處理器。連接應答處理器會創建一個能與客戶端通信的 socket01,并將該 socket01 的 AE_READABLE 事件與命令請求處理器關聯。
-
假設此時客戶端發送了一個 set key value 請求,此時 redis 中的 socket01 會產生 AE_READABLE 事件,IO 多路復用程序將事件壓入隊列,此時事件分派器從隊列中獲取到該事件,由于前面 socket01 的 AE_READABLE 事件已經與命令請求處理器關聯,因此事件分派器將事件交給命令請求處理器來處理。命令請求處理器讀取 socket01 的 key value 并在自己內存中完成 key value 的設置。操作完成后,它會將 socket01 的 AE_WRITABLE 事件與令回復處理器關聯。
-
如果此時客戶端準備好接收返回結果了,那么 redis 中的 socket01 會產生一個 AE_WRITABLE 事件,同樣壓入隊列中,事件分派器找到相關聯的命令回復處理器,由命令回復處理器對 socket01 輸入本次操作的一個結果,比如 ok,之后解除 socket01 的 AE_WRITABLE 事件與命令回復處理器的關聯。
這樣便完成了一次通信。😈 耐心理解一下,灰常重要。如果還是不能理解,可以在網絡上搜一些資料,在理解理解。
為什么 Redis 單線程模型也能效率這么高?
-
1、純內存操作。
Redis 為了達到最快的讀寫速度,將數據都讀到內存中,并通過異步的方式將數據寫入磁盤。所以 Redis 具有快速和數據持久化的特征。
如果不將數據放在內存中,磁盤 I/O 速度為嚴重影響 Redis 的性能。
-
2、核心是基于非阻塞的 IO 多路復用機制。
-
3、單線程反而避免了多線程的頻繁上下文切換問題。
Redis 利用隊列技術,將并發訪問變為串行訪問,消除了傳統數據庫串行控制的開銷
-
4、Redis 全程使用 hash 結構,讀取速度快,還有一些特殊的數據結構,對數據存儲進行了優化,如壓縮表,對短數據進行壓縮存儲,再如,跳表,使用有序的數據結構加快讀取的速度。
Redis 是單線程的,如何提高多核 CPU 的利用率?
可以在同一個服務器部署多個 Redis 的實例,并把他們當作不同的服務器來使用,在某些時候,無論如何一個服務器是不夠的, 所以,如果你想使用多個 CPU ,你可以考慮一下分區。
Redis 有幾種持久化方式?
:這個問題有一丟丟長,耐心看完。
面試的時候,如果不能完整回答出來,也不會有大問題。重點,在于有條理,對 RDB 和 AOF 有理解。
🦅 持久化方式
Redis 提供了兩種方式,實現數據的持久化到硬盤。
-
1、【全量】RDB 持久化,是指在指定的時間間隔內將內存中的數據集快照寫入磁盤。實際操作過程是,fork 一個子進程,先將數據集寫入臨時文件,寫入成功后,再替換之前的文件,用二進制壓縮存儲。
-
2、【增量】AOF持久化,以日志的形式記錄服務器所處理的每一個寫、刪除操作,查詢操作不會記錄,以文本的方式記錄,可以打開文件看到詳細的操作記錄。
🦅 RDB 優缺點
① 優點
-
靈活設置備份頻率和周期。你可能打算每個小時歸檔一次最近 24 小時的數據,同時還要每天歸檔一次最近 30 天的數據。通過這樣的備份策略,一旦系統出現災難性故障,我們可以非常容易的進行恢復。
-
非常適合冷備份,對于災難恢復而言,RDB 是非常不錯的選擇。因為我們可以非常輕松的將一個單獨的文件壓縮后再轉移到其它存儲介質上。推薦,可以將這種完整的數據文件發送到一些遠程的安全存儲上去,比如說 Amazon 的 S3 云服務上去,在國內可以是阿里云的 OSS 分布式存儲上。
-
性能最大化。對于 Redis 的服務進程而言,在開始持久化時,它唯一需要做的只是 fork 出子進程,之后再由子進程完成這些持久化的工作,這樣就可以極大的避免服務進程執行 IO 操作了。也就是說,RDB 對 Redis 對外提供的讀寫服務,影響非常小,可以讓 Redis 保持高性能。
-
恢復更快。相比于 AOF 機制,RDB 的恢復速度更更快,更適合恢復數據,特別是在數據集非常大的情況。
② 缺點
-
如果你想保證數據的高可用性,即最大限度的避免數據丟失,那么 RDB 將不是一個很好的選擇。因為系統一旦在定時持久化之前出現宕機現象,此前沒有來得及寫入磁盤的數據都將丟失。
所以,RDB 實際場景下,需要和 AOF 一起使用。
-
由于 RDB 是通過 fork 子進程來協助完成數據持久化工作的,因此,如果當數據集較大時,可能會導致整個服務器停止服務幾百毫秒,甚至是 1 秒鐘。
所以,RDB 建議在業務低估,例如在半夜執行。
🦅 AOF 優缺點
① 優點
-
該機制可以帶來更高的
數據安全性
,即數據持久性。Redis 中提供了 3 種同步策略,即每秒同步、每修改(執行一個命令)同步和不同步。
-
事實上,每秒同步也是異步完成的,其效率也是非常高的,所差的是一旦系統出現宕機現象,那么這一秒鐘之內修改的數據將會丟失。
-
而每修改同步,我們可以將其視為同步持久化,即每次發生的數據變化都會被立即記錄到磁盤中。可以預見,這種方式在效率上是最低的。
-
至于無同步,無需多言,我想大家都能正確的理解它。
-
-
由于該機制對日志文件的寫入操作采用的是append模式,因此在寫入過程中即使出現宕機現象,也不會破壞日志文件中已經存在的內容。
-
因為以 append-only 模式寫入,所以沒有任何磁盤尋址的開銷,寫入性能非常高。
-
另外,如果我們本次操作只是寫入了一半數據就出現了系統崩潰問題,不用擔心,在 Redis 下一次啟動之前,我們可以通過 redis-check-aof 工具來幫助我們解決數據一致性的問題。
-
-
如果日志過大,Redis可以自動啟用 rewrite 機制。即使出現后臺重寫操作,也不會影響客戶端的讀寫。因為在 rewrite log 的時候,會對其中的指令進行壓縮,創建出一份需要恢復數據的最小日志出來。再創建新日志文件的時候,老的日志文件還是照常寫入。當新的 merge 后的日志文件 ready 的時候,再交換新老日志文件即可。
-
AOF 包含一個格式清晰、易于理解的日志文件用于記錄所有的修改操作。事實上,我們也可以通過該文件完成數據的重建。
② 缺點
-
對于相同數量的數據集而言,AOF 文件通常要大于 RDB 文件。RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
-
根據同步策略的不同,AOF 在運行效率上往往會慢于 RDB 。總之,每秒同步策略的效率是比較高的,同步禁用策略的效率和 RDB 一樣高效。
-
以前 AOF 發生過 bug ,就是通過 AOF 記錄的日志,進行數據恢復的時候,沒有恢復一模一樣的數據出來。所以說,類似 AOF 這種較為復雜的基于命令日志/merge/回放的方式,比基于 RDB 每次持久化一份完整的數據快照文件的方式,更加脆弱一些,容易有 bug 。不過 AOF 就是為了避免 rewrite 過程導致的 bug ,因此每次 rewrite 并不是基于舊的指令日志進行 merge 的,而是基于當時內存中的數據進行指令的重新構建,這樣健壯性會好很多。
🦅 如何選擇
-
不要僅僅使用 RDB,因為那樣會導致你丟失很多數據
-
也不要僅僅使用 AOF,因為那樣有兩個問題,第一,你通過 AOF 做冷備,沒有 RDB 做冷備,來的恢復速度更快; 第二,RDB 每次簡單粗暴生成數據快照,更加健壯,可以避免 AOF 這種復雜的備份和恢復機制的 bug 。
-
Redis 支持同時開啟開啟兩種持久化方式,我們可以綜合使用 AOF 和 RDB 兩種持久化機制,用 AOF 來保證數據不丟失,作為數據恢復的第一選擇; 用 RDB 來做不同程度的冷備,在 AOF 文件都丟失或損壞不可用的時候,還可以使用 RDB 來進行快速的數據恢復。
-
如果同時使用 RDB 和 AOF 兩種持久化機制,那么在 Redis 重啟的時候,會使用 AOF 來重新構建數據,因為 AOF 中的數據更加完整。
一般來說, 如果想達到足以媲美 PostgreSQL 的數據安全性, 你應該同時使用兩種持久化功能。如果你非常關心你的數據, 但仍然可以承受數分鐘以內的數據丟失,那么你可以只使用 RDB 持久化。
有很多用戶都只使用 AOF 持久化,但并不推薦這種方式:因為定時生成 RDB 快照(snapshot)非常便于進行數據庫備份, 并且 RDB 恢復數據集的速度也要比AOF恢復的速度要快,除此之外,使用 RDB 還可以避免之前提到的 AOF 程序的 bug。
-
在 Redis4.0 版本開始,允許你使用 RDB-AOF 混合持久化方式,詳細可見 《Redis4.0 之 RDB-AOF 混合持久化》 。也因此,RDB 和 AOF 同時使用,是希望達到安全的持久化的推薦方式。
另外,RDB 和 AOF 涉及的知識點蠻多的,可以看看:
-
《Redis 設計與實現 —— RDB》
-
《Redis 設計與實現 —— AOF》
如下是老錢對這塊的總結,可能更加適合面試的場景:
-
bgsave 做鏡像全量持久化,AOF 做增量持久化。因為 bgsave 會耗費較長時間,不夠實時,在停機的時候會導致大量丟失數據,所以需要 AOF 來配合使用。在 Redis 實例重啟時,會使用 bgsave 持久化文件重新構建內存,再使用 AOF 重放近期的操作指令來實現完整恢復重啟之前的狀態。
-
對方追問那如果突然機器掉電會怎樣?取決于 AOF 日志 sync 屬性的配置,如果不要求性能,在每條寫指令時都 sync 一下磁盤,就不會丟失數據。但是在高性能的要求下每次都 sync 是不現實的,一般都使用定時 sync ,比如 1 秒 1 次,這個時候最多就會丟失 1 秒的數據。
-
對方追問 bgsave 的原理是什么?你給出兩個詞匯就可以了,fork 和 cow 。fork 是指 Redis 通過創建子進程來進行 bgsave 操作。cow 指的是 copy on write ,子進程創建后,父子進程共享數據段,父進程繼續提供讀寫服務,寫臟的頁面數據會逐漸和子進程分離開來。
:這里 bgsave 操作后,會產生 RDB 快照文件。
TODO 和曉峰溝通下,使用哪個策略。
TODO 來自飛哥,主 aof ,從 rdb + aof
Redis 有幾種數據“過期”策略?
Redis 的過期策略,就是指當 Redis 中緩存的 key 過期了,Redis 如何處理。
Redis 提供了 3 種數據過期策略:
-
被動刪除:當讀/寫一個已經過期的 key 時,會觸發惰性刪除策略,直接刪除掉這個過期 key 。
-
主動刪除:由于惰性刪除策略無法保證冷數據被及時刪掉,所以 Redis 會定期主動淘汰一批已過期的 key 。
-
主動刪除:當前已用內存超過 maxmemory 限定時,觸發主動清理策略,即 「數據“淘汰”策略」 。
在 Redis 中,同時使用了上述 3 種策略,即它們非互斥的。
想要進一步了解,可以看看 《關于 Redis 數據過期策略》 文章。
Redis 有哪幾種數據“淘汰”策略?
Redis 內存數據集大小上升到一定大小的時候,就會進行數據淘汰策略。
Redis 提供了 6 種數據淘汰策略:
volatile-lru
volatile-ttl
volatile-random
allkeys-lru
allkeys-random
no-enviction
具體的每種數據淘汰策略的定義,和如何選擇討論策略,可見 《Redis實戰(二) 內存淘汰機制》 。
🦅 Redis LRU 算法
另外,Redis 的 LRU 算法,并不是一個嚴格的 LRU 實現。這意味著 Redis 不能選擇最佳候選鍵來回收,也就是最久未被訪問的那些鍵。相反,Redis 會嘗試執行一個近似的 LRU 算法,通過采樣一小部分鍵,然后在采樣鍵中回收最適合(擁有最久未被訪問時間)的那個。
-
具體的可以看看 《使用 Redis 作為一個 LRU 緩存》 文章。
🦅 MySQL 里有 2000w 數據,Redis 中只存 20w 的數據,如何保證 Redis 中的數據都是熱點數據?
:這個是從網絡上找到的一個神奇的問題,并且看了答案之后,覺得有點莫名的對不上。
所以,感覺這個問題的目的是,如何保證熱點數據不要被淘汰。
在 「Redis 有哪幾種數據“淘汰”策略?」 問題中,我們已經看到,“Redis 內存數據集大小上升到一定大小的時候,就會進行數據淘汰策略。” 。
那么,如果我們此時要保證熱點數據不被淘汰,那么需要選擇 volatile-lru 或 allkeys-lru 這兩個基于 LRU 算法的淘汰策略。
相比較來說,最終會選擇 allkeys-lru 淘汰策略。原因是,如果我們的應用對緩存的訪問符合冪律分布,也就是存在相對熱點數據,或者我們不太清楚我們應用的緩存訪問分布狀況,我們可以選擇 allkeys-lru 策略。
🦅 Redis 回收進程如何工作的?
理解回收進程如何工作是非常重要的:
-
一個客戶端運行了新的命令,添加了新的數據
-
Redis 檢查內存使用情況,如果大于 maxmemory 的限制, 則根據設定好的策略進行回收。
-
Redis 執行新命令……
所以我們不斷地穿越內存限制的邊界,通過不斷達到邊界然后不斷地回收回到邊界以下(跌宕起伏)。
如果有大量的 key 需要設置同一時間過期,一般需要注意什么?
如果大量的 key 過期時間設置的過于集中,到過期的那個時間點,Redis可能會出現短暫的卡頓現象。
一般需要在時間上加一個隨機值,使得過期時間分散一些。
上次基友也碰到這個問題,請教了下,他的方案是調大 hz 參數,每次過期的 key 更多,從而最終達到避免一次過期過多。
這個定期的頻率,由配置文件中的 hz 參數決定,代表了一秒鐘內,后臺任務期望被調用的次數。Redis3.0.0 中的默認值是 10 ,代表每秒鐘調用 10 次后臺任務。
hz 調大將會提高 Redis 主動淘汰的頻率,如果你的 Redis 存儲中包含很多冷數據占用內存過大的話,可以考慮將這個值調大,但 Redis 作者建議這個值不要超過 100 。我們實際線上將這個值調大到 100 ,觀察到 CPU 會增加 2% 左右,但對冷數據的內存釋放速度確實有明顯的提高(通過觀察 keyspace 個數和 used_memory 大小)。
Redis 有哪些數據結構?
如果你是 Redis 普通玩家,可能你的回答是如下五種數據結構:
-
字符串 String
-
字典Hash
-
列表List
-
集合Set
-
有序集合 SortedSet
如果你是 Redis 中級玩家,還需要加上下面幾種數據結構:
-
HyperLogLog
-
Geo
-
Pub / Sub
如果你是 Redis 高端玩家,你可能玩過 Redis Module ,可以再加上下面幾種數據結構:
-
BloomFilter
-
RedisSearch
-
Redis-ML
-
JSON
另外,在 Redis 5.0 增加了 Stream 功能,一個新的強大的支持多播的可持久化的消息隊列,提供類似 Kafka 的功能。😈 默默跟面試官在裝一波。
聊聊 Redis 使用場景
Redis 可用的場景非常之多:
-
數據緩存
-
會話緩存
-
時效性數據
-
訪問頻率
-
計數器
-
社交列表
-
記錄用戶判定信息
-
交集、并集和差集
-
熱門列表與排行榜
-
最新動態
-
消息隊列
-
分布式鎖
詳細的介紹,可以看看如下文章:
-
《聊聊 Redis 使用場景》
-
《Redis 應用場景及實例》
-
《Redis 常見的應用場景解析》
-
《Redis 和 Memcached 各有什么優缺點,主要的應用場景是什么樣的?》
🦅 請用 Redis 和任意語言實現一段惡意登錄保護的代碼,限制 1 小時內每用戶 Id 最多只能登錄 5 次。
用列表實現,列表中每個元素代表登陸時間,只要最后的第 5 次登陸時間和現在時間差不超過 1 小時就禁止登陸。
具體的代碼實現,可以看看 《一道 Redis 面試題》 。
Redis 支持的 Java 客戶端都有哪些?
使用比較廣泛的有三個 Java 客戶端:
-
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
Jedis 是 Redis 的 Java 實現的客戶端,其 API 提供了比較全面的 Redis 命令的支持。
Redisson 實現了分布式和可擴展的 Java 數據結構,和 Jedis 相比,Jedis 功能較為簡單,不支持字符串操作,不支持排序、事務、管道、分區等 Redis 特性。
Redisson 的宗旨是促進使用者對 Redis 的關注分離,從而讓使用者能夠將精力更集中地放在處理業務邏輯上。
-
Lettuce
Lettuc e是一個可伸縮線程安全的 Redis 客戶端。多個線程可以共享同一個 RedisConnection 。它利用優秀 Netty NIO 框架來高效地管理多個連接。
Redis 官方推薦使用 Redisson 或 Jedis 。Spring Boot 2.x 內置使用 Lettuce 。
如何使用 Redis 實現分布式鎖?
🦅 方案一:set 指令
先拿 setnx 來爭搶鎖,搶到之后,再用 expire 給鎖加一個過期時間防止鎖忘記了釋放。
-
這時候對方會告訴你說你回答得不錯,然后接著問如果在 setnx 之后執行 expire 之前進程意外 crash 或者要重啟維護了,那會怎么樣?
-
這時候你要給予驚訝的反饋:唉,是喔,這個鎖就永遠得不到釋放了。緊接著你需要抓一抓自己得腦袋,故作思考片刻,好像接下來的結果是你主動思考出來的,然后回答:我記得 set 指令有非常復雜的參數,這個應該是可以同時把 setnx 和 expire 合成一條指令來用的!對方這時會顯露笑容,心里開始默念:摁,這小子還不錯。
所以,我們可以使用 set 指令,實現分布式鎖。指令如下:
SET key value [EX seconds] [PX milliseconds] [NX|XX]-
可以使用 SET key value EX seconds NX 命令,嘗試獲得鎖。
-
具體的實現,可以參考 《Redis 分布式鎖的正確實現方式(Java版)》 文章。
🦅 方案二:redlock
set 指令的方案,適合用于在單機 Redis 節點的場景下,在多 Redis 節點的場景下,會存在分布式鎖丟失的問題。所以,Redis 作者 Antirez 基于分布式環境下提出了一種更高級的分布式鎖的實現方式:Redlock 。
具體的方案,同學們可以看看老友飛哥的兩篇博客:
-
《Redlock:Redis分布式鎖最牛逼的實現》
-
《Redisson 實現 Redis 分布式鎖的 N 種姿勢》
🦅 對比 Zookeeper 分布式鎖
-
從可靠性上來說,Zookeeper 分布式鎖好于 Redis 分布式鎖。
-
從性能上來說,Redis 分布式鎖好于 Zookeeper 分布式鎖。
所以,沒有絕對的好壞,可以根據自己的業務來具體選擇。
如何使用 Redis 實現分布式限流?
在 Spring Cloud Gateway 中,提供了 Redis 分布式限流器的實現,具體直接看寫的 《Spring-Cloud-Gateway 源碼解析 —— 過濾器 (4.10) 之 RequestRateLimiterGatewayFilterFactory 請求限流》 的 「5.3 Redis Lua 腳本」 部分。
另外,Redisson 庫中,也提供了 Redis 分布式限流的實現,不過需要使用 Pro 版本。
如何使用 Redis 實現消息隊列?
一般使用 list 結構作為隊列,rpush 生產消息,lpop 消費消息。當 lpop 沒有消息的時候,要適當 sleep 一會再重試。
-
如果對方追問可不可以不用 sleep 呢?list 還有個指令叫 blpop ,在沒有消息的時候,它會阻塞住直到消息到來。
-
如果對方追問能不能生產一次消費多次呢?使用 pub / sub 主題訂閱者模式,可以實現 1:N 的消息隊列。
-
如果對方追問 pub / sub 有什么缺點?在消費者下線的情況下,生產的消息會丟失,得使用專業的消息隊列如 rabbitmq 等。
-
如果對方追問 redis 如何實現延時隊列?我估計現在你很想把面試官一棒打死如果你手上有一根棒球棍的話,怎么問的這么詳細。但是你很克制,然后神態自若的回答道:使用 sortedset ,拿時間戳作為 score ,消息內容作為 key 調用 zadd 來生產消息,消費者用 zrangebyscore 指令獲取 N 秒之前的數據輪詢進行處理。
到這里,面試官暗地里已經對你豎起了大拇指。但是他不知道的是此刻你卻豎起了中指,在椅子背后。
當然,實際上 Redis 真的真的真的不推薦作為消息隊列使用,它最多只是消息隊列的存儲層,上層的邏輯,還需要做大量的封裝和支持。
另外,在 Redis 5.0 增加了 Stream 功能,一個新的強大的支持多播的可持久化的消息隊列,提供類似 Kafka 的功能。
什么是 Redis Pipelining ?
一次請求/響應服務器能實現處理新的請求即使舊的請求還未被響應。這樣就可以將多個命令發送到服務器,而不用等待回復,最后在一個步驟中讀取該答復。
這就是管道(pipelining),是一種幾十年來廣泛使用的技術。例如許多 POP3 協議已經實現支持這個功能,大大加快了從服務器下載新郵件的過程。
Redis 很早就支持管道(pipelining)技術,因此無論你運行的是什么版本,你都可以使用管道(pipelining)操作 Redis。
🦅 Redis 如何做大量數據插入?
Redis2.6 開始,Redis-cli 支持一種新的被稱之為 pipe mode 的新模式用于執行大量數據插入工作。
具體可見 《Redis 大量數據插入》 文章。
什么是 Redis 事務?
和眾多其它數據庫一樣,Redis 作為 NoSQL 數據庫也同樣提供了事務機制。在Redis中,MULTI / EXEC / DISCARD / WATCH 這四個命令是我們實現事務的基石。相信對有關系型數據庫開發經驗的開發者而言這一概念并不陌生,即便如此,我們還是會簡要的列出 Redis 中事務的實現特征:
-
1、在事務中的所有命令都將會被串行化的順序執行,事務執行期間,Redis 不會再為其它客戶端的請求提供任何服務,從而保證了事物中的所有命令被原子的執行。
-
2、和關系型數據庫中的事務相比,在 Redis 事務中如果有某一條命令執行失敗,其后的命令仍然會被繼續執行。
-
3、我們可以通過 MULTI 命令開啟一個事務,有關系型數據庫開發經驗的人可以將其理解為 "BEGIN TRANSACTION" 語句。在該語句之后執行的命令都,將被視為事務之內的操作,最后我們可以通過執行 EXEC / DISCARD 命令來提交 / 回滾該事務內的所有操作。這兩個 Redis 命令,可被視為等同于關系型數據庫中的 COMMIT / ROLLBACK 語句。
-
4、在事務開啟之前,如果客戶端與服務器之間出現通訊故障并導致網絡斷開,其后所有待執行的語句都將不會被服務器執行。然而如果網絡中斷事件是發生在客戶端執行 EXEC 命令之后,那么該事務中的所有命令都會被服務器執行。
-
5、當使用 Append-Only 模式時,Redis 會通過調用系統函數 write 將該事務內的所有寫操作在本次調用中全部寫入磁盤。然而如果在寫入的過程中出現系統崩潰,如電源故障導致的宕機,那么此時也許只有部分數據被寫入到磁盤,而另外一部分數據卻已經丟失。
Redis 服務器會在重新啟動時執行一系列必要的一致性檢測,一旦發現類似問題,就會立即退出并給出相應的錯誤提示。此時,我們就要充分利用 Redis 工具包中提供的 redis-check-aof 工具,該工具可以幫助我們定位到數據不一致的錯誤,并將已經寫入的部分數據進行回滾。修復之后我們就可以再次重新啟動Redis服務器了。
🦅 如何實現 Redis CAS 操作?
在 Redis 的事務中,WATCH 命令可用于提供CAS(check-and-set)功能。
假設我們通過 WATCH 命令在事務執行之前監控了多個 keys ,倘若在 WATCH 之后有任何 Key 的值發生了變化,EXEC 命令執行的事務都將被放棄,同時返回 nil 應答以通知調用者事務執行失敗。
具體的示例,可以看看 《Redis 事務鎖 CAS 實現以及深入誤區》 。
##
Redis 集群都有哪些方案?
Redis 集群方案如下:
-
1、Redis Sentinel
-
2、Redis Cluster
-
3、Twemproxy
-
4、Codis
-
5、客戶端分片
關于前四種,可以看看 《Redis 實戰(四)集群機制》 這篇文章。
關于最后一種,客戶端分片,在 Redis Cluster 出現之前使用較多,目前已經使用比較少了。實現方式如下:
在業務代碼層實現,起幾個毫無關聯的 Redis 實例,在代碼層,對 Key 進行 hash 計算,然后去對應的 Redis 實例操作數據。
這種方式對 hash 層代碼要求比較高,考慮部分包括,節點失效后的替代算法方案,數據震蕩后的自動腳本恢復,實例的監控,等等。
🦅 選擇
目前一般在選型上來說:
-
體量較小時,選擇 Redis Sentinel ,單主 Redis 足以支撐業務。
-
體量較大時,選擇 Redis Cluster ,通過分片,使用更多內存。
🦅 Redis 集群如何擴容?
這個問題,了解的也不是很多,建議在搜索有什么方案。
-
如果 Redis 被當做緩存使用,使用一致性哈希實現動態擴容縮容。
-
如果 Redis 被當做一個持久化存儲使用,必須使用固定的 keys-to-nodes 映射關系,節點的數量一旦確定不能變化。否則的話(即Redis 節點需要動態變化的情況),必須使用可以在運行時進行數據再平衡的一套系統,而當前只有 Redis Cluster、Codis 可以做到這樣。
什么是 Redis 主從同步?
Redis 主從同步
Redis 的主從同步(replication)機制,允許 Slave 從 Master 那里,通過網絡傳輸拷貝到完整的數據備份,從而達到主從機制。
-
主數據庫可以進行讀寫操作,當發生寫操作的時候自動將數據同步到從數據庫,而從數據庫一般是只讀的,并接收主數據庫同步過來的數據。
-
一個主數據庫可以有多個從數據庫,而一個從數據庫只能有一個主數據庫。
-
第一次同步時,主節點做一次 bgsave 操作,并同時將后續修改操作記錄到內存 buffer ,待完成后將 RDB 文件全量同步到復制節點,復制節點接受完成后將 RDB 鏡像加載到內存。加載完成后,再通知主節點將期間修改的操作記錄同步到復制節點進行重放就完成了同步過程。
好處
通過 Redis 的復制功,能可以很好的實現數據庫的讀寫分離,提高服務器的負載能力。主數據庫主要進行寫操作,而從數據庫負責讀操作。
Redis 主從同步,是很多 Redis 集群方案的基礎,例如 Redis Sentinel、Redis Cluster 等等。
更多詳細,可以看看 《Redis 主從架構》 。
如何使用 Redis Sentinel 實現高可用?
可以看看 《Redis 哨兵集群實現高可用》 。
如果使用 Redis Cluster 實現高可用?
可以看看
-
《Redis 集群教程》 完整版
-
《Redis 集群模式的工作原理能說一下么?》 精簡版
🦅 說說 Redis 哈希槽的概念?
Redis Cluster 沒有使用一致性 hash ,而是引入了哈希槽的概念。
Redis 集群有 16384 個哈希槽,每個 key 通過 CRC16 校驗后對 16384 取模來決定放置哪個槽,集群的每個節點負責一部分 hash 槽。
因為最大是 16384 個哈希槽,所以考慮 Redis 集群中的每個節點都能分配到一個哈希槽,所以最多支持 16384 個 Redis 節點。
🦅 Redis Cluster 的主從復制模型是怎樣的?
為了使在部分節點失敗或者大部分節點無法通信的情況下集群仍然可用,所以集群使用了主從復制模型,每個節點都會有 N-1 個復制節點。
所以,Redis Cluster 可以說是 Redis Sentinel 帶分片的加強版。也可以說:
-
Redis Sentinel 著眼于高可用,在 master 宕機時會自動將 slave 提升為 master ,繼續提供服務。
-
Redis Cluster 著眼于擴展性,在單個 Redis 內存不足時,使用Cluster 進行分片存儲。
🦅 Redis Cluster 方案什么情況下會導致整個集群不可用?
有 A,B,C 三個節點的集群,在沒有復制模型的情況下,如果節點 B 宕機了,那么整個集群就會以為缺少 5501-11000 這個范圍的槽而不可用。
🦅 Redis Cluster 會有寫操作丟失嗎?為什么?
Redis 并不能保證數據的強一致性,而是【異步復制】,這意味這在實際中集群在特定的條件下可能會丟失寫操作。
🦅 Redis 集群如何選擇數據庫?
Redis 集群目前無法做數據庫選擇,默認在 0 數據庫。
🦅 請說說生產環境中的 Redis 是怎么部署的?
重點問題,仔細理解。
-
Redis Cluster,10 臺機器,5 臺機器部署了 redis 主實例,另外 5 臺機器部署了 redis 的從實例,每個主實例掛了一個從實例,5 個節點對外提供讀寫服務,每個節點的讀寫高峰 qps 可能可以達到每秒 5 萬,5 臺機器最多是 25 萬讀寫請求每秒。
-
機器是什么配置?32G 內存 + 8 核 CPU + 1T 磁盤,但是分配給 Redis 進程的是 10g 內存,一般線上生產環境,Redis 的內存盡量不要超過 10g,超過 10g 可能會有問題。那么,5 臺機器對外提供讀寫,一共有 50g 內存。
-
因為每個主實例都掛了一個從實例,所以是高可用的,任何一個主實例宕機,都會自動故障遷移,Redis 從實例會自動變成主實例繼續提供讀寫服務。
-
你往內存里寫的是什么數據?每條數據的大小是多少?商品數據,每條數據是 10kb 。100 條數據是 1mb ,10 萬條數據是 1g 。常駐內存的是 200 萬條商品數據,占用內存是 20g,僅僅不到總內存的 50%。目前高峰期每秒就是 3500 左右的請求量。
-
其實大型的公司,會有基礎架構的 team 負責緩存集群的運維。
什么是 Redis 分區?
這個問題,和 「Redis 集群都有哪些方案?」 是同類問題。
🦅 關于如下四個問題,直接看 《Redis 分區》 文章。
-
Redis 分區是什么?
-
分區的優勢?
-
分區的不足?
-
分區類型?
可能有同學們會懵逼,又是 Redis 主從復制,又是 Redis 分區,又是 Redis 集群。傻傻分不清啊!
-
Redis 分區是一種模式,將數據分區到不同的 Redis 節點上,而 Redis 集群的 Redis Cluster、Twemproxy、Codis、客戶端分片( 不包括 Redis Sentinel ) 這四種方案,是 Redis 分區的具體實現。
-
Redis 每個分區,如果想要實現高可用,需要使用到 Redis 主從復制。
🦅 你知道有哪些 Redis 分區實現方案?
Redis 分區方案,主要分成兩種類型:
-
客戶端分區,就是在客戶端就已經決定數據會被存儲到哪個 Redis 節點或者從哪個 Redis 節點讀取。大多數客戶端已經實現了客戶端分區。
-
案例:Redis Cluster 和客戶端分區。
-
-
代理分區,意味著客戶端將請求發送給代理,然后代理決定去哪個節點寫數據或者讀數據。代理根據分區規則決定請求哪些 Redis 實例,然后根據 Redis 的響應結果返回給客戶端。
-
案例:Twemproxy 和 Codis 。
-
查詢路由(Query routing)的意思,是客戶端隨機地請求任意一個 Redis 實例,然后由 Redis 將請求轉發給正確的 Redis 節點。Redis Cluster 實現了一種混合形式的查詢路由,但并不是直接將請求從一個Redis 節點轉發到另一個 Redis 節點,而是在客戶端的幫助下直接 redirect 到正確的 Redis 節點。
🦅 分布式 Redis 是前期做還是后期規模上來了再做好?為什么??
如下是網絡上的一個大答案:
既然 Redis 是如此的輕量(單實例只使用1M內存),為防止以后的擴容,最好的辦法就是一開始就啟動較多實例。即便你只有一臺服務器,你也可以一開始就讓 Redis 以分布式的方式運行,使用分區,在同一臺服務器上啟動多個實例。
一開始就多設置幾個 Redis 實例,例如 32 或者 64 個實例,對大多數用戶來說這操作起來可能比較麻煩,但是從長久來看做這點犧牲是值得的。
這樣的話,當你的數據不斷增長,需要更多的 Redis 服務器時,你需要做的就是僅僅將 Redis 實例從一臺服務遷移到另外一臺服務器而已(而不用考慮重新分區的問題)。一旦你添加了另一臺服務器,你需要將你一半的 Redis 實例從第一臺機器遷移到第二臺機器。
-
和飛哥溝通了下,這個操作不是很合理。
-
無論怎么說,建議,需要搭建下 Redis Sentinel 高可用,至于拓展性,根據自己的情況,是否使用 Redis Cluster 集群。
Redis 有哪些重要的健康指標?
推薦閱讀 《Redis 幾個重要的健康指標》
-
存活情況
-
連接數
-
阻塞客戶端數量
-
使用內存峰值
-
內存碎片率
-
緩存命中率
-
OPS
-
持久化
-
失效KEY
-
慢日志
如何提高 Redis 命中率?
推薦閱讀 《如何提高緩存命中率(Redis)》 。
怎么優化 Redis 的內存占用
推薦閱讀 《Redis 的內存優化》
-
redisObject 對象
-
縮減鍵值對象
-
共享對象池
-
字符串優化
-
編碼優化
-
控制 key 的數量
🦅 一個 Redis 實例最多能存放多少的 keys?List、Set、Sorted Set 他們最多能存放多少元素?
一個 Redis 實例,最多能存放多少的 keys ,List、Set、Sorted Set 他們最多能存放多少元素。
理論上,Redis 可以處理多達 2^32 的 keys ,并且在實際中進行了測試,每個實例至少存放了 2 億 5 千萬的 keys。
任何 list、set、和 sorted set 都可以放 2^32 個元素。
🦅 假如 Redis 里面有 1 億個 key,其中有 10w 個 key 是以某個固定的已知的前綴開頭的,如果將它們全部找出來?
使用 keys 指令可以掃出指定模式的 key 列表。
-
對方接著追問:如果這個 Redis 正在給線上的業務提供服務,那使用keys指令會有什么問題?
-
這個時候你要回答 Redis 關鍵的一個特性:Redis 的單線程的。keys 指令會導致線程阻塞一段時間,線上服務會停頓,直到指令執行完畢,服務才能恢復。這個時候可以使用 scan 指令,scan 指令可以無阻塞的提取出指定模式的 key 列表,但是會有一定的重復概率,在客戶端做一次去重就可以了,但是整體所花費的時間會比直接用 keys 指令長。
Redis 常見的性能問題都有哪些?如何解決?
-
1、Master 最好不要做任何持久化工作,如 RDB 內存快照和 AOF 日志文件。
-
Master 寫內存快照,save 命令調度 rdbSave 函數,會阻塞主線程的工作,當快照比較大時對性能影響是非常大的,會間斷性暫停服務,所以 Master 最好不要寫內存快照。
-
Master AOF 持久化,如果不重寫 AOF 文件,這個持久化方式對性能的影響是最小的,但是 AOF 文件會不斷增大,AOF 文件過大會影響 Master 重啟的恢復速度。
-
所以,Master 最好不要做任何持久化工作,包括內存快照和 AOF 日志文件,特別是不要啟用內存快照做持久化。如果數據比較關鍵,某個 Slave 開啟AOF備份數據,策略為每秒同步一次。
-
-
2、Master 調用 BGREWRITEAOF 重寫 AOF 文件,AOF 在重寫的時候會占大量的 CPU 和內存資源,導致服務 load 過高,出現短暫服務暫停現象。
-
TODO 怎么解決?
-
-
3、盡量避免在壓力很大的主庫上增加從庫。
-
TODO 怎么解決?
-
-
4、主從復制不要用圖狀結構,用單向鏈表結構更為穩定,即:
Master <- Slave1 <- Slave2 <- Slave3...。-
這樣的結構,也方便解決單點故障問題,實現 Slave 對 Master 的替換。如果 Master掛了,可以立刻啟用 Slave1 做 Master ,其他不變。
-
-
5、Redis 主從復制的性能問題,為了主從復制的速度和連接的穩定性,Slave 和 Master 最好在同一個局域網內。
和飛哥溝通過后,他們主節點開啟 AOF ,從節點開啟 AOF + RDB 。
和曉峰溝通后,他們主節點開啟 AOF ,從節點開啟 RDB 居多,也有開啟 AOF + RDB 的。
修改配置不重啟 Redis 會實時生效嗎?
針對運行實例,有許多配置選項可以通過 CONFIG SET 命令進行修改,而無需執行任何形式的重啟。
從 Redis 2.2 開始,可以從 AOF 切換到 RDB 的快照持久性或其他方式而不需要重啟 Redis。檢索 CONFIG GET * 命令獲取更多信息。
但偶爾重新啟動是必須的,如為升級 Redis 程序到新的版本,或者當你需要修改某些目前 CONFIG 命令還不支持的配置參數的時候。
redis相比memcached有哪些優勢?
memcached所有的值均是簡單的字符串,redis作為其替代者,支持更為豐富的數據類型
redis的速度比memcached快很多
redis可以持久化其數據
Memcache與Redis的區別都有哪些?
存儲方式 Memecache把數據全部存在內存之中,斷電后會掛掉,數據不能超過內存大小。 Redis有部份存在硬盤上,這樣能保證數據的持久性。
數據支持類型 Memcache對數據類型支持相對簡單。 Redis有復雜的數據類型。
使用底層模型不同 它們之間底層實現方式 以及與客戶端之間通信的應用協議不一樣。 Redis直接自己構建了VM 機制 ,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。
redis常見性能問題和解決方案:
Master寫內存快照,save命令調度rdbSave函數,會阻塞主線程的工作,當快照比較大時對性能影響是非常大的,會間斷性暫停服務,所以Master最好不要寫內存快照。
Master AOF持久化,如果不重寫AOF文件,這個持久化方式對性能的影響是最小的,但是AOF文件會不斷增大,AOF文件過大會影響Master重啟的恢復速度。Master最好不要做任何持久化工作,包括內存快照和AOF日志文件,特別是不要啟用內存快照做持久化,如果數據比較關鍵,某個Slave開啟AOF備份數據,策略為每秒同步一次。
Master調用BGREWRITEAOF重寫AOF文件,AOF在重寫的時候會占大量的CPU和內存資源,導致服務load過高,出現短暫服務暫停現象。
Redis主從復制的性能問題,為了主從復制的速度和連接的穩定性,Slave和Master最好在同一個局域網內
7mySQL里有2000w數據,redis中只存20w的數據,如何保證redis中的數據都是熱點數據
相關知識:redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略(回收策略)。
redis 提供 6種數據淘汰策略:
volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
no-enviction(驅逐):禁止驅逐數據
請用Redis和任意語言實現一段惡意登錄保護的代碼,限制1小時內每用戶Id最多只能登錄5次。具體登錄函數或功能用空函數即可,不用詳細寫出。
用列表實現:列表中每個元素代表登陸時間,只要最后的第5次登陸時間和現在時間差不超過1小時就禁止登陸.用Python寫的代碼如下:
#!/usr/bin/env python3 import redis ? import sys ? import time ? ? r = redis.StrictRedis(host=’127.0.0.1′, port=6379, db=0) ? try: ? ? ? id = sys.argv[1] except: ? ? ?print(‘input argument error’) ? ?sys.exit(0) ? if r.llen(id) >= 5 and time.time() – float(r.lindex(id, 4)) <= 3600: ? ? ?print(“you are forbidden logining”) else: ? ? ? print(‘you are allowed to login’) ? ?r.lpush(id, time.time()) ? ?# login_func()?
為什么redis需要把所有數據放到內存中?
Redis為了達到最快的讀寫速度將數據都讀到內存中,并通過異步的方式將數據寫入磁盤。所以redis具有快速和數據持久化的特征。如果不將數據放在內存中,磁盤I/O速度為嚴重影響redis的性能。在內存越來越便宜的今天,redis將會越來越受歡迎。
如果設置了最大使用的內存,則數據已有記錄數達到內存限值后不能繼續插入新值。
?
Redis是單進程單線程的
redis利用隊列技術將并發訪問變為串行訪問,消除了傳統數據庫串行控制的開銷
?
redis的并發競爭問題如何解決?
Redis為單進程單線程模式,采用隊列模式將并發訪問變為串行訪問。Redis本身沒有鎖的概念,Redis對于多個客戶端連接并不存在競爭,但是在Jedis客戶端對Redis進行并發訪問時會發生連接超時、數據轉換錯誤、阻塞、客戶端關閉連接等問題,這些問題均是由于客戶端連接混亂造成。
對此有2種解決方法:
1.客戶端角度,為保證每個客戶端間正常有序與Redis進行通信,對連接進行池化,同時對客戶端讀寫Redis操作采用內部鎖synchronized。
2.服務器角度,利用setnx實現鎖。
注:對于第一種,需要應用程序自己處理資源的同步,可以使用的方法比較通俗,可以使用synchronized也可以使用lock;第二種需要用到Redis的setnx命令,但是需要注意一些問題。
?
redis事物的了解CAS(check-and-set 操作實現樂觀鎖 )?
和眾多其它數據庫一樣,Redis作為NoSQL數據庫也同樣提供了事務機制。在Redis中,MULTI/EXEC/DISCARD/WATCH這四個命令是我們實現事務的基石。
相信對有關系型數據庫開發經驗的開發者而言這一概念并不陌生,即便如此,我們還是會簡要的列出Redis中事務的實現特征:
在事務中的所有命令都將會被串行化的順序執行,事務執行期間,Redis不會再為其它客戶端的請求提供任何服務,從而保證了事物中的所有命令被原子的執行。
和關系型數據庫中的事務相比,在Redis事務中如果有某一條命令執行失敗,其后的命令仍然會被繼續執行。
我們可以通過MULTI命令開啟一個事務,有關系型數據庫開發經驗的人可以將其理解為"BEGIN TRANSACTION"語句。在該語句之后執行的命令都將被視為事務之內的操作,最后我們可以通過執行EXEC/DISCARD命令來提交/回滾該事務內的所有操作。這兩個Redis命令可被視為等同于關系型數據庫中的COMMIT/ROLLBACK語句。
在事務開啟之前,如果客戶端與服務器之間出現通訊故障并導致網絡斷開,其后所有待執行的語句都將不會被服務器執行。然而如果網絡中斷事件是發生在客戶端執行EXEC命令之后,那么該事務中的所有命令都會被服務器執行。
當使用Append-Only模式時,Redis會通過調用系統函數write將該事務內的所有寫操作在本次調用中全部寫入磁盤。然而如果在寫入的過程中出現系統崩潰,如電源故障導致的宕機,那么此時也許只有部分數據被寫入到磁盤,而另外一部分數據卻已經丟失。
Redis服務器會在重新啟動時執行一系列必要的一致性檢測,一旦發現類似問題,就會立即退出并給出相應的錯誤提示。
此時,我們就要充分利用Redis工具包中提供的redis-check-aof工具,該工具可以幫助我們定位到數據不一致的錯誤,并將已經寫入的部分數據進行回滾。修復之后我們就可以再次重新啟動Redis服務器了。
?
WATCH命令和基于CAS的樂觀鎖:
在Redis的事務中,WATCH命令可用于提供CAS(check-and-set)功能。假設我們通過WATCH命令在事務執行之前監控了多個Keys,倘若在WATCH之后有任何Key的值發生了變化,EXEC命令執行的事務都將被放棄,同時返回Null multi-bulk應答以通知調用者事務
執行失敗。例如,我們再次假設Redis中并未提供incr命令來完成鍵值的原子性遞增,如果要實現該功能,我們只能自行編寫相應的代碼。其偽碼如下:
val = GET mykey val = val + 1 SET mykey $val以上代碼只有在單連接的情況下才可以保證執行結果是正確的,因為如果在同一時刻有多個客戶端在同時執行該段代碼,那么就會出現多線程程序中經常出現的一種錯誤場景--競態爭用(race condition)。
比如,客戶端A和B都在同一時刻讀取了mykey的原有值,假設該值為10,此后兩個客戶端又均將該值加一后set回Redis服務器,這樣就會導致mykey的結果為11,而不是我們認為的12。為了解決類似的問題,我們需要借助WATCH命令的幫助,見如下代碼:
WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC和此前代碼不同的是,新代碼在獲取mykey的值之前先通過WATCH命令監控了該鍵,此后又將set命令包圍在事務中,這樣就可以有效的保證每個連接在執行EXEC之前,如果當前連接獲取的mykey的值被其它連接的客戶端修改,那么當前連接的EXEC命令將執行失敗。這樣調用者在判斷返回值后就可以獲悉val是否被重新設置成功。
?
redis持久化的幾種方式
1、快照(snapshots)
缺省情況情況下,Redis把數據快照存放在磁盤上的二進制文件中,文件名為dump.rdb。你可以配置Redis的持久化策略,例如數據集中每N秒鐘有超過M次更新,就將數據寫入磁盤;或者你可以手工調用命令SAVE或BGSAVE。
工作原理
-
Redis forks.
-
子進程開始將數據寫到臨時RDB文件中。
-
當子進程完成寫RDB文件,用新文件替換老文件。
-
這種方式可以使Redis使用copy-on-write技術。
2、AOF
快照模式并不十分健壯,當系統停止,或者無意中Redis被kill掉,最后寫入Redis的數據就會丟失。
這對某些應用也許不是大問題,但對于要求高可靠性的應用來說,Redis就不是一個合適的選擇。Append-only文件模式是另一種選擇。你可以在配置文件中打開AOF模式
3、虛擬內存方式
當你的key很小而value很大時,使用VM的效果會比較好.因為這樣節約的內存比較大.
當你的key不小時,可以考慮使用一些非常方法將很大的key變成很大的value,比如你可以考慮將key,value組合成一個新的value.
vm-max-threads這個參數,可以設置訪問swap文件的線程數,設置最好不要超過機器的核數,如果設置為0,那么所有對swap文件的操作都是串行的.可能會造成比較長時間的延遲,但是對數據完整性有很好的保證.
自己測試的時候發現用虛擬內存性能也不錯。如果數據量很大,可以考慮分布式或者其他數據庫。
?
redis的緩存失效策略和主鍵失效機制
作為緩存系統都要定期清理無效數據,就需要一個主鍵失效和淘汰策略.
在Redis當中,有生存期的key被稱為volatile。在創建緩存時,要為給定的key設置生存期,當key過期的時候(生存期為0),它可能會被刪除。
1、影響生存時間的一些操作
生存時間可以通過使用 DEL 命令來刪除整個 key 來移除,或者被 SET 和 GETSET 命令覆蓋原來的數據,也就是說,修改key對應的value和使用另外相同的key和value來覆蓋以后,當前數據的生存時間不同。
比如說,對一個 key 執行INCR命令,對一個列表進行LPUSH命令,或者對一個哈希表執行HSET命令,這類操作都不會修改 key 本身的生存時間。另一方面,如果使用RENAME對一個 key 進行改名,那么改名后的 key的生存時間和改名前一樣。
RENAME命令的另一種可能是,嘗試將一個帶生存時間的 key 改名成另一個帶生存時間的 another_key ,這時舊的 another_key (以及它的生存時間)會被刪除,然后舊的 key 會改名為 another_key ,因此,新的 another_key 的生存時間也和原本的 key 一樣。使用PERSIST命令可以在不刪除 key 的情況下,移除 key 的生存時間,讓 key 重新成為一個persistent key 。
2、如何更新生存時間
可以對一個已經帶有生存時間的 key 執行EXPIRE命令,新指定的生存時間會取代舊的生存時間。過期時間的精度已經被控制在1ms之內,主鍵失效的時間復雜度是O(1),EXPIRE和TTL命令搭配使用,TTL可以查看key的當前生存時間。設置成功返回 1;當 key 不存在或者不能為 key 設置生存時間時,返回 0 。
最大緩存配置:
在 redis 中,允許用戶設置最大使用內存大小,server.maxmemory默認為0,沒有指定最大緩存,如果有新的數據添加,超過最大內存,則會使redis崩潰,所以一定要設置。redis 內存數據集大小上升到一定大小的時候,就會實行數據淘汰策略。
redis 提供 6種數據淘汰策略:
volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
no-enviction(驅逐):禁止驅逐數據
注意這里的6種機制,volatile和allkeys規定了是對已設置過期時間的數據集淘汰數據還是從全部數據集淘汰數據,后面的lru、ttl以及random是三種不同的淘汰策略,再加上一種no-enviction永不回收的策略。
使用策略規則:
如果數據呈現冪律分布,也就是一部分數據訪問頻率高,一部分數據訪問頻率低,則使用allkeys-lru
如果數據呈現平等分布,也就是所有的數據訪問頻率都相同,則使用allkeys-random
三種數據淘汰策略:
ttl和random比較容易理解,實現也會比較簡單。主要是Lru最近最少使用淘汰策略,設計上會對key 按失效時間排序,然后取最先失效的key進行淘汰
?
redis 最適合的場景
Redis最適合所有數據in-momory的場景,雖然Redis也提供持久化功能,但實際更多的是一個disk-backed的功能,跟傳統意義上的持久化有比較大的差別,那么可能大家就會有疑問,似乎Redis更像一個加強版的Memcached,那么何時使用Memcached,何時使用Redis呢?
如果簡單地比較Redis與Memcached的區別,大多數都會得到以下觀點:
Redis不僅僅支持簡單的k/v類型的數據,同時還提供list,set,zset,hash等數據結構的存儲。
Redis支持數據的備份,即master-slave模式的數據備份。
Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啟的時候可以再次加載進行使用。
1、會話緩存(Session Cache)
最常用的一種使用Redis的情景是會話緩存(session cache)。用Redis緩存會話比其他存儲(如Memcached)的優勢在于:Redis提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的購物車信息全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎?
幸運的是,隨著 Redis 這些年的改進,很容易找到怎么恰當的使用Redis來緩存會話的文檔。甚至廣為人知的商業平臺Magento也提供Redis的插件。
2、全頁緩存(FPC)
除基本的會話token之外,Redis還提供很簡便的FPC平臺。回到一致性問題,即使重啟了Redis實例,因為有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似PHP本地FPC。
再次以Magento為例,Magento提供一個插件來使用Redis作為全頁緩存后端。
此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。
3、隊列
Reids在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作為一個很好的消息隊列平臺來使用。Redis作為隊列使用的操作,就類似于本地程序語言(如Python)對 list 的 push/pop 操作。
如果你快速的在Google中搜索“Redis queues”,你馬上就能找到大量的開源項目,這些項目的目的就是利用Redis創建非常好的后端工具,以滿足各種隊列需求。例如,Celery有一個后臺就是使用Redis作為broker,你可以從這里去查看。
4、排行榜/計數器
Redis在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(Sorted Set)也使得我們在執行這些操作的時候變的非常簡單,Redis只是正好提供了這兩種數據結構。
所以,我們要從排序集合中獲取到排名最靠前的10個用戶–我們稱之為“user_scores”,我們只需要像下面一樣執行即可:
當然,這是假定你是根據你用戶的分數做遞增的排序。如果你想返回用戶及用戶的分數,你需要這樣執行:
ZRANGE user_scores 0 10 WITHSCORESAgora Games就是一個很好的例子,用Ruby實現的,它的排行榜就是使用Redis來存儲數據的,你可以在這里看到。
5、發布/訂閱
最后(但肯定不是最不重要的)是Redis的發布/訂閱功能。發布/訂閱的使用場景確實非常多。我已看見人們在社交網絡連接中使用,還可作為基于發布/訂閱的腳本觸發器,甚至用Redis的發布/訂閱功能來建立聊天系統!(不,這是真的,你可以去核實)。
Redis提供的所有特性中,我感覺這個是喜歡的人最少的一個,雖然它為用戶提供如果此多功能。
其他問題
有些比較兇殘的面試官,可能會問我們一些 Redis 數據結構的問題,例如:
-
Skiplist 插入和查詢原理?
-
壓縮列表的原理?
-
Redis 底層為什么使用跳躍表而不是紅黑樹?
跳躍表在范圍查找的時候性能比較高。
666. 彩蛋
哇哦,爽。雖然過程痛苦,但是中間請教了蠻多人問題,收獲頗多哈。
參考與推薦如下文章:
-
JeffreyLcm 《Redis 面試題》
-
烙印99 《史上最全 Redis 面試題及答案》
-
yanglbme 《Redis 和 Memcached 有什么區別?Redis 的線程模型是什么?為什么單線程的 Redis 比多線程的 Memcached 效率要高得多?》
-
老錢 《天下無難試之 Redis 面試題刁難大全》
-
yanglbme 《Redis 的持久化有哪幾種方式?不同的持久化機制都有什么優缺點?持久化機制具體底層是如何實現的?》
總結
以上是生活随笔為你收集整理的Redis面试题详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis在生产中不得不重视的几个运维问
- 下一篇: 沙盘数据库表的设计