你觉得RedisCluster的从节点可以进行读操作嘛?

2,678 阅读2分钟

1. 前言

数据库读写分离是应用程序性能优化的一个手段,读写分离原理:master节点负责应用的写操作(也处理实时性要求高的读场景)、slave节点负责应用程序的读操作。RedisCluster同样包含masterslave节点,其中的slave节点是否和数据库的slave节点一样可以处理读请求,从而实现读写分离?

2.RedisCluster原理

jedis连接池如何实现?一文中讲述了单机模式下JedisConnectionFactory的初始化工作会直接创建一个JedisPool用于实现对连接资源的管理、复用,那么集群下会如何处理呢?

public void afterPropertiesSet() {
    // 1.如果配置了连接池并且不是集群模式,则直接创建连接池
    if (getUsePool() && !isRedisClusterAware()) {
        this.pool = createPool();
    }

    // 2.如果是集群模式,则创建集群
    if (isRedisClusterAware()) {
        this.cluster = createCluster();
        this.topologyProvider = createTopologyProvider(this.cluster);
        this.clusterCommandExecutor = new ClusterCommandExecutor(this.topologyProvider,
                                                                 new JedisClusterConnection.JedisClusterNodeResourceProvider(this.cluster, this.topologyProvider),
                                                                 EXCEPTION_TRANSLATION);
    }
}

2.1 连接节点

private void initializeSlotsCache(Set<HostAndPort> startNodes,
      int connectionTimeout, int soTimeout, String user, String password, String clientName,
                                  boolean ssl, SSLSocketFactory sslSocketFactory, SSLParameters sslParameters, HostnameVerifier hostnameVerifier) {
    // 1.遍历所有的集群节点
    for (HostAndPort hostAndPort : startNodes) {
        Jedis jedis = null;
        try {
            // 2.根据节点信息创建Jedis对象,用于操作redis
            jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout, soTimeout, ssl, sslSocketFactory, sslParameters, hostnameVerifier);
            // 3.初始化slot
            cache.discoverClusterNodesAndSlots(jedis);
            break;
        } catch (JedisConnectionException e) {
            // try next nodes
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}

2.2 初始化slot

在分析初始化slot代码之前,先来执行一下cluster slots命令

cluster slots会返回slot的开始、结束值,所在master的ip、端口、id以及slave的ip、端口、id。了解返回结果后再来分析源码会相当轻松

public void discoverClusterNodesAndSlots(Jedis jedis) {
    w.lock();

    try {
        reset();
        // 1.执行clster slots命令
        List<Object> slots = jedis.clusterSlots();
        // 2.遍历命令返回结果
        for (Object slotInfoObj : slots) {
            List<Object> slotInfo = (List<Object>) slotInfoObj;

            if (slotInfo.size() <= MASTER_NODE_INDEX) {
                continue;
            }
			
            // 3.获取对应的slot数量,例如第一个对应的slot数量为501
            List<Integer> slotNums = getAssignedSlotArray(slotInfo);

            // hostInfos
            int size = slotInfo.size();
            // 4.从master信息开始处理
            for (int i = MASTER_NODE_INDEX; i < size; i++) {
                // 5.获取对应的ip、端口、id
                List<Object> hostInfos = (List<Object>) slotInfo.get(i);
                if (hostInfos.isEmpty()) {
                    continue;
                }
				
                // 6.根据ip、端口组装HostAndPort
                HostAndPort targetNode = generateHostAndPort(hostInfos);
                // 7.存放ip:端口与JedisPool的映射关系
                setupNodeIfNotExist(targetNode);
                // 8.如果是master,存放slot与JedisPool的映射关系,因此JedisPool创建的连接只能操作mater节点
                if (i == MASTER_NODE_INDEX) {
                    assignSlotsToNode(slotNums, targetNode);
                }
            }
        }
    } finally {
        w.unlock();
    }
}

2.3 ip:端口与JedisPool的映射关系

2.4 slot与JedisPool的映射关系

2.5 计算key对应的slot

public static int getSlot(byte[] key) {
    if (key == null) {
        throw new JedisClusterOperationException("Slot calculation of null is impossible");
    }

    int s = -1;
    int e = -1;
    boolean sFound = false;
    for (int i = 0; i < key.length; i++) {
        if (key[i] == '{' && !sFound) {
            s = i;
            sFound = true;
        }
        if (key[i] == '}' && sFound) {
            e = i;
            break;
        }
    }
    if (s > -1 && e > -1 && e != s + 1) {
        return getCRC16(key, s + 1, e) & (16384 - 1);
    }
    return getCRC16(key) & (16384 - 1);
}

执行命令时对key进行CRC并进行与运算得到slot

2.6 获取对应的JedisPool

public JedisPool getSlotPool(int slot) {
    r.lock();
    try {
        return slots.get(slot);
    } finally {
        r.unlock();
    }
}

有了slot值,就可以从2.4章节的映射关系中获取对应的JedisPool,得到的JedisPool是根据master的地址构建的,因此可以知晓RedisCluster从节点是不提供读服务,只做高可用。