一、API网关

API网关作用就是把各个服务对外提供的API汇聚起来,让外界看起来是一个统一的接口。同时也可以再网关中提供额外的功能。API网关就是所有项目的一个统一的入口。
API网关=路由转发+过滤器

1、路由转发

接受外界请求,通过网关的路由转发,转发到后端的服务上。与Nginx的区别在于,Nginx实现用户到前端的响应,gateway实现Vue到网关的相应。
image.png

2、过滤器

网关非常重要的功能就是过滤器。
过滤器中默认提供了25个内置功能还自持有额外自定义的功能。
对于我们来说常用的功能有网关的容错、限流以及请求和相应的额外处理。

3、网关解决方案

3.1 Spring Cloud Netflix Zuul

属于Spring Cloud Netflix下一个组件,具有灵活、简单的特点。在早期Spring Cloud中使用的比较多。
其版本更新都依赖于Netflix Zuul。

3.1 Spring Cloud Gateway

由Spring 自己推出的网关产品,完全依赖Spring自家产品。符合Spring战略意义,其更新版本等都由Spring自己把控。
目前很多项目中都是使用Gateway替代Zuul。

二、Spring Cloud Gateway

1、介绍

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

2、Route

Route路由,一个Gateway项目中可以包含多个Route。一个路由包含ID、URI、Predicate集合、FIlter集合。在Route中ID是自定义的,URI就是一个地址。
Predicate:谓词,即为一些附加判断条件和内容。

3、filter

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

三、Predicate谓词配置

1、Predicate三种形式

  1. spring:
  2. application:
  3. name: gatewayserve
  4. cloud:
  5. gateway:
  6. discovery:
  7. locator:
  8. enabled: true # 开启当前服务注册与发现功能
  9. lower-case-service-id: true
  10. routes:
  11. - id: gateway01 #自定义的唯一标识,不能重复
  12. uri: lb://EUREKACLIENT # 负载均衡,EUREKACLIENT为代理项目名
  13. # predicates: Path=/sxt/**,Query=name #路径规则 方式一
  14. # predicates: #方式二
  15. # - Path=/sxt/**
  16. # - Query=name,123
  17. predicates: #方式三
  18. - Path= /sxt/**
  19. - name: Query
  20. args:
  21. param: name
  22. regexp: 12,3 #参数之间是与的关系
  23. filters: StripPrefix=1 #转发后忽略第一层

2、Route相关配置

spring:
  application:
    name: gatewayserve
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启当前服务注册与发现功能
          lower-case-service-id: true
      routes:
        - id: gateway01 #自定义的唯一标识,不能重复
          uri: lb://EUREKACLIENT # 负载均衡,EUREKACLIENT为代理项目名
          predicates:
            - Path=/demo01
            - Query=name,123 #请求中必须含有name=123的参数和值
            - Header=sxt,456 #判断请求头中是否含有指定的内容sxt=456 请求头
            - Method=get,post,put  #当前请求必须是Get或者Post或者Put请求
            - RemoteAddr=127.0.0.1 #限制远端的服务器地址
            - Host=127.0.0.1:2324 #限制本机的ip和端口号
            - Cookie=uname,.* #限定请求中必须包含cookie 值: .*任意长度字符
            - Before=2021-12-31T18:00:00.000+08:00[Asia/Shanghai] # 指定时间之前访问
            - Weight=group,3 #和其他项目构成集群 3代表的是权重 但是注意组名称必须相同

# gateway01和gateway02通过Weight组成了一个集群,两者的代理项目名可以不同,端口号也可以不同。主要实现负载均衡和权重设置

        - id: gateway02 #自定义的唯一标识,不能重复
          uri: lb://EUREKACLIENT2 # 负载均衡,EUREKACLIENT2为代理项目名
          predicates:
            - Path=/demo01
            - Query=name,123 #请求中必须含有name=123的参数和值
            - Header=sxt,456 #判断请求头中是否含有指定的内容sxt=456 请求头
            - Method=get,post,put  #当前请求必须是Get或者Post或者Put请求
            - RemoteAddr=127.0.0.1 #限制远端的服务器地址
            - Host=127.0.0.1:2324 #限制本机的ip和端口号
            - Cookie=uname,.* #限定请求中必须包含cookie 值: .*任意长度字符
            - Before=2021-12-31T18:00:00.000+08:00[Asia/Shanghai] # 指定时间之前访问
            - Weight=group,2 #和其他项目构成集群 2代表的是权重 但是注意组名称必须相同

四、Filter过滤器配置

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

  • 路由过滤器:框架内置的Filter实现都是路由过滤器,都是GatewayFilter实现类型。
  • 全局过滤器:框架未内置全局过滤器实现,需自定义。全局过滤器需实现接口GlobalFilter
  • 自定义过滤器:可以根据自己要求定义对应过滤器

    1、内置Filter

    使用时filters属性中过滤器名为XXXGatewayFilterFactory的类对应的名称为XXX。所有Filter基本都是支持单个值,如果需要设置多个,需要写多个对应Filter。 ```yaml spring: application: name: gatewayserve cloud: gateway:
    discovery:
      locator:
        enabled: true # 开启当前服务注册与发现功能
        lower-case-service-id: true
    routes:
      - id: gateway01 #自定义的唯一标识,不能重复
        uri: lb://EUREKACLIENT # 负载均衡,EUREKACLIENT为代理项目名
        #          predicates: Path=/sxt/**,Query=name #路径规则
        #          predicates:
        #            - Path=/sxt/**
        #            - Query=name,123
        predicates: #谓词
          - Path= /sxt/{path}
        filters: #定义过滤器
          - StripPrefix=1 #下游地址跳转的时候进行增加/去除某些配置。
          - AddRequestHeader=headname,zs #给下游设置请求头参数
          - AddRequestParameter=name,abc #给下游设置请求参数
          - AddResponseHeader=bj,123 #给下游添加响应头
          - AddResponseHeader=bj,234
          - DedupeResponseHeader=bj,RETAIN_FIRST #响应头去重策略 RETAIN_FIRST默认值保留第一个 RETAIN_LAST保留最后一个 RETAIN_UNIQUE保留唯一一个,出现重复值的时候保留一个
          - SetPath=/{path} # 设置请求路径并将请求路径发给下游
          - PrefixPath=/bjsxt #给请求地址添加前缀 如请求为/demo01 设置后:/bjsxt/demo01
    
<a name="UM54i"></a>
### 2、使用Gateway实现限流
<a name="b75yC"></a>
#### 2.1 计数器算法
以QPS(每秒查询率)为100举例子。<br />从第一个请求开始计时。每个请求让计数器加一。当达到100以后,其他请求都拒绝。<br />如果一秒钟内前200ms请求数量已经到达了100,后面的800ms中500次请求都被拒绝了,这种情况称为“突刺现象”。
<a name="zEcUZ"></a>
#### 2.2 漏桶算法
漏桶算法可以解决突刺现象。<br />漏桶算法为了消除“突刺现象”,可以采用漏桶算法实现限流,漏桶算法这个名字就很形象。算法内部有一个容器,类似生活中用到的漏斗,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面的流量多大,下面流出的水的速度始终保不变。不管服务调用方多么不稳定,通过漏桶算法进行限流,每10ms处理一次请求。因为处理的速度始终是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限的,如果桶满了,那么新进来的请求就丢弃。
<a name="tlH21"></a>
#### 2.3 令牌桶算法
令牌桶算法可以说是对漏桶算法的一种改进。<br />从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许有一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定得到速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择等待可用的令牌、或者直接拒绝。放令牌的动作是持续不断进行,如果桶中的令牌达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有了100个令牌了,这时服务还没有完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬间的100个请求。所以,只有桶中没有令牌时,请求才会进行等地啊,最后相当于以一定的速率执行。还有一点就是可以限制客户端的每一个客户端请求。
<a name="mfKY5"></a>
#### 2.4 Gateway中限流
RequestRateLimiter是基于Redis和Lua脚本实现的令牌桶算法。<br />第一步:添加Redis依赖
```yaml
        <!--添加gateway网关依赖-->
        <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>
        <!--添加Redis依赖,用于实现令牌桶限流-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
第二步:编写application.yml配置文件
server:
  port: 2324
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8888/eureka/
spring:
  application:
    name: gatewayserve
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启当前服务注册与发现功能
          lower-case-service-id: true
      routes:
        - id: gateway01 #自定义的唯一标识,不能重复
          uri: lb://EUREKACLIENT # 负载均衡,EUREKACLIENT为代理项目名
          predicates: #谓词
            - Path= /sxt/**
          filters: #定义过滤器
            - StripPrefix=1 #下游地址跳转的时候进行增加/去除某些配置。
            - name: RequestRateLimiter
              args:
                key-resolver: '#{@gatewayComponent}' #用户的唯一凭证,使用SPEL获取Spring容器中实例。
                redis-rate-limiter.replenishRate: 1 #工厂每秒生产的令牌个数
                redis-rate-limiter.burstCapacity: 2 #令牌桶的大小 容量
  redis:
    host: 192.168.80.128
第三步:新建com.bjsxt.component.GatewayComponent
@Component
public class GatewayComponent implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        //远程端地址
        //这个代表的是限流的凭证  一个客户端只可以发送指定数量的请求
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
}
第四步:使用JMeter测试<br />发送10个请求,结果成功三次,前两次是一秒内执行的,第三次是另外一秒执行的,其他的执行失败被拒绝。

三、使用Gateway实现服务的降级

Spring Cloud Gateway可以利用Hystrix实现服务的降级功能。
当Gateway进行路由转发时,如果发现下游服务连接超时允许进行服务降级。
实现原理:当连接超时时,使用Gateway自己的一个降级接口返回托底数据,保证程序继续运行。
步骤一:添加Hystrix依赖

        <!--添加降级 容灾的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
步骤二:添加降级控制器方法,方法中根据需求编写自己的逻辑。
@RestController
public class MyController {

    @RequestMapping("/fallback")
    public String fallback(){
        return "托底数据";
    }
}
步骤三:在配置文件中的filters进行属性的添加。<br />其中args.name取值任意,最终都会被设置为Hystrix的CommandKey。但是不能省略,省略会导致org.springframework.cloud.gateway.filter.factory.HystrixGatewayFilterFactory.Config的Setter为null,因为没有设置Hystrix的commandKey等内容时就没有执行Setter的构造方法。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/22310212/1636786507427-99ba6437-be3d-47eb-b054-a0984cd206ac.png#clientId=u1a238caf-0f0b-4&from=paste&height=520&id=TFWFN&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1039&originWidth=2169&originalType=binary&ratio=1&size=292733&status=done&style=none&taskId=u4b307ffd-0618-40e1-bedd-9b374147b28&width=1084.5)
server:
  port: 2324
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8888/eureka/
spring:
  application:
    name: gatewayserve
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启当前服务注册与发现功能
          lower-case-service-id: true
      routes:
        - id: gateway01 #自定义的唯一标识,不能重复
          uri: lb://EUREKACLIENT # 负载均衡,EUREKACLIENT为代理项目名
          predicates: #谓词
            - Path= /sxt/**
          filters: #定义过滤器
            - StripPrefix=1 #下游地址跳转的时候进行增加/去除某些配置。
            - name: Hystrix
              args:
                name: hystrixfallback #取值任意,不可省略
                fallbackUri: forward:/fallback #托底方法路径

步骤四:关闭eureka-client将会执行托底方法。http://localhost:2324/sxt/demo01

四、全局过滤器

全局过滤器不需要工厂,也不需要配置,直接对所有的路由都生效。
可以使用GlobalFilter实现统一的权限认证、日志记录等希望对所有代理的项目都生效的内容都可以配置在全局的过滤器中。

全局过滤器A

@Component
public class MyGolbalFilter implements GlobalFilter {
    //创建日志对象,获取用户对项目的调用操作日志
    private Logger logger= LoggerFactory.getLogger(MyGolbalFilter.class);
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        logger.info("经过了全局过滤器A");
        Mono<Void> filter = chain.filter(exchange);
        logger.info("全局过滤器A执行完毕");
        return filter;
    }
}

全局过滤器B

@Component
public class MyGolbalFilterB implements GlobalFilter {
    //创建日志对象,获取用户对项目的调用操作日志
    private Logger logger= LoggerFactory.getLogger(MyGolbalFilterB.class);
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        logger.info("经过了全局过滤器B");
        Mono<Void> filter = chain.filter(exchange);
        logger.info("全局过滤器B执行完毕");
        return filter;
    }
}

image.png
项目中可以配置多个GlobalFilter的实现类,都可以自动执行。执行顺序如上图所示,每个过滤器都是按顺序依次执行完毕,执行下一个过滤器。
但是spring.io官网中的执行流程为:
全局A开始—-全局B开始—-全局B结束—-全局A结束,spring官网中的全局的过滤器执行顺序如下图所示。
image.png

五、自定义FilterFactory(路由过滤器)

可以定义针对于Router的FIlter。
注意:

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

步骤一:

//[1]自定义过滤器的时候需要注意名称 xxxxGatewayFilterFactory yml访问时候就是xxxx
//[2]自己定义的过滤器 需要继承extends AbstractGatewayFilterFactory<MySxtGatewayFilterFactory.Config>
// MySxtGatewayFilterFactory自己的类名   Config自己定义的静态内部类
@Component
public class MyRouterGatewayFilterFactory extends AbstractGatewayFilterFactory<MyRouterGatewayFilterFactory.Config>{
    public MyRouterGatewayFilterFactory(){
        super(MyRouterGatewayFilterFactory.Config.class);
    }
    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter(){
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println(config.getName()+"----"+config.getPwd());
                return chain.filter(exchange);
            }
        };
    }

    public static class Config {
        private String name;
        private String pwd;

        public Config() {
        }

        public String getName() {
            return name;
        }

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

        public String getPwd() {
            return pwd;
        }

        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    }
}

步骤二:配置application.yml

server:
  port: 2324
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8888/eureka/
spring:
  application:
    name: gatewayserve
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启当前服务注册与发现功能
          lower-case-service-id: true
      routes:
        - id: gateway01 #自定义的唯一标识,不能重复
          uri: lb://EUREKACLIENT # 负载均衡,EUREKACLIENT为代理项目名
          predicates: #谓词
            - Path= /sxt/**
          filters: #定义过滤器
            - StripPrefix=1 #下游地址跳转的时候进行增加/去除某些配置。
            - name: MyRouter
              args:
                name: zs #全局过滤器中的name参数
                pwd: 123456 #全局过滤器中的pwd参数

执行结果如下:
访问http://localhost:2324/sxt/demo01
image.png