SpringCloud-Ribbon和Feign

1 服务调用Ribbon入门

当启动某个服务的时候,可以通过HTTP的形式
将信息注册到注册中心,并且可以通过SpringCloud提供的工具获取注册中心的服务列表。但是服务之间的调用还存在很多的问题,如何更加方便的调用微服务,多个微服务的提供者如何选择,如何负载均衡等。

image-20210619164252059

多个微服务的提供者如何选择,如何负载均衡等

1.2 Ribbon概述

1 什么是Ribbon

是 Netflflixfa 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中,Eureka一般配合Ribbon进行使用

Ribbon提供了客户端负载均衡的功能,Ribbon利用从Eureka中读

取到的服务信息,在调用服务节点提供的服务时,会合理的进行负载。

在SpringCloud中可以将注册中心和Ribbon配合使用**,Ribbon自动的从注册中心中获取服务提供者的列表信息,并基于内置的负载均衡算法,请求服务**

2 Ribbon的主要作用

(1)服务调用

基于Ribbon实现服务调用, 是通过拉取到的所有服务列表组成(服务名-请求路径的)映射关系。借助RestTemplate 最终进行调用

(2)负载均衡

当有多个服务提供者时,Ribbon可以根据负载均衡的算法自动的选择需要调用的服务地址

1.3 负载均衡的理解

在搭建网站时,如果单节点的 web服务性能和可靠性都无法达到要求;或者是在使用外网服务时,经常
担心被人攻破,一不小心就会有打开外网端口的情况,通常这个时候加入负载均衡就能有效解决服务问题。

负载均衡是一种基础的网络服务,其原理是通过运行在前面的负载均衡服务,按照指定的负载均衡算法,将流量分配到后端服务集群上,从而为系统提供并行扩展的能力。

负载均衡的应用场景包括流量包、转发规则以及后端服务,由于该服务有内外网个例、健康检查等功能,能够有效提供系统的安全性和可用性。

2 客户端负载均衡与服务端负载均衡

服务端负载均衡

先发送请求到负载均衡服务器或者软件,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端再进行负载均衡算法分配

image-20210619165913920

客户端负载均衡

客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,然后进行访问,这是客户端负载均衡;即在客户端就进行负载均衡算法分配

image-20210619170339809

Ribbon是一个典型的客户端负载均衡器,Ribbon会获取服务的所有地址,根据内部的负载均衡算法,获取本次请求的有效地址

1.4 Ribbon 实现服务调用

image-20210619164837048

1 给RestTemplate 添加注解


	@Bean
	@LoadBalanced
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}

3 将访问路径替换为商品服务名称

image-20210625173344774

4 测试 http://localhost:9002/order/buy/1

image-20210625173711611

1.5 Ribbon 实现负载均衡

  • 准备两个商品微服务(9001,9011 )

  • 在订单系统中远程以负载均衡的形式调用商品服务

在订单系统中使用@LoadBlance 注解

public class OrderApplication {

	@Bean
	@LoadBalanced
	public RestTemplate restTemplate() {
		return new RestTemplate();
	}

测试

访问http://localhost:9002/order/buy/1

第一端口为9011 第二次为9012 ****

1.6 Ribbon的负载均衡策略

Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为

com.netflix.loadbalancer.IRule ,实现方式如下

image-20210625182646572

com.netflix.loadbalancer.RoundRobinRule :以轮询的方式进行负载均衡。

com.netflix.loadbalancer.RandomRule :随机策略

com.netflix.loadbalancer.RetryRule :重试策略。

com.netflix.loadbalancer.WeightedResponseTimeRule :权重策略。会计算每个服务的权重,越高的被调用的可能性越大。

com.netflix.loadbalancer.BestAvailableRule :最佳策略。遍历所有的服务实例,过滤掉故障实例,并返回请求数最小的实例返回。

在服务消费者的application.yml配置文件中修改负载均衡策略

##需要调用的微服务名称
shop-service-product:
 ribbon:
   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

策略选择:

1、如果每个机器配置一样,则建议不修改策略 (推荐)

2、如果部分机器配置强,则可以改为 WeightedResponseTimeRule

1.7 Ribbon请求重试

当一个服务不可用的时候,Ribbon重试去访问另外一个服务

1 订单中导入Spring重试的坐标


        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>

2 在application.yaml 进行重试的配置

    #服务名称
SHOP-SERVICE-PRODUCT:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
    ConnectTimeout: 250 # Ribbon的连接超时时间
    ReadTimeout: 1000 # Ribbon的数据读取超时时间
    OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 1 # 切换实例的重试次数
    MaxAutoRetries: 1 # 对当前实例的重试次数

3 访问地址进行测试

原来访问一个停掉的服务会报错,现在一个服务出错后,访问另一服务

1.7 Ribbon中负载均衡的源码解析

@LoadBalanced注解

/**
* 基于Ribbon的服务调用与负载均衡
*/
@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
    return new RestTemplate();
}

这个注解的主要作用是什么呢,查看源码

/**
* Annotation to mark a RestTemplate bean to be configured to use a 
LoadBalancerClient
* @author Spencer Gibb
*/
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

通过注释可以知道@LoadBalanced注解是用来给RestTemplate做标记,方便我们对RestTemplate添加一个LoadBalancerClient,以实现客户端负载均衡。

自动装配

根据SpringBoot中的自动装配规则可以在 spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar 中可以找到 spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframew
ork.cloud.netflix.ribbon.RibbonAutoConfiguration

找到自动装配的类RibbonAutoConfifiguration

@Configuration
@Conditional({RibbonAutoConfiguration.RibbonClassesConditions.class})
@RibbonClients
@AutoConfigureAfter(
    name = {"org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration"} )
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, 
AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, 
ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
 @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
   }
    
    @Bean
    @ConditionalOnMissingBean({LoadBalancerClient.class})
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(this.springClientFactory());
   }
    //省略
}

通过 RibbonAutoConfiguration 引入LoadBalancerAutoConfiguration 配置类

@Configuration
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnBean({LoadBalancerClient.class})
@EnableConfigurationProperties({LoadBalancerRetryProperties.class})
public class LoadBalancerAutoConfiguration {
    @LoadBalanced
    @Autowired(
        required = false
   )
    private List<RestTemplate> restTemplates = Collections.emptyList();
    @Autowired(
        required = false
   )
    private List<LoadBalancerRequestTransformer> transformers =
Collections.emptyList();
   public LoadBalancerAutoConfiguration() {
   }
    @Bean
    public SmartInitializingSingleton
loadBalancedRestTemplateInitializerDeprecated(ObjectProvider<List<RestTemplateCu
stomizer>> restTemplateCustomizers) {
        return () -> {
            restTemplateCustomizers.ifAvailable((customizers) -> {
                Iterator var2 = this.restTemplates.iterator();
                while(var2.hasNext()) {
                    RestTemplate restTemplate = (RestTemplate)var2.next();
                    Iterator var4 = customizers.iterator();
                    while(var4.hasNext()) {
                        RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
                        customizer.customize(restTemplate);
                   }
               }
           });
       };
   }
    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory
loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, 
this.transformers);
   }
    @Configuration
    @ConditionalOnClass({RetryTemplate.class})
    public static class RetryInterceptorAutoConfiguration {
        public RetryInterceptorAutoConfiguration() {
       }
        @Bean
        @ConditionalOnMissingBean
        public RetryLoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient
loadBalancerClient, LoadBalancerRetryProperties properties, 
LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory
loadBalancedRetryFactory) {
            return new RetryLoadBalancerInterceptor(loadBalancerClient, 
properties, requestFactory, loadBalancedRetryFactory);
       }
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer
restTemplateCustomizer(RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return (restTemplate) -> {
                List<ClientHttpRequestInterceptor> list = new
ArrayList(restTemplate.getInterceptors());
  list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
           };
       }
   }
    @Configuration
    @ConditionalOnClass({RetryTemplate.class})
    public static class RetryAutoConfiguration {
        public RetryAutoConfiguration() {
       }
        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryFactory loadBalancedRetryFactory() {
            return new LoadBalancedRetryFactory() {
           };
       }
   }
    @Configuration
   
@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
    static class LoadBalancerInterceptorConfig {
        LoadBalancerInterceptorConfig() {
       }
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient
loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, 
requestFactory);
       }
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer
restTemplateCustomizer(LoadBalancerInterceptor loadBalancerInterceptor) {
            return (restTemplate) -> {
                List<ClientHttpRequestInterceptor> list = new
ArrayList(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
           };
       }
   }
}


1.8 Ribbon负载均衡的源码分析

1 查看@LoadBalanced 注解

@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

@LoadBalanced 是一个标记注解

2 自动装配

根据SpringBoot中的自动装配规则可以在 spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar 中可以找到 spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\org.springframew
ork.cloud.netflix.ribbon.RibbonAutoConfiguration

找到自动装配的类RibbonAutoConfiguration

@Configuration
@Conditional({RibbonAutoConfiguration.RibbonClassesConditions.class})
@RibbonClients
@AutoConfigureAfter(
    name = {"org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration"} )
@AutoConfigureBefore({LoadBalancerAutoConfiguration.class, 
AsyncLoadBalancerAutoConfiguration.class})
@EnableConfigurationProperties({RibbonEagerLoadProperties.class, 
ServerIntrospectorProperties.class})
public class RibbonAutoConfiguration {
 @Bean
    public SpringClientFactory springClientFactory() {
        SpringClientFactory factory = new SpringClientFactory();
        factory.setConfigurations(this.configurations);
        return factory;
   }
    
    @Bean
    @ConditionalOnMissingBean({LoadBalancerClient.class})
    public LoadBalancerClient loadBalancerClient() {
        return new RibbonLoadBalancerClient(this.springClientFactory());
   }
    //省略
}

通过 RibbonAutoConfiguration 引入了 LoadBalancerAutoConfiguration 配置

@Configuration
@ConditionalOnClass({RestTemplate.class})
@ConditionalOnBean({LoadBalancerClient.class})
@EnableConfigurationProperties({LoadBalancerRetryProperties.class})
public class LoadBalancerAutoConfiguration {
    @LoadBalanced
    @Autowired(
        required = false
   )
    private List<RestTemplate> restTemplates = Collections.emptyList();
    @Autowired(
        required = false
   )
    private List<LoadBalancerRequestTransformer> transformers =
Collections.emptyList();
    public LoadBalancerAutoConfiguration() {
   }
    @Bean
    public SmartInitializingSingleton
loadBalancedRestTemplateInitializerDeprecated(ObjectProvider<List<RestTemplateCu
stomizer>> restTemplateCustomizers) {
        return () -> {
            restTemplateCustomizers.ifAvailable((customizers) -> {
                Iterator var2 = this.restTemplates.iterator();
                while(var2.hasNext()) {
                    RestTemplate restTemplate = (RestTemplate)var2.next();
                    Iterator var4 = customizers.iterator();
                    while(var4.hasNext()) {
                        RestTemplateCustomizer customizer = (RestTemplateCustomizer)var4.next();
                        customizer.customize(restTemplate);
                   }
               }
           });
       };
   }
    @Bean
    @ConditionalOnMissingBean
    public LoadBalancerRequestFactory
loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient) {
        return new LoadBalancerRequestFactory(loadBalancerClient, 
this.transformers);
   }
    @Configuration
    @ConditionalOnClass({RetryTemplate.class})
    public static class RetryInterceptorAutoConfiguration {
        public RetryInterceptorAutoConfiguration() {
       }
        @Bean
        @ConditionalOnMissingBean
        public RetryLoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient
loadBalancerClient, LoadBalancerRetryProperties properties, 
LoadBalancerRequestFactory requestFactory, LoadBalancedRetryFactory
loadBalancedRetryFactory) {
            return new RetryLoadBalancerInterceptor(loadBalancerClient, 
properties, requestFactory, loadBalancedRetryFactory);
       }
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer
restTemplateCustomizer(RetryLoadBalancerInterceptor loadBalancerInterceptor) {
            return (restTemplate) -> {
                List<ClientHttpRequestInterceptor> list = new
ArrayList(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
           };
       }
   }
    @Configuration
    @ConditionalOnClass({RetryTemplate.class})
    public static class RetryAutoConfiguration {
        public RetryAutoConfiguration() {
       }
        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryFactory loadBalancedRetryFactory() {
            return new LoadBalancedRetryFactory() {
           };
       }
   }
    @Configuration
   
@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
    static class LoadBalancerInterceptorConfig {
        LoadBalancerInterceptorConfig() {
       }
        @Bean
        public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient
loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
            return new LoadBalancerInterceptor(loadBalancerClient, 
requestFactory);
       }
        @Bean
        @ConditionalOnMissingBean
        public RestTemplateCustomizer
restTemplateCustomizer(LoadBalancerInterceptor loadBalancerInterceptor) {
            return (restTemplate) -> {
                List<ClientHttpRequestInterceptor> list = new
ArrayList(restTemplate.getInterceptors());
                list.add(loadBalancerInterceptor);
                restTemplate.setInterceptors(list);
           };
       }
   }
}

在该自动化配置类中,主要做了下面三件事:

  • 创建了一个 LoadBalancerInterceptor 的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡

  • 创建了一个 RestTemplateCustomizer 的Bean,用于给 RestTemplate 增加LoadBalancerInterceptor 拦截器。

  • 维护了一个被 @LoadBalanced 注解修饰的 RestTemplate 对象列表,并在这里进行初始化,通过调用 RestTemplateCustomizer 的实例来给需要客户端负载均衡的 RestTemplate 增加LoadBalancerInterceptor 拦截器。

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

通过源码以及之前的自动化配置类,我们可以看到在拦截器中注入了 LoadBalancerClient 的实现。当

一个被 @LoadBalanced 注解修饰的 RestTemplate 对象向外发起HTTP请求时,会被

LoadBalancerInterceptor 类的 intercept 函数所拦截。由于我们在使用RestTemplate时候采用了

服务名作为host,所以直接从 HttpRequest 的URI对象中通过getHost()就可以拿到服务名,然后调用

execute 函数去根据服务名来选择实例并发起实际的请求。

分析到这里, LoadBalancerClient 还只是一个抽象的负载均衡器接口,所以我们还需要找到它的具体实现类来进一步分析。通过查看ribbon的源码,我们可以很容易的在

org.springframework.cloud.netflix.ribbon 包下找到对应的实现类:

RibbonLoadBalancerClient 。

public class RibbonLoadBalancerClient implements LoadBalancerClient {
    public ServiceInstance choose(String serviceId) {
        Server server = this.getServer(serviceId);
        return server == null ? null : new
RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, 
serviceId), this.serverIntrospector(serviceId).getMetadata(server));
   }
    
    public <T> T execute(String serviceId, LoadBalancerRequest<T> request) 
throws IOException {
        ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
        Server server = this.getServer(loadBalancer);
        if (server == null) {
            throw new IllegalStateException("No instances available for " +
serviceId);
       } else {
  RibbonLoadBalancerClient.RibbonServer ribbonServer = new
RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, 
serviceId), this.serverIntrospector(serviceId).getMetadata(server));
            return this.execute(serviceId, ribbonServer, request);
       }
   }
    
    protected Server getServer(String serviceId) {
        return this.getServer(this.getLoadBalancer(serviceId));
   }
    
    protected Server getServer(ILoadBalancer loadBalancer) {
        return loadBalancer == null ? null : 
loadBalancer.chooseServer("default");
   }
    
    protected ILoadBalancer getLoadBalancer(String serviceId) {
        return this.clientFactory.getLoadBalancer(serviceId);
   }
    
    //省略...
}
  • ServiceInstance choose(String serviceId):根据传入的服务id,从负载均衡器中为指定的服务选择一个服务实例
  • T execute(String serviceId, LoadBalancerRequest request):根据传入的服务id,指定的负载均衡器中的服务实例执行请求。
  • T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest request):根据传入的服务实例,执行请求

从 RibbonLoadBalancerClient 代码可以看出,实际负载均衡的是通过 ILoadBalancer 来实现的

@Bean
@ConditionalOnMissingBean
public ILoadBalancer ribbonLoadBalancer(IClientConfig config, ServerList<Server>
serverList, ServerListFilter<Server> serverListFilter, IRule rule, IPing ping, 
ServerListUpdater serverListUpdater) {
    return (ILoadBalancer)(this.propertiesFactory
   .isSet(ILoadBalancer.class, this.name) ? (ILoadBalancer)this.propertiesFactory
   .get(ILoadBalancer.class, config, this.name) : new
ZoneAwareLoadBalancer(config, rule, ping, serverList, serverListFilter, 
serverListUpdater));
}

2 服务调用Feign入门

前面我们使用的RestTemplate实现REST API调用,代码大致如下:

@GetMapping("/buy/{id}")
public Product order() {
    Product product = restTemplate.getForObject("http://shop-serviceproduct/product/1", Product.class);
    return product; }

由代码可知,我们是使用拼接字符串的方式构造URL的,该URL只有一个参数。但是,在现实中,URL中往往含有多个参数。这时候我们如果还用这种方式构造URL,那么就会非常痛苦。那应该如何解决?我们带着这样的问题进入到本章的学习。

Feign是Netflflix开发的声明式,模板化的HTTP客户端,其灵感来自Retrofifit,JAXRS-2.0以及WebSocket

  • 在SpringCloud中,使用Feign非常简单——创建一个接口,并在接口上添加一些注解,代码就完成了。
  • Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等
  • SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解,并整合了Ribbon和Eureka,从而让Feign的使用更加方便。

2.2 基于Feign的服务调用

1 导入Feign的坐标

在服务消费者 shop_service_order 添加Fegin依赖


           <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

2 启动类添加Feign的支持

@SpringBootApplication(scanBasePackages="cn.itcast.order")
@EntityScan("cn.itcast.entity")
@EnableFeignClients
public class OrderApplication {
 public static void main(String[] args) {
 SpringApplication.run(OrderApplication.class, args);
 }
}

(3)启动类激活FeignClient

创建一个Feign接口,此接口是在Feign中调用微服务的核心接口
在服务消费者 shop_service_order 添加一个 ProductFeginClient 接口

@FeignClient(name="service-product")
public interface ProductFeginClient {
    @RequestMapping(value= "/product/{id}",method = RequestMethod.GET)
    Product buy(@PathVariable("id") Long id);
}

  • 定义各参数绑定时,@PathVariable、@RequestParam、@RequestHeader等可以指定参数属性,在Feign中绑定参数必须通过value属性来指明具体的参数名,不然会抛出异常

  • @FeignClient:注解通过name指定需要调用的微服务的名称,用于创建Ribbon的负载均衡器。所以Ribbon会把 shop-service-product 解析为注册中心的服务。

(4)配置请求提供者的调用接口

修改 OrderController ,添加ProductFeginClient的自动注入,并在order方法中使用ProductFeginClient 完成微服务调用

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

    @Autowired
    private ProductFeginClient productFeginClient;

    //使用Ribbon 访问服务
    @RequestMapping(value = "/buy/{id}",method = RequestMethod.GET)
    public Product findById(@PathVariable Long id) {

        Product product = productFeginClient.findById(id);
        return product;

    }


}


访问测试

http://localhost:9003/order/buy/1

2.3 Feign 与Ribbon的联系

Ribbon是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。它可以 在客户端 配置RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate 模拟http请求,步骤相当繁琐

Feign 是在 Ribbon的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方

法即可, 不需要自己构建http请求。然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写客户端变得非常容易

2.4 负载均衡

Feign中本身已经集成了Ribbon依赖和自动配置,因此我们不需要额外引入依赖,也不需要再注册
RestTemplate 对象。另外,我们可以像上节课中讲的那样去配置Ribbon,可以通过 ribbon.xx 来进行全局配置。也可以通过 服务名.ribbon.xx 来对指定服务配置

启动两个 shop_service_product ,重新测试可以发现使用Ribbon的轮询策略进行负载均衡

2.5 服务调用Feign高级

2.1 Feign的配置
从Spring Cloud Edgware开始,Feign支持使用属性自定义Feign。对于一个指定名称的Feign
Client(例如该Feign Client的名称为 feignName ),Feign支持如下配置项:

image-20210622164241301

2.2 请求压缩

Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。通过下面的参数

即可开启请求与响应的压缩功能:

image-20210622164329923

2.3 日志级别

在开发或者运行阶段往往希望看到Feign请求过程的日志记录,默认情况下Feign的日志是没有开启的。
要想用属性配置方式来达到日志效果,只需在 application.yml 中添加如下内容即可:

feign:
  client:
    config:
      shop-service-product: #服务名
        loggerLevel: FULL #日志级别
logging:
  level:
    cn.itcast.feign.ProductFeginClient: debug  
 

image-20210622170403668

  • logging.level.xx : debug : Feign日志只会对日志级别为debug的做出响应

feign.client.config.shop-service-product.loggerLevel : 配置Feign的日志Feign有四种日志级别:

image-20210622170508680

3 Feign的源码分析

image-20210622184439360

2.6 Feign的源码分析

通过上面的使用过程,@EnableFeignClients和@FeignClient两个注解就实现了Feign的功能,那我们从

@EnableFeignClients注解开始分析Feign的源码

(1)EnableFeignClients注解

@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({FeignClientsRegistrar.class}) public @interface EnableFeignClients { //略 }

通过 @EnableFeignClients 引入了FeignClientsRegistrar客户端注册类

class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar, 

ResourceLoaderAware, EnvironmentAware { 

public void registerBeanDefinitions(AnnotationMetadata metadata, 

BeanDefinitionRegistry registry) { 

this.registerDefaultConfiguration(metadata, registry); 

this.registerFeignClients(metadata, registry); 

} 

}

通过其类结构可知,由于实现了ImportBeanDefifinitionRegistrar接口,那么在

registerBeanDefifinitions()中就会解析和注册BeanDefifinition,主要注册的对象类型有两种:

  • 注册缺省配置的配置信息

  • 注册那些添加了@FeignClient的类或接口 : 这也是我们讨论的重点

public void registerFeignClients(AnnotationMetadata metadata,
			BeanDefinitionRegistry registry) {
		ClassPathScanningCandidateComponentProvider scanner = getScanner();
		scanner.setResourceLoader(this.resourceLoader);

		Set<String> basePackages;

		Map<String, Object> attrs = metadata
				.getAnnotationAttributes(EnableFeignClients.class.getName());
		AnnotationTypeFilter annotationTypeFilter = new AnnotationTypeFilter(
				FeignClient.class);
		final Class<?>[] clients = attrs == null ? null
				: (Class<?>[]) attrs.get("clients");
		if (clients == null || clients.length == 0) {
			scanner.addIncludeFilter(annotationTypeFilter);
			basePackages = getBasePackages(metadata);
		}
		else {
			final Set<String> clientClasses = new HashSet<>();
			basePackages = new HashSet<>();
			for (Class<?> clazz : clients) {
				basePackages.add(ClassUtils.getPackageName(clazz));
				clientClasses.add(clazz.getCanonicalName());
			}
			AbstractClassTestingTypeFilter filter = new AbstractClassTestingTypeFilter() {
				@Override
				protected boolean match(ClassMetadata metadata) {
					String cleaned = metadata.getClassName().replaceAll("\\$", ".");
					return clientClasses.contains(cleaned);
				}
			};
			scanner.addIncludeFilter(
					new AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));
		}

		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidateComponents = scanner
					.findCandidateComponents(basePackage);
			for (BeanDefinition candidateComponent : candidateComponents) {
				if (candidateComponent instanceof AnnotatedBeanDefinition) {
					// verify annotated class is an interface
					AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
					AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();
					Assert.isTrue(annotationMetadata.isInterface(),
							"@FeignClient can only be specified on an interface");

					Map<String, Object> attributes = annotationMetadata
							.getAnnotationAttributes(
									FeignClient.class.getCanonicalName());

					String name = getClientName(attributes);
					registerClientConfiguration(registry, name,
							attributes.get("configuration"));

					registerFeignClient(registry, annotationMetadata, attributes);
				}
			}
		}
	}

该方法主要是扫描类路径,对所有的FeignClient生成对应的 BeanDefinitio 。同时又调用了registerClientConfiguration 注册配置的方法,这里是第二处调用。这里主要是将扫描的目录下,每个项目的配置类加载的容器当中。调用 registerFeignClient 注册对象

(3) 注册FeignClient对象


	private void registerFeignClient(BeanDefinitionRegistry registry,
			AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		BeanDefinitionBuilder definition = BeanDefinitionBuilder
				.genericBeanDefinition(FeignClientFactoryBean.class);
		validate(attributes);
		definition.addPropertyValue("url", getUrl(attributes));
		definition.addPropertyValue("path", getPath(attributes));
		String name = getName(attributes);
		definition.addPropertyValue("name", name);
		String contextId = getContextId(attributes);
		definition.addPropertyValue("contextId", contextId);
		definition.addPropertyValue("type", className);
		definition.addPropertyValue("decode404", attributes.get("decode404"));
		definition.addPropertyValue("fallback", attributes.get("fallback"));
		definition.addPropertyValue("fallbackFactory", attributes.get("fallbackFactory"));
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

		String alias = contextId + "FeignClient";
		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();

		boolean primary = (Boolean)attributes.get("primary"); // has a default, won't be null

		beanDefinition.setPrimary(primary);

		String qualifier = getQualifier(attributes);
		if (StringUtils.hasText(qualifier)) {
			alias = qualifier;
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
				new String[] { alias });
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
	}

通过分析可知:我们最终是向Spring中注册了一个bean,bean的名称就是类或接口的名称(也就是本例中的FeignService),bean的实现类是FeignClientFactoryBean,其属性设置就是我们在

@FeignClient中定义的属性。那么下面我们在Controller中对FeignService的的引入,实际就是引入了FeignClientFactoryBean 类

(4) FeignClientFactoryBean类

们对@EnableFeignClients注解的源码进行了分析,了解到其主要作用就是把带有@FeignClient注解的类或接口用FeignClientFactoryBean类注册到Spring

image-20210625200401155

通过 FeignClientFactoryBean 类结构可以发现其实现了FactoryBean类,那么当从ApplicationContext中获取该bean的时候,实际调用的是其getObject()方法。返回调用getTarget()方法

image-20210625200435390

image-20210625200449205

初步配置完Feign.Builder之后再判断是否需要loadBalance,如果需要则通过loadBalance方法来

设置,不需要则在Client是LoadBalancerFeignClient的时候进行unwrap

(5) 发送请求
由上可知,FeignClientFactoryBean.getObject()具体返回的是一个代理类,具体为FeignInvocationHandler

image-20210625200532990

  • FeignInvocationHandler实现了InvocationHandler,是动态代理的代理类。

  • 当执行非Object方法时进入

  • this.dispatch.get(method)).invoke(args)dispatch是一个map集合,根据方法名称获取MethodHandler。具体实现类为SynchronousMethodHandler

image-20210625200625198

  • SynchronousMethodHandler内部创建了一个RequestTemplate对象,是Feign中的请求模板对象。内部封装了一次请求的所有元数据。

  • retryer中定义了用户的重试策略。

  • 调用executeAndDecode方法通过client完成请求处理,client的实现类是LoadBalancerFeignClient

总结

1 先找到所有@FeignClient的注解,然后将信息保留到,然后根据动态代理生成对应的实现类

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值