(7)服务门户API网关——Gateway

1、API网关引言

Nacos、Ribbon、OpenFeign等组件解决微服务间的服务调用问题,但对于用户端从外侧访问微服务时,如何有效管理微服务,如何暴露服务给用户呢?

将每一个微服务的接口直接暴露给用户(错误做法):

  • 所有 API 接口对外直接暴露给用户端是不安全和不可控的,用户可能越权访问不属于它的功能
  • 后台服务可能采用不同的通信方式(restful/RPC等),不同的接入方式让用户端接入困难。
  • 不同微服务可能有相同的处理逻辑,直接暴露服务给用户很难做到统一的前置处理,如访问前对用户鉴权,就必须将鉴权代码分散到每个服务模块中,随着服务数量增加代码将难以维护。

通过 API 网关为微服务访问提供统一的访问入口
在这里插入图片描述
作用:

  • API 网关是用户端访问 API 的唯一入口,从用户的角度来说只需关注 API 网关暴露哪些接口,后端服务的处理细节用户不需要知道。从这方面讲,将用户端与微服务的具体实现进行了解耦。
  • 针对所有请求进行统一鉴权、熔断、限流、日志等前置处理,让微服务专注自己的业务。
  • 统一调用风格,通常 API 网关对外提供 RESTful 风格 URL 接口。用户传入请求后,由 API 网关负责转换为后端服务需要的 RESTful、RPC、WebService 等方式,大幅度简化用户的接入难度。

2、Gateway的使用

2.1、Gateway的首次使用

a. 新建一个Spring Boot项目模块,并引入依赖

注意:gateway-server模块一定是单独的模块,gateway依赖和spring-boot-starter-web依赖有冲突

<!-- 引入gateway starter依赖-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
b. 配置application.yml
# 配置服务端口为8080
server:
  port: 8080

# 配置gateway
spring:
  cloud:
    gateway:
      #配置路由规则
      routes:
          # id是路由的唯一标识名
        - id: order-service-route
          # uri 匹配后的路由地址
          uri: http://localhost:18082
          #断言,也就是匹配规则
          predicates:
            - Path=/orders/**

#配置日志,方便在控制台查看输出效果
logging:
  level:
    root: debug
c. 启动order-service,并配置端口号为18082
d.启动gate-server,访问

访问localhost:8080/order-service的URI(controller路径),即可路由到localhost:18082/order-service的URI(controller路径)

2.2、集成Nacos注册中心

由于配置路由地址的方式因路由地址是静态的,不能支持服务实例数目的扩展和伸缩。因此Gateway集成注册中心,从注册中心获取动态的服务地址列表。

a. 启动多个order-service实例,并将其注册到注册中心
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.136.137:8848
        username: nacos
        password: nacos
        namespace: ...
        group: dev
b. gateway-server模块添加nacos-discovery的起步依赖
<!-- 引入nacos starter依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
c. 配置gateway-server
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.136.137:8848
        username: nacos
        password: nacos
        namespace: ...
        group: dev
        #是否注册本服务到nacos,默认true。只订阅发现服务而不需要被别的服务发现可设置为false
        register-enabled: false
    gateway:
      routes:
          # id是路由的唯一标识名
        - id: order-service-route
          # lb代表负载均衡LoadBalance(使用服务id代替静态路由地址)
          uri: lb://order-service
          #断言,也就是匹配规则
          predicates:
            - Path=/orders/**
d.Gateway的负载均衡(默认使用ribbon的轮询策略)
order-service:
 ribbon:
#默认是轮询,这里修改为随机
   NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
e.启动gate-server,访问

访问localhost:8080/order-service的URI(controller路径),即可路由到localhost:18082/order-service的URI(controller路径)

2.3 Gateway自动集成Nacos实现路由转发

Gateway中配置路由规则的数量会随着微服务的数量而改变,当数量多时逐一手动配置就显得繁琐,此时可以使用Gateway的自动路由转发功能。

spring:
 cloud:
   nacos:
     discovery:
       server-addr: 192.168.136.137:8848
       username: nacos
       password: nacos
       namespace: ...
       group: dev
       #是否注册本服务到nacos,默认true。只订阅发现服务而不需要被别的服务发现可设置为false
       register-enabled: false
   gateway:
     #自动路由转发
     discovery:
       locator:
         enabled: true

这是一个自动项,允许 Gateway 自动实现后端微服务路由转发
注:访问地址由原先的直接访问URI(controller的路径)————>先说明访问的微服务id,再访问URI

#http://网关IP:端口/注册到nacos的微服务id/URI
http://localhost:8080/order-service/orders/101

3、Gateway配置详解

在这里插入图片描述

Gateway 网关三个关键名词:路由(Route)、谓词(Predicate)、过滤器(Filter)。

  • 路由(Route)是指一个完整的网关地址映射与处理过程。一个完整的路由包含两部分配置:谓词与过滤器。
  • 谓词(Predicate)决定前端应用发来的请求要被转发到哪个微服务上。
  • 过滤器(Filter)决定了转发过程中请求、响应数据被网关如何加工处理。

完整的路由配置格式:

spring:
   gateway: 
     routes: 
       - id: xxx #路由规则id
         uri: lb://微服务id  #路由转发至哪个微服务
         #具体的谓词
         predicates: 
        #具体的过滤器
         filters:

3.1 Predicate谓词

Spring Cloud Gateway内置了很多的Predicate,不同的Predicate匹配HTTP请求的不同属性(匹配路径、匹配时间、匹配Cookie、匹配头部信息)

常用谓词
  • Before/After/Between :在指定时点 前/后/内 路由规则生效。
predicates:
  #注意日期必须满足ZonedDateTime的形式
#  - After=2023-01-01T00:00:00.000+08:00[Asia/Shanghai]
#  - Before=2024-01-01T00:00:00.000+08:00[Asia/Shanghai]
  - Between=2023-01-01T00:00:00.000+08:00[Asia/Shanghai],2024-01-01T00:00:00.000+08:00[Asia/Shanghai]
  • Header 代表包含指定请求头,且请求头参数值与规则匹配时生效。
predicates:
  #value部分是正则表达式,\d+表示1个以上的数字
  - Header=X-Request-Id,\d+
  • Host 代表请求头中Host字段值匹配指定地址时生效。
predicates:
  - Host=**.baizhi.com,**.baizhiedu.com
  • Method 代表要求 HTTP 方法符合规定时生效。
predicates:
  - Method=GET,POST
  • Path 代表 URI 符合映射规则时生效。
predicates:
  # /order/{segment} 匹配后,可以segment当一个变量后续在Filter中可以获取到
  # /orders/** 匹配所有以 /orders/开头的
  - Path=/orders/**,/order/{segment}

3.2 Filter过滤器

过滤器(Filter)可以对请求或响应的数据进行额外处理,在Spring Cloud Gateway中内置了很多Filter实现。Filter分为局部过滤GatewayFilter和全局过滤GlobalFilter。

常用的内置GatewayFilter:
  • AddRequestParameter 是对所有匹配的请求添加一个查询参数。
filters:
 #在请求参数中追加foo=bar
  - AddRequestParameter=foo,bar
  • AddResponseHeader 会对所有匹配的请求,在返回结果给客户端之前,在 Header 中添加响应的数据。
#在Response中添加Header头,key=X-Response,Value=Blue。
filters:
  - AddResponseHeader=X-Response,Blue
  • Retry 为重试过滤器,当后端服务不可用时,网关会根据配置参数来发起重试请求。
filters:
#涉及过滤器参数时,采用name-args的完整写法
  - name: Retry #name是内置的过滤器名
    args: #参数部分使用args说明 
      retries: 3 #重试次数
      status: 503 #返回指定错误状态码,才发起重试
      methods: GET,DELETE #指定哪些请求方式需要重试
      series: CLIENT_ERROR,SERVER_ERROR #和status类似,series表示错误码段 client_error是4xx,server_error是5xx
常用的内置GlobalFilter:
  • LoadBalancerClientFilter 配合注册中心实现服务的自动发现和负载均衡。
  • NettyRoutingFilter 使用基于Netty实现的HttpClient请求后端服务,完成服务转发。
  • GatewayMetricsFilter 启动网关指标,需要添加 spring-boot-starter-actuator依赖并进行相关配置

pom.xml

 <!-- 引入actuator starter依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.yml

management:
  endpoint:
    gateway:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "*"

访问gatewaymetric端口

# 如果没有数据,请先访问一次网关的接口
http://localhost:8080/actuator/metrics/gateway.requests

3.3 处理跨域问题

spring:
 cloud:
   gateway:
     globalcors:
       cors-configurations:
         '[/**]':	#处理所有请求路径
           allowedOrigins: "*"
           allowedHeaders: "*"
           allowCredentials: true
           allowedMethods: "*"
     # 使用过滤器去重响应头
     default-filters:
       - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials,RETAIN_FIRST

3.4 路径重写

Gateway的路由在匹配请求后,默认将匹配到的uri拼接到目标地址后
http://网关ip:port/uri 会转发到 http://服务ip:port/uri
但有时前端发起的请求地址和要匹配的地址可能并不一致,比如说前端发起请求时在路径上添加/api前缀,这就需要在网关进行路径重写。
例:将http://localhost:8080/api/orders/1——>http://localhost:8081/orders/1
方式一:

spring:
 cloud:
   gateway:
     routes:
       - id: order-service-route
         uri: lb://order-service/
         predicates:
         	#路径需要修改
           - Path=/api/orders/**
         filters:
           # 使用RewritePath过滤器处理请求地址
           - RewritePath=/api/?(?<segment>.*),/$\{segment}

方式二:

spring:
 cloud:
   gateway:
     routes:
       - id: order-service-route
         uri: lb://order-service/
         predicates:
         	#路径需要修改
           - Path=/api/orders/**
         filters:
           # 使用StripPrefix过滤器,忽略掉第一层前缀进行转发
           - StripPrefix=1

方式三:

spring:
 cloud:
   gateway:
     routes:
       - id: order-service-route
         uri: lb://order-service/
         predicates:
         	#路径需要修改
           - Path=/api/orders/**
         filters:
         	#使用StripPrefix过滤器,忽略掉前2层前缀进行转发  /api/orders/1  ==> /1
           - StripPrefix=2
           #转发前使用PrefixPath再补上orders前缀   /1 ==> /orders/1
           - PrefixPath=/orders

3.5 路由的优先级

随着微服务数目的增加,网关中路由的配置也会逐渐增多,有时候会出现一个请求路径多个路由规则都匹配的情况,这个时候就需要保证路由的优先级

     routes:
       - id: ab
         uri: lb://user-service/
         #定义路由优先级,order值越小优先级越高
         order: 2
         predicates:
           - Path=/a/b/**
         filters:
           - RewritePath=/a/b/?(?<segment>.*),/users/$\{segment}
       - id: abc
         uri: lb://order-service/
         order: 1
         predicates:
           - Path=/a/b/c/**
         filters:
           - RewritePath=/a/b/c/?(?<segment>.*),/orders/$\{segment}

4、自定义Gateway过滤器

a.自定义全局过滤器
  • 编码:实现GlobalFilter接口,并@Component配置为bean
    @Component
    public class MyGlobalFilter implements GlobalFilter {
        //exchange:包含了请求和响应 chain:过滤器链
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            System.out.println("MyGlobalFilter.filter before...");
            long begin=System.currentTimeMillis());
    
            Mono<Void> mono = chain.filter(exchange).doFinally(signalType->{
                System.out.println("MyGlobalFilter.filter after...");
                long endTime = System.currentTimeMillis();
                System.out.println("duration filter time: "+(endTime-startTime)+"ms");
            });
            return mono;
        }
    }
    
b.自定义网关过滤器
  • 编码:继承AbstractGatewayFilterFactory
    //注意:类名必须以GatewayFilterFactory结尾
    @Component
    public class MyAddRequestHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory<MyAddRequestHeaderGatewayFilterFactory.MyAddRequestHeaderConfig> {
    	//一定要调用父类的有参构造方法,传入配置类的类型
        public MyAddRequestHeaderGatewayFilterFactory() {
            super(MyAddRequestHeaderConfig.class);
        }
    
        @Override
        //config对应着用户使用自定义网关过滤器提供的配置
        public GatewayFilter apply(MyAddRequestHeaderConfig config) {
            return new GatewayFilter() {
                @Override
                public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                    //获取配置中的参数值
                    String headerName = config.getHeaderName();
                    String headerValue = config.getHeaderValue();
                    //修改request和exchange
                    ServerHttpRequest request = exchange.getRequest().mutate().header(headerName, headerValue).build();
                    ServerWebExchange webExchange = exchange.mutate().request(request).build();
                    return chain.filter(webExchange);
                }
            };
        }
    
        //用来自定义过滤器配置参数
        public static class MyAddRequestHeaderConfig {
            private String headerName;
            private String headerValue;
    
            public String getHeaderName() {
                return headerName;
            }
    
            public void setHeaderName(String headerName) {
                this.headerName = headerName;
            }
    
            public String getHeaderValue() {
                return headerValue;
            }
    
            public void setHeaderValue(String headerValue) {
                this.headerValue = headerValue;
            }
        }
    }
    
  • 配置:在指定路由中使用
    spring:
      cloud:
        gateway:
          routes:
            - id: order-service-route
              uri: lb://order-service/
              predicates:
                - Path=/orders/**
              filters:
                # 网关过滤器名就是类名去掉GatewayFilterFactory后缀
                - name: MyAddRequestHeader
                  args:
                    headerName: X-Request
                    headerValue: xxx
    

5、集成Sentinel限流

从Sentinel 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:

  • route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
  • 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组

5.1、基本使用

  1. pom.xml引入依赖
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
  1. application.yml增加sentinel配置(连接sentinel的控制台,并设置gateway限流的响应),并启动微服务
spring:
 gateway: 
   routes: 
     - id: xxx #路由规则id
       uri: lb://微服务id  #路由转发至哪个微服务
       #具体的谓词
       predicates: 
      #具体的过滤器
       filters:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:9191
      scg:
        fallback:
          # 模式 response redirect
          mode: response
          response-status: 429
          content-type: application/json
          response-body: "对不起,已经被限流了!!!"
  1. 在Dashboard中配置规则(对指定route_id进行限流配置)
    在这里插入图片描述
    说明:Burst size是指应对突发请求时额外允许的请求数目,我们配置为0。
  2. 测试查看限流效果
    在这里插入图片描述

5.2、限流规则持久化

  1. pom.xml新增 nacos-config和 sentinel-datasource-nacos 依赖
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- 引入config-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  1. bootstrap.yml添加nacos-config的配置(连接nacos配置中心)
spring:
  application:
    name: gateway-server
  profiles:
    #环境
    active: dev
  cloud:
    nacos:
      config:
        file-extension: yml
        server-addr: 192.168.136.137:8848
        username: nacos
        password: nacos
        namespace: ...
        group: dev
        cluster-name: HZ
  1. application.yml中增加 Nacos下载规则
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:9191
      scg:
        fallback:
          # 模式 response redirect
          mode: response
          response-status: 429
          content-type: application/json
          response-body: "对不起,已经被限流了!!!"
      datasource:
        flow:
          nacos:
            server-addr: 192.168.136.137:8848
            dataId: ${spring.application.name}-gw-flow-rules
            namespace: ...
            groupId: dev
            rule-type: gw-flow #规则类型必须为gw-flow
            username: nacos
            password: nacos
  1. 在 Nacos 配置中心页面,新增 data-id 为gateway-server-gw-flow-rules 的配置项
[
   {
       "resource":"",//资源名称,网关的 route/自定义API 分组名
       "resourceMode":0,//规则是针对 API Gateway 的 route还是自定义API分组
       "grade":1, //类型 0-线程 1-QPS
       "count":2, //超过2个QPS限流将被限流
       "strategy":0, //限流策略: 0-直接 1-关联 2-链路
       "controlBehavior":0, //控制行为: 0-快速失败 1-WarmUp 2-排队等待
       "intervalSec":1,//统计时间窗口
       "burst":0//应对突发请求额外允许数目
   }
]
  1. 启动微服务,查看Sentinel Dashboard

5.3、限流异常处理

Spring Cloud Gateway,默认的限流处理逻辑是返回默认的流控文本Blocked by Sentinel,返回status code429 Too Many Requests。您可以通过以下Spring配置项来配置限流后的处理策略。

  • spring.cloud.sentinel.scg.fallback.mode:限流处理策略,目前支持跳转redirect和自定义返回response两种策略。
  • spring.cloud.sentinel.scg.fallback.redirect:限流之后的跳转URL,仅在mode=redirect的时候生效。
  • spring.cloud.sentinel.scg.fallback.response-body:限流之后的返回内容,仅在mode=response的时候生效。
  • spring.cloud.sentinel.scg.fallback.response-status:限流之后的返回status code,仅在mode=response的时候生效。
  • 除此之外,您也可以在GatewayCallbackManager上通过setBlockHandler注册函数实现自定义的逻辑处理被限流的请求,对应接口为BlockRequestHandler
  1. 自定义BlockRequestHandler,并配置为bean
@Component
public class SentinelBlockRequestHandler implements BlockRequestHandler {
    @Override
    public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {

        ObjectMapper objectMapper = new ObjectMapper();
        Map<String, Object> map = new HashMap<>();
        map.put("code", 429);
        map.put("message", "访问人数过多");

        String json = "";
        try {
            json = objectMapper.writeValueAsString(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        return ServerResponse
                .status(429)
                .contentType(MediaType.APPLICATION_JSON)
                .body(fromValue(json));
    }
}
  1. yaml中配置使用自定义的BlockRequestHandler
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:9191
      scg:
        fallback:
          # 模式 只要不是response redirect2种,任意的字符串都表示使用自定义Handler
          mode: custom
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值