REDIS面試問題總結

豎橫山發表於2021-07-16

系統命令

shutdown 正常關閉伺服器
redis-server 啟動伺服器
redis-cli 客戶端連線伺服器
flushall 刪庫跑路,一般不這麼做

REDIS 持久化 RDB AOF區別

RDB:[Redis Database] 在指定時間間隔把記憶體中的資料快照寫入磁碟,之後可以備份快照,或者複製到其他伺服器建立相同副本,或者伺服器重啟也會用到這個快照恢復資料,預設持久化方式

觸發時機
手動執行save和bgsave時
配置檔案 設定  save <seconds> <changes>,自動間隔執行
主從複製時
執行flushall時
執行shutdown時

save 
阻塞redis伺服器程式,完成之前伺服器都是炸的
bgsave
主程式會fork一個子程式出來建立RDB,先把資料寫入臨時檔案,再用二進位制壓縮並替換之前的RDB檔案,全程主程式不受影響繼續工作

##redis.conf配置檔案  save <seconds> <changes>
`save 900 1` 當時間到900秒時,如果至少有1個key發生變化,就會自動觸發`bgsave`命令建立快照
`save 300 10` 當時間到300秒時,如果至少有10個key發生變化,就會自動觸發`bgsave`命令建立快照  
`save 60 10000` 當時間到60秒時,如果至少有10000個key發生變化,就會自動觸發`bgsave`命令建立快照
----------------
AOF:[Append Only File] 預設沒有開啟,開啟後每執行一條寫命令就會記錄到aof_buf快取,隨後寫入AOF檔案末尾,且根據配置定時重寫壓縮檔案,多條合併成一條

##redis.conf配置檔案
appendonly yes    //是否開啟
appendfilename "appendonly.aof"   //日誌名稱
appendfsync always/everysec/no  每次/每秒/作業系統自行決定,預設是everysec
no-appendfsync-on-rewrite no //重寫期間是否同步
auto-aof-rewrite-percentage 100 //日誌檔案如果增長100%觸發重寫
auto-aof-rewrite-min-size 64mb//日誌檔案大於64mb時,才會觸發重寫,權重比上面大

AOF重寫
手動觸發bgrewriteaof 和 bgsave 同一個原理,這裡按下不表
重寫aof檔案的操作,並沒有讀取舊的aof檔案,而是將整個記憶體中的資料庫內容用命令的方式重寫了一個新的aof檔案,RDB相似,RDB存的是二進位制壓縮資料,AOF存的是日誌

混合持久化
aof-use-rdb-preamble yes //開啟,預設關閉
bgrewriteaof時fork出的子程式先將資料以RDB方式寫入aof檔案,然後在將重寫緩衝區的增量命令以AOF方式寫入到檔案,寫入完成後通知主程式更新統計資訊,並將新的含有RDB格式和AOF格式的AOF檔案替換舊的的AOF檔案

RESP 協議 [REdis Serialization Protocol]

redis客戶端 和 redis-server 之間的通訊協議,這個瞭解一下就好

"+OK\\r\\n"//simple string
"-ERR unknown command 'foobar'"//error msg
":1000\\r\\n"//integer
"$12\\r\\nHello World!\\r\\n"//bulk string
"$0\\r\\n\\r\\n"  //空字串
"$-1\\r\\n"    //nil
"*2\\r\\n$5\\r\\nhello\\r\\n$5\\r\\nworld\\r\\n"//陣列

架構

單機模式
優點:部署簡單,成本低,高效能,不需要同步資料
缺點:有當機風險,單執行緒受限於CPU處理能力,單機承載QPS大概在幾萬左右,如果QPS達到10+,單機模式會直接掛掉
主從複製
slaveof 192.168.1.1 6379 //Version<5.0
replicaof 192.168.249.20 6379 //Version>=5.0
成功配置一個從伺服器, 從伺服器向主伺服器傳送一個SYNC命令,主伺服器BGSAVE,完成後傳送RDB檔案到從伺服器,從伺服器載入RDB檔案啟動,第一次連線是全量重同步
之後主伺服器每執行一次寫命令都會順便發一份給從伺服器,如果主從斷開,重連的時候會根據offset複製偏移量部分重同步,比全量重同步塊一些
優點:增加從伺服器,QPS增加,降低MASTER讀壓力
缺點:主伺服器寫壓力沒有解決,當機無法繼續寫入,從節點晉升主節點需要改程式碼的配置,和從伺服器的配置,及其麻煩
哨兵模式 Sentinel
相當於主從模式下,給每臺(主從)伺服器新增一個程式,根據配置定時每秒,每十秒去判斷伺服器是否當機,如果是主伺服器當機,則當有足夠數量的哨兵程式確定主伺服器當機之後,將投票從從伺服器中選舉出新的主伺服器,程式碼方面需要執行SENTINEL相關命令獲取當前的主伺服器是哪臺,再例項化操作
優點:主伺服器當機的時候,不用半夜跑起來一頓操作,全自動化
缺點:已然沒有解決主庫寫壓力,QPS大的時候,該當機還得當機
叢集模式  REDIS CLUSTER  version>3.0
採用無中心結構,至少6個節點(11從 乘以3)才能保證高可用叢集,3個主伺服器採用虛擬雜湊槽分割槽,公式為
hash_slot = crc16(key) % 16384 ;
雜湊槽的區間為[0,16383],擴容和縮容都是對槽的重新分配,服務不需要下線
某一組主節點伺服器當機,他的從節點會升級成主節點,宕掉的主節點重啟之後會變成新主節點的從節點
也就是說每個主節點只儲存有部分資料,不是全部,不過如果某一組主節點和他的從節點都當機的話,整個叢集會掛掉
優點:有條件的話肯定用這個方案啦
缺點:搭建發雜,用docker搭建會好點

一致性雜湊演算法

傳統雜湊取模演算法 N表示伺服器的總數
hash(key) % N  每個鍵都根據雜湊演算法分佈到N臺伺服器中,但是當N變化時(當機/新增)會導致鍵和伺服器的對映關係發生變化導致快取失效,新的鍵值不影響,主要是舊的鍵值,hash(key1)%3=1突然變成hash(key1)%2=2,到第2臺伺服器發現沒有key1,如果同一時刻發生大量快取失效,會導致快取雪崩
一致性雜湊演算法
通過一個雜湊環資料結構實現,環的取值範圍[0-2^32-1]
將伺服器的主機名或者IP進行雜湊運算對映到雜湊環上
hash(server.name/server.ip) 
將鍵值通過雜湊運算也對映到雜湊環上,在雜湊環上順時針尋找最近雜湊值的伺服器,把值存進去
伺服器增加的時候同樣雜湊運算對映到雜湊環,只會影響當前雜湊值逆時針上一個伺服器到當前雜湊值之間的區間物件需要重新分配伺服器,其他伺服器沒影響
伺服器減少的時候,也只是需要把當前減少的伺服器雜湊值到逆時針上一個伺服器雜湊值之間的區間物件重新分配,其他伺服器沒影響
虛擬節點
可以把一臺伺服器虛擬成多個節點,使資料分佈更加分散,但是節點越多,新增和減少伺服器時重新分配的物件就越多,可以權衡一下

REDIS面試問題總結

分散式鎖

單機分散式鎖
set lock_resource_id 1 nx ex 10 //原子操作,搶鎖10秒自動釋放
正常情況:A獲得鎖,A執行邏輯,成功之後,A10秒內釋放鎖
異常情況:A獲得鎖,A執行邏輯,超過10,redis主動釋放鎖
B獲得鎖,A完成業務,然後把B的鎖給釋放了,
拉都拉不住,此時B的心態逐漸發生了一些變化
解決方案:
避免在可能會超時的場景使用鎖,加鎖時可以把value設定成一個自己知道的數字,釋放的時候判斷一下是否仍是自己的鎖再刪除,判斷和刪除是兩個原子性操作,需要用到LUA指令碼才能實現,這並不是一個完美解決方案,這裡沒有解決A超時鎖被提前釋放,B乘虛而入的問題,不過這也是一個無解的問題
set lock_resource_id thread_id nx ex 10
##LUA指令碼##
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end    
##LUA指令碼##
Redisson分散式鎖
開啟一個定時的守護執行緒/程式,判斷是否要給當前鎖新增過期時間,也就是再給你10秒鐘
Redlock
單節點不需要用到redlock
多節點,主從複製的時候,會發生A從主節點獲取鎖,沒同步到從節點發生故障轉移,從節點被選舉成主節點,B獲得相同的鎖,就變成兩個客戶端都獲取到同一把鎖的狀態
redlock原理:客戶端向所有主節點獲取鎖,當且僅當從一半以上的節點取到鎖,且使用的時間小於鎖失效的時間,才算獲得成功,釋放的時候向全部節點釋放
php有現成的包(暴露身份了),使用簡單
https://github.com/ronnylt/redlock-php
這個演算法不是100可靠的,詳情可以去研究
https://redis.io/topics/distlock

非同步佇列

List 常用命令
lpush,lpop,blpop(blocking 堵塞)
rpush,rpop,brpop(blocking 堵塞)
rpoplpush,brpoplpush(blocking 堵塞)
llen,lrange

可以用(b)rpoplpush把佇列的值pop出來push到另一個佇列裡面,業務處理完再刪除,有問題就回滾,實現訊息確認機制,原子性操作
rpoplpush myqueue queuebak

缺點:不能生產一次消費多次,其實不算缺點,設計如此

PUB/SUB

釋出/訂閱 可以實現生產一次消費多次,但是不能實現訊息持久化,客戶端下線之後,就會丟失資訊,設計如此

stream Version>=5.0

就目前來說,stream還不能當做主流MQ使用,慎用在生產環境
stream實現了消費組消費和應答機制,簡單使用如下

xadd mystream * k1 v1 k2 v2 k3 v3//新增元素
xadd mystream 1609404470049-1 k4 v4//新增元素
xrange mystream - + //檢視全部元素
xdel mystream 1609404470049-1//刪除元素
xdel mystream 刪除key
xlen mystream//容量
##獨立消費
xread count 2 streams mystream 0//從頭開始讀2條
xread count 2 streams mystream $//從尾開始讀最新
xread block 0 streams mysteam $//永久堵塞讀最新
xread streams mystream1 mystream2 $ $//同時讀兩個
##消費組消費 每個組狀態獨立,互不影響,同一個組內多個消費者是競爭關係,每個消費組都有一個遊標last_delivered_id在陣列上往前移動,表示消費組消費到哪條資訊,消費未應答的id儲存在pending_ids裡面.
xgroup create mystream mygroup $//建立組讀最新資料
xinfo stream mystream//檢視stream和消費者組的資訊
xreadgroup group mygroup c1 count1 streams mystream //消費組mygroup的消費者c1消費1條資料

延時佇列

通過zset的score來實現
zadd key 100 member1
zadd key 110 member2
zadd key 120 member3
zrangebyscore key  100 100 //member1
zrem key member1

布隆過濾器

原理:使用多個不同的雜湊函式把元素雜湊到一個BIT向量表中,二向箔打擊,非常節省空間和成本,查詢也快,缺點就是判斷元素是否已經存在會有誤差,很小,但是無法完全消除,不過判斷元素是否不存在準確率是100%,另外不支援刪除操作,REDIS並不自帶布隆過濾器的實現,需要應用端實現多個雜湊函式和利用REIDSSETBIT,GETBIT命令實現
應用:
1.大量的郵件,URL去重,過濾,識別
2.記錄使用者已經讀過的文章
3.解決快取穿透的問題

快取穿透

一般的業務邏輯是先去REDIS查詢有沒快取KEY,如果沒有就去資料庫查,但是一些惡意請求會故意查詢不存在的key,導致每個請求直達資料庫
解決方案:
1.查詢結果為空的情況也快取進REDIS,過期時間設定稍微短些
2.將所有可能存在的KEY雜湊到一個足夠大的bitmap中,對一定不存在的key進行過濾,用到上面說的布隆過濾器

快取雪崩

REDIS重啟或者大量快取同一個時間點失效,這樣請求又直達資料庫,引起連鎖反應
解決方案:
1.不同的KEY,設定不同的過期時間,可以帶個隨機數
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章