因?yàn)樽罱疽灿?jì)劃將原有的項(xiàng)目改造成微服務(wù)的架構(gòu)伴找,加上之前自己面試過程中也發(fā)現(xiàn)自己在這方面也比較欠缺,所以準(zhǔn)備好好的來學(xué)習(xí)一下微服務(wù)的各個(gè)常用的組件查库。其實(shí)微服務(wù)發(fā)展到現(xiàn)在出現(xiàn)的各種架構(gòu)都比較成熟了鼎姊,我覺得具體情況具體分析就好了,不存在最好只有最適合箍鼓,當(dāng)然如果不知道怎么選型崭参,那我覺得選擇spring cloud官方的組件應(yīng)該也不會(huì)差。 上周開始項(xiàng)目組就在研究技術(shù)選型的問題款咖,因?yàn)闆]有架構(gòu)師所以很多東西需要項(xiàng)目組來確定何暮,另外也要參考公司的其他部門的經(jīng)驗(yàn)奄喂,大概介紹一下我們的技術(shù)組件吧:Nacos做注冊中心和配置中心;網(wǎng)關(guān)就是這次學(xué)習(xí)的spring cloud gateway海洼;日志框架就是ELK跨新,這個(gè)之前我也搭建過;緩存的話選擇的Redis坏逢;微服務(wù)監(jiān)控選擇的是admin域帐;另外就是跟蹤鏈選擇的是skywalking,其實(shí)上周有幾天我一直在熟悉skywalking的部署是整,還是踩了一些坑肖揣,后面有機(jī)會(huì)我專門再寫一篇吧。其他的比如jekins浮入、docker龙优、k8s等等,目前技術(shù)框架整體就是這樣一個(gè)情況舵盈,這些內(nèi)容也需要后面去學(xué)習(xí)陋率。
今天主要學(xué)習(xí)下微服務(wù)網(wǎng)關(guān),API 網(wǎng)關(guān)可以說是整個(gè)微服務(wù)的統(tǒng)一入口秽晚,其可以提供請求路由瓦糟、協(xié)議轉(zhuǎn)換、安全認(rèn)證赴蝇、服務(wù)鑒權(quán)菩浙、流量控制等等一系列服務(wù)。之前spring cloud推薦的網(wǎng)關(guān)是Zuul
句伶,它的1.0版本是基于阻塞IO的網(wǎng)關(guān)劲蜻,其性能相比spring cloud gateway是要差很多的,不過Zuul
也出了2.0版本考余,基于Netty
先嬉、非阻塞、支持長鏈接楚堤,性能會(huì)有較大提升疫蔓,只是目前spring cloud還沒有整合。
Gateway
是建立在Spring WebFlux
身冬、Spring Boot2.x
衅胀、Project Reactor
之上,這是一個(gè)全新的項(xiàng)目酥筝。不管是從性能還是說以后的可擴(kuò)展性考慮滚躯,我們項(xiàng)目組對原有項(xiàng)目的改造在網(wǎng)關(guān)上就選擇了使用Gateway
。Gateway
有幾個(gè)比較新的特征:1、基于java 8掸掏,2 茁影、基于Spring 5
,Spring Boot 2.x
;3、支持動(dòng)態(tài)路由等丧凤。這個(gè)我建議多看下文檔呼胚,介紹的比較詳細(xì),spring gateway 文檔息裸。我覺得看了文檔后使用上應(yīng)該沒有什么問題,下圖是Gateway
的工作流程沪编『襞瑁客戶端請求會(huì)先打到Gateway
,具體的講應(yīng)該是DispacherHandler
(因?yàn)?code>Gateway引入了WebFlux
蚁廓,作用可以類比MVC的DispacherServlet
)访圃,Gateway
根據(jù)用戶的請求找到相應(yīng)的HandlerMapping
,請求和具體的handler之間有一個(gè)映射關(guān)系相嵌,網(wǎng)關(guān)會(huì)對請求進(jìn)行路由腿时,handler會(huì)匹配到RoutePredicateHandlerMapping
,匹配請求對應(yīng)的Route
饭宾,然后到達(dá)Web處理器批糟,WebHandler
代理了一系列網(wǎng)關(guān)過濾器和全局過濾器的實(shí)例,這些過濾器可以對請求和響應(yīng)進(jìn)行修改看铆,最后由代理服務(wù)完成用戶請求徽鼎,并將結(jié)果返回。
接下來我們還是創(chuàng)建一個(gè)項(xiàng)目弹惦,引入相關(guān)的依賴:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
之所以引入eureka是因?yàn)榫W(wǎng)關(guān)在微服務(wù)的架構(gòu)中也是作為一個(gè)服務(wù)存在的否淤,因此需要將gateway注冊到eureka。
自定義路由
gateway有幾個(gè)核心的概念棠隐,需要我們注意石抡,1:Route
,即路由助泽,它是gateway非硢福基礎(chǔ)也非常重要的一個(gè)塊,主要由一個(gè)id报咳,一個(gè)目標(biāo)URI
侠讯,一個(gè)predicates集合和一個(gè)filter集合幾部分組成。2暑刃、Predicate
厢漩,這個(gè)java 8中的一個(gè)斷言函數(shù),它的返回結(jié)果是boolean岩臣,這里就不再介紹了溜嗜。它的入?yún)⑹?code>ServerWebExchange宵膨,它封裝了ServerHttpRequest
和ServerHttpResponse
,這樣開發(fā)過程中就可以針對用戶請求和響應(yīng)進(jìn)行一些操作炸宵。3:Filter
辟躏,這些filter都是由相關(guān)的工廠創(chuàng)建 出來的GatewayFilter實(shí)例,在這些過濾器里面可以對請求和響應(yīng)進(jìn)行修改土全,然后傳遞給下游捎琐。
下面的配置文件就是自己定義的一個(gè)路由:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: http://localhost:9010
predicates:
- Path=/user/**
- id: order-service
uri: http://localhost:9020
predicates:
- Path=/order/**
- Query=role,ABO
因?yàn)槲疫€沒有創(chuàng)建其他服務(wù),所以只是隨意配置了兩個(gè)路由裹匙,id屬性可以自己隨意指定瑞凑,如果不顯式指定的話就是一個(gè)UUID
;uri是具體的目標(biāo)URI
概页;predicates則是一個(gè)List籽御,可以定義多個(gè)不同的PredicateDefinition
,即請求都會(huì)找到具體的路由信息惰匙,然后判斷是否滿足定義的predicates條件技掏。比如上面我定義的id為order-service
的路由,在進(jìn)行路由時(shí)會(huì)判斷請求的path
是不是滿足"/order/**"项鬼,且請求參數(shù)中是不是有參數(shù)名稱為"role"哑梳,且值為"ABO",只有同時(shí)滿足這兩個(gè)條件才對請求路由绘盟。因?yàn)樽约哼€沒完整的搭建好相關(guān)的服務(wù)涧衙,所以關(guān)于這塊只能后期完善之后在進(jìn)行測試了。
除了使用配置文件奥此,我們也可以通過配置類來完成這些配置內(nèi)容弧哎,比如下面使用代碼定義了上面配置文件的路由斷言,代碼如下:
@Configuration
public class GatewayConfig {
@Bean
public RouterFunction<ServerResponse> pathPredicate() {
RouterFunction routerFunction = RouterFunctions.route(
RequestPredicates.path("/user/**"),
serverRequest -> ServerResponse.ok().body(BodyInserters.fromValue("custom path route predicate")));
return routerFunction;
}
@Bean
public RouterFunction<ServerResponse> pathAndQueryPredicate() {
RouterFunction routerFunction = RouterFunctions.route(
RequestPredicates.path("/order/**").and(RequestPredicates.queryParam("role",Predicate.isEqual("ABO"))),
serverRequest -> ServerResponse.ok().body(BodyInserters.fromValue("custom path and query param route predicate")));
return routerFunction;
}
}
此外也可以通過代碼實(shí)現(xiàn)自定義的過濾器稚虎,如下:
@Bean
public RouteLocator customRoute(RouteLocatorBuilder locatorBuilder) {
log.info(">>>> custom routeLocator <<<<");
RouteLocator routeLocator = locatorBuilder.routes().route("custom-route",
r -> r.path("/order/hello").
filters(f -> f.addRequestParameter("role","ABO")).uri("http://localhost:9010"))
.build();
return routeLocator;
}
上面的代碼中自定義了一個(gè)過濾器撤嫩,當(dāng)請求為"/order/hello"時(shí),網(wǎng)關(guān)會(huì)將請求轉(zhuǎn)發(fā)給"localhost:9010"蠢终,且在會(huì)增加一個(gè)請求參數(shù)"role"序攘,其值為"ABO"。
關(guān)于使用上這里就不再介紹了寻拂,官方文檔非常的詳細(xì)程奠,需要的時(shí)候看下官方文檔我覺得應(yīng)該沒什么問題。有一點(diǎn)可能會(huì)比較好奇祭钉,就是微服務(wù)是有多個(gè)實(shí)例的瞄沙,那么網(wǎng)關(guān)在進(jìn)行路由的時(shí)候是怎么做負(fù)載均衡的呢?這一點(diǎn)其實(shí)只需要在自定義路由時(shí)進(jìn)行配置就可以了,依然用上面的配置文件舉例距境,我們需要對每個(gè)服務(wù)進(jìn)行路由配置申尼,比如有"user-service"和"order-service"兩個(gè)服務(wù),負(fù)載均衡可以如下:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/user/**
- id: order-service
uri: lb://user-service
predicates:
- Path=/order/**
- Query=role,ABO
這樣網(wǎng)關(guān)就可以對我們的路由進(jìn)行負(fù)載均衡垫桂,但是負(fù)載均衡的實(shí)現(xiàn)還不是很了解师幕,包括負(fù)載均衡的策略,這些都是以后自己需要學(xué)習(xí)的方向诬滩。
GlobalFilter
另外有一個(gè)很重要的一點(diǎn)霹粥,不管我們使用Gateway是做鑒權(quán)還是認(rèn)證都其實(shí)都需要一個(gè)過濾器作為入口,這個(gè)過濾器在Gateway中就是GlobalFilter
疼鸟,這個(gè)接口的特殊之處在于它默認(rèn)的會(huì)應(yīng)用于所有的路由蒙挑,也就是說當(dāng)一個(gè)請求進(jìn)來之后,GlobalFilter
的實(shí)例和其他的過濾器都會(huì)被添加到過濾器鏈愚臀,當(dāng)然這些過濾器鏈中的過濾器都是有序的,這些過濾器是按照org.springframework.core.Ordered
進(jìn)行排序的矾利。另外一點(diǎn)姑裂,就是過濾器是雙向的,也就是說請求時(shí)第一個(gè)進(jìn)入的過濾器男旗,在響應(yīng)時(shí)是最后一個(gè)出去的舶斧。接下來我們代碼來實(shí)現(xiàn)一個(gè)GlobalFilter
,代碼如下:
@Component
public class CustomFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest httpRequest = exchange.getRequest();
ServerHttpResponse httpResponse = exchange.getResponse();
// 業(yè)務(wù)代碼省略
....
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
在這個(gè)過濾器里面我們可以獲取到請求和響應(yīng)察皇,所以不管我們想針對用戶的某個(gè)請求或者響應(yīng)做一些特殊處理都可以在這里完成茴厉,使用上應(yīng)該還是很簡單的。
其實(shí)就使用上來講Gateway
應(yīng)該沒什么問題什荣,而且官方的文檔內(nèi)容也是比較詳細(xì)的矾缓,但是比較遺憾的是我這次沒有準(zhǔn)備項(xiàng)目,按理講其實(shí)我應(yīng)該先準(zhǔn)備好幾個(gè)小的demo稻爬,然后再來學(xué)習(xí)Gateway
會(huì)好一些嗜闻,比如我自定義的路由是不是生效,負(fù)載均衡有沒有問題桅锄,怎么自定義負(fù)載均衡策略琉雳,全局過濾器的使用等等。不過自己之后慢慢的會(huì)把微服務(wù)的各個(gè)組件會(huì)搭建起來友瘤,這些問題就以后再講吧翠肘。其實(shí)我比較關(guān)心的可能還是一些底層的實(shí)現(xiàn),特別是源碼部分辫秧,但是因?yàn)樽约阂彩亲罱佑|束倍,所以這里暫時(shí)不對源碼進(jìn)行分析了,等對Gateway
熟悉之后,我們再來學(xué)習(xí)它的源碼肌幽。