写点什么

👊【SpringCloud 技术专题】超级详细的 Gateway 网关的技术指南

发布于: 1 小时前
👊【SpringCloud技术专题】超级详细的Gateway网关的技术指南

SpringCloud Gateway 简介

Spring Cloud Gateway 是由 spring 官方基于 Spring5.0、Spring Boot2.x、Project Reactor 等技术开发的 网关,目的是代替原先版本中的 Spring Cloud Netfilx Zuul。

这是我在 2019 年做的一个 SpringCloud 的课件,主要目的是培训团队成员。



Gateway 网关特性

  • 统一入口所有请求通过网关路由到内部其他服务。

  • 断言(Predicates)和过滤器(filters)特定路由。断言是根据具体的请求的规则由 route 去处理;过滤器用来对请求做各种判断和修改。

  • 可集成熔断器:Hystrix 熔断机制。Hystrix 是 spring cloud gateway 中是以 filter 的形式使用的。

  • 流量限速:请求限流防止大规模请求对业务数据造成破坏。

  • 路径重写(rewrite):路径重写自定义路由转发规则。

  • 能够自由设置任何请求属性的路由

  • Spring Cloud DiscoveryClient 原生支持

Gateway 流程架构

静态路由

所谓静态路由,就是指 API 网关启动前,通过配置文件或者代码的方式,静态的配置好 API 之间的路由关系,此后不需要二次维护,大多数的内部 API 网关适用于这种方式。

方法一 配置文件方式

本质上是修改application.yml文件,相关修改方法,官网已经有详尽的描述了,如需帮助可参考官方文档。本文仅举例其中一种,一看便知。

spring:  cloud:    gateway:      routes:      - id: ingredients        uri: lb://ingredients        predicates:        - Path=//ingredients/**        filters:        - name: CircuitBreaker          args:            name: fetchIngredients            fallbackUri: forward:/fallback      - id: ingredients-fallback        uri: http://localhost:9994        predicates:        - Path=/fallback        filters:        - name: FallbackHeaders          args:            executionExceptionTypeHeaderName: Test-Header     - id: ijep-service-sys  #基础服务       uri: lb://ijep-service-sys       predicates:       - Path=/api/sys/**       filters:       - StripPrefix=2
复制代码

方法二 代码构建路由

// static imports from GatewayFilters and RoutePredicates@Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGatewayFilterFactory throttle) {    return builder.routes()            .route(r -> r.host("**.abc.org").and().path("/image/png")                .filters(f ->                        f.addResponseHeader("X-TestHeader", "foobar"))                .uri("http://httpbin.org:80")            )            .route(r -> r.path("/image/webp")                .filters(f ->                        f.addResponseHeader("X-AnotherHeader", "baz"))                .uri("http://httpbin.org:80")                .metadata("key", "value")            )            .route(r -> r.order(-1)                .host("**.throttle.org").and().path("/get")                .filters(f -> f.filter(throttle.apply(1,                        1,                        10,                        TimeUnit.SECONDS)))                .uri("http://httpbin.org:80")                .metadata("key", "value")            )            .build();}
复制代码

通过代码即可完成路由的创建,会在 Spring Cloud Gateway 启动时自动加载到运行态,本质上配置文件和代码方式,仅仅是两种加载形态,底层没有太大的区别。然而,静态路由往往满足不了我们的需求。

断言(Predicates)





官方参考地址https://cloud.spring.io/spring-cloud-gateway/reference/html/#_after_route_predicate_factory

过滤器(Filter)

路由过滤器允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。路径过滤器的范围限定为特定路径。Spring Cloud Gateway 包含许多内置的 GatewayFilter 工厂。

GlobalFilter 全局过滤器


CORS 跨域处理

例子:对于所有 GET 请求的路径,将允许来自 docs.spring.io 的请求的 CORS 请求。



Gateway API

支持通过接口动态调整网关策略。

/actuator/gateway/refresh

POST

刷新路由缓存

/actuator/gateway/routes

GET

查询路由

/actuator/gateway/globalfilters

GET

查询全局过滤器

/actuator/gateway/routefilters

GET

查询过滤器

/actuator/gateway/routes/{id}

GET、POST、DELETE

查询指定路由信息



无论是哪一种,在启动网关后将无法修改路由配置,如有新服务要上线,则需要先把网关下线,修改 yml 配置后,再重启网关。


动态路由

Gateway 网关启动时,路由信息默认会加载内存中,路由信息被封装到 RouteDefinition 对象中,配置多个 RouteDefinition 组成 Gateway 的路由系统。

package org.springframework.cloud.gateway.route; 
@Validated public class RouteDefinition {
@NotEmpty private String id = UUID.randomUUID().toString();
@NotEmpty @Valid private List<PredicateDefinition> predicates = new ArrayList<>();
@Valid private List<FilterDefinition> filters = new ArrayList<>();
@NotNull private URI uri;
private int order = 0;
public RouteDefinition() {}
.....此处省略......
}
复制代码

RouteDefinition 中的字段与上面代码配置方式比较对应

package org.springframework.cloud.gateway.route;
import reactor.core.publisher.Flux;
public interface RouteDefinitionLocator { Flux<RouteDefinition> getRouteDefinitions();}
复制代码


RouteDefinitionLocator 是个接口,在 org.springframework.cloud.gateway.route 包下,如果想查看网关中所有的路由信息,可以调用此方法;后面还有另外一种查看方式,是 Spring Cloud Gateway 的 Endpoint 端点提供的方法

处。

Gateway Endpoint 端点

Endpoint 端点有暴露路由信息、获取所有路由、刷新路由、查看单个路由、删除路由等方法,源码在 org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint 中,访问端点中的方法需要修改 pom 和配置文件:

添加 pom 文件依赖:

<dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-actuator</artifactId></dependency>
复制代码
yml 配置
# 暴露端点management: endpoints:   web:    exposure:    include: '*' endpoint:    health:    show-details: always
复制代码

动态路由实现

创建路由模型

过滤器模型

public class GatewayFilterDefinition {//Filter Nameprivate String name;//对应的路由规则private Map<String, String> args = new LinkedHashMap<>();   ......此处省略Get、Set方法......}
复制代码

路由断言模型

public class GatewayPredicateDefinition {  //断言对应的Name  private String name;
//配置的断言规则 private Map<String, String> args = new LinkedHashMap<>();
......此处省略Get、Set方法...... }
复制代码

路由模型

public class GatewayRouteDefinition {
//路由的Idprivate String id;
//路由断言集合配置private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
//路由过滤器集合配置private List<GatewayFilterDefinition> filters = new ArrayList<>();
//路由规则转发的目标uriprivate String uri;
//路由执行的顺序private int order = 0;
......此处省略Get、Set方法......}
复制代码
编写态路由实现类,实现 ApplicationEventPublisherAware 接口
@Servicepublic class DynamicRouteServiceImpl implements ApplicationEventPublisherAware{
@Autowired private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
@Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.publisher = applicationEventPublisher;}
/** * 增加路由 * @param definition * @return */ public String add(RouteDefinition definition) { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); this.publisher.publishEvent(new RefreshRoutesEvent(this)); return "success"; }
/** * 更新路由 * @param definition * @return */public String update(RouteDefinition definition) { try { delete(definition.getId()); } catch (Exception e) { return "update fail,not find route routeId: "+definition.getId(); } try { routeDefinitionWriter.save(Mono.just(definition)).subscribe(); this.publisher.publishEvent(new RefreshRoutesEvent(this)); return "success"; } catch (Exception e) { return "update route fail"; }}
/** * 删除路由 * @param id * @return */ public Mono<ResponseEntity<Object>> delete(String id) { return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> { return Mono.just(ResponseEntity.ok().build()); })).onErrorResume((t) -> { return t instanceof NotFoundException; }, (t) -> { return Mono.just(ResponseEntity.notFound().build()); }); }}
复制代码
编写 API 接口,实现动态路由功能
@RestController@RequestMapping("/api/route")public class RouteRestController {
@Autowired private DynamicRouteServiceImpl dynamicRouteService;
/** * 增加路由 * @param gatewayRouteDefinition 路由模型 * 报文:{"filters":[{"name":"StripPrefix","args":{"_genkey_0":"2"}}],"id":"ijep-service-sys-hyk11","uri":"lb://ijep-service-sys","order":2,"predicates":[{"name":"Path","args":{"_genkey_0":"/api/hyk11/**"}}]} * @return */@PostMapping("/add")public RestResponse add(@RequestBody GatewayRouteDefinition gatewayRouteDefinition) { String flag = "fail"; try { RouteDefinition definition = assembleRouteDefinition(gatewayRouteDefinition); flag = this.dynamicRouteService.add(definition); } catch (Exception e) { e.printStackTrace(); }
return RestResponseBuilder.restResponse().data(flag).build();}

/** * 更新路由 * @param gatewayRouteDefinition 路由模型 * 报文:{"filters":[{"name":"StripPrefix","args":{"_genkey_0":"2"}}],"id":"ijep-service-sys-hyk11","uri":"lb://ijep-service-sys","order":2,"predicates":[{"name":"Path","args":{"_genkey_0":"/api/hyk11/**"}}]} * @return */@PostMapping("/update")public RestResponse update(@RequestBody GatewayRouteDefinition gatewayRouteDefinition) {
RouteDefinition definition = assembleRouteDefinition(gatewayRouteDefinition); String flag = this.dynamicRouteService.update(definition);
return RestResponseBuilder.restResponse().data(flag).build();}
/** * 删除路由 * @param id 路由Id * @return */@DeleteMapping("/routes/{id}")public RestResponse delete(@PathVariable String id) {
Mono<ResponseEntity<Object>> responseEntityMono = this.dynamicRouteService.delete(id); return RestResponseBuilder.restResponse().data(responseEntityMono).build();}

/** * 把传递进来的参数转换成路由对象 * @param gatewayRouteDefinition 路由模型 * @return */private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gatewayRouteDefinition) { RouteDefinition definition = new RouteDefinition(); definition.setId(gatewayRouteDefinition.getId()); definition.setOrder(gatewayRouteDefinition.getOrder());
//设置断言 List<PredicateDefinition> pdList = new ArrayList<>(); List<GatewayPredicateDefinition> gatewayPredicateDefinitionList = gatewayRouteDefinition.getPredicates(); for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) { PredicateDefinition predicate = new PredicateDefinition(); predicate.setArgs(gpDefinition.getArgs()); predicate.setName(gpDefinition.getName()); pdList.add(predicate); }
definition.setPredicates(pdList);
//设置过滤器 List<FilterDefinition> filters = new ArrayList(); List<GatewayFilterDefinition> gatewayFilters = gatewayRouteDefinition.getFilters(); for(GatewayFilterDefinition filterDefinition : gatewayFilters){ FilterDefinition filter = new FilterDefinition(); filter.setName(filterDefinition.getName()); filter.setArgs(filterDefinition.getArgs()); filters.add(filter); } definition.setFilters(filters);
URI uri = null; if(gatewayRouteDefinition.getUri().startsWith("http")){ uri = UriComponentsBuilder.fromHttpUrl(gatewayRouteDefinition.getUri()).build().toUri(); }else{ // uri为 lb://consumer-service 时使用下面的方法 uri = URI.create(gatewayRouteDefinition.getUri()); } definition.setUri(uri); return definition;}}
复制代码

新增路由接口


发布于: 1 小时前阅读数: 3
用户头像

🏆2021年InfoQ写作平台-签约作者 🏆 2020.03.25 加入

👑【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 我们始于迷惘,终于更高水平的迷惘

评论

发布
暂无评论
👊【SpringCloud技术专题】超级详细的Gateway网关的技术指南