keepalived+twemproxy部署redis集羣高可用

轉載原文鏈接:http://bylijinnan.iteye.com/blog/2175191

架構圖 





機器說明 

Java代碼  收藏代碼
  1. 10.75.201.67:keepalived + twemproxy  
  2. 10.75.201.66:keepalived + twemproxy  
  3. 初始化時,VIP綁定在10.75.201.67  
  4. 10.75.201.26:ClusterA(redis master A + redis slave A1)  
  5. 10.75.201.68:ClusterB(redis master B + redis slave B1)  


如果機器充足的話,redis master A與redis slave A1部署在兩臺機器上(redis master B + redis slave B1也一樣)
爲實驗方便,目前redis master與redis slave是在同一機器上,通過不同的端口來啓動 

安裝目錄 
Java代碼  收藏代碼
  1. /home/redis:  
  2. |-- nutcracker  
  3. |   |-- conf  
  4. |   |-- sbin  
  5. |-- redis  
  6. |   |-- bin  
  7. |   |-- conf  
  8. |   `-- logs  

版本 
redis-2.8.19 
nutcracker-0.4.0 
keepalived-1.2.12 

各框架作用: 
1.keepalived提供VIP漂移,避免twemproxy的單點故障 
2. twemproxy作爲redis代理,可以提供一致性哈希;當它代理的某個Cluster掛掉了,它會把該Cluster移除,並把原本屬於該Cluster的讀寫請求按哈希算法重新分派給另外的Cluster 
3.ClusterA,ClusterB,ClusterC各有一主一從。可以橫向擴展,增加ClusterD、ClusterE等 

說明: 
上述方案有個瑕疵: 
當ClusterX中的redis master掛掉後,整個ClusterX就被twemproxy移除了(即使redis slave還正常)。可以通過keepalived或sentinel來使得slave可以在master掛掉時升級爲master並綁定VIP。但這樣意義不大,配置相對複雜(使用sentinel的例子見http://blog.youyo.info/blog/2014/05/24/redis-cluster/) 
一個更完美的方案是: 
https://blog.recurly.com/2014/05/clustering-redis-maximize-uptime-scale 
使用了keepalived+twemproxy+smitty+sentinel 
sentinel可以使得redis slave升級爲master,而smitty可以監測到該變化並更新twemproxy的配置文件 
但到smitty的github上看,smitty還不能應用到生產環境: 





配置 

keepalived+twemproxy 

vim /etc/keepalived/keepalived.conf 
Java代碼  收藏代碼
  1. vrrp_script chk_nutcraker {  
  2.                 script "</dev/tcp/127.0.0.1/63790" #監測nutcraker是否正常  
  3.                 interval 2  
  4. }  
  5. vrrp_instance VI_2 {  
  6.         state BACKUP        #both BACKUP  
  7.         interface eth1  
  8.         virtual_router_id 12  
  9.         priority 101    #101 on master, 100 on backup  
  10.         nopreempt       #both nopreempt  
  11.         track_script {  
  12.                 chk_nutcraker  
  13.         }  
  14.         virtual_ipaddress {  
  15.              10.75.201.3  
  16.         }  
  17. }  

兩臺keepalived都配置爲BACKUP + nopreempt,表示不搶佔,避免VIP不必要的漂移;爲了使得初始時VIP綁定在10.75.201.67上,配置10.75.201.67的優先級爲101,10.75.201.66爲100 

vim  /home/redis/nutcracker/conf/nutcracker.yml 
Java代碼  收藏代碼
  1. nutcrakerB:  
  2.   listen: 0.0.0.0:63790    #nutcraker在端口63790啓動。keepalived應該監控該端口  
  3. hash: one_at_a_time  
  4.   hash_tag: "{}"  
  5.   distribution: modula  
  6.   auto_eject_hosts: true  
  7.   redis: true  
  8.   server_retry_timeout: 2000  
  9.   server_failure_limit: 1  
  10. timeout: 400  
  11.   servers:  
  12.    - 10.75.201.26:6379:1    #這裏只需要寫Cluster中redis master的IP和端口  
  13.    - 10.75.201.68:6379:1    #同上  

說明: 
hash: one_at_a_time 
hash_tag: "{}" 
distribution: modula 
這三行配置在測試時可採用,可以準確地知道數據將會保存在哪臺機器: 
distribution: modula表示根據key值的hash值取模,根據取模的結果選擇對應的服務器 
hash_tag: "{}"表示計算hash值時,只取key中包含在{}裏面的那部分來計算 
one_at_a_time計算hash值的,java版本的實現: 
Java代碼  收藏代碼
  1. private static int oneAtATime (String k) {  
  2.         int hash = 0;  
  3.         try {  
  4.             for (byte bt : k.getBytes("utf-8")) {  
  5.                 hash += (bt & 0xFF);  
  6.                 hash += (hash << 10);  
  7.                 hash ^= (hash >>> 6);  
  8.             }  
  9.             hash += (hash << 3);  
  10.             hash ^= (hash >>> 11);  
  11.             hash += (hash << 15);  
  12.         } catch (Exception e) {  
  13.             e.printStackTrace();  
  14.         }  
  15.         return hash;  
  16.     }  

測試可得: 
oneAtATime("a") % 2得到0 
oneAtATime("b") % 2得到1 
因此,zzz{a}xxx=yyy這樣的鍵值對會保存在10.75.201.26,而xxx{b}yyy=zzz則保存在10.75.201.68 

生產環境可採用: 
hash: fnv1a_64 
distribution: ketama 


Redis Cluster 

目錄結構: 
Java代碼  收藏代碼
  1. |--/home/redis/redis  
  2.    |--bin  
  3.    |--6379  
  4.       |--redis.conf  
  5.       |--redis.pid  
  6.    |--63791  
  7.       |--redis.conf  
  8.       |--redis.pid  


6379爲redis master,63791爲redis slave 
需要修改redis.conf中對應的配置: 

vim /home/redis/redis/6379/redis.conf 
daemonize yes 
pidfile /home/redis/redis/6379/redis.pid 
port 6379 

在63791/redis.conf中還要配置: 
slaveof 127.0.0.1 6379 

啓動 

1.啓動redis 
在10.75.201.26和10.75.201.68上啓動: 
redis-server /home/redis/redis/6379/redis.conf 
redis-server /home/redis/redis/63791/redis.conf 
2.啓動twemproxy+keepalived 
先啓動10.75.201.67: 
nutcracker -d -c /home/redis/nutcracker/conf/nutcracker.yml 
service keepalived start 
再啓動10.75.201.66,重複上述操作 

測試驗證 

1.正常情況下 
查看10.75.201.26的redis,6379爲master,63791爲slave 
查看10.75.201.68的redis,6379爲master,63791爲slave 

客戶端連接並寫入: 

redis-cli -h 10.75.201.3 -p 63790 
10.75.201.3:63790> set {a}1 a1 
10.75.201.3:63790> set {b}1 b1 
則{a}1=a1寫到10.75.201.26,{b}1=b1寫入10.75.201.68 

在10.75.201.26上(:6379以及:63791): 
get {a}1得到a1,get {b}1得到nil 

在10.75.201.68上(:6379以及:63791) 
get {a}1得到nil,get {b}1得到b1 

2.把10.75.201.67上的twemproxy或keepalived進程kill掉 
則VIP轉移到10.75.201.66: 
在10.75.201.66上執行ip add | grep eth1,輸出: 
eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UNKNOWN qlen 1000 
    inet 10.75.201.66/24 brd 10.75.201.255 scope global eth1 
    inet 10.75.201.3/32 scope global eth1 
此時客戶端仍可連接redis-cli -h 10.75.201.3 -p 63790並進行讀寫,與正常情況下沒什麼區別 

3.把10.75.201.26的redis master進程kill掉: 
lsof -i:6379 
kill -9 <pid> 
則客戶端取不到之前寫入ClusterA的數據了: 
10.75.201.3:63790> get {a}1 
(nil) 

但ClusterA上的數據還在ClusterA-redis-slave上: 
10.75.201.26:63791> get {a}1 
"a1" 

注意客戶端有可能: 
10.75.201.3:63790> get {a}1 
(error) ERR Connection refused 
10.75.201.3:63790> get {a}1 
(nil) 
第一次表明沒有連接上,第二次表明連接上了但查詢不到數據 
這時需要注意客戶端的重連和失敗次數設置,官方文檔說: 

To ensure that requests always succeed in the face of server ejections (auto_eject_hosts: is enabled), some form of retry must be implemented at the client layer since nutcracker itself does not retry a request. This client-side retry count must be greater than server_failure_limit: value, which ensures that the original request has a chance to make it to a live server. 

因此代碼裏可以這樣寫: 
Java代碼  收藏代碼
  1. int retryTimes = 2;  
  2. boolean done = false;  
  3. while (!done && retryTimes > 0) {  
  4.     try {  
  5.               bean.getRedisTemplate().opsForHash().put("{a}4""a4".hashCode(),"a4");  
  6.               done = true;  
  7.           } catch (Exception e) {  
  8.               e.printStackTrace();  
  9.           } finally {  
  10.               retryTimes--;  
  11.           }  
  12. }  

代碼略顯醜陋,不知爲什麼RedisTemplate沒有類似retryTimes這樣的參數 

部署說明就到這裏 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章