1、前言
前面介紹了Spring Cloud 中的靈魂擺渡者Nacos
,和它的前輩們相比不僅僅功能強大,而且部署非常簡單。
今天介紹一款服務調用的組件:OpenFeign
,同樣是一款超越先輩(Ribbon
、Feign
)的狠角色。
文章目錄如下:
2、Feign是什么?
Feign也是一個狠角色,Feign旨在使得Java Http客戶端變得更容易。
Feign集成了Ribbon、RestTemplate實現了負載均衡的執行Http調用,只不過對原有的方式(Ribbon+RestTemplate)進行了封裝,開發者不必手動使用RestTemplate調服務,而是定義一個接口,在這個接口中標注一個注解即可完成服務調用,這樣更加符合面向接口編程的宗旨,簡化了開發。
但遺憾的是Feign現在停止迭代了,當然現在也是有不少企業在用。
有想要學習Feign的讀者可以上spring Cloud官網學習,陳某這里也不再詳細介紹了,不是今天的重點。
3、openFeign是什么?
前面介紹過停止迭代的Feign,簡單點來說:OpenFeign是springcloud在Feign的基礎上支持了SpringMVC的注解,如@RequestMapping
等等。OpenFeign的@FeignClient
可以解析SpringMVC的@RequestMapping
注解下的接口,並通過動態代理的方式產生實現類,實現類中做負載均衡並調用其他服務。
官網地址:https://docs.spring.io/spring-cloud-openfeign/docs/2.2.10.BUILD-SNAPSHOT/reference/html
4、Feign和openFeign有什么區別?
Feign | openFiegn |
---|---|
Feign是SpringCloud組件中一個輕量級RESTful的HTTP服務客戶端,Feign內置了Ribbon,用來做客戶端負載均衡,去調用服務注冊中心的服務。Feign的使用方式是:使用Feign的注解定義接口,調用這個接口,就可以調用服務注冊中心的服務 | OpenFeign 是SpringCloud在Feign的基礎上支持了SpringMVC的注解,如@RequestMapping等。OpenFeign 的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,並通過動態代理的方式產生實現類,實現類中做負載均衡並調用其他服務。 |
5、環境准備
本篇文章Spring Cloud版本、JDK環境、項目環境均和上一篇Nacos的環境相同:五十五張圖告訴你微服務的靈魂擺渡者Nacos究竟有多強?。
注冊中心就不再使用Eureka
了,直接使用Nacos
作為注冊和配置中心,有不會的可以查看Nacos文章。
本篇文章搭建的項目結構如下圖:
注冊中心使用Nacos,創建個微服務,分別為服務提供者Produce,服務消費者Consumer。
6、創建服務提供者
既然是微服務之間的相互調用,那么一定會有服務提供者了,創建openFeign-provider9005
,注冊進入Nacos中,配置如下:
server:
port: 9005
spring:
application:
## 指定服務名稱,在nacos中的名字
name: openFeign-provider
cloud:
nacos:
discovery:
# nacos的服務地址,nacos-server中IP地址:端口號
server-addr: 127.0.0.1:8848
management:
endpoints:
web:
exposure:
## yml文件中存在特殊字符,必須用單引號包含,否則啟動報錯
include: '*'
注意:此處的spring.application.name
指定的名稱將會在openFeign接口調用中使用。
項目源碼都會上傳,關於如何注冊進入Nacos,添加什么依賴源碼都會有,結合陳某上篇Nacos文章,這都不是難事!
7、創建服務消費者
新建一個模塊openFeign-consumer9006
作為消費者服務,步驟如下。
1、添加依賴
除了Nacos的注冊中心的依賴,還要添加openFeign的依賴,如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、添加注解@EnableFeignClients開啟openFeign功能
老套路了,在Spring boot 主啟動類上添加一個注解@EnableFeignClients
,開啟openFeign功能,如下:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OpenFeignConsumer9006Application
{
public static void main(String[] args) {
SpringApplication.run(OpenFeignConsumer9006Application.class, args);
}
}
3、新建openFeign接口
新建一個openFeign接口,使用@FeignClient
注解標注,如下:
@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
}
注意:該注解
@FeignClient
中的value
屬性指定了服務提供者在nacos注冊中心的服務名。
4、新建一個Controller調試
新建一個controller用來調試接口,直接調用openFeign的接口,如下:
@RestController
@RequestMapping("/openfeign")
public class OpenFeignController {
}
好了,至此一個openFeign的微服務就搭建好了,並未實現具體的功能,下面一點點實現。
8、openFeign如何傳參?
開發中接口傳參的方式有很多,但是在openFeign中的傳參是有一定規則的,下面詳細介紹。
1、傳遞JSON數據
這個也是接口開發中常用的傳參規則,在Spring Boot 中通過@RequestBody
標識入參。
provider接口中JSON傳參方法如下:
@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
@PostMapping("/order2")
public Order createOrder2(@RequestBody Order order){
return order;
}
}
consumer中openFeign接口中傳參代碼如下:
@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
/**
* 參數默認是@RequestBody標注的,這里的@RequestBody可以不填
* 方法名稱任意
*/
@PostMapping("/openfeign/provider/order2")
Order createOrder2(@RequestBody Order order);
}
注意:openFeign
默認的傳參方式就是JSON傳參(@RequestBody
),因此定義接口的時候可以不用@RequestBody
注解標注,不過為了規范,一般都填上。
2、POJO表單傳參
這種傳參方式也是比較常用,參數使用POJO對象接收。
provider服務提供者代碼如下:
@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
@PostMapping("/order1")
public Order createOrder1(Order order){
return order;
}
}
consumer消費者openFeign代碼如下:
@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
/**
* 參數默認是@RequestBody標注的,如果通過POJO表單傳參的,使用@SpringQueryMap標注
*/
@PostMapping("/openfeign/provider/order1")
Order createOrder1(@SpringQueryMap Order order);
}
網上很多人疑惑POJO表單方式如何傳參,官方文檔明確給出了解決方案,如下:
openFeign提供了一個注解@SpringQueryMap
完美解決POJO表單傳參。
3、URL中攜帶參數
此種方式針對restful方式中的GET請求,也是比較常用請求方式。
provider服務提供者代碼如下:
@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
@GetMapping("/test/{id}")
public String test(@PathVariable("id")Integer id){
return "accept one msg id="+id;
}
consumer消費者openFeign接口如下:
@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
@GetMapping("/openfeign/provider/test/{id}")
String get(@PathVariable("id")Integer id);
}
使用注解@PathVariable
接收url中的占位符,這種方式很好理解。
4、普通表單參數
此種方式傳參不建議使用,但是也有很多開發在用。
provider服務提供者代碼如下:
@RestController
@RequestMapping("/openfeign/provider")
public class OpenFeignProviderController {
@PostMapping("/test2")
public String test2(String id,String name){
return MessageFormat.format("accept on msg id={0},name={1}",id,name);
}
}
consumer消費者openFeign接口傳參如下:
@FeignClient(value = "openFeign-provider")
public interface OpenFeignService {
/**
* 必須要@RequestParam注解標注,且value屬性必須填上參數名
* 方法參數名可以任意,但是@RequestParam注解中的value屬性必須和provider中的參數名相同
*/
@PostMapping("/openfeign/provider/test2")
String test(@RequestParam("id") String arg1,@RequestParam("name") String arg2);
}
5、總結
傳參的方式有很多,比如文件傳參.....陳某這里只是列舉了四種常見得傳參方式。
9、超時如何處理?
想要理解超時處理,先看一個例子:我將provider服務接口睡眠3秒鍾,接口如下:
@PostMapping("/test2")
public String test2(String id,String name) throws InterruptedException {
Thread.sleep(3000);
return MessageFormat.format("accept on msg id={0},name={1}",id,name);
}
此時,我們調用consumer的openFeign接口返回結果如下圖:
很明顯的看出程序異常了,返回了接口調用超時。what?why?...........
openFeign其實是有默認的超時時間的,默認分別是連接超時時間10秒
、讀超時時間60秒
,源碼在feign.Request.Options#Options()
這個方法中,如下圖:
那么問題來了:為什么我只設置了睡眠3秒就報超時呢?
其實openFeign集成了Ribbon,Ribbon的默認超時連接時間、讀超時時間都是是1秒,源碼在org.springframework.cloud.openfeign.ribbon.FeignLoadBalancer#execute()
方法中,如下圖:
源碼大致意思:如果openFeign沒有設置對應得超時時間,那么將會采用Ribbon的默認超時時間。
理解了超時設置的原理,由之產生兩種方案也是很明了了,如下:
- 設置openFeign的超時時間
- 設置Ribbon的超時時間
1、設置Ribbon的超時時間(不推薦)
設置很簡單,在配置文件中添加如下設置:
ribbon:
# 值的是建立鏈接所用的時間,適用於網絡狀況正常的情況下, 兩端鏈接所用的時間
ReadTimeout: 5000
# 指的是建立鏈接后從服務器讀取可用資源所用的時間
ConectTimeout: 5000
2、設置openFeign的超時時間(推薦)
openFeign設置超時時間非常簡單,只需要在配置文件中配置,如下:
feign:
client:
config:
## default 設置的全局超時時間,指定服務名稱可以設置單個服務的超時時間
default:
connectTimeout: 5000
readTimeout: 5000
default設置的是全局超時時間,對所有的openFeign接口服務都生效
但是正常的業務邏輯中可能涉及到多個openFeign接口的調用,如下圖:
上圖中的偽代碼如下:
public T invoke(){
//1. 調用serviceA
serviceA();
//2. 調用serviceA
serviceB();
//3. 調用serviceA
serviceC();
}
那么上面配置的全局超時時間能不能通過呢?很顯然是serviceA
、serviceB
能夠成功調用,但是serviceC
並不能成功執行,肯定報超時。
此時我們可以給serviceC
這個服務單獨配置一個超時時間,配置如下:
feign:
client:
config:
## default 設置的全局超時時間,指定服務名稱可以設置單個服務的超時時間
default:
connectTimeout: 5000
readTimeout: 5000
## 為serviceC這個服務單獨配置超時時間
serviceC:
connectTimeout: 30000
readTimeout: 30000
注意:單個配置的超時時間將會覆蓋全局配置。
10、如何開啟日志增強?
openFeign雖然提供了日志增強功能,但是默認是不顯示任何日志的,不過開發者在調試階段可以自己配置日志的級別。
openFeign的日志級別如下:
- NONE:默認的,不顯示任何日志;
- BASIC:僅記錄請求方法、URL、響應狀態碼及執行時間;
- HEADERS:除了BASIC中定義的信息之外,還有請求和響應的頭信息;
- FULL:除了HEADERS中定義的信息之外,還有請求和響應的正文及元數據。
配置起來也很簡單,步驟如下:
1、配置類中配置日志級別
需要自定義一個配置類,在其中設置日志級別,如下:
注意:這里的logger是feign包里的。
2、yaml文件中設置接口日志級別
只需要在配置文件中調整指定包或者openFeign的接口日志級別,如下:
logging:
level:
cn.myjszl.service: debug
這里的cn.myjszl.service
是openFeign接口所在的包名,當然你也可以配置一個特定的openFeign接口。
3、演示效果
上述步驟將日志設置成了FULL
,此時發出請求,日志效果如下圖:
日志中詳細的打印出了請求頭、請求體的內容。
11、如何替換默認的httpclient?
Feign在默認情況下使用的是JDK原生的URLConnection發送HTTP請求,沒有連接池,但是對每個地址會保持一個長連接,即利用HTTP的persistence connection。
在生產環境中,通常不使用默認的http client,通常有如下兩種選擇:
- 使用ApacheHttpClient
- 使用OkHttp
至於哪個更好,其實各有千秋,我比較傾向於ApacheHttpClient,畢竟老牌子了,穩定性不在話下。
那么如何替換掉呢?其實很簡單,下面演示使用ApacheHttpClient替換。
1、添加ApacheHttpClient依賴
在openFeign接口服務的pom文件添加如下依賴:
<!-- 使用Apache HttpClient替換Feign原生httpclient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
為什么要添加上面的依賴呢?從源碼中不難看出,請看org.springframework.cloud.openfeign.FeignAutoConfiguration.HttpClientFeignConfiguration
這個類,代碼如下:
上述紅色框中的生成條件,其中的@ConditionalOnClass(ApacheHttpClient.class)
,必須要有ApacheHttpClient
這個類才會生效,並且feign.httpclient.enabled
這個配置要設置為true
。
2、配置文件中開啟
在配置文件中要配置開啟,代碼如下:
feign:
client:
httpclient:
# 開啟 Http Client
enabled: true
3、如何驗證已經替換成功?
其實很簡單,在feign.SynchronousMethodHandler#executeAndDecode()
這個方法中可以清楚的看出調用哪個client,如下圖:
上圖中可以看到最終調用的是ApacheHttpClient
。
4、總結
上述步驟僅僅演示一種替換方案,剩下的一種不再演示了,原理相同。
12、如何通訊優化?
在講如何優化之前先來看一下GZIP 壓縮算法,概念如下:
gzip是一種數據格式,采用用deflate算法壓縮數據;gzip是一種流行的數據壓縮算法,應用十分廣泛,尤其是在Linux平台。
當GZIP壓縮到一個純文本數據時,效果是非常明顯的,大約可以減少70%以上的數據大小。
網絡數據經過壓縮后實際上降低了網絡傳輸的字節數,最明顯的好處就是可以加快網頁加載的速度。網頁加載速度加快的好處不言而喻,除了節省流量,改善用戶的瀏覽體驗外,另一個潛在的好處是GZIP與搜索引擎的抓取工具有着更好的關系。例如 Google就可以通過直接讀取GZIP文件來比普通手工抓取更快地檢索網頁。
GZIP壓縮傳輸的原理如下圖:
按照上圖拆解出的步驟如下:
- 客戶端向服務器請求頭中帶有:
Accept-Encoding:gzip,deflate
字段,向服務器表示,客戶端支持的壓縮格式(gzip或者deflate),如果不發送該消息頭,服務器是不會壓縮的。 - 服務端在收到請求之后,如果發現請求頭中含有
Accept-Encoding
字段,並且支持該類型的壓縮,就對響應報文壓縮之后返回給客戶端,並且攜帶Content-Encoding:gzip
消息頭,表示響應報文是根據該格式壓縮過的。 - 客戶端接收到響應之后,先判斷是否有Content-Encoding消息頭,如果有,按該格式解壓報文。否則按正常報文處理。
openFeign支持請求/響應開啟GZIP壓縮,整體的流程如下圖:
上圖中涉及到GZIP傳輸的只有兩塊,分別是Application client -> Application Service、 Application Service->Application client。
注意:openFeign支持的GZIP僅僅是在openFeign接口的請求和響應,即是openFeign消費者調用服務提供者的接口。
openFeign開啟GZIP步驟也是很簡單,只需要在配置文件中開啟如下配置:
feign:
## 開啟壓縮
compression:
request:
enabled: true
## 開啟壓縮的閾值,單位字節,默認2048,即是2k,這里為了演示效果設置成10字節
min-request-size: 10
mime-types: text/xml,application/xml,application/json
response:
enabled: true
上述配置完成之后,發出請求,可以清楚看到請求頭中已經攜帶了GZIP壓縮,如下圖:
13、如何熔斷降級?
常見的熔斷降級框架有Hystrix
、Sentinel
,openFeign默認支持的就是Hystrix
,這個在官方文檔上就有體現,畢竟是一奶同胞嘛,哈哈...........
但是阿里的Sentinel無論是功能特性、簡單易上手等各方面都完全秒殺Hystrix,因此此章節就使用openFeign+Sentinel進行整合實現服務降級。
說明:此處並不着重介紹Sentinel,陳某打算放在下一篇文章詳細介紹Sentinel的強大之處。
1、添加Sentinel依賴
在openFeign-consumer9006
消費者的pom文件添加sentinel依賴(由於使用了聚合模塊,不指定版本號),如下:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、配置文件中開啟sentinel熔斷降級
要想openFeign使用sentinel的降級功能,還需要在配置文件中開啟,添加如下配置:
feign:
sentinel:
enabled: true
3、添加降級回調類
這個類一定要和openFeign接口實現同一個類,如下圖:
OpenFeignFallbackService
這個是降級回調的類,一旦OpenFeignService
中對應得接口出現了異常則會調用這個類中對應得方法進行降級處理。
4、添加fallback屬性
在@FeignClient
中添加fallback
屬性,屬性值是降級回調的類,如下:
@FeignClient(value = "openFeign-provider",fallback = OpenFeignFallbackService.class)
public interface OpenFeignService {}
5、演示
經過如上4個步驟,openFeign的熔斷降級已經設置完成了,此時演示下效果。
通過postman調用http://localhost:9006/openfeign/order3
這個接口,正常邏輯返回如下圖:
現在手動造個異常,在服務提供的接口中拋出異常,如下圖:
此時重新調用http://localhost:9006/openfeign/order3
,返回如下圖:
哦豁,可以很清楚的看到服務已經成功降級調用,哦了,功能完成。
注意:實際開發中返回結果應該根據架構統一定制,陳某這里只是為了演示方便,不要借鑒,哈哈。。。
14、總結
本篇文章主要面對初學者,深入的源碼以及熔斷降級放在后面詳細介紹,文中若有表述不清,錯誤的地方歡迎指正!
這是陳某Spring Cloud 進階專欄的第二篇文章,覺得文章不錯的,歡迎點贊、收藏、轉發。
以上源碼已經上傳GitHub,需要的公號【碼猿技術專欄】回復關鍵詞
9528
獲取。