
文章目錄
- 前言
- 一、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是Netflix公司寫的,是SpringCloud元件中的一個輕量級RESTful的HTTP服務用戶端,是SpringCloud中的第一代負載均衡用戶端。Feign:
也就是說Feign有缺陷,是以OpenFeign就産生了。
- -
是Spring CLoud的二級子項目,是基于Feign的基礎上進行了完善和優化,Openfeign:
,支援了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聲明
【實作步驟】
- 引入依賴
<!--OpenFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 在服務消費者端,寫一個調用服務的接口
@Component
@FeignClient(name = "stock-service",path = "/stock")
public interface FeignStockService {
@RequestMapping("/reduct")
public String name();
}
- @FeignClient:指明需要調用的服務的名稱以及路徑和失敗回調,這個注解
;如果未指定,會掃描@EnableFeignClients指定的包
則會找@FeignClient指定的服務名,在注冊中心找對應的服務IP萬層服務調用
- name:
,如果項目使用了Ribbon,name屬性會作為微服務的名稱,用于服務發現指定FeignClient的名稱
- path:
,當我們項目中配置了server.context-path,server.servlet-path時使用定義目前FeignClient的統一字首
- fallbackFactory:
,通過這個屬性我們可以實作每個接口通用的容錯邏輯,減少重複的代碼工廠類,用于生成fallback類示例
- fallback:
定義容錯的處理類,當調用遠端接口失敗或逾時時,會調用對應接口的容錯邏輯,fallback指定的類必須實作@FeignClient标記的接口
- configuration:
,可以自定義Feign的Encoder、Decoder、LogLevel、ContractFeign配置類
- url:
url一般用于調試
在寫FeignClients的時候,我們可以找一個規律,就是這裡的接口方法實際就是從服務提供者的controller裡粘貼過來的方法,如下圖所示:
- 控制層實作調用
@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,去擷取服務。很友善,但是又有一個新的問題❓
就是我們又要去寫很多遍的服務接口方法—這樣耦合度還是很高,還不夠優雅—接着改
- 在啟動類上加上
注解,表示自動開啟Feign遠端調用。@EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient // nacos版本在1.4以後就可以不用添加 表示nacos啟動用戶端
@EnableFeignClients // 開啟Feign調用
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
三、程式設計式服務調用🔥🔥
我們可以發現,雖然OpenFeign已經使得我們在調用服務的時候簡化了一些備援操作,但是又引出了一些新的問題。是以我們在公司開發項目的時候,是不太可能會使用上述的方式實作,最佳實踐如下操做:
【實作步驟】
- 在建立一個Maven子產品,用于封裝定義的接口API方法
【好處】
,将公共的feign的調用接口抽取成一個公共的子產品,通過GAV引入到服務提供者和服務消費者的子產品下,這樣就可以實作服務間的調用通信。更加優雅
這樣做的目的就是實作解耦合
- 在公共的接口子產品裡,隻需要寫一個接口,将原來寫在服務消費端的接口定義子這裡
/**
* 定義公共接口子產品
* 類似于之前的服務接口一樣,隻不過在微服務裡面我們是通過子產品 引入gav實作調用
* 原理大同小異
* @author wangruoxian
*
*/
public interface CommonStockApi {
@RequestMapping("/reduct")
public String name();
}
到這裡,我們需要将它maven install以下生成對應的jar包。友善其他子產品引用
- 在服務提供這裡的控制層實作此接口
先引入公共接口的坐标
<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;
}
}
- 最後在服務的消費者端的接口就隻需要繼承公共接口,而不要聲明定義。
這樣就将服務消費者與提供者之間的耦合度消除了。
@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:
4.1.2 局部配置
【編碼的方式配置局部日志】
要使用局部配置就不需要在寫@Configuration注解,隻需要在@feignClient裡聲明
configuration
即可
/**
* 指定輸出Product服務的日志資訊
* @author wangruoxian
*
*/
@FeignClient(name = "server-product",path = "/product",configuration = MyFeignConfig.class)
public interface FeignProductService extends CommonProductApi {}
這裡僅有product的日志資訊
補充: 局部配置還可以寫在yml配置檔案裡
feign:
client:
config:
server-product: # 對應的服務名稱
logger-level:
這裡很明顯的觀察到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;
}
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:
-
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;
}
}