初識Redis Cluster & 深入Redis Cluster & 緩存設計與優化
實驗部分對應 Redis筆記(四)實驗部分:redisCluster的原生安裝與官方工具安裝
第9章 初識Redis Cluster
Redis Cluster是Redis 3提供的分佈式解決方案,有效解決了Redis分佈式方面的需求,同時它也是學習分佈式存儲的絕佳案例。本章將針對Redis Cluster的數據分佈,搭建集羣進行分析說明。
9-1 本章目錄
- 呼喚集羣
- 數據分佈
- 搭建集羣
- 集羣伸縮
- 客戶端路由
- 集羣原理
- 開發運維常見問題
9-2 呼喚集羣
爲什麼呼喚
-
併發量
-
數據量
-
網絡流量
“解決方法"
配置“強悍”的機器:超大內存、牛xCPU等
正確的解決方法
分佈式:簡單的認爲加機器
9-3 數據分佈概論
兩種分區方式
兩種數據分佈對比
9-4 節點取餘分區
擴容
若添加一個節點在下面途中會有8成的遷移量
但如果採用翻倍擴容(比如下面 的六個 需要遷移的數據量將大大降低)
優缺點
優點: 客戶端分片,使用 哈希+取餘較爲簡單
缺點:節點伸縮時數據節點關係變化,會導致數據遷移,而遷移數量和添加節點數量有關所以建議翻倍擴容
9-5 一致性哈希分區
一個tocken環的長度設爲 0~2的32次方,放置四個節點,新添加的key則會順勢針的找到最近的node
擴容
若添加n5 則變動的數據僅爲n1和n2之間的 ,需要變動(回寫)的數據就會減少
優缺點
優點
客戶端分片:哈希+順時針(優化取餘)
節點伸縮:隻影響鄰近節點,但是還是有數據遷移
缺點
添加n5後,會使除 n2 n5外的節點的數據量相對更大 因此建議使用翻倍伸縮,保證最小遷移數據和負載均衡
9-6 虛擬槽哈希分佈
- 預設虛擬槽:每個槽映射一個數據子集,一般比節點數大
- 良好的哈希函數:例如CRC16
- 服務端管理節點、槽、數據:例如Redis Cluster
9-7 基本架構
分佈式架構中每個節點都會負責讀和寫 並且彼此能夠相互通信
Redis Cluster架構
- 節點
# 節點集羣模式啓動該節點
cluster-enabled:yes
-
meet 幾點之前要達到互通
-
指派槽
-
複製
9-8 原生安裝
#準備節點
節點握手
分配槽
分配主從
9-9 工具安裝
第10章 深入Redis Cluster
本章將針對Redis Cluster的集羣伸縮,請求路由,故障轉移等方面進行分析說明。
10-1 集羣伸縮目錄
主要分爲三個部分 集羣的擴容與縮容及其伸縮的原理
10-2 集羣伸縮原理
下圖代表節點的加入與下線
下圖是一個“伸”的過程,即將節點加入6385加入集羣,該過程需要meet操作 和槽的分配。
其實集羣伸縮就等於是槽和數據在節點之間的移動
10-3 集羣的擴展
10-3-1 擴展集羣-1.加入節點
準備新節點
新節點:
- 集羣模式
- 配置和其他節點統一·啓動後是孤兒節點。
redis-server conf/redis-6385.conf
redis-server conf/redis-6386.conf
啓動兩個孤立節點之後集羣結構如下:
加入集羣
127.0.0.1:6379>cluster meet 127.0.0.1 6385
127.0.0.1:6379>cluster meet 127.0.0.1 6386
加入之後如下
加入集羣有兩個作用
- 爲它遷移槽和數據實現擴容
- 作爲從節點負責故障轉移
當然也可以使用官方工具進行加入節點
redis-trib.rb add-node new_host:new port existing_host:existing port --slave --master-id <arg>
redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379
使用redis-trib.rb能夠避免新節點已經加入了其他集羣,造成故障。
遷移槽和數據
主要分爲三步
槽遷移計劃
分配給新節點一定的槽數:
遷移數據
- 對目標節點發送:
cluster setslot {slot} importing {sourceNodeld}
命令,讓目標節點準備導入槽的數據。 - 對源節點發送:
cluster setslot {slot} migrating {targetNodeld}
命令,讓源節點準備遷出槽的數據。 - 源節點循環執行
cluster getkeysinslot {slot){count}
命令,每次獲取count個屬於槽的健。 - 在源節點上執行
migrate {targetlp} {targetPort} key 0 {timeout}
命令把指定key遷移。 - 重複執行步驟3~4直到槽下所有的鍵數據遷移到目標節點。
- 向集羣內所有主節點發送
cluster setslot {slot} node {targetNodeld}
命令,通知槽分配給目標節點。
添加從節點
10-3-2 集羣擴容演示-1
10-3-3 集羣擴容演示-2
10-4 集羣的縮容
10-4-1 集羣縮容-說明
10-4-2 集羣縮容-操作
10-5客戶端路由
10-5-1 moved異常說明和操作
下圖是指向自身 之後槽命中的效果
槽不命中:moved異常
下面的實例演示中先 添加 -c 參數以集羣方式啓動 可以看到 鍵值爲hello是直接命中了節點 鍵值爲php時出現了moved異常 並且幫我們完成跳轉成7001客戶端。
而不使用 -c 參數時則會直接返回異常
10-5-2 ask重定向
由於槽遷移而客戶端中所保存的地址不變會造成訪問上的問題 這就有了ask重定向異常
moved和ask的比較
- 兩者都是客戶單重定向
- moved:槽已經確定遷移
- ask:槽還在遷移中
10-5-3 smart客戶端實現原理
smart客戶端原理主要就在於追求性能:
- 從集羣中選一個可運行節點,使用cluster slots初始化槽和節點映射。
- 將cluster slots的結果映射到本地,爲每個節點創建JedisPool。
- 準備執行命令。
其中執行命令是smart客戶端的核心過程
JedisCluster中 存儲的是 key及其對應的槽的位置 和改槽所在的節點
若目標節點返回出錯則會隨機請求一個節點 這個過程大概率會發生 moved異常然後就會重置本地緩存 如果五次未成功則返回異常
大致流暢圖如下:
10-5-4 JedisCluster執行源碼分析
10-6 smart客戶端JedisCluster
10-6-1 JedisCluster基本使用
10-6-2 整合spring-1
10-6-3 整合spring-2
10-6-4 多節點操作命令
10-6-5 批量操作優化
批量操作怎麼實現?
mget mset必須在一個槽
四種批量優化的方法:
- 串行mget
2. 串行IO
其實就是對上面的改進 通過sunKeys按照所屬節點聚合分組 降低傳輸次數
3. 並行IO
使用多線程進行並行操作
4. hash_tag
使用tag進行包裝 效率更高 全部請求到一個節點上
四種方式比較
10-7 故障轉移
10-7-1 故障發現
◆通過ping/pong消息實現故障發現:不需要sentinel
◆主觀下線和客觀下線
主觀下線
定義:某個節點認爲另一個節點不可用,“偏見"
主管下線流程:
其中 pfail就代表是主管下線
客觀下線
定義:當半數以上持有槽的主節點都標記某節點主觀下線
客觀下線流程:一個主節點接收到其它節點的ping消息 並且該節點會維護每個節點及其對應的pfail信息的一個列表
嘗試客觀下線的流程
該節點的作用有兩個:
- 通知集羣內所有節點標記故障節
- 點爲客觀下線通知故障節點的從節點觸發故障
轉移流程
10-7-2 故障恢復
故障恢復主要分爲四步:
資格檢查
- 每個從節點檢查與故障主節點的斷線時間。
- 超過cluster-node-timeout*cluster-slave-validity-factor取消資格。
- cluster-slave-validity-factor:默認是10
cluster-node-timeout默認是15
即可得知 時間超過150秒則取消資格
備選舉時間
上圖b-1節點偏移量 更大意味着丟失數據更小 所以要給予偏袒 讓其延遲一秒就參與選舉以獲得更多的票數。
選舉投票
獲得票數過半即可替換主節點
替換主節點
- 當前從節點取消複製變爲主節點。(slaveof no one)
- 執行clusterDelSlot撤銷故障主節點負責的槽,並執行clusterAddSlot把這些槽分配給自己。
- 向集羣廣播自己的pong消息,表明已經替換了故障從節點
10-7-3 故障模擬
10-8 Redis Cluster常見開發運維問題
10-8-1 集羣完整性
cluster-require-full-coverage默認爲yes(即所有節點和槽可用集羣才提提供服務)
- 集羣中16384個槽全部可用:保證集羣完整性
- 節點故障或者正在故障轉移:
(error)CLUSTERDOWN The cluster is down
大多數業務無法容忍,cluster-require-full-coverage建議設置爲no
10-8-2 帶寬消耗
體現在三個方面:
- 消息發送頻率:節點發現與其它節點最後通信時間超過cluster-node-timeout/2時會直接發送ping消息
- 消息數據量:slots槽數組(2KB空間)和整個集羣1/10的狀態數據(10個節點狀態數據約1KB)
- 節點部署的機器規模:集羣分佈的機器越多且每臺機器劃分的節點數越均勻,則集羣內整體的可用帶寬越高。
舉例
- 規模:節點200個、20臺物理機(每臺10個節點)
- cluster-node-timeout=15000,ping/pong帶寬爲25Mb
- cluster-node-timeout=20000,ping/pong帶寬低於15Mb
優化
- 避免“大”集羣:避免多業務使用一個集羣,大業務可以多集羣。
- cluster-node-timeout:帶寬和故障轉移速度的均衡。
- 儘量均勻分配到多機器上:保證高可用和帶寬
10-8-3 Pub/Sub廣播
問題:publish在集羣每個節點廣播:加重帶寬
解決:單獨“走”一套Redis Sentinel
10-8-4 集羣傾斜-目錄
10-8-5 數據傾斜
- 節點和槽分配不均
redis-trib.rb info ip:port 查看節點、槽、鍵值分佈
redis-trib.rb rebalance ip:port 進行均衡(謹慎使用)
-
不同槽對應鍵值數量差異較大
CRC16正常情況下比較均勻。
可能存在hash_tag ,可通過cluster countkeysinslot {slot}
獲取槽對應鍵值個數 -
包含bigkey
bigkey:例如大字符串、幾百萬的元素的hash、set等
可以在從節點執行:redis-cli --bigkeys
查看bigkey
優化:優化數據結構。 -
內存相關配置不一致
hash-max-ziplist-value、set-max-intset-entries等
優化:定期“檢查”配置一致性
10-8-6 請求傾斜
熱點key:
- 重要的key或者bigkey
優化:
- 避免bigkey
- 熱鍵不要用hash_tag
- 當一致性不高時,可以用本地緩存+MQ
10-8-7 讀寫分離
只讀連接:集羣模式的從節點不接受任何讀寫請求。
- 重定向到負責槽的主節點
- readonly命令可以讀:連接級別命令
讀寫分離:更加複雜
- 同樣的問題:複製延遲、讀取過期數據、從節點故障
- 修改客戶端:cluster slaves{nodeld}
10-8-9 數據遷移
官方遷移工具:redis-trib.rb import
- 只能從單機遷移到集羣
- 不支持在線遷移:source需要停寫
- 不支持斷點續傳
- 單線程遷移:影響速度
在線遷移:
- 唯品會redis-migrate-tool
- 豌豆莢:redis-port
10-8-10 集羣vs單機
集羣的限制
- key批量操作支持有限:例如mget、mset必須在一個slot
- Key事務和Lua支持有限:操作的key必須在一個節點
- key是數據分區的最小粒度:不支持bigkey分區
- 不支持多個數據庫:集羣模式下只有一個db0
- 複製只支持一層:不支持樹形複製結構
分佈式redis不一定好用:
1.Redis Cluster:滿足容量和性能的擴展性,很多業務”不需要”。
大多數時客戶端性能會”降低"。
命令無法跨節點使用:mget、keys、scan、flush、sinter等。
Lua和事務無法跨節點使用。
客戶端維護更復雜:SDK和應用本身消耗(例如更多的連接池)。
2.很多場景Redis Sentinel已經足夠好。
第11章 緩存設計與優化
講解將緩存加入應用架構後帶來的一些問題,這些問題常常會成爲應用的致命點。
11-1 目錄
11-2 緩存的受益和成本
受益
1.加速讀寫
通過緩存加速讀寫速度:CPUL1/L2/L3 Cache、Linux page Cache加速硬盤讀寫、瀏覽器緩存、Ehcache緩存數據庫結果。
2.降低後端負載
後端服務器通過前端緩存降低負載:業務端使用Redis降低後端MySQL負載等
成本
1.數據不一致:緩存層和數據層有時間窗口不一致,和更新策略有關。
2.代碼維護成本:多了一層緩存邏輯。
3.運維成本:例如Redis Cluster
使用場景
1.降低後端負載:
對高消耗的SQL:join結果集/分組統計結果緩存。
2.加速請求響應:
利用Redis/Memcache優化IO響應時間
3.大量寫合併爲批量寫:
如計數器先Redis累加再批量寫DB
11-3 緩存的更新策略
1.LRU/LFU/FIFO算法剔除:例如maxmemory-policy。
2.超時剔除:例如expire。
3.主動更新:開發控制生命週期
兩條建議
1.低一致性:最大內存和淘汰策略
2.高一致性:超時剔除和主動更新結合,最大內存和淘汰策略兜底。
11-4 緩存粒度問題
緩存粒度控制-三個角度
1.通用性:全量屬性更好。
2.佔用空間:部分屬性更好。
3.代碼維護:表面上全量屬性更好。
11-5 緩存穿透問題
原因
1.業務代碼自身問題
2.惡意攻擊、爬蟲等等
如何發現
1.業務的相應時間
2.業務本身問題
3.相關指標:總調用數、緩存層命中數、存儲層命中數
解決方法1-緩存空對象
兩個問題
1.需要更多的鍵。
2. 存儲層數據可能只是短期掛掉,緩存層數據置爲空會導致 二者“短期”不一致。
代碼中若 存儲層中的數據爲空 則設置在緩存中設爲空,並且設置過期時間
解決方法2-布隆過濾器攔截
該方法更適用於較爲固定的數據。
11-6 緩存雪崩優化
11-7 無底洞問題
問題描述:
2010年,Facebook有了3000個Memcache節點。
發現問題:“加”機器性能沒能提升,反而下降。
http://highscalability.com/blog/2009/10/26/facebooks-memcached-multiget-hole-more-machines-more-capacit.html
問題關鍵點:
- 更多的機器!=更高的性能
- 批量接口需求(mget,mset等)
- 數據增長與水平擴展需求
優化IO的幾種方法
1.命令本身優化:例如慢查詢keys、hgetall bigkey
2.減少網絡通信次數
3.降低接入成本:例如客戶端長連接/連接池、NIO等
四種批量優化的方法
1.串行mget
2.串行I0
3.並行I0
4.hash_tag
11-8 熱點key的重建優化
三個目標和兩個解決
1.三個目標:
減少重緩存的次數
數據儘可能一致
減少潛在危險
2.兩個解決:
互斥鎖(mutex key)
實例代碼
永遠不過期
1.緩存層面:沒有設置過期時間(沒有用expire)。
2.功能層面:爲每個value添加邏輯過期時間,但發現超過邏輯過期時間後,會使用單獨的線程去構建緩存。
代碼示例:
兩種方案比較: