1、为什么要使用连接池以及常用客户端的区别
众所周知,Redis是单线程的,那为什么还要使用连接池?首先Redis也是一种基于内存数据库,有着很高的性能,但是我们的系统使用Redis服务时需要先建立连接才能使用,使用之后又需要断开连接,而一个完整的请求处理过程中性能的消耗主要是在底层的网络通信;连接池则可以实现在客户端建立多个链接并且不释放,当需要使用连接的时候通过一定的算法获取已经建立的连接,使用完了以后则还给连接池,这就免去了数据库连接所占用的时间。
在Java中Redis提供了常见的客户端如Lettuce和Jedis,这两种客户端主要的区别是Jedis是采用直接连接的模式来访问Redis服务,但是对于多线程中只有一个Jedis实例的情况是非线程安全的,我们看一下Jedis进行连接的源码来说明下为什么说Jedis是线程非安全的:
// outputStream/inputStream用于发送命令和获取返回值,但是这两个参数也是共享的,所以也会引发线程安全问题
private RedisOutputStream outputStream;
private RedisInputStream inputStream;
private Socket socket;
public void connect() {
if (!isConnected()) {
try {
// 当有多个线程时,socket是共享的,所以socket 会出现线程安全问题
socket = new Socket();
// ->@wjw_add
socket.setReuseAddress(true);
socket.setKeepAlive(true); // Will monitor the TCP connection is
// valid
socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to
// ensure timely delivery of data
socket.setSoLinger(true, 0); // Control calls close () method,
// the underlying socket is closed
// immediately
// <-@wjw_add
socket.connect(new InetSocketAddress(host, port), connectionTimeout);
socket.setSoTimeout(soTimeout);
if (ssl) {
if (null == sslSocketFactory) {
sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
}
socket = sslSocketFactory.createSocket(socket, host, port, true);
if (null != sslParameters) {
((SSLSocket) socket).setSSLParameters(sslParameters);
}
if ((null != hostnameVerifier) &&
(!hostnameVerifier.verify(host, ((SSLSocket) socket).getSession()))) {
String message = String.format(
"The connection to '%s' failed ssl/tls hostname verification.", host);
throw new JedisConnectionException(message);
}
}
outputStream = new RedisOutputStream(socket.getOutputStream());
inputStream = new RedisInputStream(socket.getInputStream());
} catch (IOException ex) {
broken = true;
throw new JedisConnectionException("Failed connecting to host "
+ host + ":" + port, ex);
}
}
}
可以通过建立多个Jedis实例来解决,但是如果链接数量过多时我们可以采用Jedis连接池(JedisPool)的方式来解决;如果使用了SpringBoot则在"JedisConnectionFactory"中有一个方法getConnection() 用来获取连接的,具体源码如下:
public RedisConnection getConnection() {
if (isRedisClusterAware()) {
return getClusterConnection();
}
Jedis jedis = fetchJedisConnector();
JedisConnection connection = (getUsePool() ? new JedisConnection(jedis, pool, getDatabase(), getClientName())
: new JedisConnection(jedis, null, getDatabase(), getClientName()));
connection.setConvertPipelineAndTxResults(convertPipelineAndTxResults);
return postProcessConnection(connection);
}
protected Jedis fetchJedisConnector() {
try {
// 如果使用了连接池,则从连接池中获取一个Jedis的实例
if (getUsePool() && pool != null) {
return pool.getResource();
}
// 如果没有使用连接池,则重新生成一个Jedis实例
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);
}
}
Lettuce是基于Netty来实现的,连接实例可以在多个线程间共享,Netty可以使多线程的应用使用同一个连接实例,而不用担心并发线程的数量。如果有必要的话也可以用来添加实例个数。
2、如何在Springboot中配置使用连接池
在本节中就上述介绍到的两个客户端进行连接池的配置,这两种客户端均有两种配置方式
方法一:使用系统配置文件进行连接池的配置
####################Jedis连接池配置########################
spring:
cache:
redis:
cache-null-values: false # 不缓存空值,如果是用于缓存时可以设置这一项
redis:
# Redis的服务器地址
host: localhost
# Redis的连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 1000
# Redis数据库索引(默认为0);Redis数据库默认有16个独立的数据库,编号0-15
database: 0
jedis:
pool:
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 0
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
####################Jedis连接池配置########################
spring:
cache:
redis:
cache-null-values: false # 不缓存空值
redis:
# Redis的服务器地址
host: localhost
# Redis的连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间(毫秒)
timeout: 1000
# Redis数据库索引(默认为0);Redis数据库默认有16个独立的数据库,编号0-15
database: 0
lettuce:
# 在关闭客户端连接之前等待任务处理完成的最长时间,在这之后,无论任务是否执行完成,都会被执行器关闭,默认100ms
shutdown-timeout: 1000
pool:
max-active: 8
min-idle: 0
max-wait: -1
max-idle: 8
如果其他都使用默认的配置,到这里就可以了
方法二:在配置类中进行配置,现在配置文件中添加如下配置:
redis.host: localhost
redis.port: 6379
redis.database: 1
redis.timeout: 3000
redis.shutdown-timeout: 1000
redis.pool.max-idle: 8
redis.pool.max-active: 8
redis.pool.min-idle: 0
redis.pool.max-wait: -1
创建配置类文件:
@Configuration
public class RedisConfig {
@Value("${redis.host}")
private String redisHostName;
@Value("${redis.port}")
private int redisPort;
@Value("${redis.database:0}")
private int database;
@Value("${redis.timeout}")
private long timeOut;
@Value("${redis.pool.max-idle}")
private int maxIdle;
@Value("${redis.pool.max-active}")
private int maxActive;
@Value("${redis.pool.min-idle}")
private int minIdle;
@Value("${redis.pool.max-wait}")
private long maxWait;
// **************LettuceConnectionFactory配置************************
@Bean
LettuceConnectionFactory lettuceConnectionFactory() {
//redis基本配置
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHostName,redisPort);
redisStandaloneConfiguration.setDatabase(database);
// 连接池的配置
GenericObjectPoolConfig<Object> genericObjectPoolConfig=new GenericObjectPoolConfig<Object>();
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMaxWaitMillis(maxWait);
genericObjectPoolConfig.setMinIdle(minIdle);
genericObjectPoolConfig.setMaxTotal(maxActive);
// redis客户端配置
LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder poolBuilder=LettucePoolingClientConfiguration.builder();
poolBuilder.poolConfig(genericObjectPoolConfig);
poolBuilder.commandTimeout(Duration.ofMillis(timeOut)); // 设置超时时间
poolBuilder.shutdownTimeout(Duration.ofMillis(timeOut)); // 设置客户端关闭之前的等待时间
LettucePoolingClientConfiguration clientConfiguration=poolBuilder.build();
// 创建连接
LettuceConnectionFactory lettConnectionFactory=new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfiguration);
return lettConnectionFactory;
}
// **************JedisConnectionFactory配置************************
@Bean
JedisConnectionFactory jedisConnectionFactory() {
// redis基本配置
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(redisHostName,redisPort);
redisStandaloneConfiguration.setDatabase(database);
// 连接池的配置
GenericObjectPoolConfig<Object> genericObjectPoolConfig=new GenericObjectPoolConfig<Object>();
genericObjectPoolConfig.setMaxIdle(maxIdle);
genericObjectPoolConfig.setMaxWaitMillis(maxWait);
genericObjectPoolConfig.setMinIdle(minIdle);
genericObjectPoolConfig.setMaxTotal(maxActive);
// redis客户端配置
JedisClientConfiguration.JedisPoolingClientConfigurationBuilder poolBuilder=JedisClientConfiguration//
.builder()//
.connectTimeout(Duration.ofMillis(timeOut))//
.usePooling()//
.poolConfig(genericObjectPoolConfig);
JedisClientConfiguration jedisClientConfiguration=poolBuilder.build();
// 创建连接
JedisConnectionFactory jedisConnectionFactory=new JedisConnectionFactory(redisStandaloneConfiguration,jedisClientConfiguration);
return jedisConnectionFactory;
}
// ....其他配置省略........
}
为了省略篇幅,将JedisConnectionFactory /LettuceConnectionFactory 的配置在一个类中展示了,实际开发中配置一个即可。