一、 Redis官方推薦集羣方案:Redis Cluster
適用於redis3.0以後版本,
Redis羣集數據分片
· Redis Cluster不使用一致的散列,而是使用不同形式的分片,其中每個鍵稱之爲 hash slot.。
Redis集羣中有16384個散列槽,爲了計算給定密鑰的散列槽,我們只需採用密鑰模數16384的CRC16。
Redis羣集中的每個節點都負責哈希槽的子集,例如,擁有一個包含3個節點的集羣,其中:
- 節點A包含從0到5500的散列槽。
- 節點B包含從5501到11000的散列槽。
- 節點C包含從11001到16383的散列槽。
這允許輕鬆添加和刪除集羣中的節點。例如,如果我想添加一個新節點D,我需要將一些哈希槽從節點A,B,C移動到D.同樣,如果我想從羣集中刪除節點A,我只需移動A服務的哈希槽。到B和C.當節點A爲空時,我可以完全從集羣中刪除它。
因爲將哈希槽從一個節點移動到另一個節點不需要停止操作,添加和刪除節點,或者更改節點所持有的哈希槽的百分比,所以不需要任何停機時間。
只要涉及單個命令執行(或整個事務或Lua腳本執行)的所有鍵都屬於同一個哈希槽,Redis Cluster就支持多個鍵操作。用戶可以通過使用稱爲哈希標記的概念強制多個密鑰成爲同一哈希槽的一部分。
Hash標籤記錄在Redis集羣規範中,但要點是如果密鑰中{}括號之間有子字符串,則只對字符串內部的內容進行哈希處理,例如this{foo}key
並another{foo}key
保證位於相同的哈希槽中,並且可以在具有多個鍵作爲參數的命令中一起使用。
Redis Cluster主從模型
爲了在主節點子集發生故障或無法與大多數節點通信時保持可用,Redis Cluster使用主從模型,其中每個散列槽從1(主機本身)到N個副本(N) -1個額外的從節點)。創建集羣時,每個主節點添加一個從節點,以便最終集羣由作爲主節點的A,B,C和作爲從節點的A1,B1,C1組成。如果節點B出現故障,系統就能繼續運行。節點B1複製B,B失敗,集羣將節點B1升級爲新的主節點,並將繼續正常運行。
注意,如果節點B和B1同時發生故障,Redis Cluster將無法繼續運行。
Redis羣集一致性保證
Redis Cluster無法保證強一致性。在某些條件下,Redis Cluster可能會丟失系統向客戶端確認的寫入。
Redis Cluster可能丟失寫入的第一個原因是它使用異步複製。這意味着在寫入期間會發生以下情況:
- 客戶端寫入master B.
- master B向客戶端回覆確定。
- master B將寫入傳播到其從設備B1,B2和B3。
(1)B在回覆客戶端之前並沒有等待來自B1,B2,B3的確認,因爲這對Redis來說是一個過高的延遲,所以如果客戶端寫了一些東西,B會確認寫入,但是在崩潰之前能夠將寫入發送到其slave,其中一個slave(沒有接收到寫入)被提升爲master ,永遠丟失寫入。
這與大多數數據庫配置爲每秒將數據刷新到磁盤的所發生的情況非常相似。同樣,可以通過在回覆客戶端之前強制數據庫刷新磁盤上的數據來提高一致性,但會導致性能過低。在Redis Cluster情況下,相當於同步複製。
解決辦法,即需要在性能和一致性之間進行權衡。
Redis Cluster在絕對需要時支持同步寫入,通過WAIT命令實現,這使得丟失寫入的可能性大大降低,但即使使用同步複製,Redis Cluster也不會實現強一致性:在更復雜的情況下總是可以實現失敗場景,無法接收寫入的slave被選爲master。
(2)還有另一個值得注意的情況是,Redis集羣將丟失寫入,這種情況發生在網絡分區中,其中客戶端與少數實例(至少包括主服務器)隔離。如,
以6個節點簇爲例,包括A,B,C,A1,B1,C1,3個主站和3個從站。還有一個客戶,我們稱之爲Z1。
在發生分區之後,可能在分區的一側有A,C,A1,B1,C1,在另一側有B和Z1。
Z1仍然可以寫入B,它將接受其寫入。如果分區在很短的時間內恢復,集羣將繼續正常運行。但是,如果分區持續足夠的時間使B1在分區的多數側被提升爲主,則Z1發送給B的寫入將丟失。
注意,Z1將能夠發送到B的寫入量存在maximum window:如果分區的多數方面已經有足夠的時間將slave選爲master,則少數端的每個主節點都會停止接受寫入。
這段時間是Redis Cluster的一個非常重要的配置指令,稱爲節點超時。
節點超時過後,master被視爲失敗,可以由其中一個副本替換。類似地,在節點超時已經過去而主節點無法感知大多數其他主節點之後,它進入錯誤狀態並停止接受寫入。
2):如果一個實例(instance)距離最後一次有效回覆 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個實例會被 Sentinel 標記爲主觀下線。
3):如果一個Master被標記爲主觀下線,則正在監視這個Master的所有 Sentinel 要以每秒一次的頻率確認Master的確進入了主觀下線狀態。
4):當有足夠數量的 Sentinel(大於等於配置文件指定的值)在指定的時間範圍內確認Master的確進入了主觀下線狀態, 則Master會被標記爲客觀下線
5):在一般情況下, 每個 Sentinel 會以每 10 秒一次的頻率向它已知的所有Master,Slave發送 INFO 命令
6):當Master被 Sentinel 標記爲客觀下線時,Sentinel 向下線的 Master 的所有 Slave 發送 INFO 命令的頻率會從 10 秒一次改爲每秒一次
7):若沒有足夠數量的 Sentinel 同意 Master 已經下線, Master 的客觀下線狀態就會被移除。
若 Master 重新向 Sentinel 的 PING 命令返回有效回覆, Master 的主觀下線狀態就會被移除。
主從複製:主節點負責寫數據,從節點負責讀數據,主節點定期把數據同步到從節點保證數據的一致性
備註:主從複製和哨兵機制需要進行手動配置。
三、Redis作爲緩存應用問題及解決方案:
1)緩存穿透
-
對所有可能查詢的參數以hash形式存儲,在控制層先進行校驗,不符合則丟棄。還有最常見的則是採用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。
-
也可以採用一個更爲簡單粗暴的方法,如果一個查詢返回的數據爲空(不管是數據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鐘。
2)緩存雪崩
-
在緩存失效後,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。
-
可以通過緩存reload機制,預先去更新緩存,再即將發生大併發訪問前手動觸發加載緩存
-
不同的key,設置不同的過期時間,讓緩存失效的時間點儘量均勻. 比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重複率就會降低,就很難引發集體失效的事件
-
做二級緩存,或者雙緩存策略。A1爲原始緩存,A2爲拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置爲短期,A2設置爲長期。
3)緩存擊穿
4)緩存預熱
-
直接寫個緩存刷新頁面,上線時手工操作下;
-
數據量不大,可以在項目啓動的時候自動進行加載;
-
定時刷新緩存;
5)緩存更新
-
定時去清理過期的緩存;
-
當有用戶請求過來時,再判斷這個請求所用到的緩存是否過期,過期的話就去底層系統得到新數據並更新緩存。
6)緩存降級
-
一般:比如有些服務偶爾因爲網絡抖動或者服務正在上線而超時,可以自動降級;
-
警告:有些服務在一段時間內成功率有波動(如在95~100%之間),可以自動降級或人工降級,併發送告警;
-
錯誤:比如可用率低於90%,或者數據庫連接池被打爆了,或者訪問量突然猛增到系統能承受的最大閥值,此時可以根據情況自動降級或者人工降級;
-
嚴重錯誤:比如因爲特殊原因數據錯誤了,此時需要緊急人工降級。
四、redis作爲分佈式鎖方案(性能最優)
分佈式鎖是控制分佈式系統之間同步訪問共享資源的一種方式。
實現思路:
- 使用
SETNX
命令獲取鎖,若不存在則設置值,設置成功則表示取得鎖成功; - 設置expire,保證超時後能自動釋放鎖(使用lua腳本將setnx和expire變成一個原子操作);
- 釋放鎖,使用
DEL
命令將鎖數據刪除。