简介
之前遇到配置ribbon的超时参数,发现无法让restTemplate生效,因此想要了解RestTemplate相关机制,以及与Ribbon集成时的相关逻辑;
RestTemplate类图
RestTemplate继承祖父类HttpAccessor的createRequest方法和父类InterceptingHttpAccessor的getRequestFactory方法;这两个方法决定了每一个Request的创建机制;
createRequest方法如下:
protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);//由子类定义的RequestFactory来进行创建Request
if (logger.isDebugEnabled()) {
logger.debug("Created " + method.name() + " request for \"" + url + "\"");
}
return request;
}
getRequestFactory方法如下:
public ClientHttpRequestFactory getRequestFactory() {
List<ClientHttpRequestInterceptor> interceptors = getInterceptors();
if (!CollectionUtils.isEmpty(interceptors)) {//在有拦截器的情况下,使用InterceptingClientHttpRequestFactory
ClientHttpRequestFactory factory = this.interceptingRequestFactory;
if (factory == null) {//设置RequestFactory为InterceptingRequestFactory
factory = new InterceptingClientHttpRequestFactory(super.getRequestFactory(), interceptors);
this.interceptingRequestFactory = factory;
}
return factory;
}
else {
return super.getRequestFactory();
}
}
RestTemplate调用时序图
具体流程就是:
- 1.RestTemplate根据传入的参数创建一个基础的HttpRequest;
- 2.InterceptingClientHttpRequest利用内部的Interceptors对HttpRequest进行拦截处理,每个Interceptors都会传入InterceptingRequestExecution参数,来保证HttpRequest的最终处理;
- 3.Interceptors处理完成后LoadBalancerClient的execute方法,在该方法中会回调InterceptingRequestExecution的execute方法来完成最后的HttpRequest的处理,源码如下:
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}
else {
HttpMethod method = request.getMethod();
Assert.state(method != null, "No standard HTTP method");
ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);
request.getHeaders().forEach((key, value) -> delegate.getHeaders().addAll(key, value));
if (body.length > 0) {
if (delegate instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingOutputMessage = (StreamingHttpOutputMessage) delegate;
streamingOutputMessage.setBody(outputStream -> StreamUtils.copy(body, outputStream));
}
else {
StreamUtils.copy(body, delegate.getBody());
}
}
return delegate.execute();
}
}
}
Ribbon集成
根据RestTemplate的流程时序,Ribbon提供了两个对象来侵入RestTemplate:
- 1.RibbonLoadBalancerClient,继承LoadBalancerClient,其execute()实现方法如下:
@Override
public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
Server server = null;
if(serviceInstance instanceof RibbonServer) {
server = ((RibbonServer)serviceInstance).getServer();
}
if (server == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
try {
T returnVal = request.apply(serviceInstance);//回调,来完成最后的HttpRequest请求构建
statsRecorder.recordStats(returnVal);
return returnVal;
}
// catch IOException and rethrow so RestTemplate behaves correctly
catch (IOException ex) {
statsRecorder.recordStats(ex);
throw ex;
}
catch (Exception ex) {
statsRecorder.recordStats(ex);
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
- 2.RibbonLoadBalancedRetryPolicy:该对象继承LoadBalancedRetryPolicy,用来实现重试策略,通过配置servicename.ribbon.MaxAutoRetries等参数可以实现重试配置,该对象会
配合RetryLoadBalancerInterceptor使用来是的RestTemplate请求能够实现重试,RetryLoadBalancerInterceptor的interceptor方法源码如下:
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri = request.getURI();
final String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
final LoadBalancedRetryPolicy retryPolicy = lbRetryFactory.createRetryPolicy(serviceName,
loadBalancer);
RetryTemplate template = createRetryTemplate(serviceName, request, retryPolicy);//创建RetryTemplate来实现重试
return template.execute(context -> {//进入重试调用链
ServiceInstance serviceInstance = null;
if (context instanceof LoadBalancedRetryContext) {
LoadBalancedRetryContext lbContext = (LoadBalancedRetryContext) context;
serviceInstance = lbContext.getServiceInstance();
}
if (serviceInstance == null) {
serviceInstance = loadBalancer.choose(serviceName);
}
//在重试调用上下文中进行request的初始执行,由此处返回到InterceptingRequestExecution中执行request执行
ClientHttpResponse response = RetryLoadBalancerInterceptor.this.loadBalancer.execute(
serviceName, serviceInstance,
requestFactory.createRequest(request, body, execution));
int statusCode = response.getRawStatusCode();
if (retryPolicy != null && retryPolicy.retryableStatusCode(statusCode)) {
byte[] bodyCopy = StreamUtils.copyToByteArray(response.getBody());
response.close();
throw new ClientHttpResponseStatusCodeException(serviceName, response, bodyCopy);
}
return response;
}, new LoadBalancedRecoveryCallback<ClientHttpResponse, ClientHttpResponse>() {
//This is a special case, where both parameters to LoadBalancedRecoveryCallback are
//the same. In most cases they would be different.
@Override
protected ClientHttpResponse createResponse(ClientHttpResponse response, URI uri) {
return response;
}
});
}
Ribbon配置加载
Ribbon加载相关配置类有两个:
- RibbonClientConfiguration:主要用来初始化Ribbon的DefaultClientConfigImpl,IRule,ILoadBalancer,RibbonLoadBalancerContext,RetryHandler等等;
- LoadBalancerAutoConfiguration:该配置主要用来初始化
LoadBalancer相关配置,如LoadBalancerInterceptor以及RestTemplateCustomizer来实现RestTemplate相关ClientHttpRequestInterceptor注入,另外还会加载
RetryLoadBalancerInterceptor
RetryLoadBalancerInterceptor必须在spring.cloud.loadbalancer.retry=true的情况下才会生效;
Ribbon配置的超时时间对RestTemplate不生效原因
这个可以看下RibbonClientConfiguration初始化IClientConfig源码,如下:
@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
DefaultClientConfigImpl config = new DefaultClientConfigImpl();
config.loadProperties(this.name);//加载配置
config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);//设置默认连接时间
config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);//设置默认请求时间;
return config;
}
这就导致了无论Ribbon的配置的超时参数是多少,这都会导致超时参数被覆盖;
但是这里并不是真正导致超时时间不生效的原因,而是SimpleClientHttpRequestFactory,因为SimpleClientHttpRequestFactory会在request数据完全准备好了之后进行Connection的初始化,可以查看SimpleClientHttpRequestFactory方法prepareConnection,源码如下:
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
if (this.connectTimeout >= 0) {
connection.setConnectTimeout(this.connectTimeout);
}
if (this.readTimeout >= 0) {
connection.setReadTimeout(this.readTimeout);
}
connection.setDoInput(true);
if ("GET".equals(httpMethod)) {
connection.setInstanceFollowRedirects(true);
}
else {
connection.setInstanceFollowRedirects(false);
}
if ("POST".equals(httpMethod) || "PUT".equals(httpMethod) ||
"PATCH".equals(httpMethod) || "DELETE".equals(httpMethod)) {
connection.setDoOutput(true);
}
else {
connection.setDoOutput(false);
}
connection.setRequestMethod(httpMethod);
}
}
SimpleClientHttpRequestFactory中的connectionTimeout和ReadTimeout并不会被Ribbon中的参数覆盖,因此要解决超时配置问题就只能在代码层面进行配置,如下:
S @Bean
@LoadBalanced//用于负载均衡的调用微服务实例,会将http://${spring.application.name}/path解析成ip+port的形式
public RestTemplate getRibbonTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setConnectTimeout(10000);
requestFactory.setReadTimeout(10000);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
版权声明:本文为heyiwuwu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。