前言
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 | 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
在系統的位置是這樣的。
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
中增加了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
中的MGET
命令的原理是這樣的,先是在Redis
中的各個例項裡獲取到符合的key
,然後再彙總到Codis
中,如果是MSETNX
的話,因為key
可能存在在多個Redis
的例項中,如果某個例項的設值成功,而另一個例項的設值不成功,從本質上講這是不成功的,但是分佈在多個例項中的Redis
是沒有回滾機制的,所以會產生髒資料,所以MSETNX
就是不能支援了。
Codis
叢集總結
Codis
是一個代理中介軟體,通過記憶體儲存著槽位和例項節點之間的對映關係,槽位間的資訊同步交給ZooKeeper
來管理。- 不支援事務和官方的某些命令,原因就是分佈多個的
Redis
例項沒有回滾機制和WAL,所以是不支援的.
本文參考資料:
推薦一篇大規模的Codis叢集治理文章
更多幹貨,歡迎也可以關注我的公眾號:spacedong