SpringCloud服务网关Gateway

  • Post author:
  • Post category:其他

Gateway

pring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。


工作原理

客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。该处理程序通过特定于请求的过滤器链运行请求。筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。所有“前置”过滤器逻辑均被执行。然后发出代理请求。发出代理请求后,将运行“后”过滤器逻辑。

在这里插入图片描述

引入POM文件

			<!--    注意,依赖中一定不要包含spring-cloud-starter-web依赖    -->
			<dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>

			<!-- consul作注册中心 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-consul-discovery</artifactId>
            </dependency>

			<!-- 健康监测 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>

            <!-- hystrix -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>

			<!-- redis限流使用 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>

项目目录

在这里插入图片描述

  • 新建Example(8080)和Example2(8081)模拟两个服务。
  • Example的Controller内容:
@RestController
public class Controller {

    @GetMapping("/a")
    public String exampleController1(){
        return "example - a";
    }

    @GetMapping("/aaa")
    public String exampleController2(){
        return "example - aaa";
    }

    @GetMapping("/aaa/a")
    public String exampleController3(){
        return "example - aaa - a";
    }
}
  • Example2的Controller内容:
@RestController
public class Example2Controller {

    @GetMapping
    public String controller(@ModelAttribute Model model) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "example2 GET " + model.toString();
    }

    @PostMapping
    public String controllerPost(@RequestBody Model model) {
        return "example2 POST " + model.toString();
    }

    @GetMapping("/a")
    public String controllerA() {
        return "example2 - a";
    }

    @GetMapping("/a/b")
    public String controllerAB() {
        return "example2 - a - b";
    }

    @GetMapping("/b")
    public String controllerB() {
        return "example2 - b";
    }

    @GetMapping("/example2/c")
    public String controllerC(String name) {
        return "example2 - c " + name;
    }
}
  • Example和Example2分别连接到Consul注册中心(省略连接配置文件)结果如图:
    在这里插入图片描述

Gateway(8082)项目

  • 路由:路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配。

  • 断言:Java8中的断言函数。Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。

  • 过滤器:一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。

  • 匹配规则:一组断言(predicates)全部满足会进行filters过滤结果转发到uri。

简单路由

spring:
  application:
    name: gateway
  cloud:
    consul:
      enabled: true
      host: localhost
      port: 8500
      discovery:
        health-check-interval: 10s
        # 是否检查自己的心跳,本地为false,方便测试
        register-health-check: true
        instance-id: ${spring.application.name}:${server.port}
        register: true
        prefer-ip-address: true
#        health-check-critical-timeout: 30s
    gateway:
      #      loadbalancer:
      #        # 当找不到对应实例的是否,是否http错误码为404,如果为false,则为503
      #        use404: true

      #      discovery:
      #        locator:
      #          # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
      #          enabled: true
		
      routes:
        - id: example1
          uri: lb://example1
          predicates:
            - Path=/aaa/**

输入:http://localhost:8082/aaa/a 则路由到:http://localhost:8080/aaa/a,输出:example – aaa – a

截取请求

spring:
  cloud:
    gateway:
      routes:
        - id: example1
          uri: lb://exampleTwo
          predicates:
            - Path=/example1/aaa/**
          filters:
          	# 截取路径位数
            - StripPrefix=2

输入:http://localhost:8082/example1/aaa/a 则路由到:http://localhost:8081/a,输出:example2 – a

传入指定参数

spring:
  cloud:
    gateway:
      routes:
        - id: example2
          uri: lb://exampleTwo
          predicates:
            - Path=/example2
          filters:
            # 截取路径位数
            - StripPrefix=1
            # 添加默认参数
            - AddRequestParameter=name, Param
            - AddRequestParameter=age, 1

输入:http://localhost:8082/example2 则路由到:http://localhost:8081,输出:example2 GET Model{name=‘Param’, age=1}

熔断

spring:
  cloud:
    gateway:
      ### 默认filters,全局生效
      default-filters:
        - name: defuaultHystrix
          args:
            name: defaultFallbackcmd
              #调用网关/fallback的controller方法
              fallbackUri: forward:/fallback
      routes:
        - id: example2
          uri: lb://exampleTwo
          predicates:
            - Path=/example2
          filters:
            # 截取路径位数
            - StripPrefix=1
            # 添加默认参数
            - AddRequestParameter=name, Param
            - AddRequestParameter=age, 1
            ## 熔断
            - name: Hystrix
              args:
                name: fallbackcmd
                ### fallback 时调用的方法 http://localhost:8001/fallback
                #调用网关/fallback的controller方法
                fallbackUri: forward:/fallback

hystrix:
  command:
    default: # 如果不是默认,可以写成name的值: fallbackcmd
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000 #1s

输入:http://localhost:8082/example2 则路由到:http://localhost:8081,由于目标Controller方法睡眠1s所以触发熔断机制,跳转熔断连接:http://localhost:8082/fallback,输出:Hystrix。把熔断时间调大即可正常访问。

限流

确保redis在 3 以上的版本!!!

新建配置类:

@Configuration
public class UriKeyResolver implements KeyResolver {
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getURI().getPath());
    }
}
spring:
  cloud:
    gateway:
      routes:
        - id: example2
          uri: lb://exampleTwo
          predicates:
            - Path=/example2
          filters:
            # 截取路径位数
            - StripPrefix=1
            # 添加默认参数
            - AddRequestParameter=name, Param
            - AddRequestParameter=age, 1
            ## 限流
            - name: RequestRateLimiter
              args:
               ### 限流过滤器的 Bean 名称
               key-resolver: '#{@uriKeyResolver}'
               ### 希望允许用户每秒处理多少个请求
               redis-rate-limiter.replenishRate: 1
               ### 用户允许在一秒钟内完成的最大请求数
               redis-rate-limiter.burstCapacity: 3

输入:http://localhost:8082/example2 则路由到:http://localhost:8081,访问正常输出:example2 GET Model{name=‘Param’, age=1}。频繁刷新则触发限流措施,页面显示:429

其它断言写法

  • Header 属性匹配

如果请求头具有名为X-Request-Id其值与\d+正则表达式匹配的标头(即,其值为一个或多个数字),则此路由匹配。

predicates:
	- Header=X-Request-Id, \d+
  • Host 属性匹配

如果请求具有这种路由匹配Host用的头值www.somehost.org或beta.somehost.org或www.anotherhost.org。

predicates:
    - Host=**.somehost.org,**.anotherhost.org
  • Method 属性匹配

匹配GET请求

predicates:
    - Method=GET
  • 路由匹配

根据路由策略匹配

predicates:
    - Path=/red/{segment},/blue/{segment}
  • 根据参数匹配

如果请求包含green查询参数,则匹配。

predicates:
    - Query=green

如果请求包含red开头(任意长度)且gree开头(长度为5)参数,则匹配。

predicates:
    - Query=red.*, gree.
  • 设置排序

匹配顺序按order指定的升序(值小优先)来依次匹配。

- id: example2
  uri: lb://exampleTwo
  order: 100
  
- id: example1
  uri: lb://exampleTwo
  order: 50

先匹配example1,如果不满足再匹配example2。

代码实现Filter

实现自定义Filter

public class HeaderFilter implements GatewayFilter {

    //Mono: 包含0个或1个元素的异步序列
    //Flux: 包含0个或多个元素的异步序列
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        //向请求中添加指定的header
        ServerHttpRequest changeRequest = exchange.getRequest()
                .mutate()
                .header("X-Request-red", "blue")
                .build();

        //将变更过的请求写入到exchange
        ServerWebExchange webExchange = exchange.mutate()
                .request(changeRequest)
                .build();
        //将变更过的exchange向下传递
        return chain.filter(webExchange);
    }
}

注入自定义FilterBean

	@Bean
    public RouteLocator someRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(ps -> ps.path("/**")
                        .filters(f -> f.filter(new HeaderFilter()))
                        .uri("http://localhost:8082")
                        .id("custom_filter_route"))
                .build();
    }
  • 全局Filter
    Global Filter不需要在任何具体路由规则中进行注册,只需在类上添加@Componment注解并实现GlobalFilter即可。

Filter工作原理

自定义三个Filter

public class OneFilter implements GatewayFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("pre-1");
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    System.out.println("post-1");
                })
        );
    }
}

public class TwoFilter implements GatewayFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("pre-2");
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    System.out.println("post-2");
                })
        );
    }
}

public class ThreeFilter implements GatewayFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("pre-3");
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    System.out.println("post-3");
                })
        );
    }
}

注入自定义FilterBean

	@Bean
    public RouteLocator someRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(ps -> ps.path("/**")
                        .filters(f -> f.filter(new OneFilter())
                                .filter(new TwoFilter())
                                .filter(new ThreeFilter()))
                        .uri("http://localhost:8082")
                        .id("custom_filter_route"))
                .build();
    }

根据注入顺序先执行OneFilter->TwoFilter->ThreeFilter

控制台输出:

pre-1
pre-2
pre-3
post-3
post-2
post-1

执行顺序
在这里插入图片描述


版权声明:本文为h273979586原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。