為什麼大廠都喜歡用 Codis 來管理分佈式集群?

為什麼大廠都喜歡用 Codis 來管理分佈式集群?

前言

Redis 集群,顧名思義就是使用多個 Redis 節點構成的集群,從而滿足在數據量和併發數大的業務需求。

在單個 Redis 的節點實例下,存儲的數據量大和高併發的情況下,內存很容易就暴漲。同時,一個 Redis 的節點,內存也是受限的,兩個原因,一個是內存過大,在進行數據同步的時候,全量同步的時候會導致時間過長,會增加同步失敗的風險;另一個原因就是一般的 Redis 都是部署在雲服務器上的,這個也會受到CPU的使用率的影響。

所以,在面對著大數據量的時候,就會 Redis 集群的方案來管理,同時也是把這麼多 Redis 實例的CPU計算能力彙集到一起,從而完成關於大數據和高併發量的的讀寫操作。

目錄

  • Redis集群的方案有哪些?優缺點分別是什麼?
  • Codis集群的分片原理是怎麼樣的?
  • Codis集群的遷移方式和工具有哪些?
  • Codis為什麼很多命令行不支持?
  • 如果 Codis 集群正在遷移中,怎麼處理發送過來的讀寫請求?

正文

Redis 集群方案有哪些

Redis 的集群解決方案有社區的,也有官方的,社區的解決方案有 Codis 和Twemproxy,Codis是由我國的豌豆莢團隊開源的,Twemproxy是Twitter團隊的開源的;官方的集群解決方案就是 Redis Cluster,這是由 Redis 官方團隊來實現的。下面的列表可以很明顯地表達出三者的不同點。

Codis 集群

Codis 是一個代理中間件,用的是 GO 語言開發的,如下圖,Codis 在系統的位置是這樣的。

為什麼大廠都喜歡用 Codis 來管理分佈式集群?

Codis分為四個部分,分別是Codis Proxy (codis-proxy)、Codis Dashboard (codis-config)、Codis Redis (codis-server)和ZooKeeper/Etcd.

Codis就是起著一箇中間代理的作用,能夠把所有的Redis實例當成一個來使用,在客戶端操作著SDK的時候和操作Redis的時候是一樣的,沒有差別。

因為Codis是一個無狀態的,所以可以增加多個Codis來提升QPS,同時也可以起著容災的作用。

Codis 分片原理

在Codis中,Codis會把所有的key分成1024個槽,這1024個槽對應著的就是Redis的集群,這個在Codis中是會在內存中維護著這1024個槽與Redis實例的映射關係。這個槽是可以配置,可以設置成 2048 或者是4096個。看你的Redis的節點數量有多少,偏多的話,可以設置槽多一些。

Codis中key的分配算法,先是把key進行CRC32 後,得到一個32位的數字,然後再hash%1024後得到一個餘數,這個值就是這個key對應著的槽,這槽後面對應著的就是redis的實例。(可以思考一下,為什麼Codis很多命令行不支持,例如KEYS操作)

CRC32:CRC本身是“冗餘校驗碼”的意思,CRC32則表示會產生一個32bit(8位十六進制數)的校驗值。由於CRC32產生校驗值時源數據塊的每一個bit(位)都參與了計算,所以數據塊中即使只有一位發生了變化,也會得到不同的CRC32值。

Codis中Key的算法代碼如下

//Codis中Key的算法
hash = crc32(command.key)
slot_index = hash % 1024
redis = slots[slot_index].redis
redis.do(command)

Codis之間的槽位同步

思考一個問題:如果這個Codis節點只在自己的內存裡面維護著槽位與實例的關係,那麼它的槽位信息怎麼在多個實例間同步呢?

Codis把這個工作交給了ZooKeeper來管理,當Codis的Codis Dashbord 改變槽位的信息的時候,其他的Codis節點會監聽到ZooKeeper的槽位變化,會及時同步過來。如圖:

為什麼大廠都喜歡用 Codis 來管理分佈式集群?

Codis中的擴容

思考一個問題:在Codis中增加了Redis節點後,槽位的信息怎麼變化,原來的key怎麼遷移和分配?如果在擴容的時候,這個時候有新的key進來,Codis的處理策略是怎麼樣的?

因為Codis是一個代理中間件,所以這個當需要擴容Redis實例的時候,可以直接增加redis節點。在槽位分配的時候,可以手動指定Codis Dashbord來為新增的節點來分配特定的槽位。

在Codis中實現了自定義的掃描指令SLOTSSCAN,可以掃描指定的slot下的所有的key,將這些key遷移到新的Redis的節點中(話外語:這個是Codis定製化的其中一個好處)。

首先,在遷移的時候,會在原來的Redis節點和新的Redis裡都保存著遷移的槽位信息,在遷移的過程中,如果有key打進將要遷移或者正在遷移的舊槽位的時候,這個時候Codis的處理機制是,先是將這個key強制遷移到新的Redis節點中,然後再告訴Codis,下次如果有新的key的打在這個槽位中的話,那麼轉發到新的節點。代碼策略如下:

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)

自動均衡策略

面對著上面講的遷移策略,如果有成千上萬個節點新增進來,都需要我們手動去遷移嗎?那豈不是得累死啊。當然,Codis也是考慮到了這一點,所以提供了自動均衡策略。自動均衡策略是這樣的,Codis 會在機器空閒的時候,觀察Redis中的實例對應著的slot數,如果不平衡的話就會自動進行遷移。

Codis的犧牲

因為Codis在Redis的基礎上的改造,所以在Codis上是不支持事務的,同時也會有一些命令行不支持,在官方的文檔上有(Codis不支持的命令)

官方的建議是單個集合的總容量不要超過1M,否則在遷移的時候會有卡頓感。在Codis中,增加了proxy來當中轉層,所以在網絡開銷上,是會比單個的Redis節點的性能有所下降的,所以這部分會有些的性能消耗。可以增加proxy的數量來避免掉這塊的性能損耗。

MGET的過程

思考一個問題:如果熟悉Redis中的MGET、MSET和MSETNX命令的話,就會知道這三個命令都是原子性的命令。但是,為什麼Codis支持MGET和MSET,卻不支持MSETNX命令呢?

為什麼大廠都喜歡用 Codis 來管理分佈式集群?

原因如下: 在Codis中的MGET命令的原理是這樣的,先是在Redis中的各個實例裡獲取到符合的key,然後再彙總到Codis中,如果是MSETNX的話,因為key可能存在在多個Redis的實例中,如果某個實例的設值成功,而另一個實例的設值不成功,從本質上講這是不成功的,但是分佈在多個實例中的Redis是沒有回滾機制的,所以會產生髒數據,所以MSETNX就是不能支持了。

Codis集群總結

  • Codis是一個代理中間件,通過內存保存著槽位和實例節點之間的映射關係,槽位間的信息同步交給ZooKeeper來管理。
  • 不支持事務和官方的某些命令,原因就是分佈多個的Redis實例沒有回滾機制和WAL,所以是不支持的.

Redis深度歷險:核心原理與應用實戰

https://juejin.im/book/5afc2e5f6fb9a07a9b362527/section/5b029e5e5188254266432000

推薦一篇大規模的Codis集群治理文章

大規模 codis 集群的治理與實踐

https://link.juejin.im/?target=https%3A%2F%2Fcloud.tencent.com%2Fdeveloper%2Farticle%2F1006262

原文鏈接:https://juejin.im/post/5c132b076fb9a04a08218eef


分享到:


相關文章: