文章目录
一、概述
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"
}
]
修改该配置会自动更新路由信息。