SpringCloud下基于OpenFeign的服務(wù)調(diào)用實(shí)踐
Feign是一個(gè)聲明式的Web Service客戶端,而OpenFeign則是Spring Cloud在Feign的基礎(chǔ)上增強(qiáng)了對Spring MVC注解的支持。其提供了比RestTemplate更加優(yōu)雅、便捷的服務(wù)調(diào)用方式

服務(wù)提供者
服務(wù)提供者payment的部分實(shí)現(xiàn)如下,可以看到其暴露了兩個(gè)接口 pay/hello1、pay/hello2 用于給消費(fèi)者調(diào)用
@RestController
@RequestMapping("pay")
@Slf4j
public?class?PaymentController?{
????@Value("${server.port}")
????private?String?serverPort;
????@GetMapping("/hello1")
????public?String?hello1(@RequestParam?String?name)?{
????????String?msg?=?"[Payment?Service-"+?serverPort?+"]:?"?+?name;
????????return?msg;
????}
????@PostMapping("/hello2")
????public?Person?hello2(@RequestBody?Person?person)?{
????????person.setServicePort(?serverPort?);
????????String?uuid?=?UUID.randomUUID().toString();
????????person.setId(?uuid?);
????????return?person;
????}
}
服務(wù)消費(fèi)者
對于服務(wù)消費(fèi)者order而言,首先需要引入OpenFeign依賴
<dependencyManagement>
??<dependencies>
????
????<dependency>
??????<groupId>org.springframework.bootgroupId>
??????<artifactId>spring-boot-dependenciesartifactId>
??????<version>2.2.2.RELEASEversion>
??????<type>pomtype>
??????<scope>importscope>
????dependency>
????
????<dependency>
??????<groupId>org.springframework.cloudgroupId>
??????<artifactId>spring-cloud-dependenciesartifactId>
??????<version>Hoxton.SR1version>
??????<type>pomtype>
??????<scope>importscope>
????dependency>
??dependencies>
dependencyManagement>
<dependencies>
??
??
??<dependency>
????<groupId>org.springframework.cloudgroupId>
????<artifactId>spring-cloud-starter-openfeignartifactId>
??dependency>
dependencies>
為了調(diào)用payment服務(wù),我們直接定義接口即可。特別地,該接口需要添加@Component注解。具體地,用@FeignClient注解的name、path屬性分別定義所調(diào)用服務(wù)的服務(wù)名、url路徑。對于接口中方法,可以通過@RequestMapping等注解定義所調(diào)用服務(wù)具體的url路徑。對于傳參,類似于Controller層參數(shù)綁定一樣,通過@RequestParam、@PathVariable、@RequestBody等注解綁定參數(shù)。不同的是,對于@RequestParam、@PathVariable而言,需要通過value屬性顯式設(shè)置參數(shù)名。示例如下
@Component
@FeignClient(name?=?"payment",?path?=?"pay"?)
public?interface?PaymentService?{
????@GetMapping("/hello1")
????//?需要通過@RequestParam注解的value屬性顯式指定參數(shù)名
????String?method1(@RequestParam(value?=?"name")?String?name);
????@PostMapping("/hello2")
????Person?method2(@RequestBody?Person?person);
}
然后,我們就可以直接注入該接口paymentService來進(jìn)行服務(wù)調(diào)用,示例如下
@RestController
@RequestMapping("order2")
public?class?OrderController2?{
????@Autowired
????private?PaymentService?paymentService;
????@GetMapping("/test1")
????public?String?test1(@RequestParam?String?name)?{
????????String?msg?=?paymentService.method1(name)?;
????????String?result?=?"[Order?Service?test1]:?"?+?msg;
????????return?result;
????}
????@GetMapping("/test2")
????public?String?test2(@RequestParam?String?name,?@RequestParam?Integer?age)?{
????????Person?person?=?Person.builder()
????????????.name(?name?)
????????????.age(?age?)
????????????.build();
????????person?=?paymentService.method2(person);
????????String?result?=?"[Order?Service?test2]:?"?+?person;
????????return?result;
????}
}
最后,需要在啟動類上添加 @EnableFeignClients 注解,如下所示
@SpringBootApplication
//?使用Consul作為注冊中心
@EnableDiscoveryClient
//?啟用Feign客戶端
@EnableFeignClients
public?class?OrderApplication?{
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(OrderApplication.class,?args);
????}
}
測試
至此,我們對于服務(wù)提供者payment分別在8004、8005、8006創(chuàng)建三個(gè)實(shí)例,對于服務(wù)消費(fèi)者order創(chuàng)建一個(gè)運(yùn)行在82端口的實(shí)例。并通過Consul作為注冊中心。測試結(jié)果如下,符合預(yù)期。且進(jìn)一步說明OpenFeign默認(rèn)支持Ribbon,實(shí)現(xiàn)了負(fù)載均衡

超時(shí)配置
OpenFeign調(diào)用服務(wù)時(shí),默認(rèn)超時(shí)配置為1秒。故可通過 feign.client.config.
#?配置名為payment的OpenFegin客戶端超時(shí)參數(shù)
feign:
??client:
????config:
??????payment:
????????#?建立連接超時(shí)閾值,?Unit:?ms
????????connectTimeout:?10000
????????#?讀取資源超時(shí)閾值,?Unit:?ms
????????readTimeout:?10000
日志配置
OpenFeign特別提供了日志功能,實(shí)現(xiàn)了對服務(wù)接口調(diào)用的監(jiān)控。具體有下述四種日志級別
NONE:默認(rèn)的,不顯示任何日志 BASIC:顯示請求方法、URL、狀態(tài)碼及執(zhí)行時(shí)間 HEADERS:除了BASIC中定義的信息之外,還會顯示請求頭、響應(yīng)頭 FULL:除了HEADERS中定義的信息之外,還會顯示請求體、響應(yīng)體及元數(shù)據(jù)
這里,我們利用Java配置類定義OpenFeign的日志級別
package?com.aaron.SpringCloud1.order.config;
import?feign.Logger;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;
@Configuration
public?class?OpenFeignLogConfig?{
????/**
?????*?配置OpenFeign日志級別
?????*?@return
?????*/
????@Bean
????public?Logger.Level?openFeignLogLevel()?{
????????return?Logger.Level.FULL;
????}
}
然后,將我們期望監(jiān)控的OpenFeign接口的日志級別設(shè)為DEBUG即可,如下所示
logging:
??level:
????#?將PaymentService接口的日志級別設(shè)為DEBUG
????com.aaron.SpringCloud1.order.service.PaymentService:?DEBUG
測試效果,如下所示,符合預(yù)期

服務(wù)降級
OpenFeign不僅支持Ribbon實(shí)現(xiàn)負(fù)載均衡,與此同時(shí)其還依賴了Hystrix以支持服務(wù)降級。故在服務(wù)消費(fèi)者order中實(shí)現(xiàn)PaymentService接口,以提供該接口的降級類。實(shí)現(xiàn)相應(yīng)方法的降級邏輯
@Service
public?class?PaymentFallBackService?implements?PaymentService?{
????@Override
????public?String?method1(String?name)?{
????????return?"invalid";
????}
????@Override
????public?Person?method2(Person?person)?{
????????person.setId(?null?);
????????person.setServicePort("-1");
????????return?person;
????}
}
然后在PaymentService接口上,通過@FeignClient注解的fallback屬性指定其相應(yīng)的降級類
@Component
@FeignClient(name?=?"payment",?path?=?"pay",?fallback?=?PaymentFallBackService.class)
public?interface?PaymentService?{
????@GetMapping("/hello1")
????String?method1(@RequestParam(value?=?"name")?String?name);
????@PostMapping("/hello2")
????Person?method2(@RequestBody?Person?person);
}
對于order服務(wù)的配置文件,做相應(yīng)調(diào)整。首先調(diào)整OpenFeign中payment服務(wù)的超時(shí)配置(connectTimeout、readTimeout)為10s,然后將 feign.hystrix.enabled 設(shè)為true,使能OpenFeign的Hystrix功能。最后設(shè)置Hystrix的相關(guān)配置。具體地,我們配置全局默認(rèn)的超時(shí)時(shí)間為3s。當(dāng)然我們也可以針對某個(gè)方法設(shè)置單獨(dú)的超時(shí)配置以覆蓋全局默認(rèn),其中配置項(xiàng)的名稱規(guī)則為 接口名#方法名(參數(shù)類型) 。例如這里設(shè)置PaymentService接口method2方法的超時(shí)時(shí)間為5s,使用的配置項(xiàng)名稱為PaymentService#method2(Person)
feign:
??client:
????config:
??????payment:
????????#?建立連接超時(shí)閾值,?Unit:?ms
????????connectTimeout:?10000
????????#?讀取資源超時(shí)閾值,?Unit:?ms
????????readTimeout:?10000
??#?啟用OpenFeign的Hystrix功能
??hystrix:
??????enabled:?true
hystrix:
??command:
????#?配置全局默認(rèn)的超時(shí)時(shí)間??
????default:
??????execution:
????????isolation:
??????????thread:
????????????timeoutInMilliseconds:?3000
????#?配置PaymentService接口method2方法的超時(shí)時(shí)間
????PaymentService#method2(Person):
??????execution:
????????isolation:
??????????thread:
????????????timeoutInMilliseconds:?6000
現(xiàn)在我們通過調(diào)用order服務(wù)接口來進(jìn)行驗(yàn)證,可以看到正常情況下,通過OpenFeign調(diào)用的服務(wù)均未發(fā)生降級

現(xiàn)在我們對payment服務(wù)進(jìn)行改造,通過Thread.sleep來模擬業(yè)務(wù)耗時(shí),如下所示
@RestController
@RequestMapping("pay")
public?class?PaymentController?{
????@Value("${server.port}")
????private?String?serverPort;
????@GetMapping("/hello1")
????public?String?hello1(@RequestParam?String?name)?{
????????String?msg?=?"[Payment?Service-"+?serverPort?+"]:?"?+?name;
????????//?模擬業(yè)務(wù)耗時(shí)
????????try{?Thread.sleep(4000);?}?catch?(Exception?e)?{}
????????
????????return?msg;
????}
????@PostMapping("/hello2")
????public?Person?hello2(@RequestBody?Person?person)?{
????????person.setServicePort(?serverPort?);
????????String?uuid?=?UUID.randomUUID().toString();
????????person.setId(?uuid?);
????????//?模擬業(yè)務(wù)耗時(shí)
????????try{?Thread.sleep(8000);?}?catch?(Exception?e)?{}
????????
????????return?person;
????}
}
可以看到兩個(gè)方法均進(jìn)行降級,測試結(jié)果符合預(yù)期,如下所示

參考文獻(xiàn)
Spring微服務(wù)實(shí)戰(zhàn) John Carnell著
