SpringCloud学习7(Spring Cloud Alibaba)服务网关GateWay、网关(局部、全局)过滤器

前面介绍的微服务方式,不适用于微服务多和复杂的场景。前面的架构存在诸多问题:

  • 客户端多次请求不同微服务,增加客户端代码和配置编写复杂性
  • 认证复杂,每个服务都需要独立认证
  • 微服务做集群的情况下,客户端并没有负责负责均衡的功能

以上问题可以借助API网关来解决。

网关概念

所谓的API网关,就是系统的统一入口,封装了应用程序内部结构,为客户端提供统一服务,一些与业务本身无关的公共逻辑可以在这里实现,如认证授权、监控、路由转发等。
在这里插入图片描述

网关的作用:

  • 请求分发
  • 负载均衡
  • 过滤拦截
  • 网络隔离

常见的网关介绍

  • Nginx + Lua :nginx支持lua脚本。使用nginx负载均衡可以实现对api服务器的负载均衡高可用。
  • Kong : 基于Nginx+Lua开发,高性能、稳定,插件,开箱即用。仅支持http协议。二开产品,扩展困难。缺乏易用的管控、配置方式。
  • Zuul :Netflix开源网关,功能丰富,易于二开。但缺乏管控,无法动态配置,依赖组件较多。处理http请求依赖web容器,性能不及nginx和Spring Cloud Gateway。底层是Servlet。
  • Gateway :Spring为替换Zuul开发的网关服务。

Gateway简介

SpringCloud Gateway是一个旨在为微服务架构提供一种简单有效的统一api路由管理方式,目标替代zuul网关。还提供了统一的对方式,且基于filter链的方式提供了网关基本的功能。eg:安全、监控、限流。

  • 优点:
    • 性能强大,是第一代网关zuul的1.6倍
    • 功能强大:内置实用功能,eg转发、监控、限流等
    • 设计优雅、容易扩展
  • 缺点:
    • 其实现依赖netty和webflux,而非传统的servlet模型,学习成本高
    • 不能部署在tomcat、jetty等servlet容器,只可以jar包方式运行
    • 需要SpringBoot2.0以上支持

微服务集成Gateway

流程:

  1. 请求进入网关,判断URL是否符合路由predicates设置的规则。如果符合就使用该路由进行处理。
  2. 根据路由filters配置,执行响应逻辑处理,对url进行改造。
  3. 将处理完成的URL拼接到uri,得到完整的地址,然后进行跳转。
    在这里插入图片描述

案例代码

创建cloud2-gateway项目,并导入依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2_parent</artifactId>
        <groupId>com.hx</groupId>
        <version>1.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud2-gateway</artifactId>
    <name>cloud2-gateway</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

编写配置文件application.yml

server:
  port: 8871
spring:
  application:
    name: cloud2-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true   # 让gateway可以发现nacos中的服务

编写启动类,将该网关配置为nacos客户端

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

测试:访问带上服务名称即可 http://localhost:8871/cloud2-order-server/

上面这种方式,由于直接暴露服务名称给外部客户端,我们希望隐藏伪装,可以通过以下配置

spring:
  cloud:
    gateway:
      routes:
        - id: cloud2-order    # 路由id,唯一
          uri: http://localhost:8882   # 跳转地址,固定地址
          predicates:
            - Path=/od/**   # 断言,路径匹配处理条件
          filters:
            - StripPrefix=1    # 网关局部过滤器:对通过网关请求进行拦截与加工。不同拦截器有不同逻辑。
        - id: cloud2-product
          uri: lb://cloud2-product-server   # 跳转地址:负载均衡
          predicates:
            - Path=/prod/**   # 拦截规则,拦截以/prod开头的路径
          filters:
            - StripPrefix=1   # StripPrefix表示过滤url第一节

测试:访问带上服务名称即可 http://localhost:8871/od/

过滤器Filter

过滤器就是在请求传递过程中,对请求与响应做一些手脚。
在Gateway中,filter的生命周期只有俩个:pre和post

  • pre:这种过滤器在请求被路由之前调用,我们可以利用这个过滤器来实现身份验证、在集群中选择请求的微服务,记录调试信息等。
  • post:这种过滤器在路由到微服务以后执行。这种过滤器可以用来为响应添加标准的http header、收集统计信息、将响应从微服务发送给客户端等。

在gateway中,filter的作用范围:

  • GatewayFilter : 应用到单个路由或者一个分组的路由上,
  • Global Filter :应用到所有路由上。

在这里插入图片描述

局部过滤器

不带参数的案例代码

编写过滤器类,过滤器工厂类将过滤器注册到网关

@Component
public class TimeGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    
    
    @Order(0)
    class TimeGatewayFilter implements GatewayFilter{
    
    
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
            long begin = System.currentTimeMillis();
            System.out.println("开始时间 = " + begin);
            return chain.filter(exchange).then(Mono.fromRunnable(()->{
    
    
                long end = System.currentTimeMillis();
                System.out.println("结束时间 = " + end);
                System.out.println("消耗时间 = " + (end - begin) + " 毫秒");
            }));
        }
    }

    /**
     * 将自定义的局部filter注册到网关
     */
    @Override
    public GatewayFilter apply(Object config) {
    
    
        return new TimeGatewayFilter();
    }
}

编写配置

spring:
  cloud:
    gateway:
      routes:
        - id: cloud2-order    # 路由id,唯一
          uri: http://localhost:8882   # 跳转地址,固定地址
          predicates:
            - Path=/od/**   # 断言,路径匹配处理条件
          filters:
            - Time

带参数的案例代码

修改配置

spring:
  cloud:
    gateway:
      routes:
        - id: cloud2-order    # 路由id,唯一
          uri: http://localhost:8882   # 跳转地址,固定地址
          predicates:
            - Path=/od/**   # 断言,路径匹配处理条件
          filters:
            - Time=111,2222

修改TimeGatewayFilterFactory类,创建内部Config类

@Component
public class TimeGatewayFilterFactory extends AbstractGatewayFilterFactory<TimeGatewayFilterFactory.Config> {
    
    

    // 指定获取配置参数之后,要封装成什么对象
    public TimeGatewayFilterFactory() {
    
    
        super(Config.class);
    }

    // 将数据添加到哪个属性上面
    @Override
    public List<String> shortcutFieldOrder() {
    
    
        return Arrays.asList("val1","val2");
    }

    @Data
    public static class Config {
    
    
        private int val1;
        private String val2;
    }

    /**
     * 将自定义的局部filter注册到网关
     */
    @Override
    public GatewayFilter apply(TimeGatewayFilterFactory.Config config) {
    
    
        return new TimeGatewayFilter(config);
    }

    @Order(0)
    @NoArgsConstructor
    class TimeGatewayFilter implements GatewayFilter {
    
    
        private TimeGatewayFilterFactory.Config timeGatewayParam;

        public TimeGatewayFilter(TimeGatewayFilterFactory.Config config) {
    
    
            this.timeGatewayParam = config;
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
            long begin = System.currentTimeMillis();
            System.out.println("开始时间 = " + begin);
            System.out.println("timeGatewayParam.getVal1() = " + timeGatewayParam.getVal1());
            System.out.println("timeGatewayParam.val2  = " + timeGatewayParam.getVal2());
            return chain.filter(exchange).then(Mono.fromRunnable(() -> {
    
    
                long end = System.currentTimeMillis();
                System.out.println("结束时间 = " + end);
                System.out.println("消耗时间 = " + (end - begin) + " 毫秒");
            }));
        }
    }
}

全局过滤器

案例代码 - 鉴权

package com.hx.gateway;

import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;

/**
 * @author Huathy
 * @date 2023-04-05 16:51
 * @description
 */
@Component
public class AuthGlobalFilter implements GlobalFilter {
    
    
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    
    
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        if(StringUtils.isBlank(token)){
    
    
            System.out.println("该请求没有登录");
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
//            return response.setComplete();  // 仅返回状态码
            String res = "{\"code\":401,\"msg\":\"未登录\"}";
            DataBuffer wrap = response.bufferFactory().wrap(res.getBytes(StandardCharsets.UTF_8));
            return response.writeWith(Mono.just(wrap));
        }
        return chain.filter(exchange);
    }
}

网关集成Sentinel实现限流

网关是所有请求的公共入口,所以可以在网关进行限流,而限流的方式也很多,我们本次采用Sentinel组件来实现网关限流。Sentinel支持对SpringCloud Gateway、Zuul等主流网关进行限流。
Sentinel提供了SpringCloudGateway的适配模块,可以提供俩种资源维度的限流:

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

API分组

Sentinel中支持按照API分组进行限流,就是我们可以按照特定规则进行限流。
在控制台页面中提供了三种方式的api分组管理

  • 精准匹配
  • 前缀匹配
  • 正则匹配

集成案例

引入依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
<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>

编写配置

spring:
  cloud:
    sentinel:
      transport:
        port: 18871
        dashboard: localhost:8080 

精确匹配

在这里插入图片描述在这里插入图片描述
测试地址:http://localhost:8871/od/gs/t1?token=1

前缀匹配

/*表示一级路径,/**表示多级路径
在这里插入图片描述

正则匹配

在这里插入图片描述

网关集成Sentinel返回值修改

添加以下配置即可

@Component
public class GatewayConfig {
    
    
    @PostConstruct
    public void initBlockHandlers() {
    
    
        BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
    
    
            @Override
            public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
    
    
                Map<String, Object> map = new HashMap<>();
                map.put("code", 500101);
                map.put("msg", "接口限流了");
                return ServerResponse.status(HttpStatus.OK)
                        .contentType(MediaType.APPLICATION_JSON)
                        .body(BodyInserters.fromValue(map));
            }
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
}

总结

Gateway执行流程

  1. GatewayClient向SpringCloudGateway发送请求
  2. 请求会被HttpWebHandlerAdapter进行组装为网关上下文(GatewayContext)
  3. 然后GatewayContext转递到DispatcherHandler,负责分发请求给RoutePredicateHandlerMapping
  4. RoutePredicateHandlerMapping 负责路由查找,并根据路由断言,判断路由是否可用。
  5. 如果断言成功,由FilteringWebHandler创建过滤器链并调用
  6. 通过特定于请求的Filter链运行请求,Filter被虚线分割的原因是Filter可以在发送代理请求之前(pre)和之后(post)运行逻辑。
  7. 执行所有pre过滤器逻辑。进行代理请求,发出请求后。将运行post过滤器逻辑。
  8. 处理完毕,将Response返回给Gateway客户端。

Gateway过滤器

  • pre过滤器:做参数校验、权限校验、流量监控、日志输出、协议转换等
  • post过滤器:响应内容、响应头修改、日志输出、流量监控

核心思想

路由转发 + 执行过滤器链

猜你喜欢

转载自blog.csdn.net/qq_40366738/article/details/129968609