天天看點

SpringCloud【OpenFeign】-服務間調用的利器

SpringCloud【OpenFeign】-服務間調用的利器

文章目錄

  • ​​前言​​
  • ​​一、OpenFeign快速使用​​
  • ​​1.1 SpringCloud Alibaba快速整合OpenFeign​​
  • ​​二、聲明式服務調用​​
  • ​​三、程式設計式服務調用:fire::fire:​​
  • ​​【好處】​​
  • ​​四、OpenFeign相關配置​​
  • ​​4.1 日志配置​​
  • ​​4.1.1 全局配置日志​​
  • ​​4.1.2 局部配置​​
  • ​​4.2 逾時時間配置​​
  • ​​4.2.1 全局配置逾時時間​​
  • ​​4.2.2 局部配置​​
  • ​​4.3 自定義攔截器​​
  • ​​4.4 熔斷降級處理​​
  • ​​4.4.1 FallBackFactory​​

前言

在學習OpenFeign之前,我們有必要了解下​

​什麼是Feign​

​❓,好多人老把這兩個東西混為一談。認為他們是一個東西。

  • ​Feign:​

    ​​Feign是Netflix公司寫的,是SpringCloud元件中的一個輕量級RESTful的HTTP服務用戶端,是SpringCloud中的第一代負載均衡用戶端。​

    ​也就是說Feign有缺陷,是以OpenFeign就産生了。​

  • -​

    ​Openfeign:​

    ​​是Spring CLoud的二級子項目,是基于Feign的基礎上進行了完善和優化,​

    ​支援了SpringMVC的注解​

    ​​,​

    ​我們可以像寫控制層的轉發一樣完成服務與服務之間的調用。​

Feign現在已經不再更新維護,是以目前基本上都是使用OpenFeign

介紹完Feign和OpenFeign的差別後,那麼我可以回想下以前我們調用服務是怎麼樣調用的?----​

​RestTemplate​

使用RestTemplate我們需要在控制層不斷地維護請求服務的位址,參數,傳回值。很不優雅,代碼閱讀也十分繁瑣。不好,不喜歡!!!

❓那麼有沒有一種更加簡便且優雅的方式調用服務呢-----那就是接下來要學習的OpenFeign了。

OpenFeign也是放在服務消費端的,也整合了負載均衡器。

一、OpenFeign快速使用

1.1 SpringCloud Alibaba快速整合OpenFeign

【父級pom】

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.11.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
  </parent>


  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <spring.cloud-version>Hoxton.SR8</spring.cloud-version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <!-- springCloud -->
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-dependencies</artifactId>
        <version>${spring.cloud-version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- springcloud alibaba -->
      <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-alibaba-dependencies</artifactId>
        <version>2.2.5.RELEASE</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <!-- springBoot -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- json -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.47</version>
    </dependency>
  </dependencies>      

二、聲明式服務調用

所謂的聲明式,就是需要利用注解聲明一個調用服務的接口方法,這個方法必須聲明在服務消費方。使用@FeignClient聲明

【實作步驟】

  1. 引入依賴
<!--OpenFeign-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>      
  1. 在服務消費者端,寫一個調用服務的接口
@Component
@FeignClient(name = "stock-service",path = "/stock")
public interface FeignStockService {
  
  @RequestMapping("/reduct")
  public String name();
  
}      
  • @FeignClient:指明需要調用的服務的名稱以及路徑和失敗回調,這個注解​

    ​會掃描@EnableFeignClients指定的包​

    ​​;如果未指定,​

    ​則會找@FeignClient指定的服務名,在注冊中心找對應的服務IP萬層服務調用​

  • name:​

    ​指定FeignClient的名稱​

    ​,如果項目使用了Ribbon,name屬性會作為微服務的名稱,用于服務發現
  • path:​

    ​定義目前FeignClient的統一字首​

    ​,當我們項目中配置了server.context-path,server.servlet-path時使用
  • fallbackFactory:​

    ​工廠類,用于生成fallback類示例​

    ​,通過這個屬性我們可以實作每個接口通用的容錯邏輯,減少重複的代碼
  • fallback:​

    ​定義容錯的處理類,當調用遠端接口失敗或逾時時,會調用對應接口的容錯邏輯,fallback指定的類必須實作@FeignClient标記的接口​

  • configuration:​

    ​Feign配置類​

    ​,可以自定義Feign的Encoder、Decoder、LogLevel、Contract
  • url:​

    ​ url一般用于調試​

在寫FeignClients的時候,我們可以找一個規律,就是這裡的接口方法實際就是從服務提供者的controller裡粘貼過來的方法,如下圖所示:
SpringCloud【OpenFeign】-服務間調用的利器
SpringCloud【OpenFeign】-服務間調用的利器
  1. 控制層實作調用
@RestController
@RequestMapping("/order")
public class OrderController {

  @Autowired
  private FeignStockService feignStockService;

  @RequestMapping("/add")
  public String add() {
    System.out.println("下單成功!");
    return "hello Feign" + feignStockService.name();
  }
}      

使用了OpenFeign後,我們在也不需要向原來使用RestTemplate一樣在寫很多遍的url,去擷取服務。很友善,但是又有一個新的問題❓

就是我們又要去寫很多遍的服務接口方法—這樣耦合度還是很高,還不夠優雅—接着改

  1. 在啟動類上加上​

    ​@EnableFeignClients​

    ​注解,表示自動開啟Feign遠端調用。
@SpringBootApplication
@EnableDiscoveryClient // nacos版本在1.4以後就可以不用添加 表示nacos啟動用戶端
@EnableFeignClients // 開啟Feign調用
public class OrderApplication {

  public static void main(String[] args) {
    SpringApplication.run(OrderApplication.class, args);
  }
}      

三、程式設計式服務調用🔥🔥

我們可以發現,雖然OpenFeign已經使得我們在調用服務的時候簡化了一些備援操作,但是又引出了一些新的問題。是以我們在公司開發項目的時候,是不太可能會使用上述的方式實作,最佳實踐如下操做:

【實作步驟】

  1. 在建立一個Maven子產品,用于封裝定義的接口API方法
SpringCloud【OpenFeign】-服務間調用的利器

【好處】

​這樣做的目的就是實作解耦合​

​,将公共的feign的調用接口抽取成一個公共的子產品,通過GAV引入到服務提供者和服務消費者的子產品下,這樣就可以實作服務間的調用通信。更加優雅
  1. 在公共的接口子產品裡,隻需要寫一個接口,将原來寫在服務消費端的接口定義子這裡
/**
 * 定義公共接口子產品
 * 類似于之前的服務接口一樣,隻不過在微服務裡面我們是通過子產品 引入gav實作調用
 * 原理大同小異
 * @author wangruoxian
 *
 */
public interface CommonStockApi {
  
  @RequestMapping("/reduct")
  public String name();
  
}      
到這裡,我們需要将它maven install以下生成對應的jar包。友善其他子產品引用
  1. 在服務提供這裡的控制層實作此接口

先引入公共接口的坐标

<dependency>
  <groupId>com.wei</groupId>
  <artifactId>DHC_SpringCloud_CommonOrderAPI</artifactId>
  <version>0.0.1-SNAPSHOT</version>
</dependency>      

兩個服務的提供者都是需要實作接口

@RestController
@RequestMapping("/stock")
public class Stock8011Controller implements CommonStockApi{
  
  @Value("${server.port}")
  String port;
  
  @RequestMapping("/reduct")
  public String name() {
    System.out.println("庫存減少");
    return "減扣庫存"+port;
  }
}      
  1. 最後在服務的消費者端的接口就隻需要繼承公共接口,而不要聲明定義。

​這樣就将服務消費者與提供者之間的耦合度消除了。​

@Component
@FeignClient(name = "stock-service",path = "/stock")
public interface FeignStockService extends CommonStockApi{}      

四、OpenFeign相關配置

OpenFeign提供了很多的擴充機制,讓使用者可以更加靈活的使用。

4.1 日志配置

有的時候我們遇到BUG,比如接口調用失敗,參數有沒有問題,或者想看調用的性能。這個時候,就需要使用Feign日志了,一次讓Feign把請求資訊輸出出來。

【源碼檢視】

public enum Level {
    /**
     * No logging.
     */
    NONE,
    /**
     * Log only the request method and URL and the response status code and execution time.
     */
    BASIC,
    /**
     * Log the basic information along with request and response headers.
     */
    HEADERS,
    /**
     * Log the headers, body, and metadata for both requests and responses.
     */
    FULL
  }      

通過檢視OpenFeign的源碼,我們發現OpenFeign提供了四種的日志級别:

  • NONE:【性能最佳,僅用于生産】不輸出任何的日志資訊(預設)
  • BASIC:【用于生産環境追蹤問題】僅記錄一些請求方法,URL,響應狀态碼以及執行時間
  • HEADERS:記錄BASIC級别基礎上,記錄請求和響應的Header。
  • FULL:【比較适合用于開發及測試環境定位問題】:記錄請求和相應的header、body以及中繼資料。

​那麼我們該如何實作呢???​

  • 全局配置:針對所有的服務
  • 局部配置:僅針對某個服務

4.1.1 全局配置日志

我們需要定義一個配置類,實作日志的全局配置

@Configuration
public class MyFeignConfig {

  @Bean
  public Logger.Level feignLoggerLevel() {
    return Logger.Level.FULL;
  }
}      
加上了@Configuration注解之後,就代表這是一個全局配置類

然後,我們還需要再yml檔案裡聲明一下它是以DEBUG的模式輸出列印的,否則你正常啟動他是不顯示的

logging:
  level:
    com.wei.feign.api:      
SpringCloud【OpenFeign】-服務間調用的利器

4.1.2 局部配置

【編碼的方式配置局部日志】

要使用局部配置就不需要在寫@Configuration注解,隻需要在@feignClient裡聲明​

​configuration​

​即可

/**
 * 指定輸出Product服務的日志資訊
 * @author wangruoxian
 *
 */
@FeignClient(name = "server-product",path = "/product",configuration = MyFeignConfig.class)
public interface FeignProductService extends CommonProductApi {}      
SpringCloud【OpenFeign】-服務間調用的利器

這裡僅有product的日志資訊

補充: 局部配置還可以寫在yml配置檔案裡
feign:
  client:
    config:
      server-product: # 對應的服務名稱
        logger-level:      
SpringCloud【OpenFeign】-服務間調用的利器
這裡很明顯的觀察到basic的日志輸出沒有full的全,僅有一些請求的資訊,沒有響應的資訊

4.2 逾時時間配置

通過Options可以配置連接配接逾時時間和讀取逾時時間,Options的第一個參數是連接配接的逾時時間(ms),預設值是2s;第二個參數是請求的逾時時間,,預設是5s。

4.2.1 全局配置逾時時間

@Configuration
public class FeignConfig(){
  @Bean
  public Request.Options options(){
    return new Request.Options(5000,10000);
  }
}      

4.2.2 局部配置

方式一還是和配置日志的方式一樣,這裡省略

方式二:通過yml檔案配置,​​

​服務消費者yml​

# 局部配置
feign:
  client:
    config:
      server-product: # 對應的服務名稱
        logger-level: basic
        connect-timeout: 5000 # 連接配接逾時 預設是2s
        read-timeout: 3000 # 請求逾時時間 預設是5s      

然後我們模拟請求逾時,讓Product服務線程sleep4s後,在啟用,這裡很明顯就會報請求異常的錯誤

@RequestMapping("/get/{id}")
  public String get(@PathVariable("id") Integer id) {
    try {
      Thread.sleep(4000);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("查詢商品"+id);
    return "查詢商品"+id+":"+port;
  }      
SpringCloud【OpenFeign】-服務間調用的利器

4.3 自定義攔截器

自定義攔截器需要實作RequestInterceptor 接口,通過Resttemplate完成對服務調用的攔截操做,

​​

​這裡比如攔截/product/get/{id}這個請求 并列印info級别的日志​

public class CustomFeignInterceptor implements RequestInterceptor {

  Logger logger = LoggerFactory.getLogger(this.getClass());

  @Override
  public void apply(RequestTemplate template) {
    // TODO Auto-generated method stub
//    template.header("xxx", "xxx");
    template.uri("/get/9");
    logger.info("feign的自定義攔截器");
  }

}      

使用局部配置自定義攔截器,使之生效

# 局部配置
feign:
  client:
    config:
      server-product: # 對應的服務名稱
        logger-level: basic
        connect-timeout: 10000 # 連接配接逾時 預設是2s
        read-timeout: 10000 # 請求逾時時間 預設是5s
        # 局部配置自定義攔截器
        request-interceptors:
        -      
SpringCloud【OpenFeign】-服務間調用的利器

4.4 熔斷降級處理

openFeign實際上是已經引入了hystrix的相關jar包,是以可以直接使用,設定逾時時間,逾時後調用FallBack方法,實作熔斷機制。

首先在消費者工程添加Maven依賴。

<!--OpenFeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--不能引入hystrix的依賴 原因在開頭已經說明-->
<!--
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
-->      
使用​

​bootstrap.yaml ​

​啟用 hystrix 斷路器,Feign 将使用斷路器包裝所有方法
  • Feign 開啟 hystrix 斷路器feign.hystrix.enabled=true
  • Feign 關閉 spring cloud 斷路器 feign.circuitbreaker.enabled=true
feign:
  hystrix:
    enabled: true
  circuitbreaker:
    enabled: false      

​注意:啟動類不能添加 @EnableCircuitBreaker ,如果添加該注解需要導入 spring-cloud-starter-netflix-hystrix 依賴​

@SpringBootApplication
@EnableDiscoveryClient // nacos版本在1.4以後就可以不用添加 表示nacos啟動用戶端
@EnableFeignClients
public class OrderApplication {

  public static void main(String[] args) {
    SpringApplication.run(OrderApplication.class, args);
  }
}      
注意:服務降級有兩種實作方式:
  • fallback:消費者端本地實作遠端接口,​

    ​即fallback形式​

    ​​,​

    ​此方式相對簡單友善,但無法處理異常​

    ​,是以個人不推薦此種方式實作的服務降級
  • fallbackfactiory:​

    ​消費者端本地實作 FallbackFactory<>接口​

    ​​,即fallbackFactory形式,此方式也比較簡單,​

    ​并且可以處理異常,推薦此方式實作。​

4.4.1 FallBackFactory

在标注了FeignClient的标注的接口上使用fallbackFactory=“自定義的失敗回調工廠類”

@FeignClient(
      name = "server-product", 
      path = "/product",
      fallbackFactory = ProductFallBackFactory.class)
public interface FeignProductService extends CommonProductApi {}      

自定義的回調工廠類ProductFallBackFactory

@Component
public class ProductFallBackFactory implements FallbackFactory<FeignProductService>{

  @Override
  public FeignProductService create(Throwable cause) {
    // TODO Auto-generated method stub
    return new FeignProductService() {
      
      @Override
      public String get(Integer id) {
        // TODO Auto-generated method stub
        return "服務調用失敗";
      }
    };
  }
}      

​注意:​

​ 自定義的FallBackfactory類所要實作的​

​FallbackFactory​

​的泛型必須要約定為提供接口服務的@FeignClient接口,然後通過retuen傳回自定義的傳回值,這樣當服務調用出現錯誤就會将return的結果傳回,而不會使服務中斷崩潰。

測試

這裡我們為了方柏測試,将preoduct服務sleep5s

@RestController
@RequestMapping("/product")
public class ProductController implements CommonProductApi{
  
  @Value("${server.port}")
  private String port;

  @RequestMapping("/get/{id}")
  public String get(@PathVariable("id") Integer id) {
    try {
      Thread.sleep(5000);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    System.out.println("查詢商品"+id);
    return "查詢商品"+id+":"+port;
  }
}