点击上方“猿芯”,选择“设为星标”
后台回复"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
控制台配置管理页面给开发者来管理服务的路由信息。
![68370705b70642764055962f0fc17cbe.png](https://img-blog.csdnimg.cn/img_convert/68370705b70642764055962f0fc17cbe.png)
Gateway
路由相关源码,其内部是提供了路由
CRUD
相关
API
接口的。
GatewayControllerEndpoint 端点
Gateway
通过 GatewayControllerEndpoint
暴露路由 Endpoint
端点进行 CRUD
操作
![f4a4396be1f23d316c6b2e4795863c62.png](https://img-blog.csdnimg.cn/img_convert/f4a4396be1f23d316c6b2e4795863c62.png)
接下来利用 Postman
(据说还有个 Postwomen
)进行路由 CRUD
操作。
添加路由:
actuator/gateway/routes/{id}
![3257af043243862c90bebfdb0d2661b4.png](https://img-blog.csdnimg.cn/img_convert/3257af043243862c90bebfdb0d2661b4.png)
删除路由:
actuator/gateway/routes/{id}
![9001abcc3c9ee83505ade62e13b15269.png](https://img-blog.csdnimg.cn/img_convert/9001abcc3c9ee83505ade62e13b15269.png)
查询单条路由:
actuator/gateway/routes/{id}
![a048d00881fc9e28040ba71e5c497b07.png](https://img-blog.csdnimg.cn/img_convert/a048d00881fc9e28040ba71e5c497b07.png)
查询所有路由:
actuator/gateway/routes
![c714419ee127e79a10abb2ae18bf403c.png](https://img-blog.csdnimg.cn/img_convert/c714419ee127e79a10abb2ae18bf403c.png)
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
操作了。
![2f9ac567c2ffa089b7c774fb31b515fa.png](https://img-blog.csdnimg.cn/img_convert/2f9ac567c2ffa089b7c774fb31b515fa.png)
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
)存储路由信息。
![410ca2055a9791386dbe30d3b414c24c.png](https://img-blog.csdnimg.cn/img_convert/410ca2055a9791386dbe30d3b414c24c.png)
其实现逻辑在 InMemoryRouteDefinitionRepository
类中,类图如下:
![d172265c6976c9fa61911ee68be58f30.png](https://img-blog.csdnimg.cn/img_convert/d172265c6976c9fa61911ee68be58f30.png)
通过查看类图,我们知道 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连!