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

spacedong發表於2018-12-14

前言

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

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

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

目錄

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

正文

Redis 叢集方案有哪些

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

- Codis Twemproxy Redis Cluster
resharding without restarting cluster Yes No Yes
pipeline Yes Yes No
hash tags for multi-key operations Yes Yes Yes
multi-key operations while resharding Yes No(details)
Redis clients supporting Any clients Any clients Clients have to support cluster protocol

Codis 叢集

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

image

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的節點數量有多少,偏多的話,可以設定槽多一些。

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

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

CodisKey的演算法程式碼如下

//Codis中Key的演算法
hash = crc32(command.key)
slot_index = hash % 1024
redis = slots[slot_index].redis
redis.do(command)
複製程式碼

Codis之間的槽位同步

思考一個問題:如果這個Codis節點只在自己的記憶體裡面維護著槽位與例項的關係,那麼它的槽位資訊怎麼在多個例項間同步呢?

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

image

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的犧牲

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

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

MGET的過程

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

image

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

Codis叢集總結

  • Codis是一個代理中介軟體,通過記憶體儲存著槽位和例項節點之間的對映關係,槽位間的資訊同步交給ZooKeeper來管理。
  • 不支援事務和官方的某些命令,原因就是分佈多個的Redis例項沒有回滾機制和WAL,所以是不支援的.

本文參考資料:

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

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

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

更多幹貨,歡迎也可以關注我的公眾號:spacedong

image

相關文章