Redis分布式部署,一致性hash;分布式與緩存隊列


 http://blog.csdn.net/yfkiss/article/details/39996129

Redis 3.0.0 RC1版本10.9號發布,Release Note
這個版本支持Redis Cluster,相信很多同學期待已久,不過這個版本只是RC版本,要應用到生產環境,還得等等

Redis Cluster設計要點:

 

架構:無中心

Redis Cluster采用無中心結構,每個節點都保存數據和整個集群的狀態
每個節點都和其他所有節點連接,這些連接保持活躍
使用gossip協議傳播信息以及發現新節點
node不作為client請求的代理,client根據node返回的錯誤信息重定向請求

數據分布:預分桶

預分好16384個桶,根據 CRC16(key) mod 16384的值,決定將一個key放到哪個桶中
每個Redis物理結點負責一部分桶的管理,當發生Redis節點的增減時,調整桶的分布即可
例如,假設Redis Cluster三個節點A/B/C,則
Node A 包含桶的編號可以為: 0 到 5500.
Node B 包含桶的編號可以為: 5500 到 11000.
Node C包含桶的編號可以為: 11001 到 16384.

當發生Redis節點的增減時,調整桶的分布即可。
預分桶的方案介於“硬Hash”和“一致性Hash”之間,犧牲了一定的靈活性,但相比“一致性Hash“,數據的管理成本大大降低

可用性:Master-Slave

為了保證服務的可用性,Redis Cluster采取的方案是的Master-Slave
每個Redis Node可以有一個或者多個Slave。當Master掛掉時,選舉一個Slave形成新的Master
一個Redis  Node包含一定量的桶,當這些桶對應的Master和Slave都掛掉時,這部分桶對應的數據不可用

Redis Cluster使用異步復制
一個完整的寫操作步驟:
1.client寫數據到master
2.master告訴client "ok"
3.master傳播更新到slave
存在數據丟失的風險:
1. 上述寫步驟1)和2)成功后,master crash,而此時數據還沒有傳播到slave
2. 由於分區導致同時存在兩個master,client向舊的master寫入了數據。
當然,由於Redis Cluster存在超時及故障恢復機制,第2個風險基本上不可能發生

數據遷移

Redis Cluster支持在線增/減節點。
基於桶的數據分布方式大大降低了遷移成本,只需將數據桶從一個Redis Node遷移到另一個Redis Node即可完成遷移。
當桶從一個Node A向另一個Node B遷移時,Node A和Node B都會有這個桶,Node A上桶的狀態設置為MIGRATING,Node B上桶的狀態被設置為IMPORTING
當客戶端請求時:
所有在Node A上的請求都將由A來處理,所有不在A上的key都由Node B來處理。同時,Node A上將不會創建新的key

多key操作

當系統從單節點向多節點擴展時,多key的操作總是一個非常難解決的問題,Redis Cluster方案如下:
1. 不支持多key操作
2. 如果一定要使用多key操作,請確保所有的key都在一個node上,具體方法是使用“hash tag”方案
hash tag方案是一種數據分布的例外情況


Reference:
Redis cluster tutorial
Redis cluster Specification

 

 

http://zhoushouby.blog.51cto.com/9150272/1560346

redis 3.0 cluster 集群 學習之路篇 [1]
2014-10-02 11:35:15
標簽: 集群  redis 3.0
版權聲明:原創作品,謝絕轉載!否則將追究法律責任。

周氏一族,整理技術文檔,給下一代留點教程......

 

目前,項目用的redis主備,感覺超不爽,很多局限性,特別是在 redis master 宕機之后,維護非常麻煩,尋思着弄一個redis集群,可不,總算到了今年10.1,redis發布了cluster版本。開啟摸索之路...

 

很多人,一看到官網有最新的cluster版本,滿懷熱血,第一件事,就是搭建cluster環境,其實,鄙人卻不,還是要從基層走起,先來了解一下官方信息,整理成下面的,大部分都是根據網絡上整理出來的,又存在相同,純屬借鑒,往見諒!!!

 

redis主備面臨的問題需求

1、主備模式,過於單一,特別是主服務器宕機的情況下,數據沒法寫入。

2、業務發展,數據龐大,主備必須同步相同數據,對存儲需求較大,有點多余

3、雖然說有sentinel,但是效率還是非常低

 

 

Redis集群是一個可以在多個 Redis 節點之間進行數據共享的設施。 這一點毫無疑問的!

Redis集群通過分區來提供一定程度的可用性:即使集群中有一部分節點失效或者無法進行通訊, 集群也可以繼續處理命令請求。

Redis 集群提供了以下兩個好處:

(1)將數據自動切分(split)到多個節點的能力。

           面臨大數據時代,集群,就是得要能實現一台宕機,不影響整個集群運作,換句話說,數據得每台機器都必須擁有對方的data,但是這樣由於業務的慢慢擴展,數據會原來越大,很明顯對存儲比較吃力。redis在這一點上面,做得比較完美,那就是"切片"。通過內部 “私有算法技術”,把data分布式存儲到集群機器,當要讀取數據時,再通過 “私有內部算法” 映射到對應機器讀取。

 

(2)當集群中的一部分節點失效或者無法進行通訊時, 仍然可以繼續處理命令請求的能力。

          因為在3.0版本當中,redis采用master n-1 salver的模式,也就是說,一台master主,就若干台備,甚至沒有備也行,但是本人強烈建議要1:1的模式,條件允許的情況下,建議1:n的模式

 

 

2、集群的數據分片功能:

集群要實現的目的是要將不同的 key 分散放置到不同的 redis 節點,這里我們需要一個規則或者算法,通常的做法是獲取 key 的哈希值,然后根據節點數來求模,但這種做法有其明顯的弊端,當我們需要增加或減少一個節點時,會造成大量的 key 無法命中,這種比例是相當高的,所以就有人提出了一致性哈希的概念。

一致性哈希有四個重要特征:

均衡性:也有人把它定義為平衡性,是指哈希的結果能夠盡可能分布到所有的節點中去,這樣可以有效的利用每個節點上的資源。

單調性:對於單調性有很多翻譯讓我非常的不解,而我想要的是當節點數量變化時哈希的結果應盡可能的保護已分配的內容不會被重新分派到新的節點。

分散性和負載:這兩個其實是差不多的意思,就是要求一致性哈希算法對 key 哈希應盡可能的避免重復。

 

但是:

Redis 集群沒有使用一致性hash, 而是引入了哈希槽的概念。

Redis 集群有16384個哈希槽,每個key通過CRC16校驗后對16384取模來決定放置哪個槽.集群的每個節點負責一部分hash槽。這種結構很容易添加或者刪除節點,並且無論是添加刪除或者修改某一個節點,都不會造成集群不可用的狀態。

使用哈希槽的好處就在於可以方便的添加或移除節點。

當需要增加節點時,只需要把其他節點的某些哈希槽挪到新節點就可以了;

當需要移除節點時,只需要把移除節點上的哈希槽挪到其他節點就行了;

在這一點上,我們以后新增或移除節點的時候不用先停掉所有的 redis 服務,very good的

 

3、Redis集群的主從架構:

為了使在部分節點失敗或者大部分節點無法通信的情況下集群仍然可用,所以集群使用了主從復制模型,每個節點都會有N-1個復制品。

例如有A,B,C三個節點的集群,在沒有復制模型的情況下,如果節點B失敗了,那么整個集群就會以為缺少B節點所承擔的哈希槽這個范圍的槽而不可用。

然而如果在集群創建的時候(或者過一段時間)我們為每個節點添加一個從節點A1,B1,C1,那么整個集群便有三個master節點和三個slave節點組成,這樣在節點B失敗后,集群便會選舉B1為新的主節點繼續服務,整個集群便不會因為槽找不到而不可用了。當然如果B和B1都down了,那集群還是不可用的,不過這種情況微乎其妙,基本不用考慮,出發你交換機掛了吧,或者機房斷電。

 

4、redis架構圖

wKiom1Qsx06wtVlXAAGEwXnBz08131.jpg

 

架構細節:

(1)所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬.

(2)節點的fail是通過集群中超過半數的節點檢測失效時才生效.

(3)客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可

(4)redis-cluster把所有的物理節點映射到[0-16383]slot上,cluster 負責維護node<->slot<->value

 

 

5、redis-cluster選舉:容錯

wKioL1Qsx8uwkQU2AAEcQpIDk1E403.jpg

 

(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狀態



 

本文出自 “周氏一族” 博客,謝絕轉載!

http://hot66hot.iteye.com/blog/2050676
最近研究redis-cluster,正好搭建了一個環境,遇到了很多坑,系統的總結下,等到redis3 release出來后,換掉memCache 集群. 轉載請注明出處哈:http://hot66hot.iteye.com/admin/blogs/2050676

一:關於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-RELEASENOTES

作者的目標: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)? 

    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

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

(1)確保系統安裝zlib,否則gem install會報(no such file to load -- zlib)
Java代碼   收藏代碼
  1. #download:zlib-1.2.6.tar  
  2. ./configure  
  3. make  
  4. make install  
 (2)安裝ruby:version(1.9.2)
Java代碼   收藏代碼
  1. # ruby1.9.2   
  2. cd /path/ruby  
  3. ./configure -prefix=/usr/local/ruby  
  4. make  
  5. make install  
  6. sudo cp ruby /usr/local/bin  
(3)安裝rubygem:version(1.8.16)
Java代碼   收藏代碼
  1. # rubygems-1.8.16.tgz  
  2. cd /path/gem  
  3. sudo ruby setup.rb  
  4. sudo cp bin/gem /usr/local/bin  
(4)安裝gem-redis:version(3.0.0)
Java代碼   收藏代碼
  1. gem install redis --version 3.0.0  
  2. #由於源的原因,可能下載失敗,就手動下載下來安裝  
  3. #download地址:http://rubygems.org/gems/redis/versions/3.0.0  
  4. gem install -l /data/soft/redis-3.0.0.gem  
(5)安裝redis-cluster
Java代碼   收藏代碼
  1. cd /path/redis  
  2. make  
  3. sudo cp /opt/redis/src/redis-server /usr/local/bin  
  4. sudo cp /opt/redis/src/redis-cli /usr/local/bin  
  5. sudo cp /opt/redis/src/redis-trib.rb /usr/local/bin  

 

2:配置redis cluster

1)redis配置文件結構:


 使用包含(include)把通用配置和特殊配置分離,方便維護.

2)redis通用配置.

Java代碼   收藏代碼
  1. #GENERAL  
  2. daemonize no  
  3. tcp-backlog 511  
  4. timeout 0  
  5. tcp-keepalive 0  
  6. loglevel notice  
  7. databases 16  
  8. dir /opt/redis/data  
  9. slave-serve-stale-data yes  
  10. #slave只讀  
  11. slave-read-only yes  
  12. #not use default  
  13. repl-disable-tcp-nodelay yes  
  14. slave-priority 100  
  15. #打開aof持久化  
  16. appendonly yes  
  17. #每秒一次aof寫  
  18. appendfsync everysec  
  19. #關閉在aof rewrite的時候對新的寫操作進行fsync  
  20. no-appendfsync-on-rewrite yes  
  21. auto-aof-rewrite-min-size 64mb  
  22. lua-time-limit 5000  
  23. #打開redis集群  
  24. cluster-enabled yes  
  25. #節點互連超時的閥值  
  26. cluster-node-timeout 15000  
  27. cluster-migration-barrier 1  
  28. slowlog-log-slower-than 10000  
  29. slowlog-max-len 128  
  30. notify-keyspace-events ""  
  31. hash-max-ziplist-entries 512  
  32. hash-max-ziplist-value 64  
  33. list-max-ziplist-entries 512  
  34. list-max-ziplist-value 64  
  35. set-max-intset-entries 512  
  36. zset-max-ziplist-entries 128  
  37. zset-max-ziplist-value 64  
  38. activerehashing yes  
  39. client-output-buffer-limit normal 0  
  40. client-output-buffer-limit slave 256mb 64mb 60  
  41. client-output-buffer-limit pubsub 32mb 8mb 60  
  42. hz 10  
  43. aof-rewrite-incremental-fsync yes  

3)redis特殊配置.

Java代碼   收藏代碼
  1. #包含通用配置  
  2. include /opt/redis/redis-common.conf  
  3. #監聽tcp端口  
  4. port 6379  
  5. #最大可用內存  
  6. maxmemory 100m  
  7. #內存耗盡時采用的淘汰策略:  
  8. volatile-lru -> remove the key with an expire set using an LRU algorithm  
  9. # allkeys-lru -> remove any key accordingly to the LRU algorithm  
  10. volatile-random -> remove a random key with an expire set  
  11. # allkeys-random -> remove a random key, any key  
  12. volatile-ttl -> remove the key with the nearest expire time (minor TTL)  
  13. # noeviction -> don't expire at all, just return an error on write operations  
  14. maxmemory-policy allkeys-lru  
  15. #aof存儲文件  
  16. appendfilename "appendonly-6379.aof"  
  17. #不開啟rdb存儲,只用於添加slave過程  
  18. dbfilename dump-6379.rdb  
  19. #cluster配置文件(啟動自動生成)  
  20. cluster-config-file nodes-6379.conf  
  21. #部署在同一機器的redis實例,把auto-aof-rewrite搓開,因為cluster環境下內存占用基本一致.  
  22. #防止同意機器下瞬間fork所有redis進程做aof rewrite,占用大量內存(ps:cluster必須開啟aof)  
  23. auto-aof-rewrite-percentage 80-100  

3:cluster 操作

cluster集群相關命令,更多redis相關命令見文檔:http://redis.readthedocs.org/en/latest/

 

Java代碼   收藏代碼
  1. 集群  
  2. CLUSTER INFO 打印集群的信息  
  3. CLUSTER NODES 列出集群當前已知的所有節點(node),以及這些節點的相關信息。  
  4. 節點  
  5. CLUSTER MEET <ip> <port> 將 ip 和 port 所指定的節點添加到集群當中,讓它成為集群的一份子。  
  6. CLUSTER FORGET <node_id> 從集群中移除 node_id 指定的節點。  
  7. CLUSTER REPLICATE <node_id> 將當前節點設置為 node_id 指定的節點的從節點。  
  8. CLUSTER SAVECONFIG 將節點的配置文件保存到硬盤里面。  
  9. 槽(slot)  
  10. CLUSTER ADDSLOTS <slot> [slot ...] 將一個或多個槽(slot)指派(assign)給當前節點。  
  11. CLUSTER DELSLOTS <slot> [slot ...] 移除一個或多個槽對當前節點的指派。  
  12. CLUSTER FLUSHSLOTS 移除指派給當前節點的所有槽,讓當前節點變成一個沒有指派任何槽的節點。  
  13. CLUSTER SETSLOT <slot> NODE <node_id> 將槽 slot 指派給 node_id 指定的節點,如果槽已經指派給另一個節點,那么先讓另一個節點刪除該槽>,然后再進行指派。  
  14. CLUSTER SETSLOT <slot> MIGRATING <node_id> 將本節點的槽 slot 遷移到 node_id 指定的節點中。  
  15. CLUSTER SETSLOT <slot> IMPORTING <node_id> 從 node_id 指定的節點中導入槽 slot 到本節點。  
  16. CLUSTER SETSLOT <slot> STABLE 取消對槽 slot 的導入(import)或者遷移(migrate)。  
  17. 鍵  
  18. CLUSTER KEYSLOT <key> 計算鍵 key 應該被放置在哪個槽上。  
  19. CLUSTER COUNTKEYSINSLOT <slot> 返回槽 slot 目前包含的鍵值對數量。  
  20. CLUSTER GETKEYSINSLOT <slot> <count> 返回 count 個 slot 槽中的鍵。  
 

4:redis cluster 運維操作

1)初始化並構建集群

(1)啟動集群相關節點(必須是空節點,beta3后可以是有數據的節點),指定配置文件和輸出日志

 

Java代碼   收藏代碼
  1. redis-server /opt/redis/conf/redis-6380.conf > /opt/redis/logs/redis-6380.log 2>&1 &  
  2. redis-server /opt/redis/conf/redis-6381.conf > /opt/redis/logs/redis-6381.log 2>&1 &  
  3. redis-server /opt/redis/conf/redis-6382.conf > /opt/redis/logs/redis-6382.log 2>&1 &  
  4. redis-server /opt/redis/conf/redis-7380.conf > /opt/redis/logs/redis-7380.log 2>&1 &  
  5. redis-server /opt/redis/conf/redis-7381.conf > /opt/redis/logs/redis-7381.log 2>&1 &  
  6. redis-server /opt/redis/conf/redis-7382.conf > /opt/redis/logs/redis-7382.log 2>&1 &  

 

(2):使用自帶的ruby工具(redis-trib.rb)構建集群

 

Java代碼   收藏代碼
  1. #redis-trib.rb的create子命令構建  
  2. #--replicas 則指定了為Redis Cluster中的每個Master節點配備幾個Slave節點  
  3. #節點角色由順序決定,先master之后是slave(為方便辨認,slave的端口比master大1000)  
  4. redis-trib.rb create --replicas 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):檢查集群狀態

Java代碼   收藏代碼
  1. #redis-trib.rb的check子命令構建  
  2. #ip:port可以是集群的任意節點  
  3. redis-trib.rb check 10.10.34.14:6380  
 最后輸出如下信息,沒有任何警告或錯誤,表示集群啟動成功並處於ok狀態
Java代碼   收藏代碼
  1. [OK] All nodes agree about slots configuration.  
  2. >>> Check for open slots...  
  3. >>> Check slots coverage...  
  4. [OK] All 16384 slots covered.  

2):添加新master節點

(1)添加一個master節點:創建一個空節點(empty node),然后將某些slot移動到這個空節點上,這個過程目前需要人工干預

a):根據端口生成配置文件(ps:establish_config.sh是我自己寫的輸出配置腳本)

 

Java代碼   收藏代碼
  1. sh establish_config.sh 6386 > conf/redis-6386.conf  
 

b):啟動節點

 

Java代碼   收藏代碼
  1. redis-server /opt/redis/conf/redis-6386.conf > /opt/redis/logs/redis-6386.log 2>&1 &  

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

 

Java代碼   收藏代碼
  1. redis-trib.rb add-node 10.10.34.14:6386 10.10.34.14:6381  

node:新節點沒有包含任何數據, 因為它沒有包含任何slot。新加入的加點是一個主節點, 當集群需要將某個從節點升級為新的主節點時, 這個新節點不會被選中

 

d):為新節點分配slot

 

Java代碼   收藏代碼
  1. redis-trib.rb reshard 10.10.34.14:6386  
  2. #根據提示選擇要遷移的slot數量(ps:這里選擇500)  
  3. How many slots do you want to move (from 1 to 16384)? 500  
  4. #選擇要接受這些slot的node-id  
  5. What is the receiving node ID? f51e26b5d5ff74f85341f06f28f125b7254e61bf  
  6. #選擇slot來源:  
  7. #all表示從所有的master重新分配,  
  8. #或者數據要提取slot的master節點id,最后用done結束  
  9. Please enter all the source node IDs.  
  10.   Type 'all' to use all the nodes as source nodes for the hash slots.  
  11.   Type 'done' once you entered all the source nodes IDs.  
  12. Source node #1:all  
  13. #打印被移動的slot后,輸入yes開始移動slot以及對應的數據.  
  14. #Do you want to proceed with the proposed reshard plan (yes/no)? yes  
  15. #結束  

3):添加新的slave節點

a):前三步操作同添加master一樣

b)第四步:redis-cli連接上新節點shell,輸入命令:cluster replicate 對應master的node-id

 

Java代碼   收藏代碼
  1. cluster replicate 2b9ebcbd627ff0fd7a7bbcc5332fb09e72788835  
 

note:在線添加slave 時,需要dump整個master進程,並傳遞到slave,再由 slave加載rdb文件到內存,rdb傳輸過程中Master可能無法提供服務,整個過程消耗大量io,小心操作.

例如本次添加slave操作產生的rdb文件

 

Java代碼   收藏代碼
  1. -rw-r--r-- 1 root root  34946 Apr 17 18:23 dump-6386.rdb  
  2. -rw-r--r-- 1 root root  34946 Apr 17 18:23 dump-7386.rdb  

4):在線reshard 數據:

對於負載/數據不均勻的情況,可以在線reshard slot來解決,方法與添加新master的reshard一樣,只是需要reshard的master節點是老節點.

5):刪除一個slave節點

Java代碼   收藏代碼
  1. #redis-trib del-node ip:port '<node-id>'  
  2. redis-trib.rb del-node 10.10.34.14:7386 'c7ee2fca17cb79fe3c9822ced1d4f6c5e169e378'  

 6):刪除一個master節點

  a):刪除master節點之前首先要使用reshard移除master的全部slot,然后再刪除當前節點(目前只能把被刪除

master的slot遷移到一個節點上)

 

Java代碼   收藏代碼
  1. #把10.10.34.14:6386當前master遷移到10.10.34.14:6380上  
  2. redis-trib.rb reshard 10.10.34.14:6380  
  3. #根據提示選擇要遷移的slot數量(ps:這里選擇500)  
  4. How many slots do you want to move (from 1 to 16384)? 500(被刪除master的所有slot數量)  
  5. #選擇要接受這些slot的node-id(10.10.34.14:6380)  
  6. What is the receiving node ID? c4a31c852f81686f6ed8bcd6d1b13accdc947fd2 (ps:10.10.34.14:6380的node-id)  
  7. Please enter all the source node IDs.  
  8.   Type 'all' to use all the nodes as source nodes for the hash slots.  
  9.   Type 'done' once you entered all the source nodes IDs.  
  10. Source node #1:f51e26b5d5ff74f85341f06f28f125b7254e61bf(被刪除master的node-id)  
  11. Source node #2:done  
  12. #打印被移動的slot后,輸入yes開始移動slot以及對應的數據.  
  13. #Do you want to proceed with the proposed reshard plan (yes/no)? yes  
 

b):刪除空master節點

 

Java代碼   收藏代碼
  1. redis-trib.rb del-node 10.10.34.14:6386 'f51e26b5d5ff74f85341f06f28f125b7254e61bf'  
三:redis cluster 客戶端(Jedis)

1:客戶端基本操作使用

 

Java代碼   收藏代碼
  1. <span style="color: #333333; font-family: Arial, sans-serif;"> private static BinaryJedisCluster jc;  
  2.   static {  
  3.        //只給集群里一個實例就可以  
  4.         Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();  
  5.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6380));  
  6.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6381));  
  7.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6382));  
  8.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6383));  
  9.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 6384));  
  10.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7380));  
  11.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7381));  
  12.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7382));  
  13.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7383));  
  14.         jedisClusterNodes.add(new HostAndPort("10.10.34.14", 7384));  
  15.         jc = new BinaryJedisCluster(jedisClusterNodes);  
  16.     }  
  17. @Test  
  18.     public void testBenchRedisSet() throws Exception {  
  19.         final Stopwatch stopwatch = new Stopwatch();  
  20.         List list = buildBlogVideos();  
  21.         for (int i = 0; i < 1000; i++) {  
  22.             String key = "key:" + i;  
  23.             stopwatch.start();  
  24.             byte[] bytes1 = protostuffSerializer.serialize(list);  
  25.             jc.setex(key, 60 * 60, bytes1);  
  26.             stopwatch.stop();  
  27.         }  
  28.         System.out.println("time=" + stopwatch.toString());  
  29.     }</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)

 

參考文檔:

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

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

 
 
 
 
 http://blog.sina.com.cn/s/blog_48c95a190101dhe9.html
 Redis Cluster即Redis的分布式版本,將是Redis繼支持Lua腳本之后的又一重磅功能,官方聲明將會在今年第三季度發布Redis Cluster的beta版並在年底發布第一個穩定版本。當前,雖然Redis的穩定版本里還沒有集成分布式功能,但實際上在開發版中Redis Cluster的開發已經取得了長足的進展,我們已經可以搭建Redis集群並使用其部分功能了。今天,本博主基於最新的開發版代碼嘗試着搭建了一個三節點的Redis集群,這里不妨把過程簡單總結一下,希望能對各位看客們有所幫助! (注:本篇博文中的搭建方法是徹徹底底的野路子,Redis官方已正式發布Redis Cluster搭建方法,鏈接如下:http://redis.io/topics/cluster-tutorial。)
    首先,下載最新的Redis開發版源碼包。這個再簡單不過了,大家既可以去Redis在github上的主頁下載,也可以直接運行“git clone git://github.com/antirez/redis.git”克隆整個Redis代碼庫,當然前提是你已經安裝了git。
    其次,安裝Redis。因為本博主要搭建一個三節點的Redis集群,所以在這三個節點上都要安裝好Redis,我們這里姑且將這三個節點命名為Redis Cluster Node1/Node2/Node3吧,安裝目錄為/usr/local/redis/。
    再次,修改Redis配置文件。因為即使是在支持分布式功能的開發版中,Redis配置文件也是默認不打開Redis Cluster的,所以在啟動Redis服務器之前,我們需要修改Redis配置文件,打開Redis Cluster。為了簡單起見,我們這里只修改與Redis Cluster相關的配置,其他無關的配置均采用默認值,具體修改內容如下:
################################ REDIS CLUSTER  ###############################
#
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
# started as cluster nodes can. In order to start a Redis instance as a
# cluster node enable the cluster support uncommenting the following:
#
cluster-enabled yes
# Every cluster node has a cluster configuration file. This file is not
# intended to be edited by hand. It is created and updated by Redis nodes.
# Every Redis Cluster node requires a different cluster configuration file.
# Make sure that instances running in the same system does not have
# overlapping cluster configuration file names.
#
cluster-config-file nodes-6379.conf
# Cluster node timeout is the amount of seconds a node must be unreachable
# for it to be considered in failure state.
# Most other internal time limits are multiplicators of the node timeout.
#
cluster-node-timeout 15
# In order to setup your cluster make sure to read the documentation
# available at http://redis.io web site.  
    再次,啟動三個節點上的Redis服務器。此時,三個Redis服務器節點均會以Redis Cluster的方式開始運行,但並沒有自動構建集群,因為三者還處於“我不認識你,你不屬於我”的狀態,它們每個都是孤零零的Redis節點,或者是只包含了一個節點的集群。我們可以通過Redis客戶端連接到服務器查看它們的狀態,圖一給出了狀態查詢方法和查詢結果,其中cluster nodes命令用於查看當前Redis節點所屬的Redis集群中的所有節點,而cluster info則用於查看當前Redis節點所屬的Redis集群的整體狀態。由圖中我們可以看到,Redis集群中僅包含一個Redis節點,也就是當前節點,整個集群的狀態是fail。
Redis <wbr>Cluster搭建方法簡介
圖一 未形成集群時Redis節點狀態
    再次,搭建Redis集群。這里所謂的搭建集群,說白了就是讓之前啟動的三個Redis節點彼此連通,意識到彼此的存在,那么如何做到這一點呢?答案就是cluster meet命令。該命令的作用就是將當前節點主動介紹給另外一個節點認識,圖二給出了cluster meet命令的執行方法和執行效果,由圖中可知我們使用cluster meet命令分別將Redis Cluster Node1介紹給了Redis Cluster Node2(節點IP地址為192.168.32.3,運行端口為6379)和Redis Cluster Node3(節點IP地址為192.168.32.4,運行端口為6379),之后我們再次查看集群節點和集群狀態就可以知道,三個節點已經成功合並到了同一個集群中。
Redis <wbr>Cluster搭建方法簡介
圖二 搭建Redis集群
    再次,為集群中的Redis節點分配hash slot。通過上面的操作,我們已經將三個各自為政的Redis節點規划到一個相同的集群中,那么我們現在是否就已經完成了集群搭建的所有工作了呢?非也!通過圖二中對集群狀態的查看我們可以知道,當前集群的狀態還是fail,此時的Redis集群是不工作的,無法處理任何Redis命令。那么集群的狀態為什么還是fail呢?本博主通過查看官方文檔說明找到了原因所在,現摘錄原文如下:
The FAIL state for the cluster happens in two cases.
1) If at least one hash slot is not served as the node serving it currently is in FAIL state.
2) If we are not able to reach the majority of masters (that is, if the majorify of masters are simply in PFAIL state, it is enough for the node to enter FAIL mode).
    很明顯,導致我們的集群處於fail狀態的原因不是第二個條,也就是說至少有一個hash slot沒有被服務!稍微考慮一下,可不是!何止有一個hash slot沒有被服務,壓根兒就沒有Redis節點為任何hash slot服務!眾所周知,Redis Cluster通過hash slot將數據根據主鍵來分區,所以一條key-value數據會根據算法自動映射到一個hash slot,但是一個hash slot存儲在哪個Redis節點上並不是自動映射的,是需要集群管理者自行分配的。那么我們需要為多少個hash slot分配Redis節點呢?根據源碼可知是16384個,即我們要將16384個hash slot分配到集群內的三個節點上。Redis中用於分配hash slot的命令有很多,其中包括cluster addslots、cluster delslots和cluster setslot。鑒於我們現在是集群的初始化階段,所以我們可以選擇cluster addslots來分配hash slot,該命令的語法為cluster addslots slot1 [slot2] ... [slotN]。
Redis <wbr>Cluster搭建方法簡介
圖三 Redis Cluster Node1上nodes-6379.conf的內容
    但是另一個問題又出現了,對於16384個hash slot來說,我們總不能用cluster addslots一個個去分配吧?幸運的是,我們還有另外一種方法來分配hash slot,那就是通過集群配置文件,也就是我們在第三步中配置的cluster-config-file參數,來完成hash slot的分段配置。此時,我們在執行Redis服務器啟動的目錄下找到名字為nodes-6379.conf的配置文件。圖三給出了Redis Cluster Node1上nodes-6379.conf的內容,由圖可知該文件中的內容與我們執行cluster nodes命令得到的結果一樣,就是對集群中所有Redis節點的描述,而我們需要修改的就是為每個節點添加上hash slot的分配信息。針對Redis Cluster Node1上nodes-6379.conf的內容,修改內容如下:cda76a0a094d2ce624e33bed7f3c75689a4128fd :0 myself,master - 0 0 connected  0-5000(注意是在自身節點的描述,也就是包含了myself那一行的后面追加hash slot的范圍)。類似的,Redis Cluster Node2上nodes-6379.conf文件中追加 5001-10000,Redis Cluster Node3上nodes-6379.conf文件中追加 10001-16383。經過這樣的配置后,Redis Cluster Node1負責存儲0至5000之間的所有hash slots,Redis Cluster Node2負責存儲5001至10000之間的所有hash slots,Redis Cluster Node3負責存儲10001至16383的所有hash slots。
    保存以上修改,通過Redis客戶端將三個Redis服務器重啟,再次執行命令cluster nodes和cluster info,我們就可以看到集群的狀態已經為ok了(如圖四所示),這說明之前對於集群配置文件的修改確實起到了作用。如果經過以上步驟之后集群狀態依然是fail的話,可以重新修改一下三個節點的nodes-6379.conf,將其中所有除myself那一行以外的所有配置行都刪除,然后再保存重啟服務器即可。當然,這里需要說明的一點是,本博主並沒有看到任何關於Redis Cluster集群配置文件的官方文檔,以上配置方法盡管有效但並不一定是官方推薦的做法!
Redis <wbr>Cluster搭建方法簡介
圖四 修改集群配置文件后重啟的集群狀態
    最后,執行Redis命令。在集群狀態顯示為ok之后,我們就可以像在Redis單機版上一樣執行Redis命令了。圖五顯示了Redis客戶端連接到Redis Cluster Node1上執行“set foo bar”命令的執行結果,由圖可知主鍵foo所屬的hash slot存儲在Redis Cluster Node3上。我們再連接到Redis Cluster Node3,執行相同的命令,正確執行!
Redis <wbr>Cluster搭建方法簡介
圖五 執行Redis命令
 
 
 
http://blog.sina.com.cn/s/blog_48c95a190101ig6a.html
 
去年本博主發表了一篇關於Redis Cluster搭建的博文,因為當時沒有找到任何官方的說明文檔,所以只能根據Redis主頁上的Redis cluster Specification一步步地摸索出了一種搭建方法,可以說是徹徹底底的野路子。后來自己怕誤人子弟,還很自覺地在原文中對這一點作了說明。后來某位網友發來一篇博文的鏈接:http://no-fucking-idea.com/blog/2012/04/16/setting-up-redis-cluster/,其中就是介紹如何使用redis源碼包中的redis-trib.rb工具來實現Redis Cluster的搭建,不僅實現方法異常簡單而且發表時間也早了一年有余,由此可知自己的那篇博文確實是閉門造車了。最近Redis官網正式推出了支持Redis Cluster的3.0 Beta版,我在官網上也找到了關於Redis Cluster搭建的tutorial,今天按照上面的步驟嘗試了一把,果真是極好的!於是乎趕緊發一篇博文,希望已經被俺前一篇博文毒害的弟兄們早日懸崖勒馬,回歸正道!
    首先,下載支持Redis Cluster的源碼包,最方便的當然就是直接下載3.0 Beta版,其鏈接地址為:https://github.com/antirez/redis/archive/3.0.0-beta1.tar.gz。解壓安裝后,對Redis進行配置,主要就是將cluster功能打開,與Cluster相關的具體配置如下所示。
cluster-enabled yes
cluster-config-file /home/wqd/work/conf/nodes-6379.conf
cluster-node-timeout 5000
    此外,這次通過運行在同一台機器上的三個不同Redis實例來搭建Redis Cluster,所以需要准備三個配置文件,每個配置文件中設置不同的端口。這里姑且將這三個配置文件分別命名為redis-6379.conf、redis-6380.conf和redis-6381.conf,其中的配置也是一樣,像pid文件、日志文件、rdb文件、aof文件和集群配置文件等都是通過端口號來區分。在完成對配置文件的修改后,分別啟動三個Redis實例,結果如圖一所示。
Redis <wbr>Cluster搭建方法簡介(續)
圖一 啟動三個Redis實例
    其次,通過redis-trib.rb工具構建Redis Cluster。通過以上操作我們已經有了三個獨立運行的Redis實例,彼此之間各自為政,接下來就是redis-trib.rb工具發揮作用的時候了。在執行該工具之前,一些准備工作是必不可少的,其一就是安裝ruby和rubygem,為了方便起見這里推薦安裝ruby 1.9.2及之后的版本,這些版本已經包含了rubygem,無需單獨安裝,安裝方法這里就不贅述了。 在完成ruby和rubygem的安裝之后,我們還需要為ruby安裝redis庫,安裝命令為:gem install redis。在做完了以上這些准備工作后,我們就可以執行redis-trib.rb這個工具了,圖二給出了該工具在沒有任何參數下的執行結果,其中我們可以了解該工具支持的所有功能,這里就不一一介紹了,官方的tutorial中有詳細的介紹。
Redis <wbr>Cluster搭建方法簡介(續)
圖二 redis-trib.rb工具使用方法
    再次,基於三個實例構建Redis Cluster。上面我們已經看到了redis-trib.rb支持的所有子命令了,而其中用於構建Redis Cluster的子命令就是create。create子命令的參數有兩種,host1:port1 ... hostN:portN指定了用於構建Redis Cluster的所有redis實例,而--replicas 則指定了為Redis Cluster中的每個Master節點配備幾個Slave節點。關於后一個參數這里需要簡單說明一下,那就是Redis Cluster中的節點分為兩種:Master節點和Slave節點,一個Master節點可以擁有若干個Slave節點,Master節點上的數據通過異步方式與Slave節點實現數據同步,當Master節點因為某種原因退出集群后,Redis Cluster會自動從該Master節點的Slave節點中選擇出一個作為新的Master節點。因此,redis-trib.rb工具的create子命令提供了--args參數來指定集群中的Master節點擁有幾個Slave節點,譬如使用6個redis實例構建集群且--args參數值為1,那么整個集群就包含三個Master節點和三個Slave節點,每個Master節點都有一個Slave節點。這里我們只有三個redis實例,所以選擇不為Master節點配備Slave節點,創建集群的方法和結果如圖三所示,從中可知Redis集群已經構建成功,其中監聽6379的實例負責存儲0-5460哈希槽,監聽6380的實例負責存儲5461-10921哈希槽,監聽6381的實例負責存儲10922-16383哈希槽。
Redis <wbr>Cluster搭建方法簡介(續)
圖三 通過redis-trib.rb創建Redis Cluster
    如果忘記了Redis Cluster的搭建環境,如集群中有幾個節點,這些節點分別負責存儲哪些哈希槽,我們可以通過redis-trib.rb工具的check子命令來查看集群信息,該子命令只需要提供集群中任意redis實例的IP地址和端口號即可,圖四給出了check子命令的執行方法和結果。
Redis <wbr>Cluster搭建方法簡介(續)
圖四 通過redis-trib.rb查看Redis Cluster
    最后,通過redis客戶端實現對Redis Cluster的讀寫。當前,redis客戶端同樣實現了對集群的支持,但使用方法略有不同, 即在啟動的時候需要添加一個-c參數 。圖五給出了redis客戶端的使用方法和執行結果,從中可以看到當讀寫Key-Value數據所屬的哈希槽存儲在別的節點上時,redis客戶端會將數據自動重定向到目標節點上,而不是像之前那樣(即不帶-c參數)返回錯誤並給出目標節點了事。
Redis <wbr>Cluster搭建方法簡介(續)
圖五 通過redis客戶端實現對Redis Cluster讀寫
    以上我們簡單介紹了通過redis-trib.rb工具構建Redis Cluster的方法與步驟,關於redis-trib.rb工具實際上還有很多實用的功能沒有介紹,像reshard、add-node、del-node等等,感興趣地話推薦大家還是去閱讀官方的tutorial,其中有詳細的說明和示例。

參考鏈接:http://no-fucking-idea.com/blog/2012/04/16/setting-up-redis-cluster/
http://redis.io/topics/cluster-tutorial
 
 
http://blog.nosqlfan.com/html/3302.html

Redis集群功能說明

雖然目前可以通過在客戶端做hash的方法來構建Redis集群,但Redis原生的集群支持還是頗受期待。本文是對Redis集群功能官方描述文檔的一個翻譯,譯者是@PPS蘿卜同學,也感謝他的投稿分享。

介紹

這篇文檔主要是為了說明正在進展中的Redis集群功能。文檔主要分為兩個部分,前一部分主要介紹我在非穩定分支已完成的代碼,后一部分主要介紹還有哪些功能待實現。

本文檔所有的說明都有可能在將來由於設計原因而進行更改,而未實現的計划比已實現的功能更有可能會被更改。

本文檔包含了所有client library所需要的細節,但是client library的作者們需要提前意識到真正實現的細節在將來很有可能會有變化。

什么是Redis集群?

Redis集群是一個實現分布式並且允許單點故障的Redis高級版本。

Redis集群沒有最重要或者說中心節點,這個版本最主要的一個目標是設計一個線性可伸縮(可隨意增刪節點?)的功能。

Redis集群為了數據的一致性可能犧牲部分允許單點故障的功能,所以當網絡故障和節點發生故障時這個系統會盡力去保證數據的一致性和有效性。(這里我們認為節點故障是網絡故障的一種特殊情況)

為了解決單點故障的問題,我們同時需要masters 和 slaves。 即使主節點(master)和從節點(slave)在功能上是一致的,甚至說他們部署在同一台服務器上,從節點也僅用以替代故障的主節點。 實際上應該說 如果對從節點沒有read-after-write(寫並立即讀取數據 以免在數據同步過程中無法獲取數據)的需求,那么從節點僅接受只讀操作。

已實現子集

Redis集群會把所有的單一key存儲在非分布式版本的Redis中。對於復合操作比如求並集求交集之類則未實現。

在將來,有可能會增加一種為“Computation Node”的新類型節點。這種節點主要用來處理在集群中multi-key的只讀操作,但是對於multi-key的只讀操作不會以集群傳輸到Computation Node節點再進行計算的方式實現。

Redis集群版本將不再像獨立版本一樣支持多數據庫,在集群版本中只有database 0,並且SELECT命令是不可用的。

客戶端與服務端在Redis集群版中的約定

在Redis集群版本中,節點有責任/義務保存數據和自身狀態,這其中包括把數據(key)映射到正確的節點。所有節點都應該自動探測集群中的其他節點,並且在發現故障節點之后把故障節點的從節點更改為主節點(原文這里有“如果有需要” 可能是指需要設置或者說存在從節點)。

集群節點使用TCP bus和二進制協議進行互聯並對任務進行分派。各節點使用gossip 協議發送ping packets給集群其他節點以確定其他節點是否正常工作。cluster bus也可以用來在節點間執行PUB/SUB命令。

當發現集群節點無應答的時候則會使用redirections errors -MOVED and -ASK命令並且會重定向至可用節點。理論上客戶端可隨意向集群中任意節點發送請求並獲得重定向,也就是說客戶端實際上並不用關心集群的狀態。然而,客戶端也可以緩存數據對應的節點這樣可以免去服務端進行重定向的工作,這在一定程度上可以提高效率。

Keys分配模式

一個集群可以包含最多4096個節點(但是我們建議最多設置幾百個節點)。

所有的主節點會控制4096個key空間的百分比。當集群穩定之后,也就是說不會再更改集群配置(更改配置指的增刪節點),那么一個節點將只為一個hash slot服務。(但是服務節點(主節點)可以擁有多個從節點用來防止單點故障)

用來計算key屬於哪個hash slot的算法如下:

HASH_SLOT = CRC16(key) mod 4096

Name: XMODEM (also known as ZMODEM or CRC-16/ACORN)
Width: 16 bit
Poly: 1021 (That is actually x^16 + x^12 + x^5 + 1)
Initialization: 0000
Reflect Input byte: False
Reflect Output CRC: False
Xor constant to output CRC: 0000
Output for "123456789": 31C3

這里我們會取CRC16后的12個字節。在我們的測試中,對於4096個slots, CRC16算法最合適。

集群節點特性

在集群中每個節點都擁有唯一的名字。節點名為16進制的160 bit隨機數,當節點獲取到名字后將被立即啟用。節點名將被永久保存到節點設置文件中,除非系統管理員手動刪除節點配置文件。

節點名是集群中每個節點的身份證明。在不更改節點ID的情況下是允許修改節點IP和地址的。cluster bus會自動通過gossip協議獲取更改后的節點設置。

每個節點可獲知其他節點的信息包括:

  • IP 端口
  • 狀態
  • 管理的hash slots
  • cluster bus最后發送PING的時間
  • 最后接收到PONG的時間
  • 從節點數量
  • 節點ID

無論是主節點還是從節點都可以通過CLUSTER NODES命令來獲取以上信息
示例如下:

$ redis-cli cluster nodes
d1861060fe6a534d42d8a19aeb36600e18785e04 :0 myself - 0 1318428930 connected 0-1364
3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 1318428930 1318428931 connected 1365-2729
d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 1318428931 1318428931 connected 2730-4095

節點交互

所有節點總是允許接受來自cluster bus的連接請求,並且即使請求PING的節點是不可信的也會進行應答。然而,所有來自非集群節點的packets都會被忽略。

只有以下兩種情況節點才會把其他節點認為是集群的一部分:

如果一個節點使用 MEET message 介紹自己。MEET message 命令是強制其他節點把自己當成是集群的一部分。只有系統管理員使用 CLUSTER MEET ip port 命令節點才會發送MEET message給其他節點。

另外一種方式就是通過集群節點間的推薦機制。例如 如果A節點知道B節點屬於集群,而B知道C節點屬於集群,那么B將會發送gossip信息告知A:C是屬於集群的。當A獲得gossip信息之后就會嘗試去連接C。

這意味着,當我們以任意連接方式為集群加入一個節點,集群中所有節點都會自動對新節點建立信任連接。也就是說,集群具備自動識別所有節點的功能,但是這僅發生在當系統管理強制為新節點與集群中任意節點建立信任連接的前提下。

這個機制使得集群系統更加健壯。

當一個節點故障時,其余節點會嘗試連接其他所有已知的節點已確定其他節點的健壯性。

被移動數據的重定向

Redis客戶端被允許向集群中的任意節點發送命令,其中包括從節點。被訪問的節點將會分析命令中所需要的數據(這里僅指請求單個key),並自己通過判斷hash slot確定數據存儲於哪個節點。

如果被請求節點擁有hash slot數據(這里指請求數據未被遷移過 或者 hash slot在數據遷移后被重新計算過),則會直接返回結果,否則將會返回一個 MOVED 錯誤。

MOVED 錯誤如下:

GET x
-MOVED 3999 127.0.0.1:6381

這個錯誤包括請求的數據所處的 hash slot(3999) 和 數據目前所屬的IP:端口。客戶端需要通過訪問返回的IP:端口獲取數據。即使在客戶端請求並等待數據返回的過程中,集群配置已被更改(也就是說請求的key在配置文件中所屬的節點ID已被重定向至新的IP:端口),目標節點依然會返回一個MOVED錯誤。

所以雖然在集群中的節點使用節點ID來確定身份,但是map依然是靠hash slot和Redis節點的IP:端口來進行配對。

客戶端雖然不被要求但是應該嘗試去記住hash slot 3999現在已被轉移至127.0.0.1:6381。這樣的話,當一個新的命令需要從hash slot 3999獲取數據時就可以有更高的幾率從hash slot獲取到正確的目標節點。

假設集群已經足夠的穩定(不增刪節點),那么所有的客戶端將會擁有一份hash slots對應節點的數據,這可以使整個集群更高效,因為這樣每個命令都會直接定向到正確的節點,不需要通過節點尋找節點或者重新計算hash slot對應的節點。

集群不下線更新配置

Redis集群支持線上增刪節點。實際上對於系統來說,增加和刪除節點在本質上是一樣的,因為他們都是把hash slot從一個節點遷移至另外一個節點而已。

增加節點:集群中加入一個空節點並且把hash slot從已存在的節點們移至新節點。
刪除節點:集群刪除一個已存在節點並且把hash slot分散到已存在的其他節點中。

所以實現這個功能的核心就是遷移slots。實際上從某種觀點上來說,hash slot只不過是一堆key的合集,所以Redis集群要做的事情只是在重分片的時候把一堆key從一個實例移動到另外一個實例。

為了清楚的了解這是如何實現的,我們需要先了解一下CLUSTER用來控制slots傳輸的底層命令。
這些底層命令包括:

CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
CLUSTER SETSLOT slot NODE node
CLUSTER SETSLOT slot MIGRATING node
CLUSTER SETSLOT slot IMPORTING node

前兩個命令 ADDSLOTS 和 DELSLOTS 是用來在Redis節點上增加/刪除slots。當hash slots被賦值之后他們會通過gossip協議在整個集群進行廣播(例如:大喊一聲 兄弟們 我現在住在X節點 有需要我的以后請到X節點來找我)。當slots被添加,ADDSLOTS 命令是用來通知集群其余所有節點最高效的方法。

SETSLOT 命令是用來給把slot注冊給一個特殊的node ID(也就是說ADDSLOTS 和 DELSLOTS 對slots進行操作是不指定節點的 而SETSLOT 是會指定節點的)。另外 SETSLOT 還包含兩個特殊的狀態 MIGRATING 和 IMPORTING:

當一個slot是以 MIGRATING 狀態進行設置,那么目標節點將在確認key存在的前提下接受這個hash slot的所有請求,否則查詢會被使用 -ASK 重定向至源節點。
當一個slot是以 IMPORTING 狀態進行設置,那么目標節點只接受被設置過ASKING命令的所有請求,否則查詢將會通過 -MOVED錯誤重定向至真正的hash slot所有者。
(MIGRATING 和 IMPORTING 我自己也沒太看懂 所以這里不敢保證翻譯的沒有問題)

當你第一次看到以上內容的時候或許會感到困惑,不過沒關系,現在我們來把思路理清楚。假設我們有2個Redis節點,一個叫A,另一個叫B。現在我們希望把hash slot8 從A移動到B,那么我們執行的命令應該如下:

We send B: CLUSTER SETSLOT 8 IMPORTING A
We send A: CLUSTER SETSLOT 8 MIGRATING B

所有來自客戶端對hash slot8的查詢每次都會被導向至節點A,實際過程如下:
所有對A節點存在的數據查詢會由A節點來處理
所有對A節點不存在的數據查詢會由B節點來處理
我們會發現我們將會無法在A節點創建任何新的數據,因為會被導向B節點。為了解決這個問題,我們設計了一個叫redis-trib的特殊客戶端來保證把A節點所有存在的key遷移至B節點。
我們用以下命令來處理:

CLUSTER GETKEYSINSLOT slot count

上面的命令將會返回hash slot中 count keys。對每一個key,redis-trib都會給A節點發送一個 MIGRATE 命令,這個命令會以一種原子的方式把key從A遷移到B(兩個節點在遷移過程中都會被鎖定)。
以下展示 MIGRATE 如何工作:

MIGRATE target_host target_port key target_database id timeout

MIGRATE 命令會先連接目標節點,並把目標key序列化后進行傳輸,當源節點收到OK返回值后會刪除源節點上的key刪除。所以從這個觀點上來看,一個key只能存在A或者B而不會同時存在與A和B。

ASK 重定向

在之前的章節我們說了一下ASK重定向,為什么我們不能只是簡單的使用 MOVED 重定向?因為如果使用MOVED命令則有可能會為一個key輪詢集群中所有的節點,而ASK命令只詢問下一個節點。

ASK是必要的因為在對於hash slot8的下一次查詢命令依然是發送給A節點,我們希望客戶端先嘗試在A節點找數據然后在獲取不到的情況下再向B節點請求數據。

然后我們真正的需求是客戶端在向A節點請求數據失敗后僅嘗試向B節點請求數據而不再輪詢。節點B將只接受帶ASKING命令的IMPORTING 數據查詢。

簡單說,ASKING 命令給IMPORTING slot添加了一個只輪詢一次的標記。

Clients implementations hints

TODO Pipelining: use MULTI/EXEC for pipelining.
TODO Persistent connections to nodes.
TODO hash slot guessing algorithm.

單點故障

節點故障偵測

故障偵測使用以下方法實現:

  • 如果一個節點沒有在給定時間內回復PING請求,則該一個節點會被其他節點設置 PFAIL 標志(possible failure 有可能故障)
  • 如果一個節點被設置 PFAIL 標志,那么對目標節點設置 PFAIL 標志的節點會在節點之間互相進行廣播通知並通知其他節點發送PING請求
  • 如果有一個節點被設置 PFAIL 標志,並且其他節點也認同其為 PFAIL 狀態,那么該節點會被設置為 FAIL 狀態(故障)
  • 一旦一個節點被設置 FAIL 標志,那么對故障節點設置 FAIL 標志的節點會通知其余所有節點

所以實際只有大多數節點認同的情況下,一個節點才會被設置為故障狀態
(還在努力實現)一旦一個節點被設置為故障,那么其他任何節點收到來自故障節點的PING或者連接請求則會返回“MARK AS FAIL”從而強制故障節點把自己設置為故障

集群狀態偵測(目前僅實現了一部分):每當集群配置文件發生變更,所有集群節點都會重新掃描節點列表(這可以是由更改一個hash slot 或者只是一個節點故障造成的)

每個被掃描的節點會返回以下狀態中的一個:

  • FAIL:節點故障
  • OK:節點正常

這意味着Redis集群被設計為有能力拒絕對故障節點的查詢。然而這里有一個特例,就是一個節點從被設置為 PFAIL 到被設置為 FAIL 是有時間差的,如果僅僅是被設置為 PFAIL 還是有可能對該節點嘗試連接

另外,Redis集群將不再支持MULTI/EXEC批量方法

從節點推舉制度(未實現)

每個主節點可以擁有0個或者多個從節點。當主節點發生故障的時候,從節點有責任/義務推舉自己成為主節點。假設我們有A1,A2,A3三個節點,A1是主節點並且A2和A3為A1的從節點

如果A1發生故障並且長時間未回復PING請求,那么其他節點將會將A1標記為故障節點。當這種情況發生的時候,第一個從節點將會嘗試推舉自己為主節點。

定義第一個從節點非常簡單。取所有從節點中節點ID最小的那個。如果第一個從節點也被標記為故障,那么就由第二個從節點推舉自己,以此類推。

實際流程是:集群配置被變更(節點故障導致的),故障節點所有的從節點檢測自己是否是第一個從節點。從節點在升級為主節點后會通知其他節點更改配置

保護模式(未實現)

如果部分節點由於網絡原因被隔離(比如斷網),則這些節點會停止判斷其他節點是否正常,而會開始從節點推舉或者其他操作去更改集群配置。為了防止這種情況發生,節點間一旦發現大部分節點在一段時間內被標記為 PFAIL 或者 FAIL 狀態則會立即讓集群啟動保護模式以阻止集群采取任何行動(更改配置)。

一旦集群狀態恢復正常則保護模式會被取消

主節點多數原則(未完成)

對於發生網絡故障的情況,2個或者更多的分片有能力處理所有的hash slots。而這會影響集群數據的一致性,所以網絡故障應該導致0或者只有1個分區可用。

為了強制此規則生效,所有符合主節點多數原則的節點應該強制不處理任何命令。

Publish/Subscribe(已實現,但是會重定義)

在一個Redis集群中,所有節點都被允許訂閱其他節點或者對其他節點進行廣播。集群系統會保證所有的廣播通知給所有節點。

目前的實現僅僅是簡單的一一進行廣播,但是在某種程度上廣播應該使用bloom filters或者其他算法進行優化。

另外,關於Redis Cluster的一些信息,你還可以參考本站較早的文章:《聽 antirez 講 Redis Cluster》(視頻+PPT)

 

 

http://quietmadman.blog.51cto.com/3269500/1553232

 

這里使用的 redis 版本是 redis-3.0.0-beta8(2.9.57)來安裝和配置的。
 
整個安裝和配置過程都是參考的 redis 官方的 cluster 手冊: http://redis.io/topics/cluster-tutorial
 
其他的一些 API 和 redis cluster 相關的開源項目有:
 
https://github.com/xetorthio/jedis
https://github.com/antirez/redis-rb-cluster
https://github.com/Grokzen/redis-py-cluster
 
在安裝配置之前對 redis cluster 有了一點初步的了解:
  
+ 每一個 redis cluster 節點都至少需要兩個 tcp 連接,一個是用於為 client 服務的監聽端口(如: 6379),另一個則用於為 cluster 節點通信提供的通道(cluster bus,如:6379+10000 = 16379);
+ cluster 節點之間傳輸的協議為 binary protocol,主要用於 故障檢測(failure detection),配置更新(configuration update),故障轉移授權(failover authorization)等等;
+ Redis Cluster 並沒有采用一致性 hash 算法來對 data 進行 sharding,而是采用了簡單的 hash slot 機制來實現 -- 計算給定 key 的 hash slot 槽位 -- CRC16(key) % 16384 ;
+ Redis Cluster 最多能夠支持 16384 個節點;
+ 為了把所有 slot 占滿,當節點少的時候,需要每一個節點分配一定范圍的 slots,如:
  - 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 16384
  當有新的 node 進來,那需要從 A,B,C 中移出一些 slots 分配給 D。
  當有節點要退出(如:A),那就需要將 A 上的 slots 再分配給 B 和 C。
+ Redis Cluster 支持 master-slave 模型來實現高可用性,一個節點退出,可以再次選舉出該節點的某一個 slave 節點作為新的 master 節點來服務;
  

下面開始安裝配置:

  

1,創建本地測試目錄:
  

1
mkdir  -p  /root/test/redis-cluster

  
2,本次測試以在同台主機上測試集群,端口從 7000~7005 的 6 個節點
  

1
2
cd  /root/test/redis-cluster
mkdir  7000 7001 7002 7003 7004 7005

  
3,在每個節點目錄下分別創建 redis.conf 配置文件,這里的配置文件內容都如下(端口除外):
  

1
2
3
4
5
port 7000
cluster-enabled  yes
cluster-config- file  nodes.conf
cluster-node-timeout 5000
appendonly  yes

  
創建好后,結構如下:
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[redis-cluster] # tree
.
├── 7000
│   └── redis.conf
├── 7001
│   └── redis.conf
├── 7002
│   └── redis.conf
├── 7003
│   └── redis.conf
├── 7004
│   └── redis.conf
└── 7005
     └── redis.conf
   
6 directories, 6 files

   
4,啟動這 6 個節點,如下示例:
  

1
2
cd  7000/
$ redis-server redis.conf

  
  運行后在每個節點的目錄下可以看到新增了 nodes.conf 配置,如 7000 節點的配置如下:
   

1
2
123ed65d59ff22370f2f09546f410d31207789f6 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0

   
  其中 123ed65d59ff22370f2f09546f410d31207789f6 為每個節點給自己分配的 NodeID,該 ID 在之后的 cluster 環境中唯一的標識該節點實例,每一個節點也都是通過這個 ID 來表示其他的節點的。
  
5,查看網絡監聽,可以看到同時監聽 700* + 10000

1
2
3
4
5
6
7
8
9
10
11
12
tcp        0      0 :::17003                    :::*                        LISTEN      10614 /redis-server  
tcp        0      0 :::17004                    :::*                        LISTEN      10655 /redis-server  
tcp        0      0 :::17005                    :::*                        LISTEN      10696 /redis-server  
tcp        0      0 :::7000                     :::*                        LISTEN      10483 /redis-server  
tcp        0      0 :::7001                     :::*                        LISTEN      10549 /redis-server  
tcp        0      0 :::7002                     :::*                        LISTEN      10573 /redis-server  
tcp        0      0 :::7003                     :::*                        LISTEN      10614 /redis-server  
tcp        0      0 :::7004                     :::*                        LISTEN      10655 /redis-server  
tcp        0      0 :::7005                     :::*                        LISTEN      10696 /redis-server  
tcp        0      0 :::17000                    :::*                        LISTEN      10483 /redis-server  
tcp        0      0 :::17001                    :::*                        LISTEN      10549 /redis-server  
tcp        0      0 :::17002                    :::*                        LISTEN      10573 /redis-server

  

6,下面開始搭建集群,這里使用源碼中自帶的 ruby 腳本 redis-trib.rb 來創建集群,因此首先確保系統已經安裝了最新版本的 ruby 環境
  
  redis-trib.rb 除了創建集群,還可以 check,reshard 一個存在的 cluster。
   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
$ . /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
>>> Creating cluster
Connecting to node 127.0.0.1:7000: OK
Connecting to node 127.0.0.1:7001: 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 開頭的為 master 節點
//  S 開頭的為 slave 節點,同時 replicates 后面的 NodeID 為 master 節點ID
M: 123ed65d59ff22370f2f09546f410d31207789f6 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
M: 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4 127.0.0.1:7001
    slots:5461-10922 (5462 slots) master
M: f5bdda1518cd3826100a30f5953ed82a5861ed48 127.0.0.1:7002
    slots:10923-16383 (5461 slots) master
S: 35e0f6fdadbf81a00a1d6d1843698613e653867b 127.0.0.1:7003
    replicates 123ed65d59ff22370f2f09546f410d31207789f6
S: 61dfb1055760d5dcf6519e35435d60dc5b207940 127.0.0.1:7004
    replicates 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4
S: bfc910f924d772fe03d9fe6a19aabd73d5730d26 127.0.0.1:7005
    replicates f5bdda1518cd3826100a30f5953ed82a5861ed48
    
//  回復  yes  表示接受 redis-trib 的 master-slave 的配置
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
   
//  等待 cluster 的 node 加入
Waiting  for  the cluster to  join ...
   
//  加入后開始給各個 node 分配 slots 槽位
//  如:127.0.0.1:7000 的 master 節點分配了 0-5460 的槽位
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 123ed65d59ff22370f2f09546f410d31207789f6 127.0.0.1:7000
    slots:0-5460 (5461 slots) master
M: 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4 127.0.0.1:7001
    slots:5461-10922 (5462 slots) master
M: f5bdda1518cd3826100a30f5953ed82a5861ed48 127.0.0.1:7002
    slots:10923-16383 (5461 slots) master
M: 35e0f6fdadbf81a00a1d6d1843698613e653867b 127.0.0.1:7003
    slots: (0 slots) master
    replicates 123ed65d59ff22370f2f09546f410d31207789f6
M: 61dfb1055760d5dcf6519e35435d60dc5b207940 127.0.0.1:7004
    slots: (0 slots) master
    replicates 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4
M: bfc910f924d772fe03d9fe6a19aabd73d5730d26 127.0.0.1:7005
    slots: (0 slots) master
    replicates f5bdda1518cd3826100a30f5953ed82a5861ed48
[OK] All nodes agree about slots configuration.
>>> Check  for  open  slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
[root@sasd redis-cluster] #

  

下面為使用 cluster nodes 和 slots 命令查看節點以及 slot 的信息:
  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ redis-cli -c -p 7000
//  查看節點分布信息
127.0.0.1:7000> cluster nodes
35e0f6fdadbf81a00a1d6d1843698613e653867b 127.0.0.1:7003 slave 123ed65d59ff22370f2f09546f410d31207789f6 0 1410835785216 4 connected
61dfb1055760d5dcf6519e35435d60dc5b207940 127.0.0.1:7004 slave 82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4 0 1410835784715 5 connected
82578e8ec9747e46cbb4b8cc2484c71b9b2c91f4 127.0.0.1:7001 master - 0 1410835786217 2 connected 5461-10922
123ed65d59ff22370f2f09546f410d31207789f6 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
bfc910f924d772fe03d9fe6a19aabd73d5730d26 127.0.0.1:7005 slave f5bdda1518cd3826100a30f5953ed82a5861ed48 0 1410835786717 6 connected
f5bdda1518cd3826100a30f5953ed82a5861ed48 127.0.0.1:7002 master - 0 1410835785715 3 connected 10923-16383
127.0.0.1:7000> 
//  查看 slots 分布信息
127.0.0.1:7000> cluster slots
1) 1) (integer) 5461       //  slots 的其實槽位索引
    2) (integer) 10922      //  slots 的結束槽位索引
                           //  即 [5461, 10922]
    3) 1)  "127.0.0.1"
       2) (integer) 7001    //  master
    4) 1)  "127.0.0.1"
       2) (integer) 7004    //  對應的 slave 節點
2) 1) (integer) 0
    2) (integer) 5460
    3) 1)  "127.0.0.1"
       2) (integer) 7000
    4) 1)  "127.0.0.1"
       2) (integer) 7003
3) 1) (integer) 10923
    2) (integer) 16383
    3) 1)  "127.0.0.1"
       2) (integer) 7002
    4) 1)  "127.0.0.1"
       2) (integer) 7005
127.0.0.1:7000>

本文出自 “安靜的瘋子” 博客,請務必保留此出處http://quietmadman.blog.51cto.com/3269500/1553232

 

 

 

http://blog.csdn.net/freebird_lb/article/details/7778999

 

Redis-2.4.15目前沒有提供集群的功能,Redis作者在博客中說將在3.0中實現集群機制。目前Redis實現集群的方法主要是采用一致性哈稀分片(Shard),將不同的key分配到不同的redis server上,達到橫向擴展的目的。下面來介紹一種比較常用的分布式場景:

在讀寫操作比較均勻且實時性要求較高,可以用下圖的分布式模式:

在讀操作遠遠多於寫操作時,可以用下圖的分布式模式:

 

       對於一致性哈稀分片的算法,Jedis-2.0.0已經提供了,下面是使用示例代碼(以ShardedJedisPool為例):

package com.jd.redis.client;

 

import java.util.ArrayList;

import java.util.List;

 

import redis.clients.jedis.JedisPoolConfig;

import redis.clients.jedis.JedisShardInfo;

import redis.clients.jedis.ShardedJedis;

import redis.clients.jedis.ShardedJedisPool;

import redis.clients.util.Hashing;

import redis.clients.util.Sharded;

 

publicclass RedisShardPoolTest {

    static ShardedJedisPoolpool;

    static{

        JedisPoolConfig config =new JedisPoolConfig();//Jedis池配置

        config.setMaxActive(500);//最大活動的對象個數

          config.setMaxIdle(1000 * 60);//對象最大空閑時間

          config.setMaxWait(1000 * 10);//獲取對象時最大等待時間

          config.setTestOnBorrow(true);

        String hostA = "10.10.224.44";

          int portA = 6379;

          String hostB = "10.10.224.48";

          int portB = 6379;

        List<JedisShardInfo> jdsInfoList =new ArrayList<JedisShardInfo>(2);

        JedisShardInfo infoA = new JedisShardInfo(hostA, portA);

        infoA.setPassword("redis.360buy");

        JedisShardInfo infoB = new JedisShardInfo(hostB, portB);

        infoB.setPassword("redis.360buy");

        jdsInfoList.add(infoA);

        jdsInfoList.add(infoB);

       

        pool =new ShardedJedisPool(config, jdsInfoList, Hashing.MURMUR_HASH,

Sharded.DEFAULT_KEY_TAG_PATTERN);

    }

   

    /**

     * @param args

     */

    publicstaticvoid main(String[] args) {

        for(int i=0; i<100; i++){

            String key = generateKey();

            //key += "{aaa}";

            ShardedJedis jds = null;

            try {

                jds = pool.getResource();

                System.out.println(key+":"+jds.getShard(key).getClient().getHost());

                System.out.println(jds.set(key,"1111111111111111111111111111111"));

            } catch (Exception e) {

                e.printStackTrace();

            }

            finally{

                pool.returnResource(jds);

            }

        }

    }

 

    privatestaticintindex = 1;

    publicstatic String generateKey(){

        return String.valueOf(Thread.currentThread().getId())+"_"+(index++);

    }

}

從運行結果中可以看到,不同的key被分配到不同的Redis-Server上去了。

 

       實際上,上面的集群模式還存在兩個問題:

1.       擴容問題:

因為使用了一致性哈稀進行分片,那么不同的key分布到不同的Redis-Server上,當我們需要擴容時,需要增加機器到分片列表中,這時候會使得同樣的key算出來落到跟原來不同的機器上,這樣如果要取某一個值,會出現取不到的情況,對於這種情況,Redis的作者提出了一種名為Pre-Sharding的方式:

Pre-Sharding方法是將每一個台物理機上,運行多個不同斷口的Redis實例,假如有三個物理機,每個物理機運行三個Redis實際,那么我們的分片列表中實際有9個Redis實例,當我們需要擴容時,增加一台物理機,步驟如下:

A.     在新的物理機上運行Redis-Server;

B.      該Redis-Server從屬於(slaveof)分片列表中的某一Redis-Server(假設叫RedisA);

C.      等主從復制(Replication)完成后,將客戶端分片列表中RedisA的IP和端口改為新物理機上Redis-Server的IP和端口;

D.     停止RedisA。

這樣相當於將某一Redis-Server轉移到了一台新機器上。Prd-Sharding實際上是一種在線擴容的辦法,但還是很依賴Redis本身的復制功能的,如果主庫快照數據文件過大,這個復制的過程也會很久,同時會給主庫帶來壓力。所以做這個拆分的過程最好選擇為業務訪問低峰時段進行。

http://blog.nosqlfan.com/html/3153.html

 

2.       單點故障問題:

還是用到Redis主從復制的功能,兩台物理主機上分別都運行有Redis-Server,其中一個Redis-Server是另一個的從庫,采用雙機熱備技術,客戶端通過虛擬IP訪問主庫的物理IP,當主庫宕機時,切換到從庫的物理IP。只是事后修復主庫時,應該將之前的從庫改為主庫(使用命令slaveof no one),主庫變為其從庫(使命令slaveof IP PORT),這樣才能保證修復期間新增數據的一致性。

 

 

 

http://www.searchdatabase.com.cn/showcontent_78941.htm

豁達是正確樂觀的面對失敗的系統。不需要過多的擔心,需要一種去說那又怎樣的能力。因此架構的設計是如此的重要。許多優秀的系統沒有進一步成長的能力,我們應該做的是使用其他的系統去共同分擔工作。

Redis是其中一個吸引我的系統,一個持久性的,鍵值對存儲內存操作高性能的平台。它是一個優秀的鍵值對數據庫。我已經在使用了。即使AWS最近宣布開始支持ElasticCache的下級緩存。但是一個無主的redis集群仍然起着重要的作用。我們需要多系統去完成工作。同時,我們能夠集合多種組件在一個容錯和無主的集群里共同工作么?在這片文章中我將介紹夢幻般的redis。

一致哈希

構建一個存儲數據集群的關鍵是有一個有效的數據存儲和復制機制。我希望通過一個行之有效的方法來說明建造一個數據集群,在這個過程中你可以隨意添加或移除一個Redis節點,同時保證你的數據仍然存在,而不會消失。這個方法稱為一致哈希。

由於它不是一個很明顯的概念,我將用一點時間來解釋一下。為了理解一致哈希,你可以想像有一個函數f(x),對於給定的x總是返回一個1到60(為什么是60?你會知道的,但現在請等等)之間的結果,同樣對於一個唯一的x,f(x)總是返回相同的結果。這些1到60的值按順時針排成一個環。

現在集群中的每個節點都需要一個唯一的名字。所以如果你將這個名字傳遞給f(''),它將返回一個1到60之間的數字(包括1和60),這個數字就是節點在環上的位置。當然它只是節點的邏輯(記錄的)位置。這樣,你獲得一個節點,將它傳給哈希函數,獲得結果並將它放到環上。是不是很簡單?這樣每個節點都在環上有了它自己的位置。假設這里有5個Redis節點,名字分別為'a','b','c','d','e'。每個節點都傳給哈希函數f(x)並且放到了環上。在這里f('a') = 21, f('b') = 49, f('c') = 11, f('d') = 40, f('e') = 57。一定記得這里位置是邏輯位置。

那么,我們為什么要將節點放在一個環上呢?將節點放到環上的目的是確定擁有哪些哈希空間。圖中的節點'd'擁有的哈希空間就是f('a')到f('d')(其值為40)之間的部分,包括f('d'),即(21, 40]。也就是說節點'd'將擁有鍵x,如果f(x)的屬於區間(21, 40】。比如鍵‘apple’,其值f('apple') = 35,那么鍵'apple'將被存在'd'節點。類似的,每個存儲在集群上的鍵都會通過哈希函數,在環上按順時針方向被恰當地存到最近的節點。

雖然一致哈希講完了,但應知道,在多數情況下,這種類型的系統是伴隨着高可用性而構建。為了滿足數據的高可用性,需要根據一些因子進行復制, 這些因子稱為復制因子。假設我們集群的復制因子為2,那么屬於'd'節點的數據將會被復制到按順時針方向與之相隔最近的'b'和'e'節點上。這就保證了如果從'd'節點獲取數據失敗,這些數據能夠從'b'或'e'節點獲取。

不僅僅是鍵使用一致哈希來存儲,也很容易覆蓋失敗了的節點,並且復制因子依然完好有效。比如'd'節點失敗了,'b'節點將獲取'd'節點哈希空間的所有權,同時'd'節點的哈希空間能夠很容易地復制到'c'節點。

壞事和好事

壞事就是目前這些討論過的所有概念,復制(冗余),失效處理以及集群規模等,在Redis之外是不可行的。一致哈希僅僅描述了節點在哈希環上的映射以及那些哈希數據的所有權,盡管這樣,它仍然是構建一個彈性可擴展系統的極好的開端。

好事就是,也有一些分立的其他工具在Redis集群上實現一致哈希,它們能提醒節點失效和新節點的加入。雖然這個功能不是一個工具的一部分,我們將看到如何用多個系統來使一個理想化的Redis集群運轉起來。

Twemproxy aka Nutcracker

Twemproxy是一個開源工具,它是一個基於memcached和Redis協議的快速、輕量的代理。其本質就是,如果你有一些Redis服務器在運行,同時希望用這些服務器構建集群,你只需要將Twemproxy部署在這些服務器前端,並且讓所有Redis流量都通過它。

Twemproxy除了能夠代理Redis流量外,在它存儲數據在Redis服務器時還能進行一致哈希。這就保證了數據被分布在基於一致哈希的多個不同Redis節點上。

但是Twemproxy並沒有為Redis集群建立高可用性支持。最簡單的辦法是為集群中的每個節點都建立一個從(冗余)服務器,當主服務器失效時將從(冗余)服務器提升為主服務器。為Redis配置一個從服務器是非常簡單的。

這種模型的缺點是非常明顯的,它需要為Redis集群中的每個節點同時運行兩個服務器。但是節點失效也是非常明顯,並且更加危險,所以我們怎么知道這些問題並解決。

Gossip on Serf

Gossip是一個標准的機制,通過這個機制集群上的節點可以很清楚的了解成員的最新情況。這樣子集群中的每個節點就很清楚集群中節點的變化,如節點的新增和節目的刪除。

Serf通過實現Gossip機制提供這樣的幫助。Serf是一個基於代理的機制,這個機制實現了Gossip的協議達到節點成員信息交換的目的。Serf是不斷運行,除此之外,它還可以生成自定義的事件

現在拿我們的節點集群為例,如果每個節點上也有一個serf代理正在運行,那么節點與節點之間可以進行細節交換.因此,群集中的每個節點都能清楚知道其他節點的存在,也能清楚知道他們的狀態。

這還並不夠,為了高可靠性我們還需要讓twemproxy知道何時一個節點已經失效,這樣的話它就可以據此修改它的配置。像前面提到的Serf,就可以做到這一點,它是基於一些gossip觸發的事件,使用自定義動作實現的。因此只要集群中的一個Redis節點因為一些原因宕掉了,另一個節點就可以發送有成員意外掉線的消息給任何給定的端點,這個端點在我們的案例中也就是twemproxy服務器。

這還不是全部

現在我們有了Redis集群,基於一致性哈希環,相應的是用twemproxy存儲數據(一致性哈希),還有Serf,它用gossip協議來檢測Redis集群成員失效,並且向twemproxy發送失效消息;但是我們還沒有建立起理想化的Redis集群。

消息偵聽器

雖然Serf可以給任何端點發送成員離線或者成員上線消息。然而twemproxy卻沒有偵聽此類事件的機制。因此我們需要自定義一個偵聽器,就像Redis-Twenproxy代理,它需要做以下這些事情。

  • 偵聽Serf消息
  • 更新nutcraker.yml 以反映新的拓撲
  • 重啟twemproxy

這個消息偵聽器可以是一個小型的http服務器;它在收到一批POST數據的時候,為twemproxy做以上列表中的動作。需要記住的是,這種消息應該是一個原子操作;因為當一個節點失效(或者意外離線)的時候,所有能發消息的活動節點都將發送這個失效事件消息給偵聽器;但是偵聽器應該只響應一次。

數據復制

在上面的“一致哈希”中,我提到了Redis集群中的復制因素。同樣它也不是Twemproxy的固有特性;Twemproxy只關心使用一致哈希存儲一個拷貝。所以在我們追求理想化Redis集群的過程中,我們還需要給twemproxy或者redis自己創建這種復制的能力。

為了給Twenproxy創建復制能力,需要將復制因子作為一個配置項目,並且將數據保存在集群中相鄰的redis節點(根據復制因子)。由於twemproxy知道節點的位置,所以這將給twemproxy增加一個很棒的功能。

 

由於twemproxy只不過是代理服務器,它的簡單功能就是它強大的地方,為它創建復制管理功能將使它臃腫膨脹。

Redis 主從環

在思考這其中的工作機制的時候,我忽然想到,為什么不將每個節點設置成另一個節點的副本,或者說從節點,並由此而形成一個主從環呢?

 

這樣的話如果一個節點失效了,失效節點的數據在這個環上相鄰的節點上仍然可以獲得。而那個具有該數據副本的節點,將作為該節點的從節點,並提供保存數據副本的服務。這是一個既是主節點也是從節點的環。Serf仍像通常一樣作為散布節點失效消息的代理。但是這一回,我們twenproxy上的的客戶端,即偵聽器,將不僅僅只更新twenproxy上的失效信息,還要調整redis服務器群集來適應這個變化。

在這個環中有一個明顯的,同樣也是技術方面的缺陷。這個明顯的缺陷是,這個環會壞掉,因為從節點的從節點無法判別哪一個是它的主節點的數據,哪一個是它的主節點的主節點的數據。這樣的話就會循環的傳送所有的數據。

同樣技術性的問題是,一旦redis將主節點的數據同步給從節點,它就會將從節點的數據擦除干凈;這樣就將曾經寫到從節點的所有數據刪除了。這種主從環顯然不能實際應用,除非修改主從環的復制機制以適應我們的需求。這樣的話每個從節點就不會將它的主節點的數據同步給它的從節點。要想實現這一點,必須的條件是每個節點都可以區分出自己的密鑰空間,以及它的主節點的密鑰空間;這樣的話它就不會將主節點的數據傳送給它的從節點。

那么這樣一來,當一個節點失效時,就需要執行四個動作。一,將失效節點的從節點作為它的密鑰空間的所有者。二,將這些密鑰散布給失效節點從節點的從節點,以便進行復制。三,將失效節點的從節點作為失效節點的主節點的從節點。最后,在新的拓撲上復位twemproxy。

如何理想化?

事實上並沒有這樣的Redis集群,它可以具有一致性的哈希,高可靠性以及分區容錯性。因此最后一幅圖片描繪了一種理想化的Redis集群;但這並不是不可能的。接下來將羅列一下需要哪些條件才能使之成為一個實實在在的產品。

透明的Twenproxy

有必要部署一個Twenproxy,這會使得Hash散列中Redis各節點的位置都是透明的。每個Redis節點都可以知道自己以及其相鄰節點的位置,這些信息對於節點的主從復制以及失敗節點的修復是有必要的。自從Twenproxy開源之后,節點的位置信息可以被修改、以及擴展。

Redis的數據擁有權

這是比較困難的部分的,每個Redis節點都應該記錄自身擁有的數據,以及哪些是主節點的數據。當前這樣的隔離是不可能的。這也需要修改Redis的基礎代碼,這樣節點才知道何時與從節點同步,什么時候不需要。

綜上所述,我們的理想化的Redis集群變成現實需要修改這樣的兩個組件。一直以來,它們都是非常大的工業級別,使用在生產環境中。這已經值得任何人去實現這個集群了。

原文出處:http://megam.in/post/66659169136/utopian-redis-cluster

 

 

 

http://www.cnblogs.com/lulu/archive/2013/06/10/3130906.html

使用zookeeper 實現一致性hash。

redis服務啟動時,將自己的路由信息通過臨時節點方式寫入zk,客戶端通過zk client讀取可用的路由信息。

image_thumb[12]

 

服務端

使用python 腳本寫的守護進程:https://github.com/LittlePeng/redis-manager

腳本部署在redis-server本機,定時ping redis-server

節點失效的情況:

1.服務器與ZK服務器失去連接 Session Expired ,環境網絡波動造成,需要根據網絡情況設置適當zookeeper的Timeout時間,避免此情況發生

2. 服務器宕機,Zookeeper server 發現zkclient ping超時,就會通知節點下線

3. redis-server 掛了,redis-manager ping 超時主動斷開與zookeeper server的連接

 

客戶端

需要zkclient監控 節點變化,及時更新路由策略

下面是C# 版本一致性hash算法:

   1:  class KetamaNodeLocator
   2:      {
   3:          private Dictionary<long, RedisCluster> ketamaNodes;
   4:          private HashAlgorithm hashAlg;
   5:          private int numReps = 160;
   6:          private long[] keys;
   7:   
   8:          public KetamaNodeLocator(List<RedisCluster> nodes)
   9:          {
  10:              ketamaNodes = new Dictionary<long, RedisCluster>();
  11:   
  12:              //對所有節點,生成nCopies個虛擬結點
  13:              for (int j = 0; j < nodes.Count; j++) {
  14:                  RedisCluster node = nodes[j];
  15:                  int numReps = node.Weight;
  16:   
  17:                  //每四個虛擬結點為一組
  18:                  for (int i = 0; i < numReps / 4; i++) {
  19:                      byte[] digest = ComputeMd5(
  20:                          String.Format("{0}_{1}_{2}", node.RoleName, node.RouteValue, i));
  21:   
  22:                      /** Md5是一個16字節長度的數組,將16字節的數組每四個字節一組,
  23:   * 分別對應一個虛擬結點,這就是為什么上面把虛擬結點四個划分一組的原因*/
  24:                      for (int h = 0; h < 4; h++) {
  25:   
  26:                          long rv = ((long)(digest[3 + h * 4] & 0xFF) << 24)
  27:                                     | ((long)(digest[2 + h * 4] & 0xFF) << 16)
  28:                                     | ((long)(digest[1 + h * 4] & 0xFF) << 8)
  29:                                     | ((long)digest[0 + h * 4] & 0xFF);
  30:   
  31:                          rv = rv & 0xffffffffL; /* Truncate to 32-bits */
  32:                          ketamaNodes[rv] = node;
  33:                      }
  34:                  }
  35:              }
  36:   
  37:              keys = ketamaNodes.Keys.OrderBy(p => p).ToArray();
  38:          }
  41:          public RedisCluster GetWorkerNode(string k)
  42:          {
  43:              byte[] digest = ComputeMd5(k);
  44:              return GetNodeInner(Hash(digest, 0));
  45:          }
  46:   
  47:          RedisCluster GetNodeInner(long hash)
  48:          {
  49:              if (ketamaNodes.Count == 0)
  50:                  return null;
  51:              long key = hash;
  52:              int near = 0;
  53:              int index = Array.BinarySearch(keys, hash);
  54:              if (index < 0) {
  55:                  near = (~index);
  56:                  if (near == keys.Length)
  57:                      near = 0;
  58:              }
  59:              else {
  60:                  near = index;
  61:              }
  62:   
  63:              return ketamaNodes[keys[near]];
  64:          }
  65:   
  66:          public static long Hash(byte[] digest, int nTime)
  67:          {
  68:              long rv = ((long)(digest[3 + nTime * 4] & 0xFF) << 24)
  69:                      | ((long)(digest[2 + nTime * 4] & 0xFF) << 16)
  70:                      | ((long)(digest[1 + nTime * 4] & 0xFF) << 8)
  71:                      | ((long)digest[0 + nTime * 4] & 0xFF);
  72:   
  73:              return rv & 0xffffffffL; /* Truncate to 32-bits */
  74:          }
 
  79:          public static byte[] ComputeMd5(string k)
  80:          {
  81:              MD5 md5 = new MD5CryptoServiceProvider();
  82:   
  83:              byte[] keyBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(k));
  84:              md5.Clear();
  85:              return keyBytes;
  86:          }
  87:      }


 

 

 

 

..


免責聲明!

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



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