一次框架升级之后,很多组件运行一段时间就会出现无法访问的现象,http请求一直pending
出现问题后立刻查看日志,出现大量异常
Socket accept failed
java.io.IOException: Too many open files
经百度是进程打开的句柄数超出限制,这导致后续的连接无法创建,所以一直pending。
继续百度,用命令 lsof|awk '{print $2}'|sort|uniq -c|sort -nr|more 给所有进程按句柄数排序,注意这个命令统计的是所有的句柄(包括正常的和异常的连接,加了grep CLOSE_WAIT也不行,请大神指教),一看最高的有几十万!!!
用 lsof -p | wc -l 详细看看每个组件的句柄数,发现大量CLOSE_WAIT连接,对端是随机的端口。
上述命令加上grep CLOSE_WAIT再统计,终于正常了一点,但是最高的也有大几千- -!
回顾了一下tcp四次握手的知识,CLOSE_WAIT是服务端收到FIN报文并ACK之后没有回复FIN报文给客户端,导致服务端到客户端的单向连接一直挂起。
花了一下午重启环境,抓包分析,然而并没有什么卵用,只是证明了确实没有回复FIN报文而已;追踪有问题的请求也没有线索,因为发起端只会收到异常,而接收端压根收不到请求。。。
cat /proc/pid/status 一看,thread有600,不正常!
经过同事的提醒,这种大量阻塞的情况,看线程状态最直观嘛(居然没想到)!
jstack走一波,发现大量死锁和WAITING状态的线程,都是自定义的数据源状态监控类(继承自spring-boot-actuator的AbstractHealthIndicator)获取数据库连接时产生的。
在配置类中注册此HealthIndicator的地方打断点,发现注入的数据源的连接属性全为空,而上一个版本中dataSource方法没有加上@Primary注解时是可以正常注入的
@Bean
@Primary
public DataSource dataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
public DataSourceHealthIndicator dataSourceHealthIndicator(){
return new DataSourceHealthIndicator(dataSource(),null);
}
往下分析发现是用这个dataSource获取连接时一直阻塞,也不报错,不想深究Druid的问题。
inter-bean references(内部级联)是指同一个@Configuration注解的配置类内部,在一个@Bean注解的方法dataSourceHealthIndicator中调用另一个@Bean注解的方法dataSource时,会直接从容器中返回dataSource的实例,而不是重新创建。参见注释:
* <h3>{@code @Bean} Methods in {@code @Configuration} Classes</h3>
*
* <p>Typically, {@code @Bean} methods are declared within {@code @Configuration}
* classes. In this case, bean methods may reference other {@code @Bean} methods in the
* same class by calling them <i>directly</i>. This ensures that references between beans
* are strongly typed and navigable. Such so-called <em>'inter-bean references'</em> are
* guaranteed to respect scoping and AOP semantics, just like {@code getBean()} lookups
* would. These are the semantics known from the original 'Spring JavaConfig' project
* which require CGLIB subclassing of each such configuration class at runtime. As a
* consequence, {@code @Configuration} classes and their factory methods must not be
* marked as final or private in this mode. For example:
而在dataSource上使用了@Primary注解之后,该配置类虽然同样会在ConfigurationClassPostProcessor.enhanceConfigurationClasses方法中创建增强的代理子类,但实际调用dataSource方法时,却没有使用代理类。头晕,请大神指教先。
使用@Qualifier注入dataSource
spring容器启动后,调用配置类专用处理器ConfigurationClassPostProcessor,在其中遍历配置类,生成增加代理子类,并设置成BeanDefinition的beanClass
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
// 遍历beanDefinition
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
// 是否@Configuration配置类
if (!(beanDef instanceof AbstractBeanDefinition)) {
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
}
else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
"' since its singleton instance has been created too early. The typical cause " +
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
"return type: Consider declaring such methods as 'static'.");
}
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
return;
}
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
// 遍历配置类
AbstractBeanDefinition beanDef = entry.getValue();
// If a @Configuration class gets proxied, always proxy the target class
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
try {
// Set enhanced subclass of the user-specified bean class
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
// 生成增强代理子类
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
// 设置成beanClass
beanDef.setBeanClass(enhancedClass);
}
}
catch (Throwable ex) {
throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
}
}
} 生成的增强代理子类实际是ConfigurationClassEnhancer,注册有两个过滤器,其中BeanMethodInterceptor会在调用带有@Bean注解的方法时切入(具体在哪配置的切入没找到,求指教ORZ),在intercept方法中判断当前执行的方法和切面方法是不是同一个,不是的话就去容器中拿切面方法对应的bean,没有就创建
@Override
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// Determine whether this bean is a scoped-proxy
Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
// To handle the case of an inter-bean method reference, we must explicitly check the
// container for already cached instances.
// First, check to see if the requested bean is a FactoryBean. If so, create a subclass
// proxy that intercepts calls to getObject() and returns any cached bean instance.
// This ensures that the semantics of calling a FactoryBean from within @Bean methods
// is the same as that of referring to a FactoryBean within XML. See SPR-6602.
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
factoryContainsBean(beanFactory, beanName)) {
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
if (factoryBean instanceof ScopedProxyFactoryBean) {
// Scoped proxy factory beans are a special case and should not be further proxied
}
else {
// It is a candidate FactoryBean - go ahead with enhancement
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
}
}
// 判断当前执行的方法是不是切面方法
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// The factory is calling the bean method in order to instantiate and register the bean
// (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
// create the bean instance.
if (logger.isWarnEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
logger.warn(String.format("@Bean method %s.%s is non-static and returns an object " +
"assignable to Spring's BeanFactoryPostProcessor interface. This will " +
"result in a failure to process annotations such as @Autowired, " +
"@Resource and @PostConstruct within the method's declaring " +
"@Configuration class. Add the 'static' modifier to this method to avoid " +
"these container lifecycle issues; see @Bean javadoc for complete details.",
beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
}
// 是的话创建当前方法的bean
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
// 不是的话从容器中获取切面方法的bean
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
}