總結:
1、簡介
Redis Cluster是一個高性能高可用的分佈式系統。由多個Redis實例組成的整體,數據按照Slot存儲分佈在多個Redis實例上,通過Gossip協議來進行節點之間通信。
1.1Redis集羣核心的目標:
在官方文檔Cluster Spec中,作者詳細介紹了Redis集羣爲什麼要設計成現在的樣子。最核心的目標有三個:
1、性能:這是Redis賴以生存的看家本領,增加集羣功能後當然不能對性能產生太大影響,所以Redis採取了P2P而非Proxy方式、Master與slave之間使用異步複製(異步replication)、客戶端重定向等設計,而犧牲了部分的一致性、使用性。
2、水平擴展:集羣的最重要能力當然是擴展,文檔中稱可以線性擴展到1000結點。
3、可用性:在Cluster推出之前,可用性要靠Sentinel保證。有了集羣之後也自動具有了Sentinel的監控和自動Failover能力。只要集羣中大多數master可達、且失效的master至少有一個slave可達時,集羣都可以繼續提供服務;同時“replicas migration”可以將那些擁有多個slaves的master的某個slave,遷移到沒有slave的master下,即將slaves的分佈在整個集羣相對平衡,盡力確保每個master都有一定數量的slave備份。
4、數據一致性:客戶端容忍一定程度的數據丟失,集羣儘可能保存Client write操作的數據,保證數據一致性。集羣將會儘可能(best-effort)保存客戶端write操作的數據;通常在failover期間,會有短暫時間內的數據丟失(因爲異步replication引起);當客戶端與少數派的節點處於網絡分區時(network partition),丟失數據的可能性會更高。(因爲節點有效性檢測、failover需要更長的時間)
Redis Cluster功能特點如下:
1)節點自動發現:所有的節點相互連接
2)集羣消息通信通過集羣總線通信,,集羣總線端口大小爲客戶端服務端口+10000,這個10000是固定值
3)節點與節點之間通過二進制協議進行通信
4)客戶端和集羣節點之間通信和通常一樣,通過文本協議進行
5)集羣節點不會代理查詢
6)數據按照Slot存儲分佈在多個Redis實例上
7)集羣節點掛掉會自動故障轉移
8)可以相對平滑擴/縮容節點
1.2 架構變化與CAP理論:
Redis Cluster集羣功能推出已經有一段時間了。在單機版的Redis中,每個Master之間是沒有任何通信的,所以我們一般在Jedis客戶端或者Codis這樣的代理中做Pre-sharding。按照CAP理論來說,單機版的Redis屬於保證CP(Consistency & Partition-Tolerancy)而犧牲A(Availability),也就說Redis能夠保證所有用戶看到相同的數據(一致性,因爲Redis不自動冗餘數據)和網絡通信出問題時,暫時隔離開的子系統能繼續運行(分區容忍性,因爲Master之間沒有直接關係,不需要通信),但是不保證某些結點故障時,所有請求都能被響應(可用性,某個Master結點掛了的話,那麼它上面分片的數據就無法訪問了)。
有了Cluster功能後,Redis從一個單純的NoSQL內存數據庫變成了分佈式NoSQL數據庫,CAP模型也從CP變成了AP。也就是說,通過自動分片和冗餘數據,Redis具有了真正的分佈式能力,某個結點掛了的話,因爲數據在其他結點上有備份,所以其他結點頂上來就可以繼續提供服務,保證了Availability。然而,也正因爲這一點,Redis無法保證曾經的強一致性了。這也是CAP理論要求的,三者只能取其二。
1.3 和單點操作變化
Cluster不能進行跨Nodes操作,也沒有nodes提供merge層代理。
Cluster中實現了一個稱爲“hash tags”的概念,每個key都可以包含一個自定義的“tags”,那麼在存儲時將根據tags計算此key應該分佈在哪個nodes上(而不是使用key計算,但是存儲層面仍然是key);此特性,可以強制某些keys被保存在同一個節點上,以便於進行“multikey”操作,比如“foo”和“{foo}.student”將會被保存在同一個node上。不過在人工對slots進行resharding期間,multikey操作可能不可用。
在Cluster環境下,將不支持SELECT命令,所有的key都將保存在默認的database中。
1.4 客戶端與Server角色
集羣中nodes負責存儲數據,保持集羣的狀態,包括keys與nodes的對應關係(內部其實爲slots與nodes對應關係)。nodes也能夠自動發現其他的nodes,檢測失效的節點,當某個master失效時還應該能將合適的slave提升爲master。
爲了達成這些行爲,集羣中的每個節點都通過TCP與其他所有nodes建立連接Nodes之間使用gossip協議(參見下文備註)向其他nodes傳播集羣信息,以達到自動發現的特性,通過發送ping來確認其他nodes工作正常,也會在合適的時機發送集羣的信息。當然在Failover時(包括人爲failover)也會使用Bus來傳播消息。
Nodes之間使用gossip協議(參見下文備註)向其他nodes傳播集羣信息,以達到自動發現的特性,通過發送ping來確認其他nodes工作正常,也會在合適的時機發送集羣的信息。當然在Failover時(包括人爲failover)也會使用Bus來傳播消息。
理論上,Client可以將請求發送給任意一個nodes,然後根據在根據錯誤信息轉發給合適的node,客戶端可以不用保存集羣的狀態信息,當然這種情況下性能比較低效,因爲Client可能需要2次TCP調用才能獲取key的結果,通常客戶端會緩存集羣中nodes與slots的映射關係,並在遇到“Redirected”錯誤反饋時,纔會更新本地的緩存。上,Client可以將請求發送給任意一個nodes,然後根據在根據錯誤信息轉發給合適的node,客戶端可以不用保存集羣的狀態信息,當然這種情況下性能比較低效,因爲Client可能需要2次TCP調用才能獲取key的結果,通常客戶端會緩存集羣中nodes與slots的映射關係,並在遇到“Redirected”錯誤反饋時,纔會更新本地的緩存。
2、集羣配置安裝
2.1 Redis Cluster 3.2.3安裝:
Redis的安裝很簡單:
mkdir /mnt/src && cd /mnt/src ;
wget http://download.redis.io/releases/redis-3.2.3.tar.gz
tar xzf redis-3.2.3.tar.gz
cd redis-3.2.3
make && make install
2.2 單機啓動:
#啓動redis
redis-server /mnt/redis-7001/redis.conf
#連接redis
redis-cli -a passwd -p 7001
#查看版本
redis-cli -a passwd -p 7001 info|grep 'redis_version'
#測試是否成功
登錄後執行:ping
收到:pong
2.3 redis集羣配置
要想開啓Redis Cluster模式,有幾項配置是必須的。此外爲了方便使用和後續的測試,我還額外做了一些配置:
- 綁定地址:bind 192.168.XXX.XXX。不能綁定到127.0.0.1或localhost,否則指導客戶端重定向時會報”Connection refused”的錯誤。
- 開啓Cluster:cluster-enabled yes :如果想在特定的Redis實例中啓用Redis羣集支持就設置爲yes。 否則,實例通常作爲獨立實例啓動。
- 集羣配置文件:cluster-config-file nodes-7000.conf。這個配置文件不是要我們去配的,而是Redis運行時保存配置的文件,所以我們也不可以修改這個文件。Redis羣集節點每次發生更改時自動保留羣集配置(基本上爲狀態)的文件,以便能夠 在啓動時重新讀取它。 該文件列出了羣集中其他節點,它們的狀態,持久變量等等。 由於某些消息的接收,通常會將此文件重寫並刷新到磁盤上。
- 集羣超時時間:cluster-node-timeout 15000。結點超時多久則認爲它宕機了。如果主節點超過指定的時間不可達,它將由其從屬設備進行故障切換。 此參數控制Redis羣集中的其他重要事項。 值得注意的是,每個無法在指定時間內到達大多數主節點的節點將停止接受查詢。
- 槽是否全覆蓋:cluster-require-full-coverage no。默認是yes,只要有結點宕機導致16384個槽沒全被覆蓋,整個集羣就全部停止服務,所以一定要改爲no。 如果將其設置爲yes,則默認情況下,如果key的空間的某個百分比未被任何節點覆蓋,則集羣停止接受寫入。 如果該選項設置爲no,則即使只處理關於keys子集的請求,羣集仍將提供查詢。
- 後臺運行:daemonize yes
- 輸出日誌:logfile “./redis.log”
- 監聽端口:port 7000
- cluster-slave-validity-factor <factor>:如果設置爲0,無論主設備和從設備之間的鏈路保持斷開連接的時間長短,從設備都將嘗試故障切換主設備。 如果該值爲正值,則計算最大斷開時間作爲節點超時值乘以此選項提供的係數,如果該節點是從節點,則在主鏈路斷開連接的時間超過指定的超時值時,它不會嘗試啓動故障切換。 例如,如果節點超時設置爲5秒,並且有效因子設置爲10,則與主設備斷開連接超過50秒的從設備將不會嘗試對其主設備進行故障切換。 請注意,如果沒有從服務器節點能夠對其進行故障轉移,則任何非零值都可能導致Redis羣集在主服務器出現故障後不可用。 在這種情況下,只有原始主節點重新加入集羣時,集羣纔會返回可用。
- cluster-migration-barrier <count>:主設備將保持連接的最小從設備數量,以便另一個從設備遷移到不受任何從設備覆蓋的主設備。有關更多信息,請參閱本教程中有關副本遷移的相應部分。
配置好後,根據我們的集羣規模,拷貝出來幾份同樣的配置文件,唯一不同的就是監聽端口,可以依次改爲7001、7002… 因爲Redis Cluster如果數據冗餘是1的話,至少要3個Master和3個Slave,所以我們拷貝出6個實例的配置文件。爲了避免相互影響,爲6個實例的配置文件建立獨立的文件夾。
2.4 redis-trib管理器
Redis作者應該是個Ruby愛好者,Ruby客戶端就是他開發的。這次集羣的管理功能沒有嵌入到Redis代碼中,於是作者又順手寫了個叫做redis-trib的管理腳本。redis-trib依賴Ruby和RubyGems,以及redis擴展。可以先用which命令查看是否已安裝ruby和rubygems,用gem list –local查看本地是否已安裝redis擴展。
最簡便的方法就是用apt或yum包管理器安裝RubyGems後執行gem install redis。如果網絡或環境受限的話,可以手動安裝RubyGems和redis擴展
#安裝rub管理工具rvm
gpg2 --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3
curl -L get.rvm.io | bash -s stable
find / -name rvm -print
#加載rvm
source /usr/local/rvm/scripts/rvm
#查看ruby版本
rvm list known
#安裝ruby,這裏我們選擇安裝2.5.1
rvm install 2.5.1
rvm use 2.5.1 --default
#安裝redis-trib.rb即redis集羣工具
gem install redis -v 3.3.5
#安裝完成後的目錄爲:
/mnt/src/redis-4.0.9/src/redis-trib.rb
2.5 啓動集羣服務
首先,啓動我們配置好的6個Redis實例。我們創建相關目錄,主文件夾是redis-cluster,在此文件夾下建立6個子文件夾,名稱分別是:7001,7002,7003,7004,7005,7006 該目錄以我們將在任何給定目錄內運行的實例的端口號命名。
mkdir -p /data/redis-cluster-{7001..7006} &&
touch /data/redis-cluster-{7001..7006}/redis.log
#啓動redis
redis-server /data/redis-7001/redis.conf
此時6個實例還沒有形成集羣,現在用redis-trb.rb管理腳本建立起集羣。可以看到,redis-trib默認用前3個實例作爲Master,後3個作爲Slave。因爲Redis基於Master-Slave做數據備份,而非像Cassandra或Hazelcast一樣不區分結點角色,自動複製並分配Slot的位置到各個結點。
#啓動集羣
/mnt/src/redis-4.0.9/src/redis-trib.rb create --replicas 1 10.26.25.115:7001 10.26.25.115:7002 10.26.25.115:7003 10.26.25.115:7004 10.26.25.115:7005 10.26.25.115:7006
至此,集羣就已經建立成功了!“貼心”的Redis還在utils/create-cluster下提供了一個create-cluster腳本,能夠創建出一個集羣,類似我們上面建立起的3主3從的集羣。
2.6 簡單測試
我們連接到集羣中的任意一個結點,啓動redis-cli時要加-c選項,存取兩個Key-Value感受一下Redis久違的集羣功能。
仔細觀察能夠注意到,redis-cli根據指示,不斷在7000和7002結點之前重定向跳轉。如果啓動時不加-c選項的話,就能看到以錯誤形式顯示出的MOVED重定向消息。
2.7 集羣重啓
目前redis-trib的功能還比較弱,需要重啓集羣的話先手動kill掉各個進程,然後重新啓動就可以了。這也有點太… 網上有人重啓後會碰到問題,我還比較幸運,這種“土鱉”的方式重啓試了兩次還沒發現問題。
[root@8gVm redis-3.0.4]# ps -ef | grep redis | awk '{print $2}' | xargs kill