微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

SpringCloud H版&alibaba之Gateway

@H_502_1@

SpringCloud(H版&alibaba)之Gateway篇

服务网关

Gateway和Zuul说明:

Zuul由于内部出现重大分歧,已经没落,新一代的服务网关是Gateway,只学这个

Gateway 并非netflix开发的,而是spring 社区自己开发的。

Gateway是什么

官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

面试可能会问,是什么,为什么选这个

是什么?

Cloud全家桶中有个很重要的组件就是网关,在1.x版本中采用zuul;

2.x版本中,SpringCloud自己研发了一个网关代替Zuul,那就是Gateway.

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

为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty.

提供 安全,监控/指标,限流。

为什么选择Gateway

一、基于异步非阻塞模型上进行开发的,性能方面不需要担心;稳定,Zuul 2 进入了维护期。

二、特性

​ 动态路由:能够匹配任何请求属性

​ 可以对路由指定 Predicate (断言) 和 Filter (过滤器);

​ 集成Hystrix 的断路器功能

​ 集成 Spring Cloud 服务发现功能

​ 易于编写的 Predicate (断言) 和 Filter (过滤器);

​ 请求限流功能

支持路径重写;

三、spring cloud gateway 还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验,Zuul阻塞模型,性能要慢,gateway非阻塞模型,是Zuul速度的1.6倍

Gateway工作流程

三大核心概念

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

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

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

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

额外的知识:一个请求的流程:

外部请求 ——> 负载均衡——> 网关 ——> 微服务

官网总结流程

​ 客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。

Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行 业务逻辑,然返回。

过滤器可能会在发送代理请求之前(pre)或之后(post)执行业务逻辑。

Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,

在 “post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

核心逻辑

路由转发 + 执行过滤器链

入门配置

新建Module : cloud-gateway-gateway9527

POM

<dependencies>
        <!--Gateway依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--需要注册进eureka-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

        <dependency>
            <groupId>com.huawei.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

    </dependencies>

YML

server:
  port: 9527
spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

业务类

​ 无

主启动类

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

9527网关如何做路由映射

​ cloud-provider-payment8001看看controller的访问地址,get / lb

​ 假如我们目前不想暴露8001端口,希望在8001外面套一层9527

YML新增网关配置

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh    #payment_route    路由的ID,没有固定规则但要求唯一,建议配合服务名
          url: http://localhost:8001   #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**     #断言,路径相匹配的进行路由

        - id: payment_routh2   #payment_route  #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001   #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**      #断言,路径相匹配的进行路由

测试

启动eureka服务注册中心 7001

启动服务提供者 cloud-provider-payment8001

启动网关9527

debug:

​ 启动报错:

Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.

​ gateway 不需要 spring cloud 的web 和actuator这两个组件,去除

一个bug:

Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'spring.cloud.gateway' to org.springframework.cloud.gateway.config.GatewayProperties Failed:

Property: spring.cloud.gateway.routes[0].uri
Value: null
Reason: 不能为null

解决方法,将yml中 routes的 url 改成uri

routes:
  - id: payment_routh    #payment_route    路由的ID,没有固定规则但要求唯一,建议配合服务名
    uri: http://localhost:8001   #匹配后提供服务的路由地址
    predicates:
      - Path=/payment/get/**     #断言,路径相匹配的进行路由

  - id: payment_routh2   #payment_route  #路由的ID,没有固定规则但要求唯一,建议配合服务名
    uri: http://localhost:8001   #匹配后提供服务的路由地址
    predicates:
      - Path=/payment/lb/**      #断言,路径相匹配的进行路由

访问说明

添加网关前:http://localhost:8001/payment/get/21

添加网关后:http://localhost:9527/payment/get/21

结果:成功通过9527端口访问到

yml配置说明

端口和路径都可以重新配置,这样可以隐藏系统真实的端口和路径

​ predicates:

​ - Path= 这里配置地址,对应controller里的方法上的 Mapping 里的值 ** 代表匹配符

Gateway配置路由的两种方式

Gateway网关路由有两种配置方式:

第一种:在配置文件yml中配置 例如上面的配置

第二种:代码中注入RouteLocator的Bean 配置类的方式

​ 例如:通过9527网关访问到百度的网页

​ 编码:cloud-gateway-gateway9527

​ 业务实现:

@Configuration
public class GatewayConfig {
    /**
     *  配置了一个id 为path_route_huawei的路由规则,
     *  当访问地址  http://localhost:9527/guonei  时,会自动转发到地址 http://news.baidu.com/guonei
     */
    @Bean
    public RouteLocator customrouteLocator (RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        routes.route("path_route_huawei", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
        return routes.build();
    }

}

其他参考文章

https://blog.csdn.net/tianyaleixiaowu/article/details/83412301?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162196452916780255275894%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=162196452916780255275894&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~rank_v29-2-83412301.pc_search_result_cache&utm_term=gateway%E7%9A%84%E8%B7%AF%E7%94%B1%E9%85%8D%E7%BD%AE&spm=1018.2226.3001.4187

Gateway配置动态路由

存在的问题

​ 1.地址被写死 2. 只写了一台机器的,现实中微服务不可能这样,又要做负载均衡了

地址写死,这跟之前做负载均衡的效果相反,之前做服务调用时是通过服务名找地址然后进行负载均衡。

解决

​ 2.认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

启动: 一个eureka7001 + 两个服务提供者 8001/8002

POM: 增加一个

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

YML:

增加网关的discovery部分,再将uri地址由写死的改成从服务中心动态获取

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true   #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh    #payment_route    路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service   #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**     #断言,路径相匹配的进行路由

        - id: payment_routh2   #payment_route  #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service   #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**      #断言,路径相匹配的进行路由

测试:http://localhost:9527/payment/lb 8001/8002两个端口切换

eureka本来就整合了ribbon,这样配置后,会实现轮询 8001和8002自动切换

Gateway常用的Predicate

是什么

当启动9527时,后台打印了

​ Loaded RoutePredicateFactory [After]

​ Loaded RoutePredicateFactory [Before]

​ Loaded RoutePredicateFactory [Path]

​ Loaded RoutePredicateFactory [Weight]

之前配置的

predicates:
  - Path=/payment/get/**     #断言,路径相匹配的进行路由

这里的 path 只是其中一种配置

Route Predicate Factories这个是什么东西?

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#the-before-route-predicate-factory

​ 查看官网4.1 到4.11部分,有如何配置及意义

​ 可以根据不同的要求进行匹配。多种进行组合,并通过逻辑and.

常用的Route Predicate

The After Route Predicate Factory

spring:  
  cloud:    
    gateway:      
      routes:      
      - id: after_route        
        uri: https://example.org        
        predicates:        
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]

This route matches any request made after Jan 20, 2017 17:42 Mountain Time (Denver).

在美国丹佛时间 xxxx 之后开始匹配 (官网)

手测案例:

predicates:
  - Path=/payment/lb/**      #断言,路径相匹配的进行路由
  - After=2021-05-26T11:22:00.789+08:00

表示东八区时间 2021-05-26T11:22:00.789 之后进行匹配

测试成功

时区也可以写上具体时区名字类似官网案例: 东八区时间可以写 [Asia/Shanghai]

The Before Route Predicate Factory

predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]

This route matches any request made before Jan 20, 2017 17:42 Mountain Time (Denver).

The Between Route Predicate Factory

predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

This route matches any request made after Jan 20, 2017 17:42 Mountain Time (Denver) and before Jan 21, 2017 17:42 Mountain Time (Denver). This Could be useful for maintenance windows.

The Cookie Route Predicate Factory

predicates:
- Cookie=chocolate, ch.p

This route matches requests that have a cookie named chocolate whose value matches the ch.p regular expression.

打开cmd

发送请求

curl http://localhost:9527/payment/lb --cookie "chocolate=ch.p"

得到结果。 一旦配置了Cookie ,发送请求时就必须带上,否则404

The Header Route Predicate Factory

predicates:
  - Header=X-Request-Id, \d+

This route matches if the request has a header named X-Request-Id whose value matches the \d+ regular expression (that is, it has a value of one or more digits).

两个参数:一个属性名称一个是正则表达式,如果这个属性的值和正则表达式匹配则执行。

配置后发送curl请求

curl http://localhost:9527/payment/lb -H "X-Request-Id:123"

-H 表示请求头 后面是名称和值

测试成功 得到端口

The Host Route Predicate Factory

predicates:
- Host=**.somehost.org,**.anotherhost.org

This route matches if the request has a Host header with a value of www.somehost.org or beta.somehost.org or www.anotherhost.org.

The Method Route Predicate Factory

predicates:
- Method=GET,POST

This route matches if the request method was a GET or a POST.

The Path Route Predicate Factory

predicates:
- Path=/red/{segment},/blue/{segment}

This route matches if the request path was, for example: /red/1 or /red/blue or /blue/green.

The Query Route Predicate Factory

predicates:
- Query=green

The preceding route matches if the request contained a green query parameter.

The RemoteAddr Predicate Factory

predicates:
- RemoteAddr=192.168.1.1/24

This route matches if the remote address of the request was, for example, 192.168.1.10.

The Weight Route Predicate Factory

predicates:
- Weight=group1,2

This route would forward ~80% of traffic to weighthigh.org and ~20% of traffic to weighlow.org

Gateway的Filter

是什么

​ 路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。

Spring Cloud Gateway的Filter

​ 生命周期:

​ pre 之前

​ post 之后

​ 种类:两种类型

​ GatewayFilter:有31个

​ https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories 官网资料看着配

​ GlobalFilter:全局的

常用的GatewayFilter

filters:      #与predicates是平级的
  - AddRequestParameter=X-Request-Id,1024  #过滤器工厂会在匹配的请求头上加上一对请求头,名称为
X-Request-Id值为1024

自定义过滤器

自定义全局过滤器GlobalFilter

​ 两个主要接口介绍:implements GlobalFilter, Ordered

​ 能干啥? 全局日志记录,统一网关鉴权,。。。

案例:

@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("******************come in MyLogGatewayFilter: " + new Date());
        //获取参数 uname (用户名),检验是否为空,为空就拒绝,直接返回
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null) {
            log.info("**************用户名为null,非法用户,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getorder() {
        //表示过滤器优先级,一般数字越小,优先级越高
        return 0;
    }
}

如果直接在浏览器输入进行测试,要把之前配置的- Header 注释掉,浏览器地址栏输入没法配

浏览器地址栏输入:http://localhost:9527/payment/lb?uname=z123 测试成功

输入http://localhost:9527/payment/lb?uname= 也成功

输入http://localhost:9527/payment/lb 失败

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐