博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redis集群方案redis-twemproxy-keepalived
阅读量:4106 次
发布时间:2019-05-25

本文共 4989 字,大约阅读时间需要 16 分钟。

架构图
 






机器说明
 


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的例子见
 

一个更完美的方案是: 

 

使用了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这样的参数 


部署说明就到这里 

转载地址:http://ffnsi.baihongyu.com/

你可能感兴趣的文章
Node.js-模块和包
查看>>
实现接口创建线程
查看>>
JavaScript实现页面无刷新让时间走动
查看>>
CSS实例:Tab选项卡效果
查看>>
前端设计之特效表单
查看>>
Java的时间操作玩法实例若干
查看>>
JavaScript:时间日期格式验证大全
查看>>
解决SimpleDateFormat线程安全问题NumberFormatException: multiple points
查看>>
MySQL数据库存储引擎简介
查看>>
处理Maven本地仓库.lastUpdated文件
查看>>
计算机网络-网络协议模型
查看>>
计算机网络-OSI各层概述
查看>>
Java--String/StringBuffer/StringBuilder区别
查看>>
分布式之redis复习精讲
查看>>
(python版)《剑指Offer》JZ01:二维数组中的查找
查看>>
(python版)《剑指Offer》JZ06:旋转数组的最小数字
查看>>
(python版)《剑指Offer》JZ13:调整数组顺序使奇数位于偶数前面
查看>>
(python版)《剑指Offer》JZ28:数组中出现次数超过一半的数字
查看>>
(python版)《剑指Offer》JZ30:连续子数组的最大和
查看>>
(python版)《剑指Offer》JZ02:替换空格
查看>>