有一次生产环境发包后,发现redis连接数变多了,由于改的代码比较多,不确定是哪里出了问题。因此用Arthas来进行了一次排查。
项目比较多,有用到 jedis、lettuce、redisson 3种客户端。
项目用到的 SpringContextHolder 代码如下:
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 命令
sc -d com.aop.util.SpringContextHolder
可以看到 classLoaderHash 值 : 33d512c1
[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() 测试一下,有输出则成功
ognl -c 33d512c1 '#conetex=@com.aop.util.SpringContextHolde@getContext()'
执行getBeanDefinitionNames()查看有哪些bean
ognl -c 33d512c1 '@com.aop.util.SpringContextHolde@getContext().getBeanDefinitionNames()'
可以看到输出的bean中只有
...
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 的连接池配置
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde @getContext(),#context.getBean("org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration")'
输出如下:
@JedisConnectionConfiguration[
COMMONS_POOL2_AVAILABLE=@Boolean[true],
properties=@RedisProperties[org.springframework.boot.autoconfigure.data.redis.RedisProperties@24d30f55],
standaloneConfiguration=null,
sentinelConfiguration=null,
clusterConfiguration=null,
]
继续看,我们用下面命令,查看配置,我们发现
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("org.springframework.boot.autoconfigure.data.redis.JedisConnectionConfiguration").getProperties().getJedis().getPool()'
输出:
@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
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() {
...
}
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("redisson").getConfig()'
上面看出redisson是集群配置
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("redisson").getConfig().getClusterServersConfig()'
Jedis连接池配置(spring-data-redis,redisTemplate,通过'redisConnectionFactory')
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')
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
ognl -c 33d512c1 '#context=@com.aop.util.SpringContextHolde@getContext(),#context.getBean("autoConfigureBeans").customContainer
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolder@getContext(),#context.getBean("autoConfigureBeans").customContainer.get("jedisPool.remote.default").provider.cache.poolConfig'
第二步:查看当前连接池使用状态
Redisson 连接池使用情况
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("redisson").getConnectionManager()'
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("redisson").getConnectionManager().getEntrySet()'
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("redisson").getConnectionManager().getEntrySet().toArray()[x].masterEntry.allConnections'
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客户端)
ognl -c 10163d6 '#context=@com.aop.util.SpringContextHolderr@getContext(),#context.getBean("autoConfigureBeans").customContainer.get("jedisPool.remote.default").provider.cache.nodes'
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")'