spring feign http客户端连接池配置以及spring zuul http客户端连接池配置解析

Wesley13
• 阅读 828

背景

Feign和网关Zuul的RPC调用,实际上都是HTTP请求。HTTP请求,如果不配置好HTTP连接池参数的话,会影响性能,或者造成堆积阻塞,对于其中一个微服务的调用影响到其他微服务的调用。

源代码类比解析

本文基于Spring Cloud Dalston.SR4,但是基本思路上,这块比较稳定,不稳定的是Feign本身HttpClient的配置实现上。

不过个人感觉,未来Feign可能也会转去用底层Ribbon的HttpClient。因为可以配置,并且实现的连接池粒度更细一些。

Feign Http客户端解析

Feign调用和网关Zuul调用都用了HttpClient,不同的是,这个HttpClient所在层不一样。Feign调用,利用的是自己这一层的HttpClient,并没有用底层Ribbon,只是从Ribbon中获取了服务实例列表。Zuul没有自己的Httpclient,直接利用底层的Ribbon的HttpClient进行调用。

先看看Feign,Feign的Http客户端默认是ApacheHttpClient。这个可以替换成OkHttpClient(参考:https://segmentfault.com/a/1190000009071952 但是,由于我们其他组件的配置,例如重试等等,导致我们这里只能用默认的ApacheHttpClient)。

打断点,看下核心实现的源代码feign.httpclient.ApacheHttpClient:

public final class ApacheHttpClient implements Client {
    private static final String ACCEPT_HEADER_NAME = "Accept";
    private final HttpClient client;

    public ApacheHttpClient() {
        this(HttpClientBuilder.create().build());
    }

    public ApacheHttpClient(HttpClient client) {
        this.client = client;
    }

    public Response execute(Request request, Options options) throws IOException {
        HttpUriRequest httpUriRequest;
        try {
            httpUriRequest = this.toHttpUriRequest(request, options);
        } catch (URISyntaxException var6) {
            throw new IOException("URL '" + request.url() + "' couldn't be parsed into a URI", var6);
        }

        HttpResponse httpResponse = this.client.execute(httpUriRequest);
        Response response = this.toFeignResponse(httpResponse).toBuilder().request(request).build();
        HttpResponseConvertUtil.convert5XXToException(httpUriRequest, httpResponse);
        return response;
    }
    //其他代码略
}

打断点确认,在某个微服务被调用时,确实HTTP请求在这里的execute方法中发出。我们看下构造方法,发现就是用默认配置的HttpClientBuilder构造的。这样不太好,默认情况下,没有连接池,而是依靠对于不同实例地址的共用不同的一个长连接。而又没找到,可以配置参数的地方,所以选择覆盖这里的源代码,将其无参构造器改成:

public ApacheHttpClient()
{
    HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
    // 长连接保持30秒
    PoolingHttpClientConnectionManager pollingConnectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
    // 总连接数
    pollingConnectionManager.setMaxTotal(1000);
    // 同路由的并发数
    pollingConnectionManager.setDefaultMaxPerRoute(100);
    // 保持长连接配置,需要在头添加Keep-Alive
    httpClientBuilder.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy());
    httpClientBuilder.setConnectionManager(pollingConnectionManager);
    this.client = httpClientBuilder.build();
}

但是,这么改只是简单的改了下,首先没有做成可配置的,其次就是没有做成对于每个实例隔离连接池(每个实例用不同的HttpClient)。只是整体上对于服务器做了每个实例最多用100个连接的配置。
个人感觉未来feign未来会更改这部分逻辑,所以没大改,而且,都是内网调用,配置成这样也基本可以接受了。

Zuul Http客户端解析

Zuul利用底层的Ribbon Http客户端,更好用些;同样的,我们先看下核心源码RibbonLoadBalancingHttpClient:

public class RibbonLoadBalancingHttpClient extends AbstractLoadBalancingClient<RibbonApacheHttpRequest, RibbonApacheHttpResponse, CloseableHttpClient> {

    public RibbonLoadBalancingHttpClient(IClientConfig config, ServerIntrospector serverIntrospector)
    {
        super(config, serverIntrospector);
    }

    public RibbonLoadBalancingHttpClient(CloseableHttpClient delegate, IClientConfig config,
            ServerIntrospector serverIntrospector)
    {
        super(delegate, config, serverIntrospector);
    }

    protected CloseableHttpClient createDelegate(IClientConfig config)
    {
        return HttpClientBuilder.create()
                // already defaults to 0 in builder, so resetting to 0 won't hurt
                .setMaxConnTotal(config.getPropertyAsInteger(CommonClientConfigKey.MaxTotalConnections, 0))
                // already defaults to 0 in builder, so resetting to 0 won't hurt
                .setMaxConnPerRoute(config.getPropertyAsInteger(CommonClientConfigKey.MaxConnectionsPerHost, 0))
                .disableCookieManagement().useSystemProperties() // for proxy
                .build();
    }

    @Override
    public RibbonApacheHttpResponse execute(RibbonApacheHttpRequest request, final IClientConfig configOverride)
            throws Exception
    {
        final RequestConfig.Builder builder = RequestConfig.custom();
        IClientConfig config = configOverride != null ? configOverride : this.config;
        builder.setConnectTimeout(config.get(CommonClientConfigKey.ConnectTimeout, this.connectTimeout));
        builder.setSocketTimeout(config.get(CommonClientConfigKey.ReadTimeout, this.readTimeout));
        builder.setRedirectsEnabled(config.get(CommonClientConfigKey.FollowRedirects, this.followRedirects));

        final RequestConfig requestConfig = builder.build();
        if (isSecure(configOverride))
        {
            final URI secureUri = UriComponentsBuilder.fromUri(request.getUri()).scheme("https").build().toUri();
            request = request.withNewUri(secureUri);
        }
        final HttpUriRequest httpUriRequest = request.toRequest(requestConfig);
        final HttpResponse httpResponse = this.delegate.execute(httpUriRequest);

        return new RibbonApacheHttpResponse(httpResponse, httpUriRequest.getURI());
    }

    @Override
    public URI reconstructURIWithServer(Server server, URI original)
    {
        URI uri = updateToHttpsIfNeeded(original, this.config, this.serverIntrospector, server);
        return super.reconstructURIWithServer(server, uri);
    }

    @Override
    public RequestSpecificRetryHandler getRequestSpecificRetryHandler(RibbonApacheHttpRequest request,
            IClientConfig requestConfig)
    {
        return new RequestSpecificRetryHandler(false, false, RetryHandler.DEFAULT, requestConfig);
    }
}

从createDelegate这个方法可以看出通过HttpClientBuilder建立HttpClient,并且是可配置的,配置类是CommonClientConfigKey,我们可以配置这几个参数实现对于连接池大小和每个路由连接大小的控制,就是:

ribbon.MaxTotalConnections=200
ribbon.MaxConnectionsPerHost=100

由于是CommonClientConfigKey下的配置,所以也可以对于每个微服务配置:

service1.ribbon.MaxTotalConnections=200
service1.ribbon.MaxConnectionsPerHost=100
service2.ribbon.MaxTotalConnections=200
service2.ribbon.MaxConnectionsPerHost=100

通过配置以及打断点,可以看出,对于每个微服务的调用,都走的是不同的CloseableHttpClient,我们可以对每个微服务单独配置;例如,假设service1有两个实例,service2有三个实例,service1访问压力大概一共需要100个连接,service2访问压力大概一共需要300个连接.我们假设平均分配没有问题,则可以这么配置:

service1.ribbon.MaxTotalConnections=100
service1.ribbon.MaxConnectionsPerHost=50
service2.ribbon.MaxTotalConnections=300
service2.ribbon.MaxConnectionsPerHost=100

但是,考虑如果某台服务器如果出异常了,这么配置会导致连接也许不够用,所以,最好PerHost的就设置为总共需要多少个连接:

service1.ribbon.MaxTotalConnections=200
service1.ribbon.MaxConnectionsPerHost=100
service2.ribbon.MaxTotalConnections=900
service2.ribbon.MaxConnectionsPerHost=300
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Nepxion Discovery 5.5.0 发布
!(https://oscimg.oschina.net/oscnet/f81c043194ef4732880459d00c1a720e.png)发布日志功能更新:增加基于Opentracing调用链的支持,目前支持UberJaeger,实现在SpringCloudGateway、Zuul和服务上的灰度
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这