一次NSF FeignClient支持Apache HttpClient的优化

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
Serverless 应用引擎免费试用套餐包,4320000 CU,有效期3个月
可观测可视化 Grafana 版,10个用户账号 1个月
简介: 一次NSF FeignClient支持Apache HttpClient的优化

背景介绍

NSF(Netease Service Framework)是网易数帆下的一款微服务框架,项目在压测过程中,发现NSF FeignClient有性能瓶颈,下面对遇到的问题及优化方案进行分析,以备忘。

问题分析

在压测过程中发现系统耗时高,通过arthas thread -b发现大量线程处于阻塞状态,进一步分析发现NSF FeignClient使用的java.net.HttpURLConnection,而NSF FeignClient当前版本没有方便的方式(比如改个配置)来支持其他HttpClient,以下通过对NSF FeignClient实现分析来尝试解决这个问题。

NSF FeignClient实现方式

NSF是通过nsf-agent将自定义的FeignClient注入到Spring容器中的,下面是NSF FeignClient的一些主要实现逻辑。

SpringConfigurationTransformer

该类向Spring注入了一些Configuration组件。

public class SpringConfigurationTransformer implements NoneNamedTransformer {
  private static Logger logger = AgentLogFactory.getLogger(SpringConfigurationTransformer.class);
  public TransformerResult transform(ClassLoader paramClassLoader, String paramString, Class<?> paramClass, ProtectionDomain paramProtectionDomain, byte[] paramArrayOfByte, Object paramObject) throws Exception {
    ClassPool classPool = ClassPool.getDefault();
    InputStream ins = new ByteArrayInputStream(paramArrayOfByte);
    CtClass ctclass = classPool.makeClass(ins);
    TransformerResult result = new TransformerResult(ctclass);
    result.setClassModified(Boolean.valueOf(true));
    logger.debug("nsf-agent: SpringConfigurationTransformer process transform ...");
    List<String> importConfigurationList = new ArrayList<>();
    importConfigurationList.add("com.netease.cloud.nsf.agent.http.configuration.SpringContextConfiguration");
    importConfigurationList.add("com.netease.cloud.nsf.agent.http.configuration.NsfRegistryConfiguration");
    importConfigurationList.add("com.netease.cloud.nsf.agent.http.configuration.SpringFeignConfiguration");
    importConfigurationList.add("com.netease.cloud.nsf.agent.http.configuration.SpringRibbonConfiguration");
    importConfigurationList.add("com.netease.cloud.nsf.agent.http.configuration.ServletFilterConfiguration");
    importConfigurationList.add("com.netease.cloud.nsf.agent.http.configuration.ToolsConfiguration");
    importConfigurationList.add("com.netease.cloud.nsf.agent.http.configuration.NacosToolsConfiguration");
    importConfigurationList.add("com.netease.cloud.nsf.agent.core.autoconfig.ConfigDebugConfiguration");
    importConfigurationList.add("com.netease.cloud.nsf.agent.http.configuration.NsfSpringDiscoveryClientConfiguration");
    JavassistUtil.addImportAnnotations(ctclass, importConfigurationList);
    result.setClassCodeBytes(ctclass.toBytecode());
    ctclass.defrost();
    return result;
  }
  public Object classNameMatch(String paramString) {
    if (paramString.equalsIgnoreCase(GlobalConfigManager.getSpringBootClassName()))
      return Boolean.valueOf(true); 
    return null;
  }
}

与该问题直接相关的是SpringFeignConfiguration。

SpringFeignConfiguration

将自定义的feignClient相关配置注入Spring。

@Configuration
@ConditionalOnClass(name = {"feign.Client", "org.springframework.cloud.openfeign.FeignClient"})
public class SpringFeignConfiguration {
  private Logger logger = AgentLogFactory.getLogger(SpringFeignConfiguration.class);
  @PostConstruct
  public void initFeign() {
    this.logger.info("find feign, init NsfFeignClient ...");
  }
  @Bean(name = {"feignClient"})
  @ConditionalOnClass(name = {"org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient"})
  public Client loadBalancerFeignClient(RestTemplate nsfMethodRestTemplate) {
    return (Client)new NsfLoadBalancerFeignClient(nsfMethodRestTemplate);
  }
  @Bean(name = {"feignClient"})
  @ConditionalOnMissingClass({"org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient"})
  public NsfFeignClient feignClient(RestTemplate nsfMethodRestTemplate) {
    return new NsfFeignClient(nsfMethodRestTemplate);
  }
}

在运行环境中,加载的是NsfLoadBalancerFeignClient。

NsfLoadBalancerFeignClient

public class NsfLoadBalancerFeignClient extends LoadBalancerFeignClient implements Client {
  private NsfFeignClient feignClient;
  public NsfLoadBalancerFeignClient(RestTemplate restTemplate) {
    super((Client)new Client.Default(null, null), null, null);
    this.feignClient = new NsfFeignClient(restTemplate);
  }
  public Response execute(Request request, Request.Options options) throws IOException {
    return this.feignClient.execute(request, options);
  }
}

我们接着查看NsfFeignClient的实现。

NsfFeignClient

public class NsfFeignClient implements Client {
  private static final String ACCEPT_HEADER_NAME = "Accept";
  private ClientHttpRequestFactory requestFactory;
  public NsfFeignClient(RestTemplate restTemplate) {
    this.requestFactory = (ClientHttpRequestFactory)new InterceptingClientHttpRequestFactory((ClientHttpRequestFactory)new NsfClientHttpRequestFactory(), restTemplate.getInterceptors());
  }
  ... ...
}

其中requestFactory是通过NsfClientHttpRequestFactory进行初始化的,也是问题的关键所在,下面看下NsfClientHttpRequestFactory的实现。

NsfClientHttpRequestFactory

public class NsfClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
  public static final ThreadLocal<TimeOut> timeoutCache = new ThreadLocal<TimeOut>() {
      protected NsfClientHttpRequestFactory.TimeOut initialValue() {
        return new NsfClientHttpRequestFactory.TimeOut();
      }
    };
  ... ...
}

NsfClientHttpRequestFactory继承了Spring中的SimpleClientHttpRequestFactory,下面看下SimpleClientHttpRequestFactory中创建连接的逻辑。

SimpleClientHttpRequestFactory
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
  HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
  prepareConnection(connection, httpMethod.name());
  if (this.bufferRequestBody) {
    return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
  }
  else {
    return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
  }
}
protected HttpURLConnection openConnection(URL url, @Nullable Proxy proxy) throws IOException {
  URLConnection urlConnection = (proxy != null ? url.openConnection(proxy) : url.openConnection());
  if (!(urlConnection instanceof HttpURLConnection)) {
    throw new IllegalStateException(
      "HttpURLConnection required for [" + url + "] but got: " + urlConnection);
  }
  return (HttpURLConnection) urlConnection;
}

其中创建连接的逻辑在openConnection方法中,使用HttpURLConnection创建的连接。

优化方案

通过问题分析知道了问题所在,优化方案就比较简单了,只要想办法替换掉java.net.HttpURLConnection就可以了,为了尽可能小的影响NSF FeignClient已有逻辑,我们仿照NSF定义一套相应的类,只需要替换掉java.net.HttpURLConnection的逻辑就可以了。

自定义FeignClient

仿照现有NSF FeignClient单独写一套即可,关键是替换掉java.net.HttpURLConnection。

替换NSF feignClient

通过BeanDefinitionRegistry使用我们自定义的feignClient替换掉NSF feignClient。

public class DefaultBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        String[] beanDefinitionNames = beanDefinitionRegistry.getBeanDefinitionNames();
        for(String beanName : beanDefinitionNames){
            if(beanName.equals("feignClient")){
                BeanDefinition wlFeignClient = beanDefinitionRegistry.getBeanDefinition("wlFeignClient");
                if(wlFeignClient != null) {
                    beanDefinitionRegistry.removeBeanDefinition("feignClient");
                    beanDefinitionRegistry.removeBeanDefinition("wlFeignClient");
                    beanDefinitionRegistry.registerBeanDefinition("feignClient", wlFeignClient);
                }
                break;
            }
        }
    }
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    }
}

总结

通过arthas定位出问题原因,通过分析搞清楚NSF FeignClient的实现,然后再实现一套支持Apache HttpClient的方案来替换原来的HttpURLConnection,经过测试NSF FeignClient已经不再成为阻塞点了。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
目录
相关文章
|
SQL 存储 JSON
阿里云数据库 SelectDB 内核 Apache Doris 2.1.0 版本发布:开箱盲测性能大幅优化,复杂查询性能提升 100%
亲爱的社区小伙伴们,Apache Doris 2.1.0 版本已于 2024 年 3 月 8 日正式发布,新版本开箱盲测性能大幅优化,在复杂查询性能方面提升100%,新增Arrow Flight接口加速数据读取千倍,支持半结构化数据类型与分析函数。异步多表物化视图优化查询并助力仓库分层建模。引入自增列、自动分区等存储优化,提升实时写入效率。Workload Group 资源隔离强化及运行时监控功能升级,保障多负载场景下的稳定性。新版本已经上线,欢迎大家下载使用!
1052 1
阿里云数据库 SelectDB 内核 Apache Doris 2.1.0 版本发布:开箱盲测性能大幅优化,复杂查询性能提升 100%
|
缓存 安全 Linux
百度搜索:蓝易云【Apache安装与优化教程。】
通过以上步骤,你已经成功安装和优化了Apache服务器。你可以根据自己的需求进行进一步的配置和调整,以满足你的网站的性能和安全需求。
280 2
|
10月前
|
消息中间件 监控 大数据
优化Apache Kafka性能:最佳实践与调优策略
【10月更文挑战第24天】作为一名已经对Apache Kafka有所了解并有实际使用经验的开发者,我深知在大数据处理和实时数据流传输中,Kafka的重要性不言而喻。然而,在面对日益增长的数据量和业务需求时,如何保证系统的高性能和稳定性成为了摆在我们面前的一个挑战。本文将从我的个人视角出发,分享一些关于如何通过合理的配置和调优来提高Kafka性能的经验和建议。
309 4
|
5月前
|
监控 安全 BI
优化 Apache 日志记录的 5 个最佳实践
Apache 日志记录对于维护系统运行状况和网络安全至关重要,其核心包括访问日志与错误日志的管理。通过制定合理的日志策略,如选择合适的日志格式、利用条件日志减少冗余、优化日志级别、使用取证模块提升安全性及实施日志轮换,可有效提高日志可用性并降低系统负担。此外,借助 Eventlog Analyzer 等专业工具,能够实现日志的高效收集、可视化分析与威胁检测,从而精准定位安全隐患、评估服务器性能,并满足合规需求,为强化网络安全提供有力支持。
129 0
优化 Apache 日志记录的 5 个最佳实践
|
11月前
|
SQL 分布式计算 NoSQL
大数据-164 Apache Kylin Cube优化 案例1 定义衍生维度与对比 超详细
大数据-164 Apache Kylin Cube优化 案例1 定义衍生维度与对比 超详细
126 1
大数据-164 Apache Kylin Cube优化 案例1 定义衍生维度与对比 超详细
|
11月前
|
存储 大数据 分布式数据库
大数据-165 Apache Kylin Cube优化 案例 2 定义衍生维度及对比 & 聚合组 & RowKeys
大数据-165 Apache Kylin Cube优化 案例 2 定义衍生维度及对比 & 聚合组 & RowKeys
163 1
|
11月前
|
SQL 存储 监控
大数据-161 Apache Kylin 构建Cube 按照日期、区域、产品、渠道 与 Cube 优化
大数据-161 Apache Kylin 构建Cube 按照日期、区域、产品、渠道 与 Cube 优化
188 0
|
监控 Apache
Apache 工作模式的区别及优化
【8月更文挑战第22天】Apache 工作模式的区别及优化
250 0
|
JSON 前端开发 API
Apache HttpClient调用Spring3 MVC Restful Web API演示
Apache HttpClient调用Spring3 MVC Restful Web API演示
104 1
|
缓存 监控 负载均衡
使用Apache Solr进行搜索优化的技术探索
【6月更文挑战第6天】探索Apache Solr搜索优化,通过字段选择、分析器优化、索引压缩提升索引效率;优化查询分析、缓存、分组排序以增强查询性能;硬件升级、分布式部署及监控调优保证系统稳定性。实战案例展示如何在电商平台上应用这些策略,实现快速准确的搜索服务。Solr在大数据时代展现出广阔的应用潜力。