微服務工程中,基礎元件應用

語言: CN / TW / HK

一、閘道器服務

1、閘道器模式

閘道器作為架構的最外層服務,用來統一攔截各個埠的請求,識別請求合法性,攔截異常動作,並提供路由和負載能力,保護業務服務;這種策略與外觀模式異曲同工。

閘道器服務和門面類服務有部分的邏輯相似,閘道器服務的攔截側重處理通用的策略和路由負載,而不同的門面聚合服務側重場景分類,例如常見的幾種門面服務:

  • Facade:服務產品開放的埠請求,例如Web,App,小程式等;

  • Admin:通常服務於內部的管理系統,例如Crm,BI報表,控制檯等;

  • Third:聚合第三方的對接服務,例如簡訊,風控,動作埋點等;

不同的門面服務中,也會存在特定的攔截策略,如果把Facade、Admin、Third等校驗都整合在閘道器中,很顯然會加重閘道器服務的負擔,不利於架構的穩定。

2、Gateway元件

如果微服務架構接觸較早的話,初期閘道器中常採用的是Zuul元件,後來SpringCloud才釋出Gateway元件,是當前常用選型。

  • 請求攔截:閘道器作為API請求的開放入口,完成請求的攔截、識別校驗等是基礎能力;

  • 定製策略:除常規身份識別,根據服務場景設計相應的攔截邏輯,儘量攔截異常請求;

  • 服務路由:請求通過攔截後轉發到具體的業務服務,這裡存在兩個核心動作路由和負載;

作為微服務架構中常用的選型元件,下面從使用細節中詳細分析Gateway閘道器的使用方式,與其他元件的對接流程和模式。

Nacos作為微服務的註冊和配置中心,已經是當下常用的元件,並且Nacos也提供Gateway元件的整合案例,首先就是把閘道器服務註冊到Nacos:

spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
discovery:
server-addr: 127.0.0.1:8848

Nacos管理閘道器服務註冊和相關配置檔案,在專案中通常與nacos共用一套MySQL庫,用來管理閘道器的服務路由資料,是當下比較常見的解決方案。

3、閘道器攔截

GlobalFilter:閘道器中的全域性過濾器,攔截經過閘道器的所有請求,經過相應的校驗策略,判斷請求是否需要執行:

@Order(-1)
@Component
public class GatewayFilter implements GlobalFilter {

@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
return chain.filter(exchange);
}
}

通常在閘道器中會執行一些必要共性攔截,例如:IP黑白名單,Token身份令牌,在請求中獲取對應引數,執行相關服務的校驗方法即可。

4、動態路由

4.1 路由定義

在服務路由的實現上,存在複雜的邏輯和策略,來適配各種場景;路由的概念如何定義,可以查閱Gateway元件的原始碼 RouteDefinition 物件,結構涉及到幾個核心的屬性:路由、斷言、過濾、元資料:

  • Route路由:由ID、轉發Uri、斷言、過濾、元資料組成;

  • Predicate斷言:判斷請求和路由是否匹配;

  • Filter過濾:可以對請求動作進行修改,例如引數;

  • Metadata元資料:裝載路由服務的元資訊;

@Validated
public class RouteDefinition {
private String id;
@NotNull
private URI uri;
@NotEmpty
@Valid
private List<PredicateDefinition> predicates = new ArrayList<>();
@Valid
private List<FilterDefinition> filters = new ArrayList<>();
private Map<String, Object> metadata = new HashMap<>();
}

通常把這些路由放在nacos庫的config_route資料表中,圍繞上述路由物件的結構,管理對應的表資料即可:

關於轉發的目標uri也有不同的配置,這裡選擇 lb://服務註冊名 的模式,即把請求負載均衡分配到路由對應的服務節點,補充說明一下Nacos元件內部採用Ribbon負載演算法,可以參考相關的文件。

4.2 管理路由

在路由的管理上有兩個核心介面: Locator載入Writer增刪 ,並且還提供了聚合的 Repository 介面:

public interface RouteDefinitionLocator {
// 獲取路由列表
Flux<RouteDefinition> getRouteDefinitions();
}
public interface RouteDefinitionWriter {
// 儲存路由
Mono<Void> save(Mono<RouteDefinition> route);
// 刪除路由
Mono<Void> delete(Mono<String> routeId);
}
public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter{}

這樣通過定義路由管理元件,實現上述聚合介面,完成路由資料從資料表載入到應用的過程:

@Component
public class RouteFactory implements RouteDefinitionRepository {
@Resource
private RouteService routeService ;

// 載入全部路由
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(routeService.getRouteDefinitions());
}
}

RouteService則是路由管理的服務類,管理配置資料以及實體物件與路由定義物件的轉換:

@Service
public class RouteServiceImpl implements RouteService {
// 資料庫路由實體,轉換為閘道器路由的定義物件
private RouteDefinition buildRoute (ConfigRoute configRoute){
RouteDefinition routeDefinition = new RouteDefinition () ;
// 基礎
routeDefinition.setId(configRoute.getRouteId());
routeDefinition.setOrder(configRoute.getOrders());
routeDefinition.setUri(URI.create(configRoute.getUri()));
// 斷言
routeDefinition.setPredicates(JSONUtil.parseArray(configRoute.getPredicates()).toList(PredicateDefinition.class));
// 過濾
routeDefinition.setFilters(JSONUtil.parseArray(configRoute.getFilters()).toList(FilterDefinition.class));
return routeDefinition ;
}
}

通過上述流程即可將路由資訊持久化儲存在資料庫,如果服務節點很少,也可以直接在nacos的配置檔案中管理。

4.3 匹配模式

Predicate斷言其實就是一個匹配規則的設定,查閱 PredicateDefinition 相關原始碼可知,有Host、Path、Time等多種模式,通過上述資料可知本文采用的是路徑匹配,

由於採用路徑匹配的方式,會把 /服務名 拼接在uri起始位置,所以在配置過濾規則的時候設定StripPrefix去掉該路由標識。

請求進入閘道器之後,首先進入全域性攔截器執行校驗策略,檢驗通過之後匹配相關路由的服務,匹配成功之後進行過濾操作,最終將請求轉發到相應的服務,這就是請求在閘道器中要執行的核心流程。

二、註冊與配置

Nacos在整個微服務體系中,提供服務註冊與配置管理兩個核心能力,通常在程式碼工程中只保留核心的 bootstrap 配置檔案即可,可以極大簡化工程中的配置並且提高相關資料的安全性。

1、服務註冊

Nacos支援基於DNS和基於RPC的服務發現,並提供對服務的實時健康檢查,阻止向非健康的主機或服務例項傳送請求:

在服務的註冊列表中可以檢視註冊資訊,例項數健康數等,並且可以刪除註冊服務;在詳情中可以檢視具體例項資訊,可以對服務例項進行動態上下線和相關配置編輯。

2、配置檔案

通常會採用 namespace 名稱空間的管理,隔離開不同的服務的註冊與配置,可以在nacos庫中 tenant_info 檢視,一般會存在如下幾個分類:

這是最常見的名稱空間,gateway閘道器比較獨立,seate事務的配置比較複雜,serve業務服務具有大量的公共配置,通常採用如下的策略:

  • application.yml:所有服務的公共配置,例如mybatis;

  • dev||pro.yml:環境隔離配置,不同環境設定不同的中介軟體地址;

  • serve.yml:服務的個性化配置,比如連線的庫,引數等;

spring:
application:
name: facade
profiles:
active: dev,facade
cloud:
nacos:
config:
prefix: application
file-extension: yml
server-addr: 127.0.0.1:8848
discovery:
server-addr: 127.0.0.1:8848

服務通過上述 profiles 引數會識別和載入對應的配置檔案,在這種管理模式中,環境的差異只在 dev||pro.yml 配置檔案中維護即可,其他配置會相對穩定許多。

三、服務間呼叫

1、Feign元件

Feign元件是宣告式、模板化的HTTP客戶端,可以讓服務之間的呼叫變得更簡單優雅,通常將服務提供Feign介面在獨立的程式碼包中管理,方便被其他服務依賴使用:

/**
* 指定服務名稱
*/

@FeignClient(name = "account")
@Component
public interface FeignService {
/**
* 服務的API介面
*/

@RequestMapping(value = "/user/profile/{paramId}", method = RequestMethod.GET)
Rep<Entity> getById(@PathVariable("paramId") String paramId);
}
public class Rep<T> {
// 響應編碼
private int code;
// 語義描述
private String msg;
// 返回資料
private T data;
}

通常會把Feign介面的響應格式做包裝,實現返參結構統一管理,有利於呼叫端的識別,這裡就涉及到泛型資料的處理問題。

2、響應解碼

通過繼承 ResponseEntityDecoder 類,實現自定義的Feign介面響應資料處理,例如返參風格,資料轉換等:

/**
* 配置解碼
*/

@Configuration
public class FeignConfig {
@Bean
@Primary
public Decoder feignDecoder(ObjectFactory<HttpMessageConverters> feignHttpConverter) {
return new OptionalDecoder(
new FeignDecode(new SpringDecoder(feignHttpConverter)));
}
}
/**
* 定義解碼
*/

public class FeignDecode extends ResponseEntityDecoder {
public FeignDecode(Decoder decoder) {
super(decoder);
}
@Override
public Object decode(Response response, Type type) {
if (!type.getTypeName().startsWith(Rep.class.getName())) {
throw new RuntimeException("響應格式異常");
}
try {
return super.decode(response, type);
} catch (IOException e) {
throw new RuntimeException(e.getMessage());
}
}
}

3、請求解析

採用註解方式就輕鬆實現服務間的通訊請求,查閱Feign元件的原始碼可以理解封裝邏輯,其內在是把介面呼叫轉換成HTTP請求:

  • FeignClientBuilder :不採用註解的方式直接構建Feign請求的客戶端,該類有助於理解 @FeignClient 註解原理;
  • FeignClientsRegistrar :即專案中採用 @FeignClient 註解方式,該API中描述了註解的解析方式和服務請求的構建邏輯;

微服務工程的架構是一項複雜和持續的過程,其中涉及到的元件也十分繁雜,本文只是選取Gateway、Nacos、Feign三個基礎元件做簡單的總結,在其邏輯的理解上需要圍繞該元件的核心功能和專案使用的API作為切入點,時常查閱原始碼和官方文件。

四、參考原始碼

應用倉庫:
https://gitee.com/cicadasmile/butte-flyer-parent

元件封裝:
https://gitee.com/cicadasmile/butte-frame-parent