Redis 集羣,顧名思義就是使用多個 Redis 節點構成的集羣,從而知足在數據量和併發數大的業務需求。面試
在單個 Redis 的節點實例下,存儲的數據量大和高併發的狀況下,內存很容易就暴漲。同時,一個 Redis 的節點,內存也是受限的,兩個緣由,一個是內存過大,在進行數據同步的時候,全量同步的時候會致使時間過長,會增長同步失敗的風險;另外一個緣由就是通常的 Redis 都是部署在雲服務器上的,這個也會受到CPU的使用率的影響。redis
因此,在面對着大數據量的時候,就會 Redis 集羣的方案來管理,同時也是把這麼多 Redis 實例的CPU計算能力聚集到一塊兒,從而完成關於大數據和高併發量的的讀寫操做。算法
Redis 的集羣解決方案有社區的,也有官方的,社區的解決方案有 Codis 和Twemproxy,Codis是由我國的豌豆莢團隊開源的,Twemproxy是Twitter團隊的開源的;官方的集羣解決方案就是 Redis Cluster,這是由 Redis 官方團隊來實現的。下面的列表能夠很明顯地表達出三者的不一樣點。服務器
很明顯codis優點比較大。網絡
Codis 是一個代理中間件,用的是 GO 語言開發的,以下圖,Codis 在系統的位置是這樣的。併發
Codis分爲四個部分,分別是Codis Proxy (codis-proxy)、Codis Dashboard (codis-config)、Codis Redis (codis-server)和ZooKeeper/Etcd.app
Codis就是起着一箇中間代理的做用,可以把全部的Redis實例當成一個來使用,在客戶端操做着SDK的時候和操做Redis的時候是同樣的,沒有差異。分佈式
由於Codis是一個無狀態的,因此能夠增長多個Codis來提高QPS,同時也能夠起着容災的做用。ide
在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中增長了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在Redis的基礎上的改造,因此在Codis上是不支持事務的,同時也會有一些命令行不支持,在官方的文檔上有(Codis不支持的命令)
官方的建議是單個集合的總容量不要超過1M,不然在遷移的時候會有卡頓感。在Codis中,增長了proxy來當中轉層,因此在網絡開銷上,是會比單個的Redis節點的性能有所降低的,因此這部分會有些的性能消耗。能夠增長proxy的數量來避免掉這塊的性能損耗。
思考一個問題:若是熟悉Redis中的MGET、MSET和MSETNX命令的話,就會知道這三個命令都是原子性的命令。可是,爲何Codis支持MGET和MSET,卻不支持MSETNX命令呢?
緣由以下: 在Codis中的MGET命令的原理是這樣的,先是在Redis中的各個實例裏獲取到符合的key,而後再彙總到Codis中,若是是MSETNX的話,由於key可能存在在多個Redis的實例中,若是某個實例的設值成功,而另外一個實例的設值不成功,從本質上講這是不成功的,可是分佈在多個實例中的Redis是沒有回滾機制的,因此會產生髒數據,因此MSETNX就是不能支持了。
SETNX key value
將key的值設爲value,而且僅當key不存在。
若給定的key已經存在,則SETNX不作任何操做。
SETNX 是SET if Not eXists的簡寫。
返回整數,具體爲
1,當 key 的值被設置
0,當 key 的值沒被設置
Codis是一個代理中間件,經過內存保存着槽位和實例節點之間的映射關係,槽位間的信息同步交給ZooKeeper來管理。其中不支持事務和官方的某些命令,緣由就是分佈多個的Redis實例沒有回滾機制和WAL,因此是不支持的。