Nacos+Spring Cloud Gateway动态路由配置

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
简介:

Nacos+Spring Cloud Gateway动态路由配置

前言
  Nacos最近项目一直在使用,其简单灵活,支持更细粒度的命令空间,分组等为麻烦复杂的环境切换提供了方便;同时也很好支持动态路由的配置,只需要简单的几步即可。在国产的注册中心、配置中心中比较突出,容易上手,本文通过gateway、nacos-consumer、nacos-provider三个简单模块来展示:Nacos下动态路由配置。

一、Nacos环境准备
1、启动Nacos配置中心并创建路由配置
具体的Nacos怎么配置就不介绍了,可以参考阿里巴巴的官方介绍,这里通过windows直接本地启动开启单机模式,登录Nacos Console,创建dev的namespace,在dev下的默认分组下创建gateway-router的dataId

gateway-router的主要初始化配置如下:关于gateway的组成(id,order、predicates断言,uri)这里就不详细说明的了,可以自行百度下

[{

"id": "consumer-router",
"order": 0,
"predicates": [{
    "args": {
        "pattern": "/consume/**"
    },
    "name": "Path"
}],
"uri": "lb://nacos-consumer"

},{

"id": "provider-router",
"order": 2,
"predicates": [{
    "args": {
        "pattern": "/provide/**"
    },
    "name": "Path"
}],
"uri": "lb://nacos-provider"

}]

2、连接Nacos配置中心
通常在项目中配置“配置中心”往往都是在bootstrap.propertis(yaml)中配置,这样才能保证项目中路由配置从Nacos Config中读取。

nacos配置中心配置建议在bootstrap.properties中配置

spring.cloud.nacos.config.server-addr=127.0.0.1:8848

spring.cloud.nacos.config.file-extension=properties

配置中心的命名空间:dev 的命名空间(环境)

spring.cloud.nacos.config.namespace=08ecd1e5-c042-410a-84d5-b0a8fbeed8ea
Application启动类中增加注解@EnableDiscoveryClient,才能保证连接到Nacos Config

@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication
{

public static void main( String[] args )
{
    SpringApplication.run(GatewayApplication.class, args);
}

}

二、项目构建
1、项目结构
创建简单的spring boot多模块结构,推荐使用idea创建

1)Nacos父模块:

com.springcloud
nacos
0.0.1-SNAPSHOT
nacos
Nacos Demo
首先pom文件引入Spring Cloud Alibaba Nacos组件:注册中心nacos-discovery与配置中心nacos-config



  com.alibaba.cloud
  spring-cloud-starter-alibaba-nacos-discovery
  ${alibaba-nacos.version}



  com.alibaba.cloud
  spring-cloud-starter-alibaba-nacos-config
  ${alibaba-nacos.version}

其次再引入Spring Cloud相关组件依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>

其它组件依赖引入(修正:如果引入了nacos-api相关的JSON依赖,那么fastjson就不需要再引入了,否则可能冲突):

<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>


<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>


<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>


<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>

注意,这里有个坑,spring cloud gateway使用的web框架为webflux,和springMVC不兼容。所以不要引入(修正:只有gateway服务不用引入springMVC,其他需要引入)

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>


2)三个子模块:gateway、nacos-consumer、nacos-provider


  nacos-provider
  nacos-consumer
  gateway

结构截图如下所示:

3)三个服务的端口分别为:

  nacos-consume:6001

nacos-provider:6002

gateway:6003

4)服务架构如下:

2、编写测试代码
(1)在gateway模块中主要实现以下功能:
第一,从Nacos配置中心中加载动态路由的相关配置,就需要读取Nacos的命名空间namespace,通过dataId获取配置

/**

  • 路由类配置
    */

@Configuration
public class GatewayConfig {

public static final long DEFAULT_TIMEOUT = 30000;

public static String NACOS_SERVER_ADDR;

public static String NACOS_NAMESPACE;

public static String NACOS_ROUTE_DATA_ID;

public static String NACOS_ROUTE_GROUP;

@Value("${spring.cloud.nacos.discovery.server-addr}")
public void setNacosServerAddr(String nacosServerAddr){
    NACOS_SERVER_ADDR = nacosServerAddr;
}

@Value("${spring.cloud.nacos.discovery.namespace}")
public void setNacosNamespace(String nacosNamespace){
    NACOS_NAMESPACE = nacosNamespace;
}

@Value("${nacos.gateway.route.config.data-id}")
public void setNacosRouteDataId(String nacosRouteDataId){
    NACOS_ROUTE_DATA_ID = nacosRouteDataId;
}

@Value("${nacos.gateway.route.config.group}")
public void setNacosRouteGroup(String nacosRouteGroup){
    NACOS_ROUTE_GROUP = nacosRouteGroup;
}

}

properties配置关于Nacos下读取gateway-router的配置:

spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.namespace=08ecd1e5-c042-410a-84d5-b0a8fbeed8ea
nacos.gateway.route.config.data-id=gateway-router
nacos.gateway.route.config.group=DEFAULT_GROUP
第二,初始化路由,监听动态路由配置的数据源变化;

/**
*

  • 通过nacos下发动态路由配置,监听Nacos中gateway-route配置
    *

*/
@Component
@Slf4j
@DependsOn({"gatewayConfig"}) // 依赖于gatewayConfig bean
public class DynamicRouteServiceImplByNacos {

@Autowired
private DynamicRouteServiceImpl dynamicRouteService;
private ConfigService configService;

@PostConstruct
public void init() {
    log.info("gateway route init...");
    try{
        configService = initConfigService();
        if(configService == null){
            log.warn("initConfigService fail");
            return;
        }
        String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT);
        log.info("获取网关当前配置:\r\n{}",configInfo);
        List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
        for(RouteDefinition definition : definitionList){
            log.info("update route : {}",definition.toString());
            dynamicRouteService.add(definition);
        }
    } catch (Exception e) {
        log.error("初始化网关路由时发生错误",e);
    }
    dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP);
}

/**
 * 监听Nacos下发的动态路由配置
 * @param dataId
 * @param group
 */
public void dynamicRouteByNacosListener (String dataId, String group){
    try {
        configService.addListener(dataId, group, new Listener()  {
            @Override
            public void receiveConfigInfo(String configInfo) {
                log.info("进行网关更新:\n\r{}",configInfo);
                List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
                for(RouteDefinition definition : definitionList){
                    log.info("update route : {}",definition.toString());
                    dynamicRouteService.update(definition);
                }
            }
            @Override
            public Executor getExecutor() {
                log.info("getExecutor\n\r");
                return null;
            }
        });
    } catch (NacosException e) {
        log.error("从nacos接收动态路由配置出错!!!",e);
    }
}

/**
 * 初始化网关路由 nacos config
 * @return
 */
private ConfigService initConfigService(){
    try{
        Properties properties = new Properties();
        properties.setProperty("serverAddr",GatewayConfig.NACOS_SERVER_ADDR);
        properties.setProperty("namespace",GatewayConfig.NACOS_NAMESPACE);
        return configService= NacosFactory.createConfigService(properties);
    } catch (Exception e) {
        log.error("初始化网关路由时发生错误",e);
        return null;
    }
}

}

第三,刷新最新的动态路由变化,实现动态增删改路由

/**

  • 动态更新路由网关service
  • 1)实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
  • 2)提供动态路由的基础方法,可通过获取bean操作该类的方法。该类提供新增路由、更新路由、删除路由,然后实现发布的功能。
    */

@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

@Autowired
private RouteDefinitionWriter routeDefinitionWriter;

/**
 * 发布事件
 */
@Autowired
private ApplicationEventPublisher publisher;

@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
    this.publisher = applicationEventPublisher;
}

/**
 * 删除路由
 * @param id
 * @return
 */
public String delete(String id) {
    try {
        log.info("gateway delete route id {}",id);
        this.routeDefinitionWriter.delete(Mono.just(id));
        return "delete success";
    } catch (Exception e) {
        return "delete fail";
    }
}
/**
 * 更新路由
 * @param definition
 * @return
 */
public String update(RouteDefinition definition) {
    try {
        log.info("gateway update route {}",definition);
        this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
    } catch (Exception e) {
        return "update fail,not find route  routeId: "+definition.getId();
    }
    try {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    } catch (Exception e) {
        return "update route fail";
    }
}

/**
 * 增加路由
 * @param definition
 * @return
 */
public String add(RouteDefinition definition) {
    log.info("gateway add route {}",definition);
    routeDefinitionWriter.save(Mono.just(definition)).subscribe();
    this.publisher.publishEvent(new RefreshRoutesEvent(this));
    return "success";
}

}

(2)在consumer创建ConsumeController:通过访问gateway网关/consume/sayHello/{name}("pattern": "/consume/**"),跳转至nacos-consumer服务("uri": "lb://nacos-consumer"),

@RequestMapping("/consume/")
@Slf4j
public class ConsumeController {

@GetMapping("/sayHello/{name}")
public String sayHello(@PathVariable("name") String name){
    log.info("I'm calling nacos-consumer service by dynamic gateway...");
    return name + " Hi~, I'm from nacos-consumer";
}

}

(3)在provider创建ProviderController:通过访问gateway网关/provide/sayHello/{name}("pattern": "/provide/**"),跳转至nacos-provider服务("uri": "lb://nacos-provider")

@RestController
@RequestMapping("/provide/")
@Slf4j
public class ProviderController {

@GetMapping("/sayHello/{name}")
public String sayHello(@PathVariable("name") String name){
    log.info("I'm calling nacos-provider service by dynamic gateway...");
    return name + " Hi~, I'm from nacos-provider";
}

}

三、测试动态网关配置
1、启动服务,观察注册中心
分别启动gateway、nacos-consumer、nacos-provider三个服务,观察是否已经在Nacos上正确注册

注意:需要指定注册中心的namespace为dev的空间,即spring.cloud.nacos.discovery.namespace=08ecd1e5-c042-410a-84d5-b0a8fbeed8ea

2、访问网关,观察服务日志
(1)查看gateway服务的初始化启动日志:会发现可以正常从Nacos获取配置gateway-router网关配置文件内容,并进行正确路由加载...

 View Code

但这只能说明是初始化静态路由,下面我们改变gateway-router网关配置内容,追加github-router路由

[{

"id": "consumer-router",
"order": 0,
"predicates": [{
    "args": {
        "pattern": "/consume/**"
    },
    "name": "Path"
}],
"uri": "lb://nacos-consumer"

},{

"id": "provider-router",
"order": 2,
"predicates": [{
    "args": {
        "pattern": "/provide/**"
    },
    "name": "Path"
}],
"uri": "lb://nacos-provider"

},{

"id": "github-router",
"order": 2,
"predicates": [{
    "args": {
        "pattern": "/github/**"
    },
    "name": "Path"
}],
"uri": "https://github.com"

}]

之后点击发布更新路由配置

观察gateway服务日志,有没有监听,并且进行正确的路由更新:如下日志所示,最新路由配置立马被打印,并且进行正确路由更新

 View Code

其实,还有办法可以知道我们的gateway服务有没有监听Nacos的gateway-router配置,那就是在Nacos Console--->监听查询----->选择配置---->输入配置文件的namespace与Group: 可以发现我本地IP地址127.0.0.1对配置文件gateway-router进行了监听

(2)访问gateway网关服务:http://localhost:6003/consume/sayHello/nacos

查看consumer服务日志:

2020-05-10 14:55:07.257 INFO 6552 --- [nio-6001-exec-2] c.n.c.controller.ConsumeController : I'm calling nacos-consumer service by dynamic gateway...
发现跳转至consumer服务,并且访问了consumer服务的CosnumerController

(3)访问gateway网关服务:http://localhost:6003/provider/sayHello/nacos

查看provider服务日志:

2020-05-10 14:56:56.144 INFO 10024 --- [nio-6002-exec-1] c.n.p.controller.ProviderController : I'm calling nacos-provider service by dynamic gateway...
发现跳转至consumer服务,并且访问了provider服务的ProviderController

(4)访问访问gateway网关服务:http://localhost:6003/github,正确跳转至github页面

四、总结
1)Spring Cloud Gateway作用不光只是简单的跳转重定向,还可以实现用户的验证登录,解决跨域,日志拦截,权限控制,限流,熔断,负载均衡,黑名单和白名单机制等。是微服务架构不二的选择;

2)Nacos的配置中心支持动态获取配置文件,可以将一些全局的经常变更的配置文件放在Nacos下,需要到微服务自行获取。

原文地址https://www.cnblogs.com/jian0110/p/12862569.html

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
23天前
|
NoSQL Java Nacos
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
SpringCloud集成Seata并使用Nacos做注册中心与配置中心
56 3
|
4天前
|
运维 Java Nacos
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
Spring Cloud应用框架:Nacos作为服务注册中心和配置中心
|
22天前
|
负载均衡 Java Spring
Spring cloud gateway 如何在路由时进行负载均衡
Spring cloud gateway 如何在路由时进行负载均衡
148 15
|
22天前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
41 3
|
3天前
|
Java 数据库连接 Nacos
SpringCloud微服务配置管理、配置热更新
SpringCloud微服务配置管理、配置热更新
15 0
|
5天前
|
Dubbo Java Nacos
【实战攻略】破解Dubbo+Nacos+Spring Boot 3 Native打包后运行异常的终极秘籍——从零开始彻底攻克那些让你头疼不已的技术难题!
【8月更文挑战第15天】Nacos作为微服务注册与配置中心受到欢迎,但使用Dubbo+Nacos+Spring Boot 3进行GraalVM native打包后常遇运行异常。本文剖析此问题及其解决策略:确认GraalVM版本兼容性;配置反射列表以支持必要类和方法;采用静态代理替代动态代理;检查并调整配置文件;禁用不支持的功能;利用日志和GraalVM诊断工具定位问题;根据诊断结果调整GraalVM配置。通过系统排查方法,能有效解决此类问题,确保服务稳定运行。
19 0
|
27天前
|
消息中间件 Java Nacos
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
通用快照方案问题之通过Spring Cloud实现配置的自动更新如何解决
48 0
|
22天前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
1月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
87 0