1. 简介
1.1 了解网关
网关:单体应用拆分成多个服务后,对外需要一个统一入口,解耦客户端与内部服务。
- 网关核心功能是路由转发,因此不要有耗时操作在网关上处理,让请求快速转发到后端服务上。
- 网关还能做统一的熔断、限流、认证、日志监控等。
- 可以和服务注册中心完美的整合,如:Eureka、Consul、Nacos。
1.2 了解SpringCloud Gateway
Spring Cloud Gateway 是 Spring 公司基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。注意:SpringCloud alibaba 技术栈中并没有提供自己的网关,我们可以采用 Spring Cloud Gateway 来做网关
功能特征:
- 基于 Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
- 动态路由:能够匹配任何请求属性;
- 集成 Spring Cloud 服务发现功能;
- 可以对路由指定 Predicate(断言)和 Filter(过滤器);
- 易于编写的 Predicate(断言)和 Filter(过滤器);
- 集成Hystrix的断路器功能;
- 请求限流功能;
- 支持路径重写。
三个核心概念:
- Route:路由是网关的基本构件。它由 ID、目标 URI、谓词集合和过滤器集合定义。如果聚合谓词为真,则匹配路由。
- Predicate:这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
- Filter:可以在发送下游请求之前或之后修改请求和响应。
- 在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,
- 在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
优点:
- 性能强劲:是第一代网关 Zuul 的 1.6 倍。
- 功能强大:内置了很多实用的功能,例如转发、监控、限流等。
- 设计优雅,容易扩展。
缺点:
- 其实现依赖 Netty 与 WebFlux,不是传统的 Servlet 编程模型,学习成本高。
- 不能将其部署在 Tomcat、Jetty 等 Servlet 容器里,只能打成 jar 包执行。
需要 Spring Boot 2.0 及以上的版本,才支持。
1.3 了解SpringCloud Gateway工作原理
客户端向 Spring Cloud Gateway 发出请求,然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
2. 快速上手
2.1 引入依赖
注意:Spring Cloud Gateway 需要 Spring Boot 和 Spring Webflux 提供的 Netty 的运行时环境,因此,它不可以打包成 war 包,也不可以在传统的 Servlet 容器(比如tomcat)中运行。所以 Spring Cloud Gateway 项目中不能依赖
<artifactId>spring-boot-starter-web</artifactId>
,要不然会报错。 ```xml <?xml version=”1.0” encoding=”UTF-8”?> <project xmlns=”http://maven.apache.org/POM/4.0.0“xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0 com.xuwei springcloudalibaba-gateway 1.0-SNAPSHOT 17 17 17 2.2.7.RELEASE Hoxton.SR12 2.3.12.RELEASE org.springframework.cloud spring-cloud-starter-gateway org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring.cloud.alibaba.version} pom import org.springframework.cloud spring-cloud-dependencies ${spring.cloud.version} pom import org.springframework.boot spring-boot-dependencies ${spring.boot.version} pom import org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok
<a name="Drrz0"></a>
## 2.2 修改配置文件
```yaml
server:
port: 8888
spring:
application:
name: api-gateway
cloud:
gateway:
routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
- id: product_route # 当前路由的标识, 要求唯一
uri: http://localhost:8081 # 请求要转发到的地址
order: 1 # 路由的优先级,数字越小级别越高
predicates: # 断言(就是路由转发要满足的条件)
- Path=/product-serv/** # 当请求路径满足Path指定的规则时,才进行路由转发
filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
- StripPrefix=1 # 转发之前去掉1层路径.
2.3 集成Nacos
去掉关于路由的配置,自动寻找服务。这时候,只要按照网关地址/微服务/接口的格式去访问,就可以得到成功响应。
server:
port: 8888
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
enabled: true
3. 断言工厂
3.1 常用断言工厂
当请求 gateway 的时候, 使用断言对请求进行匹配, 如果匹配成功就路由转发, 如果匹配失败就返回 404。SpringCloud Gateway 包括许多内置的断言工厂,所有这些断言都与 HTTP 请求的不同属性匹配。具体如下:
- 基于 Datetime 类型的断言工厂:
- AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期。例如
- After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
- BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期。
- BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内。
- AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期。例如
- 基于远程地址的断言工厂:
- RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中。例如
- RemoteAddr=192.168.1.1/24
- RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中。例如
- 基于 Cookie 的断言工厂:
- CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求 cookie 是否具有给定名称且值与正则表达式匹配。例如
-Cookie=chocolate, ch.
- CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求 cookie 是否具有给定名称且值与正则表达式匹配。例如
- 基于 Header 的断言工厂:
- HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求 Header 是否具有给定名称且值与正则表达式匹配。例如
-Header=X-Request-Id, \d+
- HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求 Header 是否具有给定名称且值与正则表达式匹配。例如
- 基于 Host 的断言工厂:
- HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。例如
-Host=**.testhost.org
- HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。例如
- 基于 Method 请求方法的断言工厂:
- MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。例如
-Method=GET
- MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。例如
- 基于 Path请求路径的断言工厂:
- PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。例如
-Path=/foo/{segment}
- PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。例如
- 基于Query 请求参数的断言工厂:
- QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。例如
-Query=baz, ba.
- QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。例如
- 基于路由权重的断言工厂:
- WeightRoutePredicateFactory:接收一个[组名,权重],然后对于同一个组内的路由按照权重转发。
routes:
-id: weight_route1
uri: host1
predicates:
-Path=/product/**
-Weight=group3, 1
-id: weight_route2
uri: host2
predicates:
-Path=/product/**
-Weight= group3, 9
3.2 自定义断言工厂
自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
- WeightRoutePredicateFactory:接收一个[组名,权重],然后对于同一个组内的路由按照权重转发。
- 必须 spring 组件装配 bean;
- 工厂类命名必须加上
RoutePredicateFactory
作为结尾; - 必须继承
AbstractRoutePredicateFactory
- 必须声明静态内部类 声明属性来接收 配置文件中对应的断言的信息;
- 需要结合
shortcutFieldOrder
进行绑定; - 通过 apply 进行逻辑判断 true 就是匹配成功 false 匹配失败。
1、定义工厂类。
@Component
@Slf4j
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange serverWebExchange) {
log.info("调用CheckAuthRoutePredicateFactory" + config.getName());
if(config.getName().equals("xushu")){
return true;
}
return false;
}
};
}
/**
* 快捷配置
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("name");
}
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
2、修改配置文件。
spring:
cloud:
gateway:
#设置路由:路由id、路由到微服务的uri、断言
routes:
- id: order_route #路由ID,全局唯一
uri: http://localhost:8020 #目标微服务的请求地址和端口
predicates:
# 测试:http://localhost:8888/order/findOrderByUserId/1
- Path=/order/** #Path路径匹配
#自定义CheckAuth断言工厂
- CheckAuth=xushu
4. 过滤器工厂
4.1 常用过滤器工厂
Gateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等。参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories。
- AddRequestHeader:将 X-Request-red:blue 标头添加到所有匹配请求的下游请求标头中。
- AddRequestParameter:为原始请求添加请求参数。
- AddResponseHeader:为原始响应添加 Header。
- PrefixPath:为原始请求路径添加前缀。
RedirectTo:将原始请求重定向到指定的 URL。
spring:
cloud:
gateway:
#设置路由:路由id、路由到微服务的uri、断言
routes:
- id: order_route #路由ID,全局唯一
uri: http://localhost:8020 #目标微服务的请求地址和端口
#配置过滤器工厂
filters:
- AddRequestHeader=X-Request-red, blue
- AddRequestParameter=color, blue # 添加请求参数
- PrefixPath=/mall-order # 添加前缀 对应微服务需要配置context-path
- RedirectTo=302, https://www.baidu.com/ #重定向到百度
server:
servlet:
context-path: /mall-order
4.2 自定义过滤器工厂
1、需要继承 AbstractNameValueGatewayFilterFactory 且我们的自定义名称必须要以 GatewayFilterFactory 结尾并交给 spring 管理。
@Component
@Slf4j
public class CheckAuthGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
@Override
public GatewayFilter apply(NameValueConfig config) {
return (exchange, chain) -> {
log.info("调用CheckAuthGatewayFilterFactory==="
+ config.getName() + ":" + config.getValue());
return chain.filter(exchange);
};
}
}
2、配置自定义的过滤器工厂。
spring:
cloud:
gateway:
#设置路由:路由id、路由到微服务的uri、断言
routes:
- id: order_route #路由ID,全局唯一
uri: http://localhost:8020 #目标微服务的请求地址和端口
#配置过滤器工厂
filters:
- CheckAuth=fox,男
4.4 全局过滤器配置
局部过滤器针对某个路由,需要在路由中进行配置;而全局过滤器针对所有路由请求,一旦定义就会投入使用。
LoadBalancerClientFilter
:LoadBalancerClientFilter
会查看 exchange 的属性 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的值(一个URI),如果该值的 scheme 是 lb,比如:lb://myservice ,它将会使用 Spring Cloud 的 LoadBalancerClient 来将 myservice 解析成实际的 host 和 port,并替换掉 ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR 的内容。其实就是用来整合负载均衡器Ribbon的。
自定义全局过滤器实现:
@Component
public class LogFilter implements GlobalFilter {
Logger log= LoggerFactory.getLogger(this.getClass());
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info(exchange.getRequest().getPath().value());
return chain.filter(exchange);
}
}
5. 实现SpringCloud Gateway跨域
1、yaml 实现方式。参考:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configurationspring。
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods:
- GET
- POST
- DELETE
- PUT
- OPTION
2、通过 java 配置的方式。
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
6. 集成Sentinel
网关作为内部系统外的一层屏障, 对内起到一定的保护作用, 限流便是其中之一. 网关层的限流可以简单地针对不同路由进行限流, 也可针对业务的接口进行限流,或者根据接口的特征分组限流。参考:https://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81