Spring Cloud Alibaba系列(四)使用gateway作为服务网关
什么是网关
在微服务架构里,服务的粒度被进一步细分,各个业务服务可以被独立的设计、开发、测试、部署和管理。这时,各个独立部署单元可以用不同的开发测试团队维护,可以使用不同的编程语言和技术平台进行设计,这就要求必须使用一种语言和平台无关的服务协议作为各个单元间的通讯方式。
换句话说就是网关为所有的请求提供了统一的入口,方便我们对服务请求和响应做统一管理。
为什么要用网关
API 网关是一个处于应用程序或服务(提供 REST API 接口服务)之前的系统,用来管理授权、访问控制和流量限制等,这样 REST API 接口服务就被 API 网关保护起来,对所有的调用者透明。
什么是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链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
gateway工作原理
客户端向Spring Cloud网关发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。该处理程序运行通过特定于请求的过滤器链发送请求。过滤器由虚线分隔的原因是,过滤器可以在发送代理请求之前或之后执行逻辑。执行所有“前置”过滤器逻辑,然后发出代理请求。发出代理请求后,将执行“后”过滤器逻辑。
路由规则
路由和过滤器是gateway中非常重要的两个概念,gateway本身提供了非常丰富的路由规则和多种过滤器来适配我们的需求。gateway提供了11种路由规则,分别是:
-
后置路由谓词工厂
该谓词匹配在当前日期时间之后发生的请求。参数名为 After
-
前置路由谓词工厂
该谓词匹配当前日期时间之前发生的请求。参数名为 Before
-
时间段路由谓词工厂
该谓词匹配在datetime1之后和datetime2之前发生的请求。参数名为 Between
-
cookie路由谓词工厂
该谓词匹配具有给定名称的cookie,并且值匹配正则表达式。参数名为 Cookie
-
标头路由谓词工厂
该谓词与具有给定名称的标头匹配,并且值与正则表达式匹配。参数名为 Header
-
主机路由谓词工厂
该谓词是指由路由进行匹配,匹配多个路由时用,隔开。参数名为 Host
-
方法路由谓词工厂
该参数是一个或多个要匹配的HTTP方法。参数名为 Method
-
路径路由谓词工厂
该谓词是指在请求路径上加一个前缀,以此来匹配。参数名为 Path
-
查询路由谓词工厂
-
RemoteAddr路由谓词工厂
-
重量路线谓词工厂
其中,我们比较常用的就是路径路由谓词工厂,配合StripPrefix GatewayFilter工厂,实现我们的路由匹配转发。
路径路由谓词工厂配置如下:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由
routes:
# 路由id,建议配合服务名
- id: demo_route
#匹配路由名
uri: lb://demo-provider
predicates:
# 断言,路径相匹配的进行路由
- Path=/demo/**
配置的含义就是,如果请求路径中是/demo/**,则转发到demo-provider服务。
网关过滤器
在spring cloud gateway 2.2.2.RELEASE版本中,已经默认实现了30种过滤器。
序号 | 过滤器工厂 | 作用 | 参数 |
---|---|---|---|
1 | AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
2 | AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
3 | AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
4 | DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
5 | Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand的名称 |
6 | CircuitBreaker | 为路由引入Resilience4J断路器保护 | CircuitBreaker的名称 |
7 | FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
MapRequestHeader | 更新原始请求中的Header | Header的值 | |
9 | PrefixPath | 为原始请求头添加前缀 | 前缀路径 |
10 | PreserveHostHeader | 为请求添加preserverHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的host | 无 |
11 | RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
12 | RedirectTo | 将原始请求重定向到指定的url | http状态码及重定向的url |
13 | RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
14 | RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
15 | RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
16 | RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteLocationResponseHeader | 重写响应头的Location 的值 |
||
18 | RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
19 | SaveSession | 在转发请求之前,强制执行WebSession::save 操作 |
无 |
20 | SecureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
21 | SetPath | 修改原始的请求路径 | 修改后的值 |
22 | SetRequestHeader | 修改原始请求中的某个Header的值 | Header名称,修改后的值 |
23 | SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
24 | SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
25 | StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
26 | Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
27 | RequestSize | 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large |
请求包大小,单位为字节,默认值为5M |
28 | ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
29 | ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
30 | Default | 为所有路由添加过滤器 | 过滤器工厂名称及值 |
这里比较常用的如第25种,配置如下:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由的功能,利用微服务名称进行路由
routes:
# 路由id,建议配合服务名
- id: demo_route
#匹配路由名
uri: lb://demo-provider
predicates:
# 断言,路径相匹配的进行路由
- Path=/demo/**
filters:
- StripPrefix=1
一般情况下我们配合path路由使用,这里的意思是假如,我们的demo-provider服务种有一个/test的接口,实际上我们的请求路径经过网关时应该时/demo/test,这样就能把这个路由分发到demo-provider服务中,但是分发过去的路由是/demo/test,和我们实际的/test接口不一样。这时候我们用StripPrefix=1,来截取掉一级路由,这样转发过去的路由就是/test了。
自定义网关过滤器
除了上面提供的30种过滤器外,我们还可以实现自定义的过滤器。
1. 实现GatewayFilter接口和Ordered接口
gatewayFilter接口是为了实现请求过滤,ordered接口是为了给过滤器设定优先级,值越大级别越低。
想要实现一个自定义的过滤器,无非就是两个步骤:1.实现过滤器,2.将过滤器添加到具体路由上。
public class TokenGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("这里处理自身逻辑");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
@Configuration
class RouteConfiguration{
@Bean
public RouteLocator routeLocator(RouteLocatorBuilder builder){
return builder.routes().route( r->
r.path("/demo/**")
.uri("lb://demo-provider ")
.filter(new TokenGatewayFilter())
.id("demo_route "))
.build();
}
}
2.继承AbstractGatewayFilterFactory类
@Component
public class TokenCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<TokenCheckGatewayFilterFactory.Config> {
public TokenCheckGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("enabled");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
system.out.println("这里处理自身逻辑")
return chain.filter(exchange);
};
}
public static class Config {
// 控制是否开启认证
private boolean enabled = true;
public Config() {}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}
这里我们可以直接在application.yml中为需要过滤的路由添加这个过滤器。
spring:
cloud:
gateway:
routes:
- id: demo_route # 路由id,建议配合服务名
uri: lb://demo-provider #匹配路由名
predicates:
- Path=/demo/** # 断言,路径相匹配的进行路由
filters:
- TokenCheck=true
需要注意的是,这个地方自定义的过滤器名称必须是XXGatewayFilterFactory,并且配置文件中配置过滤器时名字必须时这个XX。
当然,我们也可以为每个路由都添加这个过滤器,可以直接这样写配置,而不用在每个路由上都去写。
spring:
cloud:
gateway:
default-filters:
- TokenCheck=true
3.实现GlobalFilter和ordered
这个GlobalFilter从名字中就可以看出,是一个全局过滤器,也就是说实现这个接口后,所有的请求都会被过滤,我们就不需要在去找往某个路由中加过滤器了。
@Component
public class TokenGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("这里处理自身逻辑");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
以上就是实现自定义网关过滤器的三种方式了。实际开发中根据需求来实现合适的过滤器就可以了。