Redis

Redis第2️⃣2️⃣课 Cluster客户端路由

2019-05-30  本文已影响2人  小超_8b2f

一、moved重定向

1.图解
image.png 槽命中 槽不命中
2.代码验证
#集群模式连接redis-cli
$ redis-cli -c -p 7000
127.0.0.1:7000> keys *
(empty list or set)
127.0.0.1:7000> set hello world
OK
127.0.0.1:7000> get hello
"world"

#被重定向到9244槽所在的节点上去了
127.0.0.1:7000> set php 'is the best'  
-> Redirected to slot [9244] located at 127.0.0.1:7001
OK
127.0.0.1:7001> get php # 能够获取到
"is the best"
127.0.0.1:7001> CLUSTER KEYSLOT php  #计算php键的slot值
(integer) 9244
127.0.0.1:7001> exit 

#以单机模式连接客户端并查询php节点,报moved异常
$ redis-cli  -p 7000 
127.0.0.1:7000> get php
(error) MOVED 9244 127.0.0.1:7001

二、ask重定向

ask重定向的产生是在迁移槽的过程中 image.png
moved和ask的区别
  1. 两者都是客户单重定向
  2. moved:槽已确认转移
  3. ask:槽还在转移过程中

三、smart客户端

1. smart客户端原理:追求性能

未采用代理模式,虽操作方便,但是会损耗性能。作者在设计的时候考虑的是直连。

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


执行命令过程

2. smart客户端使用:JedisCluster

1)JedisCluster基本使用
Set<HostAndPort> nodeList = new HashSet<>();
nodeList.add(new HostAndPort(HOST1,PORT1));
nodeList.add(new HostAndPort(HOST2,PORT2));
nodeList.add(new HostAndPort(HOST3,PORT3));
nodeList.add(new HostAndPort(HOST4,PORT4));
nodeList.add(new HostAndPort(HOST5,PORT5));
nodeList.add(new HostAndPort(HOST6,PORT6));
JedisCluster redisCluster = new JedisCluster(nodeList,timeout,poolConfig);
redisCluster.commond......
2)使用技巧

(1)单例:内置初始化了所有节点的连接池(包括从节点),保证在做故障转移的时候也有客户端连接池。(设置成spring的bean)
(2)无需手动借还连接池
(3)合理配置commons-pool配置

3)JedisCluster实例小程序
public class JedisClusterFactory {
    private static Logger logger = LoggerFactory.getLogger(JedisClusterFactory.class);
    public static void main(String[] args){
        Set<HostAndPort> node = new HashSet<>();
        String host = "127.0.0.1";
        int port = 7000;
        
        for (int i = 0; i < 7; i++)
            node.add(new HostAndPort(host,port++));
        
        JedisPoolConfig poolConfig = new JedisPoolConfig();//设定配置.....
        JedisCluster redisCluster = null;
        try {
            redisCluster = new JedisCluster(node, 5000, poolConfig);
            Set<String> keys = redisCluster.keys("{*}");
            keys.forEach((x) -> System.out.println(x));
            String set = redisCluster.set("hello", "world2");
            
            System.out.println(set);
            System.out.println(redisCluster.get("hello"));
            System.out.println(redisCluster.get("php"));
            
            clusterNodes(redisCluster); //多节点分别执行命令
        } catch (Exception e) {
            logger.debug(e.getMessage(), e);
        } finally {
            redisCluster.close();
        }
    }
4)多节点命令实现
    /**
     * 多节点分别执行命令实现
     * eg:scan命令是扫描所有节点的键值,
     * 对于Redis Cluster来说不支持一个scan命令扫描所有节点
     * 需求,在所有节点上去执行这个命令
     */
    public static void clusterNodes(JedisCluster redisCluster) {
        //获取所有节点的JedisPool
        Map<String, JedisPool> clusterNodes = redisCluster.getClusterNodes();
        Set<Entry<String, JedisPool>> entrySet = clusterNodes.entrySet();
        for(Entry<String, JedisPool> entry : entrySet) {
            JedisPool value = entry.getValue();
            Jedis jedis = value.getResource();
            
            Set<String> keys = jedis.keys("*");
            System.out.println((isMaster(jedis) ? "master" : "slave") + " | " + entry.getKey() + " | " + keys);
            
            if(!isMaster(jedis)) { //只对主节点数据进行修改
                continue;
            }
        }
    }
    
    /**
     * 判断是否是主节点
     */
    public static boolean isMaster(Jedis jedis) {
        String infos = jedis.info("Replication");
        String[] split = infos.split("\n");
        for(String info: split) 
            if(info.startsWith("role")) 
                return info.contains("master");
        return false;
    }
}
OK
world2
is the best
slave | 127.0.0.1:7004 | [php]
slave | 127.0.0.1:7005 | []
master | 127.0.0.1:7002 | []
slave | 127.0.0.1:7003 | [hello]
master | 127.0.0.1:7000 | [hello]
master | 127.0.0.1:7001 | [php]

四、 如何实现批量命令操作?

mget、mset、hmget、hmset ......

1. mset、mget的键必须在一个槽,否则报错

//无论hello,hell是否在同一个节点上都会报这个❌,和槽有关,和节点无关
List<String> mget = redisCluster.mget("hello","hell");
System.out.println(mget);
16:48:18.511 [main] DEBUG com.designPattern.jedis.JedisClusterFactory - No way to dispatch this command to Redis Cluster because keys have different slots.
redis.clients.jedis.exceptions.JedisClusterOperationException: No way to dispatch this command to Redis Cluster because keys have different slots.
    at redis.clients.jedis.JedisClusterCommand.run(JedisClusterCommand.java:39)
    at redis.clients.jedis.JedisCluster.mget(JedisCluster.java:1459)
    at com.designPattern.jedis.JedisClusterFactory.main(JedisClusterFactory.java:42)

思考:一个槽可以放多少数据?可以放多少个key?

2. 四种批量优化的方法

1) 串行mget

通过for循环redisCluster.getClusterNodes();获取每个Jedis,然后分别mget
简单、效率差


串行mget
2) 串行IO

在本地进行了一个内聚,前提:知道所有的key。通过本地的CRC16(key)取余,算出槽,通过槽和节点的对应关系对key进行分组


串行IO图解
3) 并行IO
并行IO
4) hash_tag
hash_tag,在key都在一个节点上 四种方式对比
上一篇 下一篇

猜你喜欢

热点阅读