Spring Cloud Feign

介绍:

image-20211110151129609

Feign 能通过注解来实现远程调用。能够帮助我们节省很多远程调用的代码

Feign 使用了Ribbon

只需要在服务调用者中使用就行了

OpenFeign是spring基于Feign开发的spring融合版

核心思想:

无感知远程HTTP调用请求

写个案例

步骤

  • 服务消费者添加Feign依赖;
  • 创建业务层接口,添加@FeignClient注解声明需要调用的服务;
  • 业务抽象方法使用 SpringMVC注解配置服务地址及参数;
  • 启动类加载 @EnableFeignClients 注解激活Feign 组件。

加载依赖包

加载依赖

  • spring-cloud-starter-netflix-eureka-client
  • spring-cloud-starter-openfeign
  • spring-boot-starter-web
  • org.projectlombok lombok
  • spring-boot-starter-test

构建项目

  • ShopApplication 中添加@EnbaleFeignClients注解 开启Feign
  • 创建ProductService接口并添加@FeignClient注解
  • 编写List<Product> getProducts()方法并使用SpringMVC注解@GetMapping("/product/list")远程调用
  • 在controller中调用看看
  • application.yml中按照常規eureka配置即可

调用接口

1
GET http://localhost:18480/shop/byid?id=331

image-20211111153326740

Feign负载均衡

Feign基于Ribbon做了负载均衡算法

全局负载均衡配置

课程中的Loadbalancer可能过时了用了没用看官方的内容

https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#spring-cloud-feign-circuitbreaker-fallback

spring-cloud-starter-openfeign支持spring-cloud-starter-loadbalancer. 但是,作为一个可选的依赖项,如果您想使用它,您需要确保将其添加到您的项目中

看看这 https://spring.io/guides/gs/spring-cloud-loadbalancer/

1
2
3
4
@Bean
public RandomRule randomRule(){
return new RandomRule();
}

局部配置

与之前是一样的 但仍3.0.4仍然不成功

在yml中配置

1
2
3
service-provider: # 服务名
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #随机调用

带有参数的请求

请求

1
2
3
4
@GetMapping()
public HttpEntity<Product> getProductByCode(String id){
return new HttpEntity<>(productRepository.getById(id));
}

这里会报错:

No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no proper

某些属性是空的找不到无法正常序列化。

在Model上加入以上注解解决这个问题

1
@JsonIgnoreProperties(value = { "hibernateLazyInitializer"})
  • 消费者关键代码

    1
    2
    @GetMapping("/product/{id}")
    Product getProductById(@PathVariable("id") String id);
  • 提供者关键代码

    正常接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @GetMapping("/{id}")
    public HttpEntity getProductByCode(@PathVariable("id") String id) {
    try {
    Product product = productRepository.getById(id);
    return new HttpEntity<>(product);
    } catch (Exception e) {
    e.printStackTrace();
    return HttpEntity.EMPTY;
    }
    }

可以用 @PathVariable 标签设定参数

post传参

post的参数在请求体中

  • 提供者关键代码

    1
    2
    3
    4
    5
    6
    7
    8
      
    @PostMapping()
    public HttpEntity<Product> saveProduct(@RequestBody Product product){
    product.setName(product.getName()+"_"+serverPort);
    product=productRepository.save(product);
    HttpEntity<Product> httpEntity = new HttpEntity(product);
    return httpEntity;
    }
  • 消費者关键代码

    1
    2
    3
      
    @PostMapping("/product")
    Product saveProduct(@RequestBody Product product);

必须用@RequestBody 来读取请求体中的内容

Gzip 压缩

降低网络传输量

谷歌可以直接读取gzip

配置Gzip

全局和局部的配置的区别。

局部是关于请求到service的,全局会从相关服务一直到浏览器都进项gzip压缩

image-20211115164857841

默认情况是没有启用gzip的

image-20211115165013830

谷歌浏览器告诉我们他是支持gzip的

局部配置

1
2
3
4
5
6
7
8
9
#Feign gzip 压缩
feign:
compression:
request:
mime-types: text/xml,application/xml,application/json # 配置压缩支持的MIME TYPE
min-request-size: 512 # 配置压缩数据的最小阈值,默认2048
enabled: true
response:
enabled: true # 返回也开启

全局配置

1
2
3
4
5
server:
port: ${SERVICE_PORT:18480}
compression:
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain

image-20211115165338249

返回也支持了

http性能提升

在feign中通过连接池来降低负载提升性能

每次请求建立都需要三次握手,四次挥手为了降低性能开销我们使用连接池方式。

Feign的HTTP客户端支持三种框架,HttpURLConnection、HttpClient、OkHttp;默认使用HttpURLConnection(JDK自带的不支持连接池)

推荐使用HttpClient

启用httpClient

  • 导入依赖

    • org.apache.httpcomponents httpclient 4.5.11

      必须要用httpclient才能正常调用,因为原生不支持

    • io.github.openfeign feign-httpclient 10.7.4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.11</version>
    </dependency>
    <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>10.7.4</version>
    </dependency>
  • 修改配置文件

    1
    2
    3
    feign:
    httpclient:
    enabled: true #开启httpClient

    image-20211115171411381

Get RequestBody传递对象参数

  • 服務提供方配置

    • pom

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
          
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-openfeign </artifactId>
      <version>${openfeign.version}</version>
      </dependency>
      <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.11</version>
      </dependency>
      <dependency>
      <groupId>io.github.openfeign</groupId>
      <artifactId>feign-httpclient</artifactId>
      <version>10.7.4</version>
      </dependency>
    • yml

      1
      2
      3
      feign:
      httpclient:
      enabled: true # 默認是開啓的
    • 关键代码

      1
      2
      3
      4
      5
      6
      7
      8
      @GetMapping("/findByProduct")
      public Product findShop(@RequestBody Product product){
      Optional<Product> productOptional = productRepository.findById(product.getCode());
      if(productOptional.isPresent()){
      return productOptional.get();
      }
      return null;
      }
  • 消费者

    • pom yml和服务方想用

    • 关键代码 java

      1
      2
      @GetMapping("/product/findByProduct")
      Product findByProduct(@RequestBody Product product);

性能调优状态查看

在服务消费者中添加一个logback.xml文件

新能调优,超时设置

1
2
3
4
5
6
feign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 3000

新版本的Feign: 已经没有再用ribbon了

Feign的雪崩处理

  • 首先搭建一个与Shop相同的环境
  • 有ProductService并且使用@FeignService注解调用
  • 添加一个ProductConntroller来调用Product并测试成功

依赖引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>${netflix-eureka-server.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.26</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign </artifactId>
<version>${openfeign.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.11</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.7.4</version>
</dependency>
</dependencies>

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
server:
port: ${SERVICE_PORT:18680}
compression: # 開啓壓縮請求
enabled: true
mime-types: application/json,application/xml,text/html,text/xml,text/plain
spring:
application:
name: eurekademo-service-order-feign-test
jpa:
database-platform: org.hibernate.dialect.MySQL8Dialect
show-sql: false
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
ddl-auto: update
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:4306/eurekademo
cloud:
inetutils:
ignoredInterfaces: ['VMware.*','ingress'] # 必须过滤掉VMware 因为会豁达86.1
preferred-networks:
- ${NET_DOMAIN:192.168/16} #必须只选择192网段的否则会选择一些虚拟机网卡
eureka:
client:
register-with-eureka: false # 将自己注册到注册中心 默认 true
fetch-registry: true # 是否从注册中心获取服务注册信息 默认true
service-url:
defaultZone: http://root:123456@localhost:18761/eureka/,http://root:123456@localhost:18762/eureka/,http://root:123456@localhost:18763/eureka/
registry-fetch-interval-seconds: 10 # 表示 eureka Client 间隔多久去服务器拉取注册信息,默认为 30 秒。
feign:
httpclient:
enabled: true #开启httpClient
client:
config:
default:
connectTimeout: 3000
readTimeout: 3000

构建一个feign的调service

  1. 创建一个service使用FeignClient注解进行调用

  2. 用REST风格的注解调用远端接口

  3. 在Application中使用@EnableFeignClients

  4. application.yml 中加入circuitbreaker开启的注解

    1
    2
    3
    feign:
    circuitbreaker:
    enabled: true

雪崩处理例子

  • 修改@FeignClient注解

    1
    @FeignClient(name = "eurekademo-service-product" , fallback = ProductServiceFallback.class)
  • 创建ProductServiceFallback 并且继承ProductService并实现

  • 必须引用spring-cloud-starter-netflix-hystrix

  • 再关闭product引用并且调用就可以访问到兜底方法了

关键问题解决!!!!

在新版本中比如3.0.4必须要引用spring-cloud-starter-netflix-hystrix 才能正常回退。

并且需要开启ciruitbreaker

1
2
3
feign:
circuitbreaker:
enabled: true

通过工厂回调(记录日志)

  • 指定调用

    在ProductService的FeignClient注解中加入参数fallbackFactory=ProductServiceFallbackFactory.class

  • 编写Factory代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    package org.akachi.eurekademo.order.service.impl.fallback.factory;

    import lombok.extern.slf4j.Slf4j;
    import org.akachi.eurekademo.order.service.ProductService;
    import org.akachi.eurekademo.product.model.Product;
    import org.springframework.cloud.openfeign.FallbackFactory;
    import org.springframework.stereotype.Component;

    import java.util.ArrayList;
    import java.util.List;

    /**
    * @Author akachi
    * @Email zsts@hotmail.com
    * @Date 2021/11/26 0:09
    */
    @Slf4j
    @Component
    public class ProductServiceFallbackFactory implements FallbackFactory<ProductService> {
    @Override
    public ProductService create(Throwable cause) {
    return new ProductService() {
    @Override
    public List<Product> getProducts() {
    log.error("fallback getProducts"+cause);
    cause.printStackTrace();
    Product product = new Product();
    List<Product> productList = new ArrayList();
    productList.add(product);
    product.setName("兜底数据");
    product.setCode("-1");
    return productList;
    }

    @Override
    public Product getProductById(String id) {
    log.error("fallback getProductById"+cause);
    cause.printStackTrace();
    Product product = new Product();
    product.setName("兜底数据");
    product.setCode("-1");
    return product;
    }

    @Override
    public List<Product> getProductByCodes(List<String> ids) {
    log.error("fallback getProductByCodes"+cause);
    cause.printStackTrace();
    Product product = new Product();
    List<Product> productList = new ArrayList();
    productList.add(product);
    product.setName("兜底数据");
    product.setCode("-1");
    return productList;
    }
    };
    }
    }

资料