关键字:GateWay, SpringCloud, Alibaba

服务网关

  • 什么是服务网关/API网关

API Gateway(APIGW / API 网关),顾名思义,是系统对外的唯一入口。API网关封装了系统内部架构,为每个客户端提供定制的API。 近几年来移动应用与企业间互联需求的兴起。从以前单一的Web应用,扩展到多种使用场景,且每种使用场景对后台服务的要求都不尽相同。 这不仅增加了后台服务的响应量,还增加了后台服务的复杂性。随着微服务架构概念的提出,API网关成为了微服务架构的一个标配组件。

  • 为什么要使用网关

微服务的应用可能部署在不同机房,不同地区,不同域名下。此时客户端(浏览器/手机/软件工具)想 要请求对应的服务,都需要知道机器的具体 IP 或者域名 URL,当微服务实例众多时,这是非常难以记忆的,对 于客户端来说也太复杂难以维护。此时就有了网关,客户端相关的请求直接发送到网关,由网关根据请求标识 解析判断出具体的微服务地址,再把请求转发到微服务实例。这其中的记忆功能就全部交由网关来操作了。

负载均衡、路由、限流、协议适配、服务降级、熔断、重试、鉴权、计量计费

各种网关方案

Nginx + Lua

没有熔断、重试等功能。可以限流,GateWay集成Redis限流就是用的这种方式。

Kong

对Nginx+Lua再次封装

SpringCloud Gateway

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。

WebFlux(异步非阻塞IO)

Zuul

1.0版本单线程,后来跳票,又已经出了GateWay,就不更新了

SpringCloud Gateway

快速上手

添加依赖

注:gateway中不能添加springMVC的依赖,因为用的是WebFlux模型

配置文件,以route(路由)为单位的微服务资源被Gateway管理

spring:
	cloud:
		gateway:
			route: 
				- id: #路由对象唯一标识(随便写,唯一就行)
				  uri: #该服务的访问地址
				  predicates:	# 断言,相当于前置fliter
				  	- Path=/path1,/path2/**	# 将该Path的访问转发到url(通过网关访问服务的路径)
				  	- After=2021-02-23T14:20:00.000+08:00[Asia/Shanghai] # 还有Before和Between(前后两个时间 )
				  	- Cookie=loginname, ruoyi
				  	- Header=X-Request-Id, \d+ #\d+值匹配正则表达式,其他项设置也可用
				  filters:				    
				    -AddRequestParameter=key, value #给当前路由对象所有请求加入添加参数(直接用形参接收的那个)
				    -AddRequestHeader=key, value	#****加入请求头信息(HttpServletRequest接收,getHeader())
				    -PrefixPath=/mypath	#最终转发路径为:uri+该前缀+path

访问规则:path后面保留,前面网关的网址替换为uri地址

负载均衡

之前用http协议写死uri的方式,没法负载均衡;
此处案例,以服务id管理路由(route),并以"lb"为前缀
lb是LoadBalance的缩写。这样表示从微服务注册中心(如Eureka)订阅服务

  • 所以负载均衡是自己实现的还是注册中心实现的?
spring:
	cloud:
		gateway:
			route:
				- id: #路由对象唯一标识(随便写,唯一就行)
				  uri: lb://服务id #地址
				  predicates:	# 断言,配置规则
				  	- Path=/path1,/path2/**	# 将该Path的访问转发到url(通过网关访问服务的路径)

类似ribbon,根据服务id去服务中心拉取该服务的列表,然后进行负载均衡

  • 拉到本地?那不是Dubbo干的事情?

自定义Filter

自定义的Filter,选择合适的接口实现,如GlobalFilter;
通过@Bean或者类上的@Component注解将自定义Filter交给Spring容器管理

官网关于全局过滤器的例子。

@Bean
public GlobalFilter customFilter() {
    return new CustomGlobalFilter();
}

public class CustomGlobalFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("custom global filter");
        return chain.filter(exchange);		//放行
    }
	
    //过滤器顺序,按自然数顺序,-1为在所有fliter之前
    @Override
    public int getOrder() {
        return -1;
    }
}

注意全局过滤器不需要在配置文件里配置到某一路由上了,因为全局有效;

非全局的自定义filter要进行配置到route上,将类名写上

      routes:
        # 认证中心
        - id: ruoyi-auth
          uri: lb://ruoyi-auth
          predicates:
            - Path=/auth/**
          filters:
            # 验证码处理
            - CacheRequestFilter
            - ValidateCodeFilter
            - StripPrefix=1
  • 原理是什么?通过类名获取bean吗?

Gateway 网关层的白名单实现原理是在过滤器内判断请求地址是否符合白名单,如果通过则跳过当前过滤器。

ServerWebExchange封装了request和response,可以通过get方法拿到

但gateway采用的是webFlux模型,返回的是ServerHttpRequest类的对象

整个Filter类似Servlet的Filter处理流程(doFilter方法)

注意放行后的代码也会被执行,因为gateway组件下响应返回时又要经过Fliter

更多功能

限流

常见的限流算法有:计数器算法,漏桶(Leaky Bucket)算法,令牌桶(Token Bucket)算法。

Spring Cloud Gateway官方提供了RequestRateLimiterGatewayFilterFactory过滤器工厂,使用RedisLua脚本实现了令牌桶的方式。

1、添加依赖

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

配置fliter

spring:
  cloud:
    gateway:
      routes:
        # 系统模块
        - id: ruoyi-system
          uri: lb://ruoyi-system
          predicates:
            - Path=/system/**
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充速率
                redis-rate-limiter.burstCapacity: 2 # 令牌桶总容量
                key-resolver: "#{@pathKeyResolver}" # 使用 SpEL 表达式按名称引用 bean

StripPrefix=1表示截取路径的前缀个数为1;
比如前端过来请求/test/good/1/view,匹配成功后,路由到后端的请求路径就会变成http://localhost:8888/good/1/view。
别忘了配置redis

  • 为什么过滤器要截取前缀?
    key-resolver,用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。

编写URI限流规则配置类

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import reactor.core.publisher.Mono;

/**
 * 限流规则配置类
 */
@Configuration
public class KeyResolverConfiguration
{
    @Bean
    public KeyResolver pathKeyResolver()
    {
        return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
    }
}

这里根据请求路径中"user"参数限流

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

KeyResolver需要实现resolve方法,比如根据userid进行限流,则需要用userid去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。

如果需要根据IP限流,定义的获取限流Key的bean为:

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

通过exchange对象可以获取到请求信息,这边用了HostName,如果你想根据用户来做限流的话这边可以获取当前请求的用户ID或者用户名就可以了,比如:

如果需要根据接口的URI进行限流,则需要获取请求地址的uri作为限流key,定义的Bean对象为:

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

通过exchange对象可以获取到请求信息,这边用了HostName,如果你想根据用户来做限流的话这边可以获取当前请求的用户ID或者用户名就可以了,比如:

如果需要根据接口的URI进行限流,则需要获取请求地址的uri作为限流key,定义的Bean对象为:

@Bean
KeyResolver apiKeyResolver() {
  return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
  • 你也是reactor?

多次请求会发现返回HTTP ERROR 429,同时在redis中会操作两个key,表示限流成功。

request_rate_limiter.{xxx}.timestamp
request_rate_limiter.{xxx}.tokens

其他限流规则

参数限流:key-resolver: "#{@parameterKeyResolver}"

@Bean
public KeyResolver parameterKeyResolver()
{
	return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}

Spring Cloud Gateway官方提供了RequestRateLimiterGateway这个过滤器,适用在Redis内的通过执行Lua脚本实现了令牌桶的方式。具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中,lua脚本spring-cloud-gateway-core中。

熔断降级

见Hystrix

原理解析

官网的流程图

gateway = 断言(前置filter)+过滤(后置filter)

此处前置后置指转发前后(这是一种比喻的说法,其实前置不像是filter,而像是predicate,满足条件才通过)

Filter来回都要走一次哦

断言和过滤都有很多工厂类,不一一讲了

似乎是抽象工厂模式,顶层接口RoutePredicateFactory下有抽象工厂类,然后又工厂类
也有责任链模式的味道

断言 Route Predicate Factories

官网有11个

常用的就是配置文件中predicate下的那些

过滤 GatewayFilter Factories

官网有31个

过滤器不止过滤的功能,而是类似切面做各种操作(如添加请求头信息)

附录:

java代码配置路由

reactor的异步非阻塞模型

java代码配置的生效优先级高,原因是先加载配置再加载代码,导致代码覆盖了配置设置的数据

	@Bean
	public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
		return builder.routes()
			.route("path_route", r -> r.path("/get")
				.uri("http://httpbin.org"))
			.route("host_route", r -> r.host("*.myhost.org")
				.uri("http://httpbin.org"))
			.route("rewrite_route", r -> r.host("*.rewrite.org")
				.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
				.uri("http://httpbin.org"))
			.route("hystrix_route", r -> r.host("*.hystrix.org")
				.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
				.uri("http://httpbin.org"))
			.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
				.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
				.uri("http://httpbin.org"))
			.route("limit_route", r -> r
				.host("*.limited.org").and().path("/anything/**")
				.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
				.uri("http://httpbin.org"))
			.build();
	}


可视化监控

Gateway暴漏了actuator端口,可用于监控路由信息等

http://localhost:8989/actuator/gateway/routes

management:
	endpoints:
		web:
			exposure:
				include: "*"	#开启所有的Web端点暴漏,有些版本要填写数组,应为["*"]

官网例子:

management.endpoint.gateway.enabled=true # default value
management.endpoints.web.exposure.include=gateway

需要注意在Gateway服务中的pom.xml文件中不要存在这个jar

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>

否则调用接口时会报以下错误因为gateway使用的是webflux,默认使用netty,所以从依赖中排除 tomcat相关的依赖

java.lang.ClassCastException: org.springframework.core.io.buffer.DefaultDataBufferFactory cannot be cast to org.springframework.core.io.buffer.NettyDataBufferFactory
at org.springframework.cloud.gateway.filter.NettyWriteResponseFilter.lambda$filter$1(NettyWriteResponseFilter.java:82) ~[spring-cloud-gateway-core-2.1.3.RELEASE.jar:2.1.3.RELEASE]
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) [reactor-core-3.2.12.RELEASE.jar:3.2.12.RELEASE]

Logo

快速构建 Web 应用程序

更多推荐