点击上方“猿芯”,选择“设为星标”
后台回复"1024",有份惊喜送给面试的你
本文将分四部分讲解:
SpringCloud Gateway 实现动态路由必要性
SpringCloud Gateway 动态路由源码解析
SpringCloud Gateway 动态路由配置实现方式
SpringCloud Gateway 动态路由配置注意的事项
SpringCloud Gateway 实现动态路由必要性
在实际的生产环境中,如果采用了微服务架构,每次功能迭代发版上线,经常会遇到需要在网关,添加路由配置,如 zuul。
zuul:
ignored-services: '*'
routes:
ddc:
path: /ddc/**
serviceId: portal-ddc
pcm:
path: /pcm/**
serviceId: portal-pcm
由于采用的是 yml 配置文件添加路由,所以每次都需要在修改配置文件后,再重启网关服务,会造成全网停服的情况,给用户带来了很大的不便。
所以我们需要实现在不重启网关服务的前提下,实现添加服务路由零配置升级。
SpringCloud Gateway 动态路由源码解析
查看 Spring Cloud Gateway 官网,不幸的是 Gateway 并没有提供类似于 Nacos 控制台配置管理页面给开发者来管理服务的路由信息。
Gateway
路由相关源码,其内部是提供了路由
CRUD
相关
API
接口的。
GatewayControllerEndpoint 端点
Gateway 通过 GatewayControllerEndpoint 暴露路由 Endpoint 端点进行 CRUD 操作
接下来利用 Postman (据说还有个 Postwomen)进行路由 CRUD 操作。
添加路由:
actuator/gateway/routes/{id}
删除路由:
actuator/gateway/routes/{id}
查询单条路由:
actuator/gateway/routes/{id}
查询所有路由:
actuator/gateway/routes
GatewayControllerEndpoint
端点中的方法,需要在
Gateway
添加
spring-boot-starter-actuator
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
并在 yml 配置文件中暴露所有端点。
management:
endpoints:
web:
exposure:
include: "*"
打开浏览器输入 actuator 地址:http://localhost:8080/actuator/,如果找到 Gateway 端点信息:http://localhost:8080/actuator/gateway,说明可以通过 GatewayControllerEndpoint 进行 CRUD 操作了。
SpringCloud Gateway 动态路由配置实现方式
除了使用 GatewayControllerEndpoint 可以配置路由之外,还可以利用 RouteLocatorBuilder 通过代码构建服务路由。
@SpringBootApplication
public class DemogatewayApplication {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.build();
}
}
另外,如果不嫌麻烦,可以利用 RouteDefinitionWriter 自定义实现类进行路由保存删除操作。
public interface RouteDefinitionWriter {
Mono<Void> save(Mono<RouteDefinition> route);
Mono<Void> delete(Mono<String> routeId);
}
默认情况下,Spring Cloud Gateway 使用内存方式(HashMap)存储路由信息。
其实现逻辑在 InMemoryRouteDefinitionRepository 类中,类图如下:
通过查看类图,我们知道 InMemoryRouteDefinitionRepository 是 RouteDefinitionWriter 的一个实现类。
这里给我们一个很大启发,是否可以利用 RouteDefinitionWriter 自定义实现类,把路由信息存储到 mysql、redis 或者 mongo 等数据库呢?
答案是可以的。
例如,我们利用 Redis 缓存路由信息,只需在 RouteDefinitionWriter 实现类 RedisRouteDefinitionRepository 中添加 redisTemplate 注解,进行路由信息的 CRUD 操作。
@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
public static final String GW_ROUTES = "apis_gateway_routes";
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
List<RouteDefinition> routeDefinitions = new ArrayList<>();
redisTemplate.opsForHash().values(GW_ROUTES).stream()
.forEach(routeDefinition -> routeDefinitions.add(JSON.parseObject(routeDefinition.toString(), RouteDefinition.class)));
return Flux.fromIterable(routeDefinitions);
}
@Override
public Mono<Void> save(Mono<RouteDefinition> route) {
RouteDefinition definition = new RouteDefinition();
definition.setId("id");
URI uri = UriComponentsBuilder.fromHttpUrl("lb://consumer-service").build().toUri();
definition.setUri(uri);
PredicateDefinition predicate = new PredicateDefinition();
predicate.setName("Path");
Map<String, String> predicateArgs = new HashMap<>();
predicateArgs.put("pattern", "/consumer/**");
predicate.setArgs(predicateArgs);
definition.setPredicates(Arrays.asList(predicate));
FilterDefinition filter = new FilterDefinition();
filter.setName("StripPrefix");
Map<String, String> filterArgs = new HashMap<>();
filterArgs.put("_genkey_0", "1");
filter.setArgs(filterArgs);
definition.setFilters(Arrays.asList(filter));
redisTemplate.opsForHash().put(GW_ROUTES, "routeKey", JSON.toJSONString(definition));
return null;
}
@Override
public Mono<Void> delete(Mono<String> routeId) {
return null;
}
}
提供 REST 对外接口,对路由进行 CRUD 操作,最后,每次完成 save 或者 delete 删除,然后发一个 RefreshRoutesEvent 事件,通知 Gateway 更新路由信息。
@RestController
@RequestMapping("/routes")
public class RouteController implements ApplicationEventPublisherAware {
@Autowired
private RedisRouteDefinitionRepository routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}
@PostMapping
public String addRoute(@RequestBody RouteDefinition definition) {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "0";
}
@GetMapping("/{id}")
public String delete(@PathVariable String id) {
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "0";
}
}
如果自定义 RouteDefinitionWriter 的实现类,就会替换 InMemoryRouteDefinitionRepository,从而当 rest 接口发送 RefreshRoutesEvent 刷新路由事件后, CachingRouteDefinitionLocator 刷新 Gateway 节点的路由缓存信息。
SpringCloud Gateway 动态路由配置注意的事项
在实际的生产环境中,Gateway网关一般是多实例部署,那么基于 InMemoryRouteDefinitionRepository 存储路由信息,并不合适。
因为每次通过 Gateway 的 rest 接口只会更新某个 Gateway 节点路由信息,并不能同步到其他节点。
这就解释为什么要用 redis 或则其他数据库存储路由信息的原因了。
这样当 Gateway 节点灰度重启或者在 Gateway 内置定时 job 刷新时,就可以通过 RedisRouteDefinitionRepository 的 getRouteDefinitions 方法 从 redis 缓存获取路由信息呢。

往期推荐
作者简介:猿芯,一枚简单的北漂程序员。喜欢用简单的文字记录工作与生活中的点点滴滴,愿与你一起分享程序员灵魂深处真正的内心独白。我的微信号:WooolaDunzung,公众号【猿芯】输入 1024 ,有份面试惊喜送给你哦。
< END >
【猿芯】

微信扫描二维码,关注我的公众号。
原创不易,莫要干想,如果觉得有点用的话,动动你的发财之手,一键三连击:分享、点赞、在看,你们的鼓励是我写作更多优质文章的最强动力 ^_^
分享、点赞、在看,3连3连!
SpringCloudGateway动态路由配置与实现
1910

被折叠的 条评论
为什么被折叠?



