一、什么是Gateway
如果沒有網(wǎng)關(guān),我們在開發(fā)和治理微服務(wù)架構(gòu)的系統(tǒng)時(shí),為了完成一個(gè)業(yè)務(wù)邏輯的功能,不得不自己調(diào)用多個(gè)服務(wù)的主機(jī)和端口,會(huì)顯得繁雜,容易出錯(cuò)。因此微服務(wù)網(wǎng)關(guān)項(xiàng)目應(yīng)運(yùn)而生。
網(wǎng)關(guān)為微服務(wù)架構(gòu)的系統(tǒng)提供簡單、有效且統(tǒng)一的API路由管理,主要功能包含協(xié)議適配、協(xié)議轉(zhuǎn)發(fā)、安全策略、防刷、流量控制、監(jiān)控日志等。
二、入門案例
從start.sprinig.io上一鍵下載包含了Gateway組件的項(xiàng)目即可。

然后在啟動(dòng)類上配置路由信息:
@SpringBootApplication
public class GatewayDemoApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayDemoApplication.class, args);
}
// 配置路由信息
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
return builder.routes()
.route("blog_route",r -> r.path("/zhangxun")
.uri("https://blog.csdn.net"))
.route("search_route", r -> r.path("/jansen")
.uri("https://www.zhihu.com/"))
.build();
}
}
以上配置路由信息主要定義了如下三個(gè)要素:
- 路由ID,此處就是
blog_route,可以自定義; - 路由斷言,此處就是
/zhangxun,凡是符合這個(gè)路由斷言的訪問路徑都會(huì)被通過; - 路由目標(biāo),此時(shí)就是
https://blog.csdn.net;
當(dāng)我們訪問Gateway項(xiàng)目時(shí),如果訪問路徑上包含"/zhangxun",網(wǎng)關(guān)就會(huì)將請求轉(zhuǎn)發(fā)到"https://blog.csdn.net"上,我們啟動(dòng)如上的項(xiàng)目,然后訪問“http://localhost:8080/zhangxun”就會(huì)看到,最終訪問到的是CSDN的網(wǎng)址。
在實(shí)際開發(fā)中,一般都會(huì)采用配置文件的方式,而不是如上代碼配置的方式。因?yàn)榕渲梦募?nèi)容可以托管到統(tǒng)一配置中心,給與服務(wù)治理更大的靈活性。如下是等價(jià)的配置文件形式:
spring:
application:
name: gateway-demo-service
cloud:
gateway:
routes:
- id: blog_route
# uri: lb://注冊中心的服務(wù)名稱
uri: https://blog.csdn.net
predicates:
- Path=/zhangxun
- id: search_route
# uri: lb://注冊中心的服務(wù)名稱
uri: https://www.zhihu.com/
predicates:
- Path=/jansen
三、路由斷言
Gateway包含了很多的路由斷言工廠,當(dāng)請求達(dá)到Gateway的時(shí)候,配置好的的路由斷言工廠就會(huì)根據(jù)配置的路由規(guī)則,對請求進(jìn)行斷言匹配,只有匹配成功才會(huì)進(jìn)入到下一步的處理中,否則斷言失敗就會(huì)直接返回錯(cuò)誤信息。
如下是Gateway支持的一些斷言工廠,分別在不同的場景下可以配置使用:

3.1 Datetime
假設(shè)我們設(shè)定一個(gè)標(biāo)準(zhǔn)時(shí)間,這個(gè)標(biāo)準(zhǔn)時(shí)間是某熱銷產(chǎn)品的開賣時(shí)間,只有當(dāng)達(dá)到這個(gè)開賣時(shí)間時(shí),用戶購買請求才可以被轉(zhuǎn)發(fā)到購買的服務(wù)上。此時(shí)我們就需要用到Datetime分類中的AfterRoutePredicateFactory。
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder){
// 設(shè)置一小時(shí)前的時(shí)間為標(biāo)準(zhǔn)時(shí)間
ZonedDateTime standardTime = LocalDateTime.now().minusHours(1).atZone(ZoneId.systemDefault());
return builder.routes()
.route("after_route", r -> r.after(standardTime)
.uri("https://www.zhihu.com/"))
.build();
}
假設(shè)我們的熱銷產(chǎn)品有一個(gè)截止買賣時(shí)間,超過這個(gè)時(shí)間就不能再購買,那么可以使用Datetime分類的BeforeRoutePredicateFactory。
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder){
// 設(shè)置一小時(shí)后的時(shí)間為標(biāo)準(zhǔn)時(shí)間
ZonedDateTime standardTime = LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault());
return builder.routes()
.route("after_route", r -> r.before(standardTime)
.uri("https://www.zhihu.com/"))
.build();
}
同樣的,我們還可以設(shè)置一個(gè)時(shí)間段,在這個(gè)時(shí)間段內(nèi)的請求才是合法的,使用Datetime分類的BetweenRoutePredicateFactory。
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder){
// 設(shè)置一小時(shí)前的時(shí)間為開始時(shí)間
ZonedDateTime startTime = LocalDateTime.now().minusHours(1).atZone(ZoneId.systemDefault());
// 設(shè)置一小時(shí)后的時(shí)間為截止時(shí)間
ZonedDateTime endTime = LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault());
return builder.routes()
.route("after_route", r -> r.between(startTime, endTime)
.uri("https://www.zhihu.com/"))
.build();
}
3.2 Cookie
我們還可以設(shè)定對請求的cookie內(nèi)容進(jìn)行斷言:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("cookie_route", r -> r.cookie("name", "zhangsan")
.uri("https://www.zhihu.com/"))
.build();
}
在測試的時(shí)候,我們使用postman進(jìn)行,首先輸入期望的cookie內(nèi)容進(jìn)行測試:

然后輸入非期望的cookie內(nèi)容進(jìn)行測試:

3.3 Weight
基于路由權(quán)重的斷言,指定路由分組和權(quán)重值即可使用:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("weight_route_1", r -> r.weight("w1", 5)
.uri("https://www.zhihu.com/"))
.route("weight_route_2", r -> r.weight("w1", 95)
.uri("https://blog.csdn.net"))
.build();
}
這里配置了兩個(gè)路由,它們屬于同一個(gè)路由分組w1,但是知乎網(wǎng)站的轉(zhuǎn)發(fā)權(quán)重為5%,csdn網(wǎng)站的轉(zhuǎn)發(fā)權(quán)重為95%,那么我們的請求將有95%概率會(huì)被轉(zhuǎn)發(fā)到csdn去。
3.4 Header
Header中的某個(gè)屬性值必須符合預(yù)期才會(huì)通過;
3.5 Host
請求來源域名必須是預(yù)期的才會(huì)通過;
3.6 Method
請求的方法必須是預(yù)期的才會(huì)通過;
3.7 Path
已經(jīng)在第二節(jié)入門案例中演示過,符合預(yù)期路徑才會(huì)通過;
3.8 Query
對請求路徑中的參數(shù)內(nèi)容進(jìn)行斷言;
3.9 RemoteAddr
基本同Host,只不過此時(shí)應(yīng)該寫IP地址或者網(wǎng)段;
四、內(nèi)置Filter
過濾器的作用就是可以按照應(yīng)用場景修改請求和返回的內(nèi)容,總的來說,F(xiàn)ilter可以分為如下七類,但是不限于以下這些Filter:

4.1 Header
對于匹配上的請求加上header內(nèi)容:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("add_request_header_route", r -> r.path("/zhangxun")
.filters(f -> f.addRequestHeader("name", "zhangsan"))
.uri("https://www.zhihu.com/"))
.build();
}
4.2 Parameter
會(huì)在請求地址后面增加上請求參數(shù):
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("add_request_parameter_route", r -> r.path("/zhangxun")
.filters(f -> f.addRequestParameter("name", "zhangsan"))
.uri("https://www.zhihu.com/"))
.build();
}
4.3 Path
StripPrefixGatewayFitlerFactory是用于去除請求前綴,而PrefixPathGatewayFilterFactory是用于增加請求前綴。
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/zhangxun/jansen")
.filters(f -> f.stripPrefix(2))
.uri("https://www.zhihu.com/"))
.build();
}
如上前綴過濾器在我們訪問http://localhost:8080/zhangxun/jansen的時(shí)候,會(huì)把路徑前綴的前兩個(gè)去掉再進(jìn)行轉(zhuǎn)發(fā)https://www.zhihu.com/。
4.4 Status
4.5 Redirect
4.6 Hystrix
4.7 RateLimeter
部分實(shí)例暫未涉及,后續(xù)再補(bǔ)充。