本文共 4989 字,大约阅读时间需要 16 分钟。
架构图 机器说明 - 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的例子见 一个更完美的方案是: 使用了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这样的参数 部署说明就到这里 转载地址:http://ffnsi.baihongyu.com/