springcloud-gateway


路由网关Gateway概念 - 课后通读

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关。但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关替代Zuul。

Gateway是在Spring生态系统之上构建的API网关服务,基于Spring 5,Spring Boot 2和Project Reactor等技术。

Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。

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

SpringCloud Gateway作为Spring Cloud 生态系统中的网关,目标是替代Zuul,在Spring Cloud 2.0以上版本中,没有对新版本的Zuul 2.0以上最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

Spring Cloud Gateway的目标提供统一的路由方式且基于 Filter链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

作用

  • 方向代理
  • 鉴权 - 登录校验[是否登录/用户权限]
  • 流量控制
  • 熔断
  • 日志监控

微服务中网关位置

为什么要有网关

API 网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:

  1. 客户端会多次请求不同的微服务,增加了客户端的复杂性。
  2. 存在跨域请求,在一定场景下处理相对复杂。
  3. 认证复杂,每个服务都需要独立认证。
  4. 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
  5. 某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难。

核心概念

  1. ROUTE(路由)
    路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true,则匹配该路由。

  2. PREDICATE(断言)
    参考的是Java8的java.util.function.Predictate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与路由相匹配则进行路由。

  3. FILTER(过滤)
    指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由之前或之后对请求进行修改。

web请求,通过一些匹配条件,定位到真正的服务节点,并在这个转发过程的前后,进行一些精细化控制。Predicate就是我们的匹配条件,而filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由。

Gateway工作流程

  1. 客户端[浏览器]向Spring Cloud Gateway发出请求。然后在**Gateway Handler Mapping[网关映射器]**中找到与请求相匹配的路由,将其发送到Gateway Web Handler[网关处理器 - 由映射器提交过来的真实的微服务的地址]。

  2. Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
    过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑的加强或其他处理。

  3. Filter 在 “pre” 类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等;在 “post” 类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等,有着非常重要的作用

Gatewey的核心逻辑就是 路由转发+执行过滤链

Gateway体验01

  1. 导入依赖

     <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
  2. application.yml网关配置

    server:
        9000
    spring:
      cloud:
        gateway:
          routes:
            - id: xxx
              uri: https://blog.csdn.net
              predicates:
                - Path=/cloud
    

当从浏览器输入localhost:9000/cloud的时候,请求自动转发到https://blog.csdn.net

路由规则

路由规则之path

spring:
  application:
    name: gateway-demo
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: http://localhost:8083
          predicates:
            - Path=/user/**

        - id: order-service
          uri: http://localhost:8082
          predicates:
            - Path=/order/**

将localhost:9000/user/1的请求转发到localhost:8083/user/1

路由规则之Query

spring:
  application:
    name: gateway-demo
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: http://localhost:8083
          predicates:
            - Query=token

        - id: order-service
          uri: http://localhost:8082
          predicates:
            - Query=name, abc.

http://localhost:9000/user/1?token=ss

http://localhost:9000/order?name=abc2

路由规则之Method

spring:
  application:
    name: gateway-demo
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: http://localhost:8083
          predicates:
            - Query=token

        - id: order-service
          uri: http://localhost:8082
          predicates:
            - Method=GET
@RequestMapping
public String gateway(Integer id){
  return "ok-gateway";
}
  1. 发送get请求 - localhost:9000/order 是ok的
  2. postman发送post请求 - localhost:9000/order 报404

路由规则之RemoteAddr

spring:
  application:
    name: gateway-demo
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: http://localhost:8083
          predicates:
            - RemoteAddr=192.168.186.216/0

        - id: order-service
          uri: http://localhost:8082
          predicates:
            - Method=GET

只能通过http://192.168.186.216:9000/user/1?token=s进行访问,不能通过http://localhost:9000/user/1?token=s进行访问

路由规则DateTime

After,Before,Between

路由规则之header

请求头信息必须包含指定的header,才能够进行转发

动态路由[服务发现的路由规则]

即和eureka结合,需要将gateway-demo注册为eureka-client

springcloud老版本使用Ribbon进行负载均衡 - eureka-client发现ribbon的依赖的.但是新的版本的springcloud使用了loadbalancer - 负载均衡

gateway只有通过uri是lb的配置方式,那么才会采用默认的负载均衡的方式 - 轮询.

gateway-demo注册到eureka-server中,成为eureka-client,才能够使用lb://serviceId

spring:
application:
name: gateway-demo
cloud:
gateway:
routes:
        - id: user-service
          uri: lb://user-demo
          predicates:
            - Path=/user/**
            - RemoteAddr=192.168.186.216/0

动态路由之服务名转发

spring:
  application:
    name: gateway-demo
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件结合,通过serviceId转发到具体服务实例
          enabled: true
          lower-case-service-id: true # 是否将服务名转小写

http://localhost:9000/user-demo/user/1

Gateway网关过滤器

spirngcloud Gateway根据作用范围划分为 GatewayFilter 和 GlobalFilter,二者区别如下:

  1. GatewayFilter:网关过滤器,需要通过 spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过 spirng.cloud.default-filters 配置在全局,作用在所有路由上。
  2. GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过 GatewayFilterAdapter 包装成 GatewayFilterChain 可识别的过滤器,它为请求业务以及路由的 URI 转换为真实业务服务请求地址的核心过滤器,不需要配置系统初始化时加载,并作用在每个路由上。

Path路径过滤器

Path 路径过滤器可以实现URL重写,通过重新URL可以实现隐藏实际路径提高安全性,易于用户记忆和键入,易于被搜索引擎收录等优点,实现方式如下:

  1. RewriterPathGatewayFilterFactory

    RewritePath 网关过滤器工厂采用路径正则表达式参数和替换参数,使用Java正则表达式来灵活地重写请求路径。

    spring:
      application:
        name: gateway-demo
      cloud:
        gateway:
          routes:
            - id: user-service
              uri: lb://user-demo
              predicates:
                - Path=/user/**,/api-gateway/**
              filters: # 网关过滤器
                # 将 /api-gateway/user/1 重写为 /user/1
                - RewritePath=/api-gateway(?/?.*), $\{segment}
    

    http://localhost:9000/api-gateway/user/1

  1. PrefixPathGatewayFilterFactory

    PrefixPath 网关过滤器工厂为匹配的 URI 添加指定前缀。

    spring:
      application:
        name: gateway-demo
      cloud:
        gateway:
          routes:
            - id: user-service
              uri: lb://user-demo
              predicates:
                - Path=/**
              filters: # 网关过滤器
    #            将 /1 重写为 /user/1
                - PrefixPath=/user
    

    http://localhost:9000/1

  2. StripPrefixGatewayFilterFactory

    StripPrefix 网关过滤器工厂采用一个参数 StripPrefix,该参数表示在将请求发送到下游之前从请求中剥离的路径个数。

    spring:
      application:
        name: gateway-demo
      cloud:
        gateway:
          routes:
            - id: user-service
              uri: lb://user-demo
              predicates:
                - Path=/**
              filters: # 网关过滤器
                - StripPrefix=2
    

    http://localhost:9000/api/aistar/user/1

  3. SetPathGatewayFilterFactory

    SetPath 网关过滤器工厂采用路径模板参数,它提供了一种通过允许模板化路径段来操作请求路径的简单方法,使用了SpringFramework中的URi模板,允许多个匹配段。

    spring:
      application:
        name: gateway-demo
      cloud:
        gateway:
          routes:
            - id: user-service
              uri: lb://user-demo
              predicates:
                - Path=/api/user/{segment}
              filters: # 网关过滤器
                # 将 /api/user/1 重写为 /user/1
                - SetPath=/user/{segment}
    

    http://localhost:9000/api/user/1

其余过滤器

  1. Parameter参数过滤器
  2. Status状态过滤器

全局过滤器

即使 Spring Cloud Gateway 自带许多实用的 GatewayFilter Factory、Gateway Filter、Global Filter,但是在很多情景下我们仍然希望可以自定义自己的过滤器,实现一些谨慎操作。

自定义网关过滤器

自定义网关过滤器需要实现以下两个接口:GatewayFilter,Ordered。

坑爹哈 - 一定要注意是否为3.x版本

tech.aisar.filter

package tech.aistar.filter;

import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec;
import org.springframework.cloud.gateway.route.builder.UriSpec;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;


/**
 * 本类用来演示: 自定义网关过滤器
 *
 * @author: success
 * @date: 2021/10/19 8:03 下午
 */
public class CustomGatewayFilter implements GatewayFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("自定义网关过滤器被执行...");
        return chain.filter(exchange);
    }

    /**
     * 过滤器执行顺序,数值越小,优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}
package tech.aistar;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.route.builder.UriSpec;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import tech.aistar.filter.CustomGatewayFilter;

import java.util.function.Function;

@EnableEurekaClient
@SpringBootApplication
public class GatewayDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayDemoApplication.class, args);
    }
        //springcloud2020写法
    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes().route("user-service",r->r
                        .path("/user/**")
                        .filters(f->f.filters(new CustomGatewayFilter()))
                        .uri("http://localhost:8083/")

        ).build();
    }
}

浏览器执行localhost:9000/user/1

自定义全局过滤器

自定义全局过滤器需要实现以下两个接口:GlobalFilter, Ordered。通过全局过滤器可以实现全校校验,安全性验证等功能。

package tech.aistar.filter;

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

/**
 * 本类用来演示: 全局过滤器
 *
 * @author: success
 * @date: 2021/10/19 8:45 下午
 */
@Component
public class CustomGlobalGateway implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("自定义全局过滤器被执行");
        return chain.filter(exchange); // 继续向下执行
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

课后练习 - 统一鉴权操作

在网关过滤器中通过token判断用户是否登录,完成一个统一的鉴权案例

 @Component
 public class AccessFilter implements GlobalFilter, Ordered {
  private Logger logger = (Logger) LoggerFactory.getLogger(AccessFilter.class);
 
  /**
     *  过滤器业务逻辑
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求参数
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        // 业务逻辑处理
        if(StringUtils.isBlank(token)){
            logger.warning("token is null ...");
            ServerHttpResponse response = exchange.getResponse();
            // 响应类型
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            // 响应状态码,HTTP 401 错误代表用户没有访问权限
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            // 响应内容
            String message = "{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}";
            DataBuffer buffer = response.bufferFactory().wrap(message.getBytes());
            // 请求结束,不在继续向下请求
            return response.writeWith(Mono.just(buffer));
        }
        // 使用token进行身份验证
        logger.info("token is OK!");
        return chain.filter(exchange);
    }
 
    /**
     * 过滤器执行顺序,数值越小,优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 1;
    }
 }

Gateway负载均衡

默认lb方式就是采用轮询的方式进行负载均衡,感兴趣的可以实现自定义负载均衡策略

Gateway熔断限流

熔断Hystrix组件 - 现在被剔除了-java.lang.IllegalArgumentException: Unable to find GatewayFilterFactory with name Hystrix

 <dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
   <version>2.0.2</version>
</dependency>
spring:
  application:
    name: gateway-demo
  cloud:
    gateway:
      routes:
        # 保证唯一性
        - id: order-service
          # uri: http://localhost:8083
          uri: lb://order-demo
          predicates:
            - Path=/order/**
          filters:
            - name: CircuitBreaker
              args:
                name: fallbackcmd
                fallbackUri: forward:/defaultfallback
package tech.aistar.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class SelfHystrixController {

    @RequestMapping("/defaultfallback")
    public Map<String,String> defaultfallback(){
        System.out.println("请求被熔断.");
        Map<String,String> map = new HashMap<>();
        map.put("Code","fail");
        map.put("Message","服务异常");
        map.put("result","");
        return map;
    }
}
package tech.aistar.config;

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.time.Duration;

/**
 * 本类用来演示: 熔断器配置
 *
 * @author: success
 * @date: 2021/10/20 11:48 上午
 */
@Configuration
public class CustomizeCircuitBreakerConfig {
    @Bean
    public ReactiveResilience4JCircuitBreakerFactory defaultCustomizer() {

        CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() //
                .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED) // 滑动窗口的类型为时间窗口
                .slidingWindowSize(10) // 时间窗口的大小为60秒
                .minimumNumberOfCalls(5) // 在单位时间窗口内最少需要5次调用才能开始进行统计计算
                .failureRateThreshold(50) // 在单位时间窗口内调用失败率达到50%后会启动断路器
                .enableAutomaticTransitionFromOpenToHalfOpen() // 允许断路器自动由打开状态转换为半开状态
                .permittedNumberOfCallsInHalfOpenState(5) // 在半开状态下允许进行正常调用的次数
                .waitDurationInOpenState(Duration.ofSeconds(5)) // 断路器打开状态转换为半开状态需要等待60秒
                .recordExceptions(Throwable.class) // 所有异常都当作失败来处理
                .build();

        ReactiveResilience4JCircuitBreakerFactory factory = new ReactiveResilience4JCircuitBreakerFactory();
        factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(1000)).build())
                .circuitBreakerConfig(circuitBreakerConfig).build());

        return factory;
    }
}

微服务系统中熔断限流环节,对保护系统的稳定性起到了很大的作用,作为网关,Spring Cloud Gateway也提供了很好的支持。先来理解下熔断限流概念:

  • 熔断降级:在分布式系统中,网关作为流量的入口,大量请求进入网关,向后端远程系统或服务发起调用,后端服务不可避免的会产生调用失败(超时或者异常),失败时不能让请求堆积在网关上,需要快速失败并返回回去,这就需要在网关上做熔断、降级操作。
  • 限流:网关上有大量请求,对指定服务进行限流,可以很大程度上提高服务的可用性与稳定性,限流的目的是通过对并发访问/请求进行限速,或对一个时间窗口内的请求进行限速来保护系统。一旦达到限制速率则可以拒绝服务、排队或等待、降级

搭建Nginx+Gateway

  1. gateway-prj - 网关服务 - 开启至少2个实例 - 集群
  2. 请求->Nginx[upstream进行负载均衡配置] - 选择具体的某个网关的具体的实例
  3. 网关内部 - > 路由转发user-demo[再次进行集群]

微服务,分布式,集群,负载均衡,网关技术,反向代理,跨域等问题


文章作者: 码农耕地人
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 码农耕地人 !
  目录