Spring Cloud Gateway架构

网关概述

前言

计算机科学领域的任何问题,都可以通过增加一个间接的中间层来解决。

​ —— David Wheeler

网关发展背景

网关技术和面向服务架构(Service Oriented Architecture,SOA)、微服务架构(MicroServices Architecture,MSA)息息相关。

SOA

在早期企业业务发展过程中,系统架构经过了单体架构、集群架构到业务垂直拆分,每个子系统都会存在比较多的共享业务。比如用户查询,在支付业务、首页中都会遇到,那么会造成重复开发造成非常多的冗余代码,这时候就引入了服务化的改造,也就是SOA。把一些通用的、会被上层服务调用的模块独立拆分出来,形成一个共享的基础服务。

面向服务架构的核心理念:松耦合带来的服务重用,通过服务编排助理业务的快速响应和创新。

ESB企业服务总线

ESB是从面向服务架构发展而来,主要是对多个系统的服务调用者和服务提供者的解耦。ESB本身提供了服务暴露、服务接入、协议转化、消息转换传输、路由等功能。

img

但基于 ESB 这种集中式管理的 SOA 方案也存在着种种问题,特别是面向互联网技术领域的爆发式发展的情况下。

传统的ESB的服务调用方式是,每一次服务的调用者要向服务提供者进行服务交互请求时都必须通过中心的ESB来进行路由。

每一次服务交互的路线是:

服务调用者 -->ESB(接收服务请求)-
-> 服务提供者(服务处理)-->ESB(服务提供返回结果)--> 服务调用者(服务返回)

在这里插入图片描述


微服务架构

微服务网关 = 传统ESB + 去掉了复杂服务适配和协议转换 +去掉了服务编排 + 提升了限流容错能力

Apache Dubbo(2011 年开源后)与新近出现的 Spring Cloud 为代表的分布式技术的出现,给了 SOA 实现的另外一个选择:去中心化的分布式服务架构。

分布式服务架构技术不再依赖于具体的服务中心容器技术(比如 ESB),而是将服务寻址和调用完全分开,这样就不需要通过容器作为服务代理,在运行期实现最高效的直连调用。

进而又在此基础上随着 REST、Docker 容器化、领域建模、自动化测试运维等领域的发展,逐渐形成了微服务架构(MSA)。

在微服务架构里,服务的粒度被进一步细分,各个业务服务可以被独立的设计、开发、测试、部署和管理。这时,各个独立部署单元可以用不同的开发测试团队维护,可以使用不同的编程语言和技术平台进行设计,这就要求必须使用一种语言和平台无关的服务协议作为各个单元间的通讯方式。

img

在微服务架构中,由于系统和服务的细分,导致服务结构变更越来复杂。API网关作为分散在各个微服务系统的API聚合点和统一接入点,外部请求通过访问这个接入点,即可访问内部所有的REST API服务。

网关介绍

思考一下,如果没有网关,那请求、鉴权、服务编排会怎么做?

当没有网关时,Client需要请求到每个具体服务应用获取数据。对客户端而言,不仅每个请求都需要授权认证,

而且也增加了网络消耗和业务响应时长。对服务端来说,每个应用都需要做重复的认证。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Hbu0HfZ-1629357130897)(/Users/xiaodian/Desktop/Gateway文档图/微服务没有网关的调用.png)]

网关职责

增加网关服务,对客户端的请求做反向代理

  • 请求接入

    作为所有 API 接口服务请求的接入点,管理所有的接入请求

  • 业务聚合

    作为所有后端业务服务的聚合点,所有的业务服务都可以在这里被调用

  • 中介策略

    实现安全、验证、路由、过滤、流控,缓存等策略,进行一些必要的中介处理

  • 统一管理

    提供配置管理工具,对所有 API 服务的调用生命周期和相应的中介策略进行统一管理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yv2JO26W-1629357130901)(/Users/xiaodian/Desktop/微服务有网关的调用.png)]

网关作用
  • 性能:API高可用,负载均衡,容错机制

  • 安全:权限身份认证、脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制)

  • 日志:日志记录(traceid,spanid)一旦涉及分布式,全链路跟踪必不可少

  • 缓存:数据缓存。

  • 监控:记录请求响应数据,api耗时分析,性能监控。

  • 限流:流量控制,错峰流控,可以定义多种限流规则。

  • 灰度:线上灰度部署,可以减小风险。

  • 路由:动态路由规则

网关要求

网关是所有服务流量的入口,对网关三高和安全性要求很高

  • 高可用

    网关必须支持集群部署,这是基本要求,否则网关服务挂掉会导致整个系统不可用

  • 高性能

    作为所有流量的入口,对性能要求非常高,一旦网关服务出现性能瓶颈,下游所有业务服务性能再高也没有意义

  • 高扩展

    可以实现定制化需求

  • 安全性

    可以防止恶意请求,保证数据传输的安全性。

    当超出服务请求阈值,对其进行限流处理,保护系统资源

网关技术

Open Resty

OpenResty 基于 Nginx,集成了 Lua 语言和 Lua 的各种工具库,可用的第三方模块,这样我们就在 Nginx 既有的高效 HTTP 处理的基础上,同时获得了 Lua 提供的动态扩展能力。

Netflix Zuul
Zuul 1.0

1.0版本基于Servlet,Servlet是单线程的接收请求和转发处理,是阻塞 IO,不支持长连接。

Servlet工作模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0vd5WCsy-1629357130903)(/Users/xiaodian/Desktop/Gateway文档图/Servlet工作模型.png)]

Zuul 1.0 工作模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FS5Y7CYg-1629357130906)(/Users/xiaodian/Desktop/Gateway文档图/Zuul1.0工作模型.png)]

Zuul 2.0

在2019年5月,Netflix终于开源了异步调用模式的Zuul2.0版本,但是此时Spring Cloud已经有了自己的Gateway网关,已经不再集成Zuul。

工作模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-965NXpi8-1629357130909)(/Users/xiaodian/Desktop/Gateway文档图/Zuul2.0工作模型.png)]

Spring Cloud Gateway
简介

Spring Cloud Gateway 构建在Spring生态系统之上的API网关,基于 Spring 5、Spring Boot和Project Reactor开发的网关,为微服务架构提供简单有效的统一的API路由管理方式。

Spring Cloud Gateway的出现是为了替代 Netflix Zuul,而且在Spring Cloud 2.0版本中集成的是 Zuul 1.0老版本,性能低下。为了提升网关的性能,Spring Cloud Gateway是基于 WebFlux 框架实现,而Web Flux则是使用了 Reactor 模式通信框架 Netty。

Spring Cloud Gateway 不仅提供了统一路由功能,并且基于 Filter 过滤链提供了安全、监控、限流等功能。

特性
  • Spring生态组件,易集成Spring生态其它组件
  • 内置了多种请求匹配、过滤、转发的实现,并且可以自定义扩展
  • 集成了Spring Cloud DiscoveryClient、Hystrix断路器
  • 高性能的Netty通信
工作模型

客户端请求到 Gateway 网关,会先经过 Gateway Handler Mapping 进行请求和路由匹配。匹配成功后再发送到 Gateway Web Handler 处理,然后会经过特定的过滤器链,经过所有前置过滤后,会发送代理请求。请求结果返回后,最后会执行所有的后置过滤器。

在这里插入图片描述

网关选型

功能特性对比

网关鉴权限流监控易用性可维护性社区成熟
Spring Cloud GatewayFilter实现Filter实现简单易用Spring 系列可扩展性强,易配置可维护性好Spring社区成熟,但Gateway资源较少
Netflix ZuulFilter实现Filter实现参考资料少可维护性比较差资料少
OpenResty需要Lua开发需要开发简单易用,但需要进行的Lua开发可维护性较差,将来需要维护大量lua脚本成熟资料很多

Gateway核心概念

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VMHWIbkD-1629357130916)(/Users/xiaodian/Desktop/Gateway文档图/Gateway工作链路路.png)]

Route

网关可以配置多个不同规则的Route,每个Route由ID、URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发。

Predicate

断言,路由转发的前置判断条件。目前Spring Cloud Gateway内置了多种断言工厂,如果不满足需求,可以自定义断言工厂。

Filter

通过 predicate 判断条件后,会进行Filter过滤拦截。Filter可分类为 RouteFilter、GlobalFilter

Gateway Filter

针对一个route生效

PreFilter前置过滤

  • 授权认证
  • 限流
  • 灰度.

PostFilter后置过滤

  • 报文修改
Global Filter

全局的拦截过滤

  • 监控统计
  • 全局限流
  • 安全防护

Gateway配置使用和详解

Spring Cloud 和 Spring Boot 版本

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>Hoxton.SR9</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>2.3.7.RELEASE</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Spring Cloud Gateway

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

配置

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: config_route
          predicates:
            - Path=/gateway/**
          filters:
            - StripPrefix=1
          uri: https://www.baidu.com

Predicate Factory

Spring Cloud Gateway 目前内置了多种路由断言工厂,其实就是对请求的URI、参数、请求头进行匹配判断路由转发。一个路由可以配置多个断言,多个断言之间的关系是 and

介绍
规则配置说明
Before- Before=2017-01-20T17:42:47.789-07:00[America/Denver]匹配 2017 年 1 月 20 日 17:42 山地时间(丹佛)之前提出的任何请求。
After- After=2017-01-20T17:42:47.789-07:00[America/Denver]匹配 2017 年 1 月 20 日 17:42 山地时间(丹佛)之后提出的任何请求
Between- Between=datetime1, datetime2匹配datetime1和datetime2之间请求
Cookie- Cookie=chocolate, ch.p匹配具有名称chocolatech.p正则表达式匹配的Cookie 的请求
Header- Header=X-Request-Id, \d+匹配请求的标头名称X-Request-Id\d+正则表达式匹配
Host- Host=www.baidu.com匹配主机名为www.baidu.com的时,直接转发到配置的URI上
Method- Method=GET匹配请求GET方法才会匹配转发请求,还可以限定POST、PUT等请求方式
Path- Path=/red匹配请求路径为/red才会转发到URI
Path Predicate Factory

判断请求的path匹配转发

配置

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: config_route
          predicates:
            - Path=/gateway/**
          filters:
            - StripPrefix=1
          uri: https://www.baidu.com

请求 http://localhost:8080/gateway

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MGJzI4pX-1629357130919)(/Users/xiaodian/Desktop/Gateway文档图/Path Predicate Factory.png)]

PathRoutePredicateFactory 关键源码

public class PathRoutePredicateFactory
		extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {
		
  ...
  
  @Override
	public List<String> shortcutFieldOrder() {
		return Arrays.asList("patterns", "matchOptionalTrailingSeparator");
	}

  @Override
	public Predicate<ServerWebExchange> apply(Config config) {
		final ArrayList<PathPattern> pathPatterns = new ArrayList<>();
		synchronized (this.pathPatternParser) {
			pathPatternParser.setMatchOptionalTrailingSeparator(
					config.isMatchOptionalTrailingSeparator());
			config.getPatterns().forEach(pattern -> {
				PathPattern pathPattern = this.pathPatternParser.parse(pattern);
				pathPatterns.add(pathPattern);
			});
		}
    
		return new GatewayPredicate() {
			@Override
			public boolean test(ServerWebExchange exchange) {
        // 获取当前请求的Path
				PathContainer path = parsePath(
						exchange.getRequest().getURI().getRawPath());

        // 根据配置规则匹配当前Path
				Optional<PathPattern> optionalPathPattern = pathPatterns.stream()
						.filter(pattern -> pattern.matches(path)).findFirst();
			
				if (optionalPathPattern.isPresent()) {
          // 如果匹配成功,则进行URL转发
					PathPattern pathPattern = optionalPathPattern.get();
					traceMatch("Pattern", pathPattern.getPatternString(), path, true);
					PathMatchInfo pathMatchInfo = pathPattern.matchAndExtract(path);
					putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables());
					return true;
				}
				else {
					traceMatch("Pattern", config.getPatterns(), path, false);
					return false;
				}
			}

		};
	}
  
  
  @Validated
	public static class Config {

		private List<String> patterns = new ArrayList<>();

		private boolean matchOptionalTrailingSeparator = true;
    
    
    ...
  }
		
}
Cookie Predicate Factory

根据请求携带的Cookie值匹配转发

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: cookie_route
          predicates:
            - Cookie=name,zicen
          uri: https://www.dian.so

请求增加Cookie

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eHFYlIZ9-1629357130923)(/Users/xiaodian/Desktop/Gateway文档图/增加Cookie.png)]

发起请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2donrMIa-1629357130924)(/Users/xiaodian/Desktop/Gateway文档图/增加Cokkie-发起请求.png)]

CookieRoutePredicateFactory 源码

public class CookieRoutePredicateFactory
		extends AbstractRoutePredicateFactory<CookieRoutePredicateFactory.Config> {

	/**
	 * Name key.
	 */
	public static final String NAME_KEY = "name";

	/**
	 * Regexp key.
	 */
	public static final String REGEXP_KEY = "regexp";

	public CookieRoutePredicateFactory() {
		super(Config.class);
	}

	@Override
	public List<String> shortcutFieldOrder() {
		return Arrays.asList(NAME_KEY, REGEXP_KEY);
	}

	@Override
	public Predicate<ServerWebExchange> apply(Config config) {
		return new GatewayPredicate() {
			@Override
			public boolean test(ServerWebExchange exchange) {
        // 根据配置的Cookie Key获取请求携带的Cookie值
				List<HttpCookie> cookies = exchange.getRequest().getCookies()
						.get(config.name);
				if (cookies == null) {
					return false;
				}
				for (HttpCookie cookie : cookies) {
          // 根据配置的Cookie Value匹配请求Cookie值,若匹配成功返回true
					if (cookie.getValue().matches(config.regexp)) {
						return true;
					}
				}
				return false;
			}

			@Override
			public String toString() {
				return String.format("Cookie: name=%s regexp=%s", config.name,
						config.regexp);
			}
		};
	}

	@Validated
	public static class Config {

		@NotEmpty
		private String name;

		@NotEmpty
		private String regexp;

		public String getName() {
			return name;
		}

		public Config setName(String name) {
			this.name = name;
			return this;
		}

		public String getRegexp() {
			return regexp;
		}

		public Config setRegexp(String regexp) {
			this.regexp = regexp;
			return this;
		}

	}

}
自定义扩展 Predicate Factory

继承 AbstractRoutePredicateFactory 抽象类,实现自定义断言工厂

@Component
public class AuthRoutePredicateFactory extends       	
                  AbstractRoutePredicateFactory<AuthRoutePredicateFactory.Config> {

    public AuthRoutePredicateFactory() {
        super(Config.class);
    }

    private static final String NAME_KEY = "name";
		
   /**
     * 配置Config匹配字段顺序
     * 
     * - Auth=name
     * 
     * @return
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(NAME_KEY);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        // header中携带了某个值
        return (exchange->{
            HttpHeaders headers = exchange.getRequest().getHeaders();
            List<String> stringList = headers.get(config.name);
            return !CollectionUtils.isEmpty(stringList) && stringList.size() > 0;
        });
    }

    @Validated
    public static class Config {

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

配置

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: auth_route
          predicates:
            - Path=/define/**
            - Auth=Authorization
          filters:
            - StripPrefix=1
          uri: https://www.dian.so

请求结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-er6lKz4g-1629357130927)(/Users/xiaodian/Desktop/Gateway文档图/自定义请求结果.png)]

Filter Factory

过滤器分为2种,针对某个路由的过滤器和全局的过滤器

Gateway Filter

目前内置的网关路由过滤器有31种,可以根据用途分类:

  • Header 头部过滤器

  • Path 路径过滤器

  • Parameter 参数过滤器

  • Body 请求响应体过滤器

  • Http Status 状态码过滤器

  • Session 会话过滤器

  • Redirect 重定向过滤器

  • Retry 重试过滤器

  • RateLimier 限流过滤器

  • Hystrix 熔断过滤器

限流过滤器

RequestRateLimiterGatewayFilterFactory 对当前路由请求进行过滤,如果没有触发限流则允许继续执行,否则默认会返回 HTTP 429 - Too Many Requests

使用的算法是 令牌桶算法。

这里演示使用通过 IP 地址进行限流,需要先实现 KeyResolver 接口获取需要限流的Key

@Component
public class IpAddressKeyResolver implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress())
                        .getAddress().getHostAddress());
    }
}

限流配置

 spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:       
        - id: ratelimiter_route
          predicates:
            - Path=/ratelimiter/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                deny-empty-key: true
                # 限流Key解析器实现,使用Bean的名称
                keyResolver: '#{@ipAddressKeyResolver}'
                # 令牌生成的速率/s,通常是用户请求的QPS
                redis-rate-limiter.replenishRate: 1
                # 令牌桶的大小
                redis-rate-limiter.burstCapacity: 3
          uri: lb://order-service

发起请求

自定义扩展 Filter Factory
@Component
public class DefineGatewayFilterFactory extends
  					AbstractGatewayFilterFactory<DefineGatewayFilterFactory.Config> {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    public DefineGatewayFilterFactory() {
        super(Config.class);
    }

    private static final String NAME_KEY = "name";


    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(NAME_KEY);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain)->{
            logger.info("[pre] Filter Request, name:"+config.getName());
            return chain.filter(exchange).then(Mono.fromRunnable(()->{
                        logger.info("[post] Filter Response.");
                    }));
        });
    }

    public static class Config {

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

}
spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:       
       - id: auth_route
          predicates:
            - Path=/define/**
            - Auth=Authorization         
          filters:
            - StripPrefix=1
          uri: https://www.dian.so
Global Filter

全局过滤器和网关过滤器有相同的方法定义,网关过滤器关注是指定路由的过滤,而全局过滤器会处理所有路由。

内置的网关过滤器有9种,可以分类为:

  • 路由转发

  • 负载均衡

  • Netty 路由

  • Websocket 路由

路由
spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: lb_route
          predicates:
            - Path=/lb/**
          filters:
            - StripPrefix=1
          uri: lb://order-service
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true

Actuator API

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#actuator-api

暴露 Web 端点

management:
  endpoints:
    web:
      exposure:
        include: "*"
查询

获取配置的路由信息列表 GET /actuator/gateway/routes

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WU5YLfwx-1629357130929)(/Users/xiaodian/Desktop/查询路由信息列表.png)]

获取路由详情 GET /actuator/gateway/routes/${routeId}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jCTPaByw-1629357130931)(/Users/xiaodian/Desktop/获取路由详情.png)]

创建删除

1、添加路由配置 POST /actuator/gateway/routes/${routeId}

body格式

{  "id": "first_route",  "predicates": [{    "name": "Path",    "args": {"_genkey_0":"/first"}  }],  "filters": [],  "uri": "https://www.uri-destination.org",  "order": 0}]

请求添加路由配置 dynamic_route

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2czatqLh-1629357130933)(/Users/xiaodian/Desktop/Gateway文档图/请求添加路由配置.png)]

创建好后, 通过 获取配置路由信息列表 获取发现并没有刚刚添加的 dynamic_route,这时因为需要刷新下缓存。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tilw2qB0-1629357130935)(/Users/xiaodian/Desktop/Gateway文档图/获取配置路由信息列表-待刷新.png)]

2、刷新路由配置缓存 POST /actuator/gateway/refresh

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aUsbVaEo-1629357130936)(/Users/xiaodian/Desktop/Gateway文档图/刷新路由配置缓存.png)]

再次获取配置路由信息列表就有了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TEGyqzhG-1629357130940)(/Users/xiaodian/Desktop/Gateway文档图/获取配置路由信息列表-刷新后.png)]

再来验证下刚创建的路由信息是否有效,嗯,很完美!

请添加图片描述

3、删除路由配置信息 DELETE /actuator/gateway/routes/${routeId}

删除也是一样,需要刷新缓存才能生效

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ao6LIOwe-1629357130946)(/Users/xiaodian/Desktop/Gateway文档图/删除路由配置信息.png)]

通过以上的API端点可以实现路由配置增删除,实现动态路由。

但是有一个问题,当前的路由配置信息是存放在内存当中,如果网关服务应用重启或者扩容,动态配置的路由就不存在了。那我们能不能替换路由配置信息的存储策略呢?

路由配置持久化
源码分析

org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint

暴露了Gateway路由配置操作,来看下他是如何存储路由配置

public class AbstractGatewayControllerEndpoint implements ApplicationEventPublisherAware {

  // 路由配置定义存储接口
	protected RouteDefinitionWriter routeDefinitionWriter;
  	
  ...
  
  public AbstractGatewayControllerEndpoint(
			RouteDefinitionLocator routeDefinitionLocator,
			List<GlobalFilter> globalFilters, List<GatewayFilterFactory> gatewayFilters,
			List<RoutePredicateFactory> routePredicates,
			RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) {
		this.routeDefinitionLocator = routeDefinitionLocator;
		this.globalFilters = globalFilters;
		this.GatewayFilters = gatewayFilters;
		this.routePredicates = routePredicates;
		this.routeDefinitionWriter = routeDefinitionWriter;
		this.routeLocator = routeLocator;
	}
	
  // 创建配置
  @PostMapping("/routes/{id}")
	@SuppressWarnings("unchecked")
	public Mono<ResponseEntity<Object>> save(@PathVariable String id,
			@RequestBody RouteDefinition route) {

		return Mono.just(route).filter(this::validateRouteDefinition)
				.flatMap(routeDefinition -> this.routeDefinitionWriter
						.save(Mono.just(routeDefinition).map(r -> {
							r.setId(id);
							log.debug("Saving route: " + route);
							return r;
						}))
						.then(Mono.defer(() -> Mono.just(ResponseEntity
								.created(URI.create("/routes/" + id)).build()))))
				.switchIfEmpty(
						Mono.defer(() -> Mono.just(ResponseEntity.badRequest().build())));
	}


   // 删除配置
	@DeleteMapping("/routes/{id}")
	public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
		return this.routeDefinitionWriter.delete(Mono.just(id))
				.then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build())))
				.onErrorResume(t -> t instanceof NotFoundException,
						t -> Mono.just(ResponseEntity.notFound().build()));
	}
  
  ...
}

路由配置存储接口 org.springframework.cloud.gateway.route.RouteDefinitionRepository

public interface RouteDefinitionWriter {

	Mono<Void> save(Mono<RouteDefinition> route);

	Mono<Void> delete(Mono<String> routeId);

}

public interface RouteDefinitionRepository
		extends RouteDefinitionLocator, RouteDefinitionWriter {

}

内置实现 org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository

可以看到是由一个HashMap实现了路由配置的存储和查询

public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {

	private final Map<String, RouteDefinition> routes = synchronizedMap(
			new LinkedHashMap<String, RouteDefinition>());

	@Override
	public Mono<Void> save(Mono<RouteDefinition> route) {
		return route.flatMap(r -> {
			if (StringUtils.isEmpty(r.getId())) {
				return Mono.error(new IllegalArgumentException("id may not be empty"));
			}
			routes.put(r.getId(), r);
			return Mono.empty();
		});
	}

	@Override
	public Mono<Void> delete(Mono<String> routeId) {
		return routeId.flatMap(id -> {
			if (routes.containsKey(id)) {
				routes.remove(id);
				return Mono.empty();
			}
			return Mono.defer(() -> Mono.error(
					new NotFoundException("RouteDefinition not found: " + routeId)));
		});
	}

	@Override
	public Flux<RouteDefinition> getRouteDefinitions() {
		return Flux.fromIterable(routes.values());
	}

}
持久化方案

那我们是不是可以通过实现 RouteDefinitionRepository 接口,来进行持久化存储呢?Redis、Mysql、Nacos等都可以帮我们实现,可以根据公司各自的技术栈产品来选择合适的存储方案。

我这里使用Redis演示一个案例

@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    private final static String GATEWAY_ROUTE_KEY = "gateway_dynamic_route";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        List<RouteDefinition> routeDefinitions = new ArrayList<>();
        redisTemplate.opsForHash().values(GATEWAY_ROUTE_KEY).stream().forEach(route->{
            routeDefinitions.add(JSON.parseObject(route.toString(), RouteDefinition.class));
        });
        return Flux.fromIterable(routeDefinitions);
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(routeDefinition -> {
            redisTemplate.opsForHash().put(GATEWAY_ROUTE_KEY,
                    routeDefinition.getId(), JSON.toJSONString(routeDefinition));
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id->{
            if (redisTemplate.opsForHash().hasKey(GATEWAY_ROUTE_KEY, id)){
                redisTemplate.opsForHash().delete(GATEWAY_ROUTE_KEY, id);
                return Mono.empty();
            }
            return Mono.defer(()->Mono.error(new Exception("routeDefinition not found")));
        });
    }
}

当前网关服务未来展望

安全

  • 反爬

对于每天认定的爬虫 IP,加入黑名单,直接限制其访问我们的 API 网关

  • 流控策略

对于未登录和识别身份的 API 调用,实现全局的流控策略,增加缓存时间和限制调用次数,保障系统稳定

网关拆分

流量网关

  • 全局性流控
  • 日志统计
  • 防止 SQL 注入
  • 防止 Web 攻击
  • 屏蔽工具扫描
  • 黑白名单控制

业务网关

  • 服务级别降级和熔断
  • 负载均衡、灰度策略
  • 权限验证
  • 业务规则和参数验证

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

子津

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值