【微服務】- 服務調用 – OpenFeign

服務調用 – OpenFeign

😄生命不息,寫作不止
🔥 繼續踏上學習之路,學之分享筆記
👊 總有一天我也能像各位大佬一樣
🏆 一個有夢有戲的人 @怒放吧德德
🌝分享學習心得,歡迎指正,大家一起學習成長!

image

介紹

OpenFeign 全稱 Spring Cloud OpenFeign,它是 Spring 官方推出的一種聲明式服務調用與負載均衡組件,它的出現就是為了替代進入停更維護狀態的 Feign。
Spring Cloud openfeign對Feign進行了增強,使其支援Spring MVC註解,另外還整合了
Ribbon和Nacos,從而使得Feign的使用更加方便。
Feign使用http遠程調用方法就好像調用本地的方法,感覺不到是遠程方法。他的使用就和直接寫控制類那樣,暴露介面提供調用,我們只需要編寫調用介面+@FeignClient註解,在使用這個api的時候,只需要定義好方法,到時候調用這個方法就可以了。這種服務之間的調用使用起來是非常的方便,體驗也比較好。

如何實現介面調用?

在平時開發的springboot項目中,像這種rest服務是如何被調用的呢?通常下是使用Httpclient、Okhttp、HttpURLConnection、RestTemplate,其中RestTemplate是最常見的。之前在 nacos配置中心 使用的是RestTemplate。

SpringCloud整合OpenFeign

就用一個例子來簡單使用OpenFeign進行服務間的調用,通過實例來學習關於Feign組件的功能。

引入依賴

使用OpenFeign組件需要引入客戶端依賴

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

編寫調用介面

通過OpenFeign遠程調用服務的時候,比RestTemplate更加方便,就跟編寫controller介面是差不多的。
需要寫上@FeignClient註解,裡面配置微服務名字和rest的@RequestMapping(“/api/store”),或者可以在聲明調用pai的時候寫上完整的路徑。
簡單的對應如下圖所示
在這裡插入圖片描述
程式碼如下:

package com.lyd.demo.feign;
import com.lyd.demo.feign.config.FeignOkhttpConfig;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.Map;
/**
 * @author: lyd
 * @description: 遠程調用 service-store 服務
 * @Date: 2022/9/24
 * 介紹:
 *      name / value : 要調用的微服務名
 *      path:控制類上面的路徑 --- @RequestMapping("/api/store")
 */
@FeignClient(name = "service-store", path = "/api/store")
public interface StoreFeignService {
    // 聲明要調用的rest
    @GetMapping("/{id}")
    Map<String, Object> getStoreNum(@PathVariable String id);
}
/**
 * @RestController
 * @RequestMapping("/api/store")
 * public class StoreController {
 *     @Value("${server.port}")
 *     private String currentPort;
 *     @GetMapping("/{id}")
 *     public Map<String, Object> getStoreNum(@PathVariable String id) throws InterruptedException {
 *         Map<String, Object> map = new HashMap<>();
 *         map.put("port", currentPort);
 *         map.put("num", 10);
 *         return map;
 *     }
 * }
 */

需要在啟動類中寫上註解 @EnableFeignClients

@Autowired
private StoreFeignService storeFeignService;
// 在業務中直接調用
storeFeignService.getStoreNum(uid);

OpenFeign自定義配置

Feign 提供了很多的擴展機制,讓用戶可以更加靈活的使用。
feign.Logger.Level:修改日誌級別,包含四種不同的級別:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder:響應結果的解析器,http遠程調用的結果做解析,例如解析json字元串為java對象
feign.codec.Encoder:請求參數編碼,將請求參數編碼,便於通過http請求發送
feign. Contract:支援的註解格式,默認是SpringMVC的註解
feign. Retryer:失敗重試機制,請求失敗的重試機制,默認是沒有,不過會使用Ribbon的重試

日誌配置

可以通過配置Feign的日誌級別來顯示需要的日誌。

1)、定義配置類

定義一個feign的配置文件,並交給spring管理。
feign的日誌級別一開始默認是NONE,不顯示任何的日誌,可以通過定義一個bean,返回日誌的級別

package com.lyd.demo.feign.config;
import feign.Logger;
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import java.util.concurrent.TimeUnit;
/**
 * @author: lyd
 * @description: feign配置文件 - 日誌
 * @Date: 2022/9/24
 */
@Configuration
public class FeignConfig {
    @Bean
    public Logger.Level feignLoggerLevel() {
        return Logger.Level.BASIC;
    }
}

日誌級別有四種:

  • NONE【性能最佳,適用於生產】:不記錄任何日誌(默認值)。
  • BASIC【適用於生產環境追蹤問題】:僅記錄請求方法、URL、響應狀態程式碼以及執行時間。
  • HEADERS:記錄BASIC級別的基礎上,記錄請求和響應的header。
  • FULL【比較適用於開發及測試環境定位問題】:記錄請求和響應的header、body和元數據。

2)、配置文件設置級別

springboot默認的級別是info,級別比較高,需要在配置文件中配置,如果只在loggin.level下配置級別,就是全局配置,所以我們可以指定包,指定哪個包下面的日誌級別。

logging:
  level:
    com.lyd.demo.feign: debug

3)、配置域

全局配置:
在feign配置類加上@Configuration註解,直接丟給spring來管理,達成全局配置。
局部配置:
①、局部配置可以通過在feign客戶端中指定配置文件,只需要在註解後面加上指定配置類

@FeignClient(name = "service-store", path = "/api/store", configuration = FeignConfig.class)

②、局部配置還可以直接通過yml配置文件來指定。

feign:
  client:
    config:
      service-goods: FULL # 指定哪個服務,並且賦上類型。

配置超時時間

通過yml直接配置超時時間

feign:
  client:
    config:
      default: # 這裡用default就是全局配置,如果是寫服務名稱,則是針對某個微服務的配置
        connectTimeout: 2000
        readTimeout: 2000

在store服務中加個Thread.sleep(5000),就能看到報超時異常SocketTimeoutException。
image

重試機制配置

通過加入bean來實現
創建重試器 (重試周期(50毫秒),最大重試周期(2000毫秒),最多嘗試次數 3次 ),feign沒有採用線性的重試機制而是採用的是一種指數級(乘法)的重試機制 每次重試時間 當前重試時間*= 1.5

@Bean
public Retryer retryer() {
    return new Retryer.Default(50, TimeUnit.SECONDS.toMillis(2), 3);
}

在來看看default的構造器,就能更清楚參數含義。

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

image
如圖,會進行重試,直到最後報出異常。
不僅如此,還可以配置契約設置,添加攔截器等等。。。

Feign使用優化

Feign底層發起http請求,依賴於其它的框架。其底層客戶端實現包括:

  • URLConnection:默認實現,不支援連接池
  • Apache HttpClient :支援連接池
  • OKHttp:支援連接池

這次就採用OkHttp

導入依賴

<!--okHttp-->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

設置配置類

package com.lyd.demo.feign.config;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
 * @author: lyd
 * @description: OkHttpFeign 的配置
 * @Date: 2022/9/24
 */
@Configuration
@ConditionalOnClass({OkHttpClient.class})
@ConditionalOnProperty({"feign.okhttp.enabled"})
public class FeignOkhttpConfig {
    @Bean
    public okhttp3.OkHttpClient okHttpClient(OkhttpProperties okhttpProperties) {
        return new okhttp3.OkHttpClient.Builder()
                //設置連接超時
                .connectTimeout(okhttpProperties.getConnectTimeout(), TimeUnit.MILLISECONDS)
                //設置讀超時
                .readTimeout(okhttpProperties.getReadTimeout(), TimeUnit.MILLISECONDS)
                //是否自動重連
                .retryOnConnectionFailure(true)
                .connectionPool(new ConnectionPool())
                .addInterceptor(new OkHttpLogInterceptor())
                //構建OkHttpClient對象
                .build();
    }
}

yml配置

feign:
  client:
    config:
      default:
        connectTimeout: 2000
        readTimeout: 2000
  httpclient:
    enabled: false
  okhttp:
    enabled: true
    connectTimeout: 4000
    readTimeout: 3000

通過類獲取超時時間

package com.lyd.demo.feign.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author: lyd
 * @description: 配置參數
 * @Date: 2022/9/24
 */
@Data
@Component
@ConfigurationProperties(prefix = "feign.okhttp")
public class OkhttpProperties {
    private Long connectTimeout;
    private Long readTimeout;
}

攔截器

可以在攔截器中配置業務需求的程式碼。

package com.lyd.demo.feign.config;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import java.io.IOException;
/**
 * @author: lyd
 * @description: 攔截器
 * @Date: 2022/9/24
 */
@Slf4j
public class OkHttpLogInterceptor implements Interceptor {
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        //這個chain裡面包含了request和response,所以你要什麼都可以從這裡拿
        Request request = chain.request();
        long t1 = System.nanoTime();//請求發起的時間
        log.info(String.format("發送請求 %s on %s%n%s",
                request.url(), chain.connection(), request.headers()));
        Response response = chain.proceed(request);
        long t2 = System.nanoTime();//收到響應的時間
        //注意這裡不能直接使用response.body().string()的方式輸出日誌
        //因為response.body().string()之後,response中的流會被關閉,程式會報錯,我們需要創建出一個新的response給應用層處理
        ResponseBody responseBody = response.peekBody(1024 * 1024);
        log.info(String.format("接收響應: [%s] %n返回json:【%s】 %.1fms%n%s",
                response.request().url(),
                responseBody.string(),
                (t2 - t1) / 1e6d,
                response.headers()));
        return response;
    }
}

引入配置

@FeignClient(name = "service-store", path = "/api/store", configuration = FeignOkhttpConfig.class)

運行結果:
image

👍創作不易,可能有些語言不是很通暢,如有錯誤請指正,感謝觀看!記得點贊哦!👍