1、API Gateway简单介绍

1.1 什么是API网关?

API网关是所有请求的入口,承载了所有的流量,API Gateway是一个门户一样,也可以说是进入系统的唯一节点。这跟面向对象设计模式中的Facet模式很像。API Gateway封装内部系统的架构,并且提供API给各个客户端。它还可能有其他功能,如授权、监控、负载均衡、缓存、请求分片和管理、静态响应处理等

画图表示,没有网关的情况,客户端的请求会直接落到后端的各个服务中,无法集中统一管理
SpringCloud系列之API Gateway开发手册 - 图1
画图表示,有网关的情况,所有的请求都先经过网关,然后进行分发到对应服务
SpringCloud系列之API Gateway开发手册 - 图2

1.2 API网关的作用

网关可以用于授权、监控、负载均衡、缓存、请求分片和管理、静态响应处理等,挑几个介绍

  • 动态路由
    网关可以做路由转发,假如服务信息变了,只要改网关配置既可,所以说网关有动态路由(Dynamic Routing)的作用,如图:
    SpringCloud系列之API Gateway开发手册 - 图3
  • 请求监控
    请求监控可以对整个系统的请求进行监控,详细地记录请求响应日志,如图,可以将日志丢到消息队列,如果没有使用网关的话,记录请求信息需要在各个服务中去做
    SpringCloud系列之API Gateway开发手册 - 图4
  • 认证鉴权
    认证鉴权可以对每一个访问请求做认证,拒绝非法请求,保护后端的服务,不需要每个服务都做鉴权,在项目中经常有加上OAuth2.0、JWT,Spring Security进行权限校验
    SpringCloud系列之API Gateway开发手册 - 图5
  • 压力测试
    有网关的系统,如果要要对某个服务进行压力测试,可以如图所示,改下网关配置既可,测试请求路由到测试服务,测试服务会有单独的测试数据库,这样测试的请求就不会影响到正式的服务和数据库
    SpringCloud系列之API Gateway开发手册 - 图6

    2、SpringCloud Gateway

2.1 What is SpringCloud Gateway?

用公网的解释是:SpringCloud Gateway是一个在Spring生态系统之上构建的API网关,包括:Spring 5,Spring Boot 2,Project Reactor(基于高性能的Reactor模式响应式通信框架Netty,异步阻塞模型)。Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注,例如:安全性,监视/度量和弹性。

SpringCloud Gateway采用了WebFlux,所以使用时不需要引入web场景启动器对应jar,而需要依赖于WebFlux,当然高版本,肯定不需要自己引入,在对应的starter已经集成
image.png

2.2 SpringCloud Gateway结构

引用官方图例如图,SpringCloud Gateway的底层基于Netty,主要组成有Predicates(谓词或者断言)、Route(路由)、Filter(过滤器)

SpringCloud系列之API Gateway开发手册 - 图8
画张思维导图表示SpringCloud Gateway的组成:
image.png

  • 路由(route):网关的基本构建块。它由ID,目标URI,谓词集合和过滤器集合定义
  • 过滤器(Filter):这些过滤器是使用特定工厂构造的Spring FrameworkGatewayFilter实例
  • 谓词(Predicates): 引用了java8的函数谓词,输入类型是Spring FrameworkServerWebExchange。谓词可以匹配HTTP请求中的所有内容,例如标头或参数

2.3 SpringCloud Gateway工作方式

从总体上概述了Spring Cloud Gateway的工作方式,引用官网的图例:

SpringCloud系列之API Gateway开发手册 - 图10
从官网的图来看,并不是特别复杂,首先客户端请求都会先经过Gateway Handler Mapping,匹配上就通过Gateway Web Handler转给过滤器处理,过滤器分为PreFilter(前置过滤器)、PostFilter(后置过滤器)。过滤器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。所有“前置”过滤器逻辑均被执行。然后发出代理请求。发出代理请求后,将运行“后置”过滤器逻辑

3、Gateway实验环境准备

环境准备:

  • JDK 1.8
  • SpringBoot2.2.3
  • SpringCloud(Hoxton.SR7)
  • Maven 3.2+
  • 开发工具

    • IntelliJ IDEA
    • smartGit

    新增SpringBoot Initializer项目:New Module->Spring Initializer,选择jdk版本,至少jdk8
    image.png
    packaging选择jar,java version选择jdk8的,然后点next
    image.png
    选择Gateway的依赖,选择之后会自动加上对应pom配置
    image.png
    Eureka客户端的依赖也可以加上,这样就可以注册服务到eureka服务端
    image.png
    新建项目之后,检查pom是否有spring-cloud-starter-gateway

    1. <dependency>
    2. <groupId>org.springframework.cloud</groupId>
    3. <artifactId>spring-cloud-starter-gateway</artifactId>
    4. </dependency>

    检查pom是否有spring-cloud-starter-netflix-eureka-client

    1. <dependency>
    2. <groupId>org.springframework.cloud</groupId>
    3. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    4. </dependency>

    如果不通过idea的Spring Initializer新建项目的,需要自己加上:

    1. <dependencyManagement>
    2. <dependencies>
    3. <dependency>
    4. <groupId>org.springframework.cloud</groupId>
    5. <artifactId>spring-cloud-dependencies</artifactId>
    6. <version>${spring-cloud.version}</version>
    7. <type>pom</type>
    8. <scope>import</scope>
    9. </dependency>
    10. </dependencies>
    11. </dependencyManagement>

本博客的是基于spring-cloud-starter-netflix-eureka-client进行试验,试验前要运行eureka服务端,eureka服务提供者,代码请参考上一章博客

4、API Gateway简单实现

4.1 YAML配置Eureka和Gateway

Eureka客户端配置:ps,注意在Application加上@EnableEurekaClient

  1. eureka:
  2. client:
  3. # 配置eureka服务地址
  4. service-url:
  5. defaultZone: http://localhost:8761/eureka/
  6. # 关闭eureka健康检查
  7. healthcheck:
  8. enabled: false
  9. # Eureka服务注册开启
  10. register-with-eureka: true
  11. # Eureka服务发现开启
  12. fetch-registry: true
  13. instance:
  14. status-page-url-path: http://localhost:8761/actuator/info
  15. health-check-url-path: http://localhost:8761/actuator//health
  16. # 显示ip
  17. prefer-ip-address: true
  18. # eureka实例id
  19. instance-id: api-gateway8082

API Gateway配置:

  1. spring:
  2. application:
  3. # 指定application name
  4. name: api-gateway
  5. cloud:
  6. gateway:
  7. routes:
  8. # 路由id
  9. - id: user-service-provider
  10. # 路由到的地址
  11. uri: http://127.0.0.1:8083/api/users/{username}
  12. # 设置谓词,路径匹配的进行路由
  13. predicates:
  14. - Path=/api/users/{username}

可能遇到:Unable to find RoutePredicateFactory with name Path ,谓词Path必须大写,而且等号之间不能有空格

4.2 Bean注册方式配置网关

除了配置文件,也可以通过配置类进行网关配置:

  1. package com.example.springcloud.gateway.configuration;
  2. import org.springframework.cloud.gateway.route.RouteLocator;
  3. import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. /**
  7. * <pre>
  8. * spring cloud gateway configuration
  9. * </pre>
  10. *
  11. * <pre>
  12. * @author mazq
  13. * 修改记录
  14. * 修改后版本: 修改人: 修改日期: 2020/09/14 15:00 修改内容:
  15. * </pre>
  16. */
  17. @Configuration
  18. public class GatewayConfiguration {
  19. @Bean
  20. public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
  21. return routeLocatorBuilder.routes()
  22. .route("user-service-provider1",
  23. r->r.path("/api/findUser").uri("http://127.0.0.1:8083/api/findUser"))
  24. .build();
  25. }
  26. }

4.3 CURL测试Gateway的接口

  1. curl http://127.0.0.1:gateway_port/api/users/admin

image.png

5、Gateway谓词工厂分类

SpringCloud Gateway的谓词工厂分类如图,所谓谓词或者说断言,其实就是一种匹配的规则,根据这些匹配,匹配到就经过过滤器
image.png
SpringCloud Gateway的谓词分类比较多,在SpringCloud官方手册也有进行比较详细的介绍,所以本文章挑几个进行介绍既可

  • After谓词配置

在指定的datetime后触发过滤器

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: after_route
  6. uri: http://127.0.0.1:8083/api/findUser
  7. predicates:
  8. - After=2019-01-01T16:30:00+08:00[Asia/Shanghai]
  • Before谓词配置

    1. spring:
    2. cloud:
    3. gateway:
    4. routes:
    5. - id: before_route
    6. uri: http://127.0.0.1:8083/api/findUser
    7. predicates:
    8. - Before=2018-01-01T16:30:00+08:00[Asia/Shanghai]
  • Between谓词配置 ```yaml spring: cloud: gateway:

    1. routes:
    2. - id: between_route
    3. uri: http://127.0.0.1:8083/api/findUser
    4. predicates:
    5. - Between=2018-01-01T16:30:00+08:00[Asia/Shanghai], 2019-01-01T16:30:00+08:00[Asia/Shanghai]
  1. - Cookie谓词配置
  2. 加上cookie "chocolate=ch.p" 经过过滤器
  3. ```yaml
  4. spring:
  5. cloud:
  6. gateway:
  7. routes:
  8. - id: cookie_route
  9. uri: http://127.0.0.1:8083/api/findUser
  10. predicates:
  11. - Cookie=chocolate, ch.p

linux curl测试接口:

  1. curl http://192.168.9.10:8082/api/findUser?username=nicky --cookie "chocolate=ch.p"

image.png

  • Header谓词配置

加上请求头参数“X-Request-Id”, \d+标识数字

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: header_route
  6. uri: http://127.0.0.1:8083/api/findUser
  7. predicates:
  8. - Header=X-Request-Id, \d+
  1. curl http://192.168.9.10:8082/api/findUser?username=nicky -H "X-Request-Id:123"
  • HOST谓词配置

加上域名host匹配

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: host_route
  6. uri: http://127.0.0.1:8083/api/findUser
  7. predicates:
  8. - Host=**.csdn.net
  1. curl http://192.168.9.10:8082/api/findUser?username=nicky -H "Host:smilenicky.csdn.net"
  • Method谓词配置

method是http请求方式,eg:GET、POST、PUT等等

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: method_route
  6. uri: http://127.0.0.1:8083/api/findUser
  7. predicates:
  8. - Method=GET
  1. curl -X POST http://192.168.9.30:8082/api/findUser?username=nicky
  2. {"timestamp":"2020-09-15T02:37:30.224+00:00","status":405,"error":"Method Not Allowed","message":"","path":"/api/findUser"}
  • Query谓词配置

请求参数匹配,url?username=???

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: query_route
  6. uri: http://127.0.0.1:8083/api/findUser
  7. predicates:
  8. - Query=username
  1. curl http://192.168.9.30:8082/api/findUser?username=nicky
  • 远程地址谓词

在远程机192.168.1.1调用接口,都会匹配

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: remoteaddr_route
  6. uri: http://127.0.0.1:8083/api/findUser
  7. predicates:
  8. - RemoteAddr=192.168.1.1/22
  1. curl http://192.168.9.30:8082/api/findUser?username=nicky
  • 权重谓词配置

权重匹配比较像nginx的权重配置,8083 url接收80%的请求,8084接收20%请求

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: weight_high
  6. uri:http://127.0.0.1:8083
  7. predicates:
  8. - Weight=group1, 8
  9. - id: weight_low
  10. uri: http://127.0.0.1:8084
  11. predicates:
  12. - Weight=group1, 2
  1. curl http://192.168.9.30:8082/api/findUser?username=nicky

6、Gateway过滤器类型分类

image.png

7、GatewayFilter工厂分类

SpringCloud的gatewayFilter有很多分类,详情参考官方手册,官方手册的介绍也相对比较详细,所以本博客挑几个介绍:

  • PrefixPath GatewayFilter

这个网关过滤器是在请求链接时候加上前缀,如下配置,加上/api的前缀

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: prefixpath_route
  6. uri: http://127.0.0.1:8083
  7. filters:
  8. - PrefixPath=/api
  1. curl http://192.168.9.30:8082/findUser?username=nicky

相当于:

  1. curl http://192.168.9.30:8082/api/findUser?username=nicky
  • AddRequestParameter GatewayFilter

AddRequestParameter GatewayFilter自动带上请求参数的过滤器,加上username=admin参数

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: add_request_parameter_route
  6. uri: http://127.0.0.1:8083
  7. filters:
  8. - AddRequestParameter=username, admin
  1. curl http://192.168.9.30:8082/api/findUser

相当于:

  1. curl http://192.168.9.30:8082/api/findUser?username=admin
  • Hystrix GatewayFilter

Hystrix分布式服务容错保护的过滤器

pom加上配置:

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>

写个ResultBean类:

  1. package com.example.springcloud.gateway.bean;
  2. import lombok.Data;
  3. import org.springframework.http.HttpStatus;
  4. /**
  5. * <pre>
  6. * ResultBean
  7. * </pre>
  8. *
  9. * <pre>
  10. * @author mazq
  11. * 修改记录
  12. * 修改后版本: 修改人: 修改日期: 2020/08/10 17:07 修改内容:
  13. * </pre>
  14. */
  15. @Data
  16. public class ResultBean {
  17. /**
  18. * 状态
  19. * */
  20. private int status;
  21. /**
  22. * 描述
  23. * */
  24. private String desc;
  25. /**
  26. * 数据返回
  27. * */
  28. private Object data;
  29. public ResultBean(int status, String desc, Object data) {
  30. this.status = status;
  31. this.desc = desc;
  32. this.data = data;
  33. }
  34. public ResultBean(Object data) {
  35. this.status = HttpStatus.OK.value();
  36. this.desc = "处理成功";
  37. this.data = data;
  38. }
  39. public static ResultBean ok(Object data) {
  40. return new ResultBean(data);
  41. }
  42. public static ResultBean ok() {
  43. return new ResultBean(null);
  44. }
  45. public static ResultBean badRequest(String desc,Object data) {
  46. return new ResultBean(HttpStatus.BAD_REQUEST.value(), desc, data);
  47. }
  48. public static ResultBean badRequest(String desc) {
  49. return new ResultBean(HttpStatus.BAD_REQUEST.value(), desc, null);
  50. }
  51. public static ResultBean serverError(String desc, Object data){
  52. return new ResultBean(HttpStatus.INTERNAL_SERVER_ERROR.value(),"服务器内部异常:"+desc,data);
  53. }
  54. public static ResultBean serverError(String desc){
  55. return new ResultBean(HttpStatus.INTERNAL_SERVER_ERROR.value(),"服务器内部异常:"+desc,null);
  56. }
  57. }

接口出错,回调这个接口,避免一直请求,造成服务雪崩

  1. package com.example.springcloud.gateway.rest.controller;
  2. import com.example.springcloud.gateway.bean.ResultBean;
  3. import org.springframework.http.HttpStatus;
  4. import org.springframework.web.bind.annotation.GetMapping;
  5. import org.springframework.web.bind.annotation.RestController;
  6. /**
  7. * <pre>
  8. * HystrixRestController
  9. * </pre>
  10. *
  11. * <pre>
  12. * @author mazq
  13. * 修改记录
  14. * 修改后版本: 修改人: 修改日期: 2020/09/15 11:46 修改内容:
  15. * </pre>
  16. */
  17. @RestController
  18. public class HystrixRestController {
  19. @GetMapping(value = {"/fallback"})
  20. public ResultBean fallback() {
  21. return ResultBean.badRequest("Hystrix fallback");
  22. }
  23. }

加上配置:

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: hystrix_route
  6. uri: http://127.0.0.1:8083/api/findUser
  7. predicates:
  8. - Method=GET
  9. filters:
  10. - name: Hystrix
  11. args:
  12. name: fallbackcmd
  13. fallbackUri: forward:/fallback

关了服务请求,让接口报错:

  1. curl http://192.168.9.10:8082/api/findUser?username=nicky
  2. {"status":400,"desc":"Hystrix fallback","data":null}

ps:spring cloud gateway:Unable to find GatewayFilterFactory with name Hystrix

项目里有引入eureka客户端,跟其源码,可以指定其实是有引入Hystrix的,所以原本,我就不加上Hystrix配置,不过项目是一直有报错:spring cloud gateway:Unable to find GatewayFilterFactory with name Hystrix,所以maven查看jar,发现eureka client只是引入部分的jar:
image.png

image.png
所以总的来说,还是要自己配置;

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  4. </dependency>
  • Requestratelimiter GatewayFilter

requestratelimiter用于简单的实现限流,基于Redis实现

  1. @Bean
  2. @Primary
  3. KeyResolver userKeyResolver() {
  4. return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("username"));
  5. }
  6. @Bean
  7. public KeyResolver ipKeyResolver() {
  8. return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
  9. }

KeyResolver需要加上@Primary
image.png

  1. spring:
  2. redis:
  3. host: 127.0.0.1
  4. password:
  5. port: 6379
  6. cloud:
  7. gateway:
  8. routes:
  9. ## RequestRateLimiter GatewayFilter简单限流
  10. - id: requestratelimiter_route
  11. uri: http://192.168.9.30:8083/api/findUser
  12. predicates:
  13. - Method=GET
  14. filters:
  15. - name: RequestRateLimiter
  16. args:
  17. # 每秒允许处理的请求数量
  18. redis-rate-limiter.replenishRate: 1
  19. # 每秒最大处理的请求数量
  20. redis-rate-limiter.burstCapacity: 2
  21. #每秒最大处理token数量
  22. redis-rate-limiter.requestedTokens: 1
  23. # 限流策略,对应策略的BeanName
  24. key-resolver: "#{@ipKeyResolver}"

image.png

  • Retry GatewayFilter

retry GatewayFilter是用于重试的过滤器

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. # RetryFilter 重试过滤器
  6. - id: retry_test
  7. uri: http://127.0.0.1:8083/api/findUser
  8. predicates:
  9. - Method=GET
  10. filters:
  11. - name: Retry
  12. args:
  13. retries: 3 # 重试次数
  14. statuses: BAD_GATEWAY
  15. methods: GET,POST
  16. backoff:
  17. firstBackoff: 10ms
  18. maxBackoff: 50ms
  19. factor: 2
  20. basedOnPreviousValue: false

image.png

8、GlobalFilter工厂分类介绍

SpringCloud Gateway的GlobalFilter分类如图,详情可以参考官方手册,GlobalFilter是全局的过滤器,作用于所有的路由

8.1 全局过滤器分类

image.png

8.2 自定义全局过滤器

  1. package com.example.springcloud.gateway.filter.global;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  4. import org.springframework.cloud.gateway.filter.GlobalFilter;
  5. import org.springframework.core.Ordered;
  6. import org.springframework.web.server.ServerWebExchange;
  7. import reactor.core.publisher.Mono;
  8. @Slf4j
  9. public class CustomGlobalFilter implements GlobalFilter, Ordered {
  10. @Override
  11. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  12. log.info("custom global filter");
  13. return chain.filter(exchange);
  14. }
  15. @Override
  16. public int getOrder() {
  17. return -1;
  18. }
  19. }

配置类进行配置:

  1. @Bean
  2. public GlobalFilter customFilter() {
  3. return new CustomGlobalFilter();
  4. }

image.png

9、SpringCloud官方手册和博客

9.1 SpringCloud Gateway官方手册