Jedis源码-连接池(一)

前言

主要是突然心血来潮想看看jedis连接池的实现策略,之前看过druid实现,印象中整体实现思路都差不多。这里Jedis源码是2.8.1版本。

为了便于理解,看之前可以先想想池化技术实现通常有什么特点(我是看内容反推结果)。

  1. 可重复使用:放到池子里面的东西需要可以重复使用,这样才有意义。
  2. 创建需要一定开销:创建如果开销很小,大部分情况下使用池化意义就不是很大。
  3. 资源有限:不能创建太多
  4. 配套监控:为了可以知道池子使用的情况,还需要一些监控数据

此外有些资源还要考虑可用性,而保证池内东西可用,就需要去检查。我们可以在borrow或return的时候看看这个东西还能用不;我们还可以派个人周期检查池子里面东西的可用性。对于资源不可用的情况,我们需要将其销毁。当然有些资源不用考虑,比如说内存块,所以需要实现的时候是可选的。所以需要的操作有:创建,检测可用性,销毁,取,释放等基础操作

所以简单来说,我们因为不想重复创建一样的东西,而且创建的开销还很大,创建多了还会降低效率,我们就想着囤积以便可以重复利用。

注:本质上还是属于源码笔记,有不清楚的可以提意见,目前我只是本着想到什么就整理什么的思路,发出来只是希望给需要的人一点帮助。

通用对象池

我本来一直以为jedis是自己实现连接池,但是其实并不是:

里面居然用的是一个通用的实现,所有的操作最后都会委托它来操作。这个连接池是apache里面的一个实现:

org.apache.commons.pool2.impl.GenericObjectPool

而且对象名称也很有意思“通用对象池”。

看源码第一步就是整理类图,注意到BaseGenericObjectPool,它承担了GenericObjectPoolMXBean接口方法,都是一些配置的get方法,至于这个接口名MX,和JMX有关系,后面再看。

我们之前说的池所需的操作,在接口(ObjectPool)都有定义,实现就在GenericObjectPool中,所以实际上我们只需要关心GenericObjectPool就行了。

此外,GenericObjectPool中持有的PooledObjectFactory它具有创建,校验,销毁等功能,看到下面,在对象池的主要操作上都有它的影子:

还有一个PoolObject池对象,这是一个包装类,里面会额外附带有些统计信息,还有状态信息(便于监控)。jedis在使用的时候,直接就是:

return new DefaultPooledObject<Jedis>(jedis);

到这里,我们对这个通用对象池有个总体的框架:

  • 池对象工厂:它会制造,销毁,校验池对象
  • 池对象:持有需要复用的对象,本身具有一系列存活状态(失效,空闲,使用,正在检查等),具有一些统计信息。
  • 对象池:存储空闲对象和所有对象,具有获取,释放,关闭等操作,暴露一些统计接口
  • 驱逐线程:定时移除和测试空闲对象可用性的逻辑在这里(可选启动,策略可以自定义)

简单来说,我们只需要实现池对象工厂里面的接口就可以使用这个对象池了,这个就是通用二字的含义,我定义了操作的步骤,你实现具体的操作。除了这个还暴露了一些自定义的接口,比如说可以自定义驱逐策略。下面就是介绍核心的操作,便于理解代码。

租借操作

borrow的目的就是为了拿到可用的对象,可用的对象存储在空闲队列里面,所以我们可以从队列里面拿,也可以让工厂给新的对象。

在此基础上,提供了额外的功能:

  • 我希望可以等待一段时间,或者阻塞等待;
  • 我希望在借之前可以自动检查一下可用性怎么样;
  • 我希望在借之前可以检查一下是不是有长时间不归还,或者长时间不使用的对象,废弃(removeAbandoned)。

removeAbandoned这个操作在驱逐线程里面也有,只不过2个地方控制的开关不一样。注意的下面这个判断,推测应该是发现空闲的很少,使用的很多要进行检查:

AbandonedConfig ac = this.abandonedConfig;
if (ac != null && ac.getRemoveAbandonedOnBorrow() &&
        (getNumIdle() < 2) &&
        (getNumActive() > getMaxTotal() - 3) ) {
    removeAbandoned(ac);
}

 判断对象是否可以分配主要看对象状态是不是空闲状态,所以也存在一种情况:对象正在驱逐检测,这时候状态并非是空闲,而是EVICTION:

@Override
public synchronized boolean allocate() {
    if (state == PooledObjectState.IDLE) {
        state = PooledObjectState.ALLOCATED;
        //...
        return true;
    } else if (state == PooledObjectState.EVICTION) {
        // 只要不被驱逐,最后需要放到队头
        state = PooledObjectState.EVICTION_RETURN_TO_HEAD;
        return false;
    }
    // TODO if validating and testOnBorrow == true then pre-allocate for
    // performance
    return false;
}

在驱逐检测完毕后,如果没有驱逐就会重置状态:

state = PooledObjectState.IDLE;
if (!idleQueue.offerFirst(this)) {
    // TODO - Should never happen
}

归还操作

驱逐检测

整个检测是单线程工作,有个Timer去执行。整个流程主要由两个方法构成:evict()和ensureMinIdle()。第一个方法作用是检测,第二个方法是保证空闲数量为minIdle。

外层是一个循环,每次需要检测多少个:

for (int i = 0, m = getNumTests(); i < m; i++) {}

看注释就行了,没啥好说的:

//非空闲状态不作为计数,空闲状态修改状态为EVICTION
if (!underTest.startEvictionTest()) {
    // Object was borrowed in another thread
    // Don't count this as an eviction test so reduce i;
    i--;
    continue;
}
if (evictionPolicy.evict(evictionConfig, underTest,
        idleObjects.size())) {
   //销毁并计数
} else {
    if (testWhileIdle) {
        boolean active = false;
        //...active = activateObject
        if (active) {
            if (!factory.validateObject(underTest)) {
                //销毁并计数
            } else {
                //passivateObject
            }
        }
    }
    //改回状态
    if (!underTest.endEvictionTest(idleObjects)) {
        // TODO - May need to add code here once additional
        // states are used
    }
}
void ensureMinIdle() throws Exception {
    //minIdle参数<1不处理
    //创建
    while (idleObjects.size() < minIdleSave) {
        PooledObject<T> p = create();
        if (p == null) {
            // Can't create objects, no reason to think another call to
            // create will work. Give up.
            break;
        }
        if (getLifo()) {
            idleObjects.addFirst(p);
        } else {
            idleObjects.addLast(p);
        }
    }
}

 

池对象状态变化图

分状态是为了避免管理上的冲突,比如说我要拿对象的时候,这个对象不能被其他人使用,例如Evict线程正在使用,再比如已经被废弃了。而且状态转换也会涉及并发问题。在实际源码中,加锁主要针对对象,锁粒度就是池对象级别。

看到空闲状态到已分配状态:

public synchronized boolean allocate() {
    //只有空闲状态才会进行转换
    if (state == PooledObjectState.IDLE) {
        state = PooledObjectState.ALLOCATED;
        lastBorrowTime = System.currentTimeMillis();
        lastUseTime = lastBorrowTime;
        //...
        return true;
    } else if (state == PooledObjectState.EVICTION) {
        // TODO Allocate anyway and ignore eviction test
        state = PooledObjectState.EVICTION_RETURN_TO_HEAD;
        return false;
    }
    // TODO if validating and testOnBorrow == true then pre-allocate for
    // performance
    return false;
}

Jedis连接池

根据上面的分析,可知池的核心就是工厂。在Jedis里面,池对象就是Jedis,其实就是客户端,比如说我们用redis-cli启动也是一个客户端,可以输入命令操作。

redis-cli和redis-server交互是基于tcp,Jedis也是一样:

  • 创建:创建tcp连接(授权,给自己标识名字,选择数据库)
  • 销毁:断开tcp连接
  • 测试:socket发送pong命令获取服务端回应
  • activate:重新选库

其实在这里池的重点就是上面的通用对象池,反而Jedis连接池没东西可说。这里就贴贴源码就完事了。这里找到一篇充电宝的demo感觉不错:Java对象池pool2分析PooledObjectFactory

@Override
public boolean validateObject(PooledObject<Jedis> pooledJedis) {
    final BinaryJedis jedis = pooledJedis.getObject();
    try {
      HostAndPort hostAndPort = this.hostAndPort.get();

      String connectionHost = jedis.getClient().getHost();
      int connectionPort = jedis.getClient().getPort();
      //发送pong命令
      return hostAndPort.getHost().equals(connectionHost)
          && hostAndPort.getPort() == connectionPort && jedis.isConnected()
          && jedis.ping().equals("PONG");
    } catch (final Exception e) {
      return false;
    }
  }

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值