1. 程式人生 > >Redis叢集這篇就夠了

Redis叢集這篇就夠了

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。

注意,如果想要叢集按照你想的那樣工作,那麼叢集中的每個節點應該:

  1. 正常的客戶端通訊埠(通常是6379)用於和所有可到達叢集的所有客戶端通訊
  2. 叢集匯流排埠(the client port + 10000)必須對所有的其它節點是可到達的

也就是,要想叢集正常工作,叢集中的每個節點需要做到以下兩點:

  1. 正常的客戶端通訊埠(通常是6379)必須對所有的客戶端都開放,換言之,所有的客戶端都可以訪問
  2. 叢集匯流排埠(客戶端通訊埠 + 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。

[[email protected] cluster-test]# ls
[[email protected] cluster-test]# cp -R 7005 7006
[[email protected] cluster-test]# vi 7006/redis.conf
[[email protected] cluster-test]# cd 7006
[[email protected] 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來建立叢集。

  1. 進入utils/create-cluster,可以看README
  2. create-cluster start
  3. create-cluster create

7、help

8、參考

https://redis.io/topics/cluster-tutorial