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"
}

]

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

相关推荐
阁阁下43 分钟前
springcloud configClient获取configServer信息失败导致启动configClient注入失败报错解决
后端·spring·spring cloud
whisperrr.1 小时前
【spring01】Spring 管理 Bean-IOC,基于 XML 配置 bean
xml·java·spring
天上掉下来个程小白1 小时前
HttpClient-03.入门案例-发送POST方式请求
java·spring·httpclient·苍穹外卖
robin_suli2 小时前
Spring事务的传播机制
android·java·spring
暮乘白帝过重山3 小时前
Singleton和Prototype的作用域与饿汉式/懒汉式的初始化方式
spring·原型模式·prototype·饿汉式·singleton·懒汉式
ejinxian4 小时前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之4 小时前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码4 小时前
Spring Task 定时任务
java·前端·spring
爱的叹息5 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
松韬5 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存