一次NSF FeignClient支持Apache HttpClient的优化

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
可观测可视化 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已经不再成为阻塞点了。

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
2月前
|
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%
|
7月前
|
缓存 安全 Linux
百度搜索:蓝易云【Apache安装与优化教程。】
通过以上步骤,你已经成功安装和优化了Apache服务器。你可以根据自己的需求进行进一步的配置和调整,以满足你的网站的性能和安全需求。
215 2
|
2月前
|
运维 Linux Apache
LAMP架构调优(十)——Apache禁止指定目录PHP解析与错误页面优化
LAMP架构调优(十)——Apache禁止指定目录PHP解析与错误页面优化
199 2
|
3月前
|
Java Apache
Apache HttpClient 4.5设置超时时间
Apache HttpClient 4.5设置超时时间
|
5月前
|
数据采集 安全 Java
Kotlin+Apache HttpClient+代理服务器=高效的eBay图片爬虫
本文将为你介绍一种高效的eBay图片爬虫的实现方式,让你可以用Kotlin+Apache HttpClient+代理服务器的组合来轻松地下载eBay的图片。
Kotlin+Apache HttpClient+代理服务器=高效的eBay图片爬虫
|
8月前
|
缓存 Linux 网络安全
百度搜索:蓝易云【Linux系统Apache优化与防盗链详细教程】
这些是关于Linux系统下Apache优化和防盗链的基本教程。根据实际需求和具体情况,您可能需要进行更多的配置和调整。在修改Apache配置文件之前,请确保您对配置语法和操作有一定的了解,并备份原始配置文件以防意外情况发生。
316 1
百度搜索:蓝易云【Linux系统Apache优化与防盗链详细教程】
|
9月前
|
监控 Java Apache
优化开发效率:耗时分析利器Apache StopWatch
Apache StopWatch是Apache Commons库中的一个组件,它提供了简单而强大的计时器功能。StopWatch可以帮助开发人员精确地计时方法或代码块的执行时间,以便进行性能分析和优化。它提供了计时、暂停、继续、重置等功能,使我们能够更好地监控和控制代码的执行时间。
117 0
 优化开发效率:耗时分析利器Apache StopWatch
|
11月前
|
XML 缓存 前端开发
apache网址优化
apache网址优化
48 0
|
12月前
|
SQL 分布式计算 监控
《Apache Flink 案例集(2022版)》——2.数据分析——BIGO-BIGO使用Flink做OLAP分析及实时数仓的实践和优化(上)
《Apache Flink 案例集(2022版)》——2.数据分析——BIGO-BIGO使用Flink做OLAP分析及实时数仓的实践和优化(上)
454 0
|
12月前
|
消息中间件 SQL 大数据
《Apache Flink 案例集(2022版)》——2.数据分析——BIGO-BIGO使用Flink做OLAP分析及实时数仓的实践和优化(下)
《Apache Flink 案例集(2022版)》——2.数据分析——BIGO-BIGO使用Flink做OLAP分析及实时数仓的实践和优化(下)
490 0

推荐镜像

更多