一样是网关gateway取代了zuul,咱们项目使用了这么多gateway特性!你有没有中标|Java 开发实战

这是我参与更文挑战的第1天,活动详情查看: 更文挑战html

本文正在参加「Java主题月 - Java 开发实战」,详情查看 活动连接java

相关文章

主流的四种限流策略,我均可以经过redis实现react

分布式系列之网关zuul包揽全局--不再用东拼西凑各个接口啦web

特性

gateway的诞生是由于zuul2.0一直跳票,既然这样gateway能够说是zuul的替代品。既然是替代品功能确定是包含了zuul的。redis

  • 上面zuul的章节主要介绍了网关的动态路由。gateway确定也是支持的。
  • 除了路由外zuul还有四种过滤器一样gateway也有相应的过滤器
  • zuul内部携带hystrix , gateway除了和hystrix整合实现熔断、降级、限流外,内部基于令牌桶实现限流
  • zuul、gateway都支持服务发现进行路由转发

前置条件

SpringCloud Gateway是基于webflux的非阻塞式的网关。 因此gateway的环境要求 Springboot 2.x+、spring webflux、project reactor算法

由于是非阻塞式,因此和咱们以前的阻塞式框架会有所不一样。spring

专业名词

名词 解释
Route 网关的基本组成部分。它由一个ID、一个目标URI、一组谓词和一组过滤器定义。若是聚合谓词为真,则匹配路由
Predicate 这是一个Java 8函数谓词。输入类型是Spring Framework serverwebeexchange。这容许您匹配HTTP请求中的任何内容,好比头或参数
Filter 这些是使用特定工厂构造的GatewayFilter实例。发送下游请求以前或以后修改请求和响应

快速入门

点我官网快速开始api

  • 新建模块geteway 并制定端口9091数组

  • 引入坐标安全

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
复制代码
  • 配置路由
spring:
  cloud:
    gateway:
      routes:
        - id: route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
复制代码
  • 接口测试 : 咱们访问http://localhost:9091/payment/get/1 会出现8001payment的信息

  • 下面咱们详细来看看gateway的路由规则

路由规则

Springcloud网关匹配路由做为Spring WebFlux HandlerMapping基础设施的一部分。Spring Cloud Gateway包括许多内置的路由前置工厂。全部这些都匹配HTTP请求的不一样属性。咱们能够将多个路由谓词工厂与逻辑和语句组合在一块儿

image-20210506135529574

  • 咱们也能够看出springcloud gateway内置了不少匹配工厂。咱们上面的案列就是经过PathRoutePredicateFactory来实现的

path

spring:
  cloud:
    gateway:
      routes:
        - id: route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
复制代码

query

spring:
  cloud:
    gateway:
      routes:
        - id: route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
            - Query=green
复制代码
  • http://localhost:9091/payment/get/1 接口将访问不了数据,必须添加green参数 http://localhost:9091/payment/get/1?green

  • 光有参数仍是不行,加入咱们要求green参数必须是数组。咱们能够以下配置

spring:
  cloud:
    gateway:
      routes:
        - id: route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
            - Query=green,\d+
复制代码
  • http://localhost:9091/payment/get/1?green=123s 不能访问 , http://localhost:9091/payment/get/1?green=123 则能够

datetime

  • 当咱们在开发限时抢购接口是就能够借助datetime功能来实现
spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: http://localhost:8001
          predicates:
            - Path=/payment/get/**
            - Query=green,\d+
            - After=2017-01-20T17:42:47.789-07:00[America/Denver]
复制代码

ip

- RemoteAddr=10.0.20.132/140
复制代码
  • 关于这个远程地址咱们实际上就是填写ip , 后面的斜杠表示一个范围ip , 上面的配置表示10.0.20.132-10.0.20.140 这个段的ip均可以访问

cookie

- Cookie=zxhtom, hello
复制代码
  • 请求中携带zxhtom=hello , 的cookie能够访问

服务发现

  • 在zuul中整合服务发现后有默认的路由规则的。一样的gateway也能够接入服务发现这里仍是以eureka为列。

  • 这里有关服务发现的配置我就再也不赘述了。在eureka专题已经介绍了,在hystrix、zuul等专题页有相关的配置,我就直接贴出gateway的开启配置

spring:
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
复制代码
  • 默认的路由规则是基于在eureka中注册名进行访问 。 好比访问payment 就是以下地址http://localhost:9091/cloud-payment-service/payment/get/2
  • 内部也是ribbon进行负载均衡的。有个小问题是gateway启动时获取服务列表后续好像没有在更新服务列表。

过滤器

  • 关于gateway内置过滤器真的是不少不少。咱们这里也不能一个一个的举例。下面咱们看看和zuul对应的几个过滤器。在看以前咱们先大概看看gateway内置有哪些

image-20210506184914527

  • 除了局部过滤器,gateway还内置几个全局过滤器

image-20210506185225830

RewiritePathGatewayFilterFactory

  • 还记得咱们在zuul专题中实现了默认路由转发规则吗,就是将CLOUD-PAYMENT-SERVICECLOUD-ORDER-SERVICE 这些微服务默认代理为paymentorder 的服务前缀。在gateway中他也为咱们提供了相同的功能即服务转发。可是他是针对具体的微服务的。咱们能够经过自定义全局过滤器来实现zuul中的功能。下面咱们来试试经过RewiritePathGatewayFilterFactory 实现它

  • 为了突出核心配置,这里我只放部分配置,须要完整的能够参考上下文。或者直接看源码。源码会在文章末尾放出

- id: order
          uri: lb://CLOUD-ORDER-SERVICE
          predicates:
            - Query=green
          filters:
            - RewritePath=/order/(?<segment>.*), /$\{segment}
复制代码
  • 和zuul的配置差很少,经过正则匹配咱们的请求,而后将咱们须要的部分进行抽取而后路由到真实服务进行调用

PrefixPathGatewayFilterFactory

  • 和上面``RewiritePathGatewayFilterFactory 相似而又不一样的是,PrefixPathGatewayFilterFactory 是在咱们匹配接口上统一加上前缀。好比以下配置
- id: pre_route
          uri: http://localhost:8001
          predicates:
            - Path=/getTimeOut/**
          filters:
            - PrefixPath=/payment
复制代码
  • http://localhost:9091/getTimeOut/123 访问这个接口的时候,咱们首先经过routes 中配置匹配到pre_route 而后在接口前添加payment而后路由到真实服务上。 这里须要注意的是咱们的匹配是从上至下的因此咱们配置的时候注意下顺序

StripPrefixGatewayFilterFactory

  • 这个过滤器的功能就是将咱们的接口进行分割,而后在路由。其实gateway为咱们提供了不少的过滤器使用起来也是很方便的。咱们基本上看官网提供的案列咱们就可以知道如何使用了。重要的咱们得了解他们内部的设计。设计才是王者
- id: pre_route
          uri: http://localhost:8001
          predicates:
            - Path=/zxhtom/**
          filters:
            - StripPrefix=2
复制代码
  • 访问http://localhost:9091/zxhtom/lqj/payment/getTimeOut/123 实际上会转发到http://localhost:8001/payment/getTimeOut/123上。注意咱们uri的协议,若是是lb表示是服务发现。这里咱们配置的是单节点

SetPathGatewayFilterFactory

- id: pre_route3
          uri: http://localhost:8001
          predicates:
            - Path=/hello/{segment}
          filters:
            - SetPath=/payment/get/{segment}
复制代码
  • 说白了仍是将请求从新转发。只不过这边更加的生硬。上述就是将http://localhost:9091/hello/123 转发到http://localhost:8081/payment/get/123

自定义网关过滤器

  • 上面咱们简单了解了gateway为咱们提供的过滤器。可是内置的永远是知足不了全部的需求的。不论是为了应对需求仍是知足本身的虚荣心。咱们都应该来看看若是实现本身的过滤器

GatewayFilterFactory

image-20210506190710838

  • gateway内置了不少网关过滤器。咱们只须要参照他们内置的过滤器实现就能够了。
  • 不知道你有没有注意到类如RewritePathGatewayFilterFactory 这些过滤器在配置的时候是配置成RewritePath 后面的GatewayFilterFactory是没有的。
  • 这是为何呢,咱们先不关这个咱们依葫芦画瓢。
  • 如今咱们有个需求须要实现登录验证过滤器。实现逻辑简单点就验证用户名和密码是否匹配。用户名密码在地址中配置

新建过滤器类

@Component
public class LoginPathGatewayFilterFactory extends AbstractGatewayFilterFactory<LoginPathGatewayFilterFactory.Config> {
    public LoginPathGatewayFilterFactory() {
        super(Config.class);
    }
    @Override
    public GatewayFilter apply(Config config) {
        return (exchage,chain)->{
            String userName = config.getUserName();
            String password = config.getPassword();
            String requestUserName = exchage.getRequest().getQueryParams().getFirst("userName");
            String requestPassword = exchage.getRequest().getQueryParams().getFirst("password");
            if (userName.equals(requestUserName) && password.equals(requestPassword)) {
                return chain.filter(exchage);
            } else {
                throw new RuntimeException("用户名错误密码。。。。");
            }
        };
    }
    public static class Config {

        private String userName;

        private String password;

        public String getUserName() {
            return userName;
        }

        public Config setUserName(String userName) {
            this.userName = userName;
            return this;
        }

        public String getPassword() {
            return password;
        }

        public Config setPassword(String password) {
            this.password = password;
            return this;
        }
    }
}
复制代码

新增配置

- id: pre_route4
          uri: http://localhost:8001
          predicates:
            - Path=/login/{segment}
          filters:
            - name: LoginPath
              args:
                userName: zxhtom
                password: test
            - SetPath=/payment/get/{segment}
复制代码

测试

image-20210506193533580

  • 当咱们的用户名和密码不一致时就会抛出异常。若是用户名和密码和咱们指定的帐户相同那么就会放心至第二个过滤器进行路由转发。

GatewayFilter

  • 在上面第一种方式中咱们可以观察到最终是生成GatewayFilter 对象。实际上GatewayFilter 才是真正参与过滤的对象。

构建

@Component
public class CustomLoginGatewayFilter implements GatewayFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("我进过滤器啦。。。");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
复制代码

注册

@Configuration
public class RouteConfig {
    @Bean
    public RouteLocator customRouteLocator(CustomLoginGatewayFilter customLoginGatewayFilter,RouteLocatorBuilder builder) {
        return builder.routes()
                .route("path_route", r -> r.path("/baidu")
                        .uri("https://www.baidu.com")
                .filter(customLoginGatewayFilter))
                .build();
    }
}
复制代码

测试

image-20210506194442468

自定义全局过滤器

image-20210507092458434

  • 仍是那句话,想要自定义得看内置是如何实现的。诺、WebsocketRoutingFilter 是gateway内置的,他的实现和网关过滤器其中一种方式同样。
@Slf4j
@Component
public class GlobalCustomRoutingFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("我是全局过滤器。。。。。。");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
复制代码
  • 编写java类注册到spring中,咱们在访问咱们的gateway路由接口就会发现有日志打印了。固然这里只是演示全局过滤器生效。具体在全局过滤器咱们能够用来作权限验证等等操做。
  • 在全局验证后咱们能够将登录用户信息写入到cookie中或者经过添加参数的方式传递到下游

过滤器名称

image-20210507100537676

  • 还记得咱们在网关过滤器那边说为何自定义名字要那么定义吗。看看上面这段源码你就理解了。在将过滤器已键值对注册到过滤器容器中

image-20210507101527587

限流

  • 关于限流笔者以前经过redis分别实现目前主流的四种限流算法。有兴趣的能够阅读下,阅读结束别忘记回到本文继续观看gateway哦。别走太远!!!

  • 上面咱们主要在介绍gateway的用法即合成方向。可是做为网站的门户性接口流量比其余服务都大的不少。其余服务正常状况咱们会依赖于hystrix 来实现对服务的降级等操做。一样咱们也能够在网关层面上加入hystrix来实现服务的高可用。 可是除了hystrix之外, gateway自己内置了限流算法。下面咱们来简单实现下限流。

  • 提到网关咱们就必定绕不开限流。网关的做用就是代理可是并非没有条件的任性代理。为何保护咱们的模块在网关中会对下游服务进行限流。有些接口处于安全和稳定考虑都会限制流量的

  • gateway实现限流也很简单。只须要咱们引入redis相关模块在使用内置过滤器就能够了

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>	
复制代码
@Component
public class HostAddrKeyResolver implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        //根據服务地址限流
        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
    }
    @Bean
    KeyResolver apiKeyResolver() {
            //按URL限流
            return exchange -> Mono.just(exchange.getRequest().getPath().toString());
            }

    @Bean
    KeyResolver userKeyResolver() {
        //按用户限流
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userName"));
    }
}
复制代码

image-20210531172056157

  • redis-rate-limiter.replenishRate : 令牌桶生成令牌的速度
  • redis-rate-limiter.burstCapaciry: 令牌桶容量
  • 下面咱们看看redis中实时存储数据状况。上面咱们经过jmeter等压测工具进行压力测试就会出现以下数据

image-20210531171802520

计数器

  • 计数器又分为固定时间窗口和滑动时间窗口两种策略。二者的区别其实就是时间间隔不同。本质上是没有区别。
  • 可是由于滑动时间窗口时间间隔很短,给咱们形成一种二者彻底不同的假象

固定时间窗口

image-20210508103957917

滑动时间窗口算法

漏铜

  • 漏桶算法就是提早准备了一个请求池,可以进入请求池就能够等待资源的分配。这样就解决了计数没法面对大流量的状况

令牌桶

  • 令牌桶是在漏桶基础上升级而来。咱们仔细想想正常请求仍是须要花费必定时间的有的重要接口甚至须要1~10秒执行时间。可是咱们生成令牌只是生成一串字符。和生成令牌相比接口的执行显然慢的多的多
  • 不知道读者有没有想过这样一种状况。漏桶算法在请求池满了以后忽然遇到大流量这个时候该怎么办。这个时候漏桶算法就只能无情的拒绝多余的请求。
  • 而令牌桶则不同了。令牌桶也有一样的池,可是内部存储的是生成好的令牌,一样在极限状况下若是令牌桶慢了遇到大流量会怎么样。
  • 在漏桶算法中遇到大流量就只能拒绝并且请求池状态跟更新很慢。可是令牌桶满了有大流量过来很快就会拿走令牌。接口的执行时间跟令牌桶就没有直接关系了。这个时候令牌桶中状态更新的很快。因此令牌桶更加能面对突如其来的大流量

整合hystrix

  • 还记得咱们以前的hystrix服务熔断、降级专栏吗。仔细想一想做为网关是否是比其余接口流量更大呢?那么如何对咱们网关的接口进行服务熔断等操做呢?
  • 笔者这里参考了写资料仅实现全局后备的实现

功能接入

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
复制代码

image-20210531170456893

  • 接入坐标后再咱们上面实现的基础上咱们直接添加过滤器就能够了。最后会将异常重定向到咱们事先写好的接口中。这个接口返回 咱们统一的数据结构
@RestController
@RequestMapping("/fallback")
public class FallbackController {

    @RequestMapping("")
    public String fallback(){
        return "error";
    }
}
复制代码

总结

  • 好了!关于网关部分笔者断断续续经过三篇文章分别从不一样框架,不一样角度解读。
  • zuul框架实现了咱们项目中经常使用到的网关代理功能、本文是经过gateway实现项目中的网关代理
  • 在加上前两天经过redis分别实现四种主流限流策略问题。上面提到的两篇文章收获颇丰



努力加油

大哥大姐,留个赞在走呗!原创属实不易