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,这里留意一下

 
 
自动装配

上方看完这些代码之后,我们了解了@RibbonClients@RibbonClient两个注解,可以对整体的流程还是有些疑惑。那么接下来就看看自动装配都是做了什么吧

查看Ribbon包下的spring.factories文件,发现引入了一个配置类RibbonAutoConfiguration,那么从这个类开始看起吧

先决条件
  1. @ConditionalOnClass,当前环境必须存在这几个类: IClientRestTemplateAsyncRestTemplateRibbon

  2. @RibbonClients,这个注解刚才已经讲过了,暂且不提

  3. @AutoConfigureAfter,负载均衡肯定是要基于注册中心来做的,所以自动装配是在Eureka初始化完毕之后初始化的

  4. @AutoConfigureBefore,这里的两个类先不说,保持神秘

  5. @EnableConfigurationProperties,两个配置类,其中:

    1. RibbonEagerLoadProperties类中是关于Ribbon的饥饿加载模式的属性

    2. ServerIntrospectorProperties类中是关于安全端口的属性

装配bean

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

  1. SpringClientFactory,我们知道每一个微服务在都会调用多个微服务,而调用各个微服务的配置可能是不一样的,所以就需要这个创建客户端负载均衡器的工厂类,它可以为每一个ribbon客户端生成不同的Spring上下文,而观察这个类的configurations属性也验证了这一点

  2. @Autowired(required = false)	
     private List<RibbonClientSpecification> configurations = new ArrayList<>();	
     @Bean	
     public SpringClientFactory springClientFactory() {	
         SpringClientFactory factory = new SpringClientFactory();	
         factory.setConfigurations(this.configurations);	
         return factory;	
     }
  3. 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请求

往期好文

640?wx_fmt=png

如果文章对您有所帮助,收藏、转发、在看安排一下!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值