spring feign http客户端连接池配置以及spring zuul http客户端连接池配置解析
2017-12-08 16:04
639 查看
背景
一般在生产项目中, Feign会使用HTTP连接池而不是默认的Java原生HTTP单路由单长连接;而是使用连接池。Zuul直接使用Ribbon的Http连接池;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
更多问题
之后我还发现了多实例重启时,短时间内重试失败的问题,在这篇文章里面说明了相关文章推荐
- spring feign http客户端连接池配置以及spring zuul http客户端连接池配置解析
- 支持并发的http客户端(基于tcp连接池以及netty)
- mybatis使用spring-druid数据源连接池配置log4j打印sql语句以及开启监控平台
- 配置Java连接池的两种方式:tomcat方式以及spring方式
- Java框架spring 学习笔记(十六):c3p0连接池的配置以及dao使用jdbcTemplate
- Spring MVC整理系列(05)————Spring MVC配置解析及整合SpriSpring MVC之@ModelAttribute、@SessionAttributes以及Model的使用介绍
- Spring HttpInvoker 服务端安全验证的和客户端请求配置
- SpringHttpInvoker解析3-客户端实现
- 配置Java连接池的两种方式:tomcat方式以及spring方式
- Spring 事务管理配置方法以及对应源码解析
- jedis(redis)整合spring,包括jedis客户端单机版,jedis集群版配置 ,连接池配置
- 利用spring+hibernate配置dbcp连接池配置的异常解析
- HttpURLConnection从客户端向服务器发送Http请求以及服务器响应全过程解析
- spring-boot 作为dubbo客户端 调用 dubbo 服务端 配置以及代码段
- Druid连接池以及在springboot下配置
- MySQL数据库事务、连接池运用(properties文件配置以及解析)
- Spring学习之使用静态工厂方法以及实例工厂方法配置bean
- Spring中数据库连接池的三种配置(dbcp,c3p0,proxool)(转载)
- Spring配置DBCP连接池不能释放的问题
- SDR(spring.data.redis)与Sentinel高可用集群Redis客户端Jedis配置