快速突击-Spring-Cloud-Gateway

54次阅读

共计 7069 个字符,预计需要花费 18 分钟才能阅读完成。

意识 Spring Cloud Gateway

Spring Cloud Gateway 是一款基于 Spring 5,Project Reactor 以及 Spring Boot 2 构建的 API 网关,是 Spring Cloud 微服务生态的次要组件之一。Spring Cloud Gateway 次要负责接口申请的路由散发,并且反对对申请的平安验证,流量监控和流量管制等扩大操作。另外值得一提的点是,Spring Cloud Gateway 默认采纳了非阻塞 I/O 模型实现申请路由的散发。对于解决一些 I /O 耗时长的申请上,相比其余一样用 Java 编写采纳的同步阻塞 I /O 模型的网关性能更高,解决的并发数也更高,防止了因为 I/O 阻塞(网络调用,数据库操作等)导致线程闲暇下来,仍能持续解决响应其余申请。

Spring Cloud Gateway 实用场景

作为 API 网关,Spring Cloud Gateway 所提供的性能也很弱小,集成了对负载平衡,动静路由,访问控制,限流熔断,埋点监控等性能的反对。如果现有的微服务体系是以 Java 生态甚至 Spring 生态为根底的,那么就非常适宜应用 Spring Cloud Gateway 作为 API 利用网关了,让聚合治理多个微服务 API,对外进行对立的输入。

同时秉承 Spring 家族的传统,Spring Cloud Gateway 也旨在提供一个简略,且高效的形式来进行 API 路由和申请关注点的扩大,对于曾经相熟 Spring 或者 Spring Boot 的开发者来说,Spring Cloud Gateway 学习老本并不高,利用底层框架所带的注解驱动和自动化配置等个性,应用和扩大起来难度都不算高。

疾速上手 Spring Cloud Gateway

利用 Spring Cloud Gateway 能疾速搭建一个 API 网关,但在这之前,先介绍一下应用 Spring Cloud Gateway 框架所波及的一些专用概念,来加深对 Spring Cloud Gateway 的意识,不便前面的应用。

  • 路由:是 Spring Cloud Gateway 中根底的组件,通常由一个 id 标识,指标 URI,以及一系列断言(Predicate)和过滤器组成。
  • 断言(Predicate):是 Java 8 函数库的 Predicate 对象,具体类型为 Predicate<ServerWebExchange>,用于匹配 HTTP 申请上数据信息,如申请头信息,申请体信息。如果对于某个申请的断言为 true,那么它所关联的路由就算匹配胜利,而后将申请给这个路由解决。
  • 过滤器:用于某一个路由的申请或者响应进行批改的组件,在 Spring Cloud Gateway 都要实现 GatewayFilter 接口,并且须要由基于 GatewayFilterFactory 具体实现类结构。

意识下面三个概念之后,再看上图所示,就能分明看出 Spring Cloud Gateway 对客户端申请的处理过程了,这帮忙咱们用好 Spring Cloud Gateway 帮忙很大。

  • 客户端申请首先被 GatewayHandlerMapping 获取,而后依据断言匹配找到对应的路由
  • 找到路由后,走完所关联的一组申请过滤器的解决办法,申请到指标 URI 所对应的服务程序,取得服务响应。
  • 网关收到响应后,通过关联的响应过滤器的解决办法后,同样由 GatewayHandlerMapping 返回响应给客户端。

额定须要留神的是 Spring Cloud Gateway 的过滤器是有序执行的,对立以 order 值的大小决定执行程序,值越小优先级越高,就越先执行。

如何实现 API 聚合

意识 Spring Cloud Gateway 整体解决申请过程之后,咱们当初就疾速构建一个基于 Spring Cloud Gateway 的 API 网关,看看在理论利用中还须要留神的哪些地方,须要留神的是本文所应用的 Spring Cloud Gateway 属于最新的里程碑版本 2.2.3,对应 Spring Boot 版本为 2.3.1,并且 Spring Cloud 版本为 Hoxton.SR6。利用 Spring Initializr,抉择对应的版本和依赖后疾速新建一个我的项目 spring-cloud-gateway-quick-start,并且为了实现申请的路由,体现网关的成果,再别离新建用户服务利用 demo-userservice 和订单服务利用 demo-orderservice,各自提供一个可调用 API 接口。

用户服务裸露 8071 端口,提供 /user/get 接口:

// demo-userservice  我的项目
@RestController
@RequestMapping("/user")
public class UserServiceController {@RequestMapping("/get")
    public User get() {return User.mock();
    }
}

相似,订单服务裸露 8061 端口,提供 /order/get 接口:

// demo-orderservice 我的项目
@RestController
@RequestMapping("/order")
public class OrderServiceController {@RequestMapping("/get")
    public Order get() {return Order.mock();
    }
}

接下来要通过 Spring Cloud Gateway 将两个服务接口聚合在 spring-cloud-gateway-quick-start 我的项目中,首先来看下利用 Spring Cloud Gateway API 形式的实现:

@SpringBootApplication
public class DemogatewayApplication {public static void main(String[] args) {SpringApplication.run(DemogatewayApplication.class, args);
    }

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes().route("user-service", r -> r.path("/user/*").uri("http://localhost:8071"))
                .route("order-service", r -> r.path("/order/*").uri("http://localhost:8061"))
                .build();}
}

接下来要通过 Spring Cloud Gateway 将两个服务接口聚合在 spring-cloud-gateway-quick-start 我的项目中,首先来看下利用 Spring Cloud Gateway API 形式的实现:

上述代码就曾经实现 API 路由的性能,是不是很疾速,同时启动 spring-cloud-gateway-quick-start 和其余服务利用,就能够对立通过网关利用拜访用户服务和订单服务了:

one@192 ~ % curl http://localhost:8080/user/get
{"id":4720186416534735290,"token":"86b6118d-7dc6-4d30-a5f3-3d5fc6348f9a"}
  
one@192 ~ % curl http://localhost:8080/order/get
{"id":5832646761962425508,"title":"My Order"}

回到 API 实现的代码,DemogatewayApplication#customRouteLocator 办法中定义了两个 id 别离为 user-service 和 order-service 的路由,并且设置了匹配申请的断言,以及真正指标申请地址。这里路由的断言采纳了门路匹配的规定,只有原始申请地址合乎对应的规定就算匹配到此路由,但 Spring Cloud Gate 还反对丰盛的断言规定,如主机匹配,申请体字段匹配,申请数据匹配等等,足以满足定制路由断言的规定了。

因为应用 API 就是硬编码方式将路由规定定义在程序里了,这样做扩展性很差,也不好保护。于是更举荐另外一种实现形式:配置化。来看下要实现雷同性能,在 application.properties 里该如何配置:

spring.cloud.gateway.routes[0].id=order-service
spring.cloud.gateway.routes[0].uri=http://localhost:8061
spring.cloud.gateway.routes[0].predicates[0].name=Path
spring.cloud.gateway.routes[0].predicates[0].args[pattern]=/order/*
spring.cloud.gateway.routes[1].id=user-service
spring.cloud.gateway.routes[1].uri=http://localhost:8071
spring.cloud.gateway.routes[1].predicates[0].name=Path
spring.cloud.gateway.routes[1].predicates[0].args[pattern]=/user/*

应用下面的配置,重启网关利用,同样能实现之前 API 形式的成果,因为路由规定转移到了配置文件中,就大大不便对 API 的治理,为实现动静路由也提供了可能。当然须要实现动静路由,除了路由配置,还须要进行额定的扩大实现路由规定的动静刷新,波及 Spring Cloud Gateway 更高级的用法,本文就不再具体赘述了,能够等后续进阶应用和剖析的文章或者参考网上其余实现材料。

如何自定义过滤器

为了能对 API 的申请或者响应解决,Spring Cloud Gateway 提供过滤器组件来实现这一性能,并且内置了很多功能强大。另外过滤器分两类,全局过滤器和网关过滤器,对于全局过滤器,所有匹配到路由的申请解决时都会通过全局过滤器解决;而网关过滤器只有显示在指定路由上时才会起到左右。

Spring Cloud Gateway 默认的全局过滤器有 8 个:

  • ForwardRoutingFilter
  • LoadBalancerClientFilter(弃用)
  • ReactiveLoadBalancerClientFilter
  • WebClientHttpRoutingFilter
  • NettyWriteResponseFilter
  • RouteToRequestUrlFilter
  • WebsocketRoutingFilter
  • GatewayMetricsFilter

而网关过滤器就更多了,并且由对应工厂类来结构,比方用于熔断的 HystrixGatewayFilterFactory,用于限流的 RequestRateLimiterGatewayFilterFactory,用于批改申请数据的 ModifyRequestBodyGatewayFilterFactory 等等,当然也反对开发者进行定义本人的过滤器。

首先来看下如何自定义一个全局过滤器,代码实现比较简单:

@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {private Logger log = LoggerFactory.getLogger(MyAuthFilterFactory.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("执行自定过滤器");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {return -1;}
}

这样就能够为所有路由增加一个全局的过滤器了。不同于全局过滤器的定义,网关过滤器必须在指定路由上进行申明能力失效,参考官网内置的网关拦截器,自定义一个用于受权的繁难网关拦截器工厂如下:

@Component
public class MyAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<MyAuthGatewayFilterFactory.Config> {private Logger logger = LoggerFactory.getLogger(MyAuthGatewayFilterFactory.class);

    public MyAuthGatewayFilterFactory() {super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest request = exchange.getRequest();
                MultiValueMap<String, String> queryParams = request.getQueryParams();
                String from = queryParams.getFirst(config.getAuthKey());
                ServerHttpResponse response = exchange.getResponse();
                logger.warn("校验受权开始");
                if (config.getAuthValue().equals(from)) {logger.warn("校验受权胜利");
                    return chain.filter(exchange);
                } else {logger.warn("校验受权失败");
                    response.setStatusCode(HttpStatus.OK);
                    response.getHeaders().setContentType(MediaType.valueOf("text/html;charset=utf-8"));
                    DataBuffer wrap = response.bufferFactory().wrap(config.getAuthFailMsg().getBytes(Charset.forName("UTF-8")));
                    return response.writeWith(Flux.just(wrap));
                }
            }
        };
    }

    public static class Config {
        private String authKey = "from";
        private String authValue = "system";
        private String authFailMsg = "受权失败";

        public String getAuthKey() {return authKey;}

        public void setAuthKey(String authKey) {this.authKey = authKey;}

        public String getAuthValue() {return authValue;}

        public void setAuthValue(String authValue) {this.authValue = authValue;}

        public String getAuthFailMsg() {return authFailMsg;}

        public void setAuthFailMsg(String authFailMsg) {this.authFailMsg = authFailMsg;}
    }
}

如果要在 user-service 路由下应用,须要在 application.properties 配置文件增加如下配置:

spring.cloud.gateway.routes[1].filters[0].name=MyAuth

这里的名称就须要跟 MyAuthGatewayFilterFactory 类的 MyAuth 保持一致,Spring Cloud Gateway 会主动拼接上 AuthGatewayFilterFactory 去查找对应的网关过滤器,没有找到就会导致启动失败,抛出异样:

java.lang.IllegalArgumentException: Unable to find GatewayFilterFactory with name MyAuth2

配置完对网关利用进行重启,这是应用原来的形式去申请用户服务,曾经无奈失常拜访,只会返回 校验受权失败 的信息,必须以 http://localhost:8080/user/get?from=system 形式拜访能力胜利获取到数据,阐明定义的受权拦截器曾经起了作用。

这里咱们就将全局拦截器和网关拦截器都实现了自定义,通常状况咱们都会在网关拦截器上进行扩大定制,也联合内置的过滤器应用。最初将残缺的实现代码上传到 Gitlab:https://github.com/wrcj12138a…,感兴趣的敌人也能够参考下。

本文由博客一文多发平台 OpenWrite 公布!

正文完
 0