Jedis学习笔记

一、Jedis介绍

Jedis是Redis官方推荐的Java连接开发工具。
Jedis的基本使用非常简单,只需要创建Jedis对象的时候指定host,port, password即可。

Jedis jedis = new Jedis("localhost", 6379);  //指定Redis服务Host和port
jedis.auth("xxxx"); //如果Redis服务连接需要密码,制定密码
String value = jedis.get("key"); //访问Redis服务
jedis.close(); //使用完关闭连接

Jedis基本使用十分简单,在每次使用时,构建Jedis对象即可。在Jedis对象构建好之后,Jedis底层会打开一条Socket通道和Redis服务进行连接。所以在使用完Jedis对象之后,需要调用Jedis.close()方法把连接关闭,不如会占用系统资源。

当然,如果应用非常频繁的创建和销毁Jedis对象,对应用的性能是很大影响的,因为构建Socket的通道是很耗时的(类似数据库连接)。我们应该使用连接池来减少Socket对象的创建和销毁过程。

二、Jedis的连接池

Jedis连接池是基于apache-commons pool2实现的。在构建连接池对象的时候,需要提供池对象的配置对象,及JedisPoolConfig(继承自GenericObjectPoolConfig)。我们可以通过这个配置对象对连接池进行相关参数的配置(如最大连接数,最大空闲连接数等)。

JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(8);
config.setMaxTotal(18);
JedisPool pool = new JedisPool(config, "127.0.0.1", 6379, 2000, "password");
Jedis jedis = pool.getResource();
String value = jedis.get("key");
......
jedis.close();
pool.close();

使用Jedis连接池之后,在每次用完连接对象后一定要记得把连接归还给连接池。Jedis对close方法进行了改造,如果是连接池中的连接对象,调用Close方法将会是把连接对象返回到对象池,若不是则关闭连接。可以查看如下代码

@Override
public void close() { //Jedis的close方法
    if (dataSource != null) {
        if (client.isBroken()) {
            this.dataSource.returnBrokenResource(this);
        } else {
            this.dataSource.returnResource(this);
        }
    } else {
        client.close();
    }
}

//另外从对象池中获取Jedis链接时,将会对dataSource进行设置
// JedisPool.getResource()方法
public Jedis getResource() {
    Jedis jedis = super.getResource();   
    jedis.setDataSource(this);
    return jedis;

三、高可用连接

我们知道,连接池可以大大提高应用访问Reids服务的性能,减去大量的Socket的创建和销毁过程。但是Redis为了保障高可用,服务一般都是Sentinel部署方式。当Redis服务中的主服务挂掉之后,会仲裁出另外一台Slaves服务充当Master。这个时候,我们的应用即使使用了Jedis连接池,Master服务挂了,我们的应用奖还是无法连接新的Master服务。为了解决这个问题,Jedis也提供了相应的Sentinel实现,能够在Redis Sentinel主从切换时候,通知我们的应用,把我们的应用连接到新的 Master服务。先看下怎么使用。

Set<String> sentinels = new HashSet<>();
sentinels.add("172.18.18.207:26379");
sentinels.add("172.18.18.208:26379");
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxIdle(5);
config.setMaxTotal(20);
JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels, config);
Jedis jedis = pool.getResource();
jedis.set("jedis", "jedis");
......
jedis.close();
pool.close();

Jedis Sentinel的使用也是十分简单的,只是在JedisPool中添加了Sentinel和MasterName参数。Jedis Sentinel底层基于Redis订阅实现Redis主从服务的切换通知。当Reids发生主从切换时,Sentinel会发送通知主动通知Jedis进行连接的切换。JedisSentinelPool在每次从连接池中获取链接对象的时候,都要对连接对象进行检测,如果此链接和Sentinel的Master服务连接参数不一致,则会关闭此连接,重新获取新的Jedis连接对象。

public Jedis getResource() {
    while (true) {
        Jedis jedis = super.getResource();
        jedis.setDataSource(this);

        // get a reference because it can change concurrently
        final HostAndPort master = currentHostMaster;
        final HostAndPort connection = new HostAndPort(jedis.getClient().getHost(), jedis.getClient().getPort());
        if (master.equals(connection)) {
            // connected to the correct master
            return jedis;
        } else {
            returnBrokenResource(jedis);
        }
    }
}

当然,JedisSentinelPool对象要时时监控RedisSentinel的主从切换。在其内部通过Reids的订阅实现。具体的实现看JedisSentinelPool的两个方法就很清晰

private HostAndPort initSentinels(Set<String> sentinels, final String masterName) {
    HostAndPort master = null;
    boolean sentinelAvailable = false;
    log.info("Trying to find master from available Sentinels...");
    for (String sentinel : sentinels) {
        final HostAndPort hap = HostAndPort.parseString(sentinel);
        log.fine("Connecting to Sentinel " + hap);
        Jedis jedis = null;
        try {
            jedis = new Jedis(hap.getHost(), hap.getPort());
            //从RedisSentinel中获取Master信息
            List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName);
            sentinelAvailable = true; // connected to sentinel...
            if (masterAddr == null || masterAddr.size() != 2) {
                log.warning("Can not get master addr, master name: " + masterName + ". Sentinel: " + hap + ".");
                continue;
            }
            master = toHostAndPort(masterAddr);
            log.fine("Found Redis master at " + master);
            break;
        } catch (JedisException e) {
            // it should handle JedisException there's another chance of raising JedisDataException
            log.warning("Cannot get master address from sentinel running @ " + hap + ". Reason: " + e + ". Trying next one.");
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
    if (master == null) {
        if (sentinelAvailable) {
            // can connect to sentinel, but master name seems to not monitored
            throw new JedisException("Can connect to sentinel, but " + masterName + " seems to be not monitored...");
        } else {
            throw new JedisConnectionException("All sentinels down, cannot determine where is " + masterName + " master is running...");
        }
    }
    log.info("Redis master running at " + master + ", starting Sentinel listeners...");
    //启动后台线程监控RedisSentinal的主从切换通知
    for (String sentinel : sentinels) {
        final HostAndPort hap = HostAndPort.parseString(sentinel);
        MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort());
        // whether MasterListener threads are alive or not, process can be stopped
        masterListener.setDaemon(true);
        masterListeners.add(masterListener);
        masterListener.start();
    }
    return master;
}


private void initPool(HostAndPort master) {
    if (!master.equals(currentHostMaster)) {
        currentHostMaster = master;
        if (factory == null) {
            factory = new JedisFactory(master.getHost(), master.getPort(), connectionTimeout, soTimeout, password, database, clientName, false, null, null, null);
            initPool(poolConfig, factory);
        } else {
            factory.setHostAndPort(currentHostMaster);
            // although we clear the pool, we still have to check the returned object
            // in getResource, this call only clears idle instances, not
            // borrowed instances
            internalPool.clear();
        }
        log.info("Created JedisPool to master at " + master);
    }
}

可以看到,JedisSentinel的监控时使用MasterListener这个对象来实现的。看对应源码可以发现是基于Redis的订阅实现的,其订阅频道为"+switch-master"。当MasterListener接收到switch-master消息时候,会使用新的Host和port进行initPool。这样对连接池中的连接对象清除,重新创建新的连接指向新的Master服务。

四、Redis Cluster与JedisCluster

Redis Cluster 将全部的键空间划分为16384块,每一块空间称之为槽(slot),又将这些槽及槽所对应的 k-v 划分给集群中的每个主节点负责。

key -> slot 的算法选择上,Redis Cluster 选择的算法是 hash(key) mod 16383,即使用CRC16算法对key进行hash,然后再对16383取模,结果便是对应的slot。

常见的数据分区方法:Redis Cluster使用了虚拟槽分区
1、节点取余分区:对特定数据取hash值再对节点数取余来决定映射到哪一个节点。优点是简单,缺点是扩容或收缩时需重新计算映射结果,极端情况下会导致数据全量迁移。
2、一致性哈希分区:给每个节点分配一个0~2^32的token,使其构成一个环,数据命中规则为根据key的hash值,顺时针找到第一个token大于等于该hash的节点。优点是加减节点只影响相邻的节点,缺点是节点少的时候优点变缺点,反倒会影响环中大部分数据,同时加减节点时候会导致部分数据无法命中。
3、虚拟槽分区:使用分散度良好的hash函数将数据映射到一个固定范围的整数集合,这些整数便是槽位,再分给具体的节点管理。Redis Cluster使用的便是虚拟槽分区。

4.1、客户端数据读写

实际上客户端是如何读写数据的呢?Redis Cluster 采用了直接节点的方式。集群模式下,客户端去操作集群是直连到一个具体的节点上操作的。当该节点接收到任何键操作命令时,会先计算键对应的slot,然后根据slot找出对应节点。

如果对应的节点是自身,则执行键操作命令,返回结果;如果不是自身,会返回给客户端MOVED重定向错误,告诉客户端应该请求具体哪个节点,由客户端发起二次请求到正确的节点,完成本次键操作。

当使用redis-cli 直连集群中节点时,使用 -c 参数,redis-cli会自动重定向连接到目标节点进行键操作。需要注意的是,这个自动重定向功能是redis-cli实现的,跟redis节点本身无关,节点本身依旧返回了MOVED错误给客户端。

在键操作命令中,除了对单个键值的操作,还有多键值以及批量操作。Redis 集群实现了所有在非分布式版本中出现的处理单一键值的命令,但是在使用多个键值的操作,由于集群跟客户端的通信方式是直连节点,对于多键的操作却是需要遍历所有节点,因此是不支持的,一般由客户端在代码中实现需要的功能。

对于批量操作,一方面可以由客户端代码计算槽位,针对单个节点进行分档,最后批量操作,另一方面,Redis Cluster 提供了hashtag 的功能,通过为key打上hashtag,让一类key在存储时就位于同一个slot,达到存储于同一个节点的效果。

hashtag: 是Cluster为了满足用户让特定Key绑定到特定槽位的需求而实现的一个功能。在计算key的slot时,如果key中包括花括号{},并且花括号中内容不为空,便会计算花括号中标志对应的slot。如果不包括{}或是其中内容为空,则计算整个key对应的slot。可以利用这个功能,在特定需求中将一类key绑定到一个槽位上,但不可滥用,毕竟本身数据是分区存的,全这么搞会导致各节点内存占用不平衡,影响集群性能。

注意:lua脚本执行、事务中key操作,前提都是所涉及的key在一个节点上,如果在使用集群时无法避免这些操作,可以考虑使用hashtag,然后客户端通过这台节点的连接去操作。

4.2、节点间的信息共享

集群中会有多个节点,每个节点负责一部分slot以及对应的k-v数据,并且通过直连具体节点的方式与客户端通信。那么问题来了,你向我(直连的节点)这里请求一个key的value,这个key对应的slot并不归我负责,但我又要需要告诉你MOVED到目标节点,我如何知道这个目标节点是谁呢?

Redis Cluster使用Gossip协议维护节点的元数据信息,这种协议是P2P模式的,主要职责就是信息交换。节点间不停地去交换彼此的元数据信息,那么总会在一段时间后,大家都知道彼此是谁,负责哪些数据,是否正常工作等等。节点间信息交换是依赖于彼此发出的Gossip消息的。常用的一般是以下四种消息:
1、meet消息: 会通知接收该消息的节点,发送节点要加入当前集群,接收者进行响应。
2、ping消息: 是集群中的节点定期向集群中其他节点(部分或全部)发送的连接检测以及信息交换请求,消息包含发送节点信息以及发送节点知道的其他节点信息。
3、pong消息:是在节点接收到meet、ping消息后回复给发送节点的响应消息,告诉发送方本次通信正常,消息包含当前节点状态。
4、fail消息: 是在节点认为集群内另外某一节点下线后向集群内所有节点广播的消息。

在集群启动的过程中,有一个重要的步骤是节点握手,其本质就是在一个节点上向其他所有节点发送meet消息,消息中包含当前节点的信息(节点id,负责槽位,节点标识等等),接收方会将发送节点信息存储至本地的节点列表中。消息体中还会包含与发送节点通信的其他节点信息(节点标识、节点id、节点ip、port等),接收方也会解析这部分内容,如果本地节点列表中不存在,则会主动向新节点发送meet消息。接收方处理完消息后,也会回复pong消息给发送者节点,发送者也会解析pong消息更新本地存储节点信息。因此,虽然只是在一个节点向其他所有节点发送meet消息,最后所有节点都会有其他所有节点的信息。

集群启动后,集群中各节点也会定时往其他部分节点发送ping消息,用来检测目标节点是否正常以及发送自己最新的节点负槽位信息。接收方同样响应pong消息,由发送方更新本地节点信息。当在与某一节点通信失败(故障发现策略后面会说)时,则会主动向集群内节点广播fail消息。考虑到频繁地交换信息会加重带宽(集群节点越多越明显)和计算的负担,Redis Cluster内部的定时任务每秒执行10次,每次遍历本地节点列表,对最近一次接受到pong消息时间大于cluster_node_timeout/2的节点立马发送ping消息,此外每秒随机找5个节点,选里面最久没有通信的节点发送ping消息。同时 ping 消息的消息投携带自身节点信息,消息体只会携带1/10的其他节点信息,避免消息过大导致通信成本过高。

cluster_node_timeout 参数影响发送消息的节点数量,调整要综合考虑故障转移、槽信息更新、新节点发现速度等方面。一般带宽资源特别紧张时,可以适当调大一点这个参数,降低通信成本。

4.3、槽位迁移与集群伸缩

Redis Cluster 支持在集群正常服务过程中,下线或是新增集群节点。但无论是集群扩容还是收缩,本质上都是槽及其对应数据在不同节点上的迁移。一般情况下,槽迁移完成后,每个节点负责的槽数量基本上差不多,保证数据分布满足理论上的均匀。

常用的有关槽的命令如下:

  • CLUSTER ADDSLOTS slot1 [slot2]…[slotN] —— 为当前节点分配要负责的槽,一般用于集群创建过程。
  • CLUSTER DELSLOTS slot1 [slot2]…[slotN] —— 将特定槽从当前节点的责任区移除,和ADDSLOTS命令一样,执行成功后会通过节点间通信将最新的槽位信息向集群内其他节点传播。
  • CLUSTER SETSLOT slotNum NODE nodeId —— 给指定ID的节点指派槽,一般迁移完成后在各主节点上执行,告知各主节点迁移完成。
  • CLUSTER SETSLOT slotNum IMPORTING sourceNodeId —— 在槽迁移的目标节点上执行该命令,意思是这个槽将由原节点迁移至当前节点,迁移过程中,当前节点(即目标节点)只会接收asking命令连接后的被设为IMPORTING状态的slot的命令。
  • CLUSTER SETSLOT slotNum MIGRATING targetNodeId —— 在槽迁移的原节点上执行该命令,意思是这个槽将由当前节点迁移至目标节点,迁移过程中,当前节点(即原节点)依旧会接受设为MIGRATING的slot相关的请求,若具体的key依旧存在于当前节点,则处理返回结果,若不在,则返回一个带有目标节点信息的ASK重定向错误。其他节点在接受到该槽的相关请求时,依旧会返回到原节点的MOVED重定向异常。

实际上迁移槽的核心是将槽对应的k-v数据迁移到目标节点。所以在完成slot在原节点和目标节点上状态设置(即上面最后两条命令)后,就要开始进行具体key的迁移。

  • CLUSTER GETKEYSINSLOT slot total —— 该命令返回指定槽指定个数的key集合
  • MIGRATE targetNodeIp targetNodePort key dbId timeout [auth password] —— 该命令在原节点执行,会连接到目标节点,将key及其value序列化后发送过去,在收到目标节点返回的ok后,删除当前节点上存储的key。整个操作是原子性的。由于集群模式下使用各节点的0号db,所以迁移时dbId这个参数只能是0。
  • MIGRATE targetNodeIp targetNodePort “” 0 timeout [auth password] keys key1 key2… —— 该命令是上面迁移命令基于pipeline的批量版本。

在整个slot的key迁移完成后,需要在各主节点分别执行CLUSTER SETSLOT slotNum NODE nodeId来通知整个slot迁移完成。redis-trib.rb 提供的reshard功能便是基于官方提供的上述命令实现的。

集群的扩展过程实际上就是启动一个新节点,加入集群(通过gossip协议进行节点握手、通信),最后从之前各节点上迁移部分slot到新节点上。

集群的收缩过程除了除了将待下线节点的槽均匀迁移到其他主节点之外,还有对节点的下线操作。官方提供了CLUSTER FORGET downNodeId命令,用于在其他节点上执行以忘记下线节点,不与其交换信息,需要注意的是该命令有效期为60s,超过时间后会恢复通信。一般建议使用redis-trib.rb 提供的del-node功能。

4.4、主从高可用

要保证高可用的前提是离不开从节点的,一旦某个主节点因为某种原因不可用后,就需要一个一直默默当备胎的从节点顶上来了。

一般在集群搭建时最少都需要6个实例,其中3个实例做主节点,各自负责一部分槽位,另外3个实例各自对应一个主节点做其从节点,对主节点的操作进行复制。Redis Cluster在给主节点添加从节点时,不支持slaveof命令,而是通过在从节点上执行命令cluster replicate masterNodeId 。

Cluster的故障发现也是基于节点通信的。每个节点在本地存储有一个节点列表(其他节点信息),列表中每个节点元素除了存储其ID、ip、port、状态标识(主从角色、是否下线等等)外,还有最后一次向该节点发送ping消息的时间、最后一次接收到该节点的pong消息的时间以及一个保存其他节点对该节点下线传播的报告链表。

节点与节点间会定时发送ping消息,彼此响应pong消息,成功后都会更新这个时间。同时每个节点都有定时任务扫描本地节点列表里这两个消息时间,若发现pong响应时间减去ping发送时间超过cluster-node-timeout配置时间(默认15秒,该参数用来设置节点间通信的超时时间)后,便会将本地列表中对应节点的状态标识为PFAIL,认为其有可能下线。

节点间通信(ping)时会携带本地节点列表中部分节点信息,如果其中包括标记为PFAIL的节点,那么在消息接收方解析到该节点时,会找自己本地的节点列表中该节点元素的下线报告链表,看是否已经存在发送节点对于该故障节点的报告,如果有,就更新接收到发送ping消息节点对于故障节点的报告的时间,如果没有,则将本次报告添加进链表。下线报告链表的每个元素结构只有两部分内容,一个是报告本地这个故障节点的发送节点信息,一个是本地接收到该报告的时间(存储该时间是因为故障报告是有有效期的,避免误报)。由于每个节点的下线报告链表都存在于各自的信息结构中,所以在浏览本地节点列表中每个节点元素时,可以清晰地知道,有其他哪些节点跟我说,兄弟,你正在看的这个节点我觉的凉凉了。

故障报告的有效期是 cluster-node-timeout * 2

消息接收方解析到PFAIL节点,并且更新本地列表中对应节点的故障报告链表后,会去查看该节点的故障报告链表中有效的报告节点是否超过所有主节点数的一半。如果没超过,便继续解析ping消息;如果超过,代表超过半数的节点认为这个节点可能下线了,当前节点就会将PFAIL节点本地的节点信息中的状态标识标记为FAIL,然后向集群内广播一条fail消息,集群内的所有节点接收到该fail消息后,会把各自本地节点列表中该节点的状态标识修改为FAIL。在所有节点对其标记未FAIL后,该FAIL节点对应的从节点就会发起转正流程。在转正流程完成后,这个节点就会正式下线,等到其恢复后,发现自己的槽已经被分给某个节点,便会将自己转换成这个节点的从节点并且ping集群内其他节点,其他节点接到恢复节点的ping消息后,便会更新其状态标识。此外,恢复的节点若发现自己的槽还是由自己负责,就会跟其他节点通信,其他主节点发现该节点恢复后,就会拒绝其从节点的选举,最终清除自己的FAIL状态。

4.5、从节点升为主节点过程

在集群中若是某个主节点发生故障,被其他主节点标记为FAIL状态,为了集群的正常使用,这时会由其对应的从节点中晋升一个为新的主节点,负责原主节点的一切工作。

并不是所有从节点都有被提名的资格,这个跟普通职员的晋升一样。只有从节点与主节点的连接断线不超过一定时间,才会初步具备被提名的资格。该时间一般为cluster-node-timeout *10,10是从节点的默认有效因子。

一般来说,故障主节点会有多个符合晋升要求的从节点,那么怎么从这些从节点中选出一个最合适的来晋升为主节点恢复工作呢?从节点的作用是作为主节点的备份,每个对于主节点的操作都会异步在多个从节点上备份,但受具体的主从节点结构决定,一般每个从节点对于主节点的同步程度是不同的。为了能更好的替代原主节点工作,就必须从这些从节点中选举一个最接近甚至完全同步主节点数据的从节点来完成最终晋升。

从节点晋升的发起点是从节点。从节点在定时任务中与其他节点通信,当发现主节点FAIL后,会判断资深是否有晋升提名资格。如果有的话,则会根据相关规则设置一个选举自己的时间。在到达那个设置的时间点后,再发起针对自己晋升的选举流程,选票则由集群中其他正常主节点选投。若自己获得的选票超过正常主节点数的一半时,则会执行替换原主节点工作,完成本次选举晋升。

设置选举时间规则:发现主节点FAIL后并不会立马发起选举。而是经过 固定延时(500ms)+ 随机延时(0-500ms)+ 从节点复制偏移量排名1000ms 后发起针对自己的选举流程。其中 固定延时 是保证主节点的FAIL状态被所有主节点获知,随机延时是为了尽量避免发生多个从节点同时发起选举的情况,最后的排名1000ms是为了保证复制偏移量最大也就是最接近于原主节点数据的从节点最先发起选举。因此一般来说,从节点晋升选举一次就会成功。主节点是没有区分哪个从节点是最适合晋升的规则的,主要靠这里的选举发起时间来让最合适的一次成功。

从节点发起选举主要分为两步:

  • 自增集群的全局配置纪元,并更新为当前节点的epoch(配置纪元这里不详细介绍,不懂的可以先简单理解为版本号,每个节点都有自己的epoch并且集群有一个全局的epoch);

  • 向集群内广播选举消息FAILOVER_AUTH_REQUEST,消息内会包含当前节点的epoch。

从节点广播选举消息后,在NODE_TIMEOUT*2时间内等待主节点的响应FAILOVER_AUTH_ACK。若收到大多数主节点的响应,代表选举成功,则会通过ping\pong消息来宣誓主权。若未收到足够响应则会中断本次选举,由其他节点重新发起选举。

主节点在每个全局配置纪元中有且只有一张选票,一旦投给某个从节点便会忽视其他节点的选举消息。一般同一个配置纪元多个从节点竞争的情况只有极小概率会发生,这是由从节点的选举时间以及选举步骤决定的。主节点的投票响应FAILOVER_AUTH_ACK消息中会返回接收到的选举消息一样的epoch,从节点也只会认可跟节点当前epoch一致的投票响应,这样可以避免因为网络延迟等因素导致认可迟来的历史认可消息。

从节点成功晋升后,在替换原主节点时,还需要进行最后三步:

  • 取消当前节点的复制工作,变身为主节点;
  • 撤销原主节点负责的槽,并把这些槽委派给自己;
  • 广播pong消息,通知所有节点自己已经完成转正以及转正后负责的槽信息。

4.6、JedisCluster

Jedis是redis的java客户端,JedisCluster则是Jedis根据Redis集群的特性提供的集群客户端。

上文介绍过了redis集群下操作key的详细流程,一般通过redis-cli启动客户端连接具体的节点时,要操作的key若不在这个节点上时,服务端会返回MOVED重定向错误,这时需要手动连接至重定向节点才能继续操作。或者redis-cli连接服务节点时加上-c 参数,就可以使用redis-cli提供的自动重定向机制,在操作其他服务节点的key时会进行自动重定向,避免客户端手动重定向。JedisCluster作为操作Redis集群的java客户端,同样遵守RedisCluster提供的客户端连接规范,本节从源码的角度去看其具体是怎么做的。

4.6.1、初始化工作

无论你使用spring集成jedis或是直接使用jedis,第一步都是客户端的初始化工作,这里直接从JedisCluster着手去看。JedisCluster实际上是一个高级客户端,它继承了BinaryJedisCluster,客户端的初始化工作实际上都是由该类负责,此外还实现了JedisCommands、MultiKeyJedisClusterCommands和JedisClusterScriptingCommands三个接口,封装了单键命令、多键操作命令以及脚本执行命令等具体的方法供开发人员调用。

JedisCluster的构造器有很多,但最终都是调用了父类BinaryJedisCluster的构造,实际上这里是初始化了一个连接处理器,并且设置了最大重试次数。

public BinaryJedisCluster(Set<HostAndPort> jedisClusterNode, 
int connectionTimeout, int soTimeout, int maxAttempts, 
String password, GenericObjectPoolConfig poolConfig) {
 
  this.connectionHandler = new JedisSlotBasedConnectionHandler(jedisClusterNode, poolConfig,
          connectionTimeout, soTimeout, password);
  this.maxAttempts = maxAttempts; 
}

JedisSlotBasedConnectionHandler实际上又调用了父类JedisClusterConnectionHandler 的构造器,而这里才是JedisCluster初始化的核心。

public JedisClusterConnectionHandler(Set<HostAndPort> nodes,
                                     final GenericObjectPoolConfig poolConfig, int connectionTimeout, int soTimeout, String password) {
  
  // 创建集群信息的缓存对象
  this.cache = new JedisClusterInfoCache(poolConfig, connectionTimeout, soTimeout, password);
 
  // 初始化连接池与缓存信息
  initializeSlotsCache(nodes, poolConfig, password);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章