记录redis连接被打满的踩坑之路

news2025/1/10 23:25:29

一、系统异常现象

系统有一个功能向别的系统多线程推送用户数据信息,前几天发现该推送功能报内部错误,经过查看后台日志文件,发现org.redisson.client.RedisConnectionException: Unable to connect to Redis server:,io.netty.channel.ChannelException: Unable to create Channel from class class io.netty.channel.socket.nio.NioSocketChannel。

错误日志输出特别清晰明了,显示redis的连接已经满了,不能创建新的NioSocketChannel了。

二、问题分析

在该功能用户redis的地方无非就两个。

  1. 第一个场景是多线程请求其他系统的token值,这个过程中加了锁,确保多线程只有一个线程拿到token值并且将该token写入redis,其他线程复用这个线程获取的token进行用户数据推送,这样可以降低双方系统的资源消耗。

2、第二个场景就是在redis里面取出上一步的token值,进行下一步的业务处理。

关键代码:

public String generateToken(RedissonClient redissonClient) {

        RLock lock = redissonClient.getLock(InterfacesConstants.REDISSON_LOCK);

        try {

            lock.lock(30, TimeUnit.SECONDS);

            RBucket<String> testBucket = redissonClient.getBucket(InterfacesConstants.PUSH_USER_TOKEN);
            //获取推送用户的token值
            String pushUserToken = testBucket.get();

            log.info("线程名为:{}的线程获取到「pushUserToken」为:{}", Thread.currentThread().getName(), pushUserToken);

            if (EmptyUtil.isEmpty(pushUserToken)) {
                //获取token
                IDasTokenResultDto tokenFromIDas = getTokenFromIDas();

                //获取IAM平台的token值
                pushUserToken = tokenFromIDas.getAccess_token();
                if (EmptyUtil.isEmpty(pushUserToken)) {
                    log.info("未接收到IAM返回的token值!!");
                    return null;
                }
                //重置token过期时间
                testBucket.set(pushUserToken, (Integer.parseInt(tokenFromIDas.getExpires_in())) / 1000 - 5, TimeUnit.SECONDS);

            }

            //拼接IAM所需要的token值
            String finalAccessToken = "Bearer " + pushUserToken;

            return finalAccessToken;
        } catch (Exception e) {
            log.error("获取IAM平台的token值错误,error message:{}", e.getMessage());
        } finally {
            lock.unlock();
        }

        return null;

    }

仔细检查上述场景的业务代码,也没有发现类似的问题。。。。。。

回过头又仔细分析了整个业务链条的场景,终于发现了问题所在。问题出在了RedissonClient实例化上,一般的项目会把这种公共的类交给spring容器去自动实例化,这个老项目因为架构的原因开发者自己去实例的该对象。

所以每次触发这个功能都会实例一次RedissonClient对象,从而导致redis的连接越来越来多并没有释放,一直到沾满。所以只要在RedissonClient实例的时候做点小动作就可以填上这个坑了。

原代码:

@Slf4j
public class RedissonConfig {

    private static final String REDIS_CONFIG_FILE = "Config/redis.properties";

    private static final String SINGLE = "standalone";

    private static final String CLUSTER = "cluster";

    private static final String HOST_PORT = "hostports";

    private RedissonConfig() {
        throw new IllegalStateException("Utility class");
    }


  
    public static RedissonClient initialRedissonClient() {
        Map<String, String> redisInfoMap = getHostAndPwd();
        if(EmptyUtil.isEmpty(redisInfoMap)){
            return null;
        }

        String runMode = redisInfoMap.get("runMode");
        RedissonClient redisson=null;
        //单机
        if(Objects.equals(SINGLE,runMode)){

            redisson = initialSingleRedisson();
            log.info("实例化单节点「RedissonClient」成功!!");
        //集群
        }else if (Objects.equals(CLUSTER,runMode)){
            redisson = initialClusterRedisson();
            log.info("实例集群「RedissonClient」成功!!");
        }

        return redisson;


    }

 
    public static RedissonClient initialSingleRedisson(){

        try {
            Map<String, String> redisInfoMap = getHostAndPwd();
            if(EmptyUtil.isEmpty(redisInfoMap)){
                return null;
            }

            String hostPort = redisInfoMap.get(HOST_PORT);
            String redisPwd = redisInfoMap.get("pwd");
            //单机
            Config config = new Config();
            config.useSingleServer().setAddress("redis://" + hostPort).setPassword(redisPwd);
            RedissonClient redisson = Redisson.create(config);
            log.info("「SingleRedisson」初始化成功!!");
            return redisson;

        }catch (Exception e){
            log.info("「SingleRedisson」初始化异常!!message:{}",e.getMessage());
            e.printStackTrace();
        }
        return null;
    }


    public static RedissonClient initialClusterRedisson(){
            //集群
        try {
            Map<String, String> redisInfoMap = getHostAndPwd();
            if(EmptyUtil.isEmpty(redisInfoMap)){
                return null;
            }

            String hostPort = redisInfoMap.get(HOST_PORT);
            String redisPwd = redisInfoMap.get("pwd");
            Config config = new Config();
            String[] hostPostArr = hostPort.split(",");
            for (String str : hostPostArr) {
                config.useClusterServers()
                        //设置扫描间隔时间
                        .setScanInterval(2000)
                        .setPassword(redisPwd)
                        .addNodeAddress("redis://" + str);
            }
            RedissonClient redisson = Redisson.create(config);
            return redisson;

        }catch (Exception e) {
            log.info("「ClusterRedisson」初始化异常!!message:{}",e.getMessage());
            e.printStackTrace();
        }
        return null;
    }


    public static Map<String,String> getHostAndPwd(){
        try {
            //加载连接池配置文件
            Properties props = PropsUtil.loadProps(REDIS_CONFIG_FILE);
            String runMode = props.getProperty("redis.runMode");
            String hostPort = props.getProperty("redis.hostports");
            String redisPwd = props.getProperty("redis.password");
            Map<String, String> map = new HashMap<>();
            map.put("runMode",runMode);
            map.put(HOST_PORT,hostPort);
            map.put("pwd",redisPwd);

            log.info("解析redis配置文件获取到的连接信息:{}",map);
            if(EmptyUtil.isEmpty(map)){
                log.warn("未解析到redis的配置信息!!");
                return Maps.newHashMap();
            }

            return map;
        }catch (Exception e) {
            log.info("redisson初始化获取结点和密码失败");
            e.printStackTrace();
        }
        return Maps.newHashMap();
    }


}

三、问题解决

知道问题在哪了,剩下的就好说了,我们只要保证该Java进程里面实例化有且只有一个RedissonClient对象就可以,spring的单例bean还用不了,这就得造一波轮子了。都到这了,单例模式走起。。。

优化后代码:

@Slf4j
public class RedissonConfig {

    private static final String REDIS_CONFIG_FILE = "Config/redis.properties";

    private static final String SINGLE = "standalone";

    private static final String CLUSTER = "cluster";

    private static final String HOST_PORT = "hostports";
    /**
     * volatile修饰(防止指令重排序)
     **/
    private volatile static RedissonClient instance;

    private RedissonConfig() {
        throw new IllegalStateException("Utility class");
    }

    public static RedissonClient initialRedissonClient() {

        //单例模式的双重校验
        if(instance == null){
            log.info("「RedissonClient」实例instance为空,进行实例化操作");
            synchronized (RedissonConfig.class) {
                //如果是空,就实例化对象
                if (instance == null) {

                    Map<String, String> redisInfoMap = getHostAndPwd();
                    if(EmptyUtil.isEmpty(redisInfoMap)){
                        return null;
                    }

                    String runMode = redisInfoMap.get("runMode");
                    //单机
                    if(Objects.equals(SINGLE,runMode)){

                        instance = initialSingleRedisson();
                        log.info("实例化单节点「RedissonClient」成功!!");
                        //集群
                    }else if (Objects.equals(CLUSTER,runMode)){
                        instance = initialClusterRedisson();
                        log.info("实例集群「RedissonClient」成功!!");
                    }

                }
            }

        }else {
            log.info("「RedissonClient」实例instance为:{},无需重新实例化「RedissonClient」",instance);
        }


        return instance;
    }

    public static RedissonClient initialSingleRedisson(){

        try {
            Map<String, String> redisInfoMap = getHostAndPwd();
            if(EmptyUtil.isEmpty(redisInfoMap)){
                return null;
            }

            String hostPort = redisInfoMap.get(HOST_PORT);
            String redisPwd = redisInfoMap.get("pwd");
            //单机
            Config config = new Config();
            config.useSingleServer().setAddress("redis://" + hostPort).setPassword(redisPwd);
            RedissonClient redisson = Redisson.create(config);
            log.info("「SingleRedisson」初始化成功!!");
            return redisson;

        }catch (Exception e){
            log.info("「SingleRedisson」初始化异常!!message:{}",e.getMessage());
            e.printStackTrace();
        }
        return null;
    }


    public static RedissonClient initialClusterRedisson(){
        //集群
        try {
            Map<String, String> redisInfoMap = getHostAndPwd();
            if(EmptyUtil.isEmpty(redisInfoMap)){
                return null;
            }

            String hostPort = redisInfoMap.get(HOST_PORT);
            String redisPwd = redisInfoMap.get("pwd");
            Config config = new Config();
            String[] hostPostArr = hostPort.split(",");
            for (String str : hostPostArr) {
                config.useClusterServers()
                        //设置扫描间隔时间
                        .setScanInterval(2000)
                        .setPassword(redisPwd)
                        .addNodeAddress("redis://" + str);
            }
            RedissonClient redisson = Redisson.create(config);
            return redisson;

        }catch (Exception e) {
            log.info("「ClusterRedisson」初始化异常!!message:{}",e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

  
    public static Map<String,String> getHostAndPwd(){
        try {
            //加载连接池配置文件
            Properties props = PropsUtil.loadProps(REDIS_CONFIG_FILE);
            String runMode = props.getProperty("redis.runMode");
            String hostPort = props.getProperty("redis.hostports");
            String redisPwd = props.getProperty("redis.password");
            Map<String, String> map = new HashMap<>();
            map.put("runMode",runMode);
            map.put(HOST_PORT,hostPort);
            map.put("pwd",redisPwd);

            log.info("解析redis配置文件获取到的连接信息:{}",map);
            if(EmptyUtil.isEmpty(map)){
                log.warn("未解析到redis的配置信息!!");
                return Maps.newHashMap();
            }

            return map;
        }catch (Exception e) {
            log.info("redisson初始化获取结点和密码失败");
            e.printStackTrace();
        }
        return Maps.newHashMap();
    }


}

对比以前的代码只需要实例化的时候改成单例模式就行了。

四、总结

出现类似问题的原因可能一般开发者潜移默化的依赖spring架构的便利性,以至于一些特殊场景的实现不知觉中就按照以前的编码风格去开发,前期简单测试不会发现问题所在,系统压力上来了以后才能暴露出来。程序猿:我不管,反正是测试的锅,谁让他们压测做的不到位呢!!测试:我尼玛。。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/157146.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用docker训练yolov5

使用docker训练yolov5 配置docker&#xff0c;配置的好处是docker中的环境或者说容器坏了不影响主机&#xff0c;并且可以减少配置环境的时间和精力 sudo apt update sudo apt install apt-transport-https ca-certificates curl gnupg-agent software-properties-common # c…

Docker 部署SQL Server 2017

Docker 部署SQL Server 2017 Docker部署 registry Docker搭建 svn Docker部署 Harbor Docker 部署SQL Server 2017 Docker 安装 MS SqlServer Docker部署 Oracle12c 文章目录Docker 部署SQL Server 2017一、部署步骤1.下载镜像2.创建容器并运行二、参考文档一、部署步骤 1.下…

Unity 之 资源加载 -- 可寻址系统概念介绍 -- 入门(一)

可寻址系统面板概念 -- 入门&#xff08;一&#xff09;一&#xff0c;可寻址系统概念介绍1.1 官方话术1.2 几个概念二&#xff0c;可寻址系统目录介绍2.1 导入工程2.2 目录介绍概述&#xff1a;本片文章带大家了解可寻址系统的相关概念&#xff0c;为大家介绍可寻址系统导入方…

生成数据分析报告pandas_profiling.ProfileReport

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 生成数据分析报告 pandas_profiling.ProfileReport 选择题 对于以下python代码表述错误的一项是? import pandas as pd import pandas_profiling as pp dfpd.DataFrame({ a:[23,18,21], b:[…

excel数据核对技巧:如何用函数公式标识输入正误

我们平时人工录入较长的文本数据时&#xff0c;稍不注意就容易出错。为了避免出错&#xff0c;通常我们会提前对单元格设置数据验证。有些时候&#xff0c;我们还会考虑列与列之间的关系&#xff0c;根据列关系自动判定数据的对错。比如下表,款号、货号、色号、条码的信息均存在…

【MySQL进阶教程】InnoDB引擎

前言 本文为 【MySQL进阶教程】InnoDB引擎 相关知识&#xff0c;下边将对InnoDB引擎介绍&#xff0c;InnoDB引擎架构&#xff0c;事务原理&#xff0c;MVCC等进行详尽介绍~ &#x1f4cc;博主主页&#xff1a;小新要变强 的主页 &#x1f449;Java全栈学习路线可参考&#xff…

获取每年的周数据 第几周 开始日及结束日 思路

public static void main(String[] args) {int year 2023;SimpleDateFormat simpleDateFormat new SimpleDateFormat("yyyy-MM-dd");while (true) {int weekValue 1;Calendar calendar new GregorianCalendar();//***踩坑 // calendar.setFirstDayOfW…

冒泡排序终极版(模拟qsort)

目录 普通版冒泡排序 qosrt函数 终极版冒泡排序 终极版冒泡排序整体测试代码 普通版冒泡排序 冒泡排序想必大家都很了解了吧&#xff0c;冒泡排序的算法思想就是两两比大小&#xff0c;一轮一轮比&#xff0c;每比完一轮排出一个数字的顺序&#xff0c;那就让我们先来看一…

软件测试/测试开发丨从 0 开始学 Python 自动化测试开发(二):环境搭建

本文是「从 0 开始学 Python 自动化测试开发」专题系列文章第二篇 —— 环境搭建篇&#xff0c;适合零基础入门的同学。没有阅读过上一篇的同学&#xff0c;请戳蓝色字体阅读。作者方程老师&#xff0c;是前某跨国通信公司高级测试经理&#xff0c;目前为某互联网名企资深测试技…

【算法基础】1.4 高精度(模拟大数运算:整数加减乘除)

文章目录高精度加法题目描述解法高精度减法题目描述解法讲解高精度乘法题目描述解法讲解高精度除法题目描述解法讲解本文主要讲解高精度计算&#xff0c;包括加法、减法、乘法和除法。 对于Python选手&#xff0c;python自带高精度计算&#xff1b;Java也有BigInteger类。但是对…

javaEE 初阶 — 多线程— JUC(java.util.concurrent) 的常见类

文章目录1. Callable 接口1.1 Callable 的用法2. ReentrantLock2.1 ReentrantLock 的缺陷2.1 ReentrantLock 的优势3. 原子类4. 信号量 Semaphore5. CountDownLatch6. 相关面试题1. Callable 接口 类似于 Runnable 一样。 Runnable 用来描述一个任务&#xff0c;描述的任务没有…

我们一直在说数字化转型,什么才是数字化转型?

我们一直在说数字化转型&#xff0c;什么才是数字化转型&#xff1f;深度长文&#xff0c;4000字&#xff0c;融合了很多国内外专业期刊观点&#xff0c;一文讲清到底什么是企业数字化转型&#xff0c;心急的小伙伴可以先看目录&#xff1a; 关于定义——到底什么是“数字化转…

24 届秋招 | 高质量学习交流环境

大家好&#xff0c;我和一些计算机方向、背景非常优秀的、来自清华、新国立等知名大学的几位同学以及工作多年的高级研发工程师一起运营了一个知识星球。 星球里有大量国内top985、海外名校的同学在一起&#xff0c;目的是为了打造一个非常优质量的社群。 如果你也曾苦于在各…

PySimpleGUI图形化界面实现Office文件格式转换

PySimpleGUI图形化界面实现Office文件格式转换Python实现三种文件两个版本的格式转换1、doc与docx格式互相转换2、xls与xlsx格式互相转换3、ppt与pptx格式互相转换PythonPySimpleGUI实现综合版本Python实现三种文件两个版本的格式转换 1、doc与docx格式互相转换 这里主要运用…

excel求和技巧:如何忽略错误值进行求和

按照对应的订单号引用已有的收货金额&#xff0c;这种问题相信很多朋友都会处理&#xff0c;用VLOOKUP函数就能搞定。我们今天要讨论的是如何对含有错误值的数据进行求和。如果直接求和&#xff0c;得到的结果也是一个错误值&#xff0c;如下图&#xff1a;对于这种要对含有错误…

Linux驱动开发基础__ Linux中断系统中的重要数据结构

目录 1 整体概述 2 irq_desc 数组 3 irqaction 结构体 4 irq_data 结构体 5 irq_domain 结构体 6 irq_chip 结构体 1 整体概述 该文章内容&#xff0c;可以从 request_irq(include/linux/interrupt.h)函数一路分析得到。 能弄清楚下面这个图&#xff0c;对 Linux 中…

Domino Nomad Web 1.0.6!

大家好&#xff0c;才是真的好。 虽然Domino Notes 9.0.x版本早前宣布从本月开始停止市场商业推广&#xff0c;并逐步停止技术支持服。但没让人意外的是&#xff0c;12月5号&#xff0c;HCL更新了一版Domino Notes 9.0.1版本的补丁程序FP10IF10&#xff1a; 没有任何额外的说…

车规级CAN FD收发器SIT1044Q,能替代TJA1044吗?

国际知名品牌NXP推出的TJA1042Q、TJA1043Q、TJA1044Q、TJA1051Q等CAN FD收发器芯片&#xff0c;相信很多电子工程师并不陌生。这类芯片应用中&#xff0c;非常成熟稳定&#xff0c;深受汽车电子工程师的认可、支持和青睐。然而&#xff0c;在实际应用中&#xff0c;很多客户由于…

新建文本文档

Spring Boot 加载外部配置文件 Spring Boot 允许你从外部加载配置&#xff0c;这样的话&#xff0c;就可以在不同的环境中使用相同的代码。支持的外部配置源包括&#xff1a;Java属性文件、YAML文件、环境变量、命令行参数。 用Value注解可以将属性值直接注入到beans中。命令行…

【win11环境编译安装deformable Detr的MultiScaleDeformableAttention模块】

Microsoft Visual C 14.0 is required.1.Compiling CUDA operators2.安装Build Tools for Visual Studio3.安装合适的cuda4.编译1.Compiling CUDA operators cd ./models/ops sh ./make.sh # unit test (should see all checking is True) python test.pyNote: win11 or win10…