SpringCloud-gateway编码实现路由策略的自动刷新,动态路由

文章目录

一、概述

1、背景

gateway可以配置路由断言过滤器,但是通常一个微服务体系下,一个gateway网关对应多个微服务,如果上线一个新的微服务或者修改一个微服务,修改网关路由配置之后,通常需要重启网关之后,路由配置才会生效,这样的影响会比较大。

考虑实现gateway的动态路由,不重启网关即可生效路由。

2、实现思路

基于nacos的配置,实现修改nacos的配置之后,通知给网关,在网关里编写逻辑,实现路由的自动刷新。

二、编码实现

1、nacos配置刷新公共类

java 复制代码
/**
 * nacos配置加载器
 */
public interface NacosPropertiesLoader {


    /**
     * 获取dataId
     */
    String getDataId();

    /**
     * 配置刷新的回调
     */
    void getConfigData(String configData);
}
java 复制代码
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.alibaba.nacos.api.exception.NacosException;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

@Configuration
public class NacosConfigHandler implements ApplicationListener<ApplicationReadyEvent>, ApplicationContextAware {

    private final NacosConfigManager nacosConfigManager;

    List<NacosPropertiesLoader> nacosPropertiesLoaderList = new CopyOnWriteArrayList<>();

    private String groupId;


    public NacosConfigHandler(NacosConfigManager nacosConfigManager) {
        this.nacosConfigManager = nacosConfigManager;
    }


    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        // 容器环境准备完毕了,加载配置
        ConfigService configService = nacosConfigManager.getConfigService();

        try {
            // 加载所有的配置,并设置监听器
            for (NacosPropertiesLoader nacosPropertiesLoader : nacosPropertiesLoaderList) {

                nacosPropertiesLoader.getConfigData(
                        configService.getConfig(nacosPropertiesLoader.getDataId(), groupId, 3000)
                );

                configService.addListener(nacosPropertiesLoader.getDataId(), groupId, new AbstractListener() {
                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        nacosPropertiesLoader.getConfigData(configInfo);
                    }
                });

            }
        } catch (NacosException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, NacosPropertiesLoader> nacosPropertiesLoaderBeans = applicationContext.getBeansOfType(NacosPropertiesLoader.class);
        if (nacosPropertiesLoaderBeans == null) {
            return;
        }
        for (NacosPropertiesLoader value : nacosPropertiesLoaderBeans.values()) {
            nacosPropertiesLoaderList.add(value);
        }

        // 从配置中读取nacos.group  nacos的groupId
        groupId = applicationContext.getEnvironment().getProperty("nacos.group");

    }
}

2、自定义RouteDefinition

java 复制代码
import com.alibaba.fastjson2.JSON;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.route.RouteDefinition;

import java.util.List;

/**
 * 自定义RouteDefinition
 */
public class MyRouteDefinition extends RouteDefinition {
    /**
     * 路由状态 0禁用 1启用
     */
    private Integer status;

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public static List<MyRouteDefinition> load(String config) {
        if (StringUtils.isEmpty(config)) {
            return null;
        }
        List<MyRouteDefinition> myRouteDefinitions = JSON.parseArray(config, MyRouteDefinition.class);
        return myRouteDefinitions;

    }

}

3、route缓存类

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static java.util.Collections.synchronizedMap;

/**
 * route缓存自定义
 */
@Component
public class MyInMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
    private static final Logger log = LoggerFactory.getLogger(DynamicRouteService.class);

    private Map<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());


    public void refreshRoute(List<MyRouteDefinition> routeDefinitions) {
        Map<String, RouteDefinition> newRoutes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());
        routeDefinitions.forEach(r -> newRoutes.put(r.getId(), r));
        routes = newRoutes;
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(r -> {
            if (ObjectUtils.isEmpty(r.getId())) {
                return Mono.error(new IllegalArgumentException("id may not be empty"));
            }
            routes.put(r.getId(), r);
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            if (routes.containsKey(id)) {
                routes.remove(id);
                return Mono.empty();
            }
//            return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
            log.warn("RouteDefinition not found: " + routeId);
            return Mono.empty();
        });
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        Map<String, RouteDefinition> routesSafeCopy = new LinkedHashMap<>(routes);
        return Flux.fromIterable(routesSafeCopy.values());
    }
}

4、动态更新路由网关service

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.List;

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

    private static final Logger log = LoggerFactory.getLogger(DynamicRouteService.class);

    @Autowired
    private MyInMemoryRouteDefinitionRepository repository;

    /**
     * 发布事件
     */

    private ApplicationEventPublisher publisher;

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

    /**
     * 删除路由
     *
     * @param id
     * @return
     */
    public synchronized void delete(String id) {
        try {
            repository.delete(Mono.just(id)).subscribe();
//            this.publisher.publishEvent(new RefreshRoutesEvent(this));
        }catch (Exception e){
            e.printStackTrace();
        }
    }

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

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

    public void refreshRoutes(List<MyRouteDefinition> load) {
        repository.refreshRoute(load);
    }
}

5、动态路由加载类

java 复制代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class DynamicRouteLoader implements NacosPropertiesLoader {

    private static final Logger log = LoggerFactory.getLogger(DynamicRouteLoader.class);


    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private DynamicRouteService dynamicRouteService;
	// nacos上配置的dataID
    private static final String DataId = "gateway_routes";

    @Override
    public String getDataId() {
        return DataId;
    }

    @Override
    public void getConfigData(String configData) {
        log.info("加载到路由配置:{}", log);
        // 动态加载路由
        List<MyRouteDefinition> load = MyRouteDefinition.load(configData);

        if (load == null || load.size() == 0) {
            log.info("未加载到routes");
            return;

        }

        dynamicRouteService.refreshRoutes(load);

        // 路由刷新事件,让路由生效
        this.applicationContext.publishEvent(new RefreshRoutesEvent(this));
    }
}

三、测试

在nacos上创建一个配置(注意dataid和group):

内容需要按照json格式进行配置(其他格式需要手写配置的解析方法)

json 复制代码
[{
	"filters": [],
	"id": "payment_routh",
	"metadata": {},
	"order": 0,
	"predicates": [{
		"args": {
			"_genkey_0": "/test/**"
		},
		"name": "Path"
	}],
	"uri": "lb://test1"
},
{
	"filters": [],
	"id": "payment_routh2",
	"metadata": {},
	"order": 0,
	"predicates": [{
		"args": {
			"_genkey_0": "/test2/**"
		},
		"name": "Path"
	}],
	"uri": "lb://test2"
}

]

修改该配置会自动更新路由信息。

相关推荐
云烟成雨TD4 小时前
Spring AI 1.x 系列【51】可观测性技术选型
java·人工智能·spring
unicrom_深圳市由你创科技4 小时前
基于Spring AI框架的RAG应用
人工智能·spring·机器学习
七老板的blog6 小时前
当 Spring StateMachine 遇见大模型:构建工业级 AI 写作流水线
java·人工智能·spring
云烟成雨TD6 小时前
Spring AI 1.x 系列【46】MCP Security 模块
java·人工智能·spring
小旭95277 小时前
Spring AI Alibaba 从入门到实战:一站式掌握企业级 AI 应用开发
java·人工智能·spring
云烟成雨TD9 小时前
Spring AI 1.x 系列【50】可观测性:接入 Prometheus + Grafana
人工智能·spring·prometheus
phltxy10 小时前
MCP 从协议到 Spring AI 实战
人工智能·spring·oracle
Volunteer Technology12 小时前
SpringSecurity请求流转的本质
java·spring
云烟成雨TD13 小时前
Spring AI 1.x 系列【42】MCP 服务端 Spring Boot 启动器
java·人工智能·spring
云烟成雨TD14 小时前
Spring AI 1.x 系列【38】模型上下文协议(MCP)
java·人工智能·spring