美文网首页Redis
Redis第2️⃣2️⃣课 Cluster客户端路由

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

作者: 小超_8b2f | 来源:发表于2019-05-30 12:33 被阅读2次

    一、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都在一个节点上 四种方式对比

    相关文章

      网友评论

        本文标题:Redis第2️⃣2️⃣课 Cluster客户端路由

        本文链接:https://www.haomeiwen.com/subject/etyetctx.html