经历描述:
在一次springboot项目里使用Redis做缓存,记录某网页访问次数的时,连接异常,一直报

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
        at redis.clients.util.Pool.getResource(Pool.java:53)
        at redis.clients.jedis.JedisPool.getResource(JedisPool.java:226)
                ...................
Caused by: redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketTimeoutException: connect timed out
                ...................
Caused by: java.net.SocketTimeoutException: connect timed out

是使用了Jedis库来操作腾讯云服务器上的Redis,检查过application.properties配置文件里面的 服务器地址信息,端口号,连接超时时间(毫秒),服务器也对6379端口号开放了 , 都没问题

redis的配置application.yml(或application.properties)中
spring.redis.timeout连接超时时间(毫秒)中设置不能为0,
一般修改如下:spring.redis.timeout=5000

#Redis服务器地址
spring.redis.host=139.**.**.*
#Redis服务器密码
spring.redis.password=111111
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0

也检查了导入的依赖问题,在网上查找了相关帖子说:如果你用的jedis 2.4.2以及以前版本,用完之后别忘了return连接到资源池。不过2.5.0版本之后,jedis使用了try-with-resource,jedis用完了就会自动归还了,不用每次都自己return了。相应的他还给出了一个完整的,带有return资源到连接池的Jedis工具类,但是我的 Jedis 版本是

在这里插入图片描述
并不是这个问题,(PS:在这个bug爆发之前,我也出现一个有关java.lang.NoClassDefFoundError: redis/clients/jedis/JedisPoolConfig的问题是因为我的jedis版本和redis版本出现冲突导致,当我删除掉jedis的version号以后,该Bug就已经解决

加粗样式

又继续在网上查找原因,这里看到一个帖子写过一个封装的很好的Jedis工具类,这里保存一下

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;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
/**
 * @说明: Redis操作工具类
 * @return
 * @date 2019/3/5 11:36
 */
public class JedisUtil {
 
    private static final Log logger = LogFactory.getLog(JedisUtil.class);
 
    //Redis服务器IP
    private static String IP = "127.0.0.1";
 
    //Redis的端口号
    private static int PORT = 6379;
 
    //Redis服务密码
    private static String password = "******";
 
    //可用连接实例的最大数目,默认值为8;
    //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
    private static int MAX_ACTIVE = 64;
 
    //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。
    private static int MAX_IDLE = 20;
 
    //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException;
    private static int MAX_WAIT = 3000;
 
    private static int TIMEOUT = 3000;
 
    //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
    private static boolean TEST_ON_BORROW = true;
 
    //在return给pool时,是否提前进行validate操作;
    private static boolean TEST_ON_RETURN = true;
 
    private static Map<String, JedisPool> maps = new ConcurrentHashMap<String, JedisPool>();
 
    private JedisUtil() {
    }
 
    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
     */
    private static class RedisUtilHolder {
        private static JedisUtil instance = new JedisUtil();
    }
 
    /**
     * 当getInstance方法第一次被调用的时候,它第一次读取 RedisUtilHolder.instance,导致RedisUtilHolder类得到初始化;
     * 而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建RedisUtil的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,
     * 并由虚拟机来保证它的线程安全性。 这个模式的优势在于,getInstance方法并没有被同步,
     * 并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
     */
    public static JedisUtil getInstance() {
        return RedisUtilHolder.instance;
    }
 
    /**
     * 获取连接池.
     */
    private JedisPool getPool(String ip, int port) {
        String key = ip + ":" + port;
        JedisPool pool = null;
        if (!maps.containsKey(key)) {//根据ip和端口判断连接池是否存在.
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            config.setTestOnReturn(TEST_ON_RETURN);
 
            //false:如果连接池没有可用Jedis连接,立即抛出异常;默认true时:如果连接池没有可用Jedis连接,
            //会等待maxWaitMillis(毫秒),依然没有获取到可用Jedis连接
            //config.setBlockWhenExhausted(false);
 
            try {
                pool = new JedisPool(config, ip, port, TIMEOUT,password);
                maps.put(key, pool);
            } catch (Exception e) {
                logger.error("初始化Redis连接池异常:", e);
            }
        } else {
            pool = maps.get(key);
        }
        return pool;
    }
 
    /**
     * 获取Jedis实例
     */
    public Jedis getJedis() {
        Jedis jedis = null;
        try {
            jedis = getPool(IP, PORT).getResource();
        } catch (Exception e) {
            logger.error("获取Jedis实例异常:", e);
            // 销毁对象
            getPool(IP, PORT).returnBrokenResource(jedis);
        }
        return jedis;
    }
 
    /**
     * 释放jedis资源到连接池
     */
    public void returnResource(final Jedis jedis) {
        if (jedis != null) {
            getPool(IP, PORT).returnResource(jedis);
        }
    }
 
    /**
     * 获取数据
     */
    public Object get(String key) {
        Object value = null;
        Jedis jedis = null;
        try {
            jedis = getJedis();
            value = jedis.get(key);
        } catch (Exception e) {
            logger.warn("获取数据异常:", e);
        } finally {
            //返还到连接池
            returnResource(jedis);
        }
        return value;
    }
 
    //设置数据
    public boolean set(String key,String value){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.set(key,value);
            return true;
        }catch (Exception e){
            logger.warn("设置数据异常",e);
            return false;
        }finally {
            //返还到连接池
            returnResource(jedis);
        }
    }
 
    //删除数据
    public void delete(String key){
        Jedis jedis = null;
        try {
            jedis = getJedis();
            jedis.del(key);
        }catch (Exception e){
            logger.warn("删除数据异常",e);
        }finally {
            //返还到连接池
            returnResource(jedis);
        }
    }
 
    public static void main(String[] args) {
        JedisUtil.getInstance().set("lp","yuzhsaneh");
        Object val = JedisUtil.getInstance().get("lp");
        System.out.println(val);
    }
}

然后继续检查,在服务器的redis配置文件redis.conf中,要将 protected-mode yes 改为 protected-mode no (即该配置项表示是否开启保护模式,默认是开启,开启后Redis只会本地进行访问,拒绝外部访问)。
注释掉 bind127.0.0.1 即 #bind 127.0.0.1 (ps: 不注释掉,表示指定 redis 只接收来自于该 IP 地址的请求,注释掉后,则表示将处理所有请求)。
daemonize yes

而我已经修改过,还是不行,这时候查阅了阿里云社区的一篇博客详细的介绍了这个问题出现的根源(链接:https://help.aliyun.com/knowledge_detail/71967.html?spm=a2c4g.11186623.2.27.345b39e9vLkWWz#cc1

无法从连接池获取到Jedis连接
异常堆栈

当blockWhenExhausted连接池参数等于true(默认值)时,如果连接池没有可用的Jedis连接,则会等待一段时间,等待的时间由maxWaitMillis参数决定,单位为毫秒,如果依然没有获取到可用的Jedis连接,才会出现下列异常。

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
    …
Caused by: java.util.NoSuchElementException: Timeout waiting for idle object
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449)

当blockWhenExhausted连接池参数等于false时,如果连接池没有可用的Jedis连接,则会立即出现下列异常。

redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
    …
Caused by: java.util.NoSuchElementException: Pool exhausted
    at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:464)

异常描述
上述异常是客户端没有从连接池获得可用的Jedis连接造成,Jedis资源最大数量由maxTotal值决定,可能有下列几种原因。

连接泄露
JedisPool默认的maxTotal值为8,从下列代码得知,从JedisPool中获取了8个Jedis资源,但是没有归还资源。因此,当第9次尝试获取Jedis资源的时候,则无法调用jedisPool.getResource().ping()。(查看过源码,在finally中链接已经释放,不是此类问题)
业务并发量大
maxTotal设置得过小了。(我的项目压根就没有上线,不存在什么并发)
Jedis连接阻塞
例如Redis发生了阻塞(例如慢查询等原因),所有连接在超时时间范围内等待,并发量较大时,会造成连接池资源不足。(我Redis第一次连接,所以这种情况也可以排除)

然后又查了是不是因为修改坏了redis.conf文件,在云服务器上卸载重装了redis,问题还是存在
,中间查过防火墙,是未启用的状态
在这里插入图片描述
还检查过服务器是否网络不通?但是并不是
后面使用RedisDesktopManager测试连接,发现还是服务器的问题,整个bug期间,服务器redis-cli一直是好的,能够ping通,以及测试数据,感觉就是配置是没有任何问题的,但是服务器一直是访问不到的情况,最后抱着死马当作活马医的心态,重启的服务器的防火墙,Bug解决
在这里插入图片描述

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐