性能文章>记一次 SpringBoot 项目启动失败排查 和 DubboReference 源码分析>

记一次 SpringBoot 项目启动失败排查 和 DubboReference 源码分析转载

1月前
178902

问题现象

在我们项目中有一个公司内部的二方包,里面有一个类:MvcInterceptorAutoConfiguration ,里面定一个了一个 Bean accessContextResolver 。生成这个 Bean 需要自动注入另一个 Bean :accessContextService。代码如下:

public class MvcInterceptorAutoConfiguration implements WebMvcConfigurer, ApplicationContextAware {

@Bean
public AccessContextResolver accessContextResolver(@Autowired AccessContextService accessContextService, @Autowired WebAuthConfig webAuthConfig) {
return new DefaultAccessContextResolver(webAuthConfig, accessContextService);
}
}

在我们项目中又有一个类:ProxyCenter ,它里面用 @DubboReference 定义了 accessContextService 。代码如下

@Component
public class ProxyCenter {

@DubboReference(timeout = 10000, check = false, version = "1.0.0")
private AccessContextService accessContextService;

...
}

但是在项目启动过程中报如下的错

***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of method accessContextResolver in cn.xxx.xxx.xxx.xxx.config.MvcInterceptorAutoConfiguration required a bean of type 'cn.xxx.xxx.xxx.xxx.service.AccessContextService' that could not be found.


Action:

Consider defining a bean of type 'cn.xxx.xxx.xxx.xxx.service.AccessContextService' in your configuration.

这个错误可能大家都很熟悉了,意思是 Spring 在创建 accessContextResolver 这个 Bean 的时候需要自动注入 accessContextService 这个 Bean ,但是 Spring 容器找不到这个 Bean ,所以启动失败。

问题分析

Dubbo版本:2.7.0

分析思路

  • 对于这个问题本质是 @Autowired 不能注入 @DubboReference 声明过的 Bean ,最主要需要弄清楚 @DubboReference 和 @Autowired 所做的事情,并且分别都是在什么时候做的。

  • 如果只使用 @Autowired 的时候,并不会出现以上这种情况,所以我们定位问题的方向优先去看 @DubboReference 的实现逻辑。

@DubboReference实现逻辑分析

背景知识

先讲一个背景知识:我们知道 Spring 创建一个 Bean 大致需要经过实例化对象、属性填充、初始化对象这几步,其中属性填充是在 populateBean 这个方法中实现的(代码如下),这里有一段逻辑是,获取 Bean 工厂中所有的 BeanPostProcessor ,如果是 InstantiationAwareBeanPostProcessor 类型,那么就调用 postProcessPropertyValues 方法。

注意:InstantiationAwareBeanPostProcessor 是一个抽象类,它本身没有提供 postProcessPropertyValues 实现,所有的实现都是在子类中的。

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {

...

Iterator var5 = this.getBeanPostProcessors().iterator();

BeanPostProcessor bp = (BeanPostProcessor)var9.next();
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor)bp;
pvs = ibp.postProcessPropertyValues((PropertyValues)pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvs == null) {
return;
}
}

...
}

以下是 InstantiationAwareBeanPostProcessorAdapter 实现类, 这里只列举了和我们这次问题相关的子类

InstantiationAwareBeanPostProcessorAdapter

AutowiredAnnotationBeanPostProcessor ( Spring 提供属性/方法注入实现)

|

AbstractAnnotationBeanPostProcessor 【com.alibaba.spring......】

|

ReferenceAnnotationBeanPostProcessor 【org.apache.dubbo......】( Dubbo提供的 @DubboReference, @Reference 实现)

从上面的源码和类的继承关系我们可以得出结论:spring进行属性填充的时候,会调用 ReferenceAnnotationBeanPostProcessor 这个类的 postProcessPropertyValues 方法。而 ReferenceAnnotationBeanPostProcessor 这个类就是Dubbo提供的 Bean 的后置处理器, @DubboReference, @Reference 就是在这个方法里面实现的。

源码分析

在了解了上面的背景知识后,我们就开始进入 @DubboReference 的源码分析。下面列出来的是 ReferenceAnnotationBeanPostProcessor 对于 postProcessPropertyValues 的实现。

我们要注意一点,那就是此时正在创建的 Bean 是 proxyCenter,至于为什么是 proxyCenter 这个 Bean ,这个很简单,因为在本案例中 accessContextService 是 ProxyCenter 这个类的属性,所以在创建 proxyCenter 这个 Bean 的时候发生对 accessContextService 这个属性的填充动作。

 public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {

//找对象
InjectionMetadata metadata = this.findInjectionMetadata(beanName, bean.getClass(), pvs);

try {
//执行注入
metadata.inject(bean, beanName, pvs);
return pvs;
} catch (BeanCreationException var7) {
throw var7;
} catch (Throwable var8) {
throw new BeanCreationException(beanName, "Injection of @" + this.getAnnotationType().getSimpleName() + " dependencies is failed", var8);
}
}

postProcessPropertyValues 方法主要做了两件事:

1、找到 @DubboReference 、@Reference 修饰属性,并且将元数据信息封装在 InjectionMetadata 中。

2、执行注入。这里 inject 方法调用的是 AbstractAnnotationBeanPostProcessor 中的inject 方法,也就是执行父类的 inject 方法。

分析思路:既然是在自动注入的时候中没有找到这个对象,也就是说 Spring 容器中没有这个对象,那么有可能是 Dubbo 生成了代理对象,但是没有放到 Spring 容器中,所以自动注入的时候没有找到。所以,我们可以先看 inject 这个方法。(后面也证实了 findInjectionMetadata 并没有什么问题,所以这里就不分析。)

 protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {

Class<?> injectedType = this.resolveInjectedType(bean, this.field);

//生成代理对象
Object injectedObject = AbstractAnnotationBeanPostProcessor.this.getInjectedObject(this.attributes, bean, beanName, injectedType, this);

//反射,给属性设置值
// bean:proxyCenter
// this.field:accessContextService
ReflectionUtils.makeAccessible(this.field);
this.field.set(bean, injectedObject);
}

inject 方法主要是生成代理对象,然后给当前对象的属性设置值。也就是这里会把生成的代理对象 accessContextResolver 设置到当前Bean 也就是 proxyCenter 这个 Bean 的属性上。

分析思路:但是我们的问题不是说这个 Bean 的属性是null,而是在 Spring 自动注入的时候没有拿到对象的值,但是 inject 方法没有涉及到把代理对象 accessContextResolver 放到 Spring 容器中这块代码,所以我们可以继续看往下看。( getInjectedObject 这个方法的核心逻辑是在 doGetInjectedBean ,只是加了缓存操作。所以这里没有列出来)

 protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {

...

ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType);

//判断当前这个服务是不是在本地使用@DubboService或者@Service定义出来的
boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes);

//如果是本地的service、并且还没有注册过,那么就会触发提前注册服务
prepareReferenceBean(referencedBeanName, referenceBean, localServiceBean);

//将服务信息 放到bean工厂里面,还没有涉及到获取真正的服务
//1、本地暴露出去的服务
//2、需要从注册中心读取的服务
registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType);

//拿到远端的服务
return referenceBean.get();
}

doGetInjectedBean 这个方法是 @DubboReference 实现的核心,这里每一步都写了注释。

分析思路:这里有个方法 registerReferenceBean ,顾名思义,应该是注册 ReferenceBean ,这里注册应该是把当前 Bean 注册到 Bean 工厂里面,那么我们需要的答案应该就在这个方法里面。( ReferenceBean 是一个对象,封装了 applicationContext、接口的代理对象:ref 等等,其中 ref 就是生成的代理对象,比如 @DubboReference AService aService ; 那么 ref 就是 aService 的代理对象。这个对象会被包装成一个 ReferenceBean, 所以可以粗暴的认为 ReferenceBean 就是一个 服务具体的引用者。)

private void registerReferenceBean(String referencedBeanName, ReferenceBean referenceBean,
AnnotationAttributes attributes,
boolean localServiceBean, Class<?> interfaceClass) {

ConfigurableListableBeanFactory beanFactory = getBeanFactory();

String beanName = getReferenceBeanName(attributes, interfaceClass);

//情况一:@Service 是本地的
if (localServiceBean) {

//如果是本地的话,服务的所有信息都在本地,
AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition(referencedBeanName);
RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) beanDefinition.getPropertyValues().get("ref");
// @Service 对应的bean名称
String serviceBeanName = runtimeBeanReference.getBeanName();

// 没有新创建一个bean 而是沿用@Service生成的bean,这样就避免bean重复
beanFactory.registerAlias(serviceBeanName, beanName);

} else {

//@Service是远端的
if (!beanFactory.containsBean(beanName)) {

//spring动态注册bean
beanFactory.registerSingleton(beanName, referenceBean);
}
}
}

registerReferenceBean 这个方法主要是把 ReferenceBean 注册到 Spring 中。至此,我们也找到了我们的答案,那就是: @DubboReference 会把生成的代理对象放到 Spring 容器中,而且触发的时机是在创建 @DubboReference 修饰属性对应的这个 Bean 创建的过程中。也就是说只要那个 Bean 没有被创建,那么 @DubboReference 修饰的属性是不会放到 Spring 容器中的。

上面的步骤用流程图来表示就是:

 

总结

上面就是 @DubboReference 在 Spring 启动过程中触发的时机,也就是说在 Spring 创建 Bean 的时候,在属性填充阶段,如果发现@DubboReference 修饰的属性,ReferenceAnnotationBeanPostProcessor 这个 Bean后置处理器会创建这个服务引用的代理对象,然后放到 Spring 容器中。

分析思路:所以文章开头的问题其实就可以理解为:Spring 在创建 proxyCenter 这个 Bean 的时候就会实例化 accessContextService 对象,然后放到 Spring 容器中,但是在使用 @Autowired 进行对 accessContextService 注入的时候,却没有找到这个 Bean 。这时候极有可能的原因:就是 Spring 先使用 @Autowired 进行对 accessContextService 注入,然后才会发生创建 proxyCenter 这个 Bean 。

我们知道 @Autowired 自动注入的时候,如果 Bean 不存在,那么就会触发创建 Bean 的过程,下面我们分析下 @Autowired 实现逻辑,为什么这里的对象是 null 。

@Autowired实现逻辑分析

说明:由于@Autowired实现逻辑比较复杂,下面列出的代码都是和本案例相关的代码,其他代码会做相应省略。

分析思路

  • @Autowired 这个注解可以修饰属性、方法、入参等,@Autowired 作用的对象不同处理的时机也不同,比如 @Autowired 修饰属性或者方法的时候,就是在属性填充的时候处理的,而本文案例中对于 @Autowired 处理是在实例化 Bean 的时候。

@Bean
public AccessContextResolver accessContextResolver(@Autowired AccessContextService accessContextService, @Autowired WebAuthConfig webAuthConfig) {
return new DefaultAccessContextResolver(webAuthConfig, accessContextService);
}

源码分析

在 Bean 的实例化过程中,有一个步骤是:createArgumentArray,这里有一种情况是创建自动注入参数:ConstructorResolver#resolveAutowiredArgument ,这个就是本案例分析的入口,由于下面很多逻辑和本案例无关,这部分代码就不列举出来了,大家可以自行查看。我们这边从 doResolveDependency 这个方法开始看起。

注意一点:beanName 是指当前要创建的 Bean 名称,而不是自动注入的 Bean 名称。本案例中指的是 accessContextResolver 而不是 accessContextService 。可以看上面的代码。

@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

//31处理普通bean key:自动注入的bean名称 ;value:class对象 或者是具体的bean
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
//如果根据bean名称没有获取到bean,@Autowire(required=true) 这种情况的话,那么就报异常
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
//如果是@Autowire(required=false)那么直接返回null
return null;
}

String autowiredBeanName; //自动注入的bean的名字
Object instanceCandidate; //自动注入的对象

//如果根据bean名称,找到了不只一个对象
if (matchingBeans.size() > 1) {
// @Primary -> @Priority -> 方法名称或字段名称匹配
autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
if (autowiredBeanName == null) {
if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
return descriptor.resolveNotUnique(type, matchingBeans);
}
else {
return null;
}
}
instanceCandidate = matchingBeans.get(autowiredBeanName);
}
else {
// 根据type,只找到了一个bean信息,那么这个就是我们要的对象
Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
autowiredBeanName = entry.getKey();
instanceCandidate = entry.getValue();
}

if (autowiredBeanNames != null) {
autowiredBeanNames.add(autowiredBeanName);
}

//这里用来判断 返回的是已经创建好的bean 还是 只是class ,如果是class 那么需要执行创建bean的逻辑,获取到真的bean对象
//因为注入的时候需要的是个对象,class没有用
if (instanceCandidate instanceof Class) {
//这里其实是执行getBean()逻辑
instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
}
Object result = instanceCandidate;
if (result instanceof NullBean) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
result = null;
}
if (!ClassUtils.isAssignableValue(type, result)) {
throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
}
return result;
}
}

doResolveDependency 这个方法是依赖注入的核心方法,里面一共做了以下几件事情:

1、处理 @Value 修饰的参数

2、处理 MultipleBean,也就是 List、Map、Array、Set 修饰的对象。比如:@Autowire private List  aServiceList ; 这种情况。

3、处理普通注入

31、首先:根据类型,查找所有的bean名称,放到map中。findAutowireCandidates返回一个map ,map 的 key:Bean名称,value:有可能是已经创建的 Bean,有可能是bean还没有创建,返回的是class对象。

32、matchingBeans 的 size > 1 :也就是说同一个 type,找到多个 Bean。一种是同一个类生成多个对象,比如多数数据源,还有就是一个接口多个实现,在注入的时候只注入接口。这时候会根据优先级取第一个(@Primary -> @Priority )

33、matchingBeans 的 size =1 :这个就是我们需要的对象。

34、判断这个对象是否是class的实例,如果是,然后进行创建 Bean 过程

4、最后返回这个对象。

分析思路:步骤【31】这里会有个问题,如果根据类型找不到 Bean 信息,那么如果这个还是 @Autowire(require=true) 这种情况,那么就会执行 raiseNoMatchingBeanFound 这个方法会报一些异常。个人猜想,我们这边启动报错会不会就是这边根据类型 AccessContextService 没有找到对应的 Bean 信息,所以才会报错?我们接着往下看 findAutowireCandidates 这个实现逻辑。(虽然下面也有一些场景会报错,但是和这个案例情况并不符合)

protected Map<String, Object> findAutowireCandidates(
@Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {

//根据requiredType找到所有这个type的bean名称
String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this, requiredType, true, descriptor.isEager());

Map<String, Object> result = new LinkedHashMap<>(candidateNames.length);

//根据bean名称查找对应的bean或者是class对象,放到map里面,注意:这里如果这个bean还没有实例化,不会提前实例化
...

return result;
}

findAutowireCandidates 方法是根据类型,找到找所有的 Bean 名称和对象的过程。

分析思路:这里的两部都可能出现问题:

1、查找所有的 Bean 名称的时候 Bean 名称没有找到;

2、根据名称查找对应的 Bean 或者是 class 对象;

优先考虑第一步是不是根据类型查找 Bean 名称没有找到。因为我们刚刚分析 @DubboReference 的时候,有一段代码:是动态注册 Bean ,注册的过程中会把这个 Bean 名称放到 manualSingletonNames 对象中。但是这个放进去的时机是在创建 proxyCenter 的时候。

beanFactory.registerSingleton(beanName, referenceBean);


//registerSingleton 实现逻辑
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {

...

if (!this.beanDefinitionMap.containsKey(beanName)) {
this.manualSingletonNames.add(beanName);

}

下面我们着重看 Spring 是怎么找到所有的 Bean 名称的,这个主要逻辑是在 doGetBeanNamesForType 方法中。

// includeNonSingletons:是否包含非单利的
//allowEagerInit :处理factoryBean的
private String[] doGetBeanNamesForType(ResolvableType type, boolean includeNonSingletons, boolean allowEagerInit) {
List<String> result = new ArrayList<>();

// 循环bean工厂所有的bean的定义
for (String beanName : this.beanDefinitionNames) {

if (!isAlias(beanName)) {
try {

RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);

//bean是非抽象的
if (!mbd.isAbstract() &&
//
(allowEagerInit ||(mbd.hasBeanClass() || !mbd.isLazyInit() || isAllowEagerClassLoading()) &&
//factoryBean相关处理
!requiresEagerInitForType(mbd.getFactoryBeanName()))) {

//判断是不是factoryBean
boolean isFactoryBean = isFactoryBean(beanName, mbd);
BeanDefinitionHolder dbd = mbd.getDecoratedDefinition();

//条件一:非factoryBean、存在dbd并且是非懒加载 或者是单利池里面已经有这个bean了
//条件二:包含非单利的,或者是这个bean是单利的
//条件三:type和 当前bean的type类型一致
boolean matchFound =
(allowEagerInit || !isFactoryBean ||(dbd != null && !mbd.isLazyInit()) || containsSingleton(beanName)) &&
(includeNonSingletons ||(dbd != null ? mbd.isSingleton() : isSingleton(beanName))) &&
isTypeMatch(beanName, type);

//处理factoryBean
if (!matchFound && isFactoryBean) {
beanName = FACTORY_BEAN_PREFIX + beanName;
matchFound = (includeNonSingletons || mbd.isSingleton()) && isTypeMatch(beanName, type);
}

//匹配上了,就放到集合里面,后面返回
if (matchFound) {
result.add(beanName);
}
}
}
catch (CannotLoadBeanClassException ex) {
//异常处理
onSuppressedException(ex);
}
catch (BeanDefinitionStoreException ex) {
//异常处理
onSuppressedException(ex);
}
}
}


// 处理手动注册的bean registerSingleton(beanName,Object)
//spring 除了扫描一些注解例如@Service、@Compoment 还可以在代码中手动注册
for (String beanName : this.manualSingletonNames) {
....
}

return StringUtils.toStringArray(result);
}

doGetBeanNamesForType 根据 Bean 的类型查找所有符合 Bean 名称。注意:这里包含了普通 Bean 和 FactoryBean ,同时,这里匹配的 Bean 包括自动注册和手动注册的。我们可以看到,这里循环了两个对象:beanDefinitionNames 和 manualSingletonNames

beanDefinitionNames 这个对象里面的 Bean 信息是 Spring 在初始化的时候扫描了项目中的类似于 @Compoment、@Service 等注解生成的,我们的 AccessContextService 肯定不会在这个对象里面,因为 AccessContextService 并不是按照 Spring 定义的 Bean 规范定义的 Bean,manualSingletonNames 是registerSingleton 调用的时候放进去的。

总结

至此,问题的原因已经很清楚了:Spring 在启动过程中,先进行 @Autowired 处理,这时候主要注入 AccessContextService 这个类型的 Bean,但是他不是我们使用 @Service 、@Compoment 等 Spring提供的定义 Bean 的方式定义的 Bean,所以 Spring 容器中不会有 AccessContextService 任何 Bean 的定义信息,而这时候 proxyCenter 这个对象还没有实例化,没有发生属性填充, AccessContextService 这个类的代理对象就没有注入到 Spring 环境中,所以就无法获取 AccessContextService 类型对象,Spring 启动报错。

解决思路

1、本案例的核心问题是:Bean 的使用优先于 Bean 的创建,但这个 Bean 又不是按照 Spring 规范定义的 Bean,所以没有办法在自动注入找不到的时候自己创建。所以我们只要保证先创建 Bean ,后注入 Bean 就可以了。

基于这样的话,解决方法可以是:让 proxyCenter 这个 Bean 先于 accessContextResolver 实例化,因为在创建的时候会对属性进行填充,这时候就会触发 AccessContextService 这个远程服务的实例化,但是 Spring Bean 的创建是无序的,怎么让这两个 Bean 按照一定顺序创建呢?

Spring 中可以使用 @DependsOn 这个注解让某个 Bean 优先于另一个 Bean 被创建,但是在我们这个案例中,accessContextResolver 处于二方包中,加 @DependsOn 并不现实。所以我们可以定义一个 BeanFactoryPostProcessor ,然后手动修改 accessContextResolver 对应的 BeanDefinition,这样就解决问题啦。代码如下:

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("accessContextResolver");
beanDefinition.setDependsOn("proxyCenter");
}
}

个人思考

以上解决方式并不是很优雅,Dubbo使用 Bean 后置处理器实现 @DubboReference 这种实现方式存在缺陷,@DubboReference 并不是 Spring定义的 Bean,所以不会生成 BeanDefinition ,也就是不会主动 createBean ,只能在属性注入的时候触发,这就会导致本文这种问题。我觉得比较好的实现方式 应该是在 Spring 没有实例化任何 Bean 之前,把所有 @DubboReference 对应的对象都事先创建出来,然后在 Spring 创建 Bean 的时候,拿来即用,那么就不会出现以上的问题。

上面的问题Dubbo在后续版本(3.0.0)中已经解决了,所以我们之前的问题也可以使用升级 Dubbo 版本来解决。至于 Dubbo 后面是怎么解决这个问题的,这里不具体展开讲修改后的实现逻辑,大家有兴起可以自行翻看源码。

 

作者:政采云技术团队

文章来源:微信公众号

原文链接:https://mp.weixin.qq.com/s/E6mANd7Glr--06lw96hieA

分类:
请先登录,再评论

暂无回复,快来写下第一个回复吧~

为你推荐

大数据中台之Kafka,到底好在哪里?
Hello,大家好,今天给大家分享一个大数据里面很火的技术——Kafka,Kafka 是一个分布式的消息系统,其高性能在圈内很出名。本人阅读过多个大数据生态的开源技术的源码,个人感觉 Kafka 的源
什么?搞不定Kafka重复消费?
今天我们聊一个话题,如何保证 Kafka 消息不重复消费?在使用 Kafka 的时候一般都会设置重试的次数,但是因为网络的一些原因,设置了重试就有可能导致有些消息重复发送了(当然导致消息重复也有可能是其他原因),那么怎么解决消息重复这个问题呢?
Kafka的生产者优秀架构设计
前言 Kafka 是一个高吞吐量的分布式的发布订阅消息系统,在全世界都很流行,在大数据项目里面使用尤其频繁。笔者看过多个大数据开源产品的源码,感觉 Kafka 的源码是其中质量比较上乘的一个,这得益于
一次 Docker 容器内大量僵尸进程排查分析
前段时间线上的一个使用 Google Puppeteer 生成图片的服务炸了,每个 docker 容器内都有几千个孤儿僵死进程没有回收,如下图所示。这篇文章比较长,主要就讲了下面这几个问题。- 什么情
What?一个 Dubbo 服务启动要两个小时!
前言前几天在测试环境碰到一个非常奇怪的与 ```dubbo``` 相关的问题,事后我在网上搜索了一圈并没有发现类似的帖子或文章,于是便有了这篇。希望对还未碰到或正在碰到的朋友有所帮助。 现象现象是这样
Jackson修改字段名和自定义命名策略
国庆期间写了一教程:[《轻松学习Jackson》程序员口袋里的开发手册](https://996geek.com/articles/164),这是其中的一篇。Jackson支持在处理数据的时候,使用不
又踩到Dubbo的坑,但是这次我笑不出来
前言直入主题,线上应用发现,偶发性出现如下异常日志。当然由于线上具体异常包含信息量过大,秉承让肥朝的粉丝没有难调试的代码的原则,我特意抽取了一个复现的demo放在了git,让你不在现场,一样享受到排查
Kafka 顺序消费线程模型的实践与优化
各类消息中间件对顺序消息实现的做法是将具有顺序性的一类消息发往相同的主题分区中,只需要将这类消息设置相同的 Key 即可,而 Kafka 会在任意时刻保证一个消费组同时只能有一个消费者监听消费,因此可