前言
在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端调用多个微服务接口的地址。另外微服务架构的请求中,90%的都携带认证信息/用户登录信息,都需要做相关的限制管理,API网关由此应允而生。
这样的架构会存在许多的问题:
- 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性。
- 认证复杂,每个服务都需要独立认证。
- 存在跨域请求,在一定场景下处理相对复杂。
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
网关的核心功能特性:
- 请求路由
- 权限控制
- 限流
常见网关
1)Ngnix+lua
基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。
使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用。 lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本。
缺点:
- 只支持Http协议。
- 二次开发,自由扩展困难。
- 提供管理API,缺乏更易用的管控、配置方式。
2)Zuul
Zuul的路由功能实现了统一处理外部请求的功能,负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。zuul还提供过滤器功能,负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。Zuul的底层基于Servlet,本质上就是一系列的filter过滤器,也是它的核心。Zuul2是采用Netty实现异步IO。
缺点:
- 缺乏管控,无法动态配置。
- 依赖组件较多。
- 处理Http请求依赖的是Web容器,性能不如Nginx。
3)Gateway
底层使用了高性能的通信框架Netty,提供了异步支持,提供了抽象负载均衡,提供了抽象流控,并默认实现了RedisRateLimiter。
介绍
Spring Cloud Gateway基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。底层使用了高性能的通信框架Netty,提供了异步支持,提供了抽象负载均衡,提供了抽象流控,并默认实现了RedisRateLimiter。
为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
特性
(1)基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
(2)可集成 Hystrix 、Sentinel实现熔断降级
(3)集成 Spring Cloud DiscoveryClient
(4)Predicates 和 Filters 作用于特定路由,易于编写的 Predicates 和 Filters
(5)具备一些网关的高级功能:动态路由、限流、路径重写
三大核心概念:
Filter(过滤器):
**使用它拦截和修改请求,并且对上游的响应,进行二次处理。**org.springframework.cloud.gateway.filter.GatewayFilter的实例,用于请求被路由前或者之后对请求进行修改。
pre类型
Filter在pre类型的过滤器可以做参数效验、权限效验、流量监控、日志输出、协议转换等。
post类型
Filter在post类型的过滤器可以做响应内容、响应头的修改、日志输出、流量监控等
Route(路由):
网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
Predicate(断言):
这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。如果请求与断言相匹配则进行路由。
Gateway核心架构
1、路由 Route
路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:
- id:路由标识符,区别于其他 Route。
- uri:路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
- order:用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。
- predicate:断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
- filter:过滤器用于修改请求和响应信息。
- predicate:断言,用于进行条件判断,只有断言都返回真,才会真正的执行路由。
2、过滤器
Gateway的过滤器的作用是:是在请求的传递过程中,对请求和响应做一些手脚。
1)Gateway的过滤器的生命周期:
Pre:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择 请求的微服务、记录调试信息等。
Post:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。GatewayFilter:应用到单个路由或者一个分组的路由上;GlobalFilter:应用到所有的路由上。
2)GatewayFilter 局部过滤器
局部过滤器是针对单个路由的过滤器。他分为内置过滤器和自定义过滤器。在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。
内置的局部过滤器内容
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、 rateLimiter、 statusCode、 denyEmptyKey、 emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save 操作 | 无 |
secureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large | 请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
当内置过滤器没有办法满足需求的时候,就可以通过继承AbstractGatewayFilterFactory来实现自定义过滤器。
3)GlobalFilter 全局过滤器
全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理。
全局过滤器常用于访问鉴权,大概执行逻辑如下:
当客户端第一次请求服务时,服务端对用户进行信息认证(登录)。
认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证。
以后每次请求,客户端都携带认证的token。
服务端对token进行解密,判断是否有效。
//鉴权示例 @Component public class AuthGlobalFilter implements GlobalFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst("token"); if (StringUtils.isBlank(token)) { System.out.println("鉴权失败"); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } }
网关限流
网关是所有请求的公共入口,所以可以在网关进行限流。可以采用Sentinel组件/Hystrix组件来实现网关的限流。
从1.6.0版本开始,Sentinel提供了SpringCloud Gateway的适配模块,可以提供两种资源维度的限流:
- route维度:即在Spring配置文件中配置的路由条目,资源名为对应的routeId
- 自定义API维度:用户可以利用Sentinel提供的API来自定义一些API分组
当在限流的时候,不想返回默认的错误,那么就需要自定义错误,指定自定义的返回格式。
//route维度
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 0);
map.put("message", "接口被限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON).
body(BodyInserters.fromValue(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
//--------------------------------------------------------------------
//自定义API维度
@PostConstruct
private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("order_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/order-serv/api/**"). setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api1);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}
@PostConstruct
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("product_route")
.setCount(3)
.setIntervalSec(1)
);
rules.add(new GatewayFlowRule("order_api").
setCount(1).
setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
}
执行流程
- Gateway Client 向 Spring Cloud Gateway 发送请求
- 请求首先会被 HttpWebHandlerAdapter 进行提取组装成网关上下文
- 然后网关的上下文会传递到 DispatcherHandler ,它负责将请求分发给 RoutePredicateHandlerMapping
- RoutePredicateHandlerMapping 负责路由查找,并根据路由断言判断路由是否可用
- 如果过断言成功,由FilteringWebHandler 创建过滤器链并调用
- 通过特定于请求的 Fliter 链运行请求,Filter 被虚线分隔的原因是Filter可以在发送代理请求之前(pre)和之后(post)运行逻辑
- 执行所有pre过滤器逻辑。然后进行代理请求。发出代理请求后,将运行“post”过滤器逻辑。
- 处理完毕之后将 Response 返回到 Gateway 客户端
其他
1、Nginx网关和Gateway网关的区别
Nignx是流量网关,Gateway是业务网关。 一般流量网关配置在前,业务网关配置在后。
流量网关相当于访问的一个总入口,前端页面的一个容器,类似于防火。主要的功能有管理日志,流量监控,黑白名单,请求的负载均衡,全局限流等。Nginx是C语言写的,Nginx主要是负载均衡,反向代理,以及做web服务器。
而业务网关是针对具体的后端应用和服务,主要的功能是缓存策略、鉴权策略等。GateWay是java语言写的,Gateway主要是路由、断言和过滤器,利用这些可以做流控。
2、Gateway 组件缺陷以及优化思路
问题1、每次请求级别都会重新组装出一个 FilterChain,并进行排序,内存分配和排序会占用 CPU
问题2、在默认情况下,如果其中一个路由配置出错了,会导致整个网关路由不可用,除非 isFailOnRouteDefinitionError 被关闭。
问题3、路由内存优化,同一份路由的配置内容竟然以 3 种形式常驻于内存中。
问题4、内存泄漏优化。通讯底座是netty,通信内容较大时,例如文件上传,则会触发内存的新分配,而 SpringCloud Gateway 在对接 netty 时存在逻辑缺陷,会导致新分配的池化内存无法完全回收,导致堆外内存泄漏。并且这块堆外内存时 netty 使用 unsafe 自行分配的,通过常规的 JVM 工具还无法观测,非常隐蔽。
问题5、预构建 URI 。针对不可变对象的一次变更,从而进行了一次深拷贝,重新重构了一个 URI
问题6、对象缓存。尽量避免调用链路中出现 new 关键字,它会加大 CPU 的开销,从而影响 IO,可以使用 ThreadLocal 或者对象池化技术进行对象复用。
来自SpringCloud Gateway 在微服务架构下的最佳实践
3、登录鉴权图
总结
Gateway主要用于为微服务架构提供一种简单有效的统一的 API 路由管理方式,主要用于微服务API共性做统一要求。采用Filter链的形式,对API做了请求头部信息做限制,对微服务常用的认证授权也做了对应限制,并可对高并发进行流量限制。经过层层过滤之后,对请求接口进行服务接口路由到对应的服务接口。