springcloud-gateway动态路由与路由持久化
gateway实现动态路由且持久化存储项目启动时,额外加载持久化路由信息路由转发可配置,操作路由无需重启网关
文章目录
网关功能-路由转发
我们的微服务网关其中一个作用便是统一入口,根据请求进行路由转发,将我们的请求解析到最终的微服务之中去
gateway网关为我们提供了丰富的路由谓词操作,根们我们的路由谓词进行请求转发
详情请查看>>>>>>>>spring-cloud(十一)GateWay强大的路由谓词(断言)功能
路由谓词转发示例:
spring:
cloud:
gateway:
routes:
# 路由ID
- id: location
order: -1
uri: lb://app-location-center
predicates:
# 当请求以/location/** 开头时,最终将被转发到上方url中
- Path=/location/**
filters:
# 截断一位url请求前缀
- StripPrefix=1
当然我们也可以不定义路由谓词进行请求转发,使用默认的微服务名进行转发(此种操作需要网关注册到注册中心)
根据微服务名-服务发现进行转发配置
spring:
cloud:
gateway:
discovery:
locator:
# 开启服务发现路由转发,会根据请求url中的服务名,将请求转发到对应的服务
enabled: true
gateway网关路由端点API
gateway 依赖中默认包含了路由端点操作,我们只需要引入依赖即可查看
implementation 'org.springframework.boot:spring-boot-starter-actuator'
服务Yml配置
management:
endpoint:
gateway:
# gateway 端点开启
enabled: true
endpoints:
web:
exposure:
include: "gateway"
我们可以在yml中按住ctrl 点后点击enable查看gateway端点底层代码
端点源代码
我们发现,gateway端点中为我们提供了路由信息的CRUD操作
url如下:
# 获取网关已加载所有路由信息
GET http://localhost:8901/actuator/gateway/routes
# 根据路由ID获取路由信息
GET http://localhost:8901/actuator/gateway/routes/{id}
# 刷新路由信息
POST http://localhost:8901/actuator/gateway/refresh
# 新增路由信息
POST http://localhost:8901/actuator/gateway/routes/{id} 与路由json体
.....更详细的请自己查看源码
gateway网关路由操作源码跟踪
新增路由
上方已经展示了路由端点API,接下来,咱们要实现路由持久化可以先看下原本默认API代码,熟悉其中的原理,然后进行改写
我们还是先来到AbstractGatewayControllerEndpoint
这个类中,查看路由新增做了什么事情
我们接下来看,进行理由保存的routeDefinitionWriter
从源码上看,其是注入的RouteDefinitionWriter
这个接口
protected RouteDefinitionWriter routeDefinitionWriter;
RouteDefinitionWriter
接口中提供了路由的新增与删除抽象方法
然后查看其实现类或者子接口
RouteDefinitionRepository
接口中又做了什么事情呢?查看源码,其无任何自身的抽象接口,仅仅继承了路由加载器接口RouteDefinitionLocator
(只有路由查询) 以及路由操作写入接口RouteDefinitionWriter
(只有路由新增与删除)
哦,懂了,其实RouteDefinitionRepository
就是将两个接口的抽象方法聚在了一起罢了
public interface RouteDefinitionRepository
extends RouteDefinitionLocator, RouteDefinitionWriter {
}
然后跟踪到最底层的实现类InMemoryRouteDefinitionRepository
,这就是路由操作的最终实现
我们发现,其对路由的增删查操作均是基于jvm内存操作的,并无任何持久化操作,这也是为何使用端点操作路由服务重启后,功能失效的原因
路由刷新
使用ApplicationEventPublisher
事件发布后,最终会被路由监听到,然后从路由加载器拉取最新路由信息
gateway启动时路由从何加载
会从接口RouteDefinitionLocator
其每个子类实现的getRouteDefinitions
方法获取路由信息,然后加载到内存之中
比如我们的服务发现路由加载器
配置文件路由加载器
然后后续拉取则从内存缓存中拉取路由信息
gateway动态路由实现
动态路由实现并持久化
持久化核心逻辑
写入持久层,刷新内存缓存,进行事件发布
package com.leilei.gateway.service;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.leilei.gateway.entity.Result;
import com.leilei.gateway.entity.form.RouteForm;
import com.leilei.gateway.entity.po.RouteInfo;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author lei
* @create 2022-04-11 15:35
* @desc
**/
@Service
@Log4j2
public class DynamicRouteService implements ApplicationEventPublisherAware {
private final RouteDefinitionWriter routeDefinitionWriter;
private final RouteService routeService;
private ApplicationEventPublisher publisher;
public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter, RouteService routeService) {
this.routeDefinitionWriter = routeDefinitionWriter;
this.routeService = routeService;
}
/**
* 增加路由
*
* @param routeForm
* @return
*/
public Result<Boolean> add(RouteForm routeForm) {
RouteDefinition definition = convert(routeForm);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
enduranceRule(routeForm.getName(), routeForm.getDescription(), definition);
publishRouteEvent();
System.out.println(JSON.toJSONString(definition));
return Result.success(true);
}
/**
* 更新路由
*
* @param routeForm
* @return
*/
public Result<Boolean> update(RouteForm routeForm) {
RouteDefinition definition = convert(routeForm);
try {
this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
} catch (Exception e) {
return Result.fail("未知路由信息");
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
enduranceRule(routeForm.getName(), routeForm.getDescription(), definition);
publishRouteEvent();
return Result.success(true);
} catch (Exception e) {
return Result.fail("路由信息修改失败!");
}
}
/**
* 删除路由
*
* @param id
* @return
*/
public Result<Boolean> delete(String id) {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
routeService.remove(buildWrapper(id));
publishRouteEvent();
return Result.success();
}
private void publishRouteEvent() {
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
public Result<Boolean> flushRoute() {
publishRouteEvent();
return Result.success(true);
}
public LambdaQueryWrapper<RouteInfo> buildWrapper(String routeId) {
return new QueryWrapper<RouteInfo>().lambda()
.eq(RouteInfo::getRouteId, routeId)
.last("limit 1");
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
/**
* 把自定义请求模型转换为
*
* @param form
* @return
*/
private RouteDefinition convert(RouteForm form) {
RouteDefinition definition = new RouteDefinition();
definition.setId(form.getId());
definition.setOrder(form.getOrder());
//设置断言
List<PredicateDefinition> predicateDefinitions = form.getPredicates().stream()
.distinct().map(predicateInfo -> {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(predicateInfo.getArgs());
predicate.setName(predicateInfo.getName());
return predicate;
}).collect(Collectors.toList());
definition.setPredicates(predicateDefinitions);
// 设置过滤
List<FilterDefinition> filterList = form.getFilters().stream().distinct().map(x -> {
FilterDefinition filter = new FilterDefinition();
filter.setName(x.getName());
filter.setArgs(x.getArgs());
return filter;
}).collect(Collectors.toList());
definition.setFilters(filterList);
// 设置URI,判断是否进行负载均衡
URI uri;
if (form.getUri().startsWith("http")) {
uri = UriComponentsBuilder.fromHttpUrl(form.getUri()).build().toUri();
} else {
uri = URI.create(form.getUri());
}
definition.setUri(uri);
return definition;
}
/**
* 数据落库
*/
public void enduranceRule(String name, String description, RouteDefinition definition) {
String id = definition.getId();
List<PredicateDefinition> predicates = definition.getPredicates();
List<FilterDefinition> filters = definition.getFilters();
int order = definition.getOrder();
URI uri = definition.getUri();
RouteInfo routeInfo = new RouteInfo();
routeInfo.setName(name);
routeInfo.setRouteId(id);
routeInfo.setUri(uri.toString());
routeInfo.setPredicates(JSON.toJSONString(predicates));
routeInfo.setFilters(JSON.toJSONString(filters));
routeInfo.setEnabled(true);
routeInfo.setDescription(description);
routeInfo.setOrderNum(order);
routeInfo.setDeleteFlag(false);
RouteInfo one = routeService.getOne(buildWrapper(id));
if (one == null) {
routeService.save(routeInfo);
} else {
routeInfo.setId(one.getId());
routeService.updateById(routeInfo);
}
}
}
自定义路由操作(可以在此类进行持久化处理也可以像我上方一样,持久层交由其他服务类去做)
package com.leilei.gateway.service;
import javassist.NotFoundException;
import lombok.extern.log4j.Log4j2;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author lei
* @create 2022-04-11 16:20
* @desc 自定义内存路由管理仓,开启日志打印
**/
@Service
@Log4j2
public class MyRouteDefinitionRepository implements RouteDefinitionRepository {
public final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap<>());
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
Collection<RouteDefinition> values = routes.values();
return Flux.fromIterable(values);
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap( r -> {
log.info("新增路由信息:{}",r);
routes.put(r.getId(), r);
return Mono.empty();
});
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
log.info("删除路由信息,路由ID为:{}",id);
if (routes.containsKey(id)) {
routes.remove(id);
return Mono.empty();
}
return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: "+routeId)));
});
}
}
从持久层额外加载动态路由
核心原理
启动项目,在从路由加载器RouteDefinitionLocator
各个子类拉取路由外,额外进入我们的路由持久层加载路由
改写上方的动态路由服务类,进行项目启动加载
package com.leilei.gateway.service;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.leilei.gateway.entity.Result;
import com.leilei.gateway.entity.form.RouteForm;
import com.leilei.gateway.entity.po.RouteInfo;
import lombok.extern.log4j.Log4j2;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author lei
* @create 2022-04-11 15:35
* @desc
**/
@Service
@Log4j2
public class DynamicRouteService implements ApplicationEventPublisherAware, ApplicationRunner {
private final RouteDefinitionWriter routeDefinitionWriter;
private final RouteService routeService;
private ApplicationEventPublisher publisher;
public DynamicRouteService(RouteDefinitionWriter routeDefinitionWriter, RouteService routeService) {
this.routeDefinitionWriter = routeDefinitionWriter;
this.routeService = routeService;
}
/**
* 增加路由
*
* @param routeForm
* @return
*/
public Result<Boolean> add(RouteForm routeForm) {
RouteDefinition definition = convert(routeForm);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
enduranceRule(routeForm.getName(), routeForm.getDescription(), definition);
publishRouteEvent();
System.out.println(JSON.toJSONString(definition));
return Result.success(true);
}
/**
* 更新路由
*
* @param routeForm
* @return
*/
public Result<Boolean> update(RouteForm routeForm) {
RouteDefinition definition = convert(routeForm);
try {
this.routeDefinitionWriter.delete(Mono.just(definition.getId())).subscribe();
} catch (Exception e) {
return Result.fail("未知路由信息");
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
enduranceRule(routeForm.getName(), routeForm.getDescription(), definition);
publishRouteEvent();
return Result.success(true);
} catch (Exception e) {
return Result.fail("路由信息修改失败!");
}
}
/**
* 删除路由
*
* @param id
* @return
*/
public Result<Boolean> delete(String id) {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
routeService.remove(buildWrapper(id));
publishRouteEvent();
return Result.success();
}
private void publishRouteEvent() {
this.publisher.publishEvent(new RefreshRoutesEvent(this));
}
public Result<Boolean> flushRoute() {
publishRouteEvent();
return Result.success(true);
}
public LambdaQueryWrapper<RouteInfo> buildWrapper(String routeId) {
return new QueryWrapper<RouteInfo>().lambda()
.eq(RouteInfo::getRouteId, routeId)
.last("limit 1");
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@Override
public void run(ApplicationArguments args) {
log.info("----------从持久层加载额外路由信息---------");
this.loadRouteConfig();
}
private void loadRouteConfig() {
List<RouteDefinition> routeList = routeService.getRouteList();
log.info("----------持久层额外路由数量:{}---------",routeList.size());
routeList.forEach(x -> routeDefinitionWriter.save(Mono.just(x)).subscribe());
publishRouteEvent();
}
/**
* 把自定义请求模型转换为
*
* @param form
* @return
*/
private RouteDefinition convert(RouteForm form) {
RouteDefinition definition = new RouteDefinition();
definition.setId(form.getId());
definition.setOrder(form.getOrder());
//设置断言
List<PredicateDefinition> predicateDefinitions = form.getPredicates().stream()
.distinct().map(predicateInfo -> {
PredicateDefinition predicate = new PredicateDefinition();
predicate.setArgs(predicateInfo.getArgs());
predicate.setName(predicateInfo.getName());
return predicate;
}).collect(Collectors.toList());
definition.setPredicates(predicateDefinitions);
// 设置过滤
List<FilterDefinition> filterList = form.getFilters().stream().distinct().map(x -> {
FilterDefinition filter = new FilterDefinition();
filter.setName(x.getName());
filter.setArgs(x.getArgs());
return filter;
}).collect(Collectors.toList());
definition.setFilters(filterList);
// 设置URI,判断是否进行负载均衡
URI uri;
if (form.getUri().startsWith("http")) {
uri = UriComponentsBuilder.fromHttpUrl(form.getUri()).build().toUri();
} else {
uri = URI.create(form.getUri());
}
definition.setUri(uri);
return definition;
}
/**
* 数据落库
*/
public void enduranceRule(String name, String description, RouteDefinition definition) {
String id = definition.getId();
List<PredicateDefinition> predicates = definition.getPredicates();
List<FilterDefinition> filters = definition.getFilters();
int order = definition.getOrder();
URI uri = definition.getUri();
RouteInfo routeInfo = new RouteInfo();
routeInfo.setName(name);
routeInfo.setRouteId(id);
routeInfo.setUri(uri.toString());
routeInfo.setPredicates(JSON.toJSONString(predicates));
routeInfo.setFilters(JSON.toJSONString(filters));
routeInfo.setEnabled(true);
routeInfo.setDescription(description);
routeInfo.setOrderNum(order);
routeInfo.setDeleteFlag(false);
RouteInfo one = routeService.getOne(buildWrapper(id));
if (one == null) {
routeService.save(routeInfo);
} else {
routeInfo.setId(one.getId());
routeService.updateById(routeInfo);
}
}
}
动态路由演示
网关启动,一开始并无额外动态路由信息
查看所有网关加载路由
http://localhost:8901/actuator/gateway/routes
[
{
"predicate": "Paths: [/location/**], match trailing slash: true",
"route_id": "location",
"filters": [
"[[StripPrefix parts = 1], order = 1]"
],
"uri": "lb://app-location-center",
"order": -1
},
{
"predicate": "Paths: [/app-location-center/**], match trailing slash: true",
"metadata": {
"nacos.instanceId": null,
"nacos.weight": "1.0",
"nacos.cluster": "DEFAULT",
"nacos.ephemeral": "true",
"nacos.healthy": "true",
"preserved.register.source": "SPRING_CLOUD"
},
"route_id": "ReactiveCompositeDiscoveryClient_app-location-center",
"filters": [
"[[RewritePath /app-location-center/(?<remaining>.*) = '/${remaining}'], order = 1]"
],
"uri": "lb://app-location-center",
"order": 0
},
{
"predicate": "Paths: [/dodo-server-gateway/**], match trailing slash: true",
"metadata": {
"nacos.instanceId": null,
"nacos.weight": "1.0",
"nacos.cluster": "DEFAULT",
"nacos.ephemeral": "true",
"nacos.healthy": "true",
"preserved.register.source": "SPRING_CLOUD"
},
"route_id": "ReactiveCompositeDiscoveryClient_dodo-server-gateway",
"filters": [
"[[RewritePath /dodo-server-gateway/(?<remaining>.*) = '/${remaining}'], order = 1]"
],
"uri": "lb://dodo-server-gateway",
"order": 0
},
{
"predicate": "Paths: [/app-base-center/**], match trailing slash: true",
"metadata": {
"preserved.heart.beat.timeout": "10000",
"nacos.instanceId": null,
"nacos.weight": "1.0",
"nacos.cluster": "DEFAULT",
"nacos.ephemeral": "true",
"nacos.healthy": "true",
"preserved.ip.delete.timeout": "15000",
"preserved.register.source": "SPRING_CLOUD",
"preserved.heart.beat.interval": "5000"
},
"route_id": "ReactiveCompositeDiscoveryClient_app-base-center",
"filters": [
"[[RewritePath /app-base-center/(?<remaining>.*) = '/${remaining}'], order = 1]"
],
"uri": "lb://app-base-center",
"order": 0
}
]
访问预添加路由信息
添加路由并持久化
{
"name":"bilibili路由",
"description":"测试自定义路由信息",
"filters": [{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}],
"id": "test-route",
"uri": "https://www.bilibili.com/anime/?spm_id_from=333.1007.0.0",
"order": 2,
"predicates": [{
"name": "Path",
"args": {
"_genkey_0": "/b3"
}
}]
}
数据也已经落到了DB
打印了路由新增信息(加入到内存)
查看网关已加载路由信息
…
再次访问
我们再次访问http://localhost:8901/b3
网关已经帮我们路由到了我们定义的url了(定义的bilibili)
修改路由后再次访问
发现网关路由信息成功修改,为我们转发到了csdn(虽然css样式丢失了)
项目重启,测试路由从持久层加载
项目源码
更多推荐
所有评论(0)