服务网关
- 什么是服务网关/API网关
API Gateway(APIGW / API 网关)
,顾名思义,是系统对外的唯一入口。API
网关封装了系统内部架构,为每个客户端提供定制的API。 近几年来移动应用与企业间互联需求的兴起。从以前单一的Web应用,扩展到多种使用场景,且每种使用场景对后台服务的要求都不尽相同。 这不仅增加了后台服务的响应量,还增加了后台服务的复杂性。随着微服务架构概念的提出,API网关成为了微服务架构的一个标配组件。
- 为什么要使用网关
微服务的应用可能部署在不同机房,不同地区,不同域名下。此时客户端(浏览器/手机/软件工具)想 要请求对应的服务,都需要知道机器的具体 IP 或者域名 URL,当微服务实例众多时,这是非常难以记忆的,对 于客户端来说也太复杂难以维护。此时就有了网关,客户端相关的请求直接发送到网关,由网关根据请求标识 解析判断出具体的微服务地址,再把请求转发到微服务实例。这其中的记忆功能就全部交由网关来操作了。
负载均衡、路由、限流、协议适配、服务降级、熔断、重试、鉴权、计量计费
各种网关方案
Nginx + Lua
没有熔断、重试等功能
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
- 那还需要nginx不?是不是需要动静分离之类的?加了nginx的话,熔断之类的还能用不?
- 熔断应该可以用,毕竟自己断了就行;重试应该也可以,对于nginx来说就是还没response,实际上是在重试
SpringCloud Gateway
添加依赖
注:gateway中不能添加springMVC的依赖,因为用的是WebFlux模型
spring:
cloud:
gateway:
route:
- id: #路由对象唯一标识(随便写,唯一就行)
uri: #地址
predicates: # 断言,配置规则
- 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地址
负载均衡
之前写死uri的方式,没法负载均衡
spring:
cloud:
gateway:
route:
- id: #路由对象唯一标识(随便写,唯一就行)
uri: lb://服务id #地址
predicates: # 断言,配置规则
- Path=/path1,/path2/** # 将该Path的访问转发到url(通过网关访问服务的路径)
lb: LoadBalance
类似ribbon,根据服务id去服务中心拉取该服务的列表,然后进行负载均衡
原理解析
看官网的流程图,
gateway = 断言(前置filter)+过滤(后置filter)
前置后置指转发前后(这是一种比喻的说法,其实前置不像是filter,而像是predicate,满足条件才通过)
来回都要走一次
断言和过滤都有很多工厂类
似乎是抽象工厂模式,顶层接口RoutePredicateFactory下有抽象工厂类,然后又工厂类
断言 Route Predicate Factories
官网有11个
常用的就是配置文件中predicate下的那些
过滤 GatewayFilter Factories
官网有31个
注意过滤器不止过滤的功能,而是类似切面做各种操作(如添加请求头信息)
自定义Filter
自定义的Filter选择合适的接口实现
官网关于全局过滤器的例子。注意全局过滤器不需要在配置文件里配置到某一服务上了,因为全局有效;
@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;
}
}
也可以直接在类上加@Component注解(ruoyi的做法)
非全局的filter要进行配置,将自定义filter的类名写上
routes:
# 认证中心
- id: ruoyi-auth
uri: lb://ruoyi-auth
predicates:
- Path=/auth/**
filters:
# 验证码处理
- CacheRequestFilter
- ValidateCodeFilter
- StripPrefix=1
Gateway 网关层的白名单实现原理是在过滤器内判断请求地址是否符合白名单,如果通过则跳过当前过滤器。
ServerWebExchange封装了request和response,可以通过get方法拿到
但gateway采用的是webFlux模型,返回的是ServerHttpRequest类的对象
整个Filter类似Servlet的Filter处理流程(doFilter方法)
注意放行后的代码也会被执行,因为gateway组件下响应返回时又要经过Fliter
限流
常见的限流算法有:计数器
算法,漏桶(Leaky Bucket)
算法,令牌桶(Token Bucket)
算法。
Spring Cloud Gateway
官方提供了RequestRateLimiterGatewayFilterFactory
过滤器工厂,使用Redis
和Lua
脚本实现了令牌桶的方式。
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
配置,表示网关转发到业务模块时候会自动截取前缀。
编写URI
限流规则配置类
package com.ruoyi.gateway.config;
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;
/**
* 限流规则配置类
*/
@Configuration
public class KeyResolverConfiguration
{
@Bean
public KeyResolver pathKeyResolver()
{
return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
}
}
多次请求会发现返回HTTP ERROR 429
,同时在redis
中会操作两个key
,表示限流成功。
request_rate_limiter.{xxx}.timestamp
request_rate_limiter.{xxx}.tokens
- 是依赖包里有lua吗?
- 似乎在容器里注册KeyResolver就行了
- name什么意思?随便写还是?看看fliter那里
其他限流规则
参数限流:key-resolver: "#{@parameterKeyResolver}"
@Bean
public KeyResolver parameterKeyResolver()
{
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("userId"));
}
IP限流:key-resolver: "#{@ipKeyResolver}"
@Bean
public KeyResolver ipKeyResolver()
{
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
拓展:
java代码配置路由
reactor的异步非阻塞模型
java代码配置的生效优先级高,原因是先加载配置再加载代码,导致代码覆盖了配置设置的数据
可视化查看路由
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