阅读 742

jedis连接池如何实现?

1. 前言

本文将从实例并结合源码分析jedis连接池原理,其中包括如何创建连接、释放连接、驱除连接以及如何确保最小空闲数量的连接

2.示例

2.1 添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
</dependencies>
复制代码

2.2 添加配置

spring:
  redis:
    url: redis://localhost:6379
    client-type: jedis
    jedis:
      pool:
        min-idle: 10
        max-active: 10
复制代码

2.3 注入redis模板对象

@Autowired
private StringRedisTemplate stringRedisTemplate;
复制代码

2.4 执行set操作

@Test
public void test() throws InterruptedException {
    stringRedisTemplate.opsForValue().set("hello", "world");
}
复制代码

3.jedis连接池

要想了解spring-boot-starter-xxx模块原理,就需要分析xxxAutoConfiguration

3.1 redis自动配置

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
复制代码

通过@EnableConfigurationProperties注解开启配置注入功能,在application.yml中关于redis的配置就会自动注入到RedisProperties对象中,@Import注解引入lettucejedis客户端连接配置,由于在yml配置中声明了jedis客户端,因此此处引入的是JedisConnectionConfiguration配置

3.2 创建JedisConnectionConfiguration

protected RedisConnectionConfiguration(RedisProperties properties,
			ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
			ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
    this.properties = properties;
    this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
    this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
}
复制代码

注入RedisPropertiesRedisSentinelConfigurationRedisClusterConfiguration

3.3 创建JedisClientConfiguration

private JedisClientConfiguration getJedisClientConfiguration(
			ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
    // 1.设置客户端读取超时时间、连接超时时间等
    JedisClientConfigurationBuilder builder = applyProperties(JedisClientConfiguration.builder());
    RedisProperties.Pool pool = getProperties().getJedis().getPool();
    // 2.如果声明了连接池,设置连接池最小值、最大值等
    if (pool != null) {
        applyPooling(pool, builder);
    }
    if (StringUtils.hasText(getProperties().getUrl())) {
        customizeConfigurationFromUrl(builder);
    }
    builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
    // 3.构建JedisClientConfiguration
    return builder.build();
}
复制代码

3.4 创建单机配置RedisStandaloneConfiguration

protected final RedisStandaloneConfiguration getStandaloneConfig() {
    RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
    ConnectionInfo connectionInfo = parseUrl(this.properties.getUrl());
    // 1.设置主机
    config.setHostName(connectionInfo.getHostName());
    // 2.设置端口
    config.setPort(connectionInfo.getPort());'
    // 3.设置用户名
    config.setUsername(connectionInfo.getUsername());
    // 4.设置密码
    config.setPassword(RedisPassword.of(connectionInfo.getPassword()));
    // 5.设置数据库
    config.setDatabase(this.properties.getDatabase());
    return config;
}
复制代码

3.5 创建JedisConnectionFactory

private JedisConnectionFactory createJedisConnectionFactory(
			ObjectProvider<JedisClientConfigurationBuilderCustomizer> builderCustomizers) {
    JedisClientConfiguration clientConfiguration = getJedisClientConfiguration(builderCustomizers);
   	// 使用2.4RedisStandaloneConfiguration和2.3JedisClientConfiguration创建JedisConnectionFactory
    return new JedisConnectionFactory(getStandaloneConfig(), clientConfiguration);
}
复制代码

JedisClientConfiguration中持有JedisClientConfigurationRedisStandaloneConfiguration

3.6 初始化JedisConnectionFactory

3.6.1 创建连接池JedisPool

protected Pool<Jedis> createRedisPool() {
    return new JedisPool(getPoolConfig(), getHostName(), getPort(), getConnectTimeout(), getReadTimeout(),
                         getUsername(), getPassword(), getDatabase(), getClientName(), isUseSsl(),
                         clientConfiguration.getSslSocketFactory().orElse(null), //
                         clientConfiguration.getSslParameters().orElse(null), //
                         clientConfiguration.getHostnameVerifier().orElse(null));
}
复制代码

3.6.2 创建JedisFactory

public JedisPool(final GenericObjectPoolConfig poolConfig, final String host, int port,
                 final int connectionTimeout, final int soTimeout, final String user, final String password,
                 final int database, final String clientName, final boolean ssl,
                 final SSLSocketFactory sslSocketFactory, final SSLParameters sslParameters,
                 final HostnameVerifier hostnameVerifier) {
    super(poolConfig, new JedisFactory(host, port, connectionTimeout, soTimeout, user, password,
                                       database, clientName, ssl, sslSocketFactory, sslParameters, hostnameVerifier));
}
复制代码

3.6.3 初始化连接池

public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {
    if (this.internalPool != null) {
        try {
            closeInternalPool();
        } catch (Exception e) {
        }
    }

    this.internalPool = new GenericObjectPool<>(factory, poolConfig);
}
复制代码

3.7 声明操作模板

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
		StringRedisTemplate template = new StringRedisTemplate();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}
}
复制代码

3.8 获取连接工厂

public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean pipeline) {
		RedisConnectionFactory factory = getRequiredConnectionFactory();
复制代码

2.5章节可以得知连接工厂为:JedisConnectionFactory

3.9 获取连接

protected Jedis fetchJedisConnector() {
    try {
		// 如果配置了连接池,则从连接池中获取连接
        if (getUsePool() && pool != null) {
            return pool.getResource();
        }

        Jedis jedis = createJedis();
        // force initialization (see Jedis issue #82)
        jedis.connect();

        potentiallySetClientName(jedis);
        return jedis;
    } catch (Exception ex) {
        throw new RedisConnectionFailureException("Cannot get Jedis connection", ex);
    }
}
复制代码

3.9.1 创建连接

private PooledObject<T> create() throws Exception {
    int localMaxTotal = getMaxTotal();
    Boolean create = null;
    final PooledObject<T> p;
    try {
        // 先从连接池中获取连接,如果池中没有连接则通过2.6.2章节的JedisFactory创建Jedis对象
        p = factory.makeObject();
        if (getTestOnCreate() && !factory.validateObject(p)) {
            createCount.decrementAndGet();
            return null;
        }
    }
    allObjects.put(new IdentityWrapper<>(p.getObject()), p);
    return p;
}
复制代码

3.9.2 归还连接

RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);
复制代码

如果设置了连接池会将使用完的连接归还到池中

3.10 连接管理

3.10.1 驱除连接

2.6.3初始化连接池会启动一个定时器执行驱除器任务

public final void setTimeBetweenEvictionRunsMillis(
            final long timeBetweenEvictionRunsMillis) {
    this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    startEvictor(timeBetweenEvictionRunsMillis);
}
复制代码

来看下驱除器的作用

Perform numTests idle object eviction tests, evicting examined objects that meet the criteria for eviction. If testWhileIdle is true, examined objects are validated when visited (and removed if invalid); otherwise only objects that have been idle for more than minEvicableIdleTimeMillis are removed.

如果testWhileIdle设置为true,使用连接的时候需要校验连接是否有效,无效则移除连接;否则超过minEvicableIdleTimeMillis时间的连接也会被从池中移除

3.10.2 创建连接

/**
 * Tries to ensure that the configured minimum number of idle instances are available in the pool.
 */
abstract void ensureMinIdle() throws Exception;
复制代码

驱除器任务不仅仅会驱除满足条件的空闲连接,还会去创建满足min-idle数量的连接放入连接池中

文章分类
后端
文章标签