Redis中必須要掌握的20個問題,快來收藏吧!!

本篇文章給大家分享20個必知必會、必須要掌握的redis問題,希望對大家有所幫助,快來收藏吧!

Redis中必須要掌握的20個問題,快來收藏吧!!

redis是什么?

Redis(Remote Dictionary Server)是一個使用 C 語言編寫的,高性能非關系型的鍵值對數據庫。與傳統數據庫不同的是,Redis 的數據是存在內存中的,所以讀寫速度非常快,被廣泛應用于緩存方向。Redis可以將數據寫入磁盤中,保證了數據的安全不丟失,而且Redis的操作是原子性的。【相關推薦:Redis視頻教程

Redis的優點?

  • 基于內存操作,內存讀寫速度快。

  • Redis是線程的,避免線程切換開銷及多線程的競爭問題。單線程是指網絡請求使用一個線程來處理,即一個線程處理所有網絡請求,Redis 運行時不止有一個線程,比如數據持久化的過程會另起線程。

  • 支持多種數據類型,包括String、Hash、List、Set、ZSet等。

  • 支持持久化。Redis支持RDB和AOF兩種持久化機制,持久化功能可以有效地避免數據丟失問題。

  • 支持事務。Redis的所有操作都是原子性的,同時Redis還支持對幾個操作合并后的原子性執行。

  • 支持主從復制。主節點會自動將數據同步到從節點,可以進行讀寫分離。

Redis為什么這么快?

  • 基于內存:Redis是使用內存存儲,沒有磁盤IO上的開銷。數據存在內存中,讀寫速度快。
  • 單線程實現( Redis 6.0以前):Redis使用單個線程處理請求,避免了多個線程之間線程切換和鎖資源爭用的開銷。
  • IO多路復用模型:Redis 采用 IO 多路復用技術。Redis 使用單線程來輪詢描述符,將數據庫的操作都轉換成了事件,不在網絡I/O上浪費過多的時間。
  • 高效的數據結構:Redis 每種數據類型底層都做了優化,目的就是為了追求更快的速度。

Redis為何選擇單線程?

  • 避免過多的上下文切換開銷。程序始終運行在進程中單個線程內,沒有多線程切換的場景。
  • 避免同步機制的開銷:如果 Redis選擇多線程模型,需要考慮數據同步的問題,則必然會引入某些同步機制,會導致在操作數據過程中帶來更多的開銷,增加程序復雜度的同時還會降低性能。
  • 實現簡單,方便維護:如果 Redis使用多線程模式,那么所有的底層數據結構的設計都必須考慮線程安全問題,那么 Redis 的實現將會變得更加復雜。

Redis應用場景有哪些?

  • 緩存熱點數據,緩解數據庫的壓力。

  • 利用 Redis 原子性的自增操作,可以實現計數器的功能,比如統計用戶點贊數、用戶訪問數等。

  • 簡單的消息隊列,可以使用Redis自身的發布/訂閱模式或者List來實現簡單的消息隊列,實現異步操作。

  • 限速器,可用于限制某個用戶訪問某個接口的頻率,比如秒殺場景用于防止用戶快速點擊帶來不必要的壓力。

  • 好友關系,利用集合的一些命令,比如交集、并集、差集等,實現共同好友、共同愛好之類的功能。

memcached和Redis的區別?

  • Redis 只使用單核,而 Memcached 可以使用多核。

  • MemCached 數據結構單一,僅用來緩存數據,而 Redis 支持多種數據類型

  • MemCached 不支持數據持久化,重啟后數據會消失。Redis 支持數據持久化

  • Redis 提供主從同步機制和 cluster 集群部署能力,能夠提供高可用服務。Memcached 沒有提供原生的集群模式,需要依靠客戶端實現往集群中分片寫入數據。

  • Redis 的速度比 Memcached 快很多。

  • Redis 使用單線程的多路 IO 復用模型,Memcached使用多線程的非阻塞 IO 模型。

Redis 數據類型有哪些?

基本數據類型

1、String:最常用的一種數據類型,String類型的值可以是字符串、數字或者二進制,但值最大不能超過512MB。

2、Hash:Hash 是一個鍵值對集合。

3、Set:無序去重的集合。Set 提供了交集、并集等方法,對于實現共同好友、共同關注等功能特別方便。

4、List:有序可重復的集合,底層是依賴雙向鏈表實現的。

5、SortedSet(ZSet):有序Set。內部維護了一個score的參數來實現。適用于排行榜和帶權重的消息隊列等場景。

特殊的數據類型

1、Bitmap:位圖,可以認為是一個以位為單位數組,數組中的每個單元只能存0或者1,數組的下標在 Bitmap 中叫做偏移量。Bitmap的長度與集合中元素個數無關,而是與基數的上限有關。

2、Hyperloglog。HyperLogLog 是用來做基數統計的算法,其優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定的、并且是很小的。典型的使用場景是統計獨立訪客。

3、Geospatial :主要用于存儲地理位置信息,并對存儲的信息進行操作,適用場景如定位、附近的人等。

Redis事務

事務的原理是將一個事務范圍內的若干命令發送給 Redis,然后再讓 Redis 依次執行這些命令。

事務的生命周期:

  • 使用MULTI開啟一個事務;

  • 在開啟事務的時候,每次操作的命令將會被插入到一個隊列中,同時這個命令并不會被真正執行;

  • EXEC命令進行提交事務。

Redis中必須要掌握的20個問題,快來收藏吧!!

一個事務范圍內某個命令出錯不會影響其他命令的執行,不保證原子性:

first:0>MULTI "OK" first:0>set?a?1 "QUEUED" first:0>set?b?2?3?4 "QUEUED" first:0>set?c?6 "QUEUED" first:0>EXEC 1)?"OK" 2)?"OK" 3)?"OK" 4)?"ERR?syntax?error" 5)?"OK" 6)?"OK" 7)?"OK"

WATCH命令

WATCH命令可以監控一個或多個鍵,一旦其中有一個鍵被修改,之后的事務就不會執行(類似于樂觀鎖)。執行EXEC命令之后,就會自動取消監控。

first:0>watch?name "OK" first:0>set?name?1 "OK" first:0>MULTI "OK" first:0>set?name?2 "QUEUED" first:0>set?gender?1 "QUEUED" first:0>EXEC (nil) first:0>get?gender (nil)

比如上面的代碼中:

  1. watch name開啟了對name這個key的監控
  2. 修改name的值
  3. 開啟事務a
  4. 在事務a中設置了name和gender的值
  5. 使用EXEC命令進提交事務
  6. 使用命令get gender發現不存在,即事務a沒有執行

使用UNWATCH可以取消WATCH命令對key的監控,所有監控鎖將會被取消。

持久化機制

持久化就是把內存的數據寫到磁盤中,防止服務宕機導致內存數據丟失。

Redis支持兩種方式的持久化,一種是RDB的方式,一種是AOF的方式。前者會根據指定的規則定時將內存中的數據存儲在硬盤上,而后者在每次執行完命令后將命令記錄下來。一般將兩者結合使用。

RDB方式

RDB是 Redis 默認的持久化方案。RDB持久化時會將內存中的數據寫入到磁盤中,在指定目錄下生成一個dump.rdb文件。Redis 重啟會加載dump.rdb文件恢復數據。

bgsave是主流的觸發 RDB 持久化的方式,執行過程如下:

Redis中必須要掌握的20個問題,快來收藏吧!!

  • 執行BGSAVE命令
  • Redis 父進程判斷當前是否存在正在執行的子進程,如果存在,BGSAVE命令直接返回。
  • 父進程執行fork操作創建子進程,fork操作過程中父進程會阻塞。
  • 父進程fork完成后,父進程繼續接收并處理客戶端的請求,而子進程開始將內存中的數據寫進硬盤的臨時文件
  • 當子進程寫完所有數據后會用該臨時文件替換舊的 RDB 文件

Redis啟動時會讀取RDB快照文件,將數據從硬盤載入內存。通過 RDB 方式的持久化,一旦Redis異常退出,就會丟失最近一次持久化以后更改的數據。

觸發 RDB 持久化的方式:

  1. 手動觸發:用戶執行SAVE或BGSAVE命令。SAVE命令執行快照的過程會阻塞所有客戶端的請求,應避免在生產環境使用此命令。BGSAVE命令可以在后臺異步進行快照操作,快照的同時服務器還可以繼續響應客戶端的請求,因此需要手動執行快照時推薦使用BGSAVE命令。

  2. 被動觸發

    • 根據配置規則進行自動快照,如SAVE 100 10,100秒內至少有10個鍵被修改則進行快照。
    • 如果從節點執行全量復制操作,主節點會自動執行BGSAVE生成 RDB 文件并發送給從節點。
    • 默認情況下執行shutdown命令時,如果沒有開啟 AOF 持久化功能則自動執行·BGSAVE·。

優點

  • Redis 加載 RDB 恢復數據遠遠快于 AOF 的方式

  • 使用單獨子進程來進行持久化,主進程不會進行任何 IO 操作,保證了 Redis 的高性能

缺點

  • RDB方式數據無法做到實時持久化。因為BGSAVE每次運行都要執行fork操作創建子進程,屬于重量級操作,頻繁執行成本比較高。

  • RDB 文件使用特定二進制格式保存,Redis 版本升級過程中有多個格式的 RDB 版本,存在老版本 Redis 無法兼容新版 RDB 格式的問題

AOF方式

AOF(append only file)持久化:以獨立日志的方式記錄每次寫命令,Redis重啟時會重新執行AOF文件中的命令達到恢復數據的目的。AOF的主要作用是解決了數據持久化的實時性,AOF 是Redis持久化的主流方式。

默認情況下Redis沒有開啟AOF方式的持久化,可以通過appendonly參數啟用:appendonly yes。開啟AOF方式持久化后每執行一條寫命令,Redis就會將該命令寫進aof_buf緩沖區,AOF緩沖區根據對應的策略向硬盤做同步操作。

默認情況下系統每30秒會執行一次同步操作。為了防止緩沖區數據丟失,可以在Redis寫入AOF文件后主動要求系統將緩沖區數據同步到硬盤上。可以通過appendfsync參數設置同步的時機。

appendfsync?always?//每次寫入aof文件都會執行同步,最安全最慢,不建議配置 appendfsync?everysec??//既保證性能也保證安全,建議配置 appendfsync?no?//由操作系統決定何時進行同步操作

接下來看一下 AOF 持久化執行流程:

Redis中必須要掌握的20個問題,快來收藏吧!!

  • 所有的寫入命令會追加到 AOP 緩沖區中。

  • AOF 緩沖區根據對應的策略向硬盤同步。

  • 隨著 AOF 文件越來越大,需要定期對 AOF 文件進行重寫,達到壓縮文件體積的目的。AOF文件重寫是把Redis進程內的數據轉化為寫命令同步到新AOF文件的過程。

  • 當 Redis 服務器重啟時,可以加載 AOF 文件進行數據恢復。

優點

  • AOF可以更好的保護數據不丟失,可以配置 AOF 每秒執行一次fsync操作,如果Redis進程掛掉,最多丟失1秒的數據。

  • AOF以append-only的模式寫入,所以沒有磁盤尋址的開銷,寫入性能非常高。

缺點

  • 對于同一份文件AOF文件比RDB數據快照要大。

  • 數據恢復比較慢。

主從復制

Redis的復制功能是支持多個數據庫之間的數據同步。主數據庫可以進行讀寫操作,當主數據庫的數據發生變化時會自動將數據同步到從數據庫。從數據庫一般是只讀的,它會接收主數據庫同步過來的數據。一個主數據庫可以有多個從數據庫,而一個從數據庫只能有一個主數據庫。

//啟動Redis實例作為主數據庫 redis-server?? //啟動另一個實例作為從數據庫 redis-server?--port?6380?--slaveof??127.0.0.1?6379??? slaveof?127.0.0.1?6379 //停止接收其他數據庫的同步并轉化為主數據庫 SLAVEOF?NO?ONE

主從復制的原理?

  • 當啟動一個從節點時,它會發送一個 PSYNC 命令給主節點;

  • 如果是從節點初次連接到主節點,那么會觸發一次全量復制。此時主節點會啟動一個后臺線程,開始生成一份 RDB 快照文件;

  • 同時還會將從客戶端 client 新收到的所有寫命令緩存在內存中。RDB 文件生成完畢后, 主節點會將RDB文件發送給從節點,從節點會先將RDB文件寫入本地磁盤,然后再從本地磁盤加載到內存中

  • 接著主節點會將內存中緩存的寫命令發送到從節點,從節點同步這些數據;

  • 如果從節點跟主節點之間網絡出現故障,連接斷開了,會自動重連,連接之后主節點僅會將部分缺失的數據同步給從節點。

哨兵sentinel

主從復制存在不能自動故障轉移、達不到高可用的問題。哨兵模式解決了這些問題。通過哨兵機制可以自動切換主從節點。

客戶端連接Redis的時候,先連接哨兵,哨兵會告訴客戶端Redis主節點的地址,然后客戶端連接上Redis并進行后續的操作。當主節點宕機的時候,哨兵監測到主節點宕機,會重新推選出某個表現良好的從節點成為新的主節點,然后通過發布訂閱模式通知其他的從服務器,讓它們切換主機。

Redis中必須要掌握的20個問題,快來收藏吧!!

工作原理

  • 每個Sentinel以每秒鐘一次的頻率向它所知道的Master,Slave以及其他 Sentinel實例發送一個 PING命令。
  • 如果一個實例距離最后一次有效回復 PING 命令的時間超過指定值, 則這個實例會被 Sentine 標記為主觀下線。
  • 如果一個Master被標記為主觀下線,則正在監視這個Master的所有 Sentinel要以每秒一次的頻率確認Master是否真正進入主觀下線狀態。
  • 當有足夠數量的 Sentinel(大于等于配置文件指定值)在指定的時間范圍內確認Master的確進入了主觀下線狀態, 則Master會被標記為客觀下線 。若沒有足夠數量的 Sentinel同意 Master 已經下線, Master 的客觀下線狀態就會被解除。若 Master重新向 Sentinel 的 PING 命令返回有效回復, Master 的主觀下線狀態就會被移除。
  • 哨兵節點會選舉出哨兵 leader,負責故障轉移的工作。
  • 哨兵 leader 會推選出某個表現良好的從節點成為新的主節點,然后通知其他從節點更新主節點信息。

Redis cluster

哨兵模式解決了主從復制不能自動故障轉移、達不到高可用的問題,但還是存在主節點的寫能力、容量受限于單機配置的問題。而cluster模式實現了Redis的分布式存儲,每個節點存儲不同的內容,解決主節點的寫能力、容量受限于單機配置的問題。

Redis cluster集群節點最小配置6個節點以上(3主3從),其中主節點提供讀寫操作,從節點作為備用節點,不提供請求,只作為故障轉移使用。

Redis cluster采用虛擬槽分區,所有的鍵根據哈希函數映射到0~16383個整數槽內,每個節點負責維護一部分槽以及槽所映射的鍵值數據。

Redis中必須要掌握的20個問題,快來收藏吧!!

哈希槽是如何映射到 Redis 實例上的?

  • 對鍵值對的key使用 crc16 算法計算一個結果

  • 將結果對 16384 取余,得到的值表示 key 對應的哈希槽

  • 根據該槽信息定位到對應的實例

優點:

  • 無中心架構支持動態擴容;
  • 數據按照slot存儲分布在多個節點,節點間數據共享,可動態調整數據分布
  • 高可用性。部分節點不可用時,集群仍可用。集群模式能夠實現自動故障轉移(failover),節點之間通過gossip協議交換狀態信息,用投票機制完成Slave到Master的角色轉換。

缺點:

  • 不支持批量操作(pipeline)。
  • 數據通過異步復制,不保證數據的強一致性
  • 事務操作支持有限,只支持多key在同一節點上的事務操作,當多個key分布于不同的節點上時無法使用事務功能。
  • key作為數據分區的最小粒度,不能將一個很大的鍵值對象如hash、list等映射到不同的節點。
  • 不支持多數據庫空間,單機下的Redis可以支持到16個數據庫,集群模式下只能使用1個數據庫空間。

過期鍵的刪除策略?

1、被動刪除(惰性)。在訪問key時,如果發現key已經過期,那么會將key刪除。

2、主動刪除(定期)。定時清理key,每次清理會依次遍歷所有DB,從db隨機取出20個key,如果過期就刪除,如果其中有5個key過期,那么就繼續對這個db進行清理,否則開始清理下一個db。

3、內存不夠時清理。Redis有最大內存的限制,通過maxmemory參數可以設置最大內存,當使用的內存超過了設置的最大內存,就要進行內存釋放, 在進行內存釋放的時候,會按照配置的淘汰策略清理內存。

內存淘汰策略有哪些?

當Redis的內存超過最大允許的內存之后,Redis 會觸發內存淘汰策略,刪除一些不常用的數據,以保證Redis服務器正常運行。

Redisv4.0前提供 6 種數據淘汰策略

  • volatile-lru:LRU(Least Recently Used),最近使用。利用LRU算法移除設置了過期時間的key
  • allkeys-lru:當內存不足以容納新寫入數據時,從數據集中移除最近最少使用的key
  • volatile-ttl:從已設置過期時間的數據集中挑選將要過期的數據淘汰
  • volatile-random:從已設置過期時間的數據集中任意選擇數據淘汰
  • allkeys-random:從數據集中任意選擇數據淘汰
  • no-eviction:禁止刪除數據,當內存不足以容納新寫入數據時,新寫入操作會報錯

Redisv4.0后增加以下兩種

  • volatile-lfu:LFU,Least Frequently Used,最少使用,從已設置過期時間的數據集中挑選最不經常使用的數據淘汰。
  • allkeys-lfu:當內存不足以容納新寫入數據時,從數據集中移除最不經常使用的key。

內存淘汰策略可以通過配置文件來修改,相應的配置項是maxmemory-policy,默認配置是noeviction。

如何保證緩存與數據庫雙寫時的數據一致性?

1、先刪除緩存再更新數據庫

進行更新操作時,先刪除緩存,然后更新數據庫,后續的請求再次讀取時,會從數據庫讀取后再將新數據更新到緩存。

存在的問題:刪除緩存數據之后,更新數據庫完成之前,這個時間段內如果有新的讀請求過來,就會從數據庫讀取舊數據重新寫到緩存中,再次造成不一致,并且后續讀的都是舊數據。

2、先更新數據庫再刪除緩存

進行更新操作時,先更新mysql,成功之后,刪除緩存,后續讀取請求時再將新數據回寫緩存。

存在的問題:更新MySQL和刪除緩存這段時間內,請求讀取的還是緩存的舊數據,不過等數據庫更新完成,就會恢復一致,影響相對比較小。

3、異步更新緩存

數據庫的更新操作完成后不直接操作緩存,而是把這個操作命令封裝成消息扔到消息隊列中,然后由Redis自己去消費更新數據,消息隊列可以保證數據操作順序一致性,確保緩存系統的數據正常。

緩存穿透、緩存雪崩、緩存擊穿【詳解】Redis緩存擊穿、穿透、雪崩概念及解決方案

緩存穿透

緩存穿透是指查詢一個不存在的數據,由于緩存是不命中時被動寫的,如果從DB查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到DB去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了。

  • 緩存空值,不會查數據庫。

  • 采用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,查詢不存在的數據會被這個bitmap攔截掉,從而避免了對DB的查詢壓力。

布隆過濾器的原理:當一個元素被加入集合時,通過K個散列函數將這個元素映射成一個位數組中的K個點,把它們置為1。查詢時,將元素通過散列函數映射之后會得到k個點,如果這些點有任何一個0,則被檢元素一定不在,直接返回;如果都是1,則查詢元素很可能存在,就會去查詢Redis和數據庫。

緩存雪崩

緩存雪崩是指在我們設置緩存時采用了相同的過期時間,導致緩存在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重掛掉。

解決方法:在原有的失效時間基礎上增加一個隨機值,使得過期時間分散一些。

緩存擊穿

緩存擊穿:大量的請求同時查詢一個 key 時,此時這個 key 正好失效了,就會導致大量的請求都落到數據庫。緩存擊穿是查詢緩存中失效的 key,而緩存穿透是查詢不存在的 key。

解決方法:加分布式鎖,第一個請求的線程可以拿到鎖,拿到鎖的線程查詢到了數據之后設置緩存,其他的線程獲取鎖失敗會等待50ms然后重新到緩存取數據,這樣便可以避免大量的請求落到數據庫。

public?String?get(String?key)?{ ????String?value?=?redis.get(key); ????if?(value?==?null)?{? ????????//緩存值過期 ????????String?unique_key?=?systemId?+?":"?+?key; ????????//設置30s的超時 ????????if?(redis.set(unique_key,?1,?'NX',?'PX',?30000)?==?1)?{??//設置成功 ????????????value?=?db.get(key); ????????????redis.set(key,?value,?expire_secs); ????????????redis.del(unique_key); ????????}?else?{?? ????????????//其他線程已經到數據庫取值并回寫到緩存了,可以重試獲取緩存值 ????????????sleep(50); ????????????get(key);??//重試 ????????} ????}?else?{ ????????return?value; ????} }

pipeline的作用?

redis客戶端執行一條命令分4個過程:發送命令、命令排隊、命令執行、返回結果。使用pipeline可以批量請求,批量返回結果,執行速度比逐條執行要快。

使用pipeline組裝的命令個數不能太多,不然數據量過大,增加客戶端的等待時間,還可能造成網絡阻塞,可以將大量命令的拆分多個小的pipeline命令完成。

原生批命令(mset和mget)與pipeline對比:

  • 原生批命令是原子性,pipeline是非原子性。pipeline命令中途異常退出,之前執行成功的命令不會回滾

  • 原生批命令只有一個命令,但pipeline支持多命令

lua腳本

Redis 通過 LUA 腳本創建具有原子性的命令:當lua腳本命令正在運行的時候,不會有其他腳本或 Redis 命令被執行,實現組合命令的原子操作。

在Redis中執行Lua腳本有兩種方法:eval和evalsha。eval命令使用內置的 Lua 解釋器,對 Lua 腳本進行求值。

//第一個參數是lua腳本,第二個參數是鍵名參數個數,剩下的是鍵名參數和附加參數 >?eval?"return?{KEYS[1],KEYS[2],ARGV[1],ARGV[2]}"?2?key1?key2?first?second 1)?"key1" 2)?"key2" 3)?"first" 4)?"second"

lua腳本作用

1、Lua腳本在Redis中是原子執行的,執行過程中間不會插入其他命令。

2、Lua腳本可以將多條命令一次性打包,有效地減少網絡開銷。

應用場景

舉例:限制接口訪問頻率。

在Redis維護一個接口訪問次數的鍵值對,key是接口名稱,value是訪問次數。每次訪問接口時,會執行以下操作:

  • 通過aop攔截接口的請求,對接口請求進行計數,每次進來一個請求,相應的接口訪問次數count加1,存入redis。
  • 如果是第一次請求,則會設置count=1,并設置過期時間。因為這里set()和expire()組合操作不是原子操作,所以引入lua腳本,實現原子操作,避免并發訪問問題。
  • 如果給定時間范圍內超過最大訪問次數,則會拋出異常。
private?String?buildLuaScript()?{ ????return?"local?c"?+ ????????"nc?=?redis.call('get',KEYS[1])"?+ ????????"nif?c?and?tonumber(c)?&gt;?tonumber(ARGV[1])?then"?+ ????????"nreturn?c;"?+ ????????"nend"?+ ????????"nc?=?redis.call('incr',KEYS[1])"?+ ????????"nif?tonumber(c)?==?1?then"?+ ????????"nredis.call('expire',KEYS[1],ARGV[2])"?+ ????????"nend"?+ ????????"nreturn?c;"; }  String?luaScript?=?buildLuaScript(); RedisScript<number>?redisScript?=?new?DefaultRedisScript(luaScript,?Number.class); Number?count?=?redisTemplate.execute(redisScript,?keys,?limit.count(),?limit.period());</number>

PS:這種接口限流的實現方式比較簡單,問題也比較多,一般不會使用,接口限流用的比較多的是令牌桶算法和漏桶算法。

更多編程相關知識,請訪問:Redis視頻教程!!

? 版權聲明
THE END
喜歡就支持一下吧
點贊7 分享