轉載原文鏈接:http://bylijinnan.iteye.com/blog/2175191
架構圖
機器說明
- 10.75.201.67:keepalived + twemproxy
- 10.75.201.66:keepalived + twemproxy
- 初始化時,VIP綁定在10.75.201.67
- 10.75.201.26:ClusterA(redis master A + redis slave A1)
- 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是在同一機器上,通過不同的端口來啓動
安裝目錄
- /home/redis:
- |-- nutcracker
- | |-- conf
- | |-- sbin
- |-- redis
- | |-- bin
- | |-- conf
- | `-- 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
- vrrp_script chk_nutcraker {
- script "</dev/tcp/127.0.0.1/63790" #監測nutcraker是否正常
- interval 2
- }
- vrrp_instance VI_2 {
- state BACKUP #both BACKUP
- interface eth1
- virtual_router_id 12
- priority 101 #101 on master, 100 on backup
- nopreempt #both nopreempt
- track_script {
- chk_nutcraker
- }
- virtual_ipaddress {
- 10.75.201.3
- }
- }
兩臺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
- nutcrakerB:
- listen: 0.0.0.0:63790 #nutcraker在端口63790啓動。keepalived應該監控該端口
- hash: one_at_a_time
- hash_tag: "{}"
- distribution: modula
- auto_eject_hosts: true
- redis: true
- server_retry_timeout: 2000
- server_failure_limit: 1
- timeout: 400
- servers:
- - 10.75.201.26:6379:1 #這裏只需要寫Cluster中redis master的IP和端口
- - 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版本的實現:
- private static int oneAtATime (String k) {
- int hash = 0;
- try {
- for (byte bt : k.getBytes("utf-8")) {
- hash += (bt & 0xFF);
- hash += (hash << 10);
- hash ^= (hash >>> 6);
- }
- hash += (hash << 3);
- hash ^= (hash >>> 11);
- hash += (hash << 15);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return hash;
- }
測試可得:
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
目錄結構:
- |--/home/redis/redis
- |--bin
- |--6379
- |--redis.conf
- |--redis.pid
- |--63791
- |--redis.conf
- |--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.
因此代碼裏可以這樣寫:
- int retryTimes = 2;
- boolean done = false;
- while (!done && retryTimes > 0) {
- try {
- bean.getRedisTemplate().opsForHash().put("{a}4", "a4".hashCode(),"a4");
- done = true;
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- retryTimes--;
- }
- }
代碼略顯醜陋,不知爲什麼RedisTemplate沒有類似retryTimes這樣的參數
部署說明就到這裏