Redis回顧
Redis支持的數據結構
- 字符串(String)
- 哈希(Hash)
- 列表(List)
- 集合(Set)
- 有序集合(Sorted Set)位數組
- 支持針對score作范圍查詢
- HyperLogLog
- 做基數統計的算法
Redis支持的操作
- 基本操作發布/訂閱
- Set get add push pop…
- Pipeline操作
- 事務
- 事務支持不完整。不提供回滾命令。
Redis適用場景
- KV存儲
- 緩存(TTL LRU...)消息中間件
- LRU是redis達到maxmemory之后,可選的key刪除策略。
- 分布式鎖
Redis集群實現方式
實現基礎——分區
- 分區是分割數據到多個Redis實例的處理過程,因此每個實例只保存key的一個子集。
- 通過利用多台計算機內存的和值,允許我們構造更大的數據庫。
- 通過多核和多台計算機,允許我們擴展計算能力;通過多台計算機和網絡適配器,允許我們擴展網絡帶寬。
集群的幾種實現方式
- 客戶端分片
- 基於代理的分片
- 路由查詢
客戶端分片
- 由客戶端決定key寫入或者讀取的節點。
- 包括jedis在內的一些客戶端,實現了客戶端分片機制。
原理如下所示:

特性
- 優點
- 簡單,性能高。
- 缺點
- 業務邏輯與數據存儲邏輯耦合
- 可運維性差
- 多業務各自使用redis,集群資源難以管理
- 不支持動態增刪節點
基於代理的分片
- 客戶端發送請求到一個代理,代理解析客戶端的數據,將請求轉發至正確的節點,然后將結果回復給客戶端。
- 開源方案
- Twemproxy
- codis
基本原理如下所示:

特性
- 透明接入Proxy 的邏輯和存儲的邏輯是隔離的。
- 業務程序不用關心后端Redis實例,切換成本低。
- 代理層多了一次轉發,性能有所損耗。
路由查詢
- 將請求發送到任意節點,接收到請求的節點會將查詢請求發送到正確的節點上執行。
- 開源方案
- Redis-cluster
基本原理如下所示:

集群的挑戰
- 涉及多個key的操作通常是不被支持的。涉及多個key的redis事務不能使用。
- 舉例來說,當兩個set映射到不同的redis實例上時,你就不能對這兩個set執行交集操作。
- 不能保證集群內的數據均衡。
- 分區的粒度是key,如果某個key的值是巨大的set、list,無法進行拆分。
- 增加或刪除容量也比較復雜。
- redis集群需要支持在運行時增加、刪除節點的透明數據平衡的能力。
Redis集群各種方案原理
Twemproxy
- Proxy-based
- twtter開源,C語言編寫,單線程。
- 支持 Redis 或 Memcached 作為后端存儲。
Twemproxy高可用部署架構

Twemproxy特性
- 支持失敗節點自動刪除
- 與redis的長連接,連接復用,連接數可配置
- 自動分片到后端多個redis實例上支持redis pipelining 操作
- 多種hash算法:能夠使用不同的分片策略和散列函數
- 支持一致性hash,但是使用DHT之后,從集群中摘除節點時,不會進行rehash操作
- 可以設置后端實例的權重
- 支持狀態監控
- 支持select切換數據庫
redis的管道(Pipelining)操作是一種異步的訪問模式,一次發送多個指令,不同步等待其返回結果。這樣可以取得非常好的執行效率。調用方法如下:
@Test public void testPipelined() { Jedis jedis = new Jedis("localhost"); Pipeline pipeline = jedis.pipelined(); long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { pipeline.set("p" + i, "p" + i); } List<Object> results = pipeline.syncAndReturnAll(); long end = System.currentTimeMillis(); System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds"); jedis.disconnect(); }
Twemproxy不足
- 性能低:代理層損耗 && 本身效率低下
- Redis功能支持不完善
- 不支持針對多個值的操作,比如取sets的子交並補等(MGET 和 DEL 除外)
- 不支持Redis的事務操作
- 出錯提示還不夠完善
- 集群功能不夠完善
- 僅作為代理層使用
- 本身不提供動態擴容,透明數據遷移等功能
- 失去維護
- 最近一次提交在一年之前。Twitter內部已經不再使用。
Redis Cluster
- Redis官網推出,可線性擴展到1000個節點
- 無中心架構
- 一致性哈希思想
- 客戶端直連redis服務,免去了proxy代理的損耗
Redis Cluster模型

Redis-cluster原理
- Hash slot。集群內的每個redis實例監聽兩個tcp端口,6379(默認)用於服務客戶端查詢,16379(默認服務端口 + 10000)用於集群內部通信。
- key的空間被分到16384個hash slot里;
- 計算key屬於哪個slot,CRC16(key) & 16384。
- 節點間狀態同步:gossip協議,最終一致性。節點間通信使用輕量的二進制協議,減少帶寬占用。
Gossip 算法又被稱為反熵(Anti-Entropy),熵是物理學上的一個概念,代表雜亂無章,而反熵就是在雜亂無章中尋求一致,這充分說明了 Gossip 的特點:在一個有界網絡中,每個節點都隨機地與其他節點通信,經過一番雜亂無章的通信,最終所有節點的狀態都會達成一致。每個節點可能知道所有其他節點,也可能僅知道幾個鄰居節點,只要這些節可以通過網絡連通,最終他們的狀態都是一致的,當然這也是疫情傳播的特點。
簡單的描述下這個協議,首先要傳播謠言就要有種子節點。種子節點每秒都會隨機向其他節點發送自己所擁有的節點列表,以及需要傳播的消息。任何新加入的節點,就在這種傳播方式下很快地被全網所知道。這個協議的神奇就在於它從設計開始就沒想到信息一定要傳遞給所有的節點,但是隨着時間的增長,在最終的某一時刻,全網會得到相同的信息。當然這個時刻可能僅僅存在於理論,永遠不可達。
Redis-cluster請求路由方式
- Redis-Cluster借助客戶端實現了混合形式的路由查詢
查詢路由並非直接從一個redis節點到另外一個redis,而是借助客戶端轉發到正確的節點。根據客戶端的實現方式,可以分為以下兩種:包括Jedis在內的許多redis client,已經實現了對Redis Cluster的支持。- dummy client
- smart client
查詢路由的流程如下所示:

Redis cluster采用這種架構的考慮:
- 減少redis實現的復雜度
- 降低客戶端等待的時間。Smart client可以在客戶端緩存 slot 與 redis節點的映射關系,當接收到 MOVED 響應時,會修改緩存中的映射關系。請求時會直接發送到正確的節點上,減少一次交互。
Redis Cluster特性
- 高性能
- 支持動態擴容,對業務透明
- 具備Sentinel的監控和自動Failover能力
Redis Cluster不足
- 官方未提供圖形管理工具,運維比較復雜要求客戶端必須支持cluster協議
- 比如數據遷移,純手動操作,先分配slot,再將slot中對應的key遷移至新的節點。
- Redis命令支持不完整
- 對於批量的指令,如mget支持不完整;不支持事務;不支持數據庫切換,只能使用0號數據庫……
- 集群管理與數據存儲耦合
- 比如如果集群管理有bug,需要升級整個redis。
Codis
- 豌豆莢開源的proxy-based的redis集群方案
- 支持透明的擴/縮容
- 運維友好同類產品中最全面的redis命令支持
- GUI監控界面和管理工具
Codis部署拓撲

Codis數據存儲
- 數據根據key分布到1024個slot內
- Key的slotid計算方法 crc32(key) % 1024
- 每個 codis-server 負責一部分數據
- 比如后端有3組codis-server,每個server負責的slot范圍可以這樣設置:
- group0 0-300
- group1 301-600
- group2 601-1023
Codis模塊簡介
- Codis Server
- 基於 redis-2.8.21 分支開發。增加了額外的數據結構,以支持 slot 有關的操作以及數據遷移指令。
- Codis Proxy
- 客戶端連接的 Redis 代理服務, 實現了 Redis 協議。
- 對於同一個業務集群而言,可以同時部署多個 codis-proxy 實例
- 不同 codis-proxy 之間由 codis-dashboard 保證狀態同步。
- Codis Dashboard
- 集群管理工具,支持 codis-proxy、codis-server 的添加、刪除,以及據遷移等操作。
- 所有對集群的修改都必須通過 codis-dashboard 完成。
- Codis Admin
- 集群管理的命令行工具。
- 可用於控制 codis-proxy、codis-dashboard 狀態以及訪問外部存儲。
- Codis FE
- 集群管理界面。
- Codis HA
- 為集群提供高可用。
- 依賴 codis-dashboard 實例,自動抓取集群各個組件的狀態;
- 會根據當前集群狀態自動生成主從切換策略,並在需要時通過 codis-dashboard 完成主從切換。
- Storage
- 為集群狀態提供外部存儲。
- 目前僅提供了 Zookeeper 和 Etcd 兩種實現,但是提供了抽象的 interface 可自行擴展。
Codis主從切換
- Codis-HA:自動切換主從的工具。
- 通過RESTful API從 codis-dashboard 中拉取集群狀態
- 默認以 5s 為周期
- 該工具會在檢測到 master 掛掉的時候主動應用主從切換策略,向codis-dashboard發送主從切換命令。
- 對自動主從切換的支持比較弱
- 主從切換的限制較多,必須在系統其他組件狀態健康,且距離上次主從同步數據在一定時間間隔內才可以執行。
- 多slave場景,提升其中的一個slave為master,其他的slave仍然會從舊的master同步數據,需要管理員手動操作。
Codis數據遷移流程
- 前提:Codis單條數據遷移是原子操作
- 單條數據遷移過程:
- 隨機選取指定 slot 中的一個 <K,V>
- 將這個<K,V>傳輸給另外一個 codis-server
- 傳輸成功后,把本地的這個 <K,V>刪除
遷移過程如下所示:

遷移過程中,Codis-dashboard與proxy通過zk通信,路由表信息全部存放在zk,保證所有proxy的視圖一致。
Codis如何保證數據遷移過程的正確及透明?
- codis-config 在實際修改slot狀態之前,會確保所有的 proxy 收到這個遷移請求。
- 遷移過程中, 如果客戶端請求 slot 的 key 數據,proxy 會將請求轉發到group2上。
- proxy會先在group1上強行執行一次 MIGRATE key 將這個鍵值提前遷移過來,
- 然后再到group2上正常讀取
Codis VS Redis
對比參數 | Codis | Redis-cluster |
---|---|---|
Redis版本 | 基於2.8分支開發 | >= 3.0 |
部署 | 較復雜。 | 簡單 |
運維 | Dashboard,運維方便。 | 運維人員手動通過命令操作。 |
監控 | 可在Dashboard里監控當前redis-server節點情況,較為便捷。 | 不提供監控功能。 |
組織架構 | Proxy-Based, 類中心化架構,集群管理層與存儲層解耦。 | P2P模型,gossip協議負責集群內部通信。去中心化 |
伸縮性 | 支持動態伸縮。 | 支持動態伸縮 |
主節點失效處理 | 自動選主。 | 自動選主。 |
數據遷移 | 簡單。支持透明遷移。 | 需要運維人員手動操作。支持透明遷移。 |
升級 | 基於redis 2.8分支開發,后續升級不能保證;Redis-server必須是此版本的codis,無法使用新版本redis的增強特性。 | Redis官方推出,后續升級可保證。 |
可靠性 | 經過線上服務驗證,可靠性較高。 | 新推出,坑會比較多。遇到bug之后需要等官網升級。 |
- 理論上,redis-cluster的性能更高,單次請求的延時低。另外,經過實測,兩種架構后端單台redis-server的條件下,TPS基本沒有差別。
- Codis的高可用依賴jodis,或者使用LVS進行高可用部署。
我們公司選擇Codis的原因
- Redis-cluster沒有成熟的應用案例
- Codis支持的命令更加豐富
- 遷移至redis cluster需要修改現有實現;而遷移到codis沒這么麻煩,只要使用的redis命令在codis支持的范圍之內,只要修改一下配置即可接入。
- Codis提供的運維工具更加友好
Codis簡單壓測
單台Codis Server壓測結果

三台Codis-Server的集群壓測結果

壓測結論
- 針對codis集群壓測過程中,后端codis server平均CPU占用約為70%~80%。
- 單台codis的TPS在6w~7w左右,集群的TPS在17w左右,可以達到近乎線性擴展的能力
- 測試期間后台codis-server的負載沒有跑滿,仍然具有繼續壓測的潛力。
- 測試期間proxy機器負載低,可以支撐更多后端codis server實例。
使用codis注意事項
- 不支持的命令
- 防止key沖突
- 建議現有業務接入codis時,加入項目前綴以作區分。
- 使用spring data redis操作codis時,只能使用RedisTemplate系列接口,Cache系列接口不可用
- Cache系列的接口使用了redis的事務指令,事務在Codis以及Redis Cluster中均未提供支持。
- 避免key value巨大的數據
- 吞吐量提升不明顯
- 可能造成各codis實例資源占用不均衡