一.首先了解一下为什么使用连接池?
首先Redis也是一种数据库,它基于C/S模式,因此如果需要使用必须建立连接,稍微熟悉网络的人应该都清楚地知道为什么需要建立连接,C/S模式本身就是一种远程通信的交互模式,因此Redis服务器可以单独作为一个数据库服务器来独立存在。假设Redis服务器与客户端分处在异地,虽然基于内存的Redis数据库有着超高的性能,但是底层的网络通信却占用了一次数据请求的大量时间,因为每次数据交互都需要先建立连接,假设一次数据交互总共用时30ms,超高性能的Redis数据库处理数据所花的时间可能不到1ms,也即是说前期的连接占用了29ms,连接池则可以实现在客户端建立多个链接并且不释放,当需要使用连接的时候通过一定的算法获取已经建立的连接,使用完了以后则还给连接池,这就免去了数据库连接所占用的时间。
二.以上内容为百度知道片段,下面我们就来通过代码实现的方式来进一步了解redis连接池使用以及配置含义
package com.linux.util;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* @author tao
* @des redis 连接池工具类,采用jedis2.9客户端
*/
public class RedisConnectPollUtil{
private static final Log LOG = LogFactory.getLog(RedisConnectPollUtil.class);
//redis获取链接的并发锁
private static ReentrantLock redisPollLock= new ReentrantLock();
//连接redis实例的ip
private static final String REDIS_ADDRESS = "localhost";
//连接redis实例的端口
private static final int PORT = 6379;
//多线程环境中,连接实例的最大数,如果设为-1则无上线,建议设置,否则有可能导致资源耗尽
private static final int MAX_ACTIVE = 8;
//在多线程环境中,连接池中最大空闲连接数,单线程环境没有实际意义
private static final int MAX_OLDE = 4;
//在多线程环境中,连接池中最小空闲连接数
private static final int MIN_OLDE = 1;
//多长时间将空闲线程进行回收,单位毫秒
private static final int METM = 2000;
//对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断 (默认逐出策略)
private static final int SMETM = 2000;
//逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1,只有运行了此线程,MIN_OLDE METM/SMETM才会起作用
private static final int TBERM = 1000;
//当连接池中连接不够用时,等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
private static final int MAX_WAIT = 1000;
//超时时间,单位毫秒
private static final int TIME_OUT = 5000;
//在借用一个jedis连接实例时,是否提前进行有效性确认操作;如果为true,则得到的jedis实例均是可用的;
private static final boolean TEST_ON_BORROW = false;
//连接池实例
private static JedisPool jedisPool = null;
//初始化连接池,有好多重载的构造函数,根据自己业务实际需要来实例化JedisPoll
private static void initPoll() {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_OLDE);
config.setMaxWaitMillis(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);
config.setMinIdle(MIN_OLDE);
// config.setMinEvictableIdleTimeMillis(METM);
config.setSoftMinEvictableIdleTimeMillis(SMETM);
config.setTimeBetweenEvictionRunsMillis(TBERM);
jedisPool = new JedisPool(config, REDIS_ADDRESS, PORT, TIME_OUT);
} catch (Exception e) {
LOG.error("initial JedisPoll fail:",e);
}
}
//获取jedis连接实例
public static Jedis getJedis() {
redisPollLock.lock();
if(jedisPool == null) {
initPoll();
}
Jedis jedis = null;
try {
if(jedisPool != null) {
jedis = jedisPool.getResource();
}
} catch (Exception e) {
LOG.error("get jedis fail:",e);
}finally {
redisPollLock.unlock();
}
return jedis;
}
//归还jedis实例,2.9版本后jedisPool.returnResource(jedis);过期,被close替代,源码如下
/*
@Override
public void close() {
if (dataSource != null) {
if (client.isBroken()) {
this.dataSource.returnBrokenResource(this);
} else {
this.dataSource.returnResource(this);
}
} else {
client.close();
}
}
*/
//如果每次获取了jedis连接后不进行归还,redis不会自动回收,那么获取的最多连接数量为MAX_ACTIVE
//超出数量则会抛出异常redis.clients.jedis.exceptions.JedisException: Could not get a resource from the pool
public static void returnSource(Jedis jedis) {
if(jedis != null) {
//如果连接池已经关闭了,则返回-1,最大活跃数不会超过MAX_ACTIVE,最大空闲数不会超过MAX_OLDE
System.out.println(jedisPool.getNumWaiters()+"链接归还前活跃数:"+jedisPool.getNumActive()+"空闲连接数:"+jedisPool.getNumIdle());
jedis.close();
System.out.println(jedisPool.getNumWaiters()+"链接归还后活跃数:"+jedisPool.getNumActive()+"空闲连接数:"+jedisPool.getNumIdle());
}
}
private static int k = 0;
public static void main(String[] args) {
for(int i=0;i<20;i++) {
new Thread(new Runnable() {
@Override
public void run() {
Jedis jedis = getJedis();
System.out.println("第"+(k++)+"次"+jedis.lpop("pageList"));
returnSource(jedis);
//判断此连接是否还有效,有效返回true,否则返回false
//连接归还后,将不可用,会抛出redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
if(!jedis.isConnected()) {
jedis.lpop("pageList");
}
// jedisPool.close(); jedisPoll关闭后将导致池不可用
// System.out.println("jedispoll是否关闭了?"+jedisPool.isClosed());
}
}).start();
}
try {
//主线程等待一定时间,否则会发生线程执行时效错乱问题
Thread.currentThread().sleep(15000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(jedisPool.getNumWaiters()+"最终链接归还后活跃数:"+jedisPool.getNumActive()+"空闲连接数:"+jedisPool.getNumIdle());
destroy();
}
//在运用正常运行时,通常是不会手动调用jedisPool.close();池内将保持最大空闲数的连接,如果设置了逐出策略
//那么池内就会保留最小空闲连接,如果应用突然关闭,我们需要在bean销毁时将连接池销毁.
public static void destroy(){
if(jedisPool != null) {
try {
jedisPool.destroy();
} catch (Exception e) {
LOG.error("jedisPool destroy fail ",e);
}
}
}
}
还有另外一种写法,这种写法跟在spring的XML中配置进行jedisPoll初始化差不多,代码如下:
package com.linux.util;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import com.linux.service.RedisConnectPoll;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
@Service("redisConnectPoll")
public class RedisConnectPollUtil2 implements InitializingBean,DisposableBean,RedisConnectPoll{
private static final Log LOG = LogFactory.getLog(RedisConnectPollUtil.class);
//连接redis实例的ip
private static final String REDIS_ADDRESS = "localhost";
//连接redis实例的端口
private static final int PORT = 6379;
//多线程环境中,连接实例的最大数,如果设为-1则无上线,建议设置,否则有可能导致资源耗尽
private static final int MAX_ACTIVE = 8;
//在多线程环境中,连接池中最大空闲连接数,单线程环境没有实际意义
private static final int MAX_OLDE = 4;
//在多线程环境中,连接池中最小空闲连接数
private static final int MIN_OLDE = 1;
//多长时间将空闲线程进行回收,单位毫秒
private static final int METM = 2000;
//对象空闲多久后逐出, 当空闲时间>该值 且 空闲连接>最大空闲数 时直接逐出,不再根据MinEvictableIdleTimeMillis判断 (默认逐出策略)
private static final int SMETM = 2000;
//逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1,只有运行了此线程,MIN_OLDE METM/SMETM才会起作用
private static final int TBERM = 1000;
//当连接池中连接不够用时,等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
private static final int MAX_WAIT = 1000;
//超时时间,单位毫秒
private static final int TIME_OUT = 5000;
//在借用一个jedis连接实例时,是否提前进行有效性确认操作;如果为true,则得到的jedis实例均是可用的;
private static final boolean TEST_ON_BORROW = false;
//连接池实例
private static JedisPool jedisPool = null;
@Override
public void afterPropertiesSet() throws Exception {
try {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(MAX_ACTIVE);
config.setMaxIdle(MAX_OLDE);
config.setMaxWaitMillis(MAX_WAIT);
config.setTestOnBorrow(TEST_ON_BORROW);
config.setMinIdle(MIN_OLDE);
// config.setMinEvictableIdleTimeMillis(METM);
config.setSoftMinEvictableIdleTimeMillis(SMETM);
config.setTimeBetweenEvictionRunsMillis(TBERM);
jedisPool = new JedisPool(config, REDIS_ADDRESS, PORT, TIME_OUT);
} catch (Exception e) {
LOG.error("initial JedisPoll fail:",e);
}
}
//获取jedis连接实例
@Override
public Jedis getJedis() {
if(jedisPool != null) {
return jedisPool.getResource();
}
return null;
}
//归还jedis连接实例
@Override
public void returnSource(Jedis jedis) {
if(jedis != null) {
jedis.close();
}
}
@Override
public void destroy() throws Exception {
if(jedisPool != null) {
try {
jedisPool.destroy();
} catch (Exception e) {
LOG.error("jedisPool destroy fail ",e);
}
}
}
}