Redis集群


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。

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

  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。

[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來創建集群。

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

7、help

8、參考

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM