Ribbon源码解析

2,321 阅读5分钟

在上篇文章Ribbon架构剖析中,我们已经介绍了Ribbon的架构组成以及很多重要的对象,相信你已经对Ribbon已经有一个清晰的认识了。本篇文章则研究一下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这个类应该是可以同时处理这两个注解的,观察一下@RibbonClients注解的源码发现它确实是引入的也是这个类
  5. 这两个注解的区别应该也可以猜测出来,单数和双数
  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文件,发现引入了一个配置类RibbonAutoConfiguration,那么从这个类开始看起吧

先决条件

  1. @ConditionalOnClass,当前环境必须存在这几个类: IClient, RestTemplate, AsyncRestTemplate, Ribbon
  2. @RibbonClients,这个注解刚才已经讲过了,暂且不提
  3. @AutoConfigureAfter,负载均衡肯定是要基于注册中心来做的,所以自动装配是在Eureka初始化完毕之后初始化的
  4. @AutoConfigureBefore,这里的两个类先不说,保持神秘
  5. @EnableConfigurationProperties,两个配置类,其中:
    1. RibbonEagerLoadProperties类中是关于Ribbon的饥饿加载模式的属性
    2. ServerIntrospectorProperties类中是关于安全端口的属性
装配bean

这个配置类加载的类挺多的,但是比较重要的有这几个:

  1. SpringClientFactory,我们知道每一个微服务在都会调用多个微服务,而调用各个微服务的配置可能是不一样的,所以就需要这个创建客户端负载均衡器的工厂类,它可以为每一个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把负载均衡相关的自动配置放在了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);
            };
        }

负载均衡核心实现

现在我们就可以猜测,整个核心应该就是在这个拦截器上了,看一看拦截器的核心方法:

    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)方法是为了把请求参数封装为request重点关注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);
    }
创建负载均衡器

我们知道,每个Ribbon客户端的负载均衡器都是唯一的,第一行getLoadBalancer就会去创建这个负载均衡器

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

最后的逻辑是如果存在缓存则从缓存中获取,如果不存在创建

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

创建的大题流程则就是通过文章开始提到的两个注解注册的几个RibbonClientSpecification类型的配置来创建

获取服务

getServer方法的实现应该可以猜出来,使用具体的负载均衡器结合相应的负载均衡算法再加上服务列表过滤、服务健康检测等操作最后会获取的一个可用服务

调用服务

这里在调用之前把服务封装成了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);
        }

这个方法就把服务从实例id转化为一个可调用的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);
    }

然后就是发送http请求

1