一、網關服務
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 = https://www.cnblogs.com/cicada-smile/p/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 = "https://www.cnblogs.com/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
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/440446.html
標籤:架構設計
上一篇:將一列中的串列拆分為多列
