Redis Cluster架構優化
在《全面剖析Redis Cluster原理和應用》中,我們已經詳細剖析了現階段Redis Cluster的缺點:
- 無中心化架構
- Gossip消息的開銷
- 不停機升級困難
- 無法根據統計區分冷熱數據 </ul> </li>
- 客戶端的挑戰
- Cluster協議支持
- 連接和路由表的維護開銷
- MultiOp和Pipeline支持有限 </ul> </li>
- Redis實現問題
- 不能自動發現
- 不能自動Resharding
- 無監控管理UI
- 最終一致性和“腦裂”問題
- 數據遷移以Key為單位,速度較慢
- 數據遷移沒有保存進度,故障時不能恢復
- Slave“冷備”,不能緩解讀壓力 </ul> </li> </ul>
- PING/PONG:Cluster的心跳,每個結點每秒隨機PING幾個結點。結點的選擇方法是:超過cluster-node-timeout一半的時間還未PING過或未收到PONG的結點,所以這個參數會極大影響集群內部的消息通信量。心跳包除了結點自己的數據外,還包含一些其他結點(集群規模的1/10)的數據,這也是“Gossip流言”的含義。
- MEET/PONG:只有MEET后的受信結點才能加入到上面的PING/PONG通信中。 </ul>
- 原來Master結點的連接池沒有處理
- 結點IP列表沒有更新,極端情況下有問題 </ol>
- 協議解析
- 實現Cluster協議,屏蔽影響
- 維護到后端的長連接 </ul> </li>
- 安全過濾
- 命令白名單
- 安全權限過濾 </ul> </li>
- 負載均衡
- Presharding哈希函數
- 緩存Slot路由表
- 控制Resharding算法和方式 </ul> </li>
- 結果聚合
- MultiOp支持
- Pipeline支持 </ul> </li>
- 讀寫分離
- 讀壓力分攤,避免Slave“冷備” </ul> </li>
- 層次化存儲
- 冷數據Swap到慢存儲
- L1緩存實現 </ul> </li>
- 監控管理
- 狀態的監控、歷史報告
- 閾值的設置、預警 </ul> </li> </ul>
- 自動故障轉移:不再需要額外的Sentinel集群
- 官方的Slot實現:不修改Redis源碼就得到Slot實現及常用操作
- 被動保證Slot一致性:Redis負責訪問了舊結點的客戶端的重定向
- 遷移中的數據訪問:Redis負責訪問遷移中數據的客戶端的重定向 </ul>
- 保存Slot和Group映射
- 將Slot和Group的變化通知Proxy
- 遷移時與Proxy進行Pre-migrate確認
- 保存Migrate任務和進度信息 </ul>
- 讀取和校驗配置文件
- 創建、綁定、關閉套接字
- 啟動、終止、維護所配置數目的worker進程
- worker進程接受、處理來自客戶端的連接
- 不中斷服務刷新配置文件
- 不中斷服務升級程序
- 反向代理和過濾功能 </ul>
- Zero-latency Proxy (Proxy):高性能的Proxy,隱藏后端存儲路由和實現
- Cluster Manager (Agent):集群的部署、配置、管理
- Management UI (Dashboard):監控管理頁面,提供RESTFul API,Web,CLI等多種管理方式 </ul>
既然保留了Proxy組件,Redis Cluster的優勢就不明顯了。那為什么后端還要用Redis Cluster而不是單機版的Redis呢?因為Redis Cluster給我們帶來幾個最大的好處:
4.1.2 Dashboard組件
一個美觀而實用的Dashboard完全有理由讓用戶拋棄redis-trib,要是再具有自動部署和Resharding算法那就更完美了!
4.1.3 Agent組件
Agent不僅可以完成運行數據采集,僅僅這樣的話Dashboard完全可以自己完成。它還可以完成Redis Cluster的部署工作,這樣就能大大降低開發人員的工作量和手工出錯的概率。
4.2 ZK去哪了?
Codis完全依賴ZooKeeper,進入到Redis Cluster后,ZooKeeper哪里去了?我們先回憶一下ZooKeeper在其中的角色,詳情請參見《豆瓣Redis解決方案Codis源碼剖析:Dashboard》:
因為Redis Cluster的P2P架構,Slot與結點的映射關系都打散到集群中的各個結點上,所以第一個問題就解決了。又因為當客戶端去舊結點請求數據時會收到MOVED或ASK消息進行重定向,就像是LAZY緩存過期策略一樣,等訪問時再更新或清除,所以第二和第三個問題也解決了。唯一要重點考慮的就是遷移任務這種Redis Cluster并不負責的全局信息的保存。
4.3 運維成本
關于如何降低Redis的運維成本,可以參考AliRedis和Reborndb。
4.3.1 阿里AliRedis
AliRedis是來自阿里巴巴的基于Redis改造的緩存產品,目前還未開源。網上只能搜到這么一篇資料《AliRedis單機180w QPS, 8臺服務器構建1000w QPS Cache集群》。
AliRedis采取“多線程Master + N*Worker的工作模式。Master線程負責監聽網絡事件, 在接收到一個新的連接后, Master會把新的fd注冊到Worker的epoll事件中, 交由worker處理這個fd的所有讀寫事件。這樣Master線程就可以完全被釋放出來接收更多的連接, 同時又不妨礙worker處理業務邏輯和IO讀寫。”
AliRedis對Redis架構上非常類似Nginx的Master-Worker架構模式,那Nginx中的Master進程都有哪些作用呢?(《Nginx工作進程模型》)
因此,每臺服務器上由一個Master管理這臺機器上的所有Redis實例,達到充分利用多核多線程。同時每臺服務器只需維護一個AliRedis實例,大大降低運維成本的目的。也可以根據需要,實現類似Nginx中Master的各種功能。
4.3.2 豆瓣Reborndb
Reborndb基本是從Codis衍生出來的,但相比Codis多了一個Agent組件。agent主要是部署在Redis實例的機器上,類似 AliRedis的Master,但Agent不負責處理請求。它負責監管Redis實例的生命周期,例如Redis部署、啟動、停止、重啟以及升級等等。它通過一套RESTFul接口來暴露這些操作。此外,它還擔當著Redis高可用性協調者的角色,類似官方HA方案的Sentinel。
4.3.3 Master vs. Proxy
Master與Redis實例在一臺機器上,Master負責建立連接,之后的I/O讀寫都交給Worker進程處理。因此,這與《Netty 4源碼解析:請求處理》介紹過的主從Reactor模式里的主Reactor非常像!只不過Netty的模型是在一個進程里通過線程實現的,而AliRedis是類似Nginx用進程實現的。
而Proxy的責任要比Master大得多,它負責請求和響應處理的全過程,而不是建立連接后直接交給后端。所以Proxy的壓力也不小,一般與Redis實例不部署在一臺機器上。實際上,它與Master并不矛盾。Proxy模式負責解決協議解析、請求的過濾轉發、結果聚合等問題,而Master-Worker模式則讓Redis享受到多核的速度、不停機程序升級、降低運維成本等。
5.理想中的Redis
5.1 第二代Codis
Codis作者談到第二代Codis,即Reborndb的發展方向,很值得學習:
“在開源Codis后,我們收到了很多社區的反饋,大多數的意見是集中在Zookeeper的依賴,Redis的修改,還有為啥需要Proxy上面,我們也在思考,這幾個東西是不是必須的。當然這幾個部件帶來的好處毋庸置疑,上面也闡述過了,但是有沒有辦法能做得更漂亮。于是,我們在下一階段會再往前走一步,實現以下幾個設計:
1)使用proxy內置的Raft來代替外部的Zookeeper,zk對于我們來說,其實只是一個強一致性存儲而已,我們其實可以使用Raft來做到同樣的事情。將raft嵌入proxy,來同步路由信息。達到減少依賴的效果。
2)抽象存儲引擎層,由proxy或者第三方的agent來負責啟動和管理存儲引擎的生命周期。具體來說,就是現在codis還需要手動的去部署底層的Redis或者qdb,自己配置主從關系什么的,但是未來我們會把這個事情交給一個自動化的agent或者甚至在proxy內部集成存儲引擎。這樣的好處是我們可以最大程度上的減小Proxy轉發的損耗(比如proxy會在本地啟動Redis instance)和人工誤操作,提升了整個系統的自動化程度。
3)還有replication based migration。眾所周知,現在Codis的數據遷移方式是通過修改底層Redis,加入單key的原子遷移命令實現的。這樣的好處是實現簡單、遷移過程對業務無感知。但是壞處也是很明顯,首先就是速度比較慢,而且對Redis有侵入性,還有維護slot信息給Redis帶來額外的內存開銷。大概對于小key-value為主業務和原生Redis是1:1.5的比例,所以還是比較費內存的。在RebornDB中我們會嘗試提供基于復制的遷移方式。
4)QDB:QDB使用LevelDB、RocksDB、GoLevelDB作為后端存儲。我們喜歡Redis,并且希望超越它的局限,因此我們創建了一個服務叫做QDB,它兼容Redis,將數據保存在磁盤來越過內存大小的限制并且將熱點數據保存在內存中以提高性能。”
除了第三點“基于復制的遷移”,這些改進思路在下面要介紹的Redis商業版(RLEC)中得到了印證。因為RLEC并未透露它的遷移實現方式,所以技術細節我們不得而知。
5.2 Redis商業版
Redis作者Salvatore Sanfilippo所在公司RedisLab提供了企業版的Redis產品——Redis Labs Enterprise Cluster (RLEC)——幾乎解決了我們上述的所有問題:對客戶端完全透明化,自動集群管理(伸縮、高可用、持久化等),多種監控和預警方式等等。
RLEC支持單結點(單主)、單結點主從(一主一從)、集群(多主)、高可用性的集群(多主多從)。從架構上看,RLEC集群的每個結點由以下組件組成:
任意時刻,集群中會有一個結點處于”Master”角色,即這個結點上的Cluster Manager負責整個集群的管理工作,包括集群健康檢查、分片遷移、請求監管等。由此能夠看出,RLEC本身的結點之前也會互相通信,選舉出一個主。
額外地,RLEC還支持一些比較高級的功能,如所有Key和熱Value保存在RAM,Swap冷Value到SSD,Rack感知的集群等。RLEC可以免費下載試用,免費版只支持4個分片,而且不能用于生產環境。感興趣的話,大家可以自己下載試用一下。
除了RLEC軟件,RedisLab還提供了RedisCloud云,以公有云、私有云等服務的形式提供緩存服務——Redis as a Service (RaaS)。可以說,RLEC代表了一個比較理想的Redis產品方向。
來自:http://blog.csdn.net/dc_726/article/details/48733265
當然之前也說過了:“這與Redis的設計初衷有關,畢竟作者都已經說了,最核心的設計目標就是性能、水平伸縮和可用性”。但綜合來看,要想在生產環境中使用Redis Cluster,我們還是有一些工作要做的。本文就從宏觀層面上,列舉一些架構優化的參考方案。
1.P2P架構副作用
1.1 Gossip通信開銷
Gossip消息的通信開銷是P2P分布式系統帶來的第一個副作用。有一篇關于Gossip通俗易懂的文章《Life in a Redis Cluster: Meet and Gossip with your neighbors》。Redis為集群操作的消息通信單獨開辟一個TCP通道,交換二進制消息:
關于Gossip的問題不可避免,我們只能通過參數調整和優化,在通信效率和開銷之間找到一個平衡點。因為筆者還未搭建過大規模的Redis Cluster集群,關于集群的性能和參數調優還不能給出建議,留到積累足夠經驗再做整理吧。
1.2 不停機升級困難
以Nginx為例,修改配置甚至升級版本都不需要停機,Master會逐一啟動新的Worker實例去替代舊的Worker。對于單機版的 Redis,我們也可以用類似的方式實現的。但目前不知道Redis Cluster或者其他P2P分布式系統像Cassandra,是否有比較好的方案。
1.3 冷熱數據無法區分
由于集群內結點都是對等的,所以像數據熱度這種整體的統計數據就無處存放。當內存有限時,要想實現層次化存儲,將冷數據Swap到慢存儲如磁盤上時,就變得有些棘手了!
解決方法就是計算機界號稱萬能的“增加中間層”方法。增加一層Proxy,負責做數據統計、Swap甚至L1緩存。關于冷熱數據的統計和處理,請參考《微博CacheService架構淺析》
2.客戶端的挑戰
Redis Cluster的引入會給客戶端帶來一些挑戰。要么“勇敢面對”,通過引入最新的支持Cluster協議的Jedis客戶端,再加以改造來應對這些挑戰。要么增加Proxy,像防洪堤壩一樣將危險隔離在外。
2.1 Cluster協議開發
對于Java,最主流的Jedis客戶端已經早早開始支持Cluster協議了,但仔細看了一下,貌似處理集群中結點Failover時有些問題。Slave替換上來了,Jedis的確可以根據”MOVED”消息更新Slot與結點的對應關系,但是:
不知道這算不算Jedis由來已久的問題了。因為之前Jedis就是只支持要么用分片連接池,要么用Sentinel連接池,沒有兩者的結合!還好有熱心的程序員“出手相助”,詳見《Jedis分片Sentinel連接池實驗》。上面兩個問題對應的源碼看得不是很細,突然想到的這兩個問題,要是說的不對還請指正!
2.2 連接和路由表的維護
為了實現Smart客戶端,Jedis要緩存16384個Slot到結點的映射關系。這還不算什么,Jedis還要為每個結點單獨開一個連接池。假如你有一臺強勁的32核服務器,為了在多核上充分釋放Redis的處理能力,可能會起16甚至32個實例,想想會有多少連接建立?如果你有兩臺應用呢?
這個問題在像Hazelcast或GridGain等其他P2P系統中還不會這么嚴重。因為這兩個產品都是用Java多線程開發的,每臺服務器上起一個實例就可以了。這樣客戶端即便是Smart模式也不會開很多連接到服務器。后面還會講到,單線程的Redis的運維成本也不小。
2.3 MultiOp和Pipeline支持有限
因為Redis Cluster自動數據Sharding的緣故,MultiOp和Pipeline都被限定在命令中的所有Key必須都在同一Slot內。如果想突破這個限定該怎么辦?那擴展Jedis或者在Proxy中實現命令拆分和結果聚合的邏輯。
3.Redis實現問題
關于Redis的具體實現細節問題,主要是Redis簡潔的設計、redis-trib等工具的欠缺導致的。我們可以通過Dashboard或Agent組件來解決這些問題。
3.1 不能自動發現
Redis Cluster沒有使用傳統的Multicast通知自動發現集群結點,我們能做的也只能是像redis-trib那樣,在用戶指定新結點時幫它執行CLUSTER MEET命令。
3.2 手動Resharding
手動指的不只是像Codis那樣要在控制臺上添加完新結點后手動觸發Rebalance,而是要我們指定哪些Slot遷移到哪些結點上!就像建立集群時做的那樣!如果我們有個統一的Dashboard,實現個簡單的根據各個機器和Slot負載進行Resharding的算法,那么就能將這部分工作自動化了。
3.3 無監控管理UI
Redis一直沒有官方的監控管理工具,到了Redis Cluster依舊是這個樣子。這個問題比較好解決,像Codis那樣提供一套漂亮的Dashboard就可以了,底層使用各種CLUSTER命令完成工作。
3.4 “腦裂”問題
關于“腦裂”(網絡分區)問題,只能靠Redis官方提供解決方案了。
3.5 遷移速度較慢
GitHub上有人提了一個Issue “redis-trib: use pipeline to speed up moving slot”,通過Pipeline調用Migrate命令,改善redis-trib的遷移速度。但這樣只是治標不治本,畢竟遷移的基本單位還是Key而不是 Slot。但因為Redis的save/bgsave都是實例級別,所以要想不改Redis源碼就獲得Slot的復制或遷移能力,還真不太好辦!
看看Codis作者的思路: “在RebornDB中我們會嘗試提供基于復制的遷移方式,也就是開始遷移時,記錄某slot的操作,然后在后臺開始同步到slave,當slave同步完后,開始將記錄的操作回放,回放差不多后,將master的寫入停止,追平后修改路由表,將需要遷移的slot切換成新的master,主從(半)同步復制,這個之前提到過。”
3.6 遷移故障恢復
由于無中心化的設計,數據遷移的進度等信息無處保存。如果遷移中發生失敗,則可能某一個Slot處于遷移中間狀態。再加上沒有進度信息的話,會給我們的恢復工作帶來很大麻煩。可以考慮重新啟用ZooKeeper,或者單獨使用一個Redis實例做全局信息存儲。
3.7 Slave冷備
對于Redis Cluster不會將請求轉發給Slave結點,造成Slave冷備的問題,可以靠Proxy做讀寫分離來解決,當然這樣會犧牲一部分的一致性。
4.優化方案總結
4.1 架構變遷
在解決上面各種問題時我們引入了三個組件:Proxy、Dashboard和Agent。這三個組件都擔當一定的職責,但這三個組件不一定非要對應部署三個子系統。根據需要,可以選擇去掉或合并來簡化設計。
4.1.1 Proxy組件
從上面問題解決方案的分析可以看出,Proxy層的保留還是有其必要性的: