记一次用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")'
相关推荐
Tttian62211 分钟前
基于Pycharm与数据库的新闻管理系统(2)Redis
数据库·redis·pycharm
言之。1 小时前
redis延迟队列
redis
做梦敲代码1 小时前
达梦数据库-读写分离集群部署
数据库·达梦数据库
小蜗牛慢慢爬行2 小时前
如何在 Spring Boot 微服务中设置和管理多个数据库
java·数据库·spring boot·后端·微服务·架构·hibernate
hanbarger2 小时前
nosql,Redis,minio,elasticsearch
数据库·redis·nosql
微服务 spring cloud2 小时前
配置PostgreSQL用于集成测试的步骤
数据库·postgresql·集成测试
先睡2 小时前
MySQL的架构设计和设计模式
数据库·mysql·设计模式
弗罗里达老大爷2 小时前
Redis
数据库·redis·缓存
别这么骄傲3 小时前
lookup join 使用缓存参数和不使用缓存参数的执行前后对比
缓存