Redis-客户端使用Cluster
标签:Redis

客户端使用Cluster

1. moved重定向

  1. 发送键命令
  2. 根据值计算hash值再去对16383取余,计算槽位置
  3. 查看是否指向自身
    1. 如果是自己的话,将会返回指向命令的结果
    2. 不是自己的话,将会返回moved异常
  4. 客户端接收到moved异常,根据异常信息中的目标节点再重新去执行(注意客户端不会自动的找到目标节点去跳转,而是要自己手动的写一下跳转)

1.1 槽命中

我们可以通过 cluster keyslot hello计算出槽的值在866,在6379的槽的范围内,故直接返回执行后的结果。

1.2 不命中,moved异常

发送 set php best 返回moved异常,表示槽的位置,最后再重新去执行set操作。

1.3 测试

[root@localhost bin]# ./redis-cli -p 7000
127.0.0.1:7000> set hello world
OK
127.0.0.1:7000> cluster keyslot hello
(integer) 866
127.0.0.1:7000> set php best
(error) MOVED 9244 127.0.0.1:7001
127.0.0.1:7000> 

上面采用的普通的连接方式,如果set的值对应的槽不在该节点上,则返回moved异常。

[root@localhost bin]# ./redis-cli -c -p 7000
127.0.0.1:7000> set php best
-> Redirected to slot [9244] located at 127.0.0.1:7001
OK
127.0.0.1:7001> get php
"best"
127.0.0.1:7001> 

上面使用的 redis-cli -c -p 7000 去连接的,如果不在该节点上,会自动去跳转到对应的节点去执行,再返回结果,并且会连接到新的客户端上并且能从中获得set进去的值。

2. ASK重定向

客户端连接source节点时,slot正在迁移中。

  1. 发送键命令
  2. 回复客户端,ask已经转向
  3. 客户端收到ask异常后,先发送一条asking命令
  4. 再发送命令
  5. target返回响应结果

2.1 moved和ask对比

3. smart客户端

3.1 原理

  1. 从集群中选择一个可运行节点,使用cluster slots初始化槽和节点映射
  2. 将cluster slots的结果映射到本地(可以存为一个map),为每个节点创建JedisPool。
  3. 准备执行命令。

4. JedisCluster使用

使用技巧:

测试代码:

package com.liuyao;

import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.*;

import java.util.HashSet;

/**
 * Created By liuyao on 2018/6/14 17:03.
 */
@Slf4j
public class RedisClusterTest {
    public static void main(String[] args) {
        HashSet<HostAndPort> nodes = new HashSet<>();
        nodes.add(new HostAndPort("192.168.91.136", 7000));
        nodes.add(new HostAndPort("192.168.91.136", 7001));
        nodes.add(new HostAndPort("192.168.91.136", 7002));
        nodes.add(new HostAndPort("192.168.91.136", 7003));
        nodes.add(new HostAndPort("192.168.91.136", 7004));
        nodes.add(new HostAndPort("192.168.91.136", 7005));
        System.out.println(nodes);
        JedisCluster jedisCluster = new JedisCluster(nodes, 10000, new JedisPoolConfig());
        jedisCluster.set("hello", "world");
        System.out.println(jedisCluster.get("hello"));
    }
}

4.1 问题总结

注意上面我最开始总是 can't get resources from pool,尝试从以下几个方面修改代码寻找原因:

还有两个重要的方面需要注意一下,一是jedis的版本2.7.2和2.9.0的许多方法不能通用,比如上面的创建新的cluster构造方法,二是,当我们本地的Java方法写的都没有问题,jar包导入也正确的情况下,我们就应该去查看是否是我们的redis配置出错了,我最开始就是在这出错,看了jedis的GitHub上的issue才解决的问题,因为我当时这是的cluster为127.0.0.1导致在虚拟机里面可以正常访问,而在虚拟机外面就没办法正常访问了。

对于虚拟机外无法访问这个问题,我们可以先删除原有的cluster生成出来的文件:

再以cluster模式启动所有节点,这个时候任意节点之间还没有meet操作,也没有分配槽,这个时候我们可以使用ruby的工具操作,执行

[root@localhost redis]# ./src/redis-trib.rb create --replicas 1 192.168.91.136:7000 192.168.91.136:7001 192.168.91.136:7002 192.168.91.136:7003 192.168.91.136:7004 192.168.91.136:7005

然后查看:

看上面的不是以127.0.0.1跟端口号就ok了。


参考链接:

  1. 设置防火墙已经iptables:Centos防火墙设置与端口开放的方法
  2. 关于虚拟机外无法访问的问题:JedisCluster can't get a resource

4.2 使用cluster

在解决问题的时候,发现了比较好的测试cluster的代码:

Redis-JedisCluster操作集群

5. 批量操作优化

mgetmset一次要操作很多个key,是通过一个原子性的命令来实现的,对于cluster要求操作的key必须要在一个槽里面去进行。

5.1 串行mget

相当于写个for循环遍历每一个key,分别去每个节点拿到各自的value,需要n次的网络时间。

5.2 串行IO

先将key在客户端进行分组,将属于同一个node的节点放在一起执行一次pipeline操作。需要node次网络时间

5.3 并行IO

在上面串行IO分组之后,使用node个线程同时对redis进行访问,这样只需要一次网络时间。

5.4 hash_tag

将key进行hash_tag包装,将tag值用一个大括号包起来,保证所有的key都落到了一个redis节点,这样每次都只需要去一个客户端去取就可以了。

5.5 对比

方案 优点 缺点 网络IO
串行mget 编程简单
少量keys满足需求
大量keys请求延迟严重 O(keys)
串行IO 编程简单
少量节点满足需求
大量node延迟严重 O(nodes)
并行IO 利用并行特性
延迟取决于最慢的节点
编程复杂
超时定位问题难
O(max_slow(node))
hash_tag 性能最高 读写增加tag维护成本
tag分布易出现数据倾斜
O(1)

6. 故障转移

6.1 故障发现

通过节点之间的ping/pong消息实现故障发现,不需要sentinel

主观下线:

某个节点认为另一个节点不可用,带有 “偏见”的。

节点1定期发送ping消息给节点2,如果ping通,则节点2回复一个pong消息,最后节点1更新与节点2的最后通信时间。ping失败后,还会周期性执行,如果发现最后通信时间超过了node-timeout,则标记node2位pfail状态。

客观下线:

当半数以上持有槽的主节点都标记某节点主观下线了。

接受到其他节点发来的ping消息,该消息包含了其他pfail节点的消息,将该消息添加到自己的故障链表中。然后计算有效下线报告数量,看是否大于槽节点总数的一半,如果不是,则退出,否者更新为客观下线,并向集群广播下线节点的fail消息。

作用:

6.2 故障恢复

6.2.1 资格检查

每个从节点检查与故障主节点的断线时间,如果该时间超过了 cluster-node-timeout * cluster-slave-validity-factor则取消资格,cluster-node-timeout的默认值是15s,cluster-slave-validity-factor默认是10s,即如果噶断线时间超过了150s,那它就没有资格成为主节点了。

6.2.2 准备选举时间

这一步主要为了保证偏移量大的从节点有更小的延迟达到选举时间,因为主节点已经挂了。

6.2.3 选举投票

越先到达选举时间的,越先开始投票,越有可能获得较多的票数。当票数大于N/2+1后就可以替换主节点了。

6.2.4 替换主节点

  1. 当前从节点取消复制变为主节点。(slaveof no one)
  2. 执行clusterDelSlot撤销故障主节点负责的槽,并执行clusterAddSlot把这些槽分配给自己。
  3. 向集群广播自己的pong消息,表明已经替换了故障从节点。
  • 10 min read

CONTRIBUTORS


  • 10 min read