在cloud中还有一个服务网关Zuul,不过现在已经过时了,如果使用的是F之前的版本的cloud或者还在使用zuul的话,可以看我的另一篇文章

1. 概述

1.1 简介

        Gateway是在Spring生态系统之上构建的API网关服务,基于Spring5,Spring Boot2和Project Reactor等技术。

        SpringCloud Gateway 是Spring Cloud的一个全新项目,基于Spring5.0+SpringBoot2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。

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

        Spring Cloud Gateway的目标提供统一的路由方式且基于Filter链的方提供了网关基本的功能,例如:安全,监控/指标,和限流。

         SpringCloud Gateway使用的是WebFlux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架,对于高并发与非阻塞式有很大的优势

1.2 作用

1)反向代理
2)鉴权
3)流量控制
4)熔断
5)日志监控

1.3 微服务架构中网关所处的位置

服务网关的作用 服务型网关_微服务

1.4 Gateway的特性

1)基于Spring Framework5,Project Reactor和spring Boot2.0进行构建
2)动态路由:能够匹配任何请求属性
3)可以对路由指定Predicate(断言)和Filter(过滤器)
4)集成Hystrix的断路器功能
5)集成spring Cloud服务发现功能
6)易于编写的Predicate(断言)和Filter(过滤器)
7)请求限流功能
8)支持路径重写

1.5 Webflux简介

        传统的web框架,比如说:Struts2,SpringMVC等都是基于Servlet APi与servlet容器基础上运行的,但是,在Servlet3.1之后有了异步非阻塞的支持。而WebFlux是一个典型的非阻塞异步的框架,它的核心是基于Reactor的相关API实现的。相对于传统的web框架来说,它可以运行在诸如Netty、Undertow及支持Servlet3.1的容器上。非阻塞式+函数式编程(spring5.0要求JDK1.8以上)
        Spring WebFlux是spring5.0引入的新的响应式框架,区别于Spring MVC,它不需要依赖Servlet API,它是完全异步非阻塞的,并且基于Reactor来实现响应式流规范。

2. GateWay三大核心概念

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

2.1 Route(路由)

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

2.2 Predicate(断言)

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

2.3 Filter(过滤)

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

3. GateWay的工作流程

服务网关的作用 服务型网关_spring_02

        客户端向Spring Cloud Gateway发出请求,然后在Gateway Handler Mapping中找到与请求相匹配的路由,将其发送到Gateway Web Handler。Handler再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
        过滤器之间可以在发送代理请求之前(pre)或者之后(post)执行业务逻辑,Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。

路由转发+执行过滤器链

4. 入门配置

新建一个工程cloud-gateway-gateway9527

4.1 pom文件

gateway坐标

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

完整文件

<?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.bjc.cloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

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

    <dependencies>
        <!-- Hystrix -->
        <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>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>
        <!-- junit测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>com.bjc.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

</project>

注意:在gateway中,不需要web启动类和actuator,即不需要配置以下内容

<!-- 添加bootweb启动依赖与健康监控依赖 -->
<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>

 

4.2 yml配置文件

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

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

4.3 启动类

package com.bjc.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

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

4.4 yml新增网关配置

在yml中新增如下配置

服务网关的作用 服务型网关_服务网关的作用_03

完整的yml

server:
  port: 9527
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      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/**

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

4.4 测试

1)分别启动eureka服务、provider(8001)服务和网关(9527)服务

2)使用网关访问

服务网关的作用 服务型网关_服务网关的作用_04

5. Gateway配置路由

5.1 静态路由配置的两种方式

5.1.1 在yml配置文件中配置

见上面的案例

5.1.2 代码中注入RouteLocator的Bean

package com.bjc.cloud.config;

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

import java.util.function.Function;

@Configuration
public class GatewayConfig {
	/**
	 * 配置一个id为zoudm的路由规则,当访问地址 http://localhost:9527/guonei时会自动转发到地址 http://news.baidu.com/guonei
	 * */
	@Bean
	public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
		RouteLocatorBuilder.Builder routes = builder.routes();
		// http://news.baidu.com/guonei
		routes.route("zoudm",r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
		return routes.build();
	}

	@Bean
	public RouteLocator customRouteLocator1(RouteLocatorBuilder builder){
		RouteLocatorBuilder.Builder routes = builder.routes();
		// http://news.baidu.com/guonei
		Function<PredicateSpec, Route.AsyncBuilder> r1 = a -> a.path("/guoji").uri("http://news.baidu.com/guoji");
		// routes.route("zoudm",r -> r.path("/guoji").uri("http://news.baidu.com.guoji")).build();
		routes.route("zoudm",r1).build();
		return routes.build();
	}
}

注意:这里定义了两个路由

测试:

服务网关的作用 服务型网关_Cloud_05

服务网关的作用 服务型网关_Cloud_06

5.2 通过微服务名实现动态路由

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

1)修改yml配置文件

服务网关的作用 服务型网关_Cloud_07

server:
  port: 9527
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/**

eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

2)访问 http://localhost:9527/payment/lb

服务网关的作用 服务型网关_Cloud_08

可以看到 8001/8002端口切换

在测试get,如图:

服务网关的作用 服务型网关_spring_09

6. predicate的使用

我们先启动Gateway工程9527,观察控制台,如图:

服务网关的作用 服务型网关_Cloud_10

这里打印出了一系列的RoutePredicateFactory,其中有一个Path,再看我们的配置文件,如图:

服务网关的作用 服务型网关_spring_11

一个predicates下配置了一个Path,以此为突破口,我们来看看其他断言是什么玩意。

6.1 简介

        SpringCloud Gateway将路由匹配作为Spring WebFlux HandlerMapping基础架构的一部分。
        SpringCloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个RoutePredicate工厂可以进行组合。
        SpringCloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route,SpringCloud Gateway包含许多内置的Route Predicate Factories。
        所有这些谓词都匹配HTTP请求的不同属性,多种谓词工厂可以组合并通过逻辑and。说白了,predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。

6.2 常用的Route Predicate

参看官网地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-request-predicates-factories

6.2.1 After Route Predicate

        在什么时间之后访问才能生效。官网给的案例配置如下:

服务网关的作用 服务型网关_spring_12

这个时间是什么?我们怎么获取这个时间了?这个就需要用到JDK8给我们提供的一个类ZonedDateTime来获取了,例如:

ZonedDateTime zone = ZonedDateTime.now();// 默认时区

我们将zone打印出来,就可以得到如下字符串:2020-04-10T21:46:16.015+08:00[Asia/Shanghai]

然后将这个字符串复制到我们的yml配置文件中,如图:

服务网关的作用 服务型网关_spring_13

配置的意思是,在2020-04-10 21:46:16之后访问才有效。

访问连接http://localhost:9527/payment/get/1,如图:

服务网关的作用 服务型网关_微服务_14

时间未到,所以不允许访问,当到了46分钟的时候,再次访问,如图:

服务网关的作用 服务型网关_微服务_15

6.2.2 Before Route Predicate与Between Route Predicate

与After类似,只需要根据官网的配置,在yml中配置一下即可

1)Before Route Predicate

服务网关的作用 服务型网关_spring_16

2)Between Route Predicate

服务网关的作用 服务型网关_微服务_17

注意:两个时间之间用逗号分隔。

6.2.3 Cookie Route Predicate

        Cookie Route Predicate需要两个参数,一个是cookie name,一个是正则表达式。路由规则会通过获取对应的cookie name值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配则不执行

官网示例:

服务网关的作用 服务型网关_spring_18

这里我们不使用postman或者Jmeter工具了,转而使用另一种根据curl,首先访问不配置cookie,访问一下,如图:

 

服务网关的作用 服务型网关_Cloud_19

然后,我们配一下cookie断言,如图:

服务网关的作用 服务型网关_spring_20

1)不带cookie的访问

如图;报404

服务网关的作用 服务型网关_微服务_21

2)带cookie的访问

加上cookie参数,可以正常访问,如图:

服务网关的作用 服务型网关_微服务_22

6.2.4 Header Route Predicate

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

官网示例:

服务网关的作用 服务型网关_spring_23

在9527配置如下:

服务网关的作用 服务型网关_Cloud_24

测试:

服务网关的作用 服务型网关_spring_25

6.2.5 Host Route Predicate

        Host Route Predicate接收一组参数,一组匹配的域名列表,这个模板是一个ant分隔的模板,用.号作为分隔符,它通过参数中的主机地址作为匹配规则

官网示例:

服务网关的作用 服务型网关_spring_26

表示请求url需要以.somehost.org结尾。

6.2.6 Method Route Predicate

服务网关的作用 服务型网关_微服务_27

6.2.7 Path Route Predicate

我们之前的配置中都使用了该配置的,官网实例如图;

服务网关的作用 服务型网关_服务网关的作用_28

同时,我们一般可以配置Method断言搭配使用,某某连接需要使用get方法才能访问等,例如:

服务网关的作用 服务型网关_服务网关的作用_29

6.2.8 Query Route Predicate

带查询条件的断言,支持传入参数,一个是属性名一个是属性值,属性值可以是正则表达式。例如:

服务网关的作用 服务型网关_Cloud_30

访问:

服务网关的作用 服务型网关_Cloud_31

7. 过滤器

        路由过滤器可用于修改进入的http请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。Spring cloud Gateway内置了多种路由过滤器,他们都由Gatewayfilter的工厂类来产生。

7.1 内置的过滤器

7.1.1 生命周期

1)pre:业务逻辑之前

2)post:业务逻辑之后

7.1.2 种类

1)GatewayFilter:单一过滤器,官网地址:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories

官网默认给出的过滤器很多,可以参考官网给出的例子去做就行了,例如:

服务网关的作用 服务型网关_微服务_32

2)GlobalFilter:全局过滤器,参看官网例子:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters

7.2 自定义全局过滤器

我们可以通过自定义过滤器来实现全局日志记录、统一网关鉴权等功能

操作步骤

1)定义过滤器实现两个接口 GlobalFilter,Ordered

package com.bjc.cloud.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;

@Component
@Slf4j
public class LogGloableFilter implements GlobalFilter, Ordered {
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		log.info("****  come in LogGloableFilter: " + new Date());
		// spring5.0 的语法,用于获取request对象
		String uname = exchange.getRequest().getQueryParams().getFirst("uname");
		if(StringUtils.isEmpty(uname)){
			log.error("非法用户!");
			exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);// 设置返回状态
			return exchange.getResponse().setComplete();// 完成退出
		}
		return chain.filter(exchange); // 放行
	}

	/*
		加载过滤器的顺序
	* */
	@Override
	public int getOrder() {
		return 0;
	}
}

2)测试

启动微服务,访问连接http://localhost:9527/payment/lb?uname=123  如图:

服务网关的作用 服务型网关_服务网关的作用_33

后台打印信息:

服务网关的作用 服务型网关_Cloud_34

然后将uname的值去掉,如图:

服务网关的作用 服务型网关_spring_35

后台打印信息:

服务网关的作用 服务型网关_微服务_36