微服务SpringCloud Alibaba ------(十)Gateway

1. 什么是API Gateway

普遍的定义是这样的

API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。
API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务。

但是大致可以总结出以下几点

服务调用的统一入口
AuthN(Authentication is establishing the your identity.)
AuthZ (Authorization is establishing your privileges.)
监控(请求延迟、异常数、审计日志、访问日志)
高可用
白名单、黑名单
限流
熔断
服务发现
协议支持 (协议转换)

脑图

在这里插入图片描述
以上转载自rianley的博客,链接:https://www.cnblogs.com/rianley/p/11948808.html

2. Spring Cloud Gateway

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

相关概念:

Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
Filter(过滤器):这是org.springframework.cloud.gateway.filter.GatewayFilter的实例,我们可以使用它修改请求和响应。

3. Gateway初体验

3.1. 添加依赖

<!-- gateway依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

3.2. 配置文件

server:
  port: 8088
spring:
  application:
    name: api-gateway
  cloud:
    # gateway配置
    gateway:
      # 路由规则
      routes:
        - id: order_route  # 路由的唯一标示
          uri: http://localhost:8010 # 需要转发的地址
          # 断言规则 用于路由规则的匹配
          predicates:
            # http://localhost:8088/order-serv/order/**会路由到http://localhost:8010/order-serv/order/**
            - Path=/order-serv/**
          filters:
            # 转发之前去掉第一层路径 http://localhost:8010/order-serv/order/** -> http://localhost:8010/order/**
            - StripPrefix=1

3.3. 添加启动类

@SpringBootApplication
public class GatewayApplication {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(GatewayApplication.class, args);
    }
}
访问http://127.0.0.1:8088/order-serv/order/add 会路由至 http://localhost:8010/order/add

3.4. 整合nacos

3.4.1. 添加依赖

<!--nacos服务发现依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

3.4.2. 修改配置文件

spring:
  application:
    name: api-gateway
  cloud:
    # gateway配置
    gateway:
      # 路由规则
      routes:
        - id: order_route  # 路由的唯一标示
          uri: lb://order-service # 需要转发的地址 lb:使用nacos中的本地负载均衡策略 order-service:服务名
          # 断言规则 用于路由规则的匹配
          predicates:
            # http://localhost:8088/order-serv/order/**会路由到http://localhost:8010/order-serv/order/**
            - Path=/order-serv/**
          filters:
            # 转发之前去掉第一层路径 http://localhost:8010/order-serv/order/** -> http://localhost:8010/order/**
            - StripPrefix=1
    # 配置nacos
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        username: nacos
        password: nacos
访问http://127.0.0.1:8088/order-serv/order/add 会路由至nacos中order-service服务的order/add接口

4. 路由断言工厂

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#the-after-route-predicate-factory

作用:当请求gateway的时候,使用断言对请求进行匹配,如果匹配成功路由转发,如果匹配失败就返回404。可以配置多个路由断言工厂
类型:内置,自定义

4.1. 基于Datetime类型的断言工厂

此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory:接收两个日期参数:判断请求日期是否在指定时间段内

	- After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
	- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
	- Between=2017-01-20T17:42:47.789-07:00[America/Denver],2017-01-21T17:42:47.789-07:00[America/Denver]

4.2. 基于Cookie的断言工厂

CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。判断请求cookie是否具有给定名称且值与正则表达式匹配。

	- Cookie=chocolate,ch.

4.3. 基于Header的断言工厂

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。判断请求Header是否具有给定名称且值与正则表达式匹配。

	- Header=X-Request-Id,\d+

4.4. 基于Host的断言工厂

HostRoutePredicateFactory:接收一个参数, 主机名模式。判断请求的Host是否满足匹配规则。

	- Host=**.somehost.org,**.anotherhost.org

4.5. 基于Method请求方法的断言工厂

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否和指定的类型匹配

	- Method=GET,POST

4.6. 基于Path请求路径的断言工厂

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则

	- Path=/red/{segment},/blue/**

4.7. 基于Query请求参数的断言工厂

QueryRoutePredicateFactory:接收两个参数,请求param和正则表达式,判断请求参数中是否具有给定名称且值与正则表达式匹配

	如果有green参数则匹配
	- Query=green
	如果有green参数并且值符合gree.正则表达式则匹配,如:green,greet
	- Query=red,gree.

4.8. 基于远程地址的断言工厂

RemoteAddrRoutePredicateFactory:接收一个IP地址段, 判断请求主机地址是否在地址段中

	- RemoteAddr=192.168.1.1/24 

4.9. 基于路由权重的断言工厂

WeighRoutePredicateFactory:接收一个[组名,权重],然后对于同一个组内的路由按照权重转发

	spring:
	  cloud:
	    gateway:
	      routes:
	      - id: weight_high
	        uri: https://weighthigh.org
	        predicates:
	        - Weight=group1,8
	      - id: weight_low
	        uri: https://weightlow.org
	        predicates:
	        - Weight=group1,2

4.10. 自定义路由断言工厂

自定义路由断言工厂需要继承AbstractRoutePredicateFactory类,重写apply方法的逻辑。在apply方法中可以通过exchange.getRequest()拿到ServerHttpRequest对象,从而可以获取到请求的参数、请求方式、请求头
等信息。

步骤:

  1. 必须是一个spring bean
  2. 必须加上RoutePredicateFactory作为结尾
  3. 必须继承AbstractRoutePredicateFactory
  4. 必须声明静态内部类声明属性来接收配置文件中对应的断言的信息
  5. 需要结合shortcutFieldOrder进行绑定
  6. 通过apply进行逻辑判断 true就是匹配成功 false匹配失败

4.10.1. 添加配置

          # 断言规则 用于路由规则的匹配
          predicates:
            # http://localhost:8088/order-serv/order/**会路由到http://localhost:8010/order-serv/order/**
            - Path=/order-serv/**
            - CheckAuth=xu # 用于自定义路由断言工厂

4.10.2. 添加代码

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
    
    

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

    public List<String> shortcutFieldOrder() {
    
    
        return Arrays.asList("name");
    }

    public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) {
    
    
        return new GatewayPredicate() {
    
    
            public boolean test(ServerWebExchange exchange) {
    
    

                if(config.getName().equals("xu")){
    
    
                    return true;
                }else{
    
    
                    return false;
                }
            }
        };
    }

    // 用于接收配置文件中断言信息
    @Validated
    public static class Config {
    
    
        private String name;

        public void setName(String name) {
    
    
            this.name = name;
        }

        public String getName() {
    
    
            return name;
        }
    }
}
此时,当- CheckAuth=xu时可以正常路由,如果修改等号右边的值,则无法路由

5. 过滤器工厂

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

5.1. AddRequestHeader

可以为原始请求添加Header

spring:
 cloud:
   gateway:
     routes:
     - id: add_request_header_route
       uri: https://example.org
       filters:
       - AddRequestHeader=X-Request-red, blue
spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        predicates:
        - Path=/red/{
    
    segment}
        filters:
        - AddRequestHeader=X-Request-Red, Blue-{
    
    segment}

5.2. AddRequestParameter

可以为原始请求添加请求参数

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        - AddRequestParameter=red, blue
spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        predicates:
        - Host: {
    
    segment}.myhost.org
        filters:
        - AddRequestParameter=foo, bar-{
    
    segment}

5.3. AddResponseHeader

可以为原始响应添加Header

spring:
  cloud:
    gateway:
      routes:
      - id: add_response_header_route
        uri: https://example.org
        filters:
        - AddResponseHeader=X-Response-Red, Blue
spring:
  cloud:
    gateway:
      routes:
      - id: add_response_header_route
        uri: https://example.org
        predicates:
        - Host: {
    
    segment}.myhost.org
        filters:
        - AddResponseHeader=foo, bar-{
    
    segment}

5.4. DedupeResponseHeader

可以剔除响应头中重复的值

接受一个name参数(header的key)和一个可选strategy参数。有多个name空格分割。
strategy:

  • RETAIN_FIRST : 默认选项。 保留第一个值。
  • RETAIN_LAST : 保留最后一个值。
  • RETAIN_UNIQUE : 按原数据顺序保留唯一值。
    如果想具体的了解该过滤器的作用可以查看源码中注释和示例。

例如:后端和API网关都添加了CORS Header,所以消费者得到的响应头是:

Access-Control-Allow-Credentials: true, false
Access-Control-Allow-Origin: https://musk.mars, https://musk.mars
# 示例一结果为:
# Access-Control-Allow-Credentials: true
# Access-Control-Allow-Origin: https://musk.mars
spring:
  cloud:
    gateway:
      routes:
      - id: dedupe_response_header_route
        uri: https://example.org
        filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
# 示例二结果为:
# Access-Control-Allow-Credentials: false
# Access-Control-Allow-Origin: https://musk.mars
spring:
  cloud:
    gateway:
      routes:
      - id: dedupe_response_header_route
        uri: https://example.org
        filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_LAST
# 示例三结果为:
# Access-Control-Allow-Credentials: true, false
# Access-Control-Allow-Origin: https://musk.mars
spring:
  cloud:
    gateway:
      routes:
      - id: dedupe_response_header_route
        uri: https://example.org
        filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE

5.5. 更多的 GatewayFilterFactory

在代码中查看 GatewayFilterFactory 的子类即可。

5.6. 全局过滤器

全局过滤器针对所有路由请求,而局部过滤器针对某个路由。
全局过滤器一旦定义就会立即生效,而局部过滤器需要配置
GlobalFilter接口和GatewayFilter有一样定义的接口,只不过GlobalFilter会作用于所有路由

参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#global-filters

自带的全局过滤器
在这里插入图片描述
一个打印请求地址的全局过滤器

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 全局过滤器打印日志
 * @Author Xxx
 * @Date 2021/12/9 17:40
 * @Version 1.0
 */
@Component
public class LogFilter implements GlobalFilter {
    
    

    Logger log = LoggerFactory.getLogger(this.getClass());

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
        log.info(exchange.getRequest().getPath().value());
        return chain.filter(exchange);
    }
}

6. Reactor Netty 访问日志

参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#reactor-netty-access-logs

要启用 Reactor Netty 访问日志,请设置-Dreactor.netty.http.server.accessLogEnabled=true

它必须是 Java 系统属性,而不是 Spring Boot 属性。

您可以将日志记录系统配置为具有单独的访问日志文件。以下示例创建一个 Logback 配置:

<appender name="accessLog" class="ch.qos.logback.core.FileAppender">
    <file>access_log.log</file>
    <encoder>
        <pattern>%msg%n</pattern>
    </encoder>
</appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="accessLog" />
</appender>

<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
    <appender-ref ref="async"/>
</logger>

6.1. idea中启用访问日志

在这里插入图片描述
在这里插入图片描述

效果

2021-12-09 17:51:20.414  INFO 53348 --- [ctor-http-nio-2] reactor.netty.http.server.AccessLog      : 127.0.0.1 - - [09/Dec/2021:17:51:19 +0800] "GET /order/auth?auth=xu HTTP/1.1" 200 7 8088 698 ms

7. Gateway跨域配置(CORS Configuration)

参考 https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration

7.1. 配置文件实现

#允许来自docs.spring.io所有GET请求路径的请求的 CORS 请求。
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 允许跨域访问的资源
            allowedOrigins: "https://docs.spring.io" #跨域允许来源
            allowedMethods: # 允许的请求
            - GET
        add-to-simple-url-handler-mapping: true

7.2. 代码实现


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

/**
 * 跨域配置
 * @Author Xxx
 * @Date 2021/12/9 18:06
 * @Version 1.0
 */
@Configuration
public class CorsConfig {
    
    
    @Bean
    public CorsWebFilter corsFilter(){
    
    
        // 开发环境
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*"); // 允许的请求方法
        config.addAllowedOrigin("*"); // 允许的来源
        config.addAllowedHeader("*"); // 允许的请求头参数

        // 允许访问的资源
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

8. Gateway整合Sentinel

参考:https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81
但是这个文档已经比较老,由于我们现在用的SpringCloud和SpringBoot依赖比较新,所以官方文档中的使用方式并不太适用。

8.1 添加依赖

<!--sentinel整合gateway依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!--sentinel依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

8.2. 添加配置项

spring:
  cloud:
    sentinel:
      transport:
        dashboard: 127.0.0.1:8858
至此完成了整合

8.3. 自定义逻辑处理被限流的请求异常

将错误直接抛向前端的方式很不友好,我们可以通过以下方式来处理


import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author Xxx
 * @Date 2021/12/13 15:25
 * @Version 1.0
 */
@Configuration
public class GatewayConfig {
    
    

    @PostConstruct
    public void init(){
    
    
        // 自定义逻辑处理被限流的请求异常
        BlockRequestHandler blockRequestHandler = (serverWebExchange, throwable) -> {
    
    

            // 可以通过throwable来判断是流控异常还是熔断异常
            Map<String, Object> map = new HashMap<>();
            map.put("code", HttpStatus.TOO_MANY_REQUESTS.value());
            map.put("msg", "限流了!");

            return ServerResponse.status(HttpStatus.OK)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(map));
        };

        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
}

处理前

在这里插入图片描述

处理后

在这里插入图片描述

9. 代码

https://download.csdn.net/download/qq_42017523/62414557

猜你喜欢

转载自blog.csdn.net/qq_42017523/article/details/121773464