SpringCloud Gateway基于Nacos的动态网关

前言

gateway可以默认通过配置开启,服务自动注册服务名为网关路由地址,比如:服务sc-xxx,可以通过:http://gateway/sc-xx/ 进行访问,开启对应的配置如下:

spring:
  cloud:
    gateway:
      routes:
        discovery:
          locator:
            enabled: true

但有时候我们不想这个样做,原因:

  • 开启默认的服务名方式路由映射,会将所有服务的路径都自动暴露在网关下。
  • 微服务体系中有多个网关,路由不同的服务,不能默认所有服务都进行路由配置。
  • 有时候需要对网关进行额外的配置,比如做个反向代理、转发服务名改为其他名称,默认locator无法满足
  • 修改网关配置,需要重启服务,有没有可以不重新打包,不重启就能设置网关配置项的方式哪?

动态网关解决的问题

  • 解决修改路由信息和网关服务配置信息必须重启服务才能生效
  • 网关作为服务入口,避免重启,造成服务不稳定

实现思路

  • 路由信息、配置信息放在nacos中,gateway项目监听nacos对应的配置修改
  • 基于SpringCloud Gateway的 RouteDefinitionWriter 接口删除和新增路由配置
  • 基于事件 RefreshRoutesEvent 刷新系统中的路由配置

实现细节

新建一个动态路由操作类 DynamicRouteManager 因为要发布 RefreshRoutesEvent 事件,所以需要实现spring 的 ApplicationEventPublisherAware 接口,提供如下方法:

  • 增加路由 void add(RouteDefinition definition)
  • 更新路由 void update(RouteDefinition definition)
  • 删除路由 void delete(String id)

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.Component;
import reactor.core.publisher.Mono;

import java.util.List;

/**
 * 动态路由OP类
 */
@Component
public class DynamicRouteManager implements ApplicationEventPublisherAware {

    private final RouteDefinitionWriter routeDefinitionWriter;

    private ApplicationEventPublisher publisher;

    public DynamicRouteManager(RouteDefinitionWriter routeDefinitionWriter) {
        this.routeDefinitionWriter = routeDefinitionWriter;
    }

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

    /**
     * 增加路由
     */
    public void add(RouteDefinition definition) {
                routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * 更新路由
     */
    public void update(RouteDefinition definition) {
                routeDefinitionWriter.delete(Mono.just(definition.getId()));
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    /**
     * 批量更新路由
     */
    public void updateList(List<RouteDefinition> routeDefinitions) {
        routeDefinitions.forEach(this::update);
    }

    /**
     * 删除路由
     */
    public void delete(String id) {
        routeDefinitionWriter.delete(Mono.just(id));
    }


}

新建一个 NacosDynamicRouteListener 监听类nacos路由信息变化


import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 动态路由监听器
 *
 * @author Chill
 */
@Order
@Slf4j
@Component
public class NacosDynamicRouteListener implements InitializingBean {

    private final DynamicRouteManager dynamicRouteManager;
    private final NacosDiscoveryProperties nacosDiscoveryProperties;
    private final NacosConfigProperties nacosConfigProperties;
    private final DynamicRouteProperties dynamicRouteProperties;

    public NacosDynamicRouteListener(DynamicRouteManager dynamicRouteManager,
                                     NacosDiscoveryProperties nacosDiscoveryProperties,
                                     NacosConfigProperties nacosConfigProperties,
                                     DynamicRouteProperties dynamicRouteProperties) {
        this.dynamicRouteManager = dynamicRouteManager;
        this.nacosDiscoveryProperties = nacosDiscoveryProperties;
        this.nacosConfigProperties = nacosConfigProperties;
        this.dynamicRouteProperties = dynamicRouteProperties;
    }

    /**
     * 监听Nacos的动态路由配置
     */
    private void dynamicRouteListener() {
        try {
            String dataId = dynamicRouteProperties.getDataId();
            String group = nacosConfigProperties.getGroup();
            Properties properties = new Properties();
            properties.setProperty(PropertyKeyConst.NAMESPACE, nacosDiscoveryProperties.getNamespace());
            properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosDiscoveryProperties.getServerAddr());
            ConfigService configService = NacosFactory.createConfigService(properties);
            configService.addListener(dataId, group, new Listener() {
                @Override
                public void receiveConfigInfo(String configInfo) {
                    log.info("---监听到路由信息刷新---\n{}\n---------------------------------------", configInfo);
                    List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
                    dynamicRouteManager.updateList(routeDefinitions);
                }

                @Override
                public Executor getExecutor() {
                    return null;
                }
            });
            String configInfo = configService.getConfig(dataId, group, 1000 * 10);
            if (configInfo != null) {
                List<RouteDefinition> routeDefinitions = JSON.parseArray(configInfo, RouteDefinition.class);
                dynamicRouteManager.updateList(routeDefinitions);
            }
        } catch (NacosException e) {
            log.error("动态路由监听异常", e);
        }
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        dynamicRouteListener();
    }
}

实例展示

监听路由配置信息:xxx.xxx.xxx.json 配置文件,初始配置如下:

[
    {
        "id":"service-xxx",
        "uri":"lb://service-xxx/",
        "predicates":[
            {
                "name":"Path",
                "args":{
                    "pattern1":"/service-xxx/**"
                }
            }
        ],
        "order":1
    }
]

访问地址:http://localhost:8000/service-xxx ,响应结果报错500说明服务路由识别成功,并没有404,可以用一个正常的接口测试就不会500了。

{
    "msg": "Failed to handle request [GET http://localhost:8002/service-xxx]: ",
    "code": 500,
    "data": null
}

修改一下Naco对应json配置,pattern1的service-xxx1 改成 service-xxx1/如下:

[
    {
        "id":"service-xxx",
        "uri":"lb://service-xxx/",
        "predicates":[
            {
                "name":"Path",
                "args":{
                    "pattern1":"/service-xxx1/**"
                }
            }
        ],
        "order":1
    }
]

日志打?。?/p>

2022-06-20 18:24:14.362 [xy-venus-gateway]  INFO 509450 [ternal.notifier] o.s.v.g.d.NacosDynamicRouteListener      76   [TID: N/A] : ---监听到路由信息刷新---
[
    {
        "id":"service-xxx",
        "uri":"lb://service-xxx/",
        "predicates":[
            {
                "name":"Path",
                "args":{
                    "pattern1":"/service-xxx1/**"
                }
            }
        ],
        "order":1
    }
]

再次访问原接口:http://localhost:8000/service-xxx ,404 报错,路由信息已经改变了。

{
    "msg": "Failed to handle request [GET http://localhost:8002/service-xxx]: 404 NOT_FOUND",
    "code": 404,
    "data": null
}
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容