微服務(wù)網(wǎng)關(guān)Gateway實(shí)踐總結(jié)
一、Gateway簡(jiǎn)介
微服務(wù)架構(gòu)中,網(wǎng)關(guān)服務(wù)通常提供動(dòng)態(tài)路由,以及流量控制與請(qǐng)求識(shí)別等核心能力,在之前的篇幅中有說(shuō)過(guò)Zuul組件的使用流程,但是當(dāng)下Gateway組件是更常規(guī)的選擇,下面就圍繞Gateway的實(shí)踐做詳細(xì)分析;
從架構(gòu)模式上看,網(wǎng)關(guān)不管采用什么技術(shù)組件,都是在客戶端與業(yè)務(wù)服務(wù)中間提供一層攔截與校驗(yàn)的能力,但是相比較Zuul來(lái)說(shuō),Gateway提供了更強(qiáng)大的功能和卓越的性能;
基于實(shí)踐的場(chǎng)景來(lái)看,在功能上網(wǎng)關(guān)更側(cè)重請(qǐng)求方的合法校驗(yàn),流量管控,以及IP級(jí)別的攔截,從架構(gòu)層面看,通常需要提供靈活的路由機(jī)制,比如灰度,負(fù)載均衡的策略等,并基于消息機(jī)制,進(jìn)行系統(tǒng)級(jí)的安全通知等;
下面圍繞客戶端、網(wǎng)關(guān)層、門(mén)面服務(wù)的三個(gè)節(jié)點(diǎn),分析Gateway的使用細(xì)節(jié),即客戶端向網(wǎng)關(guān)發(fā)出請(qǐng)求,經(jīng)過(guò)網(wǎng)關(guān)路由到門(mén)面服務(wù)處理;
二、動(dòng)態(tài)路由
1.基礎(chǔ)概念
路由:作為網(wǎng)關(guān)中最核心的能力,從源碼結(jié)構(gòu)上看,包括ID、請(qǐng)求URI、斷言集合、過(guò)濾集合等組成;
public class RouteDefinition {
private String id;
private URI uri;
private List<PredicateDefinition> predicates = new ArrayList<>();
private List<FilterDefinition> filters = new ArrayList<>();
}
斷言+過(guò)濾:通常在斷言中定義請(qǐng)求的匹配規(guī)則,在過(guò)濾中定義請(qǐng)求的處理動(dòng)作,結(jié)構(gòu)上看都是名稱加參數(shù)集合,并且支持快捷的方式配置;
public class PredicateDefinition {
private String name;
private Map<String, String> args = new LinkedHashMap<>();
}
public class FilterDefinition {
private String name;
private Map<String, String> args = new LinkedHashMap<>();
}
2.配置路由
以配置的方式,添加facade?服務(wù)路由,以路徑匹配的方式,如果請(qǐng)求路徑錯(cuò)誤則斷言失敗,StripPrefix設(shè)置為1,即在過(guò)濾中去掉第一個(gè)/facade參數(shù);
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: facade
uri: http://127.0.0.1:8082
predicates:
- Path=/facade/**
filters:
- StripPrefix=1
執(zhí)行原理如下:
這里是以配置文件的方式,設(shè)置facade服務(wù)的路由策略,其中指定了路徑方式,在Gateway文檔中提供了多種路由樣例,比如:Header、Cookie、Method、Query、Host等斷言方式;
3.編碼方式
基于編碼的方式管理路由策略,在Gateway文檔同樣提供了多種參考樣例,如果路由服務(wù)少并且固定,配置的方式可以解決,如果路由服務(wù)很多,并且需要?jiǎng)討B(tài)添加,那基于庫(kù)表方式更適合;
@Configuration
public class GateConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("facade",r -> r.path("/facade/**").filters(f -> f.stripPrefix(1))
.uri("http://127.0.0.1:8082")).build();
}
}
4.庫(kù)表加載
在常規(guī)的應(yīng)用中,從庫(kù)表中讀取路由策略是比較常見(jiàn)的方式,定義路由工廠類并實(shí)現(xiàn)RouteDefinitionRepository?接口,涉及加載、添加、刪除三個(gè)核心方法,然后基于服務(wù)類從庫(kù)中讀取數(shù)據(jù)轉(zhuǎn)換為RouteDefinition對(duì)象即可;
@Component
public class DefRouteFactory implements RouteDefinitionRepository {
@Resource
private ConfigRouteService routeService ;
// 加載
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(routeService.getRouteDefinitions());
}
// 添加
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(routeDefinition -> { routeService.saveRouter(routeDefinition);
return Mono.empty();
});
}
// 刪除
@Override
public Mono<Void> delete(Mono<String> idMono) {
return idMono.flatMap(routeId -> { routeService.removeRouter(routeId);
return Mono.empty();
});
}
}
在源碼倉(cāng)庫(kù)中采用的就是庫(kù)表管理的方式,代碼邏輯的更多細(xì)節(jié)可以移步Git參考,此處不再過(guò)多粘貼;
三、自定義路由策略
自定義斷言,繼承AbstractRoutePredicateFactory?類,注意命名以RoutePredicateFactory?結(jié)尾,重寫(xiě)apply方法,即可執(zhí)行特定的匹配規(guī)則;
@Component
public class DefCheckRoutePredicateFactory extends AbstractRoutePredicateFactory<DefCheckRoutePredicateFactory.Config> {
public DefCheckRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
log.info("DefCheckRoutePredicateFactory:" + config.getName());
return StrUtil.equals("butte",config.getName());
}
};
}
@Data
public static class Config { private String name; }
@Override
public List<String> shortcutFieldOrder() { return Collections.singletonList("name"); }
}
自定義過(guò)濾,繼承AbstractNameValueGatewayFilterFactory?類,注意命名以GatewayFilterFactory?結(jié)尾,重寫(xiě)apply方法,即可執(zhí)行特定的過(guò)濾規(guī)則;
@Component
public class DefHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(AbstractNameValueGatewayFilterFactory.NameValueConfig config) {
return (exchange, chain) -> {
log.info("DefHeaderGatewayFilterFactory:"+ config.getName() + "-" + config.getValue());
return chain.filter(exchange);
};
}
}
配置加載方式,此處斷言與過(guò)濾即快捷的配置方式,所以在命名上要遵守Gateway的約定;
spring:
cloud:
gateway:
routes:
- id: facade
uri: http://127.0.0.1:8082
predicates:
- Path=/facade/**
- DefCheck=butte
filters:
- StripPrefix=1
- DefHeader=cicada,smile
通常來(lái)說(shuō),在應(yīng)用級(jí)的系統(tǒng)中都需要進(jìn)行斷言和過(guò)濾的策略自定義,以提供業(yè)務(wù)或者架構(gòu)層面的支撐,完成更加細(xì)致的規(guī)則校驗(yàn),尤其在相同服務(wù)多版本并行時(shí),可以更好的管理路由策略,從而避免分支之間的影響;
四、全局過(guò)濾器
在路由中采用的過(guò)濾是GatewayFilter?,實(shí)際Gateway中還提供了GlobalFilter全局過(guò)濾器,雖然從結(jié)構(gòu)上看十分相似,但是其職責(zé)是有本質(zhì)區(qū)別的;
全局過(guò)濾器1:打印請(qǐng)求ID
@Component
@Order(1)
public class DefOneGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("request-id:{}",exchange.getRequest().getId()) ;
return chain.filter(exchange);
}
}
全局過(guò)濾器2:打印請(qǐng)求URI
@Component
@Order(2)
public class DefTwoGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("request-uri:{}",exchange.getRequest().getURI()) ;
return chain.filter(exchange);
}
}
Gateway網(wǎng)關(guān)作為微服務(wù)架構(gòu)系統(tǒng)中最先接收請(qǐng)求的一層,可以定義許多策略來(lái)保護(hù)系統(tǒng)的安全,比如高并發(fā)接口的限流,第三方授權(quán)驗(yàn)證,遭到惡意攻擊時(shí)的IP攔截等等,盡量將非法請(qǐng)求在網(wǎng)關(guān)中攔截掉,從而保證系統(tǒng)的安全與穩(wěn)定。
五、參考源碼
應(yīng)用倉(cāng)庫(kù):https://gitee.com/cicadasmile/butte-flyer-parent
組件封裝:https://gitee.com/cicadasmile/butte-frame-parent