05 | REST消息通信:如何使用 OpenFeign 简化服务间通信
上一講我們學習了 Ribbon 與 RestTemplate 兩個組件。Ribbon 提供了客戶端負載均衡,而 RestTemplate 則封裝了 HTTP 的通訊,簡化了發送請求的過程。兩者相輔相成構建了服務間的高可用通信。
不過在使用后,你也應該會發現 RestTemplate,它只是對 HTTP 的簡單封裝,像 URL、請求參數、請求頭、請求體這些細節都需要我們自己處理,如此底層的操作都暴露出來這肯定不利于項目團隊間協作,因此就需要一種封裝度更高、使用更簡單的技術屏蔽通信底層復雜度。好在 Spring Cloud 團隊提供了 OpenFeign 技術,大幅簡化了服務間高可用通信處理過程。本講將主要介紹三部分:
- 介紹 Feign 與 OpenFeign;
- 講解 OpenFeign 的使用辦法;
- 講解生產環境 OpenFeign 的配置優化。
Feign 與 OpenFeign
Spring Cloud OpenFeign 并不是獨立的技術。它底層基于 Netflix Feign,Netflix Feign 是 Netflix 設計的開源的聲明式 WebService 客戶端,用于簡化服務間通信。Netflix Feign 采用“接口+注解”的方式開發,通過模仿 RPC 的客戶端與服務器模式(CS),采用接口方式開發來屏蔽網絡通信的細節。OpenFeign 則是在 Netflix Feign 的基礎上進行封裝,結合原有 Spring MVC 的注解,對 Spring Cloud 微服務通信提供了良好的支持。使用 OpenFeign 開發的方式與開發 Spring MVC Controller 頗為相似。下面我們通過代碼說明 OpenFeign 的各種開發技巧。
OpenFeign 的使用辦法
為了便于理解,我們模擬實際案例進行說明。假設某電商平臺日常訂單業務中,為保證每一筆訂單不會超賣,在創建訂單前訂單服務(order-service)首先去倉儲服務(warehouse-service)檢查對應商品 skuId(品類編號)的庫存數量是否足夠,庫存充足創建訂單,不存不足 App 前端提示“庫存不足”。
在這個業務中,訂單服務依賴于倉儲服務,那倉儲服務就是服務提供者,訂單服務是服務消費者。下面我們通過代碼還原這個場景。
首先,先創建倉儲服務(warehouse-service),倉儲服務作為提供者就是標準微服務,使用 Spring Boot 開發。
第一步,利用 Spring Initializr 向導創建 warehouse-service 工程。確保在 pom.xml 引入以下依賴。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>第二步,編輯 application.yml 新增 Nacos 通信配置。
spring:application:name: warehouse-service #應用/微服務名字cloud:nacos:discovery:server-addr: 192.168.31.102:8848 #nacos服務器地址username: nacos #用戶名密碼password: nacosserver:port: 80第三步,創建 Stock 庫存類,用于保存庫存數據。
package com.lagou.warehouseservice.dto;//庫存商品對象public class Stock {private Long skuId; //商品品類編號private String title; //商品與品類名稱private Integer quantity; //庫存數量private String unit; //單位private String description; //描述信息//帶參構造函數public Stock(Long skuId, String title, Integer quantity, String unit) {this.skuId = skuId;this.title = title;this.quantity = quantity;this.unit = unit;}//getter and setter省略...}第四步,創建倉儲服務控制器 WarehouseController,通過 getStock() 方法傳入 skuId 編號則返回具體商品庫存數量,代碼中模擬 skuId 編號為 1101 的“紫色 128G iPhone 11”庫存 32 臺,而編號 1102 的“白色 256G iPhone 11”已沒有庫存。
package com.lagou.warehouseservice.controller;//省略 import 部分//倉儲服務控制器@RestControllerpublic class WarehouseController {/*** 查詢對應 skuId 的庫存狀況* @param skuId skuId* @return Stock 庫存對象*/@GetMapping("/stock")public Stock getStock(Long skuId){Map result = new HashMap();Stock stock = null;if(skuId == 1101l){//模擬有庫存商品stock = new Stock(1101l, "Apple iPhone 11 128GB 紫色", 32, "臺");stock.setDescription("Apple 11 紫色版對應商品描述");}else if(skuId == 1102l){//模擬無庫存商品stock = new Stock(1101l, "Apple iPhone 11 256GB 白色", 0, "臺");stock.setDescription("Apple 11 白色版對應商品描述");}else{//演示案例,暫不考慮無對應 skuId 的情況}return stock;}}可以看到 WarehouseController 就是普通的 Spring MVC 控制器,對外暴露了 stock 接口,當應用啟動后,查看 Nacos 服務列表,已出現 warehouse-service 實例。
訪問下面的 URL 可看到 Stock 對象 JSON 序列化數據。
至此,服務提供者 warehouse-service 示例代碼已開發完畢。下面我們要開發服務消費者 order-service。
第一步,確保利用 Spring Initializr 創建 order-service 工程,確保 pom.xml 引入以下 3 個依賴。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId><version>2.2.5.RELEASE</version></dependency>這里關鍵在于服務消費者依賴了 spring-cloud-starter-openfeign,在 Spring Boot 工程會自動引入 Spring Cloud OpenFeign 與 Netflix Feign 的 Jar 包。這里有個重要細節,當我們引入 OpenFeign 的時候,在 Maven 依賴中會出現 netflix-ribbon 負載均衡器的身影。
沒錯,OpenFeign 為了保證通信高可用,底層也是采用 Ribbon 實現負載均衡,其原理與 Ribbon+RestTemplate 完全相同,只不過相較 RestTemplate,OpenFeign 封裝度更高罷了。
第二步,啟用 OpenFeign 需要在應用入口 OrderServiceApplication 增加 @EnableFeignClients 注解,其含義為通知 Spring 啟用 OpenFeign 聲明式通信。
package com.lagou.orderservice;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.openfeign.EnableFeignClients;@SpringBootApplication@EnableFeignClients //啟用OpenFeignpublic class OrderServiceApplication {public static void main(String[] args) {SpringApplication.run(OrderServiceApplication.class, args);}}第三步,默認 OpenFeign 并不需要任何配置,在 application.yml 配置好 Nacos 通信即可。
spring:application:name: order-servicecloud:nacos:discovery:server-addr: 192.168.31.102:8848username: nacospassword: nacosserver:port: 80第四步,最重要的地方來了,創建OpenFeign的通信接口與響應對象,這里先給出完整代碼。
package com.lagou.orderservice.feignclient;import com.lagou.orderservice.dto.Stock;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;@FeignClient("warehouse-service")public interface WarehouseServiceFeignClient {@GetMapping("/stock")public Stock getStock(@RequestParam("skuId") Long skuId);}在 order-service 工程下,創建一個 feignclient 包用于保存通信接口。OpenFeign 通過“接口+注解”形式描述數據傳輸邏輯,并不需要程序員編寫具體實現代碼便能實現服務間高可用通信,下面我們來學習這段代碼:
- @FeignClient 注解說明當前接口為 OpenFeign 通信客戶端,參數值 warehouse-service 為服務提供者 ID,這一項必須與 Nacos 注冊 ID 保持一致。在 OpenFeign 發送請求前會自動在 Nacos 查詢 warehouse-service 所有可用實例信息,再通過內置的 Ribbon 負載均衡選擇一個實例發起 RESTful 請求,進而保證通信高可用。
- 聲明的方法結構,接口中定義的方法通常與服務提供者的方法定義保持一致。這里有個非常重要的細節:用于接收數據的 Stock 對象并不強制要求與提供者端 Stock 對象完全相同,消費者端的 Stock 類可以根據業務需要刪減屬性,但屬性必須要與提供者響應的 JSON 屬性保持一致。距離說明,我們在代碼發現消費者端 Stock 的包名與代碼與提供者都不盡相同,而且因為消費者不需要 description 屬性便將其刪除,其余屬性只要保證與服務提供者響應 JSON 保持一致,在 OpenFeign 獲取響應后便根據 JSON 屬性名自動反序列化到 Stock 對象中。
- @GetMapping/@PostMapping,以前我們在編寫 Spring MVC 控制器時經常使用 @GetMapping 或者@ PostMapping 聲明映射方法的請求類型。雖然 OpenFeign 也使用了這些注解,但含義完全不同。在消費者端這些注解的含義是:OpenFeign 向服務提供者 warehouse-service 的 stock 接口發起 Get 請求。簡單總結下,如果在服務提供者書寫 @GetMapping 是說明 Controller 接收數據的請求類型必須是 Get,而寫在消費者端接口中則說明 OpenFeign 采用 Get 請求發送數據,大多數情況下消費者發送的請求類型、URI 與提供者定義要保持一致。
- @RequestParam,該注解說明方法參數與請求參數之間的映射關系。舉例說明,當調用接口的 getStock() 方法時 skuId 參數值為 1101,那實際通信時 OpenFeign 發送的 Get 請求格式就是:
介紹每一個細節后,我用自然語言完整描述處理邏輯:
4.warehouse-service 處理完畢返回 JSON 數據,消費者端 OpenFeign 接收 JSON 的同時反序列化到 Stock 對象,并將該對象返回。
到這里我們花了較大的篇幅介紹 OpenFeign 的執行過程,那該怎么使用呢?這就是第五步的事情了。
第五步,在消費者 Controller 中對 FeignClient 接口進行注入,像調用本地方法一樣完成業務邏輯。
package com.lagou.orderservice.controller;import com.lagou.orderservice.dto.Stock;import com.lagou.orderservice.feignclient.WarehouseServiceFeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;import java.util.LinkedHashMap;import java.util.Map;@RestControllerpublic class OrderController {//利用@Resource將IOC容器中自動實例化的實現類對象進行注入@Resourceprivate WarehouseServiceFeignClient warehouseServiceFeignClient;/*** 創建訂單業務邏輯* @param skuId 商品類別編號* @param salesQuantity 銷售數量* @return*/@GetMapping("/create_order")public Map createOrder(Long skuId , Long salesQuantity){Map result = new LinkedHashMap();//查詢商品庫存,像調用本地方法一樣完成業務邏輯。Stock stock = warehouseServiceFeignClient.getStock(skuId);System.out.println(stock);if(salesQuantity <= stock.getQuantity()){//創建訂單相關代碼,此處省略//CODE=SUCCESS代表訂單創建成功result.put("code" , "SUCCESS");result.put("skuId", skuId);result.put("message", "訂單創建成功");}else{//code=NOT_ENOUGN_STOCK代表庫存不足result.put("code", "NOT_ENOUGH_STOCK");result.put("skuId", skuId);result.put("message", "商品庫存數量不足");}return result;}}啟動后分別傳入不同 skuId 與銷售數量??梢钥吹?1101 商品庫存充足訂單創建成功,1102 商品因為沒有庫存導致無法創建訂單。
http://192.168.1.120/create_order?skuId=1101&salesQuantity=1{code: "SUCCESS", skuId: 1101, message: "訂單創建成功" }創建訂單成功消息
http://192.168.1.120/create_order?skuId=1102&salesQuantity=1{code: "NOT_ENOUGH_STOCK", skuId: 1102, message: "商品庫存數量不足" }庫存數量不足錯誤提示
到這里已經基于 OpenFeign 實現了服務間通信。但事情還不算完,OpenFeign 默認的配置并不能滿足生產環境的要求,下面咱們來講解在生產環境下 OpenFeign 還需要哪些必要的優化配置。
生產環境 OpenFeign 的配置事項
如何更改 OpenFeign 默認的負載均衡策略
前面提到,在 OpenFeign 使用時默認引用 Ribbon 實現客戶端負載均衡。那如何設置 Ribbon 默認的負載均衡策略呢?在 OpenFeign 環境下,配置方式其實與之前 Ribbon+RestTemplate 方案完全相同,只需在 application.yml 中調整微服務通信時使用的負載均衡類即可。
開啟默認的 OpenFeign 數據壓縮功能
在 OpenFeign 中,默認并沒有開啟數據壓縮功能。但如果你在服務間單次傳遞數據超過 1K 字節,強烈推薦開啟數據壓縮功能。默認 OpenFeign 使用 Gzip 方式壓縮數據,對于大文本通常壓縮后尺寸只相當于原始數據的 10%~30%,這會極大提高帶寬利用率。但有一種情況除外,如果應用屬于計算密集型,CPU 負載長期超過 70%,因數據壓縮、解壓縮都需要 CPU 運算,開啟數據壓縮功能反而會給 CPU 增加額外負擔,導致系統性能降低,這是不可取的。
替換默認通信組件
OpenFeign 默認使用 Java 自帶的 URLConnection 對象創建 HTTP 請求,但接入生產時,如果能將底層通信組件更換為 Apache HttpClient、OKHttp 這樣的專用通信組件,基于這些組件自帶的連接池,可以更好地對 HTTP 連接對象進行重用與管理。作為 OpenFeign 目前默認支持 Apache HttpClient 與 OKHttp 兩款產品。我以OKHttp配置方式為例,為你展現配置方法。
做到這里,我們已將OpenFeign的默認通信對象從URLConnection調整為OKHttp,至于替換為HttpClient組件的配置思路是基本相同的。如果需要了解OpenFeign更詳細的配置選項,可以訪問Spring Cloud OpenFeign的官方文檔進行學習。
https://docs.spring.io/spring-cloud-openfeign/docs/2.2.6.RELEASE/reference/html/
總結
以上是生活随笔為你收集整理的05 | REST消息通信:如何使用 OpenFeign 简化服务间通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 04 | 负载均衡:Ribbon 如何保
- 下一篇: HttpClient在传参和返回结果的中