您的位置:  首页 > 技术杂谈 > 正文

新一代服务网关Gateway的实践笔记

2022-07-12 19:00 https://my.oschina.net/jiagoushi/blog/5553039 博学谷狂野架构师 次阅读 条评论

3、SpringCloud Gateway

file

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

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

总结:微服务网关就是一个系统,通过暴露该微服务网关系统,方便我们进行相关的鉴权,安全控制,日志统一处理,易于监控,限流等相关功能。

实现微服务网关的技术有很多,

  • nginx:Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务
  • zuul :Zuul 是 Netflflix 出品的一个基于 JVM 路由和服务端的负载均衡器。
  • spring-cloud-gateway:是spring 出品的基于spring的网关项目,集成断路器,路径重写,性能比Zuul好。

我们使用gateway这个网关技术,无缝衔接到基于spring cloud的微服务开发中来。

gateway官网:

https://spring.io/projects/spring-cloud-gateway

3.1 Gateway工作原理

我们在学习Gateway之前,先弄清楚Gateway的工作原理,后面使用它的各个功能时,就知道该如何使用了,工作流程图如下:

file

Gateway的执行流程如下:

1:Gateway的客户端回向Spring Cloud Gateway发起请求,请求首先会被HttpWebHandlerAdapter进行提取组装成网关的上下文,然后网关的上下文会传递到DispatcherHandler。

2:DispatcherHandler是所有请求的分发处理器,DispatcherHandler主要负责分发请求对应的处理器,比如将请求分发到对应RoutePredicateHandlerMapping(路由断言处理器映射器)。

3:路由断言处理映射器主要用于路由的查找,以及找到路由后返回对应的FilteringWebHandler。

4:FilteringWebHandler主要负责组装Filter链表并调用Filter执行一系列Filter处理,然后把请求转到后端对应的代理服务处理,处理完毕后,将Response返回到Gateway客户端。
在Filter链中,通过虚线分割Filter的原因是,过滤器可以在转发请求之前处理或者接收到被代理服务的返回结果之后处理。所有的Pre类型的Filter执行完毕之后,才会转发请求到被代理的服务处理。被代理的服务把所有请求完毕之后,才会执行Post类型的过滤器。

3.2 Gateway路由

Gateway路由配置分为基于配置的静态路由设置和基于代码动态路由配置,

静态路由是指在application.yml中把路由信息配置好了,而动态路由则支持在代码中动态加载路由信息,更加灵活,我们接下来把这2种路由操作都实现一次。

3.2.1 业务说明

file

如上图:

1:用户所有请求以/order开始的请求,都路由到 hailtaxi-order服务
2:用户所有请求以/driver开始的请求,都路由到 hailtaxi-driver服务
3:用户所有请求以/pay开始的请求,都路由到 hailtaxi-pay服务

3.2.2 基于配置路由设置

file

如上图所示,正是Gateway静态路由配置:

1:用户所有请求以/order开始的请求,都路由到 hailtaxi-order服务
2:用户所有请求以/driver开始的请求,都路由到 hailtaxi-driver服务
3:用户所有请求以/pay开始的请求,都路由到 hailtaxi-pay服务

配置参数说明:

routes:路由配置
- id:唯一标识符
uri:路由地址,可以是 lb://IP:端口     也可以是   lb://${spring.application.name}
predicates:断言,是指路由条件
- Path=/driver/**:路由条件。Predicate 接受一个输入参数,返回一个布尔值结果。这里表示匹配所有以driver开始的请求。
filters:过滤器
- StripPrefix=1:真实路由的时候,去掉第1个路径,路径个数以/分割区分

测试url:http://localhost:8001/driver/info/1

3.2.3 基于代码路由配置

我们同样实现上面的功能,但这里基于代码方式实现。所有路由规则我们可以从数据库中读取并加载到程序中。基于代码的路由配置我们只需要创建RouteLocator并添加路由配置即可,代码如下:

/***
 * 路由配置
 * @param builder
 * @return
 */
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder) {

    return builder.routes()
        .route("hailtaxi-driver", r -> r.path("/driver/**").uri("lb://hailtaxi-driver"))
        .route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order"))
        .route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay"))
        .build();
}

在真实场景中,基于配置文件的方式更直观、简介,但代码的路由配置是更强大,可以实现很丰富的功能,可以把路由规则存在数据库中,每次直接从数据库中加载规则,这样的好处是可以动态刷新路由规则,通常应用于权限系统动态配置。

spring: 
	cloud: 
		gateway:
          #路由配置
          routes:
            #唯一标识符
            - id: hailtaxi-driver
              uri: lb://hailtaxi-driver
              #路由断言
              predicates:
                - Path=/driver/**
            #唯一标识符
            - id: hailtaxi-order
              uri: lb://hailtaxi-order
              #路由断言
              predicates:
                - Path=/order/**
            #唯一标识符
            - id: hailtaxi-pay
              uri: lb://hailtaxi-pay
              #路由断言
              predicates:
                - Path=/pay/**

3.2.4 Gateway-Predicate

上面路由匹配规则中我们都用了- Path方式,其实就是路径匹配方式,除了路径匹配方式,Gateway还支持很多丰富的匹配方式,我们对这些方式分别进行讲解。

关于Predicate学习地址,可以参考官网:

https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gateway-request-predicates-factories

或者:

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#gateway-request-predicates-factories

routes下面的属性含义如下:

id:我们自定义的路由 ID,保持唯一

uri:目标服务地址

predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该属性包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)

Predicate 来源于 Java 8,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。

在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,通过 Header、请求参数等不同的条件来作为条件匹配到对应的路由。

下面的一张图(来自网络)总结了 Spring Cloud 内置的几种 Predicate 的实现:

file

我们在这里讲解几个断言匹配 方式。

Cookie:

Gateway的Cookie匹配接收两个参数:一个是 Cookie name ,一个是正则表达式。路由规则就是通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。如下配置:

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/driver/**
        - Cookie=username,itheima

这里表示请求携带了cookie为username的数据,并且值为itheima,就允许通过。

Header 匹配:

Header 匹配 和 Cookie 匹配 一样,也是接收两个参数,一个 header 中属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。配置如下:

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/driver/**
        - Header=token,^(?!\d+$)[\da-zA-Z]+$

上面的匹配规则,就是请求头要有token属性,并且值必须为数字和字母组合的正则表达式,例如携带token=19and30就可以通过访问。

请求方式匹配:

通过请求的方式是 POST、GET、PUT、DELETE 等进行路由。配置如下:

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/driver/**
        - Method=GET,POST

通过yml的完整代码如下(注释掉java的配置

server:
  port: 8001
spring:
  application:
    name: hailtaxi-gateway
  main:
    allow-bean-definition-overriding: true
  cloud:
    #Consul配置
    consul:
      host: 127.0.0.1
      port: 8500
      discovery:
        #注册到Consul中的服务名字
        service-name: ${spring.application.name}
        #注册的服务的实例 Id,最好不要重复,这里参考官网建议的方式 带随机数 默认:应用名:port
        #instance-id: ${spring.application.name}:${vcap.application.instance_id:${spring.application.i nstance_id:${random.value}}}
        # 自定义实例id为:应用名:ip:port
        instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
        prefer-ip-address: true
        # 开启服务注册
        register: true
        # 开启服务发现
        enabled: true
        #2 分钟之后健康检查未通过取消注册
        health-check-critical-timeout: 2m
        #consul 健康检查的轮询周期
        health-check-interval: 10s
    gateway:
      #路由配置
      routes:
        #唯一标识符
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由断言
          predicates:
            - Path=/driver/**
            - Cookie=username,itheima
            - Header=token,^(?!\d+$)[\da-zA-Z]+$
            - Method=GET,POST
        #唯一标识符
        - id: hailtaxi-order
          uri: lb://hailtaxi-order
          #路由断言
          predicates:
            - Path=/order/**
        #唯一标识符
        - id: hailtaxi-pay
          uri: lb://hailtaxi-pay
          #路由断言
          predicates:
            - Path=/pay/**
            

3.2.5、断言源码剖析

Cookie断言来说,首先看它的体系结构

file

public class CookieRoutePredicateFactory
		extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Config> {

	/**
	 * Name key.
	 */
	public static final String NAME_KEY = "name";

	/**
	 * Regexp key.
	 */
	public static final String REGEXP_KEY = "regexp";

	public CookieRoutePredicateFactory() {
		super(Config.class);
	}
	
    /*
      通过shortcutFieldOrder方法设置Config配置类中的属性,需要根据具体的规则来设置
      通过shortcutType方法获取具体规则,具体参看:org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType
      规则包括以下几种:
		DEFAULT : 按照shortcutFieldOrder顺序依次赋值
    */
	@Override
	public List<String> shortcutFieldOrder() {
		return Arrays.asList(NAME_KEY, REGEXP_KEY);
	}

	@Override
	public Predicate<ServerWebExchange> apply(Config config) {
		return new GatewayPredicate() {
			@Override
			public boolean test(ServerWebExchange exchange) {
				List<HttpCookie> cookies = exchange.getRequest().getCookies()
						.get(config.name);
				if (cookies == null) {
					return false;
				}
				for (HttpCookie cookie : cookies) {
					if (cookie.getValue().matches(config.regexp)) {
						return true;
					}
				}
				return false;
			}

			@Override
			public String toString() {
				return String.format("Cookie: name=%s regexp=%s", config.name,
						config.regexp);
			}
		};
	}
	
    /*
     内部配置类是用来接收在配置文件中配置的参数的
      routes:
        #唯一标识符
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由断言
          predicates:
            - Cookie=username,itheima
    */
	@Validated
	public static class Config {

		@NotEmpty
		private String name;

		@NotEmpty
		private String regexp;

		public String getName() {
			return name;
		}

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

		public String getRegexp() {
			return regexp;
		}

		public Config setRegexp(String regexp) {
			this.regexp = regexp;
			return this;
		}

	}

}

尽管Spring Cloud Gateway已经包含了很多路由匹配规则,有时候我们需要开发自定义路由匹配规则来满足需求,下面简单的介绍一下如何自定义路由匹配规则。

案例

需求:转发带token的请求到hailtaxi-drvier服务中,这里定义请求带token是指包含某个请求头的请求,至于是什么请求头可以由配置指定

1、修改配置文件

    gateway:
      #路由配置
      routes:
        #唯一标识符
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由断言
          predicates:
			# 自定义一个Token断言,如果请求包含Authorization的token信息则通过
            - Token=Authorization

2、创建 RoutePredicateFactory

断言工厂默认命名规则必须按照"名称"+RoutePredicateFactory,如上TokenRoutePredicateFactory的断言名称为Token

@Slf4j
@Component // 要交给spring容器管理
public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenRoutePredicateFactory.Config> {

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

    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            // 打印配置文件参数值
            String headerName = config.getHeaderName();
            HttpHeaders headers = exchange.getRequest().getHeaders();
            List<String> header = headers.get(headerName);
            log.info("Token Predicate headers:{}", header);
            // 断言返回的是boolean值
            return header!=null && header.size()>0;
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("headerName");//指定配置文件中加载到的配置信息应填充到Config的哪个属性上
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.DEFAULT;
    }

    @Data
    public static class Config { //static class
        private String headerName;//存储从配置文件中加载的配置
    }
}

启动测试:http://localhost:8001/driver/info/1

3.3 Gateway过滤器

Spring Cloud Gateway根据作用范围划分为GatewayFilterGlobalFilter,二者区别如下:

  • GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上;gateway内置了多种过滤器工厂,配套的过滤器可以直接使用,如下图所示:

    file

    file

  • GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。

file

过滤器作为Gateway的重要功能。常用于请求鉴权、服务调用时长统计、修改请求或响应header、限流、去除路径等等。

关于Gateway过滤器的更多使用,大家可以参考官方地址:

https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories

或者:

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#_gatewayfilter_factories

3.3.1 过滤器分类

默认过滤器:出厂自带,实现好了拿来就用,不需要实现
  全局默认过滤器
  局部默认过滤器
  
自定义过滤器:根据需求自己实现,实现后需配置,然后才能用哦。
  全局过滤器:作用在所有路由上。
  局部过滤器:配置在具体路由下,只作用在当前路由上。

默认过滤器十好几个,常见如下:

过滤器名称说明对应的类父类
AddRequestHeader对匹配上的请求加上HeaderAddRequestHeaderGatewayFilterFactoryAbstractNameValueGatewayFilterFactory
AddRequestParameters对匹配上的请求路由AddRequestHeaderGatewayFilterFactoryAbstractNameValueGatewayFilterFactory
AddResponseHeader对从网关返回的响应添加HeaderAddResponseHeaderGatewayFilterFactoryAbstractNameValueGatewayFilterFactory
StripPrefix对匹配上的请求路径去除前缀StripPrefixGatewayFilterFactoryAbstractGatewayFilterFactory

3.3.2 默认过滤器的使用

所谓默认过滤器就是系统自带的。有很多,这里简要说明几个:(通过java配置,注释掉yaml配置

1)添加响应头

AddResponseHeaderGatewayFilterFactory 属于 GatewayFilter

对输出响应头设置属性,比如对输出的响应设置其头部属性名称为:X-Response-Default-MyName , 值为itheima

修改配置文件,配置如下:

spring:
  cloud:
    gateway:
     # 配置全局默认过滤器 作用在所有路由上,也可单独为某个路由配置
      default-filters:
      # 往响应过滤器中加入信息
        - AddResponseHeader=X-Response-Default-MyName,itheima

请求http://localhost:8001/driver/info/1,响应数据添加了X-Response-Default-MyName: itheima,如下图:

file

2)前缀处理

在项目中做开发对接接口的时候,我们很多时候需要统一API路径,比如统一以/api开始的请求调用hailtaxi-driver服务,但真实服务接口地址又没有/api路径,我们可以使用Gateway的过滤器处理请求路径。

在gateway中可以通过配置路由的过滤器StripPrefix实现映射路径中的前缀处理,我们来使用一下该过滤器,再进一步做说明。

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/api/driver/**
        filters:
          - StripPrefix=1

此处- StripPrefix=1表示真实请求地址是当前用户请求以/api开始的uri中去除第1个路径/api.

上面配置最终执行如下表:

配置路由地址访问地址
StripPrefix=1http://localhost:8001/api/driver/info/2http://localhost:18081/driver/info/2
StripPrefix=2http://localhost:8001/api/suri/driver/info/2http://localhost:18081/driver/info/2

有时候为了简化用户请求地址,比如用户请求http://localhost:8001/info/1我们想统一路由到http://localhost:18081/driver/info/1,可以使用PrefixPath过滤器增加前缀。

    gateway:
      routes:
      - id: hailtaxi-driver
        uri: lb://hailtaxi-driver
        predicates:
        - Path=/**
        filters:
          - PrefixPath=/driver

上面配置最终执行如下表:

配置路由地址访问地址
- PrefixPath=/driverhttp://localhost:8001/info/2http://localhost:18081/driver/info/2

3.3.3自定义GatewayFilter

1、实现GatewayFilter接口

GatewayFilter 一般作用在某一个路由上,需要实例化创建才能使用,局部过滤器需要实现接口GatewayFilter、Ordered

创建com.itheima.filter.PayFilter代码如下:

public class PayFilter implements GatewayFilter,Ordered {

    /***
     * 过滤器执行拦截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("GatewayFilter拦截器执行---pre-----PayFilter");
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            System.out.println("GatewayFilter拦截器执行---post-----PayFilter");
        }));
    }

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

使用局部过滤器:(使用下面RouteLocator的时候,配置文件中的路由记得注释或删除)

/***
 * 路由配置
 * @param builder
 * @return
 */
 @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("hailtaxi-driver", r -> r.path("/api/driver/**")
                        .and().cookie("username","itheima")
                        .and().header("token","123456")
                                .filters(f->f.filter(new PayFilter()).addResponseHeader("X-Response-Default-MyName", "itheima")
                                        .addRequestHeader("myheader", "1234567")
                                .stripPrefix(1)
                                )
                       // .and().method(HttpMethod.POST)
                        .uri("lb://hailtaxi-driver")
                        //.filter(new PayFilter())
                 )
                .route("hailtaxi-order", r -> r.path("/order/**").uri("lb://hailtaxi-order"))
                .route("hailtaxi-pay", r -> r.path("/pay/**").uri("lb://hailtaxi-pay"))
                .build();
    }

为了更好看到效果,我们在RouterFilter添加System.out.println("GlobalFilter拦截器执行");再访问测试。

访问:http://localhost:8001/api/driver/info/1,注意使用postman发送请求时添加请求头,添加cookie。

2、继承GatewayFilterFactory

如果定义局部过滤器,想在配置文件中进行配置来使用,可以继承AbstractGatewayFilterFactory<T>抽象类或者AbstractNameValueGatewayFilterFactory

整个体系结构为:

file

这两个抽象类的区别就是前者接收一个参数(像StripPrefix和我们创建的这种),后者接收两个参数(像AddResponseHeader)

代码的编写可以参考:StripPrefixGatewayFilterFactoryAddRequestHeaderGatewayFilterFactory

过滤器工厂默认命名规则必须按照"名称"+GatewayFilterFactory`,如上StripPrefixGatewayFilterFactory的过滤器名称为StripPrefix

2.1、继承AbstractGatewayFilterFactory

需求:

在网关中统一支付方式,编写一个过滤器:PayMethodGatewayFilterFactory

1、编写过滤器

@Slf4j
@Component //一定要将其交给spring容器管理
public class PayMethodGatewayFilterFactory extends AbstractGatewayFilterFactory<PayMethodGatewayFilterFactory.Config> {

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

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String paymethod = config.getPayMethod();
            String msg = config.getMsg();
            log.info("PayMethodGatewayFilterFactory 加载到的配置信息为:{}---{}",paymethod,msg);
            //将paymethod添加到请求头中
            exchange.getRequest().mutate().header("paymethod",paymethod);
            return chain.filter(exchange);
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return  Arrays.asList("payMethod","msg");//指定从yml中提前出来的配置信息填充到配置类中哪个属性,按规则配置
    }

    @Override
    public ShortcutType shortcutType() {
        return ShortcutType.DEFAULT;//默认规则
    }

    /**
     * 加载从yml中提取出来的配置信息
     */
    @Data
    public static class Config {
        private String payMethod;
        private String msg;
    }
}

2、配置文件中使用如下:

    gateway:
      #路由配置
      routes:
        #唯一标识符
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由断言
          predicates:
            - Path=/driver/**
            - Cookie=username,itheima
            - Header=token,^(?!\d+$)[\da-zA-Z]+$
            - Method=GET,POST
            - Token=Authorization
          filters:
            - PayMethod=alipay,业务整合

再次测试,查看hailtaxi-driver 服务接收到请求后是否多了paymethod请求头信息

2.2、继承AbstractNameValueGatewayFilterFactory

直接查看AddRequestHeaderGatewayFilterFactory 源码,分析即可!

3.3.4 自定义GlobalFilter

定义全局过滤器需要实现GlobalFilter,Ordered接口:

GlobalFilter:过滤器拦截处理方法
Ordered:过滤器也有多个,这里主要定义过滤器执行顺序,里面有个方法getOrder()会返回过滤器执行顺序,返回值越小,越靠前执行

需求

我们创建全局过滤器并完成常见业务用户权限校验,如果请求中有带有一个名字为token的请求参数,则认为请求有效放行,如果没有则拦截提示授权无效。

创建全局过滤器:com.itheima.filter.RouterFilter,代码如下:

@Component
public class RouterFilter implements GlobalFilter,Ordered {

    /***
     * 路由拦截
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("RouterFilter----------------");
        //获取请求参数
        String token = exchange.getRequest().getQueryParams().getFirst("token");
        //如果token为空,则表示没有登录
        if(StringUtils.isEmpty(token)){
            //没登录,状态设置403
            exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE);
            //结束请求
            return exchange.getResponse().setComplete();
        }
        //放行
        return chain.filter(exchange);
    }

    /***
     * 拦截器顺序
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

此时请求,我们不携带token参数,效果如下:

file

我们携带token参数则可以正常访问,效果如下:

file

3.4 跨域配置

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)。

在Spring Cloud Gateway中配置跨域是非常简单的,如下面application.yml所示:

    gateway:
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods:
              - GET
              - POST
              - PUT

但如果涉及到Cookie跨域,上面的配置就不生效了,如果涉及到Cookie跨域,需要创建CorsWebFilter过滤器,代码如下:

/**
 * 配置跨域
 * @return
 */
@Bean
public CorsWebFilter corsFilter() {
    CorsConfiguration config = new CorsConfiguration();
    // cookie跨域
    config.setAllowCredentials(Boolean.TRUE);
    config.addAllowedMethod("*");
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    // 配置前端js允许访问的自定义响应头
    config.addExposedHeader("Authorization");

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
    source.registerCorsConfiguration("/**", config);
    return new CorsWebFilter(source);
}

3.5 限流

网关可以做很多的事情,比如,限流,当我们的系统 被频繁的请求的时候,就有可能 将系统压垮,所以 为了解决这个问题,需要在每一个微服务中做限流操作,但是如果有了网关,那么就可以在网关系统做限流,因为所有的请求都需要先通过网关系统才能路由到微服务中。

3.5.1 漏桶算法讲解

file

漏桶算法是常见的限流算法之一,我们讲解一下漏桶算法:

1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)根据限流大小,设置按照一定的速率往桶里添加令牌;
3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流

漏桶算法的实现,有很多技术,Guaua是其中之一,redis客户端也有其实现。

3.5.2 限流案例

spring cloud gateway 默认使用redis的RateLimter限流算法来实现,外面来简要实现一下:

1、引入依赖

首先需要引入redis的依赖:

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

同时不要忘记Redis配置:

  redis:
    host: 127.0.0.1
    port: 6379

2、定义KeyResolver

在Application引导类中添加如下代码,KeyResolver用于计算某一个类型的限流的KEY也就是说,可以通过KeyResolver来指定限流的Key。

我们可以根据IP来限流,比如每个IP每秒钟只能请求一次,在GatewayApplication定义key的获取,获取客户端IP,将IP作为key,如下代码:

/***
 * IP限流
 * @return
 */
@Bean(name="ipKeyResolver")
public KeyResolver userKeyResolver() {
    return new KeyResolver() {
        @Override
        public Mono<String> resolve(ServerWebExchange exchange) {
            //获取远程客户端IP
            String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
            System.out.println("hostName:"+hostName);
            return Mono.just(hostName);
        }
    };
}

在路由中配置如下:

    gateway:
      #路由配置
      routes:
        #唯一标识符
        - id: hailtaxi-driver
          uri: lb://hailtaxi-driver
          #路由断言
          predicates:
            - Path=/driver/**
            - Cookie=username,itheima
            - Header=token,^(?!\d+$)[\da-zA-Z]+$
            - Method=GET,POST
            - Token=Authorization
          filters:
            - PayMethod=alipay,业务整合
            - name: RequestRateLimiter #请求数限流 名字不能随便写 ,使用默认的facatory
              args:
                key-resolver: "#{@ipKeyResolver}"
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 1

参数说明:

redis-rate-limiter.replenishRate是您希望允许用户每秒执行多少请求,而不会丢弃任何请求。这是令牌桶填充的速率
redis-rate-limiter.burstCapacity是指令牌桶的容量,允许在一秒钟内完成的最大请求数,将此值设置为零将阻止所有请求。
key-resolver: “#{@ipKeyResolver}” 用于通过SPEL表达式来指定使用哪一个KeyResolver.

如上配置: 表示 一秒内,允许 一个请求通过,令牌桶的填充速率也是一秒钟添加一个令牌。 最大突发状况 也只允许 一秒内有一次请求,可以根据业务来调整 。

我们请求http://localhost:8001/driver/info/1?token=aa执行测试,效果如下:

file

本文由传智教育博学谷 - 狂野架构师教研团队发布 转载请注明出处!

展开阅读全文
  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接