图解+源码讲解 Ribbon 如何发起网络请求


构成天才的决定因素就应是勤奋 —— 郭沫若


相关文章
​图解+源码讲解 Ribbon 如何获取注册中心的实例图解+源码讲解 Ribbon 原理初探图解+源码讲解 Ribbon 服务列表更新图解+源码讲解 Ribbon 服务选择原理

从哪里进行分析

    指定也是从执行的方法里面进行开始执行的,也是通过 RestTemplate发起请求的,之后通过拦截器进行拦截处理,先获取负载均衡器,之后再从负载均衡器里面获取服务列表,之后再进行地址解析,进行网络的最终请求,之后再返回给RestTemplate 进行结果返回数据的解析。

简图

源码概览图

图解+源码讲解 Ribbon 如何发起网络请求_拦截器

源码分析

1. RestTemplate 发起请求

@RestController
@RequestMapping("portal")
public class GoodsController {
// 需要访问的注册中心的实例名称
private static final String GOODS_SERVICE_URL =
"http://GOODS-APPLICATION/good/getGoods";
@Autowired
private RestTemplate restTemplate;

@RequestMapping(value = "/getGoods",produces = "application/json;charset=UTF-8")
public String getGoods() {
// 进行 RestTemplate 请求访问
ResponseEntity<ResultObject> result = restTemplate
.getForEntity(GOODS_SERVICE_URL, ResultObject.class);
// 获取返回的请求结果
ResultObject resultBody = result.getBody();
System.out.println(resultBody.getData());
return resultBody.getStatusMessage();
}
}

注册中心服务列表详情

图解+源码讲解 Ribbon 如何发起网络请求_Spring Cloud_02
     调用的是 RestTemplate 中的doExecute方法

protected <T> T doExecute(URI url, @Nullable HttpMethod method,
@Nullable RequestCallback requestCallback,
@Nullable ResponseExtractor<T> responseExtractor)
throws RestClientException {
// 进行真正的请求访问
response = request.execute();
// 处理返回结果
handleResponse(url, method, response);
// 进行返回的结果解析
return (responseExtractor != null ? responseExtractor.extractData(response) : null);
}
}

2. 第一次进入InterceptingRequestExecution进行请求拦截

protected final ClientHttpResponse executeInternal(HttpHeaders headers,
byte[] bufferedOutput) throws IOException {
// 创建一个拦截器请求执行对象
InterceptingRequestExecution requestExecution = new InterceptingRequestExecution();
// 发起访问
return requestExecution.execute(this, bufferedOutput);
}

    第一次执行的时候会进入到if模块,因为还有迭代器没有执行,当所有的迭代器都执行完毕后就可以走下面的else 逻辑了,这就进行了请求的拦截,进行请求地址替换和选择

private class InterceptingRequestExecution implements ClientHttpRequestExecution {

private final Iterator<ClientHttpRequestInterceptor> iterator;
public InterceptingRequestExecution() {
// 给拦截器中的值进行赋值
// interceptors 这个值是LoadBalancerInterceptor初始话的时候进行设置的
this.iterator = interceptors.iterator();
}

@Override
public ClientHttpResponse execute(HttpRequest request, byte[] body)
throws IOException {
// 第一次进入的时候会走这里,因为迭代器里面有值,所以走这里,
// 等所有的迭代器都执行完毕后进行 else 逻辑请求
if (this.iterator.hasNext()) {
ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
return nextInterceptor.intercept(request, body, this);
}
else {
// 当迭代器里面的值没有的时候会走这里
HttpMethod method = request.getMethod();// 获取请求方法
ClientHttpRequest delegate =
// 创建请求方法,其中有一个获取真实URL地址的方法
requestFactory.createRequest(request.getURI(), method);
// 进行请求头部 header 完善
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();
}
}
}

3. 调用负载均衡器进行请求执行

    因为在 LoadBalancerAutoConfiguration 初始化的时候设置了拦截器就是 LoadBalancerInterceptor,并将其放入了 ClientHttpRequestInterceptor拦截器列表中并设置了设置RestTemplate拦截器是LoadBalancerInterceptor

@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(
final LoadBalancerInterceptor loadBalancerInterceptor) {
return restTemplate -> {
// 将 LoadBalancerInterceptor 拦截器放入 ClientHttpRequestInterceptor
// 拦截器列表中
List<ClientHttpRequestInterceptor> list = new ArrayList<>(
restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
// 设置RestTemplate拦截器是 LoadBalancerInterceptor
restTemplate.setInterceptors(list);
};
}

图解+源码讲解 Ribbon 如何发起网络请求_拦截器_03
    所以此时走的拦截器方法就是 LoadBalancerInterceptor 的 intercept 方法

@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
// 获取请求的URL地址
final URI originalUri = request.getURI();
// 获取请求的 serviceName
String serviceName = originalUri.getHost();
// LoadBalancerClient loadBalancer 调用的是 LoadBalancerClient的execute方法
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}

图解+源码讲解 Ribbon 如何发起网络请求_负载均衡_04
    这里调用的是 LoadBalancerClient的execute方法,这里面涉及到了获取负载均衡器,以及通过默认的轮询算法获取负载均衡器中的服务实例

public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
throws IOException {
// 获取负载均衡器,默认是动态服务列表负载均衡器 DomainExtractingServerList
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 通过轮询算法获取载均衡器中的服务实例
Server server = getServer(loadBalancer, hint);
RibbonServer ribbonServer = new RibbonServer(serviceId, server,
isSecure(server, serviceId),
serverIntrospector(serviceId).getMetadata(server));
// 发起执行
return execute(serviceId, ribbonServer, request);
}

图解+源码讲解 Ribbon 如何发起网络请求_ide_05

4. 获取负载均衡器里面的服务实例【默认是轮询算法获取实例】

// 获取负载均衡器,默认是动态服务列表负载均衡器
ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
// 通过轮询算法获取载均衡器中的服务实例
Server server = getServer(loadBalancer, hint);

图解+源码讲解 Ribbon 如何发起网络请求_负载均衡_06

5. 再一次进入 InterceptingRequestExecution 进行请求

    当所有迭代器都执行完后才会走到 else 这里面,否则会走 if 的逻辑里面

private class InterceptingRequestExecution implements ClientHttpRequestExecution {
private final Iterator<ClientHttpRequestInterceptor> iterator;
public InterceptingRequestExecution() {
this.iterator = interceptors.iterator();
}
@Override
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();
// 创建请求对象
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();
}
}
}

6. 解析真正的URL地址

    ServiceRequestWrapper 封装了解析地址的方法,这个方法也是在拦截器执行的过程中创建的
图解+源码讲解 Ribbon 如何发起网络请求_拦截器_07图解+源码讲解 Ribbon 如何发起网络请求_Spring Cloud_08
    解析地址的方法其实是调用的 RibbonLoadBalancerClient 中的 reconstructURI方法

@Override
public URI getURI() {
URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());
return uri;
}

解析地址详细流程

@Override
public URI reconstructURI(ServiceInstance instance, URI original) {
String serviceId = instance.getServiceId(); // GOODS-APPLICATION
RibbonLoadBalancerContext context = this.clientFactory
.getLoadBalancerContext(serviceId);
URI uri;
Server server;
if (instance instanceof RibbonServer) {
RibbonServer ribbonServer = (RibbonServer) instance;
server = ribbonServer.getServer(); // 192.168.2.100.9200
// http://GOODS-APPLICATION/good/getGoods
uri = updateToSecureConnectionIfNeeded(original, ribbonServer);
}
return context.reconstructURIWithServer(server, uri);
}

    根据服务实例和URI原生地址为参数,并且调用 LoadBalancerContextreconstructURIWithServer进行地址解析替换得出最终的实际要访问的地址,也就是 http://GOODS-APPLICATION/good/getGoods​ 转换成http://192.168.2.100:9200/good/getGoods 这个样子

public URI reconstructURIWithServer(Server server, URI original) {
String host = server.getHost(); // 192.168.2.100
int port = server.getPort();// 9200
String scheme = server.getScheme();// null
if (host.equals(original.getHost())
&& port == original.getPort()
&& scheme == original.getScheme()) {
return original;
}
if (scheme == null) {
scheme = original.getScheme(); // http
}
if (scheme == null) {
scheme = deriveSchemeAndPortFromPartialUri(original).first();
}

StringBuilder sb = new StringBuilder();
sb.append(scheme).append("://");// http://
if (!Strings.isNullOrEmpty(original.getRawUserInfo())) {
sb.append(original.getRawUserInfo()).append("@");
}
sb.append(host);// http://192.168.2.100
if (port >= 0) {
sb.append(":").append(port);// 192.168.2.100:9200
}
// original.getRawPath() 是 /good/getGoods
sb.append(original.getRawPath());// http://192.168.2.100:9200/good/getGoods
if (!Strings.isNullOrEmpty(original.getRawQuery())) {
sb.append("?").append(original.getRawQuery());
}
if (!Strings.isNullOrEmpty(original.getRawFragment())) {
sb.append("#").append(original.getRawFragment());
}
// 创建新的URI
URI newURI = new URI(sb.toString());
// http://192.168.2.100:9200/good/getGoods
return newURI;

}

7. 进行 ClientHttpRequest 请求创建

    创建 SimpleBufferingClientHttpRequest 请求对象,进行后续的方法访问

public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
// sun.net.www.protocol.http.HttpURLConnection:http://192.168.2.100:9200/good/getGoods
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
// 创建请求对象
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
}

8. 进行网络访问

    delegate.execute(),其实最终走的是 SimpleBufferingClientHttpRequest 里面的 executeInternal 方法

@Override
protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException {
// 进行构造请求
addHeaders(this.connection, headers);
// 进行请求访问
this.connection.connect();
// 进行返回结果的封装
return new SimpleClientHttpResponse(this.connection);
}

9. 进行结果解析

    ResponseEntityResponseExtractor RestTemplate 内部的私有类,调用这个类的 extractData 方法进行最后的结果解析

@Override
public ResponseEntity<T> extractData(ClientHttpResponse response) throws IOException {
if (this.delegate != null) {
// 真正的方法请求返回结果解析
T body = this.delegate.extractData(response);
// 封装返回结果
return ResponseEntity.status(response.getRawStatusCode()).
headers(response.getHeaders()).body(body);
}
else {
return ResponseEntity.status(response.getRawStatusCode()).
headers(response.getHeaders()).build();
}
}

解析结果展示

图解+源码讲解 Ribbon 如何发起网络请求_Spring Cloud_09

10. 进行结果返回

@RestController
@RequestMapping("portal")
public class GoodsController {
private static final String GOODS_SERVICE_URL =
"http://GOODS-APPLICATION/good/getGoods";
@Autowired
private RestTemplate restTemplate;
@RequestMapping(value = "/getGoods",
produces = "application/json;charset=UTF-8")
public String getGoods() {
ResponseEntity<ResultObject> result = restTemplate
.getForEntity(GOODS_SERVICE_URL, ResultObject.class);
ResultObject resultBody = result.getBody();
System.out.println(resultBody.getData());
return resultBody.getStatusMessage();
}
}

输出结果

图解+源码讲解 Ribbon 如何发起网络请求_拦截器_10

返回结果

图解+源码讲解 Ribbon 如何发起网络请求_ide_11

小结

    说到底其实就是在 LoadBalancerAutoConfiguration 在进行初始化的时候已经给RestTemplate 请求的拦截器中设置好了他自己的拦截器,就是 LoadBalancerInterceptor ,所以后续的方法都是这个拦截器进行处理的,比如挑选负载均衡器,获取负载均衡器中的服务实例等,之后通过HttpURLConnection这个工具类进行的访问,将返回的结果通过RestTemplate 内部的一个类进行解析返回给调用者