在微服務的實踐過程當中,Spring Cloud Ribbon 和 Spring Cloud Hystrix 一般一塊兒使用。java
Spring Cloud Feign 是對這兩個基礎工具的更高層次封裝,在 Netflix Feign 的基礎上擴展了對 Spring MVC 的註解支持,提供了一種聲明式的 Web 服務客戶端定義方式。git
啓動服務註冊中心 eureka-server 及服務提供方 hello-service,建立 spring boot 工程 feign-consumer。github
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
複製代碼
package com.ulyssesss.feignconsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
複製代碼
package com.ulyssesss.feignconsumer.service;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient("hello-service")
public interface HelloService {
@GetMapping("hello")
String hello(@RequestParam("p1") String p1, @RequestParam("p2") String p2);
}
複製代碼
其中 @FeignClient 指定服務名,Spring MVC 註解綁定具體的 REST 接口及請求參數。web
注意在定義參數綁定時,@RequestParam 、@RequestHeader 等註解的 value 不能省略,Spring MVC 會根據參數名做爲默認值,但 Feign 中必須經過 value 指定。spring
package com.ulyssesss.feignconsumer.web;
import com.ulyssesss.feignconsumer.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FeignConsumerController {
@Autowired
HelloService helloService;
@GetMapping("hello")
public String hello(@RequestParam String p1, @RequestParam String p2) {
System.out.println("feign consumer get hello");
return helloService.hello(p1, p2);
}
}
複製代碼
spring.application.name=feign-consumer
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
複製代碼
啓動所有應用,訪問 http://localhost:8080/hello?p1=a&p2=b ,feign-consumer 以聲明式的服務調用訪問 hello-service,返回 hello, a, b
。api
當使用 Spring MVC 註解來綁定服務接口時,幾乎能夠徹底從服務提供方的 Controller 中複製,因此可以利用 Feign 的繼承特性作進一步抽象,複用 REST 接口定義,減小編碼量。app
建立 Maven 工程 hello-service-api,因爲須要用到 Spring MVC 的註解,因此添加 spring-boot-starter-web 依賴。負載均衡
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
複製代碼
在 hello-service-api 中定義可以複用的 DTO 和接口定義。ide
package com.ulyssesss.helloserviceapi.service;
import com.ulyssesss.helloserviceapi.dto.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
public interface HelloService {
@GetMapping("hello")
String hello(@RequestParam("p1") String p1, @RequestParam("p2") String p2);
@GetMapping("user")
User user();
@PostMapping("post")
String post();
}
複製代碼
package com.ulyssesss.helloserviceapi.dto;
public class User {
private String name;
private int age;
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
}
// set get
}
複製代碼
在 hello-service 中引入 hello-service-api 依賴,重寫 HelloController。spring-boot
package com.ulyssesss.helloservice.web;
import com.ulyssesss.helloserviceapi.dto.User;
import com.ulyssesss.helloserviceapi.service.HelloService;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController implements HelloService {
@Override
public String hello(@RequestParam String p1, @RequestParam String p2) {
System.out.println("hello service get hello");
return "hello, " + p1 + ", " + p2;
}
@Override
public User user() {
System.out.println("hello service get user");
return new User("Jack", 22);
}
@Override
public String post() {
System.out.println("hello service post");
return "post";
}
}
複製代碼
在 feign-consumer 中引入 hello-service-api 依賴,建立 RefactorHelloService 繼承自 HelloService。
package com.ulyssesss.feignconsumer.service;
import com.ulyssesss.helloserviceapi.service.HelloService;
import org.springframework.cloud.netflix.feign.FeignClient;
@FeignClient(name = "hello-service")
public interface RefactorHelloService extends HelloService {
}
複製代碼
修改 FeignConsumerController,注入 RefactorHelloService。
package com.ulyssesss.feignconsumer.web;
import com.ulyssesss.feignconsumer.service.RefactorHelloService;
import com.ulyssesss.helloserviceapi.dto.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FeignConsumerController {
@Autowired
RefactorHelloService refactorHelloService;
@GetMapping("hello")
public String hello(@RequestParam String p1, @RequestParam String p2) {
System.out.println("feign consumer get hello");
return refactorHelloService.hello(p1, p2);
}
@GetMapping("user")
public User user() {
System.out.println("feign consumer get user");
return refactorHelloService.user();
}
@PostMapping("post")
public String post() {
System.out.println("feign consumer post");
return refactorHelloService.post();
}
}
複製代碼
啓動所有應用,訪問 http://localhost:8080/hello?p1=a&p2=b ,feign-consumer 以聲明式的服務調用訪問 hello-service,返回 hello, a, b
。
使用 Spring Cloud Feign 的繼承特性,能夠將接口從 Controller 中玻璃,配合 Maven 私有倉庫能夠實現接口定義的共享,減小服務消費方的綁定配置。
Spring Cloud Feign 的客戶端負載均衡經過 Spring Cloud Ribbon 實現,經過配置 Ribbon 能夠定義客戶端調用參數。Riibon 和 Hystrix 的配置以下:
spring.application.name=feign-consumer
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
## 啓用 hystrix
feign.hystrix.enabled=true
## 全局超時熔斷時間
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
## 全局鏈接超時時間
ribbon.ConnectTimeout=250
## 全局接口調用超時時間
ribbon.ReadTimeout=10000
## 全局重試全部請求(POST 請求等)開關
ribbon.OkToRetryOnAllOperations=false
## 針對 hello-service 服務,重試切換的實例數
hello-service.ribbon.MaxAutoRetriesNextServer=1
## 針對 hello-service 服務,對當前實例重試次數
hello-service.ribbon.MaxAutoRetries=0
複製代碼
Hystrix 提供的服務降級是容錯的重要方法,因爲 Feign 在定義服務客戶端時將 HystrixCommand 的定義進行了封裝,致使沒法使用 @HystrixCommand 的 fallback 參數指定降級邏輯。
Spring Cloud Feign 提供了一種簡單的定義服務降級的方式,建立 HelloServiceFallback 實現 HelloService 接口,經過 @Component 聲明爲 Bean,在 HelloServiceFallback 中實現具體的降級邏輯,最後在 @FeignClient 中經過 fallback 屬性聲明處理降級邏輯的 Bean。
package com.ulyssesss.feignconsumer.service;
import com.ulyssesss.helloserviceapi.dto.User;
import org.springframework.stereotype.Component;
@Component
public class HelloServiceFallback implements RefactorHelloService {
@Override
public String hello(String p1, String p2) {
return "error";
}
@Override
public User user() {
return new User("error", 0);
}
@Override
public String post() {
return "error";
}
}
複製代碼
package com.ulyssesss.feignconsumer.service;
import com.ulyssesss.helloserviceapi.service.HelloService;
import org.springframework.cloud.netflix.feign.FeignClient;
@FeignClient(name = "hello-service", fallback = HelloServiceFallback.class)
public interface RefactorHelloService extends HelloService {
}
複製代碼
啓動服務後斷開服務提供方 hello-service,訪問 feign-consumer 接口,feign-consumer 會按照 HelloServiceFallback 中的定義執行降級邏輯。
示例代碼 歡迎 Star