SpringCloud Gateway 在不重启网关服务的前提下,实现添加服务路由零配置升级 | 您所在的位置:网站首页 › gateway网关springcloud › SpringCloud Gateway 在不重启网关服务的前提下,实现添加服务路由零配置升级 |
点击上方“猿芯”,选择“设为星标” 后台回复"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 控制台配置管理页面给开发者来管理服务的路由信息。 ![]() GatewayControllerEndpoint 端点 Gateway 通过 GatewayControllerEndpoint 暴露路由 Endpoint 端点进行 CRUD 操作 ![]() 接下来利用 Postman (据说还有个 Postwomen)进行路由 CRUD 操作。 添加路由:actuator/gateway/routes/{id} ![]() 删除路由:actuator/gateway/routes/{id} ![]() 查询单条路由:actuator/gateway/routes/{id} ![]() 查询所有路由:actuator/gateway/routes ![]() 并在 yml 配置文件中暴露所有端点。 management: endpoints: web: exposure: include: "*"打开浏览器输入 actuator 地址:http://localhost:8080/actuator/,如果找到 Gateway 端点信息:http://localhost:8080/actuator/gateway,说明可以通过 GatewayControllerEndpoint 进行 CRUD 操作了。 ![]() 除了使用 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}")) .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 save(Mono route); Mono delete(Mono 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 getRouteDefinitions() { List 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 save(Mono 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 predicateArgs = new HashMap(); predicateArgs.put("pattern", "/consumer/**"); predicate.setArgs(predicateArgs); definition.setPredicates(Arrays.asList(predicate)); FilterDefinition filter = new FilterDefinition(); filter.setName("StripPrefix"); Map 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 delete(Mono 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 缓存获取路由信息呢。 往期推荐 肝九千字长文 | MyBatis-Plus 码之重器 lambda 表达式使用指南,开发效率瞬间提升80% 用 MHA 做 MySQL 读写分离,频繁爆发线上生产事故后,泪奔分享 Druid 连接池参数优化实战 微服务架构下,解决数据库跨库查询的一些思路 一文读懂阿里大中台、小前台战略 作者简介:猿芯,一枚简单的北漂程序员。喜欢用简单的文字记录工作与生活中的点点滴滴,愿与你一起分享程序员灵魂深处真正的内心独白。我的微信号:WooolaDunzung,公众号【猿芯】输入 1024 ,有份面试惊喜送给你哦。 < END > 【猿芯】 微信扫描二维码,关注我的公众号。 原创不易,莫要干想,如果觉得有点用的话,动动你的发财之手,一键三连击:分享、点赞、在看,你们的鼓励是我写作更多优质文章的最强动力 ^_^ 分享、点赞、在看,3连3连! |
今日新闻 |
推荐新闻 |
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 |