Ribbon源碼解析

在上篇文章Ribbon架構(gòu)剖析中,我們已經(jīng)介紹了Ribbon的架構(gòu)組成以及很多重要的對象,相信你已經(jīng)對Ribbon已經(jīng)有一個清晰的認識了。本篇文章則研究一下Ribbon的原理

首先我們知道,在普通項目中Ribbon的使用是這樣的

@SpringBootApplication
@RibbonClient(name = "provider-demo", configuration = cn.org.config.LoadBalanced.class)
public class CloudDemoConsumerApplication {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(CloudDemoConsumerApplication.class, args);
    }
}

這里面最引人矚目的就是注解@RibbonClient了,看一下這個注解都是做了什么吧

@RibbonClient

觀察@RibbonClient的源碼可知,這個注解使用@Import注解引入了配置類RibbonClientConfigurationRegistrar,看一下這個類的registerBeanDefinitions方法

public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        Map<String, Object> attrs = metadata.getAnnotationAttributes(
                RibbonClients.class.getName(), true);
        if (attrs != null && attrs.containsKey("value")) {
            AnnotationAttributes[] clients = (AnnotationAttributes[]) attrs.get("value");
            for (AnnotationAttributes client : clients) {
                registerClientConfiguration(registry, getClientName(client),
                        client.get("configuration"));
            }
        }
        if (attrs != null && attrs.containsKey("defaultConfiguration")) {
            String name;
            if (metadata.hasEnclosingClass()) {
                name = "default." + metadata.getEnclosingClassName();
            } else {
                name = "default." + metadata.getClassName();
            }
            registerClientConfiguration(registry, name,
                    attrs.get("defaultConfiguration"));
        }
        Map<String, Object> client = metadata.getAnnotationAttributes(
                RibbonClient.class.getName(), true);
        String name = getClientName(client);
        if (name != null) {
            registerClientConfiguration(registry, name, client.get("configuration"));
        }
    }
  1. 首先會判斷是否存在注解@RibbonClients,注意,這里可是多了一個s的
  2. 然后判斷@RibbonClients注解上是否存在屬性valuedefaultConfiguration,如果存在的話分別注冊他們
  3. 接著最后才是處理@RibbonClient注解
  4. 這里我們就可以猜測RibbonClientConfigurationRegistrar這個類應(yīng)該是可以同時處理這兩個注解的,觀察一下@RibbonClients注解的源碼發(fā)現(xiàn)它確實是引入的也是這個類
  5. 這兩個注解的區(qū)別應(yīng)該也可以猜測出來,單數(shù)和雙數(shù)
  6. 觀察最后注冊的代碼,可以看到最后注冊bean的類型都是RibbonClientSpecification,這里留意一下
    private void registerClientConfiguration(BeanDefinitionRegistry registry,
            Object name, Object configuration) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(RibbonClientSpecification.class);
        builder.addConstructorArgValue(name);
        builder.addConstructorArgValue(configuration);
        registry.registerBeanDefinition(name + ".RibbonClientSpecification",
                builder.getBeanDefinition());
    }
自動裝配

上方看完這些代碼之后,我們了解了@RibbonClients@RibbonClient兩個注解,可以對整體的流程還是有些疑惑。那么接下來就看看自動裝配都是做了什么吧

查看Ribbon包下的spring.factories文件,發(fā)現(xiàn)引入了一個配置類RibbonAutoConfiguration,那么從這個類開始看起吧

先決條件
  1. @ConditionalOnClass,當前環(huán)境必須存在這幾個類: IClient, RestTemplate, AsyncRestTemplate, Ribbon
  2. @RibbonClients,這個注解剛才已經(jīng)講過了,暫且不提
  3. @AutoConfigureAfter,負載均衡肯定是要基于注冊中心來做的,所以自動裝配是在Eureka初始化完畢之后初始化的
  4. @AutoConfigureBefore,這里的兩個類先不說,保持神秘
  5. @EnableConfigurationProperties,兩個配置類,其中:
    1. RibbonEagerLoadProperties類中是關(guān)于Ribbon的饑餓加載模式的屬性
    2. ServerIntrospectorProperties類中是關(guān)于安全端口的屬性
裝配bean

這個配置類加載的類挺多的,但是比較重要的有這幾個:

  1. SpringClientFactory,我們知道每一個微服務(wù)在都會調(diào)用多個微服務(wù),而調(diào)用各個微服務(wù)的配置可能是不一樣的,所以就需要這個創(chuàng)建客戶端負載均衡器的工廠類,它可以為每一個ribbon客戶端生成不同的Spring上下文,而觀察這個類的configurations屬性也驗證了這一點
@Autowired(required = false)
    private List<RibbonClientSpecification> configurations = new ArrayList<>();
    @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
    }
  1. RibbonLoadBalancerClient,持有SpringClientFactory對象,當然,它還有其他的功能,這里暫且不提
負載均衡

上方雖然看了Ribbon的自動裝配功能,但是好像離真相還有一些距離,這是因為雖然Ribbon準備好了,但是負載均衡還沒看呢。SpringCloud把負載均衡相關(guān)的自動配置放在了spring-cloud-commons包下
負載均衡的配置類是LoadBalancerAutoConfiguration

這個類里注冊的幾個bean就比較核心了

LoadBalancerInterceptor

客戶端請求攔截器

RestTemplateCustomizer

用于給所有的RestTemplate增加攔截器

@Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer restTemplateCustomizer(
                final LoadBalancerInterceptor loadBalancerInterceptor) {
            return restTemplate -> {
                List<ClientHttpRequestInterceptor> list = new ArrayList<>(
                        restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
            };
        }
負載均衡核心實現(xiàn)

現(xiàn)在我們就可以猜測,整個核心應(yīng)該就是在這個攔截器上了,看一看攔截器的核心方法:

    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
        return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
    }

其中requestFactory.createRequest(request, body, execution)方法是為了把請求參數(shù)封裝為request
重點關(guān)注execute方法

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer);
        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);
    }
創(chuàng)建負載均衡器

我們知道,每個Ribbon客戶端的負載均衡器都是唯一的,第一行getLoadBalancer就會去創(chuàng)建這個負載均衡器

   protected ILoadBalancer getLoadBalancer(String serviceId) {
        return this.clientFactory.getLoadBalancer(serviceId);
    }
   public ILoadBalancer getLoadBalancer(String name) {
        return getInstance(name, ILoadBalancer.class);
    }
    public <C> C getInstance(String name, Class<C> type) {
        C instance = super.getInstance(name, type);
        if (instance != null) {
            return instance;
        }
        IClientConfig config = getInstance(name, IClientConfig.class);
        return instantiateWithConfig(getContext(name), type, config);
    }

最后的邏輯是如果存在緩存則從緩存中獲取,如果不存在創(chuàng)建

static <C> C instantiateWithConfig(AnnotationConfigApplicationContext context,
                                        Class<C> clazz, IClientConfig config) {
        C result = null;
        
        try {
            Constructor<C> constructor = clazz.getConstructor(IClientConfig.class);
            result = constructor.newInstance(config);
        } catch (Throwable e) {
            // Ignored
        }
        
        if (result == null) {
            result = BeanUtils.instantiate(clazz);
            
            if (result instanceof IClientConfigAware) {
                ((IClientConfigAware) result).initWithNiwsConfig(config);
            }
            
            if (context != null) {
                context.getAutowireCapableBeanFactory().autowireBean(result);
            }
        }
        
        return result;
    }

創(chuàng)建的大題流程則就是通過文章開始提到的兩個注解注冊的幾個RibbonClientSpecification類型的配置來創(chuàng)建

獲取服務(wù)

getServer方法的實現(xiàn)應(yīng)該可以猜出來,使用具體的負載均衡器結(jié)合相應(yīng)的負載均衡算法再加上服務(wù)列表過濾、服務(wù)健康檢測等操作最后會獲取的一個可用服務(wù)

調(diào)用服務(wù)

這里在調(diào)用之前把服務(wù)封裝成了RibbonServer

        private final String serviceId;
        private final Server server;
        private final boolean secure;
        private Map<String, String> metadata;

除了這幾個屬性外,RibbonServer還有一個方法

public URI getUri() {
            return DefaultServiceInstance.getUri(this);
        }

這個方法就把服務(wù)從實例id轉(zhuǎn)化為一個可調(diào)用的url了

    public static URI getUri(ServiceInstance instance) {
        String scheme = (instance.isSecure()) ? "https" : "http";
        String uri = String.format("%s://%s:%s", scheme, instance.getHost(),
                instance.getPort());
        return URI.create(uri);
    }

然后就是發(fā)送http請求

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容