Ribbon工作流程细节

起因是ribbon集成spring boot、openfeign实现负载均衡远程调用,初始阶段没有添加下面配置,发现第一次进行远程调用,ribbon报错 【read time out】。然后添加如下配置,解决问题~

# 设置ribbon 项目启动时加载配置项,避免feign第一次调用【read time out】
ribbon:
  eager-load:
    enabled: true
    clients: api-service

上面的配置,从字面意思看出来,让Ribbon及时加载,那么问题来了?应用启动时,ribbon是怎么起作用的呢?ok,继续往下看!

@ConfigurationProperties(prefix = "ribbon.eager-load")
public class RibbonEagerLoadProperties {
         private boolean enabled = false;

    private List clients;
}

看出来了吧,是org.springframework.cloud.netflix.ribbon.RibbonEagerLoadProperties 起作用了。那么这个类是如何被唤起了呢?

@EnableConfigurationProperties({ RibbonEagerLoadProperties.class,
        ServerIntrospectorProperties.class })
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
        havingValue = "true", matchIfMissing = true)
public class RibbonAutoConfiguration {

    @Autowired(required = false)
    private List configurations = new ArrayList<>();

    @Autowired
    private RibbonEagerLoadProperties ribbonEagerLoadProperties;

    @Bean
    @ConditionalOnProperty("ribbon.eager-load.enabled")
    public RibbonApplicationContextInitializer ribbonApplicationContextInitializer() {
        return new RibbonApplicationContextInitializer(springClientFactory(),
                ribbonEagerLoadProperties.getClients());
    }
}

这个类在spring-cloud-netflix包中,ribbonEagerLoadProperties被注入进来,然后在
ribbonApplicationContextInitializer()中声明RibbonApplicationContextInitializer类,它继承了ApplicationListener,在实现方法onApplicationEvent()中调用initialize(),从而把我们的配置的目标clients(也就是"api-service")加载到org.springframework.cloud.context.named.NamedContextFactory的contexts中。下面是具体的调用链:

Ribbon工作流程细节_第1张图片
图1

public void onApplicationEvent(ApplicationReadyEvent event) {
        if (clientNames != null) {
            for (String clientName : clientNames) {
                this.springClientFactory.getContext(clientName);
            }
        }
    }

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);
    }

下面的图是NamedContextFactory中存储的AnnotationConfigApplicationContext实例:
Ribbon工作流程细节_第2张图片
图2

下面我们调用远程服务的checkToken(),下面的图很好得反应了具体调用栈信息:
Ribbon工作流程细节_第3张图片
图3

到此为止,Robbin的工作方式已经大概了解,后续会继续更新Robbin的更多细节,欢迎留言沟通~

下面的部分,可以继续探索feign执行细节
org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute()。

public Response execute(Request request, Request.Options options) throws IOException {
        try {
            URI asUri = URI.create(request.url());
            String clientName = asUri.getHost();
            URI uriWithoutHost = cleanUrl(request.url(), clientName);
            FeignLoadBalancer.RibbonRequest ribbonRequest = new FeignLoadBalancer.RibbonRequest(
                    this.delegate, request, uriWithoutHost);

            IClientConfig requestConfig = getClientConfig(options, clientName);
            //这里根据clientName执行负载均衡逻辑,并根据url发送http 请求
            return lbClient(clientName)
                    .executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse();
        }
        catch (ClientException e) {
            IOException io = findIOException(e);
            if (io != null) {
                throw io;
            }
            throw new RuntimeException(e);
        }
    }

feign的调用链如下:
image.png

image.png

image.png

image.png

其中,Client.execute()细节:

 public Response execute(Request request, Options options) throws IOException {
      HttpURLConnection connection = convertAndSend(request, options);
      return convertResponse(connection, request);
    }

public HttpURLConnection getConnection(final URL url) throws IOException {
      //还有这里^ _ ^
      return (HttpURLConnection) url.openConnection();
    }

HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
      final URL url = new URL(request.url());
      //重点在这里^ - ^
      final HttpURLConnection connection = this.getConnection(url);
      if (connection instanceof HttpsURLConnection) {
        HttpsURLConnection sslCon = (HttpsURLConnection) connection;
        if (sslContextFactory != null) {
          sslCon.setSSLSocketFactory(sslContextFactory);
        }
        if (hostnameVerifier != null) {
          sslCon.setHostnameVerifier(hostnameVerifier);
        }
      }
}

看到了没有,最终用jdk的HttpURLConnection 执行http请求,并获取执行结果:
Ribbon工作流程细节_第4张图片

你可能感兴趣的:(Ribbon工作流程细节)