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


    一、概述

    1、背景

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

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

    2、实现思路

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

    二、编码实现

    1、nacos配置刷新公共类

    /**
     * nacos配置加载器
     */
    public interface NacosPropertiesLoader {
    
    
        /**
         * 获取dataId
         */
        String getDataId();
    
        /**
         * 配置刷新的回调
         */
        void getConfigData(String configData);
    }
    
    
    
    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

    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缓存类

    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

    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、动态路由加载类

    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格式进行配置(其他格式需要手写配置的解析方法)

    [{
    	"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"
    }
    
    ]
    

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

  • 相关阅读:
    自定义实现基于注解的缓存使用
    Scala Important Tips For Newbie => Scala入门小纸条(1)
    Java线程发生IO阻塞时的线程状态
    【SCI征稿】2/3区智能类,仅2-3个月左右录用
    Limit分页遇到百万级数据该何去何从
    c++ chrono
    JVM相关面试题及常用命令参数
    基于 vite2 + Vue3 写一个在线帮助文档工具
    c++ 泛型编程之类模板
    Vue中的路由守卫
  • 原文地址:https://blog.csdn.net/A_art_xiang/article/details/141020807