09.微服务网关组件之Gateway

Posted 潮汐先生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了09.微服务网关组件之Gateway相关的知识,希望对你有一定的参考价值。

微服务网关组件之Gateway

服务网关

概念

网关是统一服务的入口,可方便实现对平台众多服务接口进行管控

作用

  • 统一所有的微服务入口
  • 可以实现请求路由转发(router dispatcher)以及请求过程负载均衡
  • 访问服务的身份认证、防报文重发与数据篡改、业务鉴权、响应数据脱敏、流量与并发控制,甚至基于API调用的计量或者计费等

简单来说:网关 = 路由转发 + 过滤器

  • 路由转发:接收一切外界请求,转发到后端的微服务上去
  • 过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等

常用网关

微服务中用到的网关主要有两个:Netflix的zuul以及spring的Gateway

zuul

zuul是从设备和网站到Netflix流媒体应用程序后端的所有请求的前门。作为一个边缘服务应用程序,zul被构建为支持动态路由、监视、弹性和安全性。

目前zuul组件已经从1.0更新到2.0,但是作为springcloud官方不再推荐使用zuul2.0,但是依然支持zuul2。

Gateway

Gateway是一个在springmvc之上构建API网关的库。springcloud Gateway旨在提供一种简单而有效的方法来路由到api,并为api提供横切关注点,比如:安全性、监控/度量和弹性。

它是基于springboot2.x 和 spring webFlux 和 Reactor 构建 响应式异步非阻塞IO模型

上面我们说了springcloud官方推荐我们使用Gateway,所以本节我们主要着重Gateway,不关心zuul的相关内容

Gateway的实现

从上面网关的定义来看,我们知道网关作为独立的应用统一管理微服务的入口而不参与任何的业务逻辑,我们新建一个网关应用,搭配我们前面OpenFeign章节的CATEGORY服务和PRODUCT服务来一起演示网关的实现

1.新建Module

我们在springcloud_parent项目中新建Module - 06.springcloud_gateway,点击Finish

2.pom.xml

<?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>springcloud_parent</artifactId>
        <groupId>com.christy</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>06.springcloud_gateway</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- springboot依赖
        gateway为了效率使用webflux进行异步非阻塞模型的实现,因此和spring-boot-starter-web的web包冲突,
        这里我们注释掉该依赖,否则启动会报错
        -->
        <!--<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>-->

        <!-- 引入consul client依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!-- 引入健康检查依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--引入gateway网关依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>
</project>

3.GatewayApplication.java

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Author Christy
 * @Date 2021/6/10 22:23
 **/
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication 
    public static void main(String[] args) 
        SpringApplication.run(GatewayApplication.class, args);
    

4.application.yml

server:
  port: 8888

spring:
  application:
    name: GATEWAY
  cloud:
    consul:
      host: localhost
      port: 8500
    gateway:
      routes: 
        - id: category_router # 路由对象唯一标识
          uri: http://localhost:8890  # CATEGORY服务地址
          predicates:   # 断言 用来配置路由规则
            # -和Path之间有个空格,Path的首字母要大写,后面如果有多个路径可以使用英文状态下的逗号分隔,也可以使用通配符
            - Path=/category/**   # uri和断言的路径组合成的路径就是路由的最终地址:http://localhost:8890/category/**
              
        - id: product_router # 路由对象唯一标识
          uri: http://localhost:8891  # PRODUCT服务地址
          predicates: # 断言 用来配置路由规则
            # -和Path之间有个空格,Path的首字母要大写,后面如果有多个路径可以使用英文状态下的逗号分隔,也可以使用通配符
            - Path=/product/**   # uri和断言的路径组合成的路径就是路由的最终地址:http://localhost:8890/product/**

上面配置文件中配置了Gateway的相关设置,这个设置也可以在java文件中实现,如果同时在java文件和配置文件中配置,则以Java文件中的配置为准。具体配置如下所示

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @Author Christy
 * @Date 2021/6/10 22:37
 **/
@Configuration
public class GatewayConfig 
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) 
        return builder.routes()
                .route("category_route", r -> r.path("/category/**")
                        .uri("http://localhost:8890"))
                .route("product_route", r -> r.path("/product/**")
                        .uri("http://localhost:8891"))
                .build();
    

在Java文件中配置先关属性是不推荐的,我们把Java中的配置代码注释掉

5.测试

我们把上述提到的三个服务:CATEGORY、PRODUCT与GATEWAY启动,然后在浏览器输入http://localhost:8888/category/product/listhttp://localhost:8888/product/list看最终能否成功输出CATEGORY和PRODUCT服务中接口的返回结果

以上结果能成功输出说明我们的网关配置成功了

GateWay负载均衡

Demo存在的问题

上述demo中网关的配置方式在uri的属性中路径直接写死为服务的某一个节点,这样没有办法实现请求的负载均衡

如何实现负载均衡

1.准备工作

我们启动ProductApplication8892服务与ProductApplication8891组成集群,此时我们可以看到consul中的PRODUCT服务有两个实例

2.集成Ribbon

我们前面学习到的能够实现负载均衡的组件OpenFeignRibbon,我们知道OpenFeign的底层也是Ribbon,所以只要项目中依赖ribbon就可以实现负载均衡

我们知道,ribbon是从服务注册中心拉去已经注册的服务列表到本地,然后通过服务ID进行检索指定的服务,通过负载均衡策略(默认轮询)提供服务,如下图

3.application.yml

在配置文件中,GateWay搭配ribbon实现负载均衡的写法是固定的:uri:lb://SERVICEID,所以我们改写application.yml中有关PRODUCT的部分,使其实现负载均衡

server:
  port: 8888

spring:
  application:
    name: GATEWAY
  cloud:
    consul:
      host: localhost
      port: 8500
    gateway:
      routes:
        - id: category_router # 路由对象唯一标识
          uri: http://localhost:8890  # CATEGORY服务地址
          predicates:   # 断言 用来配置路由规则
            # -和Path之间有个空格,Path的首字母要大写,后面如果有多个路径可以使用英文状态下的逗号分隔,也可以使用通配符
            - Path=/category/**   # uri和断言的路径组合成的路径就是路由的最终地址:http://localhost:8890/category/**

        - id: product_router # 路由对象唯一标识
          uri: lb://PRODUCT # PRODUCT服务地址
          predicates: # 断言 用来配置路由规则
            # -和Path之间有个空格,Path的首字母要大写,后面如果有多个路径可以使用英文状态下的逗号分隔,也可以使用通配符
            - Path=/product/**   # uri和断言的路径组合成的路径就是路由的最终地址:http://localhost:8890/product/**

4.测试

我们重新启动GATEWAY服务,浏览器输入http://localhost:8888/product/list访问PRODUCT服务,可以看到确实按照默认的轮询策略实现了负载均衡

网关的断言和过滤

GateWay为我们提供了大量的断言过滤。可以好不夸张的说

GateWay(网关) = 断言(predicate) + 过滤(filter)

**断言:**当请求到达网关时,网关前置处理。满足断言放心请求,不满足断言立即返回

**过滤:**当请求满足断言的所有条件之后,会向后端服务转发,在向后端服务转发之前会经过一些过滤

断言(Route Predicate Factories)

我们上面说了GateWay内置了多个断言,如下图

我们这里捡几个比较常用的来说一下

1.The Path Route Predicate Factories

简称Path断言(路径断言),后面配上需要映射的路径;可以跳转到uri下指定的映射常用的写法- Path=path,比如在上面的demo中我们使用的方式:

predicates: 
	- Path=/category/**

2.The After Route Predicate Factories

简称After断言,后面加上datetime(ZoneDateTime),表示上面Path断言后配置的映射路径需要在指定的日期时间后才能生效;写法为- After=datetime,例如

predicates: 
	- Path=/category/**
	- After=2021-07-02T15:30:19.601+08:00[Asia/Shanghai] # 在2021-07-02 15:30:19之后生效

3.The Before Route Predicate Factories

简称Before断言,后面加上datetime(ZoneDateTime),表示上面Path断言后配置的映射路径需要在指定的日期时间前才能生效;写法为- Before=datetime,例如

predicates: 
	- Path=/category/**
	- Before=2021-07-02T18:30:19.601+08:00[Asia/Shanghai] # 在2021-07-02 18:30:19之前生效

4.The Between Route Predicate Factories

简称Between断言,后面加上两个datetime(ZoneDateTime),表示上面Path断言后配置的映射路径需要在指定的两个日期时间之间才能生效;写法为- Between=datetime1,datetime2,例如

predicates: 
	- Path=/category/**
	- Between=2021-07-02T15:30:19.601+08:00[Asia/Shanghai],2021-07-02T18:30:19.601+08:00[Asia/Shanghai] # 在2021-07-02 18:30:19之前生效

5.The Cookie Route Predicate Factories

简称Cookie断言,后面加上key,value,表示上面Path断言后配置的映射路径需要基于指定cookie的请求才能访问;写法为- Cookie=key,value,例如

predicates: 
	- Path=/category/**
	- Cookie=username,christy
  1. 这里如果想演示效果的话只能使用curl工具了,我们可以在命令行里面直接输入

    curl http://localhost:port/** --cookie "username=christy"即可看到效果

  2. Cookie的value也支持正则表达式,比如:- Cookie=username,[A-Za-z0-9]+

6.The Header Route Predicate Factories

简称Header断言,后面加上key,value,表示上面Path断言后配置的映射路径需要基于指定的Header请求才能访问;写法为- Header=key,value,例如

predicates: 
	- Path=/category/**
	- Cookie=X-Request-Id, \\d+

7.The Method Route Predicate Factories

简称Method断言,后面加上value,表示上面Path断言后配置的映射路径需要基于指定的请求方式才能访问;写法为- Header=value…,例如

predicates: 
	- Path=/category/**
	- Header=GET,POST

更多断言(Predicate)详见官方文档

过滤(Route filters)

我们上面说了网关过滤是在想后端服务转发之前进行的过滤,Gateway也为我们提供了大量的Filter,如下图

我们下面来看一些比较常用的网关过滤器

1.The AddRequestHeader GatewayFilter Factory

用来给路由对象的所有请求加入指定请求头信息,常用写法:- AddRequestHeader=key,value。比如:

predicates: 
	- Path=/category/**
filters:
	- AddRequestHeader=username, christy

2.The AddRequestParameter GatewayFilter Factory

用来给路由对象的所有请求加入指定请求参数,常用写法:- AddRequestParameter =key,value。比如:

predicates: 
	- Path=/category/**
filters:
	- AddRequestParameter=username, christy

3.The AddResponseHeader GatewayFilter Factory

用来给路由对象的所有请求加入指定响应头,常用写法:- AddResponseHeader =key,value。比如:

predicates: 
	- Path=/category/**
filters:
	- AddResponseHeader=username, christy

4.The PrefixPath GatewayFilter Factory

用来给路由对象的所有请求加入指定前缀,常用写法:- PrefixPath=path。比如:

predicates: 
	- Path=/category/**
filters:
	- PrefixPath=/christy

添加了指定前缀的访问路径为uri + prefix path + path

5.The StripPrefix GatewayFilter Factory

用来给路由对象的所有请求的url去掉指定的n级前缀,常用写法- StripPrefix=n。比如:

predicates: 
	- Path=/category/**
filters:
	- StripPrefix=1

比如我们浏览器的访问地址是/product/list,但是我们后端接口实际地址是/list,此时我们只要设置StripPrefix=1,我们我们浏览器输入的/product/list转发到后端时的接口地址就为/list

更多过滤器(Filter)详见官方文档

自定义全局过滤器

CustomerGlobalFilter

自定义全局过滤器比较简单,按照官网文档我们自定义一个CustomerGlobalFilter,内容如下

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class CustomerGlobalFilter implements GlobalFilter, Ordered 

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) 
        System.out.println("进入自定义的filter");
        if(exchange.getRequest().getQueryParams().get("username")!=null)
            System.out.println("用户身份信息合法,放行请求继续执行!!!");
            return chain.filter(exchange);
        
        System.out.println("非法用户,拒绝访问!!!");
        return exchange.getResponse().setComplete();
    

    /**
     * order 排序  int数字:用来指定filter执行顺序  
     * 默认顺序按照自然数字进行排序  -1 在所有filter执行之前执行l
     */
    @Override
    public int getOrder() 
        return -1;
    

测试

我们在浏览器中输入http://localhost:8888/category/product/list,我们可以看到请求被拦截,没有转发到后端

当我们在浏览器输入http://localhost:8888/category/product/list?username=123,请求能转发到后端正常访问接口

查看网关路由规则列表

Gateway提供路由访问规则列表的web界面,但是默认是关闭的,如果想要查看服务路由规则可以在配置文件中开启

server:
  port: 8888

spring:
  application:
    name: GATEWAY
  cloud:
    consul:
      host: localhost
      port: 8500
    gateway:
      routes:
        - id: category_router # 路由对象唯一标识
          uri: http://localhost:8890  # CATEGORY服务地址
          predicates:   # 断言 用来配置路由规则
            # -和Path之间有个空格,Path的首字母要大写,后面如果有多个路径可以使用英文状态下的逗号分隔,也可以使用通配符
            - Path=/category/**   # uri和断言的路径组合成的路径就是路由的最终地址:http://localhost:8890/category/**

        - id: product_router # 路由对象唯一标识
          uri: lb://PRODUCT # PRODUCT服务地址
          predicates: # 断言 用来配置路由规则
            # -和Path之间有个空格,Path的首字母要大写,后面如果有多个路径可以使用英文状态下的逗号分隔,也可以使用通配符
            - Path=/product/**   # uri和断言的路径组合成的路径就是路由的最终地址:http://localhost:8890/product/**

management:
  endpoints:
    web:
      exposure:
        exclude: "*"    # 开启所有web端点暴露

我们重启Gateway服务,浏览器输入http://localhost:8888/actuator/gateway/routes,如下图

以上是关于09.微服务网关组件之Gateway的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud Alibaba全家桶——微服务网关Gateway组件

Java之 Spring Cloud 微服务搭建网关SpringCloud Gateway微服务网关GateWay(第三个阶段)SpringBoot项目实现商品服务器端是调用

Java之 Spring Cloud 微服务搭建网关SpringCloud Gateway微服务网关GateWay(第三个阶段)SpringBoot项目实现商品服务器端是调用

Spring Cloud微服务网关Gateway组件

#私藏项目实操分享#SpringCloud技术专题「Gateway网关系列」微服务网关服务的Gateway组件的原理介绍分析

微服务中的网关