1. 程式人生 > >Spring Cloud Feign(宣告式服務呼叫)(2)

Spring Cloud Feign(宣告式服務呼叫)(2)

繼承特性

1.首先新建個工程,名為hello-service-api。因為要用到Spring MVC的註解所以要要加鎖spring-boot-web的依賴,具體如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>hello-service-api</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>hello-service-api</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.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>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

2.因為在服務提供者和服務消費者中需要用到User這個類,所以將其提取出來放置到新建的工程中。

3.建立HelloService介面:

@RequestMapping("/refactor")
public interface HelloService {

    @RequestMapping("/hello")
    public String hello();

    @RequestMapping(value = "/hello1", method = RequestMethod.GET)
    String hello(@RequestParam("name") String name);

    @RequestMapping(value = "/hello2", method = RequestMethod.GET)
    Map<String,Object> hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price);

    @RequestMapping(value = "/hello3", method = RequestMethod.POST)
    String hello(@RequestBody Map<String, Object> book);

    @RequestMapping(value = "/hello4", method = RequestMethod.POST)
    String hello(@RequestBody User user);
}

最後的目錄結構:

然後需要對hello-service(服務提供者)和feign-consumer(服務呼叫)進行重構:

4.首先需要在各自的pom.xml檔案中引入對hello-service-api的依賴:

        <dependency>
            <groupId>com.example</groupId>
            <artifactId>hello-service-api</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

5.重寫服務提供者的控制器,改為:


@RestController
@RequestMapping("/refactor")
public class HelloController {

    private final Logger logger=Logger.getLogger(getClass());

    @RequestMapping("/hello")
    public String index() throws InterruptedException {
//        int sleepTime=new Random().nextInt(3000);
//        logger.info("sleep:"+sleepTime);
//        Thread.sleep(sleepTime);
        logger.info(new Date());
        return "Hello"+new Date()+"---"+new Random().nextInt();
    }


    @RequestMapping(value = "/hello1", method = RequestMethod.GET)
    public String hello1(@RequestParam String name) {
        return "hello " + name + "!";
    }

    @RequestMapping(value = "/hello2", method = RequestMethod.GET)
    public Map<String,Object> hello2(@RequestHeader String name, @RequestHeader String author, @RequestHeader Integer price) throws UnsupportedEncodingException {
        Map<String,Object> map = new HashMap<String,Object>();
        map.put("name",URLDecoder.decode(name));
        map.put("author",URLDecoder.decode(author));
        return map;
    }


    @RequestMapping(value = "/hello3", method = RequestMethod.POST)
    public String hello3(@RequestBody Map<String,Object> book) {
        return "書名為:" + book.get("name") + ";作者為:" + book.get("author");
    }

    @RequestMapping(value = "/hello4", method = RequestMethod.POST)
    public String hello4(@RequestBody User user) {
        return "使用者名稱:" + user.getName() + ";年齡為:" + user.getAge();
    }

}

需要注意的是在原先的控制器上新增了@RequestMapping("/refactor"),要跟hello-service-api上的RequestMapping相同,這裡也可以用實現(implements)的方式實現。

6.建立HelloServiceImpl實現HelloService介面並新增@FeignClient註解繫結服務。

@FeignClient("hello-service")//用於繫結名為hello-service服務
public interface HelloServiceImpl extends HelloService {
}

7.修改其控制器如:

@RestController
public class ConsumerController {

    //    @Autowired
//    HelloService helloService;
    @Autowired
    HelloServiceImpl helloService;

    @GetMapping("/feign-consumer")
    public String helloConsumer() {
        return helloService.hello();
    }

    @RequestMapping("/hello1")
    public String hello1() {
        return helloService.hello("張三");
    }

    @RequestMapping(value = "/hello2")
    public Map<String, Object> hello2() throws UnsupportedEncodingException {
        Map<String, Object> book = helloService.hello(URLEncoder.encode("三國演義", "UTF-8"), URLEncoder.encode("羅貫中", "UTF-8"), 33);
        System.out.println(book);
        return book;
    }

    @RequestMapping("/hello3")
    public String hello3() {
        Map<String, Object> book = new HashMap<String, Object>();
        book.put("name", "三國演義");
        book.put("author", "羅貫中");
        return helloService.hello(book);
    }

    @RequestMapping("/hello4")
    public String hello4() {
        User user = new User("劉德華", 99);
        return helloService.hello(user);
    }

}

OK這就是Feign的繼承特性

Ribbon配置

由於Spring Cloud Feign的客戶端負載均衡是通過Spring Cloud Ribbon實現,所以我們可以直接通過配置Ribbon客戶端方式來自定義各個服務客戶端呼叫的引數。

全域性配置

全域性配置的方式十分容易,如同Ribbon的配置一樣可以直接使用ribbon.<key>=<value>的方式來設定ribbon的各項預設引數。如修改客戶端呼叫超時時間:

ribbon.ConnectTimeout=500
ribbon.ReadTimeout=5000

指定服務設定

在使用Spring Cloud Feign的時候,針對各個服務客戶端進行個性化配置的方式與使用Spring Cloud Ribbon時的配置方式是一樣的,都採用<client>.ribbon.key=value的格式進行設定。

在定義Feign客戶端時,我們使用了@FeignClient註解。在初始化過程中,Spring Cloud Feign會根據該註解的name屬性或value屬性指定的服務名,自動建立一個同名的Ribbon客戶端。在上面的程式碼中使用的@FeignClient("hello-service")這時的application.properties的配置如:

# 設定針對hello-service服務的連線超時時間
hello-service.ribbon.ConnectTimeout=600
# 設定針對hello-service服務的讀取超時時間
hello-service.ribbon.ReadTimeout=6000
# 設定針對hello-service服務所有操作請求都進行重試
hello-service.ribbon.OkToRetryOnAllOperations=true
# 設定針對hello-service服務切換例項的重試次數
hello-service.ribbon.MaxAutoRetriesNextServer=2
# 設定針對hello-service服務的當前例項的重試次數
hello-service.ribbon.MaxAutoRetries=1

Hystrix配置

在Spring Cloud Feign中除了引入了客戶端負載均衡的Spring Cloud Ribbon之外,還引入了Hystrix。預設情況下Spring Cloud Feign會為所有Feign客戶端方法都封裝到Hystrix命令中進行服務保護。

全域性配置

對於Hystrix的全域性配置同Spring Cloud Ribbon的全域性配置一樣,直接使用它的預設配置字首hystrix.command.default進行設定,如設定全域性超時時間:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=5000

另外對Hystrix進行配置之前,我們需要確認feign.hystrix.enabled引數沒有被設定為false,否則該引數設定會關閉Feign客戶端的Hystrix支援。feign.hystrix.enabled=false可以關閉Hystrix功能,hystrix.command.default.execution.timeout.enabled=false關閉熔斷功能。

禁用Hystrix

除了feign.hystrix.enabled=false可以關閉Hystrix功能,如果想針對某個服務客戶端關閉Hystrix支援可以使用@Scope("prototype")註解為指定的客戶端配置Feign.Builder例項。

@Configuration
public class DisableHystrixConfiguration{


    @Bean
    @Scope("prototype")
    public Builder feignBuilder(){
        return Feign.builder();
    }
}

在HelloService中的@FeignClient註解中,通過configuration引數引入上面實現的配置。

@FeignClient(name="hello-service",configuration=DisableHystrixConfiguration.class)

指定的命令配置

指定命令配置方法也跟傳統的Hystrix 命令的引數配置相似,採用hystrix.command.<commandkey>作為字首,而預設情況下會採用Feign客戶端中的方法名作為標識,比如, 針對hello介面的熔斷超時時間的配置可以通過其方法名作為來進行配置, 具體如下:

hystrix.command.hello.execution.isolation.thread.timeoutinMilliseconds=5OOO 

在使用指定命令配置的時候, 需要注意, 由於方法名很有可能重複, 這個時候相同方法名的Hystrix配置會共用,所以在進行方法定義與配置的時候需要做好一定的規劃。當然,也可以重寫Feign.Builder的實現,並在應用主類中建立它的例項來覆蓋自動化配置的HystrixFeign.Builder實現

服務降級配置

由於Spring Cloud Feign在定義服務客戶端的時候與Spring Cloud Ribbon有很大差別,HystrixCommand定義被封裝了起來, 我們無法像之前介紹Spring Cloud Hystrix時, 通過@HystrixCommand註解的fallback引數那樣來指定具體的服務降級處理方法。但是, Spring Cloud Feign提供了另外一種簡單的定義方式,如下: 

1.繼承HelloService類,實現服務降級邏輯:

@Component
@RequestMapping("fallback/refactor")//必須要加,不然會報bean建立異常
public class HelloServiceFallbackImpl implements HelloService {
    @Override
    public String hello() {
        return "error";
    }

    @Override
    public String hello(@RequestParam("name") String name) {
        return "error1";
    }

    @Override
    public Map<String, Object> hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price) {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("error", "未知");
        return map;
    }

    @Override
    public String hello(@RequestBody Map<String, Object> book) {
        return "error3";
    }

    @Override
    public String hello(@RequestBody User user) {
        return "error4";
    }
}

2.通過FeignClient註解的fallback屬性來指定對應的服務降級實現類 

@RequestMapping("/refactor")
@FeignClient(name = "hello-service",fallback = HelloServiceFallbackImpl.class)//用於繫結名為hello-service服務 ,fallback = HelloServiceFallbackImpl.class
public interface HelloService {

    @RequestMapping("/hello")
    public String hello();

    @RequestMapping(value = "/hello1", method = RequestMethod.GET)
    String hello(@RequestParam("name") String name);

    @RequestMapping(value = "/hello2", method = RequestMethod.GET,produces = {"application/json;charset-UTF-8"})
    Map<String,Object> hello(@RequestHeader("name") String name, @RequestHeader("author") String author, @RequestHeader("price") Integer price);

    @RequestMapping(value = "/hello3", method = RequestMethod.POST)
    String hello(@RequestBody Map<String,Object> book);

    @RequestMapping(value = "/hello4", method = RequestMethod.POST)
    String hello(@RequestBody User user);
}

3.修改application.properties開啟服務降級功能

spring.application.name=feign-consumer
server.port=9001

eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/

feign.hystrix.enabled=true

並且修改Hello-service使其超時

啟動服務後訪問:

 

其他配置

請求壓縮

Spring Cloud Feign支援對請求與響應的GZIP壓縮,以減少通訊過程中的效能損耗,只需要設定下面兩個引數即可:

# 配置請求GZIP壓縮
feign.compression.request.enabled=true
# 配置響應GZIP壓縮
feign.compression.response.enabled=true

並且能對請求壓縮做更細緻的設定,如:


# 配置壓縮支援的MIME TYPE
feign.compression.request.mime-types=text/xml,application/xml,application/json
# 配置壓縮資料大小的下限
feign.compression.request.min-request-size=2048

日誌配置

Spring Cloud Feign在構建被@FeignClient註解修飾的客戶端服務時,會為每一個客戶端都建立一個feign.Logger例項。開啟方式如下:

logging.level.com.example.feign.consumer.service.HelloService=DEBUG

但是由於Feign客戶端預設的Logger.Level物件定義為NONE級別,該級別不會記錄任何Feign呼叫過程中的資訊。所以要在應用主類中加入Logger.Level的Bean建立:

@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class FeignConsumerApplication {

    @Bean
    Logger.Level feignLoggerLevel(){
        return Logger.Level.FULL;
    }
    
    public static void main(String[] args) {
        SpringApplication.run(FeignConsumerApplication.class, args);
    }
}

Logger級別主要有4類:

NONE:不記錄任何資訊

BASIC:僅記錄請求方法,URL以及響應嗎和執行時間。

HEADERS:除了記錄BASIC級別的資訊外,還會記錄請求和響應的頭資訊。

FULL:記錄所有請求與響應的明細,包括頭資訊,請求體,元資料等。

 

參考:《Spring Cloud 微服務實戰》