SpringCloud?Gateway的使用?+?Nacos動態(tài)路由實踐指南
一、簡介
1、什么是gateway?
- SpringCloud Gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技術(shù)開發(fā)的網(wǎng)關(guān),旨在為微服務(wù)架構(gòu)提供簡單、有效和統(tǒng)一的API路由管理方式
- SpringCloud Gateway作為SpringCloud生態(tài)系統(tǒng)中的網(wǎng)關(guān),目標是替代Netflix Zuul,在SpringCloud 2.0以上版本中,沒有對新版本的Zuul 2.0以上最新高性能版本進行集成,仍然還是使用Zuul 1.x非Reactor模式的老版本。二為了提高網(wǎng)關(guān)的性能,SpringCloud Gateway是基于WebFlux框架實現(xiàn)的,而WebFlux框架底層則使用了高性能的Reactor模式通信框架Netty
- SpringCloud Gateway不僅提供統(tǒng)一的路由方式,并且還基于Filer鏈的方式提供了網(wǎng)關(guān)基本的功能,例如:鑒權(quán)、監(jiān)控/指標、流量控制、熔斷限流等
2、沒有g(shù)ateway的弊端
- 客戶端多次請求不同的微服務(wù),會增加客戶端代碼和配置的復雜性,維護成本比價高
- 認證復雜,每個微服務(wù)可能存在不同的認證方式,客戶端去調(diào)用,要去適配不同的認證
- 存在跨域的請求,調(diào)用鏈有一定的相對復雜性(防火墻 / 瀏覽器不友好的協(xié)議)
- 難以重構(gòu),隨著項目的迭代,可能需要重新劃分微服務(wù)
3、gateway解決了什么?
為了解決上面的問題,微服務(wù)引入了 網(wǎng)關(guān) 的概念,網(wǎng)關(guān)為微服務(wù)架構(gòu)的系統(tǒng)提供簡單、有效且統(tǒng)一的API路由管理,作為系統(tǒng)的統(tǒng)一入口,提供內(nèi)部服務(wù)的路由中轉(zhuǎn),給客戶端提供統(tǒng)一的服務(wù),可以實現(xiàn)一些和業(yè)務(wù)沒有耦合的公用邏輯,主要功能包含認證、鑒權(quán)、路由轉(zhuǎn)發(fā)、安全策略、防刷、流量控制、監(jiān)控日志等
4、gateway和zuul的區(qū)別
- Zuul構(gòu)建于 Servlet 2.5,兼容 3.x,使用的是阻塞式的 API,不支持長連接,比如 websockets
- Spring Cloud Gateway構(gòu)建于 Spring 5+,基于 Spring Boot 2.x 響應(yīng)式的、非阻塞式的 API。同時,它支持 websockets,和 Spring 框架緊密集成,開發(fā)體驗相對來說十分不錯
5、gateway核心概念
- Route(路由): 路由是網(wǎng)關(guān)最基礎(chǔ)的部分,路由信息有一個ID、一個目的URL、一組斷言和一組Filter組成。如果斷言路由為真,則說明請求的URL和配置匹配
- Predicate(斷言): 參考的是java8的java.util.function.Predicate,開發(fā)人員可以匹配Http請求中的所有內(nèi)容(例如請求頭或請求參數(shù)),如果請求與斷言相匹配則進行路由
- Filter(過濾器): 一個標準的Spring webFilter。SpringCloud Gateway中的Filter分為兩種類型的Filter,分別是Gateway Filter和Global Filter。使用過濾器,可以在請求被路由前或者之后對請求進行修改
6、gateway是如何工作的

官方解釋:
客戶端SpringCloud Gateway發(fā)出請求,然后在Gateway Handler Mapping中找到與之請求相匹配的路由,將其發(fā)送到Gateway Web Handler,Handler再通過指定的過濾器鏈來將請求發(fā)送到我們實際的服務(wù)執(zhí)行業(yè)務(wù)邏輯,然后返回。
過濾器之間用虛線分開是因為過濾器可能會發(fā)在代理請求之前(“pre”)或之后(“post”)執(zhí)行業(yè)務(wù)邏輯,這樣,F(xiàn)ilter在“pre”類型的過濾器可以做參數(shù)校驗,權(quán)限校驗,流量監(jiān)控,日志輸出,協(xié)議轉(zhuǎn)換等;在“post”類型的過濾器可以做響應(yīng)內(nèi)容,響應(yīng)頭的修改,日志的輸出,流量監(jiān)控等有著非常重要的作用
二、構(gòu)建一個springcloud Gateway服務(wù)
1、新建一個微服務(wù)
1.1、新建gateway子模塊

nacos注冊中心和配置中心以及服務(wù)服搭建可以參考之前的文章,這里基于之前的項目構(gòu)建gateway服務(wù)
SpringCloud Alibaba微服務(wù)實戰(zhàn)之遠程Feign請求頭丟失問題解決方案
1.2、引入依賴
gateway服務(wù)依賴
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>3.0.7</version>
</dependency>
<dependency>
<groupId>com.mdx</groupId>
<artifactId>mdx-shop-common</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>下面是全局的關(guān)于springcloud的依賴
spring-cloud.version:2021.0.1
spring-cloud-alibaba.version: 2021.0.1.0
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>${spring-cloud-alibaba.version}</version>
</dependency>2、配置服務(wù)
2.1、創(chuàng)建啟動類
@SpringBootApplication
@EnableFeignClients
public class MdxShopGateWayApplication {
public static void main(String[] args) {
SpringApplication.run(MdxShopGateWayApplication.class, args);
}
}
2.2、創(chuàng)建application.yml配置文件
使用ip路由的方式:
server:
port: 9010
spring:
application:
name: mdx-shop-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: mdx
group: mdx
gateway:
routes:
- id: mdx-shop-user #路由的ID,沒有固定規(guī)則但要求唯一,建議配合服務(wù)名
uri: http://localhost:9090 #匹配后提供服務(wù)的路由地址
predicates:
- Path=/user/** #斷言,路徑相匹配的進行路由
- id: mdx-shop-order
uri: http://localhost:9091
predicates:
- Path=/order/**2.3、啟動并訪問Gateway服務(wù)
發(fā)現(xiàn)報錯了…
大致意思是在springboot整合gateway時, gateway組件中的 【spring-boot-starter-webflux】 和 springboot作為web項目啟動必不可少的 【spring-boot-starter-web】 出現(xiàn)沖突

我們按照提示: Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.
在配置文件配置下 spring.main.web-application-type=reactive 就好了
main:
web-application-type: reactive接著在重新啟動項目,成功啟動
然后我們再依次啟動order服務(wù)和user服務(wù)
通過gateway訪問user服務(wù):
http://localhost:9010/user/getOrderNo?userId=mdx123456
其中9010端口為網(wǎng)關(guān)服務(wù)

通過gateway訪問order服務(wù):
http://localhost:9010/order/getOrderNo?userId=mdx123456
其中9010端口為網(wǎng)關(guān)服務(wù)

可見以上gateway均已成功路由到兩個服務(wù)
2.4、通過微服務(wù)名稱的方式來路由服務(wù)
把 gateway配置文件中的 uri: http://localhost:9090 改為 uri: lb://mdx-shop-user 這種服務(wù)名的形式
server:
port: 9010
spring:
application:
name: mdx-shop-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: mdx
group: mdx
gateway:
routes:
- id: mdx-shop-user #路由的ID,沒有固定規(guī)則但要求唯一,建議配合服務(wù)名
uri: lb://mdx-shop-user #匹配后提供服務(wù)的路由地址
predicates:
- Path=/user/** #斷言,路徑相匹配的進行路由
- id: mdx-shop-order
uri: lb://mdx-shop-order
predicates:
- Path=/order/**
main:
web-application-type: reactive再來測試一下user服務(wù)
http://localhost:9010/user/getOrderNo?userId=mdx123456
其中9010端口為網(wǎng)關(guān)服務(wù)

成功返回
2.5、路由websocket服務(wù)
將 uri: lb://mdx-shop-user 改為 uri: lb:ws://mdx-shop-user
routes:
- id: mdx-shop-user #路由的ID,沒有固定規(guī)則但要求唯一,建議配合服務(wù)名
uri: lb:ws://mdx-shop-user #匹配后提供服務(wù)的路由地址
predicates:
- Path=/user/** #斷言,路徑相匹配的進行路由2.6、測試負載均衡
采用這種路由方式 uri: lb://mdx-shop-user
在gateway添加配置:
開啟通過服務(wù)中心的自動根據(jù) serviceId 創(chuàng)建路由的功能
gateway:
discovery:
locator:
enabled: true我們在order服務(wù)中寫一個測試類,如下
/**
* 測試負載均衡
* @return
*/
@GetMapping("lb")
public String lb(){
System.out.println("test lb");
return "lb";
}分別啟動兩個order服務(wù)(啟動一個order服務(wù)之后,修改下端口號再啟動一個)
在idea中啟動同一個服務(wù)的多個端口操作如下:

成功啟動了兩個order服務(wù)

nacos狀態(tài)如下(啟動了兩個實例)

我們再來通過網(wǎng)關(guān)訪問下order服務(wù)
http://localhost:9010/order/lb
其中 9010 為網(wǎng)關(guān)端口
首先訪問一次
我們看到order1服務(wù)打印了日志,order2服務(wù)沒有日志


再訪問一次接口
這個時候order2打印了日志,order1沒有打印日志

如此實現(xiàn)了簡單的負載均衡
三、通過nacos實現(xiàn)動態(tài)路由
微服務(wù)都是互相獨立的,假如我們的網(wǎng)關(guān)和其他服務(wù)都在線上已經(jīng)運行了好久,這個時候增加了一個微服務(wù),這個時候要通過網(wǎng)關(guān)訪問的話需要通過修改配置文件來增加路由規(guī)則,并且需要重啟項目,所以我們需要實現(xiàn)動態(tài)路由
1、創(chuàng)建路由配置接口
新建路由發(fā)布接口
/**
* 路由配置服務(wù)
* @author : jiagang
* @date : Created in 2022/7/20 11:07
*/
public interface RouteService {
/**
* 更新路由配置
*
* @param routeDefinition
*/
void update(RouteDefinition routeDefinition);
/**
* 添加路由配置
*
* @param routeDefinition
*/
void add(RouteDefinition routeDefinition);
}實現(xiàn)類如下
package com.mdx.gateway.service.impl;
import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
/**
* @author : jiagang
* @date : Created in 2022/7/20 11:10
*/
@Service
@Slf4j
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
/**
* 事件發(fā)布者
*/
private ApplicationEventPublisher publisher;
@Override
public void update(RouteDefinition routeDefinition) {
log.info("更新路由配置項:{}", routeDefinition);
this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void add(RouteDefinition routeDefinition) {
log.info("新增路由配置項:{}", routeDefinition);
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
}其中:
RouteDefinitionWriter:提供了對路由的增加刪除等操作
ApplicationEventPublisher: 是ApplicationContext的父接口之一,他的功能就是發(fā)布事件,也就是把某個事件告訴所有與這個事件相關(guān)的監(jiān)聽器
2、在nacos創(chuàng)建gateway-routes配置文件
將路由信息放到nacos的配置文件下
新建配置文件,并將order服務(wù)的路由添加到配置文件

配置路由如下:
[
{
"predicates":[
{
"args":{
"pattern":"/order/**"
},
"name":"Path"
}
],
"id":"mdx-shop-order",
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"lb://mdx-shop-order",
"order":1
}
]這個路由配置對應(yīng)的就是gateway中的RouteDefinition類
3、在本地配置文件下配置路由的data-id和group和命名空間
gateway:
routes:
config:
data-id: gateway-routes #動態(tài)路由
group: shop
namespace: mdx完整配置文件(刪除或者注釋掉之前配置在本地文件的路由)
server:
port: 9010
spring:
application:
name: mdx-shop-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: mdx
group: mdx
gateway:
discovery:
locator:
enabled: true #開啟通過服務(wù)中心的自動根據(jù) serviceId 創(chuàng)建路由的功能
main:
web-application-type: reactive
gateway:
routes:
config:
data-id: gateway-routes #動態(tài)路由
group: shop
namespace: mdx4、創(chuàng)建路由相關(guān)配置類
創(chuàng)建配置類引入配置
/**
* @author : jiagang
* @date : Created in 2022/7/20 14:44
*/
@ConfigurationProperties(prefix = "gateway.routes.config")
@Component
@Data
public class GatewayRouteConfigProperties {
private String dataId;
private String group;
private String namespace;
}5、實例化nacos的ConfigService,交由springbean管理
ConfigService 這個類是nacos的分布式配置接口,主要是用來獲取配置和添加監(jiān)聽器
由NacosFactory來創(chuàng)建ConfigService
/**
* 將configService交由spring管理
* @author : jiagang
* @date : Created in 2022/7/20 15:27
*/
@Configuration
public class GatewayConfigServiceConfig {
@Autowired
private GatewayRouteConfigProperties configProperties;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Bean
public ConfigService configService() throws NacosException {
Properties properties = new Properties();
properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosConfigProperties.getServerAddr());
properties.setProperty(PropertyKeyConst.NAMESPACE, configProperties.getNamespace());
return NacosFactory.createConfigService(properties);
}
}6、動態(tài)路由主要實現(xiàn)
項目啟動時會加載這個類
@PostConstruc 注解的作用,在spring bean的生命周期依賴注入完成后被調(diào)用的方法
package com.mdx.gateway.route;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mdx.common.utils.StringUtils;
import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* @author : jiagang
* @date : Created in 2022/7/20 15:04
*/
@Component
@Slf4j
@RefreshScope
public class GatewayRouteInitConfig {
@Autowired
private GatewayRouteConfigProperties configProperties;
@Autowired
private NacosConfigProperties nacosConfigProperties;
@Autowired
private RouteService routeService;
/**
* nacos 配置服務(wù)
*/
@Autowired
private ConfigService configService;
/**
* JSON 轉(zhuǎn)換對象
*/
private final ObjectMapper objectMapper = new ObjectMapper();
@PostConstruct
public void init() {
log.info("開始網(wǎng)關(guān)動態(tài)路由初始化...");
try {
// getConfigAndSignListener()方法 發(fā)起長輪詢和對dataId數(shù)據(jù)變更注冊監(jiān)聽的操作
// getConfig 只是發(fā)送普通的HTTP請求
String initConfigInfo = configService.getConfigAndSignListener(configProperties.getDataId(), configProperties.getGroup(), nacosConfigProperties.getTimeout(), new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
if (StringUtils.isNotEmpty(configInfo)) {
log.info("接收到網(wǎng)關(guān)路由更新配置:\r\n{}", configInfo);
List<RouteDefinition> routeDefinitions = null;
try {
routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {
});
} catch (JsonProcessingException e) {
log.error("解析路由配置出錯," + e.getMessage(), e);
}
for (RouteDefinition definition : Objects.requireNonNull(routeDefinitions)) {
routeService.update(definition);
}
} else {
log.warn("當前網(wǎng)關(guān)無動態(tài)路由相關(guān)配置");
}
}
});
log.info("獲取網(wǎng)關(guān)當前動態(tài)路由配置:\r\n{}", initConfigInfo);
if (StringUtils.isNotEmpty(initConfigInfo)) {
List<RouteDefinition> routeDefinitions = objectMapper.readValue(initConfigInfo, new TypeReference<List<RouteDefinition>>() {
});
for (RouteDefinition definition : routeDefinitions) {
routeService.add(definition);
}
} else {
log.warn("當前網(wǎng)關(guān)無動態(tài)路由相關(guān)配置");
}
log.info("結(jié)束網(wǎng)關(guān)動態(tài)路由初始化...");
} catch (Exception e) {
log.error("初始化網(wǎng)關(guān)路由時發(fā)生錯誤", e);
}
}
}如果項目啟動時,在發(fā)布路由的時候卡在 this.publisher.publishEvent(new RefreshRoutesEvent(this)); 這個地方走不下去
請在GatewayRouteInitConfig這個類加@RefreshScope注解
5、測試動態(tài)路由
前面我們已經(jīng)把本地的yml中的路由注釋掉了,現(xiàn)在我們來通過gateway服務(wù)來掉一個order服務(wù)的接口
接口地址:http://localhost:9010/mdx-shop-order/order/lb
其中9010是網(wǎng)關(guān)端口
可以看到路由成功

然后我們再在nacos配置中心加一個user服務(wù)的路由
[
{
"predicates":[
{
"args":{
"pattern":"/mdx-shop-order/**"
},
"name":"Path"
}
],
"id":"mdx-shop-order",
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"lb://mdx-shop-order",
"order":1
},
{
"predicates":[
{
"args":{
"pattern":"/mdx-shop-user/**"
},
"name":"Path"
}
],
"id":"mdx-shop-user",
"filters":[
{
"args":{
"parts":1
},
"name":"StripPrefix"
}
],
"uri":"lb://mdx-shop-user",
"order":2
}
]然后點發(fā)布
可以看到gateway的監(jiān)聽器已經(jīng)監(jiān)聽到配置的改動

不重新啟動gateway的情況下再來通過網(wǎng)關(guān)訪問下user服務(wù)
接口地址:http://localhost:9010/mdx-shop-user/user/getOrderNo?userId=mdx123456
其中9010是網(wǎng)關(guān)端口
可以看到成功路由

到這里gateway的使用和nacos動態(tài)路由就結(jié)束了~
到此這篇關(guān)于SpringCloud Gateway的使用 + Nacos動態(tài)路由實踐指南的文章就介紹到這了,更多相關(guān)springcloud gateway使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC Mock測試實現(xiàn)原理及實現(xiàn)過程詳解
這篇文章主要介紹了SpringMVC Mock測試實現(xiàn)原理及實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-10-10
解決springSecurity 使用默認登陸界面登錄后無法跳轉(zhuǎn)問題
這篇文章主要介紹了解決springSecurity 使用默認登陸界面登錄后無法跳轉(zhuǎn)問題,項目環(huán)境springboot下使用springSecurity 版本2.7.8,本文通過實例代碼給大家介紹的非常詳細,需要的朋友參考下吧2023-12-12
Java創(chuàng)建多線程異步執(zhí)行實現(xiàn)代碼解析
這篇文章主要介紹了Java創(chuàng)建多線程異步執(zhí)行實現(xiàn)代碼解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-07-07
Spring?框架的?MethodInterceptor?簡介及示例代碼
MethodInterceptor接口定義了一個方法Object?intercept(Object?obj,?Method?method,?Object[]?args,?MethodProxy?proxy)?,該方法在代理對象的方法被調(diào)用時被觸發(fā),這篇文章主要介紹了Spring?框架的?MethodInterceptor?簡介及示例代碼,需要的朋友可以參考下2023-09-09

