最近研究redis-cluster,正好搭建了一個環境,遇到了很多坑,系統的總結下,等到redis3 release出來后,換掉memCache 集群.
一:關于redis cluster
1:redis cluster的現狀
reids-cluster計劃在redis3.0中推出,可以看作者antirez的聲明:http://antirez.com/news/49 (ps:跳票了好久,今年貌似加快速度了),目前的最新版本是redis3 beta2(2.9.51).
作者的目標:Redis Cluster will support up to ~1000 nodes. 贊...
目前redis支持的cluster特性(已親測):
1):節點自動發現
2):slave->master 選舉,集群容錯
3):Hot resharding:在線分片
4):進群管理:cluster xxx
5):基于配置(nodes-port.conf)的集群管理
6):ASK 轉向/MOVED 轉向機制.
2:redis cluster 架構
1)redis-cluster架構圖
架構細節:
(1)所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬.
(2)節點的fail是通過集群中超過半數的節點檢測失效時才生效.
(3)客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可
(4)redis-cluster把所有的物理節點映射到[0-16383]slot上,cluster 負責維護node<->slot<->value
2) redis-cluster選舉:容錯
(1)領著選舉過程是集群中所有master參與,如果半數以上master節點與master節點通信超過(cluster-node-timeout),認為當前master節點掛掉.
(2):什么時候整個集群不可用(cluster_state:fail),當集群不可用時,所有對集群的操作做都不可用,收到((error) CLUSTERDOWN The cluster is down)錯誤
a:如果集群任意master掛掉,且當前master沒有slave.集群進入fail狀態,也可以理解成進群的slot映射[0-16383]不完成時進入fail狀態.
b:如果進群超過半數以上master掛掉,無論是否有slave集群進入fail狀態.
二:redis cluster的使用
1:安裝redis cluster
1):安裝redis-cluster依賴:redis-cluster的依賴庫在使用時有兼容問題,在reshard時會遇到各種錯誤,請按指定版本安裝.
(1)確保系統安裝zlib,否則gem install會報(no such file to load -- zlib)
(1)安裝ruby:version(1.9.2)
- # ruby1.9.2
- cd /path/ruby
- ./configure -prefix=/usr/local/ruby
- make
- make install
- sudo cp ruby /usr/local/bin
(2)安裝rubygem:version(1.8.16)
(3)安裝gem-redis:version(3.0.0)
- gem install redis --version 3.0.0
- #由于源的原因,可能下載失敗,就手動下載下來安裝
- #download地址:http://rubygems.org/gems/redis/versions/3.0.0
- gem install -l /data/soft/redis-3.0.0.gem
2)安裝redis-cluster
- cd /path/redis
- make
- sudo cp /opt/redis/src/redis-server /usr/local/bin
- sudo cp /opt/redis/src/redis-cli /usr/local/bin
- sudo cp /opt/redis/src/redis-trib.rb /usr/local/bin
2:配置redis cluster
1)redis配置文件結構:
使用包含(include)把通用配置和特殊配置分離,方便維護.
2)redis通用配置.
- #GENERAL
- daemonize no
- tcp-backlog 511
- timeout 0
- tcp-keepalive 0
- loglevel notice
- databases 16
- dir /opt/redis/data
- slave-serve-stale-data yes
- #slave只讀
- slave-read-only yes
- #not use default
- repl-disable-tcp-nodelay yes
- slave-priority 100
- #打開aof持久化
- appendonly yes
- #每秒一次aof寫
- appendfsync everysec
- #關閉在aof rewrite的時候對新的寫操作進行fsync
- no-appendfsync-on-rewrite yes
- auto-aof-rewrite-min-size 64mb
- lua-time-limit 5000
- #打開redis集群
- cluster-enabled yes
- #節點互連超時的閥值
- cluster-node-timeout 15000
- cluster-migration-barrier 1
- slowlog-log-slower-than 10000
- slowlog-max-len 128
- notify-keyspace-events ""
- hash-max-ziplist-entries 512
- hash-max-ziplist-value 64
- list-max-ziplist-entries 512
- list-max-ziplist-value 64
- set-max-intset-entries 512
- zset-max-ziplist-entries 128
- zset-max-ziplist-value 64
- activerehashing yes
- client-output-buffer-limit normal 0 0 0
- client-output-buffer-limit slave 256mb 64mb 60
- client-output-buffer-limit pubsub 32mb 8mb 60
- hz 10
- aof-rewrite-incremental-fsync yes
3)redis特殊配置.
- #包含通用配置
- include /opt/redis/redis-common.conf
- #監聽tcp端口
- port 6379
- #最大可用內存
- maxmemory 100m
- #內存耗盡時采用的淘汰策略:
- # volatile-lru -> remove the key with an expire set using an LRU algorithm
- # allkeys-lru -> remove any key accordingly to the LRU algorithm
- # volatile-random -> remove a random key with an expire set
- # allkeys-random -> remove a random key, any key
- # volatile-ttl -> remove the key with the nearest expire time (minor TTL)
- # noeviction -> don't expire at all, just return an error on write operations
- maxmemory-policy allkeys-lru
- #aof存儲文件
- appendfilename "appendonly-6379.aof"
- #rdb文件,只用于動態添加slave過程
- dbfilename dump-6379.rdb
- #cluster配置文件(啟動自動生成)
- cluster-config-file nodes-6379.conf
- #部署在同一機器的redis實例,把<span style="font-size: 1em; line-height: 1.5;">auto-aof-rewrite搓開,防止瞬間fork所有redis進程做rewrite,占用大量內存</span>
- auto-aof-rewrite-percentage 80-100
3:cluster 操作
cluster集群相關命令,更多redis相關命令見文檔:http://redis.readthedocs.org/en/latest/
- 集群
- CLUSTER INFO 打印集群的信息
- CLUSTER NODES 列出集群當前已知的所有節點(node),以及這些節點的相關信息。
- 節點
- CLUSTER MEET <ip> <port> 將 ip 和 port 所指定的節點添加到集群當中,讓它成為集群的一份子。
- CLUSTER FORGET <node_id> 從集群中移除 node_id 指定的節點。
- CLUSTER REPLICATE <node_id> 將當前節點設置為 node_id 指定的節點的從節點。
- CLUSTER SAVECONFIG 將節點的配置文件保存到硬盤里面。
- 槽(slot)
- CLUSTER ADDSLOTS <slot> [slot ...] 將一個或多個槽(slot)指派(assign)給當前節點。
- CLUSTER DELSLOTS <slot> [slot ...] 移除一個或多個槽對當前節點的指派。
- CLUSTER FLUSHSLOTS 移除指派給當前節點的所有槽,讓當前節點變成一個沒有指派任何槽的節點。
- CLUSTER SETSLOT <slot> NODE <node_id> 將槽 slot 指派給 node_id 指定的節點,如果槽已經指派給另一個節點,那么先讓另一個節點刪除該槽>,然后再進行指派。
- CLUSTER SETSLOT <slot> MIGRATING <node_id> 將本節點的槽 slot 遷移到 node_id 指定的節點中。
- CLUSTER SETSLOT <slot> IMPORTING <node_id> 從 node_id 指定的節點中導入槽 slot 到本節點。
- CLUSTER SETSLOT <slot> STABLE 取消對槽 slot 的導入(import)或者遷移(migrate)。
- 鍵
- CLUSTER KEYSLOT <key> 計算鍵 key 應該被放置在哪個槽上。
- CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的鍵值對數量。
- CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 個 slot 槽中的鍵。
4:redis cluster 運維操作
1)初始化并構建集群
(1)#啟動集群相關節點(必須是空節點),指定配置文件和輸出日志
- redis-server /opt/redis/conf/redis-6380.conf > /opt/redis/logs/redis-6380.log 2>&1 &
- redis-server /opt/redis/conf/redis-6381.conf > /opt/redis/logs/redis-6381.log 2>&1 &
- redis-server /opt/redis/conf/redis-6382.conf > /opt/redis/logs/redis-6382.log 2>&1 &
- redis-server /opt/redis/conf/redis-7380.conf > /opt/redis/logs/redis-7380.log 2>&1 &
- redis-server /opt/redis/conf/redis-7381.conf > /opt/redis/logs/redis-7381.log 2>&1 &
- redis-server /opt/redis/conf/redis-7382.conf > /opt/redis/logs/redis-7382.log 2>&1 &
(2):使用自帶的ruby工具(redis-trib.rb)構建集群
- #redis-trib.rb的create子命令構建
- #--replicas 則指定了為Redis Cluster中的每個Master節點配備幾個Slave節點
- #節點角色由順序決定,先master之后是slave(為方便辨認,slave的端口比master大1000)
- redis-trib.rb create --replicas 1 10.10.34.14:6380 10.10.34.14:6381 10.10.34.14:6382 10.10.34.14:7380 10.10.34.14:7381 10.10.34.14:7382
(3):檢查集群狀態,
最后輸出如下信息,沒有任何警告或錯誤,表示集群啟動成功并處于ok狀態
- [OK] All nodes agree about slots configuration.
- >>> Check for open slots...
- >>> Check slots coverage...
- [OK] All 16384 slots covered.
2):添加新master節點
(1)添加一個master節點:創建一個空節點(empty node),然后將某些slot移動到這個空節點上,這個過程目前需要人工干預
a):根據端口生成配置文件(ps:establish_config.sh是我自己寫的輸出配置腳本)
b):啟動節點
c):加入空節點到集群
add-node 將一個節點添加到集群里面, 第一個是新節點ip:port, 第二個是任意一個已存在節點ip:port
node:新節點沒有包含任何數據, 因為它沒有包含任何slot。新加入的加點是一個主節點, 當集群需要將某個從節點升級為新的主節點時, 這個新節點不會被選中
d):為新節點分配slot
- redis-trib.rb reshard 10.10.34.14:6386
- #根據提示選擇要遷移的slot數量(ps:這里選擇500)
- How many slots do you want to move (from 1 to 16384)? 500
- #選擇要接受這些slot的node-id
- What is the receiving node ID? f51e26b5d5ff74f85341f06f28f125b7254e61bf
- #選擇slot來源:
- #all表示從所有的master重新分配,
- #或者數據要提取slot的master節點id,最后用done結束
- Please enter all the source node IDs.
- Type 'all' to use all the nodes as source nodes for the hash slots.
- Type 'done' once you entered all the source nodes IDs.
- Source node #1:all
- #打印被移動的slot后,輸入yes開始移動slot以及對應的數據.
- #Do you want to proceed with the proposed reshard plan (yes/no)? yes
- #結束
3):添加新的slave節點
a):前三步操作同添加master一樣
b)第四步:redis-cli連接上新節點shell,輸入命令:cluster replicate 對應master的node-id
note:在線添加slave 時,需要dump整個master進程,并傳遞到slave,再由 slave加載rdb文件到內存,rdb傳輸過程中Master可能無法提供服務,整個過程消耗大量io,小心操作.
例如本次添加slave操作產生的rdb文件
- -rw-r--r-- 1 root root 34946 Apr 17 18:23 dump-6386.rdb
- -rw-r--r-- 1 root root 34946 Apr 17 18:23 dump-7386.rdb
4):在線reshard 數據:
對于負載/數據均勻的情況,可以在線reshard slot來解決,方法與添加新master的reshard一樣,只是需要reshard的master節點是老節點.
5):刪除一個slave節點
- #redis-trib del-node ip:port '<node-id>'
- redis-trib.rb del-node 10.10.34.14:7386 'c7ee2fca17cb79fe3c9822ced1d4f6c5e169e378'
a):刪除master節點之前首先要使用reshard移除master的全部slot,然后再刪除當前節點(目前只能把被刪除
master的slot遷移到一個節點上)
- #把10.10.34.14:6386當前master遷移到10.10.34.14:6380上
- redis-trib.rb reshard 10.10.34.14:6380
- #根據提示選擇要遷移的slot數量(ps:這里選擇500)
- How many slots do you want to move (from 1 to 16384)? 500(被刪除master的所有slot數量)
- #選擇要接受這些slot的node-id(10.10.34.14:6380)
- What is the receiving node ID? c4a31c852f81686f6ed8bcd6d1b13accdc947fd2 (ps:10.10.34.14:6380的node-id)
- Please enter all the source node IDs.
- Type 'all' to use all the nodes as source nodes for the hash slots.
- Type 'done' once you entered all the source nodes IDs.
- Source node #1:f51e26b5d5ff74f85341f06f28f125b7254e61bf(被刪除master的node-id)
- Source node #2:done
- #打印被移動的slot后,輸入yes開始移動slot以及對應的數據.
- #Do you want to proceed with the proposed reshard plan (yes/no)? yes
b):刪除空master節點
1:客戶端基本操作使用
- <span style="color: #333333; font-family: Arial, sans-serif;"><span style="color: #333333; font-family: Arial, sans-serif;"> private static BinaryJedisCluster jc;
- static {
- //只給集群里一個實例就可以
- Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6380));
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6381));
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6382));
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6383));
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6384));
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7380));
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7381));
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7382));
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7383));
- jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7384));
- jc = new BinaryJedisCluster(jedisClusterNodes);
- }
- @Test
- public void testBenchRedisSet() throws Exception {
- final Stopwatch stopwatch = new Stopwatch();
- List list = buildBlogVideos();
- for (int i = 0; i < 1000; i++) {
- String key = "key:" + i;
- stopwatch.start();
- byte[] bytes1 = protostuffSerializer.serialize(list);
- jc.setex(key, 60 * 60, bytes1);
- stopwatch.stop();
- }
- System.out.println("time=" + stopwatch.toString());
- }</span></span>
2:jedis客戶端的坑.
1)cluster環境下redis的slave不接受任何讀寫操作,
2)client端不支持keys批量操作,不支持select dbNum操作,只有一個db:select 0
3)JedisCluster 的info()等單機函數無法調用,返回(No way to dispatch this command to Redis Cluster)錯誤,.
4)JedisCluster 沒有針對byte[]的API,需要自己擴展(附件是我加的基于byte[]的BinaryJedisCluster api)
參考文檔:
- jedis-2.5.0-sohu-20140420.125537-2-sources.jar (115.2 KB)
- 下載次數: 6
- jedis-2.5.0-sohu-20140420.125537-2.jar (342.8 KB)
- 下載次數: 5