记一次用Arthas排查Redis连接数增加问题(附:redis连接池优化)

手打不易,如果转摘,请注明出处!

注明原文: https://zhangxiaofan.blog.csdn.net/article/details/136493572

前言

有一次生产环境发包后,发现redis连接数变多了,由于改的代码比较多,不确定是哪里出了问题。因此用Arthas来进行了一次排查。

项目比较多,有用到 jedislettuceredisson3种客户端。

项目用到的 SpringContextHolder 代码如下:

java 复制代码
package com.aop.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component("springContextHolder")
public class SpringContextHolder implements ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);

    private static ApplicationContext applicationContext;

    /**
     * 实现ApplicationContextAware接口, 注入Context到静态变量中.
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        logger.info("ServletContextUtil is init");
        SpringContextHolder.applicationContext = applicationContext;
    }

    /**
     * 获取静态变量中的ApplicationContext.
     */
    public static ApplicationContext getApplicationContext() {
        assertContextInjected();
        return applicationContext;
    }


    /**
     * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) {
        assertContextInjected();
        return (T) applicationContext.getBean(name);
    }

    /**
     * 从静态变量applicationContext中得到Bean, 自动转型为所赋值对象的类型.
     */
    public static <T> T getBean(Class<T> requiredType) {
        assertContextInjected();
        return applicationContext.getBean(requiredType);
    }


    public static <T> Map<String, T> getBeansOfType(Class<T> requiredType) {
        assertContextInjected();
        return applicationContext.getBeansOfType(requiredType);
    }


    /**
     * 检查ApplicationContext不为空.
     */SpringContextHolder
    private static void assertContextInjected() {
        if (applicationContext == null) {
            throw new IllegalStateException("Application Context Have Not Injection");
        }
    }

    /**
     * 清除SpringContextHolder中的ApplicationContext为Null.
     */
    public static void clearHolder() {
        applicationContext = null;
    }

    /**
     * 打印对应Class的Bean是否注入到容器
     */
    public static <T> void printBean(Class<T> requiredType) {
        assertContextInjected();
        T instance = null;
        try {
            instance = SpringContextHolder.getBean(requiredType);
        } catch (Exception ex) {
            logger.warn("bean:" + requiredType.getSimpleName() + " is null");
        }
        logger.info(requiredType.getSimpleName() + ":{}", instance != null ? instance.toString() : "null");
    }
}

第一步:看项目中启动类使用到了哪些客户端

启动arthas,怎么启动,网上有资料,可以搜一下。

使用 sc 命令

java 复制代码
sc -d com.aop.util.SpringContextHolder

可以看到 classLoaderHash 值 : 33d512c1

java 复制代码
[arthas@1]$ sc -d com.aop.util.SpringContextHolde
 class-info        com.aop.util.SpringContextHolde                                                                                                                  
 code-source       /opt/cloud/tomcat/appstore%23file%23api/WEB-INF/classes/                                                                                                                    
 name              com.aop.util.SpringContextHolde                                                                                                                  
 isInterface       false                                                                                                                                                                       
 isAnnotation      false                                                                                                                                                                       
 isEnum            false                                                                                                                                                                       
 isAnonymousClass  false                                                                                                                                                                       
 isArray           false                                                                                                                                                                       
 isLocalClass      false                                                                                                                                                                       
 isMemberClass     false                                                                                                                                                                       
 isPrimitive       false                                                                                                                                                                       
 isSynthetic       false                                                                                                                                                                       
 simple-name       SpringContextHolde                                                                                                                                                    
 modifier          public                                                                                                                                                                      
 classLoaderHash   33d512c1   

执行 ognl 的 getContext() 测试一下,有输出则成功

java 复制代码
ognl -c 33d512c1 '#conetex=@com.aop.util.SpringContextHolde@getContext()'

执行getBeanDefinitionNames()查看有哪些bean

java 复制代码
ognl -c 33d512c1 '@com.aop.util.SpringContextHolde@getContext().getBeanDefinitionNames()'

可以看到输出的bean中只有

java 复制代码
...
org.springframework.boot.autoconfigure.data.redis.JedisConnectionConfiguration
...

我们确定,我们项目是引入了 **lettuce ,**为什么没有有看到相关的Bean,"org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration" ?

通过查看文档:在 Spring Boot 中整合、使用 Redis - spring 中文网

我们发现

注意如果用到

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

默认是使用 lettuce,如果要用 jedis 或者 redisson 务必,排除

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

我们项目有人提交代码的时候,把lettuce排除了,导致我们用到了 jedis 客户端。

第二步:查看客户端的连接池、连接串配置

继续看 JedisConnectionConfiguration的连接池配置

java 复制代码
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde @getContext(),#context.getBean("org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration")'

输出如下:

java 复制代码
@JedisConnectionConfiguration[
    COMMONS_POOL2_AVAILABLE=@Boolean[true],
    properties=@RedisProperties[org.springframework.boot.autoconfigure.data.redis.RedisProperties@24d30f55],
    standaloneConfiguration=null,
    sentinelConfiguration=null,
    clusterConfiguration=null,
]

继续看,我们用下面命令,查看配置,我们发现

java 复制代码
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("org.springframework.boot.autoconfigure.data.redis.JedisConnectionConfiguration").getProperties().getJedis().getPool()'

输出:

java 复制代码
@Pool[
    enabled=null,
    maxIdle=@Integer[100],
    minIdle=@Integer[100],
    maxActive=@Integer[200],
    maxWait=@Duration[PT0.002S],
    timeBetweenEvictionRuns=null,
]

问题就出在这里,我们 minIdle 设置太大,redis 又是集群部署了 6个 节点,因此连接数增加了 600个。之前用的 lettuce 客户端,minIdle=0,maxIdle=8,远远小于 jedis 的配置。

第三步:解决

修改jedis minIdle 配置

Arthas线程池调优(重点)

第一步:查看当前连接池配置

查看所有bean

java 复制代码
ognl -c 33d512c1 '@com.aop.util.SpringContextHolde@getContext()'
ognl -c 33d512c1 '#conetex=@com.aop.util.SpringContextHolde@getContext()'
ognl -c 33d512c1 '@com.aop.util.SpringContextHolde@getContext().getBeanDefinitionNames()'

Redisson连接池配置

先查看配置的模式 ,例如下面是看集群的配置。我们项目中的RedissonClient

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() {    
        ...
    }
java 复制代码
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("redisson").getConfig()'

上面看出redisson是集群配置

java 复制代码
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("redisson").getConfig().getClusterServersConfig()'

Jedis连接池配置(spring-data-redis,redisTemplate,通过'redisConnectionFactory')

java 复制代码
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("org.springframework.boot.autoconfigure.data.redis.JedisConnectionConfiguration").getProperties().getJedis().getPool()'

Lettuce连接池配置(spring-data-redis,redisTemplate,通过'redisConnectionFactory')

java 复制代码
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration").getProperties().getLettuce().getPool()'

JetCache连接池配置(默认Jedis客户端)

我们可以通过配置,搜索到配置Jetcache的源码:

com.alicp.jetcache.autoconfigure.RedisAutoConfiguration

com.alicp.jetcache.autoconfigure.RedisAutoConfiguration.RedisAutoInit

java 复制代码
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("autoConfigureBeans").customContainer
java 复制代码
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolder@getContext(),#context.getBean("autoConfigureBeans").customContainer.get("jedisPool.remote.default").provider.cache.poolConfig'

第二步:查看当前连接池使用状态

Redisson 连接池使用情况

java 复制代码
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("redisson").getConnectionManager()'
java 复制代码
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("redisson").getConnectionManager().getEntrySet()'
java 复制代码
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("redisson").getConnectionManager().getEntrySet().toArray()[x].masterEntry.allConnections'
java 复制代码
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("redisson").getConnectionManager().getEntrySet().toArray()[x].masterEntry.allConnections.size'

Jedis 连接池使用情况(spring-data-redis,redisTemplate,通过'redisConnectionFactory')

ognl -c 4e0ae11f '#context=@com.aop.util.SpringContextHolder@getContext(),#context.getBean("redisConnectionFactory").topologyProvider.cluster.provider.cache'

ognl -c 4e0ae11f '#context=@com.aop.util.SpringContextHolder@getContext(),#context.getBean("redisConnectionFactory").topologyProvider.cluster.provider.cache.nodes.get("x.x.x.x:6379")'

Lettuce 连接池使用情况(spring-data-redis,redisTemplate,通过'redisConnectionFactory')

这个是过'redisConnectionFactory'

ognl -c 3d9c13b5 '#context=@com.aop.util.SpringContextHolder@getContext(),#context.getBean("redisConnectionFactory").connectionProvider.delegate.pools'

ognl -c 3d9c13b5 '#context=@com.aop.util.SpringContextHolder@getContext(),#context.getBean("redisConnectionFactory").connectionProvider.delegate.pools.values().toArray()[0]'

不知道为什么,lettuce设置了minIdle=20,理论上来说allObjects应该大于等于20才对。如果有同学知道原因,还望指正!

Jetcache 连接池使用情况(默认redis客户端)

java 复制代码
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("autoConfigureBeans").customContainer.get("jedisPool.remote.default").provider.cache.nodes'
java 复制代码
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("autoConfigureBeans").customContainer.get("jedisPool.remote.default").provider.cache.nodes.get("x.x.x.x:6379")'
相关推荐
阿华的代码王国36 分钟前
MySQL ------- 索引(B树B+树)
数据库·mysql
码爸1 小时前
flink 批量压缩redis集群 sink
大数据·redis·flink
Hello.Reader1 小时前
StarRocks实时分析数据库的基础与应用
大数据·数据库
执键行天涯1 小时前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
yanglamei19621 小时前
基于GIKT深度知识追踪模型的习题推荐系统源代码+数据库+使用说明,后端采用flask,前端采用vue
前端·数据库·flask
工作中的程序员2 小时前
ES 索引或索引模板
大数据·数据库·elasticsearch
严格格2 小时前
三范式,面试重点
数据库·面试·职场和发展
微刻时光2 小时前
Redis集群知识及实战
数据库·redis·笔记·学习·程序人生·缓存
单字叶2 小时前
MySQL数据库
数据库·mysql
mqiqe2 小时前
PostgreSQL 基础操作
数据库·postgresql·oracle