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

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第15天,点击查看活动详情

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

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

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

从哪里进行分析

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

简图

源码概览图

image.png

源码分析

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();
    }
}
复制代码

注册中心服务列表详情

image.png
     调用的是 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);
    };
}
复制代码

image.png
    所以此时走的拦截器方法就是 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));
}
复制代码

image.png
    这里调用的是 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);
}
复制代码

image.png

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

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

image.png

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 封装了解析地址的方法,这个方法也是在拦截器执行的过程中创建的
image.pngimage.png
    解析地址的方法其实是调用的 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();
    }
}
复制代码

解析结果展示

image.png

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();
    }
}
复制代码

输出结果

image.png

返回结果

image.png

小结

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

猜你喜欢

转载自juejin.im/post/7086632160785858596
今日推荐