06.OpenFeign介面呼叫

长名06發表於2024-10-08

1.提問

1.1 已經有RestTemplate + LoadBalancer的方式進行服務的呼叫,為什麼還要有OpenFeign?

因為OpenFeign的功能更強大,和使用更便攜。

1.2 使用那個?

推薦使用OpenFeign

2.OpenFeign是什麼

2.1 官網翻譯

https://docs.spring.io/spring-cloud-openfeign/reference/spring-cloud-openfeign.html

Feign是一個宣告性web服務客戶端。它使編寫web服務客戶端變得更容易。使用Feign建立一個介面並對其進行註釋。它具有可插入的註解支援,包括Feign註解和JAX-RS註解。Feign還支援可插拔編碼器和解碼器。Spring Cloud新增了對Spring MVC註釋的支援,以及對使用Spring Web中預設使用的HttpMessageConverter的支援。Spring Cloud整合了Eureka、Spring Cloud CircuitBreaker以及Spring Cloud LoadBalancer,以便在使用Feign時提供負載平衡的http客戶端。

JAX-RS註解:JAX-RS(Java API for RESTful Web Services)是一套用於構建RESTful Web服務的標準Java API。JAX-RS提供了一組註解來簡化HTTP請求處理和資源的定義。這些註解可以幫助開發者輕鬆地建立RESTful服務。

@Path,@GET,@POST,@PUT,@DELETE,@HEAD,@OPTIONS,@PATCH等。-- 來源,通義千問2.5。

2.2 GitHub

https://github.com/spring-cloud/spring-cloud-openfeign

2.3 總結

OpenFeign是一個宣告式的Web服務客戶端,只需要建立一個Rest介面,並在改介面上新增註解@FeignClient即可使用。

OpenFeign基本上就是當前微服務之間的呼叫的事實標準。

3.OpenFeign作用

前面在使用SpringCloud LoadBalancer+RestTemplate時,利用RestTemplate對http請求的封裝處理形成了一套模版化的呼叫方法。但是在實際開發中,由於對服務依賴的呼叫可能不止一處,往往一個介面會被多處呼叫,所以通常都會針對每個微服務自行封裝一些客戶端類來包裝這些依賴服務的呼叫。所以,OpenFeign在此基礎上做了進一步封裝,由他來幫助我們定義和實現依賴服務介面的定義。在OpenFeign的實現下,我們只需建立一個介面並使用註解的方式來配置它(在一個微服務介面上面標註一個@FeignClient註解即可),即可完成對服務提供方的介面繫結,統一對外暴露可以被呼叫的介面方法,大大簡化和降低了呼叫客戶端的開發量,也即由服務提供者給出呼叫介面清單,消費者直接透過OpenFeign呼叫即可,O(∩_∩)O。

OpenFeign同時還整合SpringCloud LoadBalancer


可以在使用OpenFeign時提供Http客戶端的負載均衡,也可以整合阿里巴巴Sentinel來提供熔斷、降級等功能。而與SpringCloud LoadBalancer不同的是,透過OpenFeign只需要定義服務繫結介面且以宣告式的方法,優雅而簡單的實現了服務呼叫。

總結

  • 可插拔的註解支援,包括Feign註解和JAX-RS註解;
  • 可插拔的HTTP編碼器和解碼器;
  • 支援Sentinel和Fallback;
  • 支援SpringCloudLoadBalancer的複雜均衡;
  • 支援HTTP請求和響應的壓縮。

4.使用

4.1 介面 + 註解

微服務API介面 + @FeignClient註解

架構說明

4.2 使用步驟

4.2.1 新建Module

略,新建一個使用OpenFeign的模組。

4.2.2 引入依賴

這是新增的依賴,其他的依賴,還需引入。

<!--openfeign-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
4.2.3 yml(基礎)

4.2.4 啟動類
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableDiscoveryClient //該註解用於向使用consul為註冊中心時註冊服務
@EnableFeignClients//啟用feign客戶端,定義服務+繫結介面,以宣告式的方法優雅而簡單的實現服務呼叫
public class MainOpenFeign80 {

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

}
4.2.5 業務類

訂單模組要去呼叫支付模組,訂單和支付兩個微服務,需要透過Api介面解耦,一般不要在訂單模組寫非訂單相關的業務,

自己的業務自己做+其它模組走FeignApi介面呼叫。

將Feign介面,寫在公共的依賴模組中,因為其他模組,可以也會需要遠端呼叫支付模組。

commons模組修改

引入OpenFeign依賴:略

新建Feign介面:

@FeignClient(value = "cloud-payment-service")
public interface PayFeignApi {
    /**
     * 新增一條支付相關流水記錄
     * @param payDTO
     * @return
     */
    @PostMapping("/pay/add")
    ResultData<String> addPay(@RequestBody PayDTO payDTO);

    /**
     * 按照主鍵記錄查詢支付流水資訊
     * @param id
     * @return
     */
    @GetMapping("/pay/get/{id}")
    ResultData getById(@PathVariable("id") Integer id);//註解中的id必須寫,

    /**
     * 負載均衡演示
     * @return
     */
    @GetMapping("/pay/get/info")
    String mylb();
}

@PathVariable註解的使用方式進行了調整,要求顯式地指定路徑變數的名稱,以確保程式碼的可讀性和維護性。在之前的版本中,如果路徑變數名與方法引數名一致,可以省略name屬性。但在SpringBoot 3.0及以後的版本中,為了遵循更好的程式設計實踐和避免潛在的混淆,要求必須明確指定路徑變數的名稱。

修改Controller層的呼叫:略,將RestTemplate方式,改成Feign遠端呼叫。

4.3 測試

5.OpenFiegn高階特性

5.1 超時控制

5.1.1 測試


在cloud-provider-payment8001模組,故意寫暫停62s程式,order模組,寫捕捉異常,進行預設超時時間測試。

//8001模組
@GetMapping("/get/{id}")
@Operation(summary = "根據id查流水", description = "查詢支付流水方法")
public ResultData<Pay> getById(@PathVariable("id") Integer id) {
    if (id <= 0) {
        throw new RuntimeException("id必須為正數");
    }

    try {
        TimeUnit.SECONDS.sleep(62);
    } catch (InterruptedException e) {
        logger.error(e.getMessage());
    }

    return ResultData.success(payService.getById(id));
}
//使用openFeign的order模組
@GetMapping("/feign/get/{id}")
public ResultData getById(@PathVariable("id")Integer id){
    logger.info("-------支付微服務遠端呼叫,按照id查詢訂單支付流水資訊");
    ResultData resultData = null;
    try {
        logger.info("呼叫開始------: " + DateUtil.now());
        resultData = payFeignApi.getById(id);
    } catch (Exception e){
        e.printStackTrace();
        logger.info("呼叫結束------: " + DateUtil.now());
        ResultData.fail(ReturnCodeEnum.RC500.getCode(), e.getMessage());
    }
    return resultData;
}

結論:

OpenFeign預設等待60s,超時後報錯

feign.RetryableException: Read timed out executing GET http://cloud-payment-service/pay/get/1
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283)
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966)
	at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:244)
	at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
	at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:343)
	at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:791)
	at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:726)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1688)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
	at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:529) //java.net.HttpURLConnection使用JDK預設的進行http請求。
5.1.2 配置處理


預設OpenFeign客戶端等待60秒鐘,但是服務端處理超過規定時間會導致Feign客戶端返回報錯。

為了避免這樣的情況,有時候我們需要設定Feign客戶端的超時控制,預設60秒太長或者業務時間太短都不好

yml檔案中開啟配置:

connectTimeout 連線超時時間

readTimeout 請求處理超時時間

5.1.3 自定義超時配置

官網出處


關鍵內容

spring:
    openfeign:
      client:
        config:
          #全域性配置
          default:
            #連線超時時間
            connectTimeout: 3000
            #讀取超時時間
            readTimeout: 3000
          #單個微服務的配置,細粒度的重寫全域性
          cloud-payment-service:
            #連線超時時間
            connect-timeout: 4000
            #讀取超時時間
            read-timeout: 4000
5.1.4 測試

5.2 重試機制

5.2.1 預設

重試機制預設是關閉的。一次失敗後,就結束。

5.2.2 開啟Retryer功能
@Configuration
public class FeignConfig {

    @Bean
    public Retryer retryer(){

        //return Retryer.NEVER_RETRY;
        //maxAttempts - 1才是重試次數
        return new Retryer.Default(100,1,3);最大請求次數(1 + 2),初始間隔時間100ms,重試間最大間隔時間1s
    }

}
public interface Retryer extends Cloneable {
    ...
    class Default implements Retryer {

        private final int maxAttempts;
        private final long period;
        private final long maxPeriod;
        int attempt;
        long sleptForMillis;

        public Default() {
          this(100, SECONDS.toMillis(1), 5);
        }

        public Default(long period, long maxPeriod, int maxAttempts) {
          this.period = period;
          this.maxPeriod = maxPeriod;
          this.maxAttempts = maxAttempts;
          this.attempt = 1;
        }
        ...

    }
    ...
}
5.2.3 測試

5.3 預設HttpClient的修改

OpenFeign中http client(發起HTTP請求的元件)

如果不做特殊配置,OpenFeign預設使用JDK自帶的HttpURLConnection傳送HTTP請求,由於預設HttpURLConnection沒有連線池、效能和效率比較低,如果採用預設,效能上不是最好的,所以加到最大。

5.3.1 使用Apache HttpClient5替換預設

5.3.2 引入依賴
<!-- httpclient5-->
<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
    <version>5.3</version>
</dependency>
<!-- feign-hc5-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-hc5</artifactId>
    <version>13.1</version>
</dependency>
5.3.3 配置項

新增配置項

#  Apache HttpClient5 配置開啟
spring:
  cloud:
    openfeign:
      httpclient:
        hc5:
          enabled: true

完整配置項

server:
  port: 8080

spring:
  application:
    name: cloud-consumer-openfeign-order
  ####Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #優先使用服務ip進行註冊
        service-name: ${spring.application.name}
    openfeign:
      httpclient:
        hc5:
          enabled: true
      client:
        config:
          #全域性配置
          default:
            #連線超時時間
            connectTimeout: 3000
            #讀取超時時間
            readTimeout: 3000
          #單個微服務的配置,細粒度的重寫全域性
          cloud-payment-service:
            #連線超時時間
            connect-timeout: 4000
            #讀取超時時間
            read-timeout: 4000
5.3.4 替換前後對比
//替換前
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283)
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966)
	at java.base/java.io.BufferedInputStream.fill(BufferedInputStream.java:244)
	at java.base/java.io.BufferedInputStream.read1(BufferedInputStream.java:284)
	at java.base/java.io.BufferedInputStream.read(BufferedInputStream.java:343)
	at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:791)
	at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:726)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1688)
	at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
	at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:529) //java.net.HttpURLConnection使用JDK預設的進行http請求。
//替換後
Caused by: java.net.SocketTimeoutException: Read timed out
	at java.base/sun.nio.ch.NioSocketImpl.timedRead(NioSocketImpl.java:283)
	at java.base/sun.nio.ch.NioSocketImpl.implRead(NioSocketImpl.java:309)
	at java.base/sun.nio.ch.NioSocketImpl.read(NioSocketImpl.java:350)
	at java.base/sun.nio.ch.NioSocketImpl$1.read(NioSocketImpl.java:803)
	at java.base/java.net.Socket$SocketInputStream.read(Socket.java:966)
	at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.fillBuffer(SessionInputBufferImpl.java:149)
	at org.apache.hc.core5.http.impl.io.SessionInputBufferImpl.readLine(SessionInputBufferImpl.java:280)
	at org.apache.hc.core5.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:247)
	at org.apache.hc.core5.http.impl.io.AbstractMessageParser.parse(AbstractMessageParser.java:54)
	at org.apache.hc.core5.http.impl.io.DefaultBHttpClientConnection.receiveResponseHeader(DefaultBHttpClientConnection.java:299)
	at org.apache.hc.core5.http.impl.io.HttpRequestExecutor.execute(HttpRequestExecutor.java:175)
	at org.apache.hc.core5.http.impl.io.HttpRequestExecutor.execute(HttpRequestExecutor.java:218)
	at org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager$InternalConnectionEndpoint.execute(PoolingHttpClientConnectionManager.java:712)
	at org.apache.hc.client5.http.impl.classic.InternalExecRuntime.execute(InternalExecRuntime.java:216)
	at org.apache.hc.client5.http.impl.classic.MainClientExec.execute(MainClientExec.java:116)
	at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
	at org.apache.hc.client5.http.impl.classic.ConnectExec.execute(ConnectExec.java:188)
	at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
	at org.apache.hc.client5.http.impl.classic.ProtocolExec.execute(ProtocolExec.java:192)
	at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
	at org.apache.hc.client5.http.impl.classic.HttpRequestRetryExec.execute(HttpRequestRetryExec.java:113)
	at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
	at org.apache.hc.client5.http.impl.classic.ContentCompressionExec.execute(ContentCompressionExec.java:152)
	at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
	at org.apache.hc.client5.http.impl.classic.RedirectExec.execute(RedirectExec.java:116)
	at org.apache.hc.client5.http.impl.classic.ExecChainElement.execute(ExecChainElement.java:51)
	at org.apache.hc.client5.http.impl.classic.InternalHttpClient.doExecute(InternalHttpClient.java:170)
	at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:87)
	at org.apache.hc.client5.http.impl.classic.CloseableHttpClient.execute(CloseableHttpClient.java:55)
	at feign.hc5.ApacheHttp5Client.execute(ApacheHttp5Client.java:88)

5.4 請求/響應壓縮

5.4.1 官網說明

5.4.2 是什麼

對請求和響應進行GZIP壓縮

Spring Cloud OpenFeign支援對請求和響應進行GZIP壓縮,以減少通訊過程中的效能損耗。

透過下面的兩個引數設定,就能開啟請求與相應的壓縮功能:

spring.cloud.openfeign.compression.request.enabled=true

spring.cloud.openfeign.compression.response.enabled=true

細粒度化設定

對請求壓縮做一些更細緻的設定,比如下面的配置內容指定壓縮的請求資料型別並設定了請求壓縮的大小下限,

只有超過這個大小的請求才會進行壓縮:

spring.cloud.openfeign.compression.request.enabled=true

spring.cloud.openfeign.compression.request.mime-types=text/xml,application/xml,application/json #觸發壓縮資料型別

spring.cloud.openfeign.compression.request.min-request-size=2048 #最小觸發壓縮的大小

5.4.3 YML修改
server:
  port: 8080

spring:
  application:
    name: cloud-consumer-openfeign-order
  ####Spring Cloud Consul for Service Discovery
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        prefer-ip-address: true #優先使用服務ip進行註冊
        service-name: ${spring.application.name}
    openfeign:
      httpclient:
        hc5:
          enabled: true
      #新增
      compression: 
        request:
          enabled: true
          min-request-size: 2048 #最小觸發壓縮的大小
          mime-types: text/xml,application/xml,application/json #觸發壓縮資料型別
        response:
          enabled: true
      client:
        config:
          #全域性配置
          default:
            #連線超時時間
            connectTimeout: 3000
            #讀取超時時間
            readTimeout: 3000
          #單個微服務的配置,細粒度的重寫全域性
          cloud-payment-service:
            #連線超時時間
            connect-timeout: 4000
            #讀取超時時間
            read-timeout: 4000

5.5 日誌列印功能

5.5.1 是什麼

Feign 提供了日誌列印功能,我們可以透過配置來調整日誌級別,

從而瞭解 Feign 中 Http 請求的細節,

說白了就是對Feign介面的呼叫情況進行監控和輸出

5.5.2 日誌級別
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
}
  • NONE:預設的,不顯示任何日誌;
  • BASIC:僅記錄請求方法、URL、響應狀態碼及執行時間;
  • HEADERS:除了 BASIC 中定義的資訊之外,還有請求和響應的頭資訊;
  • FULL:除了 HEADERS 中定義的資訊之外,還有請求和響應的正文及後設資料。
5.5.3 配置
@Bean
public Logger.Level logger(){
    return Logger.Level.FULL;
}
logging:
  level:
    com.atguigu.cloud.apis.PayFeignApi: debug

5.5.4 測試輸出日誌




6.OpenFeign和Sentinel整合實現fallback服務降級

後續Alibaba課程會講到

只是為了記錄自己的學習歷程,且本人水平有限,不對之處,請指正。

相關文章