为什么一个@LoadBalanced注解就可以实现RestTem

2020-08-31  本文已影响0人  凡毓不凡
  • SpringCloud 版本Hoxton.SR1
  • SpringBoot 版本2.2.1.RELEASE
  • 本文适用于对SpringBoot有一定基础的人,主要讲解RestTemplate的工作过程。讲解方式:场景驱动
  • 关键词 :RestTemplate 配置使用演示及代码示例、负载均衡过程源码分析

1. RestTemplate

1.1 使用RestTemplate实现负载均衡的先决条件

准备工作:一个Eureka消费者,两个Eureka提供者,两个Eureka服务器做服务端集群

1.1.1 Maven中引入如下依赖,版本自适应( 此依赖包中默认包含RestTemplate核心模块调用类 )
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
1.1.2 创建RestTemplate模板调用类,创建的方式多种多样,常用的就是基于@Configuration + @Bean的方式 图1 配置负载均衡客户端RestTemplate
图3 服务器2
1.2 如何使用RestTemplate ?
1.2.1 前面我们也说了,RestTemplate中提供了若干个调用方法,使用者可以根据自己的需求选择性调用,此处作者写了个简单的Web接口,接口中调用RestTemplate的getForObject方法,需要注意的是:要想实现客户端的负载均衡,调用的URL中要使用应用名spring.application.name对应的值。如图: 图4 comsumer中提供的接口
1.2.2 而图1(作者暂时未找到在简书中如何实现页内跳转,此处还请读者向上翻到图1,若是有读者知道烦请评论区留言哦~)中的配置,我们观察两个RestTemplate模板类的唯一不同之处就是第一个创建过程多了一个@LoadBalanced注解,此时我们可以得出结论就是带有@LoadBalanced注解的RestTemplate具有负载均衡的能力,得出这个结论之后实际开发上已经够用了,但是为了做到 知根知底,我们将剖析负载均衡背后的原理!

代码下载地址:https://github.com/LiujunjieALiling/spring-cloud-netflix.git

1.2.3 在此,我们先回答上一篇中的问题1: 为什么只会注入带有@LoadBalanced注解的RestTemplate实例,而普通的RestTemplate不会注入进去?
    @Override
    public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
        boolean match = super.isAutowireCandidate(bdHolder, descriptor);
        if (match) {
            // 校验依赖得属性中得注解与当前bean定义中是否匹配(很关键得步骤)
            match = checkQualifiers(bdHolder, descriptor.getAnnotations());
            if (match) {
                MethodParameter methodParam = descriptor.getMethodParameter();
                if (methodParam != null) {
                    Method method = methodParam.getMethod();
                    if (method == null || void.class == method.getReturnType()) {
                        match = checkQualifiers(bdHolder, methodParam.getMethodAnnotations());
                    }
                }
            }
        }
        return match;
    }

    /**
     * Match the given qualifier annotations against the candidate bean definition.
     */
    protected boolean checkQualifiers(BeanDefinitionHolder bdHolder, Annotation[] annotationsToSearch) {
        if (ObjectUtils.isEmpty(annotationsToSearch)) {
            return true;
        }
        SimpleTypeConverter typeConverter = new SimpleTypeConverter();
        for (Annotation annotation : annotationsToSearch) {
            Class<? extends Annotation> type = annotation.annotationType();
            boolean checkMeta = true;
            boolean fallbackToMeta = false;
            if (isQualifier(type)) {
                if (!checkQualifier(bdHolder, annotation, typeConverter)) {
                    fallbackToMeta = true;
                }
                else {
                    checkMeta = false;
                }
            }
            if (checkMeta) {
                boolean foundMeta = false;
                for (Annotation metaAnn : type.getAnnotations()) {
                    Class<? extends Annotation> metaType = metaAnn.annotationType();
                    if (isQualifier(metaType)) {
                        foundMeta = true;
                        // Only accept fallback match if @Qualifier annotation has a value...
                        // Otherwise it is just a marker for a custom qualifier annotation.
                        if ((fallbackToMeta && StringUtils.isEmpty(AnnotationUtils.getValue(metaAnn))) ||
                                !checkQualifier(bdHolder, metaAnn, typeConverter)) {
                            return false;
                        }
                    }
                }
                if (fallbackToMeta && !foundMeta) {
                    return false;
                }
            }
        }
        return true;
    }
    /**
     * Match the given qualifier annotation against the candidate bean definition.
     */
    protected boolean checkQualifier(
            BeanDefinitionHolder bdHolder, Annotation annotation, TypeConverter typeConverter) {

        Class<? extends Annotation> type = annotation.annotationType();
        RootBeanDefinition bd = (RootBeanDefinition) bdHolder.getBeanDefinition();
        // 此处我们没有对bean定义指定解析器,所以获取得为空
        AutowireCandidateQualifier qualifier = bd.getQualifier(type.getName());
        if (qualifier == null) {
            // 同理,也为空
            qualifier = bd.getQualifier(ClassUtils.getShortName(type));
        }
        if (qualifier == null) {
            //  正常情况下创建得bean定义也没有设置此属性,所以返回也为null
            Annotation targetAnnotation = getQualifiedElementAnnotation(bd, type);
            // Then, check annotation on factory method, if applicable
            if (targetAnnotation == null) {
                // 此处针对于@Bean 方式创建得bean会设置一个FactoryMethod属性,此属性保存得就是配置类中得创建方法,
                // 此处根据type(@Qualifier)获取到得目标注解就是@LoadBalanced
                targetAnnotation = getFactoryMethodAnnotation(bd, type);
            }
            if (targetAnnotation == null) {
                RootBeanDefinition dbd = getResolvedDecoratedDefinition(bd);
                if (dbd != null) {
                    targetAnnotation = getFactoryMethodAnnotation(dbd, type);
                }
            }
            if (targetAnnotation == null) {
                // Look for matching annotation on the target class
                if (getBeanFactory() != null) {
                    try {
                        Class<?> beanType = getBeanFactory().getType(bdHolder.getBeanName());
                        if (beanType != null) {
                            targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(beanType), type);
                        }
                    }
                    catch (NoSuchBeanDefinitionException ex) {
                        // Not the usual case - simply forget about the type check...
                    }
                }
                if (targetAnnotation == null && bd.hasBeanClass()) {
                    targetAnnotation = AnnotationUtils.getAnnotation(ClassUtils.getUserClass(bd.getBeanClass()), type);
                }
            }
            //  此处获取得targetAnnotation注解为@LoadBalanced,并且annotation也是,则会直接返回匹配成功,注入属性
            if (targetAnnotation != null && targetAnnotation.equals(annotation)) {
                return true;
            }
        }

        Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(annotation);
        if (attributes.isEmpty() && qualifier == null) {
            // If no attributes, the qualifier must be present
            return false;
        }
        for (Map.Entry<String, Object> entry : attributes.entrySet()) {
            String attributeName = entry.getKey();
            Object expectedValue = entry.getValue();
            Object actualValue = null;
            // Check qualifier first
            if (qualifier != null) {
                actualValue = qualifier.getAttribute(attributeName);
            }
            if (actualValue == null) {
                // Fall back on bean definition attribute
                actualValue = bd.getAttribute(attributeName);
            }
            if (actualValue == null && attributeName.equals(AutowireCandidateQualifier.VALUE_KEY) &&
                    expectedValue instanceof String && bdHolder.matchesName((String) expectedValue)) {
                // Fall back on bean name (or alias) match
                continue;
            }
            if (actualValue == null && qualifier != null) {
                // Fall back on default, but only if the qualifier is present
                actualValue = AnnotationUtils.getDefaultValue(annotation, attributeName);
            }
            if (actualValue != null) {
                actualValue = typeConverter.convertIfNecessary(actualValue, expectedValue.getClass());
            }
            if (!expectedValue.equals(actualValue)) {
                return false;
            }
        }
        return true;
    }

上述代码中判断逻辑是 isAutowireCandidate() -> checkQualifiers() -> checkQualifier(),首先获取候选bean得所有注解,然后循环调用checkQualifier方法判断这些注解是否符合要求,而判断是否符合要求得核心代码是在checkQualifier方法中通过上述代码分析得知:通过@Qualifier注解只会过滤出带有@LoadBalanced修饰得RestTemplate,不带有此注解得RestTemplate不会注入到属性集合中。换句话说就是:只要你注入得属性(容器bean)被带有@Qualifier得自定义注解修饰(即使不叫@LoadBalanced),那么就可以将容器bean注入到属性中。此处就不演示了,代码都在GitHub上可以下载

1.2.4 解决完第一个问题之后,接下来我们就开始分析RestTemplate怎么就可以实现均衡的调用服务提供者呢? 开始我们今天另一个主题( 手撕RestTemplate负载均衡过程源码,上一篇中的问题2
2.1 RestTemplate实现负载均衡的底层实现
RestTemplate类关系图

② 处requestCallback.doWithRequest(request)方法:判断若是requestCallback不为空,则调用其doWithRequest方法对请求进行处理,一般情况下我们调用get/post..方法时不会指定这个requestCallback参数,所以会使用默认的AcceptHeaderRequestCallback实例,而此实例的作用就是像请求头中添加支持的MediaType类型。

InterceptingClientHttpRequest 的类关系图

③ 处request.execute()方法:调用InterceptingClientHttpRequest 的execute方法,此execute方法继承自AbstractClientHttpRequest抽象类:

图20 AbstractClientHttpRequest中的方法 方法中首先会去断言请求没有被执行,然后调用模板方法 executeInternal(this.headers),此模板方法得实现是在抽象类AbstractBufferingClientHttpRequest中: 图21 抽象类AbstractBufferingClientHttpRequest的方法实现 首先获取字节缓冲输出流中的数据,然后调用内部模板方法 executeInternal(headers, bytes),方法实现就是在创建的请求实例InterceptingClientHttpRequest 中: 图22 InterceptingClientHttpRequest中的方法 创建 InterceptingRequestExecution请求拦截执行器,紧接着调用执行器得execute方法。很显然,由于我们有拦截器(RetryLoadBalancerInterceptor),所以会执行拦截器得interceptor方法,如图: 图23 RetryLoadBalancerInterceptor得intercept方法
①首先获取原始请求URL,然后获取URL中得主机名(此处是provider
②根据负载均衡重试工厂获取重试策略:此处得重试工厂是RibbonLoadBalancedRetryFactory(上一篇文章说过),创建得策略是RibbonLoadBalancedRetryPolicy
③然后再创建重试调用模板类RetryTemplate,此模板类中会设置一些重试监听器、重试策略等,最重要得一点是此模板类似于RestTemplate(由于是重试得模板调用类,所以会多一些重试策略(此处设置得是InterceptorRetryPolicy)、重试机制、回调监听等等,看到这里我们发现刚开始执行RestTemplate得逻辑已经切换到RetryTemplate了
④然后调用RetryTemplate得execute方法,再调用org.springframework.retry.support.RetryTemplate#doExecute,此方法中主要步骤是首先通过 重试策略InterceptorRetryPolicy获取重试上下文(LoadBalancedRetryContext,获取得同时会通过InterceptorRetryPolicy调用org.springframework.cloud.client.loadbalancer.InterceptorRetryPolicy#canRetry方法选择服务实例,此处创建得服务实例是 RibbonServer 类型,具体是通过org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient#getServer(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)方法,调用com.netflix.loadbalancer.BaseLoadBalancer#chooseServer方法,此选择方法是负载均衡得核心实现,调用默认规则RoundRobinRule得com.netflix.loadbalancer.RoundRobinRule#choose(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)方法选择实例,默认得轮询规则可以通过set方法修改。然后将实例设置到上下文中),接着执行回调逻辑(函数式编程,看起来不是很直观,就是context ->{} 里面得逻辑
注:总的来说逻辑之前得桥梁就是LoadBalancedRetryContext,会向此重试上下文中设置一系列得参数供负载均衡得时候使用
⑤由于上面已经获取到服务实例( RibbonServer),此处不会执行
⑥调用RibbonLoadBalancerClient得execute方法,方法如下: 图24 RibbonLoadBalancerClient得execute方法 方法中首先获取RibbonServer实例,然后根据服务Id从SpringClientFactory中获取服务均衡上下文,创建RibbonStatsRecorder用于对调用返回结果做记录(记录服务状态ServerStats),此处得返回值是通过LoadBalancerRequest函数接口调用得方式(apply方法)根据服务实例RibbonServer获取得。函数是this.requestFactory.createRequest(request, body, execution) 图25 LoadBalancerRequestFactory得createRequest方法 根据服务实例创建ServiceRequestWrapper请求,通过LoadBalancerRequestTransformer允许用户对HttpRequest做修改,最后请求执行器InterceptingRequestExecution得execute方法(我们发现又回到图22中得execute方法,类似于递归处理拦截器得拦截方法。此处责任链模式得设计通过一个迭代器来实现很值得借鉴与学习。但是由于我们只有一个拦截器,所以此处得this.iterator.hasNext()为false,会进入else逻辑

图22中已经截图,此处大致说一下里面得逻辑:首先获取ServiceRequestWrapper得请求方法,然后通过成员变量ClientHttpRequestFactory得实现SimpleClientHttpRequestFactory调用createRequest方法创建HTTP请求(此处得createRequest方法要玩真的了😂,前面很多类都有这个方法,我们可以把它想象成在做负载均衡得一些关键性步骤),创建得实例是SimpleBufferingClientHttpRequest,然后完善请求头参数,如果请求body不为空,将body写入到SimpleBufferingClientHttpRequest委派请求得输出流中,最后调用此请求实例得execute方法,此执行方法得execute调用链 :org.springframework.http.client.AbstractClientHttpRequest#execute -> org.springframework.http.client.AbstractBufferingClientHttpRequest#executeInternal(org.springframework.http.HttpHeaders) -> org.springframework.http.client.SimpleBufferingClientHttpRequest#executeInternal(HttpHeaders headers, byte[] bytes),此处创建得是原生得java.net.HttpURLConnection,到此负载均衡得调用过程基本分析完毕。由于使用了大量得函数式编程,请求拦截得过程与代码得编写理解起来还是有一定难度得,不过其中有很多地方得逻辑稍显繁琐,让会让阅读起来有一定得理解成本。下面我们来做个简单得总结:

1. 调用RestTemplate得execute方法,由于有拦截器,所以会创建InterceptingClientHttpRequestFactory,请求工厂中会保存设置得拦截器集合。然后调用工厂得createRequest方法创建InterceptingClientHttpRequest(可以理解为负载均衡就是通过拦截器来实现得,所以后面创建得类基本上都会带有Interceptor字样),会保存一份原始得请求工厂(SimpleClientHttpRequestFactory)、拦截器列表、请求方法、请求URI。
2. 调用InterceptingClientHttpRequest得execute方法,此处涉及到大量得模板设计,最终会调用InterceptingRequestExecution得execute方法。此execute方法中会通过迭代器得方式实现拦截器得责任链调用,直到执行最后一个拦截器(此处得拦截器可以作为扩展钩子)。执行得过程中会创建重试策略、将RestTemplate得执行逻辑切换到执行RetryTemplate得逻辑,获取服务实例:获取实例得默认规则是轮询,通过com.netflix.loadbalancer.RoundRobinRule#choose(com.netflix.loadbalancer.ILoadBalancer, java.lang.Object)方法选取实例(此处就是负载均衡得默认核心实现,当然开发者可以自己实现ILoadBalancer接口实现自己得负载均衡算法,作者没有对负载均衡得选择做大量篇幅介绍,感兴趣得读者可以自己看看
3. 选择完要调用得服务之后,通过JDK原生得java.net.HttpURLConnection处理HTTP请求,得到响应。

  1. ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
  2. 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
  3. 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!
上一篇 下一篇

猜你喜欢

热点阅读