美文网首页
Ribbon及Feign源码剖析

Ribbon及Feign源码剖析

作者: 王侦 | 来源:发表于2023-03-06 07:55 被阅读0次

1.Ribbon源码

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    RestTemplate restTemplate;

    @RequestMapping("/add")
    public String add() {
        System.out.println("下单成功!");
        String result = restTemplate.getForObject("http://stock-service/stock/deduct", String.class);

        return "下单成功!" + result;
    }
}

1.1 RibbonAutoConfiguration

spring-cloud-netflix-ribbon-2.2.9.RELEASE.jar,spring.factories中的自动配置类:
RibbonAutoConfiguration,这个在LoadBalancerAutoConfiguration前执行,引入的核心类:

  • SpringClientFactory是个ApplicationContextAware,包含每个RPC服务端对应的Spring容器,并且会找出RibbonClientConfiguration
  • RibbonLoadBalancerClient,其clientFactory是SpringClientFactory

1.2 LoadBalancerAutoConfiguration

spring-cloud-commons-2.2.9.RELEASE.jar,spring.factories中的自动配置类:LoadBalancerAutoConfiguration引入几个核心类:

  • LoadBalancerInterceptor,负载均衡拦截器,入口逻辑在这
  • RestTemplateCustomizer,将上面的拦截器加入到RestTemplate的interceptors中
  • SmartInitializingSingleton,对所有RestTemplate调用RestTemplateCustomizer,也即将所有RestTemplate的拦截器加上LoadBalancerInterceptor
  • LoadBalancerRequestFactory,包含属性loadBalancer:LoadBalancerClient(这个就是spring-cloud-netflix-ribbon-2.2.9.RELEASE.jar引入的RibbonLoadBalancerClient)

1.3 SpringClientFactory创建各RPC服务自己的Spring容器

容器启动过程中:

  • SpringApplication#run
  • 最后listeners.running(context);发布ApplicationReadyEvent事件
  • RibbonApplicationContextInitializer#onApplicationEvent处理该事件
  • RibbonApplicationContextInitializer#initialize
  • 调用this.springClientFactory.getContext(clientName);这里clientName就是RPC远程调用的服务名stock-service
  • SpringClientFactory#getContext
  • this.contexts.put(name, createContext(name));
  • NamedContextFactory#createContext,在这里会创建一个AnnotationConfigApplicationContext容器,并且进行刷新,然后放到contexts的hashmap中,也即一个远程服务对应一个容器
  • context.register(RibbonClientConfiguration.class)
  • 在RibbonClientConfiguration就定义了ZoneAwareLoadBalancer
  • 并且默认情况HttpClient用的是HttpClientRibbonConfiguration,在这里面定义的是RibbonLoadBalancingHttpClient
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
// Order is important here, last should be the default, first should be optional
// see
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
@Import({ HttpClientConfiguration.class, OkHttpRibbonConfiguration.class,
        RestClientRibbonConfiguration.class, HttpClientRibbonConfiguration.class })
public class RibbonClientConfiguration {

1.4 负载均衡器执行流程

执行流程:

  • RestTemplate.getForObject()
  • InterceptingClientHttpRequest.InterceptingRequestExecution#execute这里会先遍历执行Interceptor#intercept,然后再执行RPC调用
  • LoadBalancerInterceptor#intercept
  • RibbonLoadBalancerClient#execute
  • 1)SpringClientFactory#getLoadBalancer获取到的是ZoneAwareLoadBalancer
  • 2)ZoneAwareLoadBalancer#chooseServer
    父类:BaseLoadBalancer#chooseServer;
    rule.choose(key);默认是ZoneAvoidanceRule;
    这里首先会获取ZoneAwareLoadBalancer的allServerList(Nacos整合关键点);
    然后调用轮询算法从中获取一个。
  • 3)execute()
    LoadBalancerRequestFactory#createRequest中的request执行;
    回到InterceptingClientHttpRequest.InterceptingRequestExecution#execute;
    HttpComponentsClientHttpRequestFactory#createRequest;
    HttpComponentsClientHttpRequest#execute;
    AbstractClientHttpRequest#execute;
    this.httpClient.execute;

RibbonLoadBalancerClient#execute()

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
            throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }
        RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));

        return execute(serviceId, ribbonServer, request);
    }

1.5 Ribbon与Nacos client的整合

ZoneAwareLoadBalancer构造方法:

  • DynamicServerListLoadBalancer#restOfInit
  • enableAndInitLearnNewServersFeature();定时执行updateListOfServers(); 30s执行一次。
  • updateListOfServers();
  • NacosServerList#getUpdatedListOfServers
  • NacosNamingService#selectInstances()
    clientProxy.subscribe()
    clientProxy.queryInstancesOfService()
  • DynamicServerListLoadBalancer#updateAllServerList
  • 设置到负载均衡器的allServerList里面

2.OpenFeign源码剖析

2.1 注解@EnableFeignClients

@EnableFeignClients

  • @Import(FeignClientsRegistrar.class)
  • FeignClientsRegistrar#registerBeanDefinitions
  • FeignClientsRegistrar#registerFeignClients
    找出所有@FeignClient注解的类;
    beanDefinition的InstanceSupplier里面设置为FeignClientFactoryBean#getObject。

2.2 FeignClientFactoryBean创建动态代理

重点看FeignClientFactoryBean#getObject

  • FeignClientFactoryBean#getTarget
  • 从容器获取FeignContext context =
    beanFactory.getBean(FeignContext.class)
  • Feign.Builder builder = feign(context);
  • 拼接url为:http://stock-service/stock
  • FeignClientFactoryBean#loadBalance
    (T) loadBalance(builder, context,new HardCodedTarget<>(type, name, url)); 这里type是StockService,name是stock-service,url是http://stock-service/stock
  • Client client = getOptional(context, Client.class);从FeignContext获取Client,实际为LoadBalancerFeignClient
  • Targeter targeter = get(context, Targeter.class);从FeignContext获取Targeter,实际为HystrixTargeter
  • HystrixTargeter#target
  • Feign.Builder#target(feign.Target<T>)
  • ReflectiveFeign#newInstance
  • 创建动态代理,核心的handler是ReflectiveFeign.FeignInvocationHandler
    <T> T getTarget() {
        FeignContext context = beanFactory != null
                ? beanFactory.getBean(FeignContext.class)
                : applicationContext.getBean(FeignContext.class);
        Feign.Builder builder = feign(context);

        if (!StringUtils.hasText(url)) {

            if (LOG.isInfoEnabled()) {
                LOG.info("For '" + name
                        + "' URL not provided. Will try picking an instance via load-balancing.");
            }
            if (!name.startsWith("http")) {
                url = "http://" + name;
            }
            else {
                url = name;
            }
            url += cleanPath();
            return (T) loadBalance(builder, context,
                    new HardCodedTarget<>(type, name, url));
        }
        if (StringUtils.hasText(url) && !url.startsWith("http")) {
            url = "http://" + url;
        }
        String url = this.url + cleanPath();
        Client client = getOptional(context, Client.class);
        if (client != null) {
            if (client instanceof LoadBalancerFeignClient) {
                // not load balancing because we have a url,
                // but ribbon is on the classpath, so unwrap
                client = ((LoadBalancerFeignClient) client).getDelegate();
            }
            if (client instanceof FeignBlockingLoadBalancerClient) {
                // not load balancing because we have a url,
                // but Spring Cloud LoadBalancer is on the classpath, so unwrap
                client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
            }
            if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
                // not load balancing because we have a url,
                // but Spring Cloud LoadBalancer is on the classpath, so unwrap
                client = ((RetryableFeignBlockingLoadBalancerClient) client)
                        .getDelegate();
            }
            builder.client(client);
        }
        Targeter targeter = get(context, Targeter.class);
        return (T) targeter.target(this, builder, context,
                new HardCodedTarget<>(type, name, url));
    }

2.3 代理对象执行过程

执行调用过程ReflectiveFeign.FeignInvocationHandler#invoke

  • dispatch.get(method).invoke(args);这里dispatch是Method到MethodHandler的映射
  • SynchronousMethodHandler#invoke
  • Request request = targetRequest(template);创建请求 GET http://stock-service/stock/deduct HTTP/1.1
  • LoadBalancerFeignClient#execute (核心)
  • lbClient(clientName).executeWithLoadBalancer(ribbonRequest, requestConfig).toResponse(); 这里clientName是stock-service,ribbonRequest里面有uriWithoutHost:http:///stock/deduct
  • LoadBalancerFeignClient#lbClient通过CachingSpringLoadBalancerFactory创建FeignLoadBalancer
  • FeignLoadBalancer#executeWithLoadBalancer
  • 父类AbstractLoadBalancerAwareClient#executeWithLoadBalancer
  • LoadBalancerCommand#submit
  • LoadBalancerCommand#selectServer
  • LoadBalancerContext#getServerFromLoadBalancer,这里实际是FeignLoadBalancer
  • 获取ILoadBalancer是ZoneAwareLoadBalancer
  • 调用ZoneAwareLoadBalancer#chooseServer负载均衡选择一个服务器,例如192.168.2.2.8021
  • LoadBalancerContext#reconstructURIWithServer构建finalUri为http://192.168.2.2:8021/stock/deduct
  • FeignLoadBalancer#execute
  • Client.Default#execute

2.4 Feign如何整合Ribbon

spring-cloud-openfeign-core-2.2.9.RELEASE.jar
spring.factories中有自动配置类FeignRibbonClientAutoConfiguration

@Import({ HttpClientFeignLoadBalancedConfiguration.class,
        OkHttpFeignLoadBalancedConfiguration.class,
        HttpClient5FeignLoadBalancedConfiguration.class,
        DefaultFeignLoadBalancedConfiguration.class })
public class FeignRibbonClientAutoConfiguration {

该自动配置类引入了两个重要类:

  • 1)CachingSpringLoadBalancerFactory,入参是SpringClientFactory
  • 2) DefaultFeignLoadBalancedConfiguration引入了LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,clientFactory);

重点看一下这里的SpringClientFactory

  • 这里还是上面Ribbon为每个RPC服务生成的子容器,里面都是该RPC服务Ribbon相关的类

那负载均衡是哪里来的?

  • 是从FeignLoadBalancer中获取负载均衡器的lb
  • FeignLoadBalancer的lb是从SpringClientFactory中获取到
  • SpringClientFactory是Ribbon生成的,也即拿到的就是ZoneAwareLoadBalancer

相关文章

网友评论

      本文标题:Ribbon及Feign源码剖析

      本文链接:https://www.haomeiwen.com/subject/zqbuldtx.html