使用SpringCloud实现Java分布式开发【part-5】:Spring Cloud Gateway网关的介绍及使用、路由前缀处理、局部过滤器和全局过滤器、负载均衡和熔断配置、跨域配置

SpringCloud简介

Spring Cloud是Spring旗下的项目之一
Spring Cloud并不是一个组件 而是许多组件的集合
其将当下非常流行的一些技术整合到了一起 实现了多个分布式开发中的重要功能
协调了分布式环境中各个系统 并且为各类服务提供模板性的配置

其主要涉及的组件包括:

  • Eureka:注册中心
  • Zuul或Spring Cloud Gateway:服务网关
  • Ribbon:负载均衡
  • Feign:服务调用
  • Hystrix或Resilience4j:熔断器

【在本篇中 将介绍Spring Cloud Gateway网关】

Spring Cloud Gateway网关

在多个微服务之间为了避免直接暴露地址 增强服务的安全性 需要有一个网关 用于将客户的请求转发到不同的微服务

Spring Cloud Gateway是Spring基于Spring 5.0、 Spring Boot 2.0、Project Reactor等技术开发的网关服务 基于了Filter链提供网关基本功能:安全 监控/埋点 限流等 为微服务架构提供了简单 有效且统一的API路由管理方式

Spring Cloud Gateway是替代Netflix Zuul的一套解决方案 该组件的核心是一系列的过滤器 通过这些过滤器可以将客户端发送的请求转发(也称路由)到对应的微服务

Spring Cloud Gateway是加在整个微服务最前沿的防火墙和代理器 通过隐藏微服务节点的IP端口信息从而加强安全保护
其本身也是一个微服务 因此 需要注册到Eureka服务注册中心

网关的核心功能是:过滤路由
使用SpringCloud实现Java分布式开发【part-5】:Spring Cloud Gateway网关的介绍及使用、路由前缀处理、局部过滤器和全局过滤器、负载均衡和熔断配置、跨域配置_第1张图片
不管是来自于PC端或移动端的请求 还是服务间的内部调用 一切对服务的请求都先经过网关 然后再由网关来实现鉴权 动态路由等操作
Gateway就是服务的统一入口 相当于一个大门

一、核心概念:

  • 路由(route)
    路由信息的组成:由一个ID 一个目的URL 一组断言工厂 一组Filter组成
    若路由断言为真 则说明请求URL和配置路由匹配
  • 断言(Predicate)
    Spring Cloud Gateway的断言函数允许开发者去定义匹配来自于Http Request中的任何信息 比如请求头和参数
    简单来说 断言就是路由转发的判断条件
    目前SpringCloud Gateway支持多种方式 常见如:Path、Query、Method、Header等
  • 过滤器(Filter)
    这里的过滤器就是一个标准的Spring WebFilter过滤器
    Spring Cloud Gateway中的Filter分为两种类型的Filter 分别是Gateway Filter(当前请求的过滤器)和Global Filter(全局过滤器)
    过滤器Filter将会对请求和响应进行修改处理

二、使用步骤

添加Spring Cloud Gateway的依赖:

<dependency>
	<groupId>org.springframework.cloudgroupId>
	<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>

然后在启动类中用@EnableDiscoveryClient注解将其注册到Eureka客户端:

@SpringBootApplication
//  开启Eureka客户端的发现功能
@EnableDiscoveryClient
public class GatewayApplication {

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

在配置文件application.yml中配置路由规则:

server:
  port: 10010
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
          # 路由id 可任意写
        - id: user-service-route
          # 代理的服务地址(注意:是uri不是url)
          # uri: http://127.0.0.1:9091
          # 路由断言 匹配映射路径的条件(注意:Path的P要大写)
          predicates:
            - Path=/user/**

当然 虽然可以直接写服务地址 更推荐面向服务的路由
一个原因是地址写死不方便后序修改维护 而且面向服务的路由可以自动进行Ribbon负载均衡
只需要将ip和端口改为服务名即可(当然 这个服务必须首先得存在)

就像这样:

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
          # 路由id 可任意写
        - id: user-service-route
          # 代理的服务名(注意:是uri不是url)
          # (lb:LoadBalancer负载均衡 底层使用Ribbon进行负载均衡)
          uri: lb://user-service
          # 路由断言 匹配映射路径的条件(注意:Path的P要大写)
          predicates:
            - Path=/user/**

需要注意的是 前面要加上lb(LoadBalancer)
当路由配置中uri所用的协议为lb时 Gateway将自动使用LoadBalancerClient将后面的服务名通过Eureka解析为实际的主机和端口 并进行Ribbon负载均衡

测试成功:
使用SpringCloud实现Java分布式开发【part-5】:Spring Cloud Gateway网关的介绍及使用、路由前缀处理、局部过滤器和全局过滤器、负载均衡和熔断配置、跨域配置_第2张图片

三、路由前缀处理

客户端访问的请求地址和微服务的服务地址不一致的时候 可以通过配置路径过滤器来实现映射路径中地址的添加或去除

1、前缀添加:

通过配置路由的过滤器(filters)的PrefixPath属性实现:

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/**
          filters:
            # 添加请求路径的前缀(prefixpath)
            # 若要添加多个只需继续添加即可 例如PrefixPath=/user/aa/bb/cc
            - PrefixPath=/user

测试成功:
使用SpringCloud实现Java分布式开发【part-5】:Spring Cloud Gateway网关的介绍及使用、路由前缀处理、局部过滤器和全局过滤器、负载均衡和熔断配置、跨域配置_第3张图片

2、前缀去除

StripPrefix属性指定在路由路径中要去除的前缀的个数:

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/aa/bb/cc/user/**
          filters:
            # 去除的路径前缀的个数(strip)
            - StripPrefix=3

测试成功:
使用SpringCloud实现Java分布式开发【part-5】:Spring Cloud Gateway网关的介绍及使用、路由前缀处理、局部过滤器和全局过滤器、负载均衡和熔断配置、跨域配置_第4张图片

四、SpringCloudGateway过滤器

Gateway作为网关的其中一个重要功能就是实现请求的鉴权 而这个操作往往是通过网关提供的过滤器来实现的

常见过滤器:

过滤器名称 功能
AddRequestHeader 对匹配上的请求添加Header
AddRequestParameters 对匹配上的请求的路由添加参数
AddResponseHeader 对从网关返回的响应添加Header
PrefixPath 对匹配上的请求路径添加前缀
PrefixPath 对匹配上的请求路径去除前缀

使用的时候 直接在配置文件中的default-filters进行配置即可:
default-filters是默认过滤器 也就是全局过滤器 会对所有的路由都生效 而不仅仅只针对某个路由

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/user/**
      # 默认过滤器 对所有的路由都生效 而不仅仅只针对某个路由
      default-filters:
        # 响应头过滤器
        # 对输出的响应设置其头部属性名称为X-Response-MyName 值为piconjo 逗号分隔属性名和属性值
        # 若有多个参数则重写一行设置不同的参数
        - AddResponseHeader=X-Response-MyName,piconjo
        - AddResponseHeader=X-Response-MyLocation,zhejiang

测试成功:
使用SpringCloud实现Java分布式开发【part-5】:Spring Cloud Gateway网关的介绍及使用、路由前缀处理、局部过滤器和全局过滤器、负载均衡和熔断配置、跨域配置_第5张图片

1、过滤器的类型

Gateway在实现方式上分为两种过滤器:

  • 1、局部过滤器:通过spring.cloud.gateway.routes.filters配置在具体路由下 只作用在当前路由

    自带的过滤器都可以配置或者自定义按照自带过滤器的方式
    若配置在spring.cloud.gateway.default-filters上 则会对所有路由生效 也算是全局的过滤器
    但是这些过滤器的实现上都是要实现GatewayFilterFactory接口
  • 2、全局过滤器:不需要在配置文件中配置 作用在所有的路由上 实现GlobalFilter接口即可
2、过滤器的执行生命周期

Spring Cloud Gateway的Filter的生命周期类似于Spring MVC的拦截器
共有两个 分别是prepost
pre和post分别会在请求被执行前调用和被执行后调用
使用SpringCloud实现Java分布式开发【part-5】:Spring Cloud Gateway网关的介绍及使用、路由前缀处理、局部过滤器和全局过滤器、负载均衡和熔断配置、跨域配置_第6张图片
pre和post可以通过过滤器的GatewayFilterChain执行filter方法来实现

常见应用场景:

  • 请求鉴权:GatewayFilterChain在执行filter方法前 若发现没有访问权限 则直接返回空
  • 异常处理:GatewayFilterChain在执行filter方法后 若发现异常 则记录异常并返回
  • 服务调用时长统计: GatewayFilterChain执行filter方法前后根据时间戳来计算并统计
3、自定义局部过滤器

首先在配置文件中配置过滤器类的类名和要过滤的属性名:

spring:
  application:
    name: api-gateway
  cloud:
    gateway:
      routes:
        - id: user-service-route
          uri: lb://user-service
          predicates:
            - Path=/user/**
          filters:
            # 自定义的过滤器 属性名就是过滤器类的类名的GatewayFilterFactory前面的部分 属性值限定了要过滤的参数名为name
            - MyParam=name

在上面的案例中 已经指定了自定义的过滤器类的类名前面为MyParam 那么后面固定的是GatewayFilterFactory 必须是这种格式 否则会报错

在过滤器类MyParamGatewayFilterFactory.java中编写过滤逻辑:

有几个关键点需要注意的是:

  • 1、过滤器要成为Spring的组件 因此类上必须添加@Component注解
  • 2、自定义的过滤器类要继承AbstractGatewayFilterFactory抽象类 泛型为当前自定义过滤器类里面的内部类
  • 3、所以 也就是说 在该过滤器类里面还需要有一个内部类 这个内部类里的属性名可以随意取 其代表的就是该过滤器要过滤的属性 比如name或者age或者id之类的 然后还需要有getter和setter方法
  • 4、还要规定一个常量作为参数名 需要注意的是 该参数名必须与内部类中的参数的名称一致
  • 5、在该自定义的过滤器类中 要有两个方法:MyParamGatewayFilterFactory和shortcutFieldOrder
    其中 MyParamGatewayFilterFactory()方法调用父类的方法 其值就是内部类的class
    shortcutFieldOrder()方法返回的是参数列表
  • 6、最后 要实现接口里的apply方法 入参就是自己写的内部类 在该方法内写具体的过滤逻辑
// 过滤器需要成为Spring的组件
@Component
// 需要继承AbstractGatewayFilterFactory 然后以内部类作为泛型
public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {

    // 属性值必须与内部类中的参数的名称一致
    static final String PARAM_NAME="param";

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

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

    // 内部类
    public static class Config
    {
        // 对应的是在配置过滤器的时候所指定的参数名(这个参数名可以是name也可以是age等等) 是随意取的
        private String param;
        public String getParam() {
            return param;
        }
        public void setParam(String param) {
            this.param = param;
        }
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                // 获取请求
                ServerHttpRequest request = exchange.getRequest();
                // 判断是否包含指定的参数名(即自己写的内部类里配置的参数名param)
                // 若包含 则进一步操作 注:这里的config.param就相当于设置的请求参数名 可以是name也可以是age等等
                if (request.getQueryParams().containsKey(config.param))
                {
                    // 根据属性名获取对应的属性值并且输出
                    request.getQueryParams().get(config.param)
                            .forEach(value -> System.out.println("自定义的局部过滤器已获取到属性名:"+config.param+",属性值:"+value));
                }
                return chain.filter(exchange);
            }
        };
    }
}

测试成功:
在这里插入图片描述
在这里插入图片描述

4、自定义全局过滤器

配置全局过滤器比较方便 直接写一个过滤器即可

这个自定义的全局过滤器必须实现GlobalFilter接口
若要修改多个过滤器的执行顺序的话 还可实现Ordered接口

直接在filter()方法内写过滤器的逻辑即可

// 过滤器需要成为Spring的组件
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {

    // 过滤器逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("全局过滤器执行...");
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        if (StringUtils.isBlank(token))
        {
            // 设置状态码为未授权
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            // 设为已完成 不再继续执行
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    // 过滤器执行顺序 return的值越小越先执行
    @Override
    public int getOrder() {
        return 1;
    }
}

测试成功:
使用SpringCloud实现Java分布式开发【part-5】:Spring Cloud Gateway网关的介绍及使用、路由前缀处理、局部过滤器和全局过滤器、负载均衡和熔断配置、跨域配置_第7张图片
使用SpringCloud实现Java分布式开发【part-5】:Spring Cloud Gateway网关的介绍及使用、路由前缀处理、局部过滤器和全局过滤器、负载均衡和熔断配置、跨域配置_第8张图片

五、Gateway负载均衡和熔断配置

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 2000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 0

六、Gateway的跨域配置

跨域:在来自前端的请求访问中 若访问的地址与当前服务器的域名 ip或端口号不一致 则称为跨域请求
若不解决则不能获取到对应地址的返回结果

同样在配置文件中进行配置即可:

spring:
  cloud:
    gateway:
      globalcors:
        corsConfigurations:
          # '[/**]' 表示对于所有访问到网关服务器的请求地址
          '[/**]':
            # 允许的源站:* # 或是下面的写法都可以 *表示全部
            allowedOrigins:
            - "http://www.baidu.com"
            - "http://localhost:8080"
            # 允许的请求方式
            allowedMethods:
            - GET

小结:Gateway和Feign的区别

  • Gateway作为整个应用的流量入口 接收所有的请求 例如PC端或是移动端等 并且将不同的请求转发至不同的微服务模块 其作用类似于Nginx
    大部分情况下用作权限鉴定和服务端流量控制
  • Feign则是将当前微服务的部分服务接口暴露出来 并且主要用于各个微服务之间的服务调用

你可能感兴趣的:(框架,过滤器,网关,spring,java,gateway)