Redis cluster tutorial
Redis叢集提供一種方式自動将資料分布在多個Redis節點上。
Redis Cluster provides a way to run a Redis installation where data is automatically sharded across multiple Redis nodes.
1、Redis叢集TCP端口(Redis Cluster TCP ports)
每個Redis叢集中的節點都需要打開兩個TCP連接配接。一個連接配接用于正常的給Client提供服務,比如6379,還有一個額外的端口(通過在這個端口号上加10000)作為資料端口,比如16379。第二個端口(本例中就是16379)用于叢集總線,這是一個用二進制協定的點對點通信信道。這個叢集總線(Cluster bus)用于節點的失敗偵測、配置更新、故障轉移授權,等等。用戶端從來都不應該嘗試和這些叢集總線端口通信,它們隻應該和正常的Redis指令端口進行通信。注意,確定在你的防火牆中開放着兩個端口,否則,Redis叢集節點之間将無法通信。
指令端口和叢集總線端口的偏移量總是10000。
注意,如果想要叢集按照你想的那樣工作,那麼叢集中的每個節點應該:
- 正常的用戶端通信端口(通常是6379)用于和所有可到達叢集的所有用戶端通信
- 叢集總線端口(the client port + 10000)必須對所有的其它節點是可到達的
也就是,要想叢集正常工作,叢集中的每個節點需要做到以下兩點:
- 正常的用戶端通信端口(通常是6379)必須對所有的用戶端都開放,換言之,所有的用戶端都可以通路
- 叢集總線端口(用戶端通信端口 + 10000)必須對叢集中的其它節點開放,換言之,其它任意節點都可以通路
如果你沒有開放TCP端口,你的叢集可能不會像你期望的那樣工作。叢集總線用一個不同的二進制協定通信,用于節點之間的資料交換
2、Redis叢集資料分片(Redis Cluster data sharding)
Redis叢集不同一緻性哈希,它用一種不同的分片形式,在這種形式中,每個key都是一個概念性(hash slot)的一部分。
There are 16384 hash slots in Redis Cluster, and to compute what is the hash slot of a given key, we simply take the CRC16 of the key modulo 16384.
Redis叢集中有16384個hash slots,為了計算給定的key應該在哪個hash slot上,我們簡單地用這個key的CRC16值來對16384取模。(即:key的CRC16 % 16384)
Every node in a Redis Cluster is responsible for a subset of the hash slots
Redis叢集中的每個節點負責一部分hash slots,假設你的叢集有3個節點,那麼:
- Node A contains hash slots from 0 to 5500
- Node B contains hash slots from 5501 to 11000
- Node C contains hash slots from 11001 to 16383
允許添加和删除叢集節點。比如,如果你想增加一個新的節點D,那麼久需要從A、B、C節點上删除一些hash slot給到D。同樣地,如果你想從叢集中删除節點A,那麼會将A上面的hash slots移動到B和C,當節點A上是空的時候就可以将其從叢集中完全删除。
因為将hash slots從一個節點移動到另一個節點并不需要停止其它的操作,添加、删除節點以及更改節點所維護的hash slots的百分比都不需要任何停機時間。也就是說,移動hash slots是并行的,移動hash slots不會影響其它操作。
Redis支援多個key操作,隻要這些key在一個單個指令中執行(或者一個事務,或者Lua腳本執行),那麼它們就屬于相同的hash slot。你也可以用hash tags倆強制多個key都在相同的hash slot中。
3、Redis叢集主從模式(Redis Cluster master-slave model)
In order to remain available when a subset of master nodes are failing or are not able to communicate with the majority of nodes, Redis Cluster uses a master-slave model where every hash slot has from 1 (the master itself) to N replicas (N-1 additional slaves nodes).
當部分master節點失敗了,或者不能夠和大多數節點通信的時候,為了保持可用,Redis叢集用一個master-slave模式,這樣的話每個hash slot就有1到N個副本。
在我們的例子中,叢集有A、B、C三個節點,如果節點B失敗了,那麼5501-11000之間的hash slot将無法提供服務。然而,當我們給每個master節點添加一個slave節點以後,我們的叢集最終會變成由A、B、C三個master節點和A1、B1、C1三個slave節點組成,這個時候如果B失敗了,系統仍然可用。節點B1是B的副本,如果B失敗了,叢集會将B1提升為新的master,進而繼續提供服務。然而,如果B和B1同時失敗了,那麼整個叢集将不可用。
4、Redis叢集一緻性保證(Redis Cluster consistency guarantees)
Redis Cluster is not able to guarantee strong consistency. In practical terms this means that under certain conditions it is possible that Redis Cluster will lose writes that were acknowledged by the system to the client.
Redis叢集不能保證強一緻性。換句話說,Redis叢集可能會丢失一些寫操作。The first reason why Redis Cluster can lose writes is because it uses asynchronous replication.
Redis叢集可能丢失寫的第一個原因是因為它用異步複制。
寫可能是這樣發生的:
- 用戶端寫到master B
- master B回複用戶端OK
- master B将這個寫操作廣播給它的slaves B1、B2、B3
正如你看到的那樣,B沒有等到B1、B2、B3确認就回複用戶端了,也就是說,B在回複用戶端之前沒有等待B1、B2、B3的确認,這對應Redis來說是一個潛在的風險。是以,如果用戶端寫了一些東西,B也确認了這個寫操作,但是在它将這個寫操作發給它的slaves之前它當機了,随後其中一個slave(沒有收到這個寫指令)可能被提升為新的master,于是這個寫操作就永遠丢失了。
這和大多數配置為每秒重新整理一次資料到磁盤的情況是一樣的。你可以通過強制資料庫在回複用戶端以前重新整理資料,但是這樣做的結果會導緻性能很低,這就相當于同步複制了。
基本上,需要在性能和一緻性之間做一個權衡。
如果絕對需要的話,Redis叢集也是支援同步寫的,這是通過WAIT指令實作的,這使得丢失寫的可能性大大降低。然而,需要注意的是,Redis叢集沒有實作強一緻性,即使用同步複制,因為總是有更複雜的失敗場景使得一個沒有接受到這個寫操作的slave當選為新的master。(however note that Redis Cluster does not implement strong consistency even when synchronous replication is used: it is always possible under more complex failure scenarios that a slave that was not able to receive the write is elected as master.)
另一個值得注意的場景,即Redis叢集将會丢失寫操作,這發生在一個網絡分區中,在這個分區中,用戶端與少數執行個體(包括至少一個主機)隔離。
假設這樣一個例子,有一個叢集有6個節點,分别由A、B、C、A1、B1、C1組成,三個masters三個slaves,有一個用戶端我們叫Z1。在分區發生以後,可能分區的一邊是A、C、A1、B1、C1,另一邊有B和Z1。此時,Z1仍然可用寫資料到B,如果網絡分區的時間很短,那麼叢集可能繼續正常工作,而如果分區的時間足夠長以至于B1在多的那一邊被提升為master,那麼這個時候Z1寫到B上的資料就會丢失。
什麼意思呢?簡單的來說就是,本來三主三從在一個網絡分區中,突然網絡分區發生,于是一邊是A、C、A1、B1、C1,另一邊是B和Z1,這時候Z1往B中寫資料,于此同時另一邊(即A、C、A1、B1、C1)認為B已經挂了,于是将B1提升為master,當分區回複的時候,由于B1變成了master,是以B就成了slave,于是B就要丢棄它自己原有的資料而從B1那裡同步資料,于是乎先去Z1寫到B的資料就丢失了。
注意,有一個最大視窗,這是Z1能夠向B寫的最大數量:如果時間足夠的話,分區的多數的那一邊已經選舉完成,選擇一個slave成為master,此時,所有在少數的那一邊的master節點将停止接受寫。
也就說說,有一個最大視窗的設定項,它決定了Z1在那種情況下能夠向B發送多數寫操作:如果分隔的時間足夠長,多數的那邊已經選舉slave成為新的master,此後少數那邊的所有master節點将不再接受寫操作。
在Redis叢集中,這個時間數量是一個非常重要的配置指令,它被稱為node timeout。在超過node timeout以後,一個master節點被認為已經失敗了,并且選擇它的一個副本接替master。類似地,如果在過了node timeout時間以後,沒有一個master能夠和其它大多數的master通信,那麼整個叢集都将停止接受寫操作。
After node timeout has elapsed, a master node is considered to be failing, and can be replaced by one of its replicas. Similarly after node timeout has elapsed without a master node to be able to sense the majority of the other master nodes, it enters an error state and stops accepting writes.
5、Redis叢集配置參數(Redis Cluster configuration parameters)
- cluster-enabled <yes/no>: 如果是yes,表示啟用叢集,否則以單例模式啟動
- cluster-config-file <filename>: 可選,這不是一個使用者可編輯的配置檔案,這個檔案是Redis叢集節點自動持久化每次配置的改變,為了在啟動的時候重新讀取它。
- cluster-node-timeout <milliseconds>: 逾時時間,叢集節點不可用的最大時間。如果一個master節點不可到達超過了指定時間,則認為它失敗了。注意,每一個在指定時間内不能到達大多數master節點的節點将停止接受查詢請求。
- cluster-slave-validity-factor <factor>: 如果設定為0,則一個slave将總是嘗試故障轉移一個master。如果設定為一個正數,那麼最大失去連接配接的時間是node timeout乘以這個factor。
- cluster-migration-barrier <count>: 一個master和slave保持連接配接的最小數量(即:最少與多少個slave保持連接配接),也就是說至少與其它多少slave保持連接配接的slave才有資格成為master。
- cluster-require-full-coverage <yes/no>: 如果設定為yes,這也是預設值,如果key space沒有達到百分之多少時停止接受寫請求。如果設定為no,将仍然接受查詢請求,即使它隻是請求部分key。
6、建立并使用Redis叢集(Creating and using a Redis Cluster)
為了建立叢集,首先我必須有一些以叢集模式(cluster mode)運作的Redis執行個體。
下面是一個最小的Redis叢集配置檔案:
1 port 7000
2 cluster-enabled yes
3 cluster-config-file nodes.conf
4 cluster-node-timeout 5000
5 appendonly yes
正如你看到的那樣,啟用叢集模式隻需要配置cluster-enabled指令為yes即可。每個執行個體都包含一個檔案,這個檔案存儲該節點的配置,模式是nodes.conf。這個檔案從來不會被手動建立,它是Redis叢集執行個體啟動的時候生成的,并且每次在需要的時候自動更新。
Note that the minimal cluster that works as expected requires to contain at least three master nodes.
最小的叢集至少需要3個master節點。這裡,我們為了測試,用三主三從。
mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005
cd 7000
touch redis.conf
cp 7000/redis.conf 7001/
cp 7000/redis.conf 7002/
cp 7000/redis.conf 7003/
cp 7000/redis.conf 7004/
cp 7000/redis.conf 7005/
cp ../redis-4.0.9/src/redis-server ./
現在的目錄結構應該是這樣的:
修改端口,依次啟動各個執行個體:
cd 7000
./redis-server 7000/redis.conf
cd 7001
./redis-server 7001/redis.conf
cd 7002
./redis-server 7002/redis.conf
cd 7003
./redis-server 7003/redis.conf
cd 7004
./redis-server 7004/redis.conf
正如你看到的那樣,每個Redis執行個體都有一個ID,在節點的整個生命周期中這個唯一的code是不會變的,我們把它叫做Node ID
6.1、建立叢集
最簡單的實作是用redis-trib工具,它在src目錄下。它是一個ruby程式,是以需要先安裝ruby。
yum install ruby
yum install rubygems
gem install redis
這個時候可能會報錯,如下:
于是,要更新Ruby版本
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
curl -sSL https://get.rvm.io | bash -s stable
source /etc/profile.d/rvm.sh
rvm list known
rvm install 2.4.1
接下來,建立叢集
gem install redis
./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
這裡,我們使用create指令來建立一個新的叢集。選項--replicas 1表示我們想為每個master指定一個slave。其餘參數是需要加到叢集的執行個體位址。
我們可以看到,7000、7001、7002是master,7004是7000的slave,7005是7001的slave,7003是7002的slave。
cluster nodes指令的輸出格式是這樣的:
- Node ID
- ip:port
- flags: master, slave, myself, fail, ...
- if it is a slave, the Node ID of the master
- Time of the last pending PING still waiting for a reply.
- Time of the last PONG received.
- Configuration epoch for this node (see the Cluster specification).
- Status of the link to this node.
- Slots served...
接下來,設定一個key試試:
6.2、添加一個新節點(Adding a new node)
添加一個新節點基本上就是添加一個空節點,然後将一些資料移動到其中,在這種情況下,它是一個新的master,或者你明确的設定它作為副本,那麼這種情況下它就是一個slave。
[root@ecs-d6b3-0002 cluster-test]# ls
[root@ecs-d6b3-0002 cluster-test]# cp -R 7005 7006
[root@ecs-d6b3-0002 cluster-test]# vi 7006/redis.conf
[root@ecs-d6b3-0002 cluster-test]# cd 7006
[root@ecs-d6b3-0002 7006]# ../redis-server redis.conf
現在,我們用redis-trib來添加一個節點到已存在的叢集:
./redis-trib.rb add-node 127.0.0.1:7006 127.0.0.1:7000
As you can see I used the add-node command specifying the address of the new node as first argument, and the address of a random existing node in the cluster as second argument.
正如你看到的那樣,add-node指令的第一個參數是新節點的位址,第二個參數是已存在的叢集中的任意節點位址。事實上,redis-trib隻是發了一個cluster meet消息給這個節點。
(PS:我在操作的過程中發現,不用add-node指令,直接啟動7006以後它就直接加入叢集了,不知道是不是因為我是同一台機器上操作,或者是因為隻有一個叢集,我猜測可能是因為這是一個僞叢集,哈哈哈,先不管了。。。)
6.3、添加一個節點作為副本(Adding a new node as a replica)
./redis-trib.rb add-node --slave 127.0.0.1:7006 127.0.0.1:7000
6.4、删除一個節點(Removing a node)
./redis-trib del-node 127.0.0.1:7000 `<node-id>`
./redis-trib.rb del-node 127.0.0.1:7006 7c7b7f68bc56bf24cbb36b599d2e2d97b26c5540
6.5、重新分片(Resharding the cluster)
./redis-trib.rb reshard 127.0.0.1:7000
./redis-trib.rb reshard --from <node-id> --to <node-id> --slots <number of slots> --yes <host>:<port>
6.6、殺死Redis執行個體
pkill -9 redis
6.7、停止叢集/删除叢集
删除叢集就是依次删除叢集中的所有節點,但在此之前需要将帶删除的節點上的資料遷移到其它節點上,是以需要重新分片。
後來想想,其實也沒有必要停止叢集
6.8、用create-cluster建立叢集
之前我們建立叢集用的是redis-trib,現在我們用create-cluster來建立叢集。
- 進入utils/create-cluster,可以看README
- create-cluster start
- create-cluster create
7、help
8、參考
https://redis.io/topics/cluster-tutorial