前面咱們談了Redis Sharding多服務器集羣技術,Redis Sharding是客戶端Sharding技術,對於服務端來講,各個Redis服務器彼此是相互獨立的,這對於服務端根據須要靈活部署Redis很是輕便,Redis Sharding具備很好的靈活性、可伸縮性,是一種輕量級集羣技術。node
本篇,介紹另一種多Redis服務器集羣技術,即Redis Cluster。Redis Cluster是一種服務器Sharding技術,3.0版本開始正式提供。redis
Redis Cluster中,Sharding採用slot(槽)的概念,一共分紅16384個槽,這有點兒相似前面講的pre sharding思路。對於每一個進入Redis的鍵值對,根據key進行散列,分配到這16384個slot中的某一箇中。使用的hash算法也比較簡單,就是CRC16後16384取模。算法
Redis集羣中的每一個node(節點)負責分攤這16384個slot中的一部分,也就是說,每一個slot都對應一個node負責處理。當動態添加或減小node節點時,須要將16384個槽作個再分配,槽中的鍵值也要遷移。固然,這一過程,在目前實現中,還處於半自動狀態,須要人工介入。spring
Redis集羣,要保證16384個槽對應的node都正常工做,若是某個node發生故障,那它負責的slots也就失效,整個集羣將不能工做。瀏覽器
爲了增長集羣的可訪問性,官方推薦的方案是將node配置成主從結構,即一個master主節點,掛n個slave從節點。這時,若是主節點失效,Redis Cluster會根據選舉算法從slave節點中選擇一個上升爲主節點,整個集羣繼續對外提供服務。這很是相似前篇文章提到的Redis Sharding場景下服務器節點經過Sentinel監控架構成主從結構,只是Redis Cluster自己提供了故障轉移容錯的能力。ruby
Redis Cluster的新節點識別能力、故障判斷及故障轉移能力是經過集羣中的每一個node都在和其它nodes進行通訊,這被稱爲集羣總線(cluster bus)。它們使用特殊的端口號,即對外服務端口號加10000。例如若是某個node的端口號是6379,那麼它與其它nodes通訊的端口號是16379。nodes之間的通訊採用特殊的二進制協議。服務器
對客戶端來講,整個cluster被看作是一個總體,客戶端能夠鏈接任意一個node進行操做,就像操做單一Redis實例同樣,當客戶端操做的key沒有分配到該node上時,Redis會返回轉向指令,指向正確的node,這有點兒像瀏覽器頁面的302 redirect跳轉。架構
Redis Cluster是Redis 3.0之後才正式推出,時間較晚,目前能證實在大規模生產環境下成功的案例還不是不少,須要時間檢驗。app
集羣搭建R工具
下面,咱們就實際操做創建一個Redis Cluster。咱們將創建3個node,每一個node架構成一主一從,故總共有6個redis實例,端口號從7000-7005。
Cluster下的Redis實例,和普通Redis實例同樣,只是處於cluster模式方式下運行。
實操步驟以下:
1. 以3.0.5爲例,創建cluster目錄,其下再建6個子目錄表明6實例環境
mkdir cluster
mkdir 7000 7001 7002 7003 7004 7005
2. 將redis.conf模板分別copy到上面6子目錄中,並作以下修改,以7000爲例:
修改以下信息
daemonize yes
pidfile /var/run/redis-7000.pid
port 7000
logfile "/var/log/redis-7000.log"
註釋掉以下信息, 不須要RDB持久化
#save 900 1
#save 300 10
#save 60 10000
修改以下信息
appendonly yes
appendfilename "appendonly-7000.aof"
取消以下注釋,讓Redis在集羣模式下運行
cluster-enabled yes 啓動cluster模式
cluster-config-file nodes-7000.conf 集羣信息文件名,由redis本身維護
cluster-node-timeout 15000 15秒中聯繫不到對方node,即認爲對方有故障可能
3. 在各個目錄下執行 redis-server redis.conf 啓動redis實例
這時,這幾個實例都是各自是一個集羣狀態在運行,並無造成一個總體集羣態,咱們須要Redis提供的基於ruby開發的工具進行人工設置。
4.安裝ruby環境
yum install ruby ruby-devel rubygems
5.安裝Redis的ruby依賴接口
gem install redis
6.利用腳本工具創建集羣
./redis-trib.rb create --replicas 1 192.168.1.142:7000 192.168.1.142:7001 192.168.1.142:7002 192.168.1.142:7003 192.168.1.142:7004 192.168.1.142:7005
該腳本自動執行節點分配方案,將前3個redis實例做爲主節點,後三個做爲從節點,如圖:
按提示敲入"yes",執行方案,將6個節點組成集羣,3主3從。
執行redis-cli -p 7000 info Replication 命令,觀察7000這個節點,發現其複製配置信息已配置成主節點,並有一個從節點7003
再執行redis-cli -p 7003 info Replication發現,該節點已設置成從節點。這些主從設置,都是在建立集羣時自動完成的。
至此,3主3從的Redis集羣創建起來了。接下來咱們作個故障轉移試驗,將主節點7001 shutdown掉,看看發生什麼?
咱們看到,從節點7004會上升爲主節點繼續提供集羣服務。那又從新啓動7001節點呢?
咱們發現7001節點已經成爲從節點,不會成爲取代7004成爲主節點。那若是將主節點7001和從節點7004都shutdown掉呢?
這時,整個cluster是拒絕提供服務的。由於原來7001分配的slot如今無節點接管,須要人工介入從新分配slots。
增刪集羣節點R
下面咱們操做下,若是修改集羣節點架構:
刪除一個從節點。注意,若是刪除主節點,其負責的slots必須爲空。
./redis-trib.rb del-node 192.168.1.142:7000 ee2fc0ea6e630f54e3b811caedf8896b26a99cba
將7001節點的slot都轉移到7000
./redis-trib.rb reshard 192.168.1.142:7000
按提示操做便可。
加一個節點。注意新添加的節點尚未分配slot,用reshard給它分配必定比例的slots
./redis-trib.rb add-node 192.168.1.142:7001 192.168.1.142:7000
給指定一個主節點添加一個從節點。
./redis-trib.rb add-node --slave --master-id f4d17d56a9dda1a102da7cd799192beff7cba69e 192.168.1.142:7004 192.168.1.142:7000
注意若是此redis實例參與過集羣,需先cluster reset 清除重置一下。
Jedis客戶端訪問R
上面介紹了服務端Redis Cluster搭建過程。下面來看看客戶端如何使用?
Java語言的客戶端驅動Jedis是支持Redis Cluster的。咱們具體實操下:
1. pom.xml中配置jedis jar包
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.0</version> <!-- 最近升級了 -->
</dependency>
2. spring配置文件中配置JedisCluster
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="4096"/>
<property name="maxIdle" value="200"/>
<property name="maxWaitMillis" value="3000"/>
<property name="testOnBorrow" value="true" />
<property name="testOnReturn" value="true" />
</bean>
<bean id = "jedisCluster" class = "redis.clients.jedis.JedisCluster">
<constructor-arg index="0">
<set>
<bean class="redis.clients.jedis.HostAndPort">
<constructor-arg index="0" value="192.168.1.142" />
<constructor-arg index="1" value="7002" />
</bean>
</set>
</constructor-arg>
<constructor-arg index="1" value="2000" type="int"/>
<constructor-arg index="2" value="2" type="int"/>
<constructor-arg index="3" ref="poolConfig"/>
</bean>
3.編寫測試代碼
@Test
public void basicOpTestForCluster(){
long begin = System.currentTimeMillis();
for(int i=0;i<10000; i++){
jedis.set("person." + i + ".name", "frank");
jedis.set("person." + i + ".city", "beijing");
String name = jedis.get("person." + i + ".name");
String city = jedis.get("person." + i + ".city");
assertEquals("frank",name);
assertEquals("beijing",city);
jedis.del("person." + i + ".name");
Boolean result = jedis.exists("person." + i + ".name");
assertEquals(false,result);
result = jedis.exists("person." + i + ".city");
assertEquals(true,result);
}
long end = System.currentTimeMillis();
System.out.println("total time: " + (end-begin)/1000);
}