定制化开发Ribbon源码深度刨析-(5)ServiceRequestWrapper

“不积跬步,定制化开发无以至千里。”

上文写到,定制化开发通过其内置的IRule组件,定制化开发使用指定的负载均衡算法(默认轮询)从ILoadBalancer组件的server list定制化开发中会拿到一个真正要发送请求的server地址,定制化开发那么接下来,定制化开发就会调用网络通信组件发起http请求了。

@Overridepublic <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);        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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);

定制化开发之前提到过,ribbon定制化开发的设计理念,定制化开发每个下游被调服务,对应一个spring容器(ApplicationContext)

这行代码,定制化开发其实就是首先根据serviceId定制化开发拿到这个服务对应的spring容器,再从其对应的spring容器中获取一个ribbon的上下文(RibbonLoadBalancerContext )这个bean

protected AnnotationConfigApplicationContext getContext(String name) {    if (!this.contexts.containsKey(name)) {        synchronized (this.contexts) {            if (!this.contexts.containsKey(name)) {                this.contexts.put(name, createContext(name));            }        }    }    return this.contexts.get(name);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

一个map中获取一个AnnotationConfigApplicationContext(spring容器),这个name就是服务名称,这个也验证的我们的结论

至于从容器中获取 RibbonLoadBalancerContext 这个bean的细节就不展开了,那个要追到spring的源码细节,不在这个专题的研究范围之内,不过spring作为一个十分优秀的开源框架,后面我会用极其庞大的一个专题来深入刨析一下,也敬请期待。

我们继续看execute的流程

T returnVal = request.apply(serviceInstance);

这是关键的一行代码,调用了一个request的apply方法,直接拿到了返回值,所以这个apply方法应该就是入口了

而这个request是通过参数传递的,LoadBalancerRequest

那么这个requet的方法,源头又是在哪里?

还记得吗?LoadBalancerInterceptor!!!

requestFactory.createRequest(request, body, execution)

就是这行代码创建了一个 LoadBalancerRequest

public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request,                                                             final byte[] body, final ClientHttpRequestExecution execution) {    return instance -> {        HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);        if (transformers != null) {            for (LoadBalancerRequestTransformer transformer : transformers) {                serviceRequest = transformer.transformRequest(serviceRequest, instance);            }        }        return execution.execute(serviceRequest, body);    };}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

这个lambda表达式就是apply方法,也就是说前面说的 request.apply(serviceInstance) 最终就会调用到这个lambda里来

HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance, loadBalancer);

我告诉你,这行代码,是一个核心逻辑,它主要的工作是干嘛?

重写获取URI逻辑。

@Overridepublic URI getURI() {    URI uri = this.loadBalancer.reconstructURI(        this.instance, getRequest().getURI());    return uri;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

因为后面会调用ClientHttpRequestExecution去发送http请求,拿到最终的响应,这个就是spring原生的http网络组件,这个组件里面一定会去调用getURI()拿到一个请求地址

而你传进来的是类似: http://springboot-project-2/test_1/zhangopop

spring组件拿到这个地址是没办法处理的,它需要是ip,主机和port端口号

是类似这种:http://localhost:10001/test_1/zhangopop

所以就需要这个ServiceRequestWrapper 包装类,去包装原生的request,来重写getURI()方法,把服务名称,变成实际发送的ip和port

然后将这个ServiceRequestWrappter交给了ClientHttpRequestExecution

execution.execute(serviceRequest, body);

再往下的话呢,其实就是走到spring-web的源码里去了,其实就是说是spring-web下的负责底层的http请求的组件,从 ServiceRequestWrapper 中获取出来了对应的真正的请求URL地址,然后发起了一次请求

ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);

这就是 ClientHttpRequestExecution 的execute最终构建请求的一行代码

所以,奥秘,不在于说spring-web的底层的http的源码

可以来看看,ServiceRequestWrappter是怎么重写了getURI的方法

@Overridepublic URI reconstructURI(ServiceInstance instance, URI original) {    Assert.notNull(instance, "instance can not be null");    String serviceId = instance.getServiceId();    RibbonLoadBalancerContext context = this.clientFactory        .getLoadBalancerContext(serviceId);    URI uri;    Server server;    if (instance instanceof RibbonServer) {        RibbonServer ribbonServer = (RibbonServer) instance;        server = ribbonServer.getServer();        uri = updateToSecureConnectionIfNeeded(original, ribbonServer);    } else {        server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());        IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);        ServerIntrospector serverIntrospector = serverIntrospector(serviceId);        uri = updateToSecureConnectionIfNeeded(original, clientConfig,                                               serverIntrospector, server);    }    return context.reconstructURIWithServer(server, uri);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

ServiceRequestWrapper的getURI()方法里面,调用了RibbonLoadBalancerClient的reconstructURI()方法,基于选择出来的server的地址,重构了请求URI

这个参数ServiceInstance instance,就是之前负载均衡从server list中选择出来的一个server

context.reconstructURIWithServer(server, uri); 然后再这个方法里,完成host和port的替换

http://springboot-project-2/test_1/zhangopop

替换为http://localhost:10001/test_1/zhangopop

就不进去细看了,一大坨琐碎的代码,StringBuilder操作,没什么营养,体力活

ok,总结一下,ribbon底层发送http使用的是spring原生的网络组件ClientHttpRequestExecution,只不过对原生的request进行了封装ServiceRequestWrapper,重写了getURI()方法,主要是把http uri中的服务名称替换为实际要发送的ip和port。

到此为止,ribbon这个客户端的负载均衡组件已经把核心源码讲完了,基本都是大白话的细致讲解,你不可能看不懂,把我这套看懂之后,然后自己画画图,出去面试,简历上写上深入研究过核心源码,基本上问题不大,可能有些面试官他自己也没看过几个源码,也问不出个啥,看源码的能力,核心竞争力,需要持续的培养,自己逼着自己看,前期会很痛苦,抓大放小,看不懂,过!把握核心流程,一些细节性的东西,需要用的时候再细看。

本人后续会刨析大量优秀开源框架核心源码,feign、hystrix、byteTcc、jta、spring、redisson等等,甚至大数据领域的hadoop、spark、es、zookeeper、flink、kafka等等,这些我都会在后面把核心的东西呈现出来,用大白话的形式让每个人看懂,源码路上,带你上道,反正我现在是停不下来了。

话不多说,下一个源码专题,让我们进入feign。

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发