redis集群之Codis

在大數據高併發場景下,單個 Redis 實例往往會顯得捉襟見肘。首先體現在內存上,單個 Redis 的內存不宜過大,內存太大會導致 rdb 文件過大,進一步導致主從同步時全量同步時間過長,在實例重啟恢復時也會消耗很長的數據加載時間,特別是在雲環境下,單個實例內存往往都是受限的。其次體現在 CPU 的利用率上,單個 Redis 實例只能利用單個核心,這單個核心要完成海量數據的存取和管理工作壓力會非常大。正是在這樣的大數據高併發的需求之下,Redis 集群方案應運而生。它可以將眾多小內存的 Redis 實例綜合起來,將分佈在多臺機器上的眾多 CPU 核心的計算能力聚集到一起,完成海量數據存儲和高併發讀寫操作。Codis 是 Redis 集群方案之一,令我們感到驕傲的是,它是中國人開發並開源的,來自前豌豆莢中間件團隊。絕大多數國內的開源項目都不怎麼靠譜,但是 Codis 非常靠譜。有了Codis 技術積累之後,項目「突頭人」劉奇又開發出來中國人自己的開源分佈式數據庫 ——TiDB,可以說 6 到飛起。從 Redis 的廣泛流行到 RedisCluster 的廣泛使用之間相隔了好多年,Codis 就是在這樣的市場空缺的機遇下發展出來的。大型公司有明確的 Redis 在線擴容需求,但是市面上沒有特別好的中間件可以做到這一點。

redis集群之Codis


  Codis 使用 Go 語言開發,它是一個代理中間件,它和 Redis 一樣也使用 Redis 協議對外提供服務,當客戶端向 Codis 發送指令時,Codis 負責將指令轉發到後面的 Redis 實例

來執行,並將返回結果再轉回給客戶端。Codis 上掛接的所有 Redis 實例構成一個 Redis 集群,當集群空間不足時,可以通過動態增加 Redis 實例來實現擴容需求。客戶端操縱 Codis 同操縱 Redis 幾乎沒有區別,還是可以使用相同的客戶端 SDK,不需要任何變化。因為 Codis 是無狀態的,它只是一個轉發代理中間件,這意味著我們可以啟動多個Codis 實例,供客戶端使用,每個 Codis 節點都是對等的。因為單個 Codis 代理能支撐的QPS 比較有限,通過啟動多個 Codis 代理可以顯著增加整體的 QPS 需求,還能起到容災功能,掛掉一個 Codis 代理沒關係,還有很多 Codis 代理可以繼續服務。

redis集群之Codis

Codis 分片原理
  Codis 要負責將特定的 key 轉發到特定的 Redis 實例,那麼這種對應關係 Codis 是如
何管理的呢?Codis 將所有的 key 默認劃分為 1024 個槽位(slot),它首先對客戶端傳過來的 key 進行 crc32 運算計算哈希值,再將 hash 後的整數值對 1024 這個整數進行取模得到一個餘數,這個餘數就是對應 key 的槽位。

redis集群之Codis

  每個槽位都會唯一映射到後面的多個 Redis 實例之一,Codis 會在內存維護槽位和Redis 實例的映射關係。這樣有了上面 key 對應的槽位,那麼它應該轉發到哪個 Redis 實例

就很明確了。
hash = crc32(command.key)
slot_index = hash % 1024
redis = slots[slot_index].redis
redis.do(command)
槽位數量默認是 1024,它是可以配置的,如果集群節點比較多,建議將這個數值配置大
一些,比如 2048、4096。
不同的 Codis 實例之間槽位關係如何同步?
  如果 Codis 的槽位映射關係只存儲在內存裡,那麼不同的 Codis 實例之間的槽位關係就無法得到同步。所以 Codis 還需要一個分佈式配置存儲數據庫專門用來持久化槽位關係。

Codis 開始使用 ZooKeeper,後來連 etcd 也一塊支持了。

redis集群之Codis


  Codis 將槽位關係存儲在 zk 中,並且提供了一個 Dashboard 可以用來觀察和修改槽位關係,當槽位關係變化時,Codis Proxy 會監聽到變化並重新同步槽位關係,從而實現多個

Codis Proxy 之間共享相同的槽位關係配置。
擴容
  剛開始 Codis 後端只有一個 Redis 實例,1024 個槽位全部指向同一個 Redis。然後一個 Redis 實例內存不夠了,所以又加了一個 Redis 實例。這時候需要對槽位關係進行調整,

將一半的槽位劃分到新的節點。這意味著需要對這一半的槽位對應的所有 key 進行遷移,遷移到新的 Redis 實例。

那 Codis 如果找到槽位對應的所有 key 呢?
  Codis 對 Redis 進行了改造,增加了 SLOTSSCAN 指令,可以遍歷指定 slot 下所有的key。Codis 通過 SLOTSSCAN 掃描出待遷移槽位的所有的 key,然後挨個遷移每個 key 到新的 Redis 節點。在遷移過程中,Codis 還是會接收到新的請求打在當前正在遷移的槽位上,因為當前槽位的數據同時存在於新舊兩個槽位中,Codis 如何判斷該將請求轉發到後面的哪個具體實例呢?Codis 無法判定遷移過程中的 key 究竟在哪個實例中,所以它採用了另一種完全不同的思路。當 Codis 接收到位於正在遷移槽位中的 key 後,會立即強制對當前的單個 key 進行遷移,遷移完成後,再將請求轉發到新的 Redis 實例。

slot_index = crc32(command.key) % 1024
if slot_index in migrating_slots:
do_migrate_key(command.key) # 強制執行遷移
redis = slots[slot_index].new_redis
else:
redis = slots[slot_index].redis
redis.do(command)
  我們知道 Redis 支持的所有 Scan 指令都是無法避免重複的,同樣 Codis 自定義的SLOTSSCAN 也是一樣,但是這並不會影響遷移。因為單個 key 被遷移一次後,在舊實例中它就徹底被刪除了,也就不可能會再次被掃描出來了。

自動均衡
  Redis 新增實例,手工均衡 slots 太繁瑣,所以 Codis 提供了自動均衡功能。自動均衡會在系統比較空閒的時候觀察每個 Redis 實例對應的 Slots 數量,如果不平衡,就會自動進行

遷移。
Codis 的代價
  Codis 給 Redis 帶來了擴容的同時,也損失了其它一些特性。因為 Codis 中所有的 key 分散在不同的 Redis 實例中,所以事務就不能再支持了,事務只能在單個 Redis 實例中完成。同樣 rename 操作也很危險,它的參數是兩個 key,如果這兩個 key 在不同的 Redis 實例中,rename 操作是無法正確完成的。Codis 的官方文檔中給出了一系列不支持的命令列表。同樣為了支持擴容,單個 key 對應的 value 不宜過大,因為集群的遷移的最小單位是key,對於一個 hash 結構,它會一次性使用 hgetall 拉取所有的內容,然後使用 hmset 放置到另一個節點。如果 hash 內部的 kv 太多,可能會帶來遷移卡頓。官方建議單個集合結構的總字節容量不要超過 1M。如果我們要放置社交關係數據,例如粉絲列表這種,就需要注意了,可以考慮分桶存儲,在業務上作折中。Codis 因為增加了 Proxy 作為中轉層,所有在網絡開銷上要比單個 Redis 大,畢竟數據包多走了一個網絡節點,整體在性能上要比單個 Redis 的性能有所下降。但是這部分性能損耗不是太明顯,可以通過增加 Proxy 的數量來彌補性能上的不足。Codis 的集群配置中心使用 zk 來實現,意味著在部署上增加了 zk 運維的代價,不過大部分互聯網企業內部都有 zk 集群,可以使用現有的 zk 集群使用即可。

Codis 的優點
  Codis 在設計上相比 Redis Cluster 官方集群方案要簡單很多,因為它將分佈式的問題交給了第三方 zk/etcd 去負責,自己就省去了複雜的分佈式一致性代碼的編寫維護工作。而Redis Cluster 的內部實現非常複雜,它為了實現去中心化,混合使用了複雜的 Raft 和Gossip 協議,還有大量的需要調優的配置參數,當集群出現故障時,維護人員往往不知道從何處著手。

MGET 指令的操作過程

redis集群之Codis


  mget 指令用於批量獲取多個 key 的值,這些 key 可能會分佈在多個 Redis 實例中。Codis 的策略是將 key 按照所分配的實例打散分組,然後依次對每個實例調用 mget 方法,最後將結果彙總為一個,再返回給客戶端。架構變遷Codis 作為非官方 Redis 集群方案,近幾年來它的結構一直在不斷變化,一方面當官方的 Redis 有變化的時候它要實時去跟進,另一方面它作為 Redis Cluster 的競爭方案之一,它還得持續提高自己的競爭力,給自己增加更多的官方集群所沒有的便捷功能。比如 Codis 有個特色的地方在於強大的 Dashboard 功能,能夠便捷地對 Redis 集群進行管理。這是 Redis 官方所欠缺的。另外 Codis 還開發了一個 Codis-fe(federation 聯邦) 工具,可以同時對多個 Codis 集群進行管理。在大型企業,Codis 集群往往會有幾十個,有這樣一個便捷的聯邦工具可以降低不少運維成本。

Codis 的尷尬
  Codis 不是 Redis 官方項目,這意味著它的命運會無比曲折,它總是要被官方 Redis 牽著牛鼻子走。當 Redis 官方提供了什麼功能它欠缺時,Codis 就會感到恐懼,害怕自己被市場甩掉,所以必須實時保持跟進。同時因為 Codis 總是要比 Redis 官方慢一拍,Redis 官方提供的最新功能,Codis 往往要等很久才能同步。比如現在 Redis 已經進入到 4.0 階段,提供了插件化 Redis-Module 支持,目前 Codis 還沒有提供解決方案。現在 Redis-Cluster 在業界已經逐漸流行起來,Codis 能否持續保持競爭力是個問題,我們看到 Codis 在不斷的差異化競爭,競爭的方法就體現在工具上,而不是內核,這個和官方的路線真是相反的,官方對工具無暇顧及,只提供基本的工具,其它完全交給第三方去開發。

Codis 的後臺管理
  後臺管理的界面非常友好,使用了最新的 BootStrap 前端框架。比較酷炫的是可以看到實時的 QPS 波動曲線。同時還支持服務器集群管理功能,可以增加分組、增加節點、執行自動均衡等指令,還可以直接查看所有 slot 的狀態,每個 slot 被分配到哪個 Redis 實例。


分享到:


相關文章: