溪源的Java筆記—Redis伺服器
溪源的Java筆記—Redis伺服器
前言
前段時間我對Mysql
資料庫的知識點進行了梳理,本篇部落格我對Redis
伺服器的相關的知識點進行整理,Redis
可以是我們在Web
應用中提升效能的利器,可以說Redis
是一箇中高階開發者必備的技能點。
Mysql的知識點可參考我的部落格
正文
Redis
Redis常見應用場景:
- 實現快取系統和記憶體資料庫:會話快取和全頁快取
- 使用
redis
來搭建訊息佇列 - 排行榜/計數器,
redis
在記憶體中對數字進行遞增或者遞減的操作實現比較好。 - 釋出/訂閱,通過
redis
來實現朋友圈功能
Redis的優勢:
- 絕大數的請求操作都是純粹的記憶體操作。
- 採用了單線模式,避免了不必要的上下文切換和競爭條件,這裡的單執行緒指的是網路請求模組只使用了一個執行緒(所以不必考慮併發安全性),即一個請求處理所有網路請求,其他模組仍使用了多個執行緒。
- 採用了非阻塞I/O多路複用機制,
Redis
管道技術使得請求不會發生阻塞。 - 採用了動態字串(
SDS
),對於字串會預留一定的空間,避免了字串在做拼接和擷取引起記憶體重新分配導致效能的損耗。
快取失效策略
三種主要演算法:
- FIFO:
First In First Out
,先進先出。判斷被儲存的時間,離目前最遠的資料優先被淘汰。 - LRU:
Least Recently Used
,最近最少使用。判斷最近被使用的時間,目前最遠的資料優先被淘汰。(時間遠近) - LFU:
Least Frequently Used
,最不經常使用。在一段時間內,資料被使用次數最少的,優先被淘汰。(次數多少)
Redis提供6種資料淘汰策略:
- volatile-lru:從已設定過期時間的資料集中挑選最近最少使用的資料淘汰
- volatile-ttl:從已設定過期時間的資料集中挑選將要過期的資料淘汰
- volatile-random:從已設定過期時間的資料集中任意選擇資料淘汰
- allkeys-lru:從資料集中挑選最近最少使用的資料淘汰
- allkeys-random:從資料集中任意選擇資料淘汰
- no-enviction(驅逐):禁止驅逐資料 預設
三種過期策略:
- 定時刪除:在設定
key
的過期時間的同時,為該key
建立一個定時器,讓定時器在key
的過期時間來臨時,對key
進行刪除 - 惰性刪除:
key
過期的時候不刪除,每次從資料庫獲取key
的時候去檢查是否過期,若過期,則刪除,返回null
- 定期刪除: 每隔一段時間執行一次刪除過期
key
操作
redis
一般使用定時刪除、定期刪除,mercached
只使用惰性刪除。
Redis持久化方式
方式一:快照
RDB
(預設) 持久化可以在指定的時間間隔內生成資料集的時間點快照。
以下設定會讓 Redis
在滿足“ 60 秒內有至少有 1000 個鍵被改動”這一條件時, 自動儲存一次資料集:
RDB
的優劣勢:
- 弊端:在儲存時間的間斷中如果不滿足儲存條件,突然發生斷電或者系統崩潰會導致資料丟失。
- 優勢:資料恢復十分快。
方式二:同步到資料檔案
AOF
持久化錄伺服器執行的所有寫操作命令,並在伺服器啟動時,通過重新執行這些命令來還原資料集。(經所有的指令儲存到文字檔案中)
AOF
的優劣勢:
- 弊端:每一條指令都記錄到文字檔案中會極大地拖垮
redis
的效能,資料恢復速度慢 - 優勢:執行的週期比
rdp
短,能防止間隔異常導致資料丟失
方式三:使用虛擬記憶體的方式
宕機如何處理
- 建立一個定期任務, 每小時將一個
RDB
檔案備份到一個資料夾, 並且每天將一個RDB
檔案備份到另一個資料夾。 - 確保快照的備份都帶有相應的日期和時間資訊, 每次執行定期任務指令碼時, 使用
find
命令來刪除過期的快照: 比如說, 你可以保留最近 48 小時內的每小時快照, 還可以保留最近一兩個月的每日快照。 - 至少每天一次, 將
RDB
備份到你的資料中心之外, 或者至少是備份到你執行Redis
伺服器的物理機器之外。
Redis 事務
Redis事務是一組命令的集合。
Redis
事務可以一次執行多個命令, 並且帶有以下的保證:
- 批量操作在傳送
EXEC
命令前被放入佇列快取。 - 收到
EXEC
命令後進入事務執行,事務中任意命令執行失敗,其餘的命令依然被執行(不具有原子性)。 - 在事務執行過程,其他客戶端提交的命令請求不會插入到事務執行命令序列中。
一個事務從開始到執行會經歷以下三個階段:
- 開始事務:事務開始的時候先向
Redis
伺服器傳送MULTI
命令 - 命令入隊:然後依次傳送需要在本次事務中處理的命令
- 執行事務:最後再發送
EXEC
命令表示事務命令結束
命令並不會立刻馬上執行,只有在執行EXEC
命令才會逐一執行命令。
事務相關的命令:
- MULTI:開啟事務
- EXEC:提交事務
- DISCARD:放棄事務
- WATCH:監控
- QUEUED:將命令加入執行的佇列
Redis事務與Mysql事務的區別
Redis分散式鎖
Redis分散式鎖的三種行為:
- 加鎖:使用
setnx
來搶奪鎖,將鎖的識別符號設定為1,表示鎖已被佔用。 - 解鎖:使用
setnx
來釋放鎖,將鎖的識別符號設定為0,表示鎖已被釋放。 - 鎖過期:用
expire
給鎖加一個過期時間防止鎖忘記了釋放,expire
時間過期將返回0。
Setnx
和expire
都是原子操作,實際應用中使用lua
指令碼來確保操作的原子性。
Redis 釋出訂閱
Redis
釋出訂閱(pub
/sub
)是一種訊息通訊模式:
- 傳送者(
pub
)傳送訊息 - 訂閱者(
sub
)接收訊息
Redis
客戶端可以訂閱任意數量的頻道。
下圖展示了頻道 channel1
, 以及訂閱這個頻道的三個客戶端 —— client2
、 client5
和 client1
之間的關係:
當有新訊息通過 PUBLISH
命令傳送給頻道 channel1
時, 這個訊息就會被髮送給訂閱它的三個客戶端:
Redis資料庫支援的資料型別
- String:字串、整數或者浮點數
- Hash:包含鍵值對的無序散列表
- List:連結串列,每個節點都是一個字串
- Set:無序收集器
- Zset:有序集合
String
Redis
的String
採用的是動態String
:
- 不會出現字串變更造成的記憶體溢位問題
- 獲取字串長度時間複雜度為1
- 空間預分配, 惰性空間釋放
free
欄位,會預設留夠一定的空間防止多次重分配記憶體
應用場景: String
快取結構體使用者資訊,計數
Hash
陣列+連結串列的基礎上,進行了一些rehash
優化;
Reids
的Hash
採用鏈地址法來處理衝突,然後它沒有使用紅黑樹優化。- 雜湊表節點採用單鏈表結構。
rehash
優化 (採用分而治之的思想,將龐大的遷移工作量劃分到每一次CURD
中,避免了服務繁忙)
rehash
指的是當hash
表中的負載因子達到負載極限的時候,hash
表會自動成倍的增加容量(桶的數量),並將原有的物件重新的分配並加入新的桶內。
應用場景:
- 儲存結構體資訊可部分獲取不用序列化所有欄位
List
應用場景:
- 比如
twitter
的關注列表,粉絲列表等都可以用Redis
的list
結構來實現 List
的實現為一個雙向連結串列,即可以支援反向查詢和遍歷
Set
內部實現是一個value
為null
的HashMap
,實際就是通過計算hash
的方式來快速排重的,這也是set
能提供判斷一個成員是否在集合內的原因。
應用場景:
- 去重的場景,交集(
sinter
)、並集(sunion
)、差集(sdiff
) - 實現如共同關注、共同喜好、二度好友等功能
Zset
內部使用HashMap
和跳躍表(SkipList
)來保證資料的儲存和有序:
HashMap
裡放的是成員到score
的對映,score
是排序的依據。- 跳躍表裡存放的是所有的成員,它的每個節點中維持多個指向其他節點的指標,從而達到快速訪問節點的目的
應用場景: - 實現延時佇列
Redis叢集化
主從備份
在Redis
中,使用者可以通過執行SLAVEOF
命令或者設定slaveof
選項,讓一個伺服器去複製(replicate
)另一個伺服器,我們稱呼被複制的伺服器為主伺服器(master
),而對主伺服器進行復制的伺服器則被稱為從伺服器(slave
)。
格式:從伺服器 saveof
主伺服器
Redis同步機制
Redis
可以使用主從同步,從從同步:
- RDB映象同步:主節點做一次
bgsave
,並同時將後續修改操作記錄到記憶體buffer
,待完成後將RDB
檔案全量同步到複製節點,複製節點接受完成後將RDB
映象載入到記憶體。 - AOP檔案同步:再通知主節點將期間修改的操作記錄同步到複製節點進行重放就完成了同步過程。
bgsave
bgsave
命令用於在後臺非同步儲存當前資料庫的資料到磁碟。
save
和 bgsave
兩個命令都會呼叫 rdbSave
函式,但它們呼叫的方式各有不同:
save
直接呼叫rdbSave
,阻塞Redis
主程序,直到儲存完成為止。在主程序阻塞期間,伺服器不能處理客戶端的任何請求。bgsave
則fork
出一個子程序,子程序負責呼叫rdbSave
,並在儲存完成之後向主程序傳送訊號,通知儲存已完成。Redis
伺服器在bgsave
執行期間仍然可以繼續處理客戶端的請求。
Redis叢集化的底層原理
- 分片:自動將資料進行分片,每個
master
上放一部分資料。 - 一致性hash演算法:提供16384槽點,藉助一致性
hash
演算法來決定資料分片放在那個槽點中。
Redis叢集化的三個階段:
- 主從複製: 實現了讀寫分離。
- 哨兵模式:主從可以自動切換,系統更加健壯,可用性更高。
- Redis-cluster:實現
redis
的分散式儲存,實現資料的去中心化。
Redis叢集化的方案:
- 官方
Redis-cluster
方案 - twemproxy:代理方案
twemproxy
是一個單點,很容易對其造成很大的壓力,所以通常會結合keepalived
來實twemproy
的高可用 codis
基於客戶端來進行分片
Redis叢集的特性
- 高可用:在
master
宕機時會自動將slave
提升為master
,繼續提供服務。 - 擴充套件性:當單個
redis
記憶體不足時,使用cluster
進行分片儲存。
Redis叢集並不能保證資料的強一致性
- 在特定條件下,
Redis
叢集可能會丟失已經被執行過的寫命令。 Redis
採用的是非同步複製:主節點處理完寫命令後立即返回客戶度,並不等待從節點複製完成。
Redis Cluster
Redis Cluster是Redis
官方多機部署方案,在官方推薦中使用6例項,其中3個為主節點,3個為從節點。
在Redis cluster
框架中:
Redis cluster
的節點會通過meet
操作來實現共享資訊,每個節點都知道是哪個節點負責哪個範圍內的資料槽。- 預設的情況在
Redis cluster
中redis-master
用於接收讀寫,而redis-slave
則用於備份,當有請求是在向slave
發起時,會直接重定向到對應key
所在的master
來處理。 - 如果存在
redis-cluster
對資料的實時性要求不高時,可以通過readonly
,將slave
設定成可讀的,然後通過slave
直接獲取相關的key
,達到讀寫分離。
快取和資料庫一致性問題
CAP原理
CAP
原理指的是,一個提供資料服務的儲存系統無法同時滿足:資料一致性、資料可用性、分割槽耐受性
- C資料一致性:所有應用程式都能訪問到相同的資料。
- A資料可用性:任何時候,任何應用程式都可以讀寫訪問。
- P分割槽耐受性:系統可以跨網路分割槽線性伸縮。(通俗來說就是資料的規模可擴充套件)
在大型網站中通常都是犧牲C,選擇AP。為了可能減小資料不一致帶來的影響,都會採取各種手段保證資料最終一致。
- 資料強一致:各個副本的資料在物理儲存中總是一致的。
- 資料使用者一致:資料在物理儲存的各個副本可能是不一致的,但是通過糾錯和校驗機制,會確定一個一致的且正確的資料返回給使用者。
- 資料最終一致:物理儲存的資料可能不一致,終端使用者訪問也可能不一致,但是一段時間內資料會達成一致。
Redis
由於器非同步複製的特性,所以它本身是資料最終一致性的。
解決快取一致性的解決方案:
- 延時雙刪策略
- 通過訊息佇列來更新快取
- 通過
binlog
來同步mysql
資料庫到redis
中
延時雙刪策略
一個寫操作會進行以下流程:
- 先淘汰快取
- 再寫資料庫
- 休眠1秒,再次淘汰快取
接著我們要明確為什麼要採用先淘汰快取,再寫資料庫的策略。
先寫資料庫再更新快取的弊端:
1.執行緒安全方向(為什麼要先操作快取,再操作資料庫)
同時有請求A和請求B進行更新操作,那麼會出現:
- 執行緒A更新了資料庫;
- 執行緒B更新了資料庫;
- 執行緒B更新了快取;
- 執行緒A更新了快取;(A出現網路波動)
這就出現請求A更新快取應該比請求B更新快取早才對,但是因為網路等原因,B卻比A更早更新了快取。這就導致了髒資料。
並且這種情況只能等快取失效,才能夠得到解決,這樣的話很大程度會對業務產生比較大的影響。
2.業務方向(為什麼選擇淘汰快取,而不是更新快取)
- 快取的意義就是為了提升讀操作的效能,如果你寫操作比較頻繁,頻繁更新快取且沒有讀操作,會造成效能浪費,所以應該由讀操作來觸發生成快取,故而在寫操作的時候應採用淘汰快取的策略。
- 有的時候我們在存入快取可能也會做一些其他轉化操作,但是如果又立馬被修改,也會造成效能的浪費。
採用先淘汰快取,再寫資料庫事實上不是完美的方案,但是是相對而言最合理的方法,它有下面的特殊情況:
- 寫請求A進行寫操作,刪除快取;
- 讀請求B查詢發現快取不存在;
- 讀請求B去資料庫查詢得到舊值;
- 讀請求B將舊值寫入快取;
- 寫請求A將新值寫入資料庫;(這裡不採取行動,會造成資料庫與快取資料不一致)
上述情況就會導致不一致的情形出現。
延時雙刪策略是為了解決採用先淘汰快取,再寫資料庫可能造成資料不一致的問題,這個時候寫請求A應採用休眠1秒,再次淘汰快取的策略:
- 採用上述的做法,第一次寫操作,會出現將近1秒(小於 1秒-讀請求操作時間)的資料不一致的問題,1秒後再次執行快取淘汰,下次讀操作後就會保證資料庫與快取資料的一致性了。
- 這裡提到的1秒,是用來確保讀請求結束(一般是幾百ms),寫請求可以刪除讀請求造成的快取髒資料。
另外還存在一種極端情況是:如果第二次淘汰快取失敗,會導致資料和快取一直不一致的問題,所以
- 快取要設定失效時間
- 設定重試機制或者採用訊息佇列的方式保證快取被淘汰。
通過訊息佇列來更新快取
採用訊息佇列中介軟體的方式能夠保證資料庫資料與快取資料的最終一致性。
- 實現了非同步更新快取,降低了系統的耦合性
- 但是破壞了資料變化的時序性
- 成本相對比較高
通過binlog來同步Mysql資料庫到Redis中
Mysql
資料庫任何時間對資料庫的修改都會記錄在binlog
中;當資料發生增刪改,建立資料庫物件都會記錄到binlog
中,資料庫的複製也是基於binlog
進行同步資料:
- 在
mysql
壓力不大情況下,延遲較低; - 和業務完全解耦;
- 解決了時序性問題。
- 成本相對而言比較大
快取機制
一個高效能網站一般都會用到快取架構,快取意味著高效能,通過空間換時間。
儲存和快取的區別
- 儲存要求資料可以進行持久化,資料不能輕易被丟失
- 儲存要保證資料結構的完整性,所以要求資料支援更多的資料型別
四層快取
- 基於瀏覽器等裝置的客戶端快取
- 基於
CDN
加速的網路層快取,通過CDN
能夠實現對頁面的快取 - 基於
Ngnix
等負載均衡元件的路由層快取 - 基於
Redis
等的業務層快取
業務層的快取可以細分為三級快取:
- 一級快取(會話級快取):在維持一個會話時,查詢獲取的資料會存放在一級快取中,下次使用從快取中獲取。
- 二級快取(應用級快取):當會話關閉時,一級快取的資料會儲存在二級快取中。
- 三級快取(資料庫級快取):可以實現跨
jvm
,通過遠端呼叫的方式實現資料同步。
快取中的常見問題
快取雪崩問題
快取雪崩,是指在某一個時間段,快取集中過期失效。
解決方法:
- 根據業務特點,對不同的“記錄”設定不同的失效週期。
- 在併發量不是很高的情況下,使用加鎖排隊的方式。
- 給每一個快取資料新增相應快取標記,記錄快取是否失效,如果快取標記失效,則更新資料快取。
快取預熱問題
新啟動的快取系統如果沒有任何資料,在重建快取資料的過程中,系統的效能和資料庫負載造成壓力。
解決方法:
- 快取系統啟動時就把熱點資料載入好,如一些元資料—城市地名列表、類目資訊等。
快取穿透問題
快取穿透,是指查詢一個數據庫一定不存在的資料。 假如有惡意攻擊,就可以利用這個漏洞,對資料庫造成壓力,甚至壓垮資料庫。即便是採用UUID
,也是很容易找到一個不存在的KEY
,進行攻擊。
解決方法:
- 再
web
伺服器啟動時,提前將有可能被頻繁併發訪問的資料寫入快取。 - 如果從資料庫查詢的物件為空,也放入快取,只是設定的快取過期時間較短,比如設定為60秒(避免大量空值的
key
佔用快取的空間)。 - 規範
key
的命名,使用布隆過濾器的方式對一些定義好的key
規範進行檢測,過濾一些惡意訪問的請求。
快取擊穿問題
快取擊穿,是指一個key
非常熱點,在不停的扛著大併發,大併發集中對這一個點進行訪問,當這個key
在失效的瞬間,持續的大併發就穿破快取,直接請求資料庫。
解決方法:
- 對熱點資料設定比較長的生命週期或者永不過期。
快取的併發競爭問題
多客戶端同時併發寫一個key
,可能本來應該先到的資料後到了,導致資料版本錯了。
解決方法:
- 使用
watch
監控物件變化來實現樂觀鎖