Spring Cloud微服務系列文,服務調用框架Feign架構師入門:搭建基本的Eureka架構(從項目里抽取)

  • 2019 年 10 月 8 日
  • 筆記

之前博文的案例中,我們是通過RestTemplate來調用服務,而Feign框架則在此基礎上做了一層封裝,比如,可以通過註解等方式來綁定參數,或者以聲明的方式來指定請求返回類型是JSON。 這種「再次封裝」能給我們帶來的便利有兩點,第一,開發者無需像使用RestTemplate那樣過多地關注HTTP調用細節,第二,在大多數場景里,某種類型的調用請求會被在多個地方被多次使用,通過Feign能方便地實現這類「重用」。

1 通過案例快速上手Feign

在FeignDemo-Server項目里,搭建基於Eureka的伺服器,該項目的埠號是8888,主機名是localhost,啟動後,能通過http://localhost:8888/eureka/查看註冊到Eureka伺服器中的諸多服務提供者或調用者的資訊。

在FeignDemo-ServiceProvider項目的控制器類里,我們提供了一個sayHello方法,本項目提供服務的埠號是1111,對外提供的application name(服務名)是sayHelloServiceProvider,是向FeignDemo-Server伺服器(也是Eureka伺服器)的http://localhost:8888/eureka/註冊服務。而提供sayHello的方法如下所示,從中,我們能看到對應的RequestMapping值。

1 @RequestMapping(value = "/hello/{username}", method = RequestMethod.GET )

2 public String sayHello(@PathVariable("username") String username){

3 return "hello " + username;

4 }

上述Eureka伺服器和客戶端的程式碼,是復用架構師入門:搭建基本的Eureka架構(從項目里抽取)這篇文章里的程式碼。

這裡我們將在FeignDemo-ServiceCaller項目里,演示通過Feign調用服務的方式。

第一步,在pom.xml里,引入Eureka、Ribbon和Feign的相關包,關鍵程式碼如下。其中,是通過第1行到第9行的程式碼引入Eureka包,通過第10行到第13行的程式碼引入Ribbon包,通過第14行到第17行的程式碼引入Feign包。

1    <dependency>  2        <groupId>org.springframework.boot</groupId>  3        <artifactId>spring-boot-starter-web</artifactId>  4        <version>1.5.4.RELEASE</version>  5    </dependency>  6        <dependency>  7            <groupId>org.springframework.cloud</groupId>  8            <artifactId>spring-cloud-starter-eureka</artifactId>  9        </dependency>  10     <dependency>  11           <groupId>org.springframework.cloud</groupId>  12           <artifactId>spring-cloud-starter-ribbon</artifactId>  13    </dependency>  14     <dependency>  15            <groupId>org.springframework.cloud</groupId>  16            <artifactId>spring-cloud-starter-feign</artifactId>  17    </dependency>

第二步,在application.yml里,通過第3行的程式碼,定義本項目的名字叫callHelloByFeign,通過第5行的程式碼,指定本項目是工作在8080埠。同時通過第9行的程式碼,指定本項目是向http://localhost:8888/eureka/ (也就是FeignDemo-Server)這個Eureka伺服器註冊。

1    spring:  2      application:  3        name: callHelloByFeign  4    server:  5      port: 8080  6    eureka:  7      client:  8        serviceUrl:  9          defaultZone: http://localhost:8888/eureka/

第三步,在啟動類里,通過第1行的程式碼,添加支援Feign的注釋,關鍵程式碼如下。這樣,在啟動這個Eureka客戶端時,就可以引入Feign支援。

1    @EnableFeignClients  2    @EnableDiscoveryClient  3    @SpringBootApplication  4    public class ServiceCallerApp  5    {  6        public static void main( String[] args )  7        {      SpringApplication.run(ServiceCallerApp.class, args);  }  8    }

第四步,通過Feign封裝客戶端調用的細節,外部模組是通過Feign來調用客戶端的,這部分的程式碼是在Controller.java里。

1    省略必要的package和import的程式碼  2    //通過註解指定待調用的服務名  3    @FeignClient("sayHelloServiceProvider")  4    //在這個介面里,通過Feign封裝客戶端的調用細節  5    interface FeignClientTool  6    {  7            @RequestMapping(method = RequestMethod.GET, value =   "/hello/{name}")  8          String sayHelloInClient(@PathVariable("name") String name);  9    }  10    //Controller是控制器類  11    @RestController  12    public class Controller {  13          @Autowired  14         private FeignClientTool tool;  15    //在callHello方法是,是通過Feign來調用服務  16        @RequestMapping(value = "/callHello", method = RequestMethod.GET)  17          public String callHello(){  18            return tool.sayHelloInClient("Peter");  19        }  20    }

在Controller.java這個文件,其實定義了一個介面和一個類。在第5行的FeignClientTool介面里,我們封裝了Feign的調用業務,具體來說,是通過第3行的FeignClient註解,指定了該介面會調用「sayHelloServiceProvider「服務提供者的服務,而通過第8行的,則指定了調用該服務提供者中sayHelloInClient的方法。

而在第12行的Controller類里,先是在第14行里,通過Autowired註解,引入了FeignClientTool類型的tool類,隨後在第17行的callHello方法里,是通過tool類的sayHelloInClient方法,調用了服務提供者的相關方法。

也就是說,在callHello方法里,我們並沒有再通過RestTemplate,以輸入地址和服務名的方式調用服務,而是通過封裝在FeignClientTool(Feign介面)里的方法調用服務。

完成上述程式碼後,我們可以通過如下的步驟查看運行效果。

第一步,啟動FeignDemo-Server項目,隨後輸入http://localhost:8888/,能看到註冊到Eureka伺服器里的諸多服務。

第二步,啟動FeignDemo-ServiceProvider項目,隨後輸入http://localhost:1111/hello/Peter,能調用其中的服務,此時,能在瀏覽里看到「hello Peter」的輸出。

第三步,啟動FeignDemo-ServiceCaller項目,隨後輸入http://localhost:8080/callHello,同樣能在瀏覽里看到「hello Peter」的輸出。請注意,這裡的調用是通過Feign完成的。

2 通過比較其它調用方式,了解Feign的封裝性

在之前的程式碼里,我們是通過如下形式,通過RestTemplate對象來調用服務。

1 RestTemplate template = getRestTemplate();

2 String retVal = template.getForEntity("http://sayHello/hello/Eureka", String.class).getBody();

在第2行的調用中,我們需要指定url以及返回類型等資訊。

之前我們還見過基於RestClient對象的調用方式,關鍵程式碼如下。

1 RestClient client = (RestClient)ClientFactory.getNamedClient("RibbonDemo");

2 HttpRequest request = HttpRequest.newBuilder().uri(new URI("/hello")).build();

3 HttpResponse response = client.executeWithLoadBalancer(request);

其中是在第1行指定發起調用的RestClient類型的對象,在第2行里指定待調用的目標地址,隨後在第3行發起調用。

這兩種調用方式有著如下的共同點:調用時,需要詳細地知道各種調用參數,比如服務提供者的url,如果有需要通過Ribbon實現負載均衡等機制,也需要在調用時一併指定。

但事實上,這些調用方式的底層細節,應該向服務使用者屏蔽,比如在調用時,無需關注目標url等資訊。這就好比某位老闆要秘書去訂飛機票,作為服務使用者的老闆只應當關心調用的結果,比如買到的飛機票是幾點開的,該去哪個航站樓登機,至於調用服務的底層細節,比如該到哪個訂票網站去買,服務使用者無需知道。

說得更專業些,這叫「解耦合」,即降低服務調動者和服務提供者之間的耦合度,這樣的好處是,一旦服務提供者改變了實現細節(沒改變服務調用介面),那麼服務調用者部分的程式碼無需改動。

我們再來回顧下通過Feign調用服務的方式。

1 private FeignClientTool tool; //定義Feign類

2 tool.sayHelloInClient("Peter"); //直接調用

第2行是調用服務,但其中,我們看不到任何服務提供者的細節,因為這些都在第1行引用的FeignClientTool類里封裝掉了。也就是說,通過基於Feign的調用方式,開發者能真正地做到「面向業務」,而無需過多地關注發起調用的細節。

3 通過註解輸出調用日誌

在開發和調試階段,我們希望能看到日誌,從而能定位和排查問題。這裡,我們將講述在Feign里輸出日誌的方法,以便讓大家能在通過Feign調用服務時,看到具體的服務資訊。

這裡我們將改寫FeignDemo-ServiceCaller項目。

改動點1:在application.yml文件里,增加如下的程式碼,以開啟Feign客戶端的DEBUG日誌模式,請注意,這裡需要指定完成的路徑,就像第3行那樣。

1 logging:

2 level:

3 com.controller.FeignClientTool: DEBUG

改動點2:在這個項目的啟動類ServiceCallerApp.java里,增加定義日誌級別的程式碼,在第7行的feignLoggerLevel方法里,我們通過第8行的程式碼,指定了Feign日誌級別是FULL。

1    //省略必要的pacakge和import程式碼  2    @EnableFeignClients  3    @EnableDiscoveryClient  4    @SpringBootApplication  5    public class ServiceCallerApp{  6        @Bean //定義日誌級別是FULL  7        Level feignLoggerLevel()    {  8            return Level.FULL;  9        }  10        //啟動類  11        public static void main( String[] args )  {  12           SpringApplication.run(ServiceCallerApp.class, args);  13       }  14    }

完成後,依次運行Eureka伺服器、服務提供者和服務調用者的啟動類,隨後在瀏覽器里輸入http://localhost:8080/callHello,即能在控制台里看到DEBUG級別的日誌,下面給出了部分輸出。

1 2018-06-17 12:18:27.296 DEBUG 208 — [rviceProvider-2] com.controller.FeignClientTool : [FeignClientTool#sayHelloInClient] —> GET http://sayHelloServiceProvider/hello/Peter?name=Peter HTTP/1.1

2 2018-06-17 12:18:27.296 DEBUG 208 — [rviceProvider-2] com.controller.FeignClientTool : [FeignClientTool#sayHelloInClient] —> END HTTP (0-byte body)

從第1行的輸出里,我們能看到以GET的方式向FeignClientTool類的sayHelloInClient方法發起調用,從第2行的輸出里,能看到調用結束。

在上文里,我們用的是FULL級別的日誌,此外,還有NONE、BASIC和HEADERS這三種,在下表裡,我們將詳細講述各級別日誌的輸出情況。

日誌輸出級別

描述

NONE

不輸出任何日誌

BASIC

只輸出請求的方法,請求的URL和相應的狀態碼,以及執行的時間

HEADERS

除了會輸出BASIC級別的日誌外,還會記錄請求和響應的頭資訊

FULL

輸出所有的和請求和響應相關的日誌資訊

一般情況下,在調試階段,可以把日誌級別設置成FULL,等上線後,可以把級別調整為BASIC,因為在生產環境上,過多的日誌反而會降低排查和定位問題的效率。

4 壓縮請求和返回,以提升訪問效率

在網路傳輸過程中,如果我們能降低傳輸流量,那麼即可提升處理請求的效率。尤其地,在一些日常訪問量比較高的網路應用中,如果能降低處理請求(Request)和發送返回資訊(Response)的時間,那麼就能提升本站的吞吐量。

在Feign里,我們一般能通過如下的配置,來壓縮請求和響應。

第一,可以通過在application.yml里增加如下的配置,從而壓縮請求和返回資訊。

1 feign:

2 compression:

3 request:

4 enabled: true

5 feign:

6 compression:

7 response:

8 enabled: true

其中,前4行是壓縮請求,而後4行是壓縮返回。

第二,可以通過如下的程式碼,設置哪類請求(或返回)將被壓縮,這裡我們在第4行里,指定了兩類格式的請求將被壓縮。

1 feign:

2 compression:

3 request:

4 mime-types: text/xml,application/xml

第三,可以通過如下的程式碼,指定待壓縮請求的最小值,這裡是2048,也就是說,超過這個值的request才會被壓縮。

1 feign:

2 compression:

3 request:

4 min-request-size:2048

本文謝絕轉載。