
什么是Redis Cluster
Redis Cluster讓多個Redis節(jié)點以集群的方式存儲數(shù)據(jù),數(shù)據(jù)會自動分片保存。
在提供分區(qū)的情況下,Redis Cluster可以保證一定程度的可用性,即在一個節(jié)點掛掉的情況下,整個集群依然可以正常提供服務。但是出現(xiàn)大多數(shù)master節(jié)點都掛的情況時,整個集群也會一起掛掉。
綜上,Redis Cluster提供了兩個特性:
1、數(shù)據(jù)的自動分區(qū)存儲
2、高可用性。即部分節(jié)點掛掉不影響集群的可用性。
TCP端口
每個Redis Cluster節(jié)點會占用兩個TCP端口,一個監(jiān)聽客戶端的請求,默認是6379,另外一個在前一個端口加上10000,比如16379,來監(jiān)聽數(shù)據(jù)的請求。
節(jié)點和節(jié)點之間會監(jiān)聽第二個端口,用一套二進制協(xié)議來通信。
節(jié)點之間會通過套協(xié)議來進行失敗檢測,配置更新,failover認證等等。
為了保證節(jié)點之間正常的訪問,需要注意防火墻的配置。
數(shù)據(jù)分片
Redis 集群沒有并使用傳統(tǒng)的一致性哈希來分配數(shù)據(jù),而是采用另外一種叫做哈希槽的方式來分配。
Redis Cluster默認分配了16384個槽,在分配的時候,會采用根據(jù)CRC16(key) % 16384的計算結(jié)果來將數(shù)據(jù)分配到節(jié)點上。
Redis Cluster中的每個節(jié)點會負責存儲數(shù)據(jù)槽的一個子集,假設有三個節(jié)點
- A節(jié)點存儲0到5500
- B節(jié)點存儲5501到11000
- C節(jié)點存儲11001到16383
根據(jù)上面的公式計算的結(jié)果,如果大于0小于5500,那么數(shù)據(jù)將被存儲到A節(jié)點上。
這樣的特性使得增加或者刪除節(jié)點非常得容易。假如要加入節(jié)點D,只需要將A,B,C的部分槽轉(zhuǎn)移到D上;同理,要刪除C,只需要將C的槽轉(zhuǎn)移到A和B上,當C節(jié)點的數(shù)據(jù)被轉(zhuǎn)移,清空后,就可以刪除C節(jié)點了。
在移動哈希槽的時候,不需要停機維護。
如果一個指令涉及到多個key時,只要這些key屬于同一個槽,Redis Cluster允許同時操作他們。用戶可以通過hash tags強制將多個key歸到一個槽中。
主從模式
Redis Cluster支持主從模式,從節(jié)點會保存主節(jié)點的全部數(shù)據(jù)
在上面的例子中,假設B節(jié)點掛了,那么5501-11000的槽將不能夠提供服務。
現(xiàn)在給上面的節(jié)點添加一個從節(jié)點,那么我們將有A,B,C,A1,B1,C1六個節(jié)點。
當B節(jié)點掛掉,B1將會升級為主節(jié)點,系統(tǒng)的可用性將不受到影響。
但是,如果B和B1節(jié)點同時掛掉的話,集群依然會掛掉。
數(shù)據(jù)一致性
Redis Cluster不會保證數(shù)據(jù)的強一致性。在部分場景下可能會出現(xiàn)數(shù)據(jù)丟失的現(xiàn)象。
會丟數(shù)據(jù)的第一個原因是Redis Cluster中主節(jié)點異步向從節(jié)點拷貝數(shù)據(jù)。
在客戶寫入數(shù)據(jù)時,會經(jīng)過下面的過程。
- 寫到主節(jié)點B
- 主節(jié)點B返回"OK"
- 主節(jié)點將數(shù)據(jù)復制到從節(jié)點B1,B2,B3
可以看到,主節(jié)點B在向客戶返回"OK"的之前,并不會等待B1,B2,B3確認寫入的回應。這樣主要是為了性能的考慮,不能讓客戶等待太長時間。
假設一種情況,client寫入數(shù)據(jù),節(jié)點B確認這次寫入,但是在步驟3之前掛掉了,B1成為了新的主節(jié)點。那么這次寫入就永遠失效了。
可以看到Redis的高性能也是需要一定的代價的。Redis Cluster采取的方案實際上是數(shù)據(jù)一致性和高性能的折衷。
Redis Cluster支持同步寫入,見WAIT操作,這樣會降低丟數(shù)據(jù)的可能性,但是在復雜的情況下,還是可能出現(xiàn)從節(jié)點沒有接到寫入,然后成為主節(jié)點。
假設有A,B,C,A1,B1,C1六個節(jié)點,三主三從,現(xiàn)在有一個客戶Z1。
現(xiàn)在網(wǎng)絡出現(xiàn)隔離,A,C,A1,B1,C1之間是聯(lián)通的;Z1和B是聯(lián)通的。Z1向B寫入成功,如果網(wǎng)絡很快恢復,那么一切正常,但是沒有很快恢復的情況下,B1會成為新的主節(jié)點,Z1向B的寫入就丟失了。
上面說了這么多,總結(jié)一句話,
<strong><big>即使有了集群,也不能將Redis用做高可靠性的存儲。</big></strong>
部署相關
配置選項
- cluster-enabled<yes/no> 如果設置為yes,redis實例會開啟cluster模式;如果設置為no,standalone模式
- cluster-config-file<filename>
- cluster-node-timeout<milliseconds> 一個節(jié)點被判定為掛掉的最大時間
- **cluster-slave-validity-factor <factor>
**:如果設置為0,從節(jié)點會一直failover主節(jié)點 - **cluster-migration-barrier <count>
**:主節(jié)點對應最小從節(jié)點的個數(shù) - **cluster-require-full-coverage <yes/no>
**:如果設為yes,一旦部分哈希槽不能訪問到,集群會停止接受寫入;如果設為no,即使在部分哈希槽不能被訪問到的情況下,集群依然后接受寫入。
創(chuàng)建集群
我用的是mac系統(tǒng),其他類unix系統(tǒng)應該差不多。
先裝一些工具軟件,下載一下源碼。redis的使用的第三方庫的源碼都包含在項目中了,這簡直是編譯安裝的福音。
brew install ruby
brew install gem
gem install redis
git clone https://github.com/antirez/redis
cd redis
make
然后進入目錄utils/create-cluster,執(zhí)行命令
./create-cluster start
看到輸出,建立了6個單獨的實例。
Starting 30001
Starting 30002
Starting 30003
Starting 30004
Starting 30005
Starting 30006
執(zhí)行命令
./create-cluster create
看到輸出
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:30001
127.0.0.1:30002
127.0.0.1:30003
Adding replica 127.0.0.1:30004 to 127.0.0.1:30001
Adding replica 127.0.0.1:30005 to 127.0.0.1:30002
Adding replica 127.0.0.1:30006 to 127.0.0.1:30003
M: 6687e151e76830f8544ab9527bbb65b8d9063de4 127.0.0.1:30001
slots:0-5460 (5461 slots) master
M: ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 127.0.0.1:30002
slots:5461-10922 (5462 slots) master
M: 4a808670aae774e49343aac8526b641d5aa79de8 127.0.0.1:30003
slots:10923-16383 (5461 slots) master
S: cee189bce870d87c318840a1e6a2d5045806acba 127.0.0.1:30004
replicates 6687e151e76830f8544ab9527bbb65b8d9063de4
S: 68838ae12c64ccfccfe55b607a99454678d35ea4 127.0.0.1:30005
replicates ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0
S: 075ced4cef8e8ec06076b5193efb81812d72dc1b 127.0.0.1:30006
replicates 4a808670aae774e49343aac8526b641d5aa79de8
Can I set the above configuration? (type 'yes' to accept): yes
輸入yes后,建立集群
可以看到30001,30002,30003作為主節(jié)點
30004,30005,30006分別作為其從節(jié)點。
執(zhí)行腳本
來看一下代碼
start
在執(zhí)行start指令的時候,實際上是執(zhí)行了下面的操作。
../../src/redis-server --port $PORT --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes
可以看到##配置選項里面項目都設置了一個值,來看一下--cluster-config-file這個配置里的文件長啥樣。挑一個主節(jié)點看看
cat nodes-30003.conf
ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 127.0.0.1:30002 master - 0 1473069793428 2 connected 5461-10922
4a808670aae774e49343aac8526b641d5aa79de8 127.0.0.1:30003 master - 0 1473069793529 3 connected 10923-16383
075ced4cef8e8ec06076b5193efb81812d72dc1b 127.0.0.1:30006 slave 4a808670aae774e49343aac8526b641d5aa79de8 0 1473069793428 6 connected
cee189bce870d87c318840a1e6a2d5045806acba 127.0.0.1:30004 slave 6687e151e76830f8544ab9527bbb65b8d9063de4 0 1473069793428 4 connected
6687e151e76830f8544ab9527bbb65b8d9063de4 127.0.0.1:30001 myself,master - 0 0 1 connected 0-5460
68838ae12c64ccfccfe55b607a99454678d35ea4 127.0.0.1:30005 slave ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 0 1473069793024 5 connected
vars currentEpoch 6 lastVoteEpoch 0
再挑一個從節(jié)點看看
cat nodes-30006.conf
4a808670aae774e49343aac8526b641d5aa79de8 127.0.0.1:30003 master - 0 1473069792576 3 connected 10923-16383
cee189bce870d87c318840a1e6a2d5045806acba 127.0.0.1:30004 myself,slave 6687e151e76830f8544ab9527bbb65b8d9063de4 0 0 4 connected
68838ae12c64ccfccfe55b607a99454678d35ea4 127.0.0.1:30005 slave ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 0 1473069793485 5 connected
ea2e8ab4d48f5b0a1527dc0ac6d4f14e80f6aca0 127.0.0.1:30002 master - 0 1473069793079 2 connected 5461-10922
075ced4cef8e8ec06076b5193efb81812d72dc1b 127.0.0.1:30006 slave 4a808670aae774e49343aac8526b641d5aa79de8 0 1473069792578 3 connected
6687e151e76830f8544ab9527bbb65b8d9063de4 127.0.0.1:30001 master - 0 1473069793079 1 connected 0-5460
vars currentEpoch 6 lastVoteEpoch 0
可以看到這里面維護了一個狀態(tài)表,紀錄了所有節(jié)點的
信息,狀態(tài)等等。這個文件會將集群的信息保存下來,用于集群下次的啟動,根據(jù)節(jié)點收到的信息,這個文件會經(jīng)常改變,并保存。
create
執(zhí)行create指令時,實際上是執(zhí)行了下面的操作
../../src/redis-trib.rb create --replicas 1 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006
redis-trib.rb是redis作者寫的一個工具。
用用看
redis-cli -c -p 30001
127.0.0.1:30001> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:30003
OK
127.0.0.1:30003> set hello world
-> Redirected to slot [866] located at 127.0.0.1:30001
OK
127.0.0.1:30001> get foo
-> Redirected to slot [12182] located at 127.0.0.1:30003
"bar"
127.0.0.1:30003> get hellp
-> Redirected to slot [8380] located at 127.0.0.1:30002
(nil)
127.0.0.1:30002> get hello
-> Redirected to slot [866] located at 127.0.0.1:30001
"world"
127.0.0.1:30001>
可以看到,集群會根據(jù)key值所在的槽重定向至對應的節(jié)點。