spring cloud 2.x版本 Gateway动态路由教程

2022-11-26,,,,

摘要

本文采用的Spring cloud为2.1.8RELEASE,version=Greenwich.SR3

本文基于前面的几篇Spring cloud Gateway文章的实现。

参考

Gateway路由网关教程
Gateway自定义过滤器教程

前言

写了几篇关于Spring Cloud Gateway的文章后发现,Gateway涉及的知识范围太广了,真是深刻体会了“一入Spring cloud深似海”。

现实生产环境中,使用Spring Cloud Gateway都是作为所有流量的入口,为了保证系统的高可用,尽量避免系统的重启,所以需要Spring Cloud Gateway的动态路由来处理。之前的文章《Gateway路由网关教程》提供的路由配置,在系统启动时候,会将路由配置和规则加载到内存当中,无法做到不重启服务就可以动态的新增、修改、删除内存中的路由配置和规则。

简单的动态路由实现

Spring Cloud Gateway源码中提供了GatewayControllerEndpoint类来修改路由配置,但是官方文档好像并没有做详细的使用说明,只是简单介绍了几个简单的api接口。感兴趣的小伙伴可以先查看官方文档(第11章节 Actuator API)。

引致官方文档:

The /gateway actuator endpoint allows to monitor and interact with a Spring Cloud Gateway application. To be remotely accessible, the endpoint has to be enabled and exposed via HTTP or JMX in the application properties.

1.1 添加相关pom依赖

在原来spring-gateway的基础上增加spring-boot-starter-actuator依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

1.2 在application.yml中增加配置

management:
endpoints:
web:
exposure:
include: "*"

配置说明:management.endpoints.web.exposure.include 暴露所有的gateway端点

1.3 启动服务

启动服务,访问http://localhost:8100/actuator/gateway/routes,这是我们可以看到所有的routes信息。

我们也可以访问单个路由信息:http://localhost:8100/actuator/gateway/routes/CompositeDiscoveryClient_EUREKA-CLIENT

显示如下:

1.4 增加、修改路由信息

Gateway默认使用的是GatewayControllerEndpoint这个类,GatewayControllerEndpoint又继承了AbstractGatewayControllerEndpoint类。

提供的方法:(只列具了几个相关方法,其他方法小伙们可以自行查看源码)

    /gateway/routes 查询所有路由信息
    /gateway/routes/{id} 根据路由id查询单个信息
    /gateway/routes/{id} @PostMapping 新增一个路由信息
    /gateway/routes/{id} @DeleteMapping 删除一个路由信息

1.4.1 新增路由

我们可根据/gateway/routes返回的路由信息,来模仿一个@PostMapping请求参数

{
"uri": "http://httpbin.org:80",
"predicates": [
{
"args": {
"pattern": "/ribbon/**"
},
"name": "Path"
}
],
"filters": []
}

这是我们可以通过postman来发送一个post请求,如图所示:

response返回1证明已经插入成功,我们可以通过http://localhost:8100/actuator/gateway/routes/查看路由信息,显示结果如下:

截图红框中的信息就是新增加的

1.4.2 删除路由信息

我们可以直接用postman模拟DeleteMapping请求,http://localhost:8100/actuator/gateway/routes/addroutes

显示如下:

这时候我们在访问http://localhost:8100/actuator/gateway/routes,可以看到新增加的路由已经被删除成功了。

1.5 小结

基于Spring Cloud Gateway默认方法实现的动态路由我就完成了,在前言中我已经提到了,这种方式是基于jvm内存实现,一旦服务重启,新增的路由配置信息就是完全消失了。所有这个时候我们可以考虑是否可以参考GatewayControllerEndpoint这类,自己实现一套动态路由方法,然后将路由信息持久化。

自定义动态路由

1.1定义相关类

1.1.1 定义实体类

可以自定义实体类,我这里偷了一个懒,直接用了Gateway的RouteDefinition类,感兴趣的小伙伴可以参考RouteDefinition类自己扩展,然后写一个Convert类将自定义的类转换成RouteDefinition就可以了。

1.1.2 自定义RedisRouteDefinitionRepository类

我这边采用redis做为路由配置的信息的持久层,所以写了一个RedisRouteDefinitionRepository。

package spring.cloud.demo.spring.gateway.component;

import com.google.common.collect.Lists;
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 reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import spring.cloud.demo.spring.gateway.redis.RedisUtils;
import spring.cloud.demo.spring.gateway.util.JsonUtils; import javax.annotation.Resource;
import java.util.List; /**
* @auther: maomao
* @DateT: 2019-11-03
*/
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository { //存储的的key
private final static String KEY = "gateway_dynamic_route"; @Resource
private RedisUtils redisUtils; /**
* 获取路由信息
* @return
*/
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<RouteDefinition> gatewayRouteEntityList = Lists.newArrayList();
redisUtils.hgets(KEY).stream().forEach(route -> {
RouteDefinition result = JsonUtils.parseJson(route.toString(), RouteDefinition.class);
gatewayRouteEntityList.add(result);
});
return Flux.fromIterable(gatewayRouteEntityList);
} /**
* 新增
* @param route
* @return
*/
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
return route.flatMap(routeDefinition -> {
redisUtils.hset(KEY, routeDefinition.getId(), JsonUtils.toString(routeDefinition));
return Mono.empty();
});
} /**
* 删除
* @param routeId
* @return
*/
@Override
public Mono<Void> delete(Mono<String> routeId) {
return routeId.flatMap(id -> {
if (redisUtils.hHashKey(KEY, id)) {
redisUtils.hdel(KEY, id);
return Mono.empty();
}
return Mono.defer(() -> Mono.error(new NotFoundException("route definition is not found, routeId:" + routeId)));
});
}
}

1.1.3 自定义Controller和Service

package spring.cloud.demo.spring.gateway.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import spring.cloud.demo.spring.gateway.service.GatewayDynamicRouteService; import javax.annotation.Resource; /**
* 自定义动态路由
* @auther: maomao
* @DateT: 2019-11-03
*/
@RestController
@RequestMapping("/gateway")
@Slf4j
public class GatewayDynamicRouteController { @Resource
private GatewayDynamicRouteService gatewayDynamicRouteService; @PostMapping("/add")
public String create(@RequestBody RouteDefinition entity) {
int result = gatewayDynamicRouteService.add(entity);
return String.valueOf(result);
} @PostMapping("/update")
public String update(@RequestBody RouteDefinition entity) {
int result = gatewayDynamicRouteService.update(entity);
return String.valueOf(result);
} @DeleteMapping("/delete/{id}")
public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
return gatewayDynamicRouteService.delete(id);
} }
package spring.cloud.demo.spring.gateway.service;

import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import spring.cloud.demo.spring.gateway.component.RedisRouteDefinitionRepository; import javax.annotation.Resource; /**
* @auther: maomao
* @DateT: 2019-11-03
*/
@Service
public class GatewayDynamicRouteService implements ApplicationEventPublisherAware { @Resource
private RedisRouteDefinitionRepository redisRouteDefinitionRepository; private ApplicationEventPublisher applicationEventPublisher; /**
* 增加路由
* @param routeDefinition
* @return
*/
public int add(RouteDefinition routeDefinition) {
redisRouteDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
return 1;
} /**
* 更新
* @param routeDefinition
* @return
*/
public int update(RouteDefinition routeDefinition) {
redisRouteDefinitionRepository.delete(Mono.just(routeDefinition.getId()));
redisRouteDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
return 1;
} /**
* 删除
* @param id
* @return
*/
public Mono<ResponseEntity<Object>> delete(String id) {
return redisRouteDefinitionRepository.delete(Mono.just(id)).then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
.onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build()));
} @Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
}

1.2 application.yml

application.yml配置要暴漏Gateway的所有端点,可以看考之前的配置信息。

1.3 启动服务

启动Spring cloud Gateway服务,先访问http://localhost:8100/actuator/gateway/routes,查看已有的路由配置信息。然后我们用postman请求add方法,http://localhost:8100/gateway/add,如果所示:

注意截图中红框的内容。证明已经新增成功。

这时我们在访问http://localhost:8100/actuator/gateway/routes查看结果。如果所示:

同理我们可以访问update和delete方法,我这里就不过多描述了。

总结

自定义动态路由核心原理其实就要重写网关模块,也就是之前提到的RedisRouteDefinitionRepository类。我这里偷懒没有重新定义对应的实体类,这里需要注意的是,传入参数一定要按照application.yml中配置的格式,然后转成json,如果格式不正确会报错。

代码地址

gitHub地址


《Srping Cloud 2.X小白教程》目录

spring cloud 2.x版本 Eureka Server服务注册中心教程
spring cloud 2.x版本 Eureka Client服务提供者教程
spring cloud 2.x版本 Ribbon服务发现教程(内含集成Hystrix熔断机制)
spring cloud 2.x版本 Feign服务发现教程(内含集成Hystrix熔断机制)
spring cloud 2.x版本 Zuul路由网关教程
spring cloud 2.x版本 config分布式配置中心教程
spring cloud 2.x版本 Hystrix Dashboard断路器教程
spring cloud 2.x版本 Gateway路由网关教程
spring cloud 2.x版本 Gateway自定义过滤器教程
spring cloud 2.x版本 Gateway熔断、限流教程
spring cloud 2.x版本 Gateway动态路由教程


写作不易,转载请注明出处,喜欢的小伙伴可以关注公众号查看更多喜欢的文章。
联系方式:4272231@163.com

spring cloud 2.x版本 Gateway动态路由教程的相关教程结束。

《spring cloud 2.x版本 Gateway动态路由教程.doc》

下载本文的Word格式文档,以方便收藏与打印。