Redis基础系列:多线程阻塞getResource

1 问题

Redis多线程读取数据,使用Jedis连接池并正常关闭Redis实例,
当获取Redis实例时,服务“卡死”,即无法从Redis正常获取数据。

2 场景复现

2.1 准备条件

  • Jedis连接池最大连接数设置为1;
  • 延迟10秒释放Redis实例资源;
  • 通过getResource获取Redis实例;
  • 开启10个线程从Redis读取数据。

2.2 测试样例

package com.monkey.java_study.redis;

import com.monkey.java_study.common.config.ThreadPoolConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Redis测试样例.
 *
 * @author xindaqi
 * @date 2021-07-09 23:45
 */
public class RedisPoolTest {

    private static final Logger logger = LogManager.getLogger(RedisPoolTest.class);

    public static String readRedis(JedisPool jedisPool) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set("uid", "123456");
            String value = jedis.get("uid");
            // 延迟10秒释放实例资源
            Thread.sleep(10000);
            return value;
        } catch (Exception e) {
            logger.info("Redis处理异常:", e);
            return null;
        }
    }

    public static void main(String[] args) {

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // Jedis池:最大连接数
        jedisPoolConfig.setMaxTotal(1);
        // Jedis池:最大空闲连接数
        jedisPoolConfig.setMaxIdle(10);
        // Jedis池:连接Redis超时时间
        int connectTimeout = 2000;
        // 创建连接池
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "localhost", 6379, connectTimeout, "123456", 0);
        try {
            for (int i = 0; i < 10; i++) {
                ThreadPoolConfig.threadPoolExecutorGenerate.submit(() -> {
                    String value = readRedis(jedisPool);
                    logger.info(">>>>>>>>Value from redis:{}", value);
                });
            }
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        } finally {
            ThreadPoolConfig.threadPoolExecutorGenerate.shutdown();
        }
    }
}

2.3 测试结果

测试结果如图2.1所示。Jedis连接池配置1个实例,开启10个线程从Redis读取数据,每次读取间隔10秒释放实例资源,因此,每次获取结果是串行的,一个线程读取Redis时,耗时10秒钟释放资源,另外一个线程才能获取Redis实例,即复现了多线程阻塞,在后台服务中,并发时出现接口无响应的问题。
在这里插入图片描述

图2.1 阻塞结果

3 方案

配置连接池的最大等待时间,当超过最大等待时间后,抛出异常,捕获异常后,返回提示信息,并释放Redis实例,为下一次查询提供释放资源。

3.1 测试样例

package com.monkey.java_study.redis;

import com.monkey.java_study.common.config.ThreadPoolConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * Redis测试样例.
 *
 * @author xindaqi
 * @date 2021-07-09 23:45
 */
public class RedisPoolTest {

    private static final Logger logger = LogManager.getLogger(RedisPoolTest.class);

    public static String readRedis(JedisPool jedisPool) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set("uid", "123456");
            String value = jedis.get("uid");
            // 延迟10秒释放实例资源
            Thread.sleep(10000);
            return value;
        } catch (Exception e) {
            logger.info("Redis处理异常:", e);
            return null;
        }
    }

    public static void main(String[] args) {

        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // Jedis池:最大连接数
        jedisPoolConfig.setMaxTotal(1);
        // Jedis池:最大空闲连接数
        jedisPoolConfig.setMaxIdle(10);
        // Jedis池:等待时间
        jedisPoolConfig.setMaxWaitMillis(3000);
        // Jedis池:连接Redis超时时间
        int connectTimeout = 2000;
        // 创建连接池
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "localhost", 6379, connectTimeout, "123456", 0);
        try {
            for (int i = 0; i < 10; i++) {
                ThreadPoolConfig.threadPoolExecutorGenerate.submit(() -> {
                    String value = readRedis(jedisPool);
                    logger.info(">>>>>>>>Value from redis:{}", value);
                });
            }
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        } finally {
            ThreadPoolConfig.threadPoolExecutorGenerate.shutdown();
        }
    }
}

3.2 测试结果

测试结果如3.1所示。当出现阻塞时,抛出异常,捕获异常,进一步处理,保证服务在配置的时间内可以拿到可读的数据。
在这里插入图片描述

图3.1 非阻塞读取Redis

4 异常信息

redis.clients.jedis.exceptions.JedisExhaustedPoolException: Could not get a resource since the pool is exhausted
at redis.clients.jedis.util.Pool.getResource(Pool.java:53) ~[jedis-3.5.1.jar:?]
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:330) ~[jedis-3.5.1.jar:?]
无法获取Redis实例资源。

  • 原因:
    资源耗尽。
  • 方案:
    等待下一次,或者提高连接池最大连接数。

5 小结

多线程使用Redis,避免阻塞:

  • 使用连接池;
  • 配置连接池最大等待时间;
  • 及时释放Redis实例资源:try-with-resource;
  • 捕获异常,并返回可读的提示信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天然玩家

坚持才能做到极致

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值