文章目录
1.网关简介
API Gateway(APIGW / API 网关),顾名思义,是出现在系统边界上的一个面向 API 的、串行集中式的强管控服务,这里的边界是企业 IT 系统的边界,可以理解为企业级应用防火墙,主要起到隔离外部访问与内部系统的作用。在微服务概念的流行之前,API 网关就已经诞生了,例如银行、证券等领域常见的前置机系统,它也是解决访问认证、报文转换、访问统计等问题的。
API 网关的流行,源于近几年来移动应用与企业间互联需求的兴起。移动应用、企业互联,使得后台服务支持的对象,从以前单一的Web应用,扩展到多种使用场景,且每种使用场景对后台服务的要求都不尽相同。这不仅增加了后台服务的响应量,还增加了后台服务的复杂性。随着微服务架构概念的提出,API网关成为了微服务架构的一个标配组件。
API 网关是一个服务器,是系统对外的唯一入口。API 网关封装了系统内部架构,为每个客户端提供定制的 API。所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有非业务功能。API 网关并不是微服务场景中必须的组件,如下图,不管有没有 API 网关,后端微服务都可以通过 API 很好地支持客户端的访问。
什么是spring cloud gateway
网关作为流量的入口,常用的功能包括路由转发、权限校验、限流等。
Spring Cloud Gateway 是基于 Spring 生态系统之上构建的 API 网关,包括:Spring 5,Spring Boot 2 和 Project Reactor。Spring Cloud Gateway 旨在提供一种简单而有效的方法来路由到 API,并为它们提供跨领域的关注点,例如:安全性,监视/指标,限流等。由于 Spring 5.0 支持 Netty,Http2,而 Spring Boot 2.0 支持 Spring 5.0,因此 Spring Cloud Gateway 支持 Netty 和 Http2 顺理成章。
客户端向 Spring Cloud Gateway 发出请求,如果请求与网关程序定义的路由匹配,则将其发送到网关 Web 处理程序,此处理程序运行特定的请求过滤器链。
过滤器之间用虚线分开的原因是过滤器可能会在发送代理请求之前或之后执行逻辑。所有 “pre” 过滤器逻辑先执行,然后执行代理请求,代理请求完成后,执行 “post” 过滤器逻辑。
spring cloud gateway功能特性:
(1)基于spring Framework5、Project Reactor和spring boot 2.0进行构建
(2)动态路由:能够匹配任何请求属性
(3)支持路径重写
(4)集成spring cloud服务发现功能(nacos)
(5)可集成流控级功能(sentinel)
(6)可以对路由指定易于编写的Predicate(断言)、Filter(过滤器)
为什么要使用网关
单体应用:浏览器发起请求到单体应用所在的机器,应用从数据库查询数据原路返回给浏览器,对于单体应用来说是不需要网关的。
微服务:微服务的应用可能部署在不同机房,不同地区,不同域名下。此时客户端(浏览器/手机/软件工具)想要请求对应的服务,都需要知道机器的具体 IP 或者域名 URL,当微服务实例众多时,这是非常难以记忆的,对于客户端来说也太复杂难以维护。此时就有了网关,客户端相关的请求直接发送到网关,由网关根据请求标识解析判断出具体的微服务地址,再把请求转发到微服务实例。这其中的记忆功能就全部交由网关来操作了。
网关介于客户端与服务器之间的中间层,所有外部请求率先经过微服务网关,客户端只需要与网关交互,只需要知道网关地址即可。这样便简化了开发且有以下优点:
易于监控,可在微服务网关收集监控数据并将其推送到外部系统进行分析
易于认证,可在微服务网关上进行认证,然后再将请求转发到后端的微服务,从而无需在每个微服务中进行认证
减少了客户端与各个微服务之间的交互次数
Nginx Gateway 区别
Nginx 是一个高性能的 HTTP 和反向代理服务器。Ngnix 一方面可以做反向代理,另外一方面做可以做静态资源服务器。
Nginx 适合做门户网关,是作为整个全局的网关,对外的处于最外层的那种;而 Gateway 属于业务网关,主要用来对应不同的客户端提供服务,用于聚合业务。各个微服务独立部署,职责单一,对外提供服务的时候需要有一个东西把业务聚合起来。
Gateway 可以实现熔断、重试等功能,这是 Nginx 不具备的。
路由(Route):
路由是网关中最重要的部分,路由信息包括一个ID、一个目的URL、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
基于配置文件的服务路由
假设本地启动了另外两个 Spring Boot 服务,分别是 服务A( http://localhost:8081 )、服务B( http://localhost:8082 ),下面通过 Spring Cloud Gateway 来路由到这两个服务。
在 resources 路径下添加配置文件 application.yml
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://localhost:8081
predicates:
- Path=/a/**
filters:
- StripPrefix=1
- id: host_route
uri: http://localhost:8082
predicates:
- Path=/b/**
filters:
- StripPrefix=1
id:固定,不同 id 对应不同的路由方式
uri:目标服务地址
predicates:路由条件
filters:过滤规则
访问 http://localhost:8080/a/ 路由到 服务A http://localhost:8081/
访问 http://localhost:8080/b/ 路由到 服务B http://localhost:8082/
其他地址,例如 http://localhost:8080/a/user/all 路由到 服务A http://localhost:8081/user/all
断言(Predicate):
Spring cloud gateway 详解和配置使用
网关Gateway网关简介及使用
java8中的断言函数,spring cloud gateway中的断言函数类型是spring 5.0框架中的ServerWebExchange。断言函数运行开发者去定义匹配Http request中的任何信息,比如请求头和参数。
过滤器(Filter):
分为Gateway filter和Global filter,Filter可以对请求和响应进行处理。
Spring Cloud gateway 网关拦截Post请求日志
Springcloud alibaba整合gateway 之 过滤器讲解
路由断言工厂(Route Predicate Factories)配置
Springcloud alibaba整合gateway应用之路由
作用:当请求gateway的时候,使用断言对请求进行匹配,如果匹配成功就路由转发,匹配不成功返回404
类型:内置
官网参考地址:Spring Cloud Gateway
SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。
(1)基于DateTime类型的断言工厂
此类型的断言工厂根据时间做判断,主要有三个:
AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期。
BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内
- After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
(2)基于远程地址的断言工厂
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中
- RemoteAddr=192.168.1.1/24
(3)基于Cookie的断言工厂
CookieRoutePredicateFactory:接收两个参数,cookie名字和一个正则表达式,判断cookie是否具有给定名称且值与正则表达式匹配。
-Cookie=chocolate,ch.
(4)基于Header的断言工厂
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式,判断请求Header是否具有给定名称值与正则表达式匹配。
-Header=X-Request-Id,\d+
(5)基于Host的断言工厂
HostRoutePredicateFactory:接收一个参数,主机名模式,判断请求的Host是否满足匹配规则。
-Host=**.testhost.org
(6)基于Method请求方法的断言工厂
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配
-Method=GET
(7)基于Path请求路径的断言工厂
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则
-Path=/foo/{segment}
(8)基于Query请求参数的断言工厂
QueryRoutePredicateFactory:接收两个参数,请求param和正则表达式,判断请求参数是否具有给定个名称且值与正则表达式匹配
-Query=baz,ba.
(9)基于路由权重的断言工厂
WeightRoutePredicateFactory:接收一个[组名,权重],然后对于同一个组内的路由按照权重转发
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
自定义路由断言工厂
自定义路由断言工厂需要继承AbstractRoutePredicateFactory类,重写apply的方法逻辑,在apply方法中通过exchange.getRequest()拿到ServerHttpRequest对象,从而可以获取到请求的参数、请求方式、请求头等信息
注意:
类的命名需要以RoutePredicateFactory结尾;
必须使用spring的bean加载到容器中;
必须继承AbstractRoutePredicateFactory;
必须声明静态内部类,声明属性来接收配置文件中对应的断言信息;
需要结合shortcutFieldOrder进行绑定;
通过apply进行逻辑判断,true就是匹配成功,false则匹配失败
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotEmpty;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
/**
* 自定义断言工厂
*/
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public static final String PARAM_KEY = "param";
public static final String REGEXP_KEY = "regexp";
public CheckAuthRoutePredicateFactory() {
super(CheckAuthRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("param", "regexp");
}
public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
if (!StringUtils.hasText(config.regexp)) {
return exchange.getRequest().getQueryParams().containsKey(config.param);
} else {
List<String> values = (List)exchange.getRequest().getQueryParams().get(config.param);
if (values == null) {
return false;
} else {
Iterator var3 = values.iterator();
String value;
do {
if (!var3.hasNext()) {
return false;
}
value = (String)var3.next();
} while(value == null || !value.matches(config.regexp));
return true;
}
}
}
public String toString() {
return String.format("Query: param=%s regexp=%s", config.getParam(), config.getRegexp());
}
};
}
@Validated
public static class Config {
@NotEmpty
private String param;
private String regexp;
public Config() {
}
public String getParam() {
return this.param;
}
public CheckAuthRoutePredicateFactory.Config setParam(String param) {
this.param = param;
return this;
}
public String getRegexp() {
return this.regexp;
}
public CheckAuthRoutePredicateFactory.Config setRegexp(String regexp) {
this.regexp = regexp;
return this;
}
}
}
(2)自定义的断言类名为CheckAuthRoutePredicateFactory,所以application.properties中使用CheckAuth作为断言规则配置
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotEmpty;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
/**
* 自定义断言工厂
*/
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(CheckAuthRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange exchange) {
if(config.getName().equals("qingyun")){
return true;
}
return false;
}
};
}
@Validated
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
server:
port: 8060
spring:
application:
name: api-gateway
cloud:
# gateway的配置
gateway:
# 路由规则
routes:
- id: order_route # 路由的唯一标识, 路由到 order
# uri: http://localhost:8020 # 需要转发的地址
uri: lb://order-nacos-service # 需要转发的地址 lb:使用nacos中的本地负载均衡策略
# 断言规则 用于路由规则的匹配
predicates:
- Path=/order-serv/**
# http://localhost:8060/order-serv/order/add 路由转到
# http://localhost:8020/order-serv/order/add
# - After=2017-01-20T17:42:47.789-07:00[Asia/Shanghai]
# - Header=X-Request-Id,\d+
# - Method=GET
# - Query=name,zhangsan|lisi
- CheckAuth=zhangsan
filters:
- StripPrefix=1 # 转发之前去掉第一层路径
# http://localhost:8020/order-serv/order/add 过虑成
# http://localhost:8020/order/add
# 配置 Nacos
nacos:
server-addr: 127.0.0.1:8848
discovery:
# server-addr: 127.0.0.1:8848
username: nacos
password: nacos
namespace: public
Filter过滤器
官网参考:Spring Cloud Gateway
自定义过滤器
注意:
类的命名需要以GatewayFilterFactory结尾;
必须使用spring的bean加载到容器中;
必须继承AbstractNameValueGatewayFilterFactory;
必须声明静态内部类,声明属性来接收配置文件中对应的断言信息;
需要结合shortcutFieldOrder进行绑定;
通过apply进行逻辑判断
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
import org.springframework.cloud.gateway.support.HttpStatusHolder;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus;
@Component
public class CheckAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAuthGatewayFilterFactory.Config> {
public CheckAuthGatewayFilterFactory() {
super(CheckAuthGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("value");
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
//获取到请求的参数值
String name = exchange.getRequest().getQueryParams().getFirst("name");
if(config.getValue().equals(name)){ //参数name的值等于配置的值
return chain.filter(exchange); //正常访问服务
}else{ //直接返回404
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND); //设置状态码
return exchange.getResponse().setComplete(); //设置结束访问
}
}
};
}
public static class Config {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
自定义全局过滤器(Global Filters)及如何忽略全局过滤器
局部过滤器和全局过滤器区别:
局部:局部针对某个路由,需要在路由中进行配置
全局:针对所有路由请求,一旦配置就会投入使用
实现GlobalFilter接口,重写filter方法
gateway 全局过滤器打印日志及如何忽略全局过滤器
Gateway整合Sentinel进行流控
Springcloud alibaba gateway 限流的两种实现方式 redis自带实现及整合sentinel实现网关限流
网关作为内部系统外的一层屏障,对内起到一定的保护作用,限流便是其中之一。网关层的限流可以针对不同路由进行限流,也可以针对接口进行限流,或者根据接口的特征进行分组限流。
Gateway – Cors解决跨域问题
gateway重试机制的使用
重试也要注意应用场景,读数据的接口比较适合重试的场景,写数据的接口就需要注意接口的幂等性了。还有就是重试次数如果太多的话会导致请求量加倍,给后端造成更大的压力,设置合理的重试机制才是最关键的。
参考博文
gateway重试机制的使用
spring Cloud Gateway动态路由实现
前文介绍了多种路由配置方式,它们存在一个共同问题:路由配置变更后必须重启Gateway应用才能生效,这样不适合生产环境!
如何让变动后的路由立即生效,而无需重启应用呢?
Spring Cloud Gateway动态路由实现