學會了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
1. RDB
創建內存中數據的二進制快照來實現持久化,可對快照備份或把快照復制到其他服務器使之成為服務器副本,還可以將快照留在原地以便重啟服務器加載使用,默認持久化文件為dump.rdb
save命令執行一次就保存一次,若數據量過大,加入單線程任務執行會阻塞任務,所以不建議使用
bgsave命令后台運行,fork子進程來進行持久化,成功后記錄到日志中
自動執行持久化:需在redis.conf中配置,執行多少次非查詢操作就保存
- save 900 1
- save 300 10
- save 60 10000
優點:
- 緊湊壓縮的二進制文件,存儲效率高
- 存儲的是某個時間點的快照,適合數據備份,全量復制
- 恢復數據速度比AOF快很多
- 應用:每隔一段時間執行bgsave備份,用於災難恢復
缺點:
- 不能實時持久化,間隔時間段的數據可能丟失
- fork子進程,內存額外消耗
- 數據量大時,持久化速度慢,全部數據持久化
2. 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. 主從復制
repl-backlog-size 設置指令緩沖區
slave-server-stale-data yes|no slave關閉寫功能
- 建立連接

方式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
- slave屬性
- 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
