使用Spring Cloud Gateway構建微服務網關
文章目錄
Spring Cloud GateWay介紹
微服務架構中,前端(APP或Web端)需要同各個微服務進行交互,因實際部署時不同服務可能在不同機器各個服務甚至還有集羣,這無形中增加了客戶端調用微服務的複雜程度。如一個添加一樣商品到購物車可能涉及到用戶服務、商品服務、購物車服務等,如果直接由客戶端去對應的服務地址去調用可能涉及到下面的問題:
- 客戶端會多次請求不同的微服務,增加了客戶端的複雜性。
- 存在跨域請求,在一定場景下處理相對複雜。
- 認證複雜,每個服務都需要獨立認證。
- 難以重構,隨着項目的迭代,可能需要重新劃分微服務。例如,可能將多個服務合併成一個或者將一個服務拆分成多個。如果客戶端直接與微服務通信,那麼重構將會很難實施。
- 某些微服務可能使用了防火牆 / 瀏覽器不友好的協議,直接訪問會有一定的困難
爲了解決上述問題,API網關應運而生。所有的服務都經過網關進行路由,這樣客戶端對接API網關,API網關對請求進行路由即可訪問對應的微服務。由於各個服務都由網關代理,認證、監控、鑑權、日誌等都可以在網關上做,總的來說,使用API網關優點如下:
- 易於監控。可以在網關收集監控數據並將其推送到外部系統進行分析。
- 易於認證。可以在網關上進行認證,然後再將請求轉發到後端的微服務,而無須在每個微服務中進行認證。
- 減少了客戶端與各個微服務之間的交互次數。
目前網關產品有很多,如Zuul、Kong、Api Umbrella等。由於Netflix宣佈閉源,Spring Cloud也推出了自己的網關SPring Cloud Gateway來代替Zuul,本文就以Spring Cloud Gateway爲例,學習API網關的使用。
網關服務搭建
筆者的理解中API網關在系統中的架構如下所示:
API網關負責接收客戶端的請求,在經過認證、鑑權、限流等功能後轉發到對應的微服務。在API網關可以實現單點登錄功能從而解決各個服務認證方式不同的問題。接下來搭建簡單的網關服務加深理解
新建項目
誰用http://start.spring.io搭建項目。可參考使用IDEA創建SpringBoot項目
引入依賴
在build.gradle中添加下面依賴
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-gateway'
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-openfeign'
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-netflix-hystrix'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-webflux'
// compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis-reactive'
compile group: 'org.springframework.boot', name: 'spring-boot-autoconfigure'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-logging'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-aop'
compile group: 'org.springframework.boot', name: 'spring-boot-starter-actuator'
compile group: 'org.springframework.boot', name: 'spring-boot-devtools'
其他代碼可以見最後的代碼倉庫,爲了方便閱讀這裏不貼其他無用代碼
配置路由
添加一個測試的路由,訪問網關的/test,自動路由到百度
@Configuration
public class Router1Config {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
//簽發服務路由
.route("license_route", r -> r.path("/test")
.uri("http://www.baidu.com"))
.build();
}
}
在瀏覽器訪問http://127.0.0.1:8900/test
,顯示結果如下:
從瀏覽器的network可以看出,訪問http://127.0.0.1:8900/test
被重定向到了百度的頁面。這裏簡單的網關功能已實現。接下來則實現集成Nacos並通過API網關訪問微服務
Spring Cloud GateWay集成Nacos
- 添加Nacos依賴
//Spring Cloud Alibaba 基礎框架
//服務註冊與發現
implementation "com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:${springCloudAlibabaVersion}"
//分佈式配置中心
implementation "com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-config:${springCloudAlibabaVersion}"
- 添加配置
在bootstrap.yml中添加配置
spring:
application:
name: api-gateway
cloud:
nacos:
config:
server-addr: 192.168.23.100:8848
file-extension: yaml
enabled: true
discovery:
server-addr: 192.168.23.100:8848
enabled: true
- 啓動服務
登錄Nacos管理頁面,可以看到網關也已經註冊到了Nacos
通過API網關訪問微服務
- 啓動之前文章中的服務
分別啓動nacos-provider-demo
和nacos-consumer-demo
,可以看到這兩個服務業註冊成功
- 添加路由,訪問微服務
添加如下路由
@Configuration
public class RouterConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
//服務路由,lb表示註冊中心的服務
.route("service_route", r -> r.path("/sayHi/*")
.uri("lb://nacos-provider-demo"))
.build();
}
}
啓動服務,網關服務,訪問http://127.0.0.1:9000/sayHi/xuda
結果如下,成功調用到了微服務的接口:
關於Spring Cloud Gateway的路由知識可參考Spring Cloud GateWay 路由轉發規則介紹
實現網關認證
API網關的重要特性之一就是可以實現集中認證,這裏實現要給簡單的認證功能。如果請求的header中包含user_name
字段即算作認證成功(這裏只是一個示例,具體的認證過程由項目決定)。
這裏需要用到過濾器,filter主要可分爲兩種,一種GlableFilter一種GateWayFilter。GlableFilter是全局filter對所有的路由都會經過這個過濾器,GateWayFilter粒度更細可以針對不同的路由做過濾。具體的過濾器區別可參考[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UOyTAsAi-1576386805878)(https://blog.csdn.net/forezp/article/details/85057268)]
接下來使用全局過濾器實現一個簡單的認證功能。下面只是一部分代碼,具體的代碼可以見最後的倉庫:
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
if (isSkip(path)) {
return chain.filter(exchange);
}
ServerHttpResponse resp = exchange.getResponse();
String username = exchange.getRequest().getHeaders().getFirst(TokenConstant.USER_NAME);
LOGGER.info("進入認證步驟");
if (StringUtils.isBlank(username)) {
return unAuth(resp, "認證失敗,缺少用戶名", HttpStatus.UNAUTHORIZED);
}
return chain.filter(exchange);
}
先使用不帶請求頭的模擬認證,結果如下:
再使用帶請求頭的模擬認證,結果如下:
請求成功,這樣就實現了模擬認證的功能。