redis知識升級
學會了Redis的基本操作還不夠,再來看看升級部分
1. 資料刪除策略
惰性刪除+定期刪除(預設)
定期刪除:預設是每隔 100ms 就輪詢各個庫隨機抽取一些設定了過期時間的key,檢查其是否過期,如果過期就刪除。每隔100ms就遍歷所有的設定過期時間的 key 的話,是個損耗。
惰性刪除:定期刪除會導致很多過期 key 到了時間並沒有被刪除掉。除非系統去查詢才會刪除。如果靠定期刪除,和沒有走惰性刪除的話會導致一大部分過期資料沒有刪除,這時候就出現了記憶體淘汰機制
2. 記憶體淘汰機制
在資料進入記憶體的時候發現記憶體不夠了,就採用記憶體淘汰機制,不一定淘汰過期的
其配置有:
-
maxmemory:最大可用記憶體
-
maxmemory-samples:每次選取刪除資料的個數
-
maxmemory-policy:刪除策略
- volatile-lru:從已設定過期時間的資料集(server.db[i].expires)最近最久未使用
- volatile-lfu:從已設定過期時間的資料集(server.db[i].expires)最近最少使用
- volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)將要過期的資料淘汰
- volatile-random:從已設定過期時間的資料集(server.db[i].expires)隨機淘汰
- allkeys-lru:在全庫資料中(server.db[i].dict),最近最久未使用(這個是最常用的)
- allkeys-lfu:在全庫資料中(server.db[i].dict),最近最少使用
- allkeys-random:在全庫資料中(server.db[i].dict)隨機淘汰
- no-eviction:禁止驅逐資料,也就是說當記憶體不足以容納新寫入資料時,新寫入操作會報錯。這個應該沒人使用
3.限制登入次數功能
判斷使用者是否被限制登入
- 有:做相應的提示
- 沒有
- 登入成功:清除失敗錯誤次數
- 登入不成功(查詢key是否存在,即是否第一次 錯誤)
- 第一次錯誤:設次數為1,user:loginCount:fail:使用者名稱進行賦值,同時設定失效期
- 不是第一次
- (判斷是否4次,是的話這次加1等於5,限制1小時),user:loginCount:fail:使用者名稱+1
- 小於第四次,失敗次數加1
// 這裡筆者用了不規範的返回值,返回數值大於10表示被限制登入
public long login(String username, String password) {
// 先判斷是否被限制,
if (jedis.exists("user:loginCount:limit:" + username)) {
return jedis.ttl("user:loginCount:limit:" + username);
}
// 然後查詢資料庫返回結果,這裡模擬查詢設資料庫,密碼輸入錯誤
if (!username.equals(password)) {
long count = 0;
// 第一次錯誤,鍵不存在,設定過期時間,10秒內可以錯誤5次
if (!jedis.exists("user:loginCount:fail:" + username)) {
jedis.setex("user:loginCount:fail:" + username, 10, "1");
return 1;
} else {
count = jedis.incr("user:loginCount:fail:" + username);
if (count == 5) {
jedis.setex("user:loginCount:limit:" + username, 10, ""); // 設定登入限制時間
jedis.del("user:loginCount:fail:" + username); // 刪除登入次數,因為被登入限制
}
return count; // 返回已嘗試次數
}
}
// 密碼正確,清除錯誤次數
jedis.del("user:loginCount:fail:" + username);
return 0L; // 表示密碼正確
}
4.訊息訂閱
subscribe channel[channel] 訂閱頻道
psubscribe pattern[pattern] 訂閱匹配的頻道
publish channel message 將訊息傳送到指定頻道
unsubscribe [channel | channel] 退訂頻道
punsubscribe [pattern | pattern] 退訂匹配的頻道
應用場景:
- 構建實時訊息系統
- 普通的即時聊天,群聊
- 粉絲訂閱之後,釋出新文章的訊息推送,公眾號模式
5. 快取雪崩
Redis過期是惰性刪除+定期刪除,如果快取資料設定的過期時間相同,那麼當這些資料全部過期時,就會在這段時間全部請求走資料庫中。簡單就是Redis某段時間,或直接掛了,請求全走資料庫,那麼導致資料庫支援不住而宕機。
解決方法:
- 給過期時間加上一個隨機值(資料分類過期),減少大幅度同一時間過期問題
- 事前:可以用叢集或高可用來儘量避免
- 事發中:使用本地快取+限流(比如驗證碼)
- 事發後:redis的持久化,從硬碟上恢復資料
6. 快取穿透
大量查詢不存在的資料,導致每次返回空,Redis不起作用,相當於直接訪問資料庫。
解決方法:
- 請求引數的校驗,使之不能進入到redis,更不要說資料庫了
- 查詢不存在資料時,也將這個資料放入Redis,下次訪問可以從裡面獲取,當然要設定過期時間
- 布隆過濾器、限流演算法、令牌桶
7. 快取與資料庫的讀寫一致
讀:
- 如果查詢資料快取裡有,直接返回
- 快取裡沒有,去資料庫查詢,將查詢結果放入快取,並返回給客戶端
對於更新時:會導致快取資料和資料庫不一致,可以先修改資料庫,再修改快取。或者先修改快取,再修改資料庫,重點在於我們要是這兩個操作突顯原子性,這樣資料才不會出錯
操作快取:可以選擇更新和刪除,但一般採取刪除操作。因為刪除相對比更新更直接簡單,如果每次更新資料庫都要更新快取,如果頻繁更新的話,會頻繁修改一定程度損耗效能,不如直接刪除,再次讀取時快取沒有就到資料庫查詢
先更新資料庫再刪除快取:也有概率出錯但很低,比如快取失效,執行緒A查詢資料庫得到舊值,期間執行緒B將新值寫入資料庫,執行緒B刪除快取,然後執行緒A才將舊址寫入快取。刪除快取失敗策略是,不斷重試刪除,直到成功。
先刪除快取,再更新資料庫:如果原子性被破壞了,第一步成功刪除快取,第二步更新資料庫失敗,那麼資料庫資料是一致的,如果第一步刪除快取失敗了,可以直接返回錯誤,資料庫資料和快取還是一致。
但是:執行緒A刪除了快取,期間執行緒B查詢會走資料庫得到舊值,並把舊值寫入快取,然後執行緒A才將新值寫入資料庫,導致資料不一致,解決方法:將刪除快取,修改資料庫,讀取快取等操作擠壓到佇列裡,實現序列化。
二者對比:
前者:高併發下表現優異,原子性破壞時不好
後者:高併發下序列,原子性破壞時優異
8. 持久化
Redis是基於記憶體的,萬一遇到宕機那麼記憶體中的資料則會丟失,而持久化則是將記憶體中的資料儲存到硬碟防止丟失。Redis支援兩種方式的持久化方式:RDB、AOF
RDB
建立記憶體中資料的二進位制快照來實現持久化,可對快照備份或把快照複製到其他伺服器使之成為伺服器副本,還可以將快照留在原地以便重啟伺服器載入使用,預設持久化檔案為dump.rdb
save命令執行一次就儲存一次,若資料量過大,加入單執行緒任務執行會阻塞任務,所以不建議使用
bgsave命令後臺執行,fork子程序來進行持久化,成功後記錄到日誌中
自動執行持久化:需在redis.conf中配置,執行多少次非查詢操作就儲存
- save 900 1
- save 300 10
- save 60 10000
優點:
- 緊湊壓縮的二進位制檔案,儲存效率高
- 儲存的是某個時間點的快照,適合資料備份,全量複製
- 恢復資料速度比AOF快很多
- 應用:每隔一段時間執行bgsave備份,用於災難恢復
缺點:
- 不能實時持久化,間隔時間段的資料可能丟失
- fork子程序,記憶體額外消耗
- 資料量大時,持久化速度慢,全部資料持久化
AOF
將除查詢外的命令追加儲存到AOF檔案中,重啟時重新執行AOF檔案中的命令達到恢復資料的目的,是主流的持久化方式,預設沒有開啟,持久化檔案為appendonly.aof
持久化資料的三種策略(寫命令重新整理到aof命令緩衝區)
- always 每次
- everysec 每秒
- no 系統控制
配置檔案
- appendonly yes|no
- appendfsync always|everysec|no
AOF重寫機制
將Redis程序內的資料轉化為寫命令同步到新的AOF檔案的過程,即將對同一個資料的若干命令的執行結果合併成一條操作指令(忽略超時資料,忽略無效指令刪除等,合併重複指令),可降低檔案大小,提高持久化與恢復效率,其也有重寫緩衝區,下面是重寫命令:
- bgrewriteaof 手動重寫
- auto-aof-rewrite-min-size size 配置自動重寫(當aof快取了多少)
- auto-aof-rewrite-percentage percentage 配置自動重寫(%)
優點: - AOF持久化的實時性更好
- 持久化速度快,追加命令
缺點:
- 因為記錄命令,持久化檔案大
- 恢復資料慢,要執行命令
9. 事務
Redis 通過 MULTI、EXEC命令來實現事務(transaction)功能,其事務實質是將多個命令打包後一次性地按順序執行,期間不會執行其他客戶端的命令請求,簡單來說是命令序列化執行功能,沒有回滾功能。關係型資料庫用 ACID 檢驗事務功能的可靠性和安全性。而 Redis 中,事務總是具有原子性、一致性、隔離性,當持久化時,事務也具有永續性
MULTI:開啟事務,建立佇列,命令來了加入佇列
EXEC:執行事務,佇列中執行命令,完後銷燬佇列
DISCARD:取消事務,銷燬佇列
流程:
- 開始事務
- 命令入隊,命令不會立即執行
- 執行事務,按上面入隊順序執行
舉例轉賬:multi開始事務,exec執行事務
set account:a 100
set account:b 100
multi
get account:a
"QUEUED"
get account:b
"QUEUED"
decrby account:a 10
"QUEUED"
incrby account:b 10
"QUEUED"
exec
1) "100"
2) "100"
3) "90"
4) "110"
Redis的事務是沒有回滾功能的,在進行事務的時候,只有報錯的命令不會執行(例外:語法錯誤整個佇列都不會執行,型別錯誤會執行),其他命令都會執行。只是單純的執行事務的時候不會有其他命令加塞
場景:動物園給熊貓投喂竹子,這裡有很多個飼養員,只要其中一個投餵了,其他飼養員就不用再投喂,使用watch解決
WATCH:執行事務前,監視Key是否被修改,若有則取消事務,返回nil(針對同時修改用處大)
UNWATCH:取消監視
watch eat
// 中間可以執行其他命令,必須在開啟事務前watch
multi
set panda 1
exec
10. 主從複製
- 建立連線
方式1:
客戶端傳送 slaveof <masterip> <masterport>
auth <password>
方式2:
啟動式伺服器引數 redis-server -slavveof <masterip> <masterport>
方式3
slave配置檔案:slaveof <masterip> <masterport>
masterauth 123456
主從複製低版本不能複製高版本的資料,筆者在這裡花了挺久時間才找出問題所在
- 資料同步
- 命令傳播
- 心跳機制
進入命令傳播階段時,master和slave的資訊交換使用心跳機制維護,實現雙方連線保持線上
主從複製的作用
- master寫,slave讀,提高讀寫負載能力
- 負載均衡,基於主從結構,配合讀寫分離
- 故障恢復,當master故障時,由slave提供服務,實現快速恢復
- 資料冗餘,實現資料熱備份
- 高可用基礎
11. 哨兵模式Sentinel(主備切換)
哨兵是一個分散式系統,也是一臺redis伺服器,對於主從結構中的每臺伺服器進行監控,出現故障時投票機制選擇新的master並將所有slave連線到新的master,演示搭建三個哨兵和1主2從
sentinel.conf的配置檔案
monitor mymaster 127.0.0.1 6379 2 // 監聽主伺服器,自定義名字,後面2表示多少個哨兵認為宕機才有效
down-after-millisecoinds mymaster 30000 // 多久才認為宕機
parallel-syncs mymaster 1 // 命令傳播
failover-timeout mymaster 180000 // 複製超時時間
先啟動1主2從,再啟動哨兵
redis-sentinel sentinel-26379.conf
redis-sentinel sentinel-26380.conf
redis-sentinel sentinel-26381.conf
啟動哨兵後,每臺伺服器的配置都會有對應的修改
哨兵模式的流程:
- 1.監控階段
- 獲取各sentinel的狀態(是否線上)
- 獲取master的狀態
- master屬性
- runId
- role:master
- 各個slave的詳細資訊
- master屬性
- 獲取所有slave的狀態(根據master中的slave資訊)
- slave屬性
- runId
- role:slave
- master_host、master_port
- offset
- 2.通知階段
- 不停地用ping去測試
- 3.故障轉移
- 發現問題
- 競選負責人
- 優選新master
- 線上的
- 響應快的
- 與原master斷開時間最短的
- 優先原則:優先順序、offset、runId
- 新master上線,其他slave切換master,原master作為slave故障恢復後連線
12. 叢集
分散單臺伺服器的訪問壓力,即負載均衡
其底層儲存原理:
- 將key進行兩次演算法運算得key應該儲存的位置(CRC16(key) % 16384)
- 將所有Redis伺服器的總儲存空間計劃切割成16384份,每臺主機儲存一部分
- 加Redis伺服器的話,原本伺服器將槽分給新的伺服器、刪除伺服器則相反
- 叢集內部通訊:記錄各伺服器槽範圍,一次命中OK,否則伺服器查詢通訊錄讓請求去對應槽伺服器(最多2次命中)
- 內部通訊這樣就不用虛擬IP了
配置3主3從(官方自帶,每個伺服器都要配置)
cluster-enabled yes // 開啟叢集節點
cluster-config-file nodes-6379 // 叢集配置檔案
cluster-node-timeout 10000 // 宕機時間
src下有redis-trib.rb(需要Ruby、Gem支援)
./redis-trib.rb create --replicas 1 // 其中1表示1主拖1從
127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381
127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384
客戶端啟動
redis-cli -c
// 不然key和伺服器沒有對應上會報錯,讓你去連對應的伺服器。加了配置會幫你重定向
故障處理:
- 從伺服器下線,各個節點能收到通知,對應master節點會標記一下宕機從伺服器
- 主伺服器下線,對應從伺服器重試,失敗就執行上面的主從切換,切換的從頂替了主叢集。原主上線變成slave
13. 併發競爭Key問題
所謂 Redis 的併發競爭 Key 的問題也就是多個系統同時對一個 key 進行操作,但是最後執行的順序和我們期望的順序不同,這樣也就導致了結果的不同
推薦一種方案:分散式鎖(zookeeper 和 redis 都可以實現分散式鎖)。(如果不存在 Redis 的併發競爭 Key 問題,不要使用分散式鎖,這樣會影響效能)
參考: https://www.bilibili.com/video/BV1CJ411m7Gc?p=101