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

]

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

相关推荐
Tatakai253 小时前
Mybatis Plus分页查询返回total为0问题
java·spring·bug·mybatis
珍珠是蚌的眼泪4 小时前
微服务_入门2
网关·微服务·gateway·远程调用·feign
掐指一算乀缺钱5 小时前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
一叶飘零_sweeeet5 小时前
为什么 Feign 要用 HTTP 而不是 RPC?
java·网络协议·http·spring cloud·rpc·feign
bug菌¹5 小时前
滚雪球学SpringCloud[4.1讲]: Spring Cloud Gateway详解
java·spring cloud·微服务
铁板鱿鱼1405 小时前
统一网关--gateway(仅供自己参考)
gateway
bug菌¹5 小时前
滚雪球学SpringCloud[4.2讲]: Zuul:Netflix API Gateway详解
spring·spring cloud·gateway
小哇6666 小时前
spring-TransactionTemplate 编程式事务
数据库·spring
小筱在线7 小时前
SpringCloud微服务实现服务熔断的实践指南
java·spring cloud·微服务
ChinaRainbowSea7 小时前
十三,Spring Boot 中注入 Servlet,Filter,Listener
java·spring boot·spring·servlet·web