微服务学习之Gateway服务网关【Hoxton.SR1版】

目录

1 Gateway是什么

2 能干嘛

3 Gateway特性

4 Gateway与Zuul的区别

5 Gateway三大核心概念

5.1 路由Route

5.2 断言Predicate

5.3 过滤器(Filter)

5.4 Gateway工作流程

6 Gateway代码实操

6.1 基本环境搭建

6.1.1 pom依赖

6.1.2 application.yml(yml配置方式配置路由)

6.1.3 主启动类

6.1.4 服务提供者

6.2 Gateway测试

6.2.1 yml方式配置路由测试

6.2.2 代码中注入RouteLocator的Bean的方式配置路由测试

6.2.3 动态路由配置

6.2.4 断言Predicate

6.2.5 Route Filter路由过滤器

7 总结


1 Gateway是什么

Gateway是一个在Spring生态系统之上构建的API网关,包括:Spring 5,Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注点,例如:安全性,监视/指标和弹性。(官网介绍

SpringCloud全家桶中有一个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关,但在2.x版本中,Netflix对Zuul的升级工作一直跳票,最终SpringCloud自己研发了Gateway用来替代Zuul。Netflix很多组件(Eureka、Hystrix等)都进入维护阶段,选择Gateway做网关是更有保障的,毕竟是SpringCloud团队自己开发的。

SpringCloud Gateway作为SpringCloud生态系统中的网关,目标是替代Zuul。在SpringCloud2.0以上版本中,没有对新版本的Zuul2.0最新高性能版本进行集成,仍然还是使用zuul1.x非Reactor模式的老版本。为了提升网关的性能,SpringCloud Gateway是基于WebFlex框架实现的,而WebFlex框架底层使用了Reactor模式通信框架Netty。

官网Gateway的工作原理图:

Spring Cloudç½å³å¾

2 能干嘛

反向代理、鉴权、流量控制、熔断、日志监控等等。

微服务中网关处于什么位置?

3 Gateway特性

1 ),基于Spring 5,Spring Boot 2和Project Reactor进行构建。

2),动态路由能够匹配任何请求属性。

3),可以对路由指定Predicate(断言)和Filter(过滤),易于编写Predicate和Filter。

4),集成Hystrix的断路器功能。

5),集成SpringCloud的服务发现功能。

6),请求限流功能。

7),支持路径重写。

4 Gateway与Zuul的区别

5 Gateway三大核心概念

5.1 路由Route

路由是构建网关的基本模块,它由ID、目标URL、一系列的断言、过滤器组成,如果断言为true,那么匹配该路由。

5.2 断言Predicate

参考Java 8 Function Predicate,开发人员可以匹配Http请求中的所有内容(例如请求头、请求参数),如果请求与断言相匹配则进行路由。

5.3 过滤器(Filter)

过滤器是Spring框架中GatewayFilter的实例,使用过滤器可以实现在请求被路由前或者之后对请求进行修改。

5.4 Gateway工作流程

路由转发+执行过滤器链。

6 Gateway代码实操

Gateway网关路由有两种配置方式:yml文件配置、代码中注入RouteLocator的Bean。

6.1 基本环境搭建

6.1.1 pom依赖

在父工程下新建一个名为cloud-gateway-gateway9527的module,对应的pom依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud2020</artifactId>
        <groupId>com.bighuan.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-gateway-gateway9527</artifactId>

    <dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- Eureka client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--引入自定义的api通用包,可以使用Payment支付Entity-->
        <dependency>
            <groupId>com.bighuan.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>com.bighuan.springcloud.GatewayMain9527</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

6.1.2 application.yml(yml配置方式配置路由)

application.yml文件配置如下。关于路由配置可以参考官网:id表示路由的ID,没有固定的规则,但是一定要唯一,可以配合服务名来配置;uri表示匹配后提供服务的路由地址;predicates下的Path是为了匹配对应的路径。

关于注册中心的相关配置,都在此前的博客中有相关记录,不再赘述。

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh          # payment_route 路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://127.0.0.1:8001 # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**    # 断言,路径相匹配的进行断言
        - id: payment_routh2          # 路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://127.0.0.1:8001 # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**    # 断言,路径相匹配的进行断言
eureka:
  instance:
    hostname: cloud-gateway-service
  client: # 服务提供者的provider注册进eureka的服务列表里
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka

6.1.3 主启动类

主启动类并没有特殊的配置,比较简单。

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

6.1.4 服务提供者

在yml文件中关于路由的配置那一块,uri和predicates下的Path等配置都是为了指向服务提供者。服务提供者相关的博客可参考此前的博客,为行文方便,只粘贴出对应的controller。

 @GetMapping(value = "/payment/lb")
    public String getPaymentLB(){
        return serverPort;
    }

    @GetMapping(value = "/payment/get/{id}")
    public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id) {

        Payment payment = paymentService.getPaymentById(id);
        log.info("*****查询结果*****:" + payment+",server.port:"+serverPort);
        if (payment != null) {
            return new CommonResult(200, "查询成功,serverPort:"+serverPort, payment);
        } else {
            return new CommonResult(444, "没有对应记录,查询ID:"+id+","+"serverPort:"+serverPort, null);
        }
    }

6.2 Gateway测试

6.2.1 yml方式配置路由测试

分别启动注册中心Eureka7001、服务提供者8001、Gateway9527,浏览器分别访问:

http://127.0.0.1:9527/payment/get/31http://127.0.0.1:9527/payment/lb

发现都成功返回了对应的数据,前者需要查询数据库,后者则直接返回服务提供者的端口8001。通过Gateway9527,将服务提供者隐藏起来,对外界是不可见的,通过服务网关的路由就可以访问。

6.2.2 代码中注入RouteLocator的Bean的方式配置路由测试

代码配置方式可参考官网的配置,如下:

本文配置类代码如下,特别注意的是,需要让此类可以被主启动类扫描到。

//代码配置: 配置路由的第二种方式
@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        RouteLocatorBuilder.Builder routes = builder.routes();

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

同样,分别启动注册中心7001、服务提供者8001(只测试代码配置路由,不需要启动也可以)、Gateway9527,访问http://127.0.0.1:9527/guonei,页面跳转到到了国内百度新闻的页面。

6.2.3 动态路由配置

以上的测试中,只有一个服务提供者8001,如果不止一个服务提供者8001,还有服务提供者8002、8003,每个都要在配置文件中配置一次的话,那么是不现实的。动态路由就可以解决这个问题。

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

动态路由的配置文件为,application-dynamic.yml,配置如下:

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh          # payment_route 路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://127.0.0.1:8001 # 匹配后提供服务的路由地址
          uri: lb://CLOUD-PAYMENT-SERVICE # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**    # 断言,路径相匹配的进行断言
        - id: payment_routh2          # 路由的ID,没有固定规则但要求唯一,建议配合服务名
          # uri: http://127.0.0.1:8001 # 匹配后提供服务的路由地址
          uri: lb://CLOUD-PAYMENT-SERVICE # 匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/**   # 断言,路径相匹配的进行断言
eureka:
  instance:
    hostname: cloud-gateway-service
  client: # 服务提供者的provider注册进eureka的服务列表里
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka

说明:

1)spring.cloud.gateway.discovery.locator.enabled设置为true,开启从注册中心动态创建路由的功能,利用微服务名进行路由。

2)uri的协议为lb,表示基于服务注册的负载均衡,即启动Gateway的负载均衡功能,CLOUD-PAYMENT-SERVICE是服务提供者的微服务名。

测试步骤:启动一个注册中心Eureka7001、分别启动服务提供者8001和8002、启动Gateway9527,然后连续访问:http://127.0.0.1:9527/payment/lb,返回结果则为8001、8002交替出现,说明动态路由配置成功,而且负载均衡也实现了。

6.2.4 断言Predicate

SpringCloud Gateway包含许多内置的Route Predicate工厂,所有这些Predicate都与HTTP请求的不同属性相匹配,多个Predicate工厂可以进行组合。可以理解为Predicate就是 Sql语句中Where后的条件,可以有多个条件进行条件限制。

SpringCloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route对象。官网可以看到,至少内置了11中Route Predicate Factory。其中,第8种Path Route Predicate Factory前文已经接触过了,主要用来匹配路径。

 

在application-dynamic.yml的payment_routh2的predicates下增加after配置,表示需要在该时间之后才能成功匹配路由匹配。

- After=2020-03-28T17:25:10.065+08:00[Asia/Shanghai]

这种特殊的时间可以通过ZoneDateTime来获取

        ZonedDateTime now = ZonedDateTime.now();
        // 2020-03-28T16:51:32.977+08:00[Asia/Shanghai]
        System.out.println(now);

分别启动项目,在设置的after时间之前访问,会报错。

当时间过了after时间之后,就可以正常访问了。

Before、Between Route Predicate Factory的配置方式类似。

还是在application-dynamic.yml的payment_routh2的predicates下增加cookie配置。

- Cookie=username,bighuan

分别启动项目,一开始请求时不带cookie,直接返回404;带上cookie后,访问正常。

6.2.5 Route Filter路由过滤器

路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器适用于特定路由。Spring Cloud Gateway包括许多内置的GatewayFilter工厂。Spring Cloud Gateway内置了多种路由过滤器,它们都GatewayFilter工厂类产生。(官网gatewayfilter-factories,配置参考官网即可)

Filter的生命周期有两种:pre、post。

在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志输出,流量监控等。(注:这段话来自某大佬博客)

Filter的种类有两种:GatewayFilter、GlobalFilter。

自定义全局GlobalFilter过滤器

自定义全局过滤器,需要实现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());
        String uname=exchange.getRequest().getQueryParams().getFirst("uname");
        if(uname == null){
            log.info("*****用户名为空,是非法用户,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    /**
     * 加载过滤器的优先级,值越小,优先级越高
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

测试:使用application.yml启动Gateway9527,访问http://127.0.0.1:9527/payment/lb?uname=bighuan可以通过获得结果,http://127.0.0.1:9527/payment/lb则会被拦截。

7 总结

虽然写这篇博客花的的时间比较长,但坚持坚持,会有收获的!

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章