Redis Cluster終於出了Stable,這讓人很是激動,等Stable很久了,所以還是先玩玩。
一. 集群簡單概念。
Redis 集群是一個可以在多個 Redis 節點之間進行數據共享的設施(installation)。
Redis 集群不支持那些需要同時處理多個鍵的 Redis 命令, 因為執行這些命令需要在多個 Redis 節點之間移動數據, 並且在高負載的情況下, 這些命令將降低 Redis 集群的性能, 並導致不可預測的行為。
Redis 集群通過分區(partition)來提供一定程度的可用性(availability): 即使集群中有一部分節點失效或者無法進行通訊, 集群也可以繼續處理命令請求。
Redis 集群提供了以下兩個好處:
- 將數據自動切分(split)到多個節點的能力。
- 當集群中的一部分節點失效或者無法進行通訊時, 仍然可以繼續處理命令請求的能力。
Redis 集群使用數據分片(sharding)而非一致性哈希(consistency hashing)來實現: 一個 Redis 集群包含 16384 個哈希槽(hash slot), 數據庫中的每個鍵都屬於這 16384 個哈希槽的其中一個, 集群使用公式 CRC16(key) % 16384 來計算鍵 key 屬於哪個槽, 其中 CRC16(key) 語句用於計算鍵 key 的 CRC16 校驗和 。
集群中的每個節點負責處理一部分哈希槽。 舉個例子, 一個集群可以有三個哈希槽, 其中:
- 節點 A 負責處理 0 號至 5500 號哈希槽。
- 節點 B 負責處理 5501 號至 11000 號哈希槽。
- 節點 C 負責處理 11001 號至 16384 號哈希槽。
這種將哈希槽分布到不同節點的做法使得用戶可以很容易地向集群中添加或者刪除節點。 比如說:
- 如果用戶將新節點 D 添加到集群中, 那么集群只需要將節點 A 、B 、 C 中的某些槽移動到節點 D 就可以了。
- 與此類似, 如果用戶要從集群中移除節點 A , 那么集群只需要將節點 A 中的所有哈希槽移動到節點 B 和節點 C , 然后再移除空白(不包含任何哈希槽)的節點 A 就可以了。
因為將一個哈希槽從一個節點移動到另一個節點不會造成節點阻塞, 所以無論是添加新節點還是移除已存在節點, 又或者改變某個節點包含的哈希槽數量, 都不會造成集群下線。
為了使得集群在一部分節點下線或者無法與集群的大多數(majority)節點進行通訊的情況下, 仍然可以正常運作, Redis 集群對節點使用了主從復制功能: 集群中的每個節點都有 1 個至 N 個復制品(replica), 其中一個復制品為主節點(master), 而其余的 N-1 個復制品為從節點(slave)。
在之前列舉的節點 A 、B 、C 的例子中, 如果節點 B 下線了, 那么集群將無法正常運行, 因為集群找不到節點來處理 5501 號至 11000號的哈希槽。
另一方面, 假如在創建集群的時候(或者至少在節點 B 下線之前), 我們為主節點 B 添加了從節點 B1 , 那么當主節點 B 下線的時候, 集群就會將 B1 設置為新的主節點, 並讓它代替下線的主節點 B , 繼續處理 5501 號至 11000 號的哈希槽, 這樣集群就不會因為主節點 B 的下線而無法正常運作了。
不過如果節點 B 和 B1 都下線的話, Redis 集群還是會停止運作。
Redis-cluster 架構圖如下:
架構細節:
(1)所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬.
(2)節點的fail是通過集群中超過半數的節點檢測失效時才生效.
(3)客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可
(4)redis-cluster把所有的物理節點映射到[0-16383]slot上,cluster 負責維護node<->slot<->value
二. Redis Cluster搭建使用
要讓集群正常工作至少需要3個主節點,在這里我們要創建6個redis節點,其中三個為主節點,三個為從節點,對應的redis節點的ip和端口對應關系如下(為了簡單演示都在同一台機器上面)
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
1. 下載最新版redis。
wget http://download.redis.io/releases/redis-3.0.0.tar.gz
2. 解壓,安裝
tar xf redis-3.0.0.tar.gz cd redis-3.0.0 make && make install
3.創建存放多個實例的目錄
mkdir /data/cluster -p cd /data/cluster mkdir 7000 7001 7002 7003 7004 7005
4.修改配置文件
cp redis-3.0.0/redis.conf /data/cluster/7000/
修改配置文件中下面選項
port 7000
daemonize yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
文件中的 cluster-enabled 選項用於開實例的集群模式, 而 cluster-conf-file 選項則設定了保存節點配置文件的路徑, 默認值為nodes.conf 。其他參數相信童鞋們都知道。節點配置文件無須人為修改, 它由 Redis 集群在啟動時創建, 並在有需要時自動進行更新。
修改完成后,把修改完成的redis.conf復制到7001-7005目錄下,並且端口修改成和文件夾對應。
5.分別啟動6個redis實例。
cd /data/cluster/7000 redis-server redis.conf cd /data/cluster/7001 redis-server redis.conf cd /data/cluster/7002 redis-server redis.conf cd /data/cluster/7003 redis-server redis.conf cd /data/cluster/7004 redis-server redis.conf cd /data/cluster/7005 redis-server redis.conf
查看進程否存在。
[root@redis-server 7005]# ps -ef | grep redis root 4168 1 0 11:49 ? 00:00:00 redis-server *:7000 [cluster] root 4176 1 0 11:49 ? 00:00:00 redis-server *:7001 [cluster] root 4186 1 0 11:50 ? 00:00:00 redis-server *:7002 [cluster] root 4194 1 0 11:50 ? 00:00:00 redis-server *:7003 [cluster] root 4202 1 0 11:50 ? 00:00:00 redis-server *:7004 [cluster] root 4210 1 0 11:50 ? 00:00:00 redis-server *:7005 [cluster] root 4219 4075 0 11:50 pts/2 00:00:00 grep redis
6.執行命令創建集群,首先安裝依賴,否則創建集群失敗。
yum install ruby rubygems -y
安裝gem-redis
下載地址:https://rubygems.org/gems/redis/versions/3.0.0
gem install -l redis-3.0.0.gem
復制集群管理程序到/usr/local/bin
cp redis-3.0.0/src/redis-trib.rb /usr/local/bin/redis-trib
創建集群:
redis-trib 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
命令的意義如下:
- 給定 redis-trib.rb 程序的命令是 create , 這表示我們希望創建一個新的集群。
- 選項 --replicas 1 表示我們希望為集群中的每個主節點創建一個從節點。
- 之后跟着的其他參數則是實例的地址列表, 我們希望程序使用這些地址所指示的實例來創建新集群。
簡單來說, 以上命令的意思就是讓 redis-trib 程序創建一個包含三個主節點和三個從節點的集群。
接着, redis-trib 會打印出一份預想中的配置給你看, 如果你覺得沒問題的話, 就可以輸入 yes , redis-trib 就會將這份配置應用到集群當中:
>>> Creating cluster Connecting to node 127.0.0.1:7000: OK Connecting to node 127.0.0.1:7001: OK Connecting to node 127.0.0.1:7002: OK Connecting to node 127.0.0.1:7003: OK Connecting to node 127.0.0.1:7004: OK Connecting to node 127.0.0.1:7005: OK >>> Performing hash slots allocation on 6 nodes... Using 3 masters: 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 Adding replica 127.0.0.1:7003 to 127.0.0.1:7000 Adding replica 127.0.0.1:7004 to 127.0.0.1:7001 Adding replica 127.0.0.1:7005 to 127.0.0.1:7002 M: 2774f156af482b4f76a5c0bda8ec561a8a1719c2 127.0.0.1:7000 slots:0-5460 (5461 slots) master M: 2d03b862083ee1b1785dba5db2987739cf3a80eb 127.0.0.1:7001 slots:5461-10922 (5462 slots) master M: 0456869a2c2359c3e06e065a09de86df2e3135ac 127.0.0.1:7002 slots:10923-16383 (5461 slots) master S: 37b251500385929d5c54a005809377681b95ca90 127.0.0.1:7003 replicates 2774f156af482b4f76a5c0bda8ec561a8a1719c2 S: e2e2e692c40fc34f700762d1fe3a8df94816a062 127.0.0.1:7004 replicates 2d03b862083ee1b1785dba5db2987739cf3a80eb S: 9923235f8f2b2587407350b1d8b887a7a59de8db 127.0.0.1:7005 replicates 0456869a2c2359c3e06e065a09de86df2e3135ac Can I set the above configuration? (type 'yes' to accept):
輸入 yes 並按下回車確認之后, 集群就會將配置應用到各個節點, 並連接起(join)各個節點 —— 也即是, 讓各個節點開始互相通訊:
Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join...... >>> Performing Cluster Check (using node 127.0.0.1:7000) M: 2774f156af482b4f76a5c0bda8ec561a8a1719c2 127.0.0.1:7000 slots:0-5460 (5461 slots) master M: 2d03b862083ee1b1785dba5db2987739cf3a80eb 127.0.0.1:7001 slots:5461-10922 (5462 slots) master M: 0456869a2c2359c3e06e065a09de86df2e3135ac 127.0.0.1:7002 slots:10923-16383 (5461 slots) master M: 37b251500385929d5c54a005809377681b95ca90 127.0.0.1:7003 slots: (0 slots) master replicates 2774f156af482b4f76a5c0bda8ec561a8a1719c2 M: e2e2e692c40fc34f700762d1fe3a8df94816a062 127.0.0.1:7004 slots: (0 slots) master replicates 2d03b862083ee1b1785dba5db2987739cf3a80eb M: 9923235f8f2b2587407350b1d8b887a7a59de8db 127.0.0.1:7005 slots: (0 slots) master replicates 0456869a2c2359c3e06e065a09de86df2e3135ac [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
一切正常輸出以下信息:
[OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
集群的客戶端
Redis 集群現階段的一個問題是客戶端實現很少。 以下是一些我知道的實現:
- redis-rb-cluster 是我(@antirez)編寫的 Ruby 實現, 用於作為其他實現的參考。 該實現是對 redis-rb 的一個簡單包裝, 高效地實現了與集群進行通訊所需的最少語義(semantic)。
- redis-py-cluster 看上去是 redis-rb-cluster 的一個 Python 版本, 這個項目有一段時間沒有更新了(最后一次提交是在六個月之前), 不過可以將這個項目用作學習集群的起點。
- 流行的 Predis 曾經對早期的 Redis 集群有過一定的支持, 但我不確定它對集群的支持是否完整, 也不清楚它是否和最新版本的 Redis 集群兼容 (因為新版的 Redis 集群將槽的數量從 4k 改為 16k 了)。
- Redis unstable 分支中的 redis-cli 程序實現了非常基本的集群支持, 可以使用命令 redis-cli -c 來啟動。
測試 Redis 集群比較簡單的辦法就是使用 redis-rb-cluster 或者 redis-cli , 接下來我們將使用 redis-cli 為例來進行演示:
[root@redis-server ~]# redis-cli -c -p 7001 127.0.0.1:7001> set name yayun OK 127.0.0.1:7001> get name "yayun" 127.0.0.1:7001>
我們可以看看還有哪些命令可以用:
[root@redis-server ~]# redis-trib help Usage: redis-trib <command> <options> <arguments ...> set-timeout host:port milliseconds add-node new_host:new_port existing_host:existing_port --master-id <arg> --slave fix host:port help (show this help) del-node host:port node_id import host:port --from <arg> check host:port call host:port command arg arg .. arg create host1:port1 ... hostN:portN --replicas <arg> reshard host:port --yes --to <arg> --from <arg> --slots <arg> For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster. [root@redis-server ~]#
可以看見有add-node,不用想了,肯定是添加節點。那么del-node就是刪除節點。還有check肯定就是檢查狀態了。
[root@redis-server ~]# redis-cli -p 7000 cluster nodes 2d03b862083ee1b1785dba5db2987739cf3a80eb 127.0.0.1:7001 master - 0 1428293673322 2 connected 5461-10922 37b251500385929d5c54a005809377681b95ca90 127.0.0.1:7003 slave 2774f156af482b4f76a5c0bda8ec561a8a1719c2 0 1428293672305 4 connected e2e2e692c40fc34f700762d1fe3a8df94816a062 127.0.0.1:7004 slave 2d03b862083ee1b1785dba5db2987739cf3a80eb 0 1428293674340 5 connected 0456869a2c2359c3e06e065a09de86df2e3135ac 127.0.0.1:7002 master - 0 1428293670262 3 connected 10923-16383 2774f156af482b4f76a5c0bda8ec561a8a1719c2 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460 9923235f8f2b2587407350b1d8b887a7a59de8db 127.0.0.1:7005 slave 0456869a2c2359c3e06e065a09de86df2e3135ac 0 1428293675362 6 connected [root@redis-server ~]#
可以看到7000-7002是master,7003-7005是slave。
故障轉移測試:
127.0.0.1:7001> KEYS * 1) "name" 127.0.0.1:7001> get name "yayun" 127.0.0.1:7001>
可以看見7001是正常的,並且獲取到了key,value,現在kill掉7000實例,再進行查詢。
[root@redis-server ~]# ps -ef | grep 7000 root 4168 1 0 11:49 ? 00:00:03 redis-server *:7000 [cluster] root 4385 4361 0 12:39 pts/3 00:00:00 grep 7000 [root@redis-server ~]# kill 4168 [root@redis-server ~]# ps -ef | grep 7000 root 4387 4361 0 12:39 pts/3 00:00:00 grep 7000 [root@redis-server ~]# redis-cli -c -p 7001 127.0.0.1:7001> get name "yayun" 127.0.0.1:7001>
可以正常獲取到value,現在看看狀態。
[root@redis-server ~]# redis-cli -c -p 7001 cluster nodes 2d03b862083ee1b1785dba5db2987739cf3a80eb 127.0.0.1:7001 myself,master - 0 0 2 connected 5461-10922 0456869a2c2359c3e06e065a09de86df2e3135ac 127.0.0.1:7002 master - 0 1428295271619 3 connected 10923-16383 37b251500385929d5c54a005809377681b95ca90 127.0.0.1:7003 master - 0 1428295270603 7 connected 0-5460 e2e2e692c40fc34f700762d1fe3a8df94816a062 127.0.0.1:7004 slave 2d03b862083ee1b1785dba5db2987739cf3a80eb 0 1428295272642 5 connected 2774f156af482b4f76a5c0bda8ec561a8a1719c2 127.0.0.1:7000 master,fail - 1428295159553 1428295157205 1 disconnected 9923235f8f2b2587407350b1d8b887a7a59de8db 127.0.0.1:7005 slave 0456869a2c2359c3e06e065a09de86df2e3135ac 0 1428295269587 6 connected [root@redis-server ~]#
原來的7000端口實例已經顯示fail,原來的7003是slave,現在自動提升為master。
關於更多的在線添加節點,刪除節點,以及對集群進行重新分片請參考官方文檔。
總結:
redis-cluster是個好東西,只是stable才出來不久,肯定坑略多,而且現在使用的人比較少,前期了解學習一下是可以的,生產環境肯定要慎重考慮。且需要進行嚴格的測試。生產環境中redis的集群可以考慮使用Twitter開源的twemproxy,以及豌豆莢開源的codis,這兩個項目都比較成熟,現在使用的公司很多。已經向業界朋友得到證實。后面也會寫博客介紹twemproxy和codis。
參考資料:
http://redis.readthedocs.org/en/latest/topic/cluster-tutorial.html
http://hot66hot.iteye.com/blog/2050676