一次NSF FeignClient支持Apache HttpClient的优化

本文涉及的产品
函数计算FC,每月15万CU 3个月
应用实时监控服务-用户体验监控,每月100OCU免费额度
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 一次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已经不再成为阻塞点了。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
6月前
|
SQL 存储 JSON
阿里云数据库 SelectDB 内核 Apache Doris 2.1.0 版本发布:开箱盲测性能大幅优化,复杂查询性能提升 100%
亲爱的社区小伙伴们,Apache Doris 2.1.0 版本已于 2024 年 3 月 8 日正式发布,新版本开箱盲测性能大幅优化,在复杂查询性能方面提升100%,新增Arrow Flight接口加速数据读取千倍,支持半结构化数据类型与分析函数。异步多表物化视图优化查询并助力仓库分层建模。引入自增列、自动分区等存储优化,提升实时写入效率。Workload Group 资源隔离强化及运行时监控功能升级,保障多负载场景下的稳定性。新版本已经上线,欢迎大家下载使用!
阿里云数据库 SelectDB 内核 Apache Doris 2.1.0 版本发布:开箱盲测性能大幅优化,复杂查询性能提升 100%
|
缓存 安全 Linux
百度搜索:蓝易云【Apache安装与优化教程。】
通过以上步骤,你已经成功安装和优化了Apache服务器。你可以根据自己的需求进行进一步的配置和调整,以满足你的网站的性能和安全需求。
249 2
|
22天前
|
消息中间件 监控 大数据
优化Apache Kafka性能:最佳实践与调优策略
【10月更文挑战第24天】作为一名已经对Apache Kafka有所了解并有实际使用经验的开发者,我深知在大数据处理和实时数据流传输中,Kafka的重要性不言而喻。然而,在面对日益增长的数据量和业务需求时,如何保证系统的高性能和稳定性成为了摆在我们面前的一个挑战。本文将从我的个人视角出发,分享一些关于如何通过合理的配置和调优来提高Kafka性能的经验和建议。
59 4
|
1月前
|
SQL 分布式计算 NoSQL
大数据-164 Apache Kylin Cube优化 案例1 定义衍生维度与对比 超详细
大数据-164 Apache Kylin Cube优化 案例1 定义衍生维度与对比 超详细
29 1
大数据-164 Apache Kylin Cube优化 案例1 定义衍生维度与对比 超详细
|
1月前
|
存储 大数据 分布式数据库
大数据-165 Apache Kylin Cube优化 案例 2 定义衍生维度及对比 & 聚合组 & RowKeys
大数据-165 Apache Kylin Cube优化 案例 2 定义衍生维度及对比 & 聚合组 & RowKeys
35 1
|
1月前
|
SQL 存储 监控
大数据-161 Apache Kylin 构建Cube 按照日期、区域、产品、渠道 与 Cube 优化
大数据-161 Apache Kylin 构建Cube 按照日期、区域、产品、渠道 与 Cube 优化
50 0
|
3月前
|
监控 Apache
Apache 工作模式的区别及优化
【8月更文挑战第22天】Apache 工作模式的区别及优化
79 0
|
5月前
|
JSON 前端开发 API
Apache HttpClient调用Spring3 MVC Restful Web API演示
Apache HttpClient调用Spring3 MVC Restful Web API演示
44 1
|
5月前
|
缓存 监控 负载均衡
使用Apache Solr进行搜索优化的技术探索
【6月更文挑战第6天】探索Apache Solr搜索优化,通过字段选择、分析器优化、索引压缩提升索引效率;优化查询分析、缓存、分组排序以增强查询性能;硬件升级、分布式部署及监控调优保证系统稳定性。实战案例展示如何在电商平台上应用这些策略,实现快速准确的搜索服务。Solr在大数据时代展现出广阔的应用潜力。
|
6月前
|
缓存 安全 网络安全
Apache服务器配置与优化指南
【5月更文挑战第7天】Apache服务器配置与优化指南包括基础配置和性能优化。安装Apache后,编辑`httpd.conf`配置文件,设置`ServerRoot`、`Listen`、`ServerName`和`DocumentRoot`。启用虚拟主机以托管多个网站。性能优化涉及启用MPM模块(如worker或event),启用压缩功能,优化KeepAlive参数,配置缓存和限制并发连接数。安全配置包括禁用不必要的模块,设置目录权限,启用SSL/TLS及限制IP访问。通过这些措施,提升服务器性能和安全性。

推荐镜像

更多