尚学堂SpringCloud GateWay笔记

Gateway:网关

主要内容

  1. API网关
  2. Spring Cloud Gateway介绍
  3. Gateway入门案例
  4. 谓词
  5. Filter
  6. 使用Gateway实现服务降级

学习目标

知识点

要求

  API网关

掌握

  Spring Cloud Gateway介绍

掌握

  Gateway入门案例

掌握

       谓词

掌握

  Filter

掌握

  使用Gateway实现服务降级

掌握

一、API 网关

1.什么是API网关

        API网关作用就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的接口。同时也可在网关中提供额外的功能。

总结:网关就是所有项目的一个统一入口。

2.网关组成

网关 = 路由转发 + 过滤器(编写额外功能)

  • 路由转发

接收外界请求,通过网关的路由转发,转发到后端的服务上。

如果只有这个一个功能看起来和之前学习的Nginx反向代理服务器很像,外界访问nginx,由nginx做负载均衡,后把请求转发到对应服务器上。

  • 过滤器

网关非常重要的功能就是过滤器。

过滤器中默认提供了25内置功能还自持额外的自定义功能。

对于我们来说比较常用的功能有网关的容错、限流以及请求及相应的额外处理。

3.Spring Cloud中提供的网关解决方案

  • Spring Cloud Netflix Zuul

属于Spring Cloud Netflix下一个组件,具有灵活、简单的特点。在早期Spring Cloud中使用的比较多。

其版本更新都依赖于Netflix Zuul。

  • Spring Cloud Gateway

由Spring 自己推出的网关产品,完全依赖Spring自家产品。符合Spring战略意义,其更新版本等都由Spring自己把控。

目前很多项目中都是使用Gateway替代Zuul。

在本套课程中讲解的也是Gateway

二、Spring Cloud Gateway介绍

1.简介

        Spring Cloud Gateway是Spring Cloud 的二级子项目,提供了微服务网关功能,包含:权限安全、监控/指标等功能。

2.名词解释

        在学习Gateway时里面有一些名词需要提前了解,这对于后面的学习是很有帮助的。

  • Route

        Route中文称为路由,Gateway里面的Route是主要学习内容,一个Gateway项目可以包含多个Route。

        一个路由包含ID、URI、Predicate集合、Filter集合。

        在Route中ID是自定的,URI就是一个地址。剩下的Predicate和Filter学习明白了,Route就学习清楚了。

  • Predicate

中文:谓词。

谓词是学习Gateway比较重要的一点,简单点理解谓词就是一些附加条件和内容。

  • Filter

        所有生效的Filter都是GatewayFilter的实例。在Gateway运行过程中Filter负责在代理服务“之前”或“之后”去做一些事情。

3.流程

4.文字解释

        网关客户端访问Gateway网关Gateway中Handler Mapping对请求URL进行处理。处理完成后交换Web Handler,Web Handler 会被Filter进行过滤。Filter中前半部分代码是处理请求的代码。处理完成后调用真实被代理的服务。被代理服务响应结果,结果会被Filter中后半部分代码进行操作,操作完成后把结果返回给Web Hanlder,在返回给Handler Mapping,最终响应给客户端。

三、Gateway入门案例

1.准备内容

Eureka Server

因为Gateway依赖Eureka,需要从Eureka中获取真实代理项目地址后,在进行访问。

所以需要准备一个Eureka Server(示例中是非集群Eureka),端口设置为8761。

2.新建项目DemoOne

一个普通的Eureka Client项目,重点关注应用程序名和控制器即可,其他代码省略。

  • 配置文件

spring:
  application:
    name: demo-one

  • 控制器

@Controller
public class DemoController {

    @RequestMapping("/one")
    @ResponseBody
    public String demo(){
        System.out.println("执行one控制器");
        return "one";
    }
    @RequestMapping("/demo/one")
    @ResponseBody
    public String demo2(){
        System.out.println("执行demo-one控制器");
        return "demo-one";
    }
}

3.新建项目GatewayDemo

  • 编写pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
</parent>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

  • 新建配置文件

谓词中Path一般会给每个项目前面起一个无意义的别名。

如 Path = /abc/** 当看到abc是知道路由转发到项目abc,后面的内容才是真实要访问abc的内容

如Path=/jqk/** 表示需要转发到jqk项目。

要明白:uri中第一部分就是为了防止各个项目中出现重名路径导致路由转发失败。

server:
  port: 9000
spring:
  application:
    name: gateway-demo
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启当前项目服务注册发现功能
          lower-case-service-id: true # 把服务名转换为小写,Eureka中默认都是大写
      routes:
        - id: demo #自定义唯一标识,只要不重复即可
          uri: lb://EUREKA-CLIENT # lb:loadBalance EUREKA-CLIENT 代理项目的名
          predicates: Path=/demo/** # 路径规则
          filters: StripPrefix=1 # 转发后忽略第一层

  • 新建启动器

新建com.bjsxt.GatewayApplication

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

4.访问测试

访问GatewayDemo项目。

http://localhost:9000/demo/one

页面会显示

通过结果可以看出,用户虽然访问的GatewayDemo网关项目,但是最后真是访问的是DemoOne项目中/one 控制器

5.修改配置参数

修改GatewayDemo项目中配置文件的路由参数,把StripPrefix的值由1改成0后,重启项目

filters: StripPrefix=0

再次访问http://localhost:9000/demo/one会发现

四、谓词

        谓词:当满足条件在进行路由转发。

    在Spring Cloud Gateway中谓词实现GatewayPredicate接口。其中类名符合:XXXRoutepredicateFactory,其中XXX就是在配置文件中谓词名称。在上面示例中Path=/demo/** 实际上使用的就是PathRoutePredicateFactory

        所有的谓词都设置在predicates属性中,当设置多个谓词时取逻辑与条件,且一个谓词只能设置一组条件,如果需要有个多条件,添加多个相同谓词

  • Query

设置必须包含的参数名。

下面两种写法等效。都表示路径满足/demo/**同时包含参数abc。

Path和Query是谓词。abc是请求参数名称。

在浏览器中输入:http://localhost:9000/demo/one?abc=jqk

predicates: Path=/demo/**,Query=abc

predicates:
  - Path=/demo/**
  - Query=abc

设置参数的值

        abc请求参数名称。jqk. 是abc的值,是一个正则表达式。在正则表达式中点(.)表示匹配任意一个字符。所以当请求参数abc=jqka或abc=jqkw能满足谓词条件。

        在谓词中赋值使用逗号(,)赋值因为Query后面已经有等号,在值内容中在出现等号语法说不过去了。

predicates:
  - Path=/demo/**
  - Query=abc,jqk.

  • Header

表示请求头中必须包含的内容。

注意:

        参数名和参数值之间依然使用逗号

        参数值要使用正则表达式

        如果Header只有一个值表示请求头中必须包含的参数。如果有两个值,第一个表示请求头必须包含的参数名,第二个表示请求头参数对应值。

predicates:
  - Path=/demo/**
  - Query=abc,jqk+
  - Header=Connection,keep-alive

        如果设定请求头中需要包含多个参数及值。设置多个Header。在此处演示多个相同谓词配置,其他谓词中就不在强调可以配置多个谓词。

predicates:
  - Path=/demo/**
  - Query=abc,jqk+
  - Header=Connection,keep-alive
  - Header=Cache-Control,max-age=0

  • Method

Method表示请求方式。支持多个值,使用逗号分隔,多个值之间为or条件。

predicates:
  - Path=/demo/**
  - Method=GET,POST

  • RemoteAddr

允许访问的客户端地址。

要注意使用127.0.0.1而不要使用localhost。

predicates:
  - Path=/demo/**
  - RemoteAddr=127.0.0.1

  • Host

匹配请求参数中Host参数的值。满足Ant模式(之前在Spring Security中学习过)可以使用

? 匹配一个字符

* 匹配0个或多个字符

** 匹配0个或多个目录

predicates:
  - Path=/demo/**
  - Host=127.0.0.1:9000

  • Cookie

要求请求中包含指定Cookie名和满足特定正则要求的值。

Cookie必须有两个值,第一个Cookie包含的参数名,第二个表示参数对应的值,正则表达式。不支持一个参数写法。

predicates:
  - Path=/demo/**
  - Cookie=age,.*

  • Before

在指定时间点之前

predicates:
  - Path=/demo/**
  - Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]

  • After

在指定时间点之前

predicates:
  - Path=/demo/**
  - After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]

  • Between

请求时必须在设定的时间范围内容,才进行路由转发。

注意:时间的格式

predicates:
  - Path=/demo/**
  - Between=2020-01-31T18:00:00.000+08:00[Asia/Shanghai], 2020-02-01T00:00:00.000+08:00[Asia/Shanghai]

  • Weight

负载均衡中权重。同一个组中URI进行负载均衡。

语法:Weight=组名,负载均衡权重

在Eureka中注册两个服务,这个服务(项目)是相同的,应用程序名分别叫做demo-one和demo-two。

Gateway在路由匹配时demo-one将占20%,demo-two将占80%

routes:
  - id: suiyi
    uri: lb://demo-one
    predicates:
      - Path=/demo/**
      - Weight=group,2
    filters: StripPrefix=1
  - id: suiyi2
    uri: lb://demo-two
    predicates:
      - Path=/demo/**
      - Weight=group,8
    filters: StripPrefix=1

五、Filter

        Filter作用:在路由转发到代理服务之前和代理服务返回结果之后额外做的事情。Filter执行了说明谓词条件通过了。

在Spring Cloud Gateway的路由中Filter分为:

        内置Filter,都是GatewayFilter实现类

        自定义GlobalFilter

        自定义GatewayFilter

        之前使用StripPrefix就是内置Filter,所有内置Filter都实现Gateway接口。使用时filters属性中过滤器名为XXXGatewayFilterFactory的类对应的名称为XXX。

当过滤器包含参数

下面所有Filter基本都是支持单个值,如果需要设置多个,需要写多个对应Filter

  • ​​​​​​​AddRequestHeader

添加请求头参数,参数和值之间使用逗号分隔

filters:
  - StripPrefix=1
  - AddRequestHeader=MyHeader,jqk

  • ​​​​​​​AddRequestParameter

添加请求表单参数,多个参数需要有多个过滤器。

filters:
  - StripPrefix=1
  - AddRequestParameter=name,bjsxt
  - AddRequestParameter=age,123

  • ​​​​​​​AddResponseHeader

添加相应头

  • ​​​​​​​DedupeResponseHeader

对指定响应头去重复。

语法:DedupeResponseHeader=响应头参数  相应头参数,strategy

可选参数strategy可取值:

RETAIN_FIRST 默认值,保留第一个

RETAIN_LAST 保留最后一个

RETAIN_UNIQUE 保留唯一的,出现重复的属性值,会保留一个。例如有两个My:bbb的属性,最后会只留一个。

- DedupeResponseHeader=MyContent-Type,RETAIN_UNIQUE

  • ​​​​​​​CircuitBreaker

实现熔断时使用,支持CircuitBreaker和Hystrix两种

  • ​​​​​​​FallbackHeaders

可以添加降级时的异常信息

  • ​​​​​​​PrefixPath

匹配所有前缀满足条件的URI

  • ​​​​​​​RequestRateLimiter

限流过滤器。

  • ​​​​​​​RedirectTo 

重定向。有两个参数,status和url。其中status应该300系列重定向状态码

  • ​​​​​​​RemoveRequestHeader

删除请求头参数

  • ​​​​​​​RemoveResponseHeader 

删除响应头参数

  • ​​​​​​​RemoveRequestParameter 

删除请求参数

  • ​​​​​​​RewritePath 

重写请求路径

  • ​​​​​​​RewriteResponseHeader 

重写响应头参数

  • ​​​​​​​SaveSession 

如果项目中使用Spring Security和Spring Session整合时,此属性特别重要

  • ​​​​​​​SecureHeaders 

具有权限验证时,建议的头信息内容。

  • ​​​​​​​SetPath 

功能和StripPrefix有点类似。语法更贴近restful

当前请求路径为/red/blue时会将/blue发送给下游。

spring: 

cloud:

gateway:

routes:

- id: setpath_route

uri: https://example.org

predicates:

- Path=/red/{segment}

filters:

- SetPath=/{segment}

  • ​​​​​​​SetRequestHeader 

替换请求参数头数。不是添加

  • ​​​​​​​SetResponseHeader 

替换响应头参数

  • ​​​​​​​SetStatus 

设置响应状态码

  • ​​​​​​​StripPrefix 

跳过路由uri中前几段后发送给下游

  • ​​​​​​​Retry 

设置重试次数

  • ​​​​​​​RequestSize 

请求最大大小。包含maxSize参数,可以有单位“KB”或“MB”默认为“B”

  • ​​​​​​​ModifyRequestBody 

修改请求体内容

  • ​​​​​​​ModifyResponseBody 

修改响应体

六、使用Gateway实现限流

可以利用Gateway中RequestRateLimiter实现限流。

1.常见的限流算法

  • 计数器算法

        以QPS(每秒查询率Queries-per-second)为100举例。

        从第一个请求开始计时。每个请求让计数器加一。当到达100以后,其他的请求都拒绝。

        如果1秒钟内前200ms请求数量已经到达了100,后面800ms中500次请求都被拒绝了,这种情况称为“突刺现象”

  • ​​​​​​​漏桶算法

        漏桶算法可以解决突刺现象。

        和生活中漏桶一样,有一个水桶,下面有一个”漏眼”往出漏水,不管桶里有多少水,漏水的速率都是一样的。但是既然是一个桶,桶里装的水都是有上限的。当到达了上限新进来的水就装不了(主要出现在突然倒进来大量水的情况)。

  • 令牌桶算法

        令牌桶算法可以说是对漏桶算法的一种改进。

        在桶中放令牌,请求获取令牌后才能继续执行。如果桶中没有令牌,请求可以选择进行等待或者直接拒绝。

        由于桶中令牌是按照一定速率放置的,所以可以一定程度解决突发访问。如果桶中令牌最多有100个,qps最大为100

2.Gateway中限流

RequestRateLimiter是基于Redis和Lua脚本实现的令牌桶算法。

  • ​​​​​​​添加依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
</parent>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Hoxton.SR3</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

  • ​​​​​​​编写配置文件

新建配置文件application.yml。

参数说明:

key-resolver: 使用SpEL获取Spring容器中实例。

replenishRate:每秒放入令牌的数量。

burstCapacity:容量

server:
  port: 9000
spring:
  application:
    name: gateway-demo
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启当前项目服务注册与发现功能
          lower-case-service-id: true # 把服务名转换为小写,Eureka中默认都是大写
      routes:
        - id: demo #自定义唯一标识,只要不重复即可
          uri: lb://appliation-client # lb:loadBalance EUREKA-CLIENT 代理项目的名
          predicates: Path=/abc # 路径规则
          filters:  # 转发后忽略第一层
            - name: RequestRateLimiter
              args:
                key-resolver: '#{@abcComponet}'
                redis-rate-limiter.replenishRate: 1
                redis-rate-limiter.burstCapacity: 3
  redis:
    host: 192.168.8.129

  • ​​​​​​​新建实例

新建com.bjsxt.component.AbcComponet。

just()方法参数表示根据什么进行限流。实例中是根据主机名进行限流。参数值影响Redis中key中红色部分。

@Component
public class AbcComponet implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}

  • ​​​​​​​新建启动类

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

  • ​​​​​​​使用JMeter测试

会发现前3个请求是成功的,后面很多失败的,1秒后的请求是成功了。

七、使用Gateway实现服务降级

        Spring Cloud Gateway 可以利用Hystrix实现服务降级等功能。

        当Gateway进行路由转发时,如果发现下游服务连接超时允许进行服务降级。

        实现原理:当连接超时时,使用Gateway自己的一个降级接口返回托底数据,保证程序继续运行。

1.添加依赖

在GatewayDemo中额外添加

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

2.新建控制器

新建com.bjsxt.controller.MyFallbackController添加降级控制器方法,方法中根据需求编写自己的逻辑。

控制器名称不要和网关代理的URL同名

@Controller
public class MyFallbackController {
    @RequestMapping("/myfallback")
    @ResponseBody
    public String fallback(){
        return "托底数据";
    }
}

3.修改配置文件

在配置文件中filters属性添加。

        其中args.name 取值是任意的,最终会被设置为Hystrix的commandKey。但是不能省略,省略会导致org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory.Config的Setter为null,因为没有设置Hystrix的commandKey等内容时就没有执行Setter的构造方法。

routes:
  - id: demo
    uri: lb://EUREKA-CLIENT
    predicates:
      - Path=/demo/**
    filters:
      - StripPrefix=1
      - name: Hystrix
        args:
          name: fallbackcmd
          fallbackUri: forward:/myfallback

八、GlobalFilter

        全局过滤器不需要工厂,也不需要配置,直接对所有的路由都生效

        可以使用GlobalFilter实现统一的权限验证、日志记录等希望对所有代理的项目都生效的内容都可以配置在全局过滤器中。

        且在项目中可以配置多个GlobalFilter的实现类。都可以自动执行

@Component
public class MyGlobalFilter implements GlobalFilter {
    private Logger logger = LoggerFactory.getLogger(MyGlobalFilter.class);
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        logger.info("执行全局过滤器-logger");
        return chain.filter(exchange);
    }
}

九、自定义FilterFactory

可以定义针对于Router的Filter。

注意:

  1. 类名必须叫做XXXGatewayFilterFactory.注入到Spring容器后使用时的名称就叫做XXX。
  2. 类必须继承AbstractGatewayFilterFactory
  3. 所有需要传递进来的参数都配置到当前类的内部类Config中

1.新建类

@Component
public class MyRouteGatewayFilterFactory extends AbstractGatewayFilterFactory<MyRouteGatewayFilterFactory.Config> {
    public MyRouteGatewayFilterFactory() {
        super(MyRouteGatewayFilterFactory.Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println("在这个位置写点东西传递进来的name"+config.getName()+",age:"+config.getAge());
                return chain.filter(exchange);
            }

            @Override
            public String toString() {
                return GatewayToStringStyler.filterToStringCreator(MyRouteGatewayFilterFactory.this).append("name", config.getName()).toString();
            }
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name");
    }

    public static class Config {
        private String name;
        private int age;

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public Config() {
        }

        public String getName() {
            return name;
        }

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

2.在配置文件中配置

filters.name : 过滤器的名称。

filters.args.name 为MyRouteGatewayFilterFactory.Config配置的属性。

routes:
  - id: suiyi
    uri: lb://JQK
    predicates:
      - Path=/project1/**
    filters:
      - StripPrefix=1
      - name: MyRoute
        args:
          name: hello
          age: 12

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值