SpringCloud Gateway 在不重启网关服务的前提下,实现添加服务路由零配置升级 您所在的位置:网站首页 gateway网关springcloud SpringCloud Gateway 在不重启网关服务的前提下,实现添加服务路由零配置升级

SpringCloud Gateway 在不重启网关服务的前提下,实现添加服务路由零配置升级

2023-12-12 17:54| 来源: 网络整理| 查看: 265

点击上方“猿芯”,选择“设为星标”

后台回复"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 于是笔者翻阅 Gateway 路由相关源码,其内部是提供了路由 CRUD 相关 API 接口的。

GatewayControllerEndpoint 端点

Gateway 通过 GatewayControllerEndpoint 暴露路由 Endpoint 端点进行 CRUD 操作

f4a4396be1f23d316c6b2e4795863c62.png

接下来利用 Postman (据说还有个 Postwomen)进行路由 CRUD 操作。

添加路由:actuator/gateway/routes/{id}

3257af043243862c90bebfdb0d2661b4.png

删除路由:actuator/gateway/routes/{id}

9001abcc3c9ee83505ade62e13b15269.png

查询单条路由:actuator/gateway/routes/{id}

a048d00881fc9e28040ba71e5c497b07.png

查询所有路由:actuator/gateway/routes

c714419ee127e79a10abb2ae18bf403c.png 另外,如果想访问 GatewayControllerEndpoint 端点中的方法,需要在 Gateway 添加 spring-boot-starter-actuator 依赖  org.springframework.boot  spring-boot-starter-actuator

并在 yml 配置文件中暴露所有端点。

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

打开浏览器输入 actuator 地址:http://localhost:8080/actuator/,如果找到 Gateway 端点信息:http://localhost:8080/actuator/gateway,说明可以通过 GatewayControllerEndpoint 进行 CRUD 操作了。

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}"))      .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)存储路由信息。

410ca2055a9791386dbe30d3b414c24c.png

其实现逻辑在 InMemoryRouteDefinitionRepository 类中,类图如下:

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 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 缓存获取路由信息呢。

22c7781fa0fe7fba3dd3ca7f646abcae.png

往期推荐

肝九千字长文 | MyBatis-Plus 码之重器 lambda 表达式使用指南,开发效率瞬间提升80%

用 MHA 做 MySQL 读写分离,频繁爆发线上生产事故后,泪奔分享 Druid 连接池参数优化实战

微服务架构下,解决数据库跨库查询的一些思路

一文读懂阿里大中台、小前台战略

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

< END >

【猿芯】

155d613b50730596c08406841f997b35.png

 微信扫描二维码,关注我的公众号。

原创不易,莫要干想,如果觉得有点用的话,动动你的发财之手,一键三连击:分享、点赞、在看,你们的鼓励是我写作更多优质文章的最强动力 ^_^

分享、点赞、在看,3连3连!5f6552299512a13ccf3f7d01c7a667dd.gif



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有