Redis
導航
- 一. Redis簡介
- 二. Redis常用的數據類型
- 三. 如何通過Redis實現分佈式鎖
- 四. Redis的持久化方式–RDB
- 五. 持久化方式-- AOF及混合模式
- 六. Pipeline及主從同步
- 七. Redis的集羣原理
一. Redis簡介
1.1 主流應用架構
解讀: 首先使用緩存可以快速查找所需要的內容,不必每次都請求數據庫;1-2則表示直接查詢緩存;如果緩存中沒有的話,就從數據庫中查詢到內容,然後返回存入Redis中再返回給客戶端,這樣下次請求此數據的時候就可以直接從緩存中查詢了–>3,4,5,6 ; 而熔斷7,8則表示直接對外提供一個服務,當存儲器掛掉了,那麼也可以直接從緩存中取值,然後直接返回結果;
1.2 緩存中間件–Memcache和Redis的區別?
-
Memcache: 代碼層次類似Hash
- 支持簡單數據類型
- 不支持數據持久化存儲
- 不支持主從
- 不支持分片
-
Redis:
- 數據類型豐富
- 支持數據磁盤持久化存儲
- 支持主從
1.3 爲什麼Redis能這麼快?
- 完全基於內存,絕大部分請求是純粹的內存操作,執行效率高;
- 數據結構簡單,對數據操作也簡單;
- 採用單線程,單線程也能處理高併發請求,想多核也可啓動多實例
- 使用多路I/O複用模型,非阻塞IO
- Redis採用I/O多路複用函數: epoll/kqueue/evport/select?
- 因地制宜
- 優先選擇時間複雜度爲O(1)的I/O多路複用函數作爲底層實現
- 以時間複雜度爲O(n)的select 作爲保底
- 基於react設計模式監聽I/O事件-
- Redis採用I/O多路複用函數: epoll/kqueue/evport/select?
二. Redis常用的數據類型
2.1 常用數據類型
- String : 最基本的數據類型,二進制安全
- Hash: String元素組成的字典,適合用於存儲對象
- List: 列表,按照String 元素插入順序排序
- Set:String元素組成的無序集合,通過哈希表實現,不允許重複
- Sorted Set: 通過分數來爲集合中的成員進行從小到大的排序
- 用於計數的HyperLogLog,用於支持存儲地理位置信息的Geo等;
存入相同的key,但value不同的時候,會被重寫,value值爲最新輸入的value值;
2.2 從海量Key裏查詢出某一固定前綴的Key
- 留意細節:
- 摸清數據規模,即問清楚邊界
- KEYS pattern: 查找所有給定模式pattern的Key
- KEYS指令一次性返回所有匹配的key
- 鍵的數量過大會使服務卡頓
- SCAN cursor [MATCH pattern] [COUNT count]
- 基於遊標的迭代器,需要基於上一次的遊標延續之前的迭代過程
- 以0作爲遊標開始一次新的迭代,直到命令返回遊標0完成一次遍歷
- 不保證每次執行都返回某個給定數量的元素,支持模糊查詢
- 一次返回的數量不可控,只能是大概率符合count參數
可能會獲取到重複數據,需要每次添加新的遊標
三. 如何通過Redis實現分佈式鎖
3.1 分佈式鎖需要解決的問題
- 互斥性
- 安全性
- 死鎖
- 容錯
3.2 分佈式鎖的嘗試:
可以使用setnx來實現分佈式鎖;當獲取到鎖的時候返回1,沒有獲取到則返回0,通過get還能確定是否爲這個鎖;
- 如何解決SETNX長期有效的問題:
- EXPIRE key seconds
- 設置key的生存時間,當key過期時(生存時間爲0),會被自動刪除
- 缺點:
- 原子性得不到滿足
- EXPIRE key seconds
RedisService redisService= SpringUtils.getBean(RedisService.class);
long status=redisService.setnx(key,"1");
if(status==1){
redisService.expire(key,expire);
//執行獨佔資源邏輯
doOcuppiedWork();
}
使用過期時間,可以當一個線程獲取到了鎖之後,給它設置一個過期時間,在這個時間內執行方法;過期後就相當於釋放鎖;
存在的風險: 在設置過期時間前被掛掉的話,這個線程可能就會一直佔用此線程
3.3 分佈式鎖的優化:
-
SET key value [EX seconds] [PX milliseconds] [NX|XX]
- EX second: 設置鍵 的過期時間爲second 秒
- PX millisecond: 設置鍵的過期時間爲millisecond 毫秒
- NX: 只在鍵不存在時,纔對鍵進行設置操作
- XX: 只在鍵已經存在時,纔對鍵進行設置操作
- SET 操作成功完成時,返回OK,否則返回nil
-
操作代碼如下:
RedisService redisService =SpringUtils.getBean(RedisService.class);
String result=redisService.set(lockKey,requestId,SET_IF_NOT_EXIST,SET_WITH_EXPIRE_TIME,expireTime);
if("OK",equals(result)){
//執行獨佔資源邏輯
doOcuppiedWork()
}
3.4 大量的Key同時過期的注意事項
- 集中過期,由於清除大量的Key很耗時,會出現短暫的卡頓現象
- 解決方案: 在設置key的過期時間的時候,給每個Key加上隨機值;
3.5 如何使用Reids做異步隊列
-
使用List作爲隊列,PRUSH生產消息,LPOP消費消息
- 缺點:沒有等待隊列裏有值就直接消費
- 彌補:可以通過在應用層引入Sleep機制去調用LPOP重試
-
BLPOP key [key…] timeout: 阻塞直到隊列有消息或者超時
- 缺點:只能供一個消費者消費
-
pub/sub: 主題訂閱模式:
-
發送者[pub]發送消息,訂閱者(sub)接收消息
-
訂閱者可以訂閱任意數量的頻道
-
圖示:
-
Redis Client1:
subscribe myTopic
Redis Client2:
subscribe myTopic
Redis Client3:
subscribe anotherTopic
Redis Client4:
publish myTopic "Hello"
//publish這個返回值爲訂閱者數量,在這裏面clent4推送的消息只有1,2能收到,3因爲訂閱的內容不同,所以無法收到
消息的發佈是無狀態的,無法保證可達
四. Redis的持久化方式–RDB
4.1 RDB持久化
-
RDB(快照)持久化: 保存某個時間點的全量數據快照
- SAVE:阻塞Redis的服務器進程,直到RDB文件被創建完畢
- BGSAVE: Fork出一個子進程來創建RDB文件,不阻塞服務器進程
-
手動觸發操作:
ls dump.rbd # 顯示dump.rdb
rm -f dump.rdb # 刪除所有dump.rdb
./redis-cli #連接Redis客戶端
save #保存RDB持久化 會卡頓一段時間
exit #退出
ls dump.rbd ...
rm-f dump.rdb ...
ls dump.rbd # 已經被刪除了,會提示:No such file or directory
./redis-cli ...
lastsave # 快照最後一次保存的時間
bgsave # 保存RDB持久化[單獨開一個線程] 提示: Background saving started 不會卡頓
lastsave # 最後一次的執行時間,這裏會被更新;因爲使用了bgsave
exit
- 自動化觸發RDB持久化的方式
- 根據redis.conf 配置裏的SAVE m n 定時觸發(用的是BGSAVE)
- 主從複製時,主節點自動觸發
- 執行Debug Reload
- 執行Shutdown 且沒有開啓AOF持久化
4.2 BGSAVE原理
- 示意圖:
系統調用fork(): 創建進程,實現了Copy-on-Write
4.3 Copy-on-Write
- 概述:
- 如果有多個調用者同時要求相同資源(如內存或磁盤上的數據存儲),他們會共同獲取相同的指針指向相同的資源,直到某個調用者試圖修改資源的內容時,系統纔會真正賦值一份專用副本給該調用者,而其他調用者所見到的最初的資源仍然保持不變;
4.4 RDB 持久化的缺點:
- 內存數據的全量同步,數據量大會由於I/O而嚴重影響性能
- 可能會因爲Redis掛掉而丟失從當前至最近一次快照期間的數據
五. 持久化方式-- AOF及混合模式
5.1 概述:
- AOF(Append-Only-File)持久化: 保存寫狀態
- 記錄下除了查詢以外的所有變更數據庫狀態的指令
- 以append的形式追加保存到AOF文件中(增量)
appendonly.aof[默認名字] 它是默認關閉的
vim redis.conf -> 將appendonly no 改爲appendonly yes
ls appendonly.aof # 判斷是否已經存在,如果不存在則繼續,假設不存在
config set appendonly yes
set aofTest "hehe"
exit
ls appendonly.aof # 這時已經能正常顯示了;
5.2 日誌重寫解決AOF文件大小不斷增大的問題,原理如下;
- 調用fork(),創建一個子進程
- 子進程把新的AOF寫到一個臨時文件裏,不依賴原來的AOF文件
- 主進程持續將新的變動同時寫到內存和原來的AOF裏
- 主進程獲取子進程重寫AOF的完成信號,往新AOF同步增量變動
- 使用新的AOF文件替換掉舊的AOF文件
5.3 RDB和AOF文件共存情況下的恢復流程:
5.4 RDB和AOF的優缺點
-
RDB
- 優點:全量數據快照,文件小,恢復快
- 缺點: 無法保存最近一次快照之後的數據
-
AOF:
- 優點:可讀性高,適合保存增量數據,數據不易丟失
- 缺點: 文件體積大,恢復時間長
5.5 RDB-AOF混合持久化方式:
- 概述:
- 細細想來aofrewrite時也是先寫一份全量數據到新AOF文件中再追加增量只不過全量數據是以redis命令的格式寫入。那麼是否可以先以RDB格式寫入全量數據再追加增量日誌呢這樣既可以提高aofrewrite和恢復速度也可以減少文件大小還可以保證數據的完畢性整合RDB和AOF的優點那麼現在4.0實現了這一特性——RDB-AOF混合持久化。
- 方式:BGSAVE做鏡像全量持久化,AOF做增量持久化
- 操作: 通過aof-use-preamble配置項可以打開混合開關
六. Pipeline及主從同步
6.1 使用Pipeline的好處:
- Pipeline和Linux的管道類似
- Redis基於請求/響應模型,單個請求處理需要一一應答
- Pipeline批量執行指令,節省多次IO往返的時間
- 有順序依賴的指令建議分批發送
6.2 Redis全同步過程:
- Salve發送sync命令到Master
- Master啓動一個後臺進程,將Redis中的數據快照保存到文件中
- Master 將保存數據快照期間接收到的寫命令緩存起來
- Master完成寫文件操作後,將該文件發送給Salve
- 使用新的AOF文件替換掉舊的AOF文件
- Master將這期間收集的增量寫命令發送給Salve端;
6.3 增量同步過程
- Master 接收到用戶的操作指令,判斷是否需要傳播到Slave
- 將操作記錄追加到AOF文件
- 將操作傳播到其他Slave: 1. 對其主從庫 2. 往響應緩存寫入指令 3. 將緩存中的數據發送給Slave
6.4 解決主從同步Master宕機後的主從切換問題:
- 監控: 檢查主從服務器是否運行正常
- 提醒: 通過API向管理員或者其他應用程序發送故障通知
- 自動故障遷移: 主從切換
6.5流言協議:Gossip
- 在雜亂無章中尋求一致:
- 每個節點都隨機地與對方通信,最終所有節點的狀態達成一致
- 種子節點定期隨機向其他節點發送節點列表以及需要傳播的消息
- 不保證信息一定會傳遞給所有節點,但是最終會趨於一致
七. Redis的集羣原理
7.1 如何從海量數據裏快速找到所需?
- 分片:按照某種規則去劃分數據,分散存儲在多個節點上
- 常規的按照哈希劃分無法實現節點的動態增減
7.2 一致性哈希算法:
- 概述: 對2^32取模,將哈希值空間組織成虛擬的圓環
- 方式: 將數據key使用相同的函數Hash計算出哈希值:
好處: 一個節點宕機後,其他的Node節點不會受到影響
7.3 Hash環的數據傾斜問題
- 概述: 當節點數比較少的時候,比如只有2個的時候,當數據計算出來都存入了節點A,只有較小部分的數據存入了節點B,則會造成數據傾斜,數據沒有平均的分配到各節點上,若A節點宕機則也會造成較大的影響;
- 解決: 引入虛擬節點解決數據傾斜的問題:
引入了虛擬節點,將本來只有A、B兩個節點變成了A#1,A#2,A#3,B#1,B#2,B#3等六個節點,解決了節點少而造成數據分佈不均勻的問題;