redis-cluster研究和使用

最近研究redis-cluster,正好搭建了一個環境,遇到了不少坑,系統的總結下,等到redis3 release出來後,換掉memCache 集羣java

一:關於redis cluster

1:redis cluster的現狀

reids-cluster計劃在redis3.0中推出,能夠看做者antirez的聲明:http://antirez.com/news/49 (ps:跳票了很久,今年貌似加快速度了),目前的最新版本見:https://raw.githubusercontent.com/antirez/redis/3.0/00-RELEASENOTESnode

目前redis支持的cluster特性(已測試):git

1):節點自動發現github

2):slave->master 選舉,集羣容錯redis

3):Hot resharding:在線分片shell

4):集羣管理:cluster xxxapi

5):基於配置(nodes-port.conf)的集羣管理ruby

6):ASK 轉向/MOVED 轉向機制.bash

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)? 

    a:若是集羣任意master掛掉,且當前master沒有slave.集羣進入fail狀態,也能夠理解成集羣的slot映射[0-16383]不完成時進入fail狀態. ps : redis-3.0.0.rc1加入cluster-require-full-coverage參數,默認關閉,打開集羣兼容部分失敗.

    b:若是集羣超過半數以上master掛掉,不管是否有slave集羣進入fail狀態.

  ps:當集羣不可用時,全部對集羣的操做作都不可用,收到((error) CLUSTERDOWN The cluster is down)錯誤

二:redis cluster的使用

1:安裝redis cluster

安裝redis-cluster依賴:redis-cluster的依賴庫在使用時有兼容問題,在reshard時會遇到各類錯誤,請按指定版本安裝.

1)安裝zlib:version(1.2.6)

確保系統安裝zlib,不然gem install會報(no such file to load -- zlib)

#download:zlib-1.2.6.tar  
./configure  
make
make install

2)安裝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

3)安裝rubygem:version(1.8.16)

# rubygems-1.8.16.tgz  
cd /path/gem  
sudo ruby setup.rb  
sudo cp bin/gem /usr/local/bin

4)安裝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

5)安裝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實例,把auto-aof-rewrite搓開,由於cluster環境下內存佔用基本一致.  
#防止贊成機器下瞬間fork全部redis進程作aof rewrite,佔用大量內存(ps:cluster必須開啓aof)  
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)啓動集羣相關節點(必須是空節點,beta3後能夠是有數據的節點),指定配置文件和輸出日誌

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):檢查集羣狀態

#redis-trib.rb的check子命令構建  
#ip:port能夠是集羣的任意節點  
redis-trib.rb check 10.10.34.14:6380

 最後輸出以下信息,沒有任何警告或錯誤,表示集羣啓動成功並處於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是我本身寫的輸出配置腳本)

sh establish_config.sh 6386 > conf/redis-6386.conf

 b):啓動節點

redis-server /opt/redis/conf/redis-6386.conf > /opt/redis/logs/redis-6386.log 2>&1 &

c):加入空節點到集羣
add-node  將一個節點添加到集羣裏面, 第一個是新節點ip:port, 第二個是任意一個已存在節點ip:port

redis-trib.rb add-node 10.10.34.14:6386 10.10.34.14:6381

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

cluster replicate 2b9ebcbd627ff0fd7a7bbcc5332fb09e72788835

 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'

6):刪除一個master節點

  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節點

redis-trib.rb del-node 10.10.34.14:6386 'f51e26b5d5ff74f85341f06f28f125b7254e61bf'

三:redis cluster 客戶端(Jedis)

1:客戶端基本操做使用

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());  
}

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)

 

參考文檔:

http://redis.io/topics/cluster-spec

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

相關文章
相關標籤/搜索