本文主要是介绍rediscluster下集群应用-rediscluster连接池实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
单例模式在生产环境jedis集群中的应用
背景:不久前单位上线一款应用,上了生产环境之后,没过多久,便吃掉了服务器所有的内存,最后导致网站服务挂了。
在解决了这一问题之后,我发现这其实是典型的一单例模式,现分享一下。
之前存在问题的老代码如下:
这是导致问题所在的那个关键方法
public synchronized static JedisCluster getJedisCluster() {JedisPoolConfig config = new JedisPoolConfig();config.setMaxTotal(MAX_ACTIVE);config.setMaxIdle(MAX_IDLE);config.setMaxWaitMillis(MAX_WAIT);config.setTestOnBorrow(TEST_ON_BORROW);// 集群模式JedisPoolConfig poolConfig = new JedisPoolConfig();Set<HostAndPort> nodes = new HashSet<HostAndPort>();HostAndPort hostAndPort1 = new HostAndPort("服务器地址1", 端口1);HostAndPort hostAndPort2 = new HostAndPort("服务器地址2", 端口2);HostAndPort hostAndPort3 = new HostAndPort("服务器地址3", 端口3);nodes.add(hostAndPort1);nodes.add(hostAndPort2);nodes.add(hostAndPort3);JedisCluster jedisCluster = new JedisCluster(nodes, poolConfig);return jedisCluster; }
以上这段代码是有问题的,大家看出来了吗?
问题在于,虽然方法声明为synchronized static,但是在并发多线程的情况下,并不能保证每个用户线程只生成一个JedisCluster的实例。
这样就会导致每个线程都会创建jedisCluster的实例,就会消耗内存,而且这块内存又没有被及时地释放掉,导致多用户并发以后,快速吃光了服务器的内存。
解决方法就是使用单例模式,把JedisCluster作为static的类成员,且使用懒汉单例模式,代码如下:
public class OuterClass{...private static JedisCluster jedisCluster = null;...public synchronized static JedisCluster getJedisCluster() {JedisPoolConfig config = new JedisPoolConfig();config.setMaxTotal(MAX_ACTIVE);config.setMaxIdle(MAX_IDLE);config.setMaxWaitMillis(MAX_WAIT);config.setTestOnBorrow(TEST_ON_BORROW);// 集群模式JedisPoolConfig poolConfig = new JedisPoolConfig();Set<HostAndPort> nodes = new HashSet<HostAndPort>();HostAndPort hostAndPort1 = new HostAndPort("服务器地址1", 端口1);HostAndPort hostAndPort2 = new HostAndPort("服务器地址2", 端口2);HostAndPort hostAndPort3 = new HostAndPort("服务器地址3", 端口3);nodes.add(hostAndPort1);nodes.add(hostAndPort2);nodes.add(hostAndPort3);// 只有当jedisCluster为空时才实例化if (jedisCluster == null) {jedisCluster = new JedisCluster(nodes, poolConfig);}return jedisCluster;} }
这样就会保证即使在高并发的环境下,所有用户线程还是只会拥有一个JedisCluster的实例。
这个是https://www.cnblogs.com/davidgu/p/6277924.html 用户的处理方式。
我的解决方式是讲Rediscluster也封装成连接池
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPoolConfig;
public class LpPoolFactory implements PooledObjectFactory<JedisCluster> {
/**
* 功能描述:激活资源对象
*
* 什么时候会调用此方法
* 1:从资源池中获取资源的时候
* 2:资源回收线程,回收资源的时候,根据配置的 testWhileIdle 参数,
* 判断 是否执行 factory.activateObject()方法,true 执行,false 不执行
* @param arg0
*/
public void activateObject(PooledObject<JedisCluster> arg0) throws Exception {
System.out.println("activate Object");
}
public void destroyObject(PooledObject<JedisCluster> arg0) throws Exception {
System.out.println("destroy Object");
JedisCluster JedisCluster = arg0.getObject();
JedisCluster = null;
}
public PooledObject<JedisCluster> makeObject() throws Exception {
System.out.println("make Object");
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大连接数
poolConfig.setMaxTotal(300);
// 最大空闲数
poolConfig.setMaxIdle(10);
// 最大允许等待时间,如果超过这个时间还未获取到连接,则会报JedisException异常:
// Could not get a resource from the pool
poolConfig.setMaxWaitMillis(1000);
Set<HostAndPort> nodes = new LinkedHashSet<HostAndPort>();
nodes.add(new HostAndPort("192.168.88.140", 7000));
nodes.add(new HostAndPort("192.168.88.140", 7001));
nodes.add(new HostAndPort("192.168.88.140", 7002));
nodes.add(new HostAndPort("192.168.88.140", 7003));
nodes.add(new HostAndPort("192.168.88.140", 7004));
nodes.add(new HostAndPort("192.168.88.140", 7005));
JedisCluster JedisCluster = new JedisCluster(nodes, poolConfig);
return new DefaultPooledObject<JedisCluster>(JedisCluster);
}
/**
* 功能描述:钝化资源对象
*
* 什么时候会调用此方法
* 1:将资源返还给资源池时,调用此方法。
*/
public void passivateObject(PooledObject<JedisCluster> arg0) throws Exception {
System.out.println("passivate Object");
}
/**
* 功能描述:判断资源对象是否有效,有效返回 true,无效返回 false
*
* 什么时候会调用此方法
* 1:从资源池中获取资源的时候,参数 testOnBorrow 或者 testOnCreate 中有一个 配置 为 true 时,则调用 factory.validateObject() 方法
* 2:将资源返还给资源池的时候,参数 testOnReturn,配置为 true 时,调用此方法
* 3:资源回收线程,回收资源的时候,参数 testWhileIdle,配置为 true 时,调用此方法
*/
public boolean validateObject(PooledObject<JedisCluster> arg0) {
System.out.println("validate Object");
return true;
}
}
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.JedisCluster;
public class RedisClusterPool {
public static GenericObjectPool<JedisCluster> objectPool=null;
static {
//工厂
LpPoolFactory factory = new LpPoolFactory();
//资源池配置
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
//设置最大实例总数
poolConfig.setMaxTotal(500);
//控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。
poolConfig.setMinIdle(1);
poolConfig.setMaxIdle(1);
//表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;
poolConfig.setMaxWaitMillis(1000);
// 在borrow一个jedis实例时,是否提前进行alidate操作;如果为true,则得到的jedis实例均是可用的;
poolConfig.setTestOnBorrow(true);
// 在还会给pool时,是否提前进行validate操作
poolConfig.setTestOnReturn(true);
//如果为true,表示有一个idle object evitor线程对idle object进行扫描,如果validate失败,此object会被从pool中drop掉;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
poolConfig.setTestWhileIdle(true);
//表示一个对象至少停留在idle状态的最短时间,然后才能被idle object evitor扫描并驱逐;这一项只有在timeBetweenEvictionRunsMillis大于0时才有意义;
poolConfig.setMinEvictableIdleTimeMillis(6000);
//表示idle object evitor两次扫描之间要sleep的毫秒数
poolConfig.setTimeBetweenEvictionRunsMillis(30000);
//在minEvictableIdleTimeMillis基础上,加入了至少minIdle个对象已经在pool里面了。如果为-1,evicted不会根据idle time驱逐任何对象。如果minEvictableIdleTimeMillis>0,则此项设置无意义,且只有在timeBetweenEvictionRunsMillis大于0时才有意义
// poolConfig.setSoftMinEvictableIdleTimeMillis();
//创建资源池
objectPool = new GenericObjectPool<JedisCluster>(factory,poolConfig);
}
@SuppressWarnings("finally")
public static JedisCluster getJedisCluster(){
JedisCluster jedisCluster=null;
try {
jedisCluster = objectPool.borrowObject();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
return jedisCluster;
}
}
public static void closeJedisCluster(JedisCluster jedisCluster ){
if(jedisCluster!=null){
objectPool.returnObject(jedisCluster);
}
}
}
jediscluster从slot中取jedis就是从jedispool中取的,所以jedisCluster初始化资源池没有意义。只要保证jedisCluster作为静态方法就好。
-
JedisPool connectionPool = cache.getSlotPool(slot);
-
if (connectionPool != null) {
-
// It can't guaranteed to get valid connection because of node
-
// assignment
-
return connectionPool.getResource();
-
}
这位仁兄,想请教一下,使用jediscluster需要自己释放资源吗?
不需要,jedis资源由handler获取,jedisCluster执行command的run方法,runWithRetries负责releaseConnection
也就是说jediscluster的底层实现本来就是用连接池做的,不需要再多此一举去做一个jediscluster的池
这篇关于rediscluster下集群应用-rediscluster连接池实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!