在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端调用多个微服务接口的地址。另外微服务架构的请求中,90%的都携带认证信息/用户登录信息,都需要做相关的限制管理,API网关由此应允而生。
这样的架构会存在许多的问题:
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。
基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。
使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用。 lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本。
缺点:
Zuul的路由功能实现了统一处理外部请求的功能,负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础。zuul还提供过滤器功能,负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。Zuul的底层基于Servlet,本质上就是一系列的filter过滤器,也是它的核心。Zuul2是采用Netty实现异步IO。
缺点:
底层使用了高性能的通信框架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)具备一些网关的高级功能:动态路由、限流、路径重写
**使用它拦截和修改请求,并且对上游的响应,进行二次处理。**org.springframework.cloud.gateway.filter.GatewayFilter的实例,用于请求被路由前或者之后对请求进行修改。
Filter在pre类型的过滤器可以做参数效验、权限效验、流量监控、日志输出、协议转换等。
Filter在post类型的过滤器可以做响应内容、响应头的修改、日志输出、流量监控等
网关配置的基本组成模块,和Zuul的路由配置模块类似。一个Route模块由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配,目标URI会被访问。
这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。如果请求与断言相匹配则进行路由。
路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息:
Gateway的过滤器的作用是:是在请求的传递过程中,对请求和响应做一些手脚。
Pre:这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择 请求的微服务、记录调试信息等。
Post:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
Gateway 的Filter从作用范围可分为两种: GatewayFilter与GlobalFilter。GatewayFilter:应用到单个路由或者一个分组的路由上;GlobalFilter:应用到所有的路由上。
局部过滤器是针对单个路由的过滤器。他分为内置过滤器和自定义过滤器。在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来实现自定义过滤器。
全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。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);
}
Nignx是流量网关,Gateway是业务网关。 一般流量网关配置在前,业务网关配置在后。
流量网关相当于访问的一个总入口,前端页面的一个容器,类似于防火。主要的功能有管理日志,流量监控,黑白名单,请求的负载均衡,全局限流等。Nginx是C语言写的,Nginx主要是负载均衡,反向代理,以及做web服务器。
而业务网关是针对具体的后端应用和服务,主要的功能是缓存策略、鉴权策略等。GateWay是java语言写的,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 在微服务架构下的最佳实践
Gateway主要用于为微服务架构提供一种简单有效的统一的 API 路由管理方式,主要用于微服务API共性做统一要求。采用Filter链的形式,对API做了请求头部信息做限制,对微服务常用的认证授权也做了对应限制,并可对高并发进行流量限制。经过层层过滤之后,对请求接口进行服务接口路由到对应的服务接口。