Spring Cloud 入门系列十一 -- API 网关 Spring Cloud Gateway

1 什么是 Spring Cloud Gateway?

Spring Cloud Gateway 是 Spring Cloud 生态系统中的网关,用于替代 zuul。 zuul 基于 Servlet,使用阻塞 API 且不支持长连接(WebSockets),而 Spring Cloud Gateway 使用非阻塞 API,支持 WebSockets。

Spring Cloud Gateway 是 Spring 官方基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,不仅旨在为微服务架构提供一种简单而有效的统一的 API 路由管理方式,而且基于 Filter 链的方式提供一系列网关基本功能,如安全,监控,限流等。

2 Spring Cloud Gateway 的网关路由

Spring Cloud Gateway 的网关路由可以通过配置文件或者代码(自定义 RouteLocator)来进行配置,我们一般使用配置文件进行网关路由的配置。

2.1 一个最简单的网关路由

我们新建一个项目,命名为 Gateway ,其 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.6.RELEASE</version>
		<relativePath/> 
	</parent>
	<groupId>com.example</groupId>
	<artifactId>Gateway</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>Gateway</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
	
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-gateway</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		
	</dependencies>
	
	<dependencyManagement> 
  		<dependencies> 
    		<dependency> 
      			<groupId>org.springframework.cloud</groupId>  
      			<artifactId>spring-cloud-dependencies</artifactId>  
      			<version>Finchley.SR2</version>  
      			<type>pom</type>  
      			<scope>import</scope> 
   			 </dependency> 
  		</dependencies> 
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

配置文件如下:

server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
      #自定义的路由ID,是唯一的
      - id: gateway-test
      #目标服务地址    
        uri: https://blog.csdn.net
        #路由条件
        predicates:    
        - Path=/nav/java

我们在配置文件中配置了一个 id 为 gateway-test 的路由规则,当我们访问 http://localhost:8080/nav/java 时会自动转发至 https://blog.csdn.net/nav/java。打开项目,在浏览器进行测试,结果如下,证明转发成功。
在这里插入图片描述

2.2 Predicate

Predicate 是 Java 8 引入的一个函数,它接受一个输入参数,返回一个布尔值结果,可以用来接口请求参数校验等操作。

Spring Cloud Gateway 利用了 Predicate 的特性实现了各种路由匹配规则,比如通过请求参数等,用来作为条件匹配到对应的路由。下面的图片总结了 Spring Cloud 内置的几种 Predicate 的实现。

在这里插入图片描述
Predicate 实现了匹配规则,在接收到请求之后,会找到相应的路由进行处理。我们接下来将介绍几种 Spring Cloud GateWay 内置的 Predicate 的使用。

2.2.1 通过时间匹配
server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
      #自定义的路由ID,是唯一的
      - id: gateway-test
      #目标服务地址    
        uri: https://blog.csdn.net
        #路由条件
        predicates:    
        - Between=2019-01-01T00:00:00+08:00[Asia/Shanghai], 2020-01-01T00:00:00+08:00[Asia/Shanghai]

Predicate 可以支持我们设置一个时间段,在请求转发时,可以判断现在是否处于这个时间段,只有处于这个时间段才能匹配到路由。

我们把时间段设置为 2019 年全年,然后在浏览器输入 http://localhost:8080/,发现确实转发到了 csdn 首页。
在这里插入图片描述

2.2.2 通过请求方式匹配

我们可以通过 POST、GET 等不同的请求方式来进行路由。

server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
      #自定义的路由ID,是唯一的
      - id: gateway-test
      #目标服务地址    
        uri: https://blog.csdn.net
        #路由条件
        predicates:    
        - Method=GET

我们设置只有使用 get 方式才能进行路由。在浏览器输入 http://localhost:8080/,由于浏览器使用 get 方式去请求,发现确实可以返回网页,证明匹配到路由,我们再使用 post 方式进行测试,发现无法匹配。

2.2.3 通过请求路径匹配

通过接收一个匹配路径的参数来判断是否进行路由。

server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
      #自定义的路由ID,是唯一的
      - id: gateway-test
      #目标服务地址    
        uri: https://blog.csdn.net
        #路由条件
        predicates:    
        - Path=/nav/{segment}

如果请求路径符合要求,那么路由将匹配,例如 /nav/java,/nav/python。 我们测试一下:

输入 http://localhost:8080/nav/java 可以正确访问页面
输入 http://localhost:8080/Geffin 时返回404

2.2.4 将各种 Predicate 组合使用

上面对于 Predicate 的使用我们都是进行单个测试,但事实上我们可以将各种 Predicate 组合使用。

server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
      #自定义的路由ID,是唯一的
      - id: gateway-test
      #目标服务地址    
        uri: https://blog.csdn.net
        #路由条件
        predicates:    
        - Path=/nav/{segment}
        - Method=GET
        - Between=2019-01-01T00:00:00+08:00[Asia/Shanghai], 2020-01-01T00:00:00+08:00[Asia/Shanghai]

当多个 Predicate 存在于同一个路由时,请求需要满足全部条件才能被路由匹配。需要注意的是,当一个请求满足多个路由的条件时,请求只会被首个成功匹配的路由转发。

3 Spring Cloud Gateway 的服务化

我们上面介绍了 Spring Cloud Gateway 作为 API 网关如何代理单个服务,但在实际开发中,我们不可能为每个服务单独配置。服务的相互调用都依赖于服务中心,事实上,只要将 Spring Cloud Gateway 注册到服务中心,它就会默认代理服务中心的所有服务。

继续修改 Gateway 项目,增加 eureka 的依赖,同时在启动类上添加 eureka 的注解,将其注册到服务中心。

配置文件修改如下:

server:
  port: 8089
spring:
  application:
    name: gateway
  cloud:
    gateway:
     discovery:
        locator:
        # 通过服务中心根据serviceId创建路由
         enabled: true

eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

我们同时启动 eureka ,eurekaProducer,Gateway 三个项目,先打开 http://localhost:8761/ 查看服务注册情况。

在这里插入图片描述
eurekaProducer,Gateway 已经被注册至服务中心。

我们先测试 eurekaProducer 的功能,在浏览器输入 http://localhost:8080/helloWorld?id=3&saying=s,发现其返回如下,证明该服务一切正常。

来自8080端口:序号为3的用户发布了一条新消息:s

我们接下来通过网关来访问 eurekaProducer ,浏览器输入 http://localhost:8089/PRODUCER/helloWorld?id=3&saying=OO,发现其返回如下,证明网关功能实现正常。

来自8080端口:序号为3的用户发布了一条新消息:OO

4 Spring Cloud Gateway 中的过滤器

在我之前讲解 Zuul 的博客中已经介绍过 Zuul 的过滤器,与 Zuul 相似,Spring Cloud Gateway 也包含过滤器,不过只有两种:

  • PRE:在请求被路由之前调用,可以用于实现权限验证等功能。
  • POST:在路由到微服务以后执行,可以用于实现统计信息等功能。

Spring Cloud Gateway 也可以根据过滤器是否会应用到所有的路由上分为两类,GlobalFilter 会应用到所有的路由上,而 GatewayFilter 只会应用到单个路由或者一个分组的路由上。

有时候我们可以使用 Spring Cloud Gateway 的过滤器完成一些具体的路由配置。下面我们使用 AddRequestParameter GatewayFilter 进行演示,该过滤器的功能为在请求中添加指定参数。

修改我们的配置文件如下:

server:
  port: 8089
spring:
  application:
    name: gateway
  cloud:
    gateway:
     discovery:
        locator:
         enabled: true
     routes:
     - id: test-filter
       uri: lb://PRODUCER
       filters:
       - AddRequestParameter=id,11
       predicates:
         - Method=GET
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

这样会给匹配的每个请求添加上 id=11 的参数和值。我们启动 eureka ,eurekaProducer,Gateway 三个项目,访问 http://localhost:8089/helloWorld?saying=a,发现其返回:

来自8080端口:序号为11的用户发布了一条新消息:a

我们并没有传入 id 的值,但服务却接收到 id = 11,这说明网关在转发的过程中已经通过过滤器添加了设置的参数和值。

注意,当路由配置中 uri 所用的协议为 lb 时,Spring Cloud Gateway 会将项目名通过 eureka 解析为实际的主机和端口。

5 Spring Cloud Gateway 中的限流功能

在开发高并发系统时,我们有三把利器用来保护系统,分别是缓存、降级和限流。API 网关作为所有请求的入口,请求量大,我们可以通过对并发访问的请求进行限速来保护系统的可用性。

Spring Cloud Gateway 默认集成了Redis 限流,可以对不同服务做不同维度的限流,例如接口限流,IP 限流等。首先我们添加 Redis 依赖。

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency> 

配置文件修改如下,这里注意,filter 名称必须是 RequestRateLimiter

server:
  port: 8089
spring:
  application:
    name: gateway
  redis:
    host: localhost
  cloud:
    gateway:
     discovery:
        locator:
         enabled: true
     routes:
     - id: test-redis
       uri: lb://PRODUCER
       filters:
       - name: RequestRateLimiter
         args:
           # 允许用户每秒处理多少个请求
           redis-rate-limiter.replenishRate: 10
           # 令牌桶的容量,允许在一秒钟内完成的最大请求数
           redis-rate-limiter.burstCapacity: 20
           # 使用 SpEL 按名称引用 bean
           key-resolver: "#{@userKeyResolver}"
       predicates:
         - Method=GET
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

然后我们需要创建一个配置类

package com.example.Gateway;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import reactor.core.publisher.Mono;

/**
 * 路由限流配置
 * @author 30309
 *
 */
@Configuration
public class Config {

	@Bean
    KeyResolver userKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
    }
}

我们目前的限流方案为根据请求参数中的 user 字段来限流,现在已实现完毕。

网关可以根据不同策略对请求进行限流,当然我们也可以使用别的方案限流,比如 IP 限流

package com.example.Gateway;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import reactor.core.publisher.Mono;

/**
 * 路由限流配置
 * @author 30309
 *
 */
@Configuration
public class Config {

	@Bean
	public KeyResolver ipKeyResolver() {
	    return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
	}
}

使用接口限流,我们需要获取请求地址的 uri 作为限流 key

package com.example.Gateway;

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import reactor.core.publisher.Mono;

/**
 * 路由限流配置
 * @author 30309
 *
 */
@Configuration
public class Config {

	@Bean
	KeyResolver apiKeyResolver() {
	    return exchange -> Mono.just(exchange.getRequest().getPath().value());
	}

}

6 Spring Cloud Gateway 中的熔断功能

Spring Cloud Gateway 可以利用 Hystrix 的熔断特性,在流量过大时进行服务降级。首先我们添加依赖

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

配置文件修改如下:

server:
  port: 8089
spring:
  application:
    name: gateway
  cloud:
    gateway:
     discovery:
        locator:
         enabled: true
     routes:
     - id: test-Hystrix
       uri: lb://PRODUCER
       predicates:
         - Method=GET
       filters:
        - name: Hystrix
          args:
            name: fallbackcmd
            # fallback的路径
            fallbackUri: forward:/fallbackTest
            
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

在发生熔断时,Hystrix 的 fallback 会被调用,请求将转发到 /fallbackTest。

我们再写一个控制器。

package com.example.Gateway;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

	@RequestMapping("/fallbackTest")
	public String fallbackTest(int id,String saying) {
		return "fallback:id=" + id + " saying=" + saying;
	}
}

我们测试一下 Spring Cloud Gateway 中的熔断功能,启动 eureka ,eurekaProducer,Gateway 三个项目,在浏览器输入 http://localhost:8089/helloWorld?id=3&saying=a,发现可以正常返回。

来自8080端口:序号为3的用户发布了一条新消息:a

然后我们关闭 eurekaProducer 项目,再次访问 http://localhost:8089/helloWorld?id=3&saying=a,发现其返回

fallback:id=3 saying=a

说明 Spring Cloud Gateway 中的熔断功能实现成功。

7 Spring Cloud Gateway 中的重试功能

配置文件修改如下,要实现重试功能,我们需要使用 RetryGatewayFilter

server:
  port: 8089
spring:
  application:
    name: gateway
  cloud:
    gateway:
     discovery:
        locator:
         enabled: true
     routes:
     - id: test-retry
       uri: lb://PRODUCER
       predicates:
         - Method=GET
       filters:
        - name: Retry
          args:
          	# 重试次数
            retries: 3
            # HTTP 的状态返回码
            statuses: BAD_GATEWAY
            
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

参考:springcloud(十五):服务网关 Spring Cloud GateWay 入门
springcloud(十六):服务网关 Spring Cloud GateWay 服务化和过滤器
springcloud(十七):服务网关 Spring Cloud GateWay 熔断、限流、重试

发布了113 篇原创文章 · 获赞 206 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Geffin/article/details/103018172