Gateway网关是我们服务的守门神,所有微服务的统一入口。Spring Cloud Gateway 是 Spring Cloud的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。在Gateway之前,SpringCloud并不自己开发网关,可能是觉得Netflflix公司的Zuul不行吧,然后自己就写了一个,也是替代Netflflix Zuul。其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
本身也是一个微服务,需要注册到Eureka。
官网:
https://spring.io/projects/spring-cloud-gateway
Route(路由):这是网关的基本模块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
Filter(过滤器):这是org.springframework.cloud.gateway.fifilter.GatewayFilter
的实例,我们可以使用它修改请求和响应。
nginx Nginx (engine x)
:是⼀个⾼性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。zuul
:是 Netflix 出品的⼀个基于 JVM 路由和服务端的负载均衡器。spring-cloud-gateway
: 是spring 出品的 基于spring 的⽹关项⽬,集成断路器,路径重写,性能⽐Zuul好。import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端发现服务
public class DemoGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(DemoGatewayApplication.class, args);
}
}
appliation.yml
# 端口号
server.port: 10010
# 应用名
spring.application.name: api-gateway
# 注册中心地址
eureka.client.service-url.defaultZone: http://8.131.239.157:10086/eureka/
spring:
cloud:
gateway:
# 路由si(集合)
routes:
# id(唯一标识)
- id: websocket_test
# 路由服务地址(转发后地址)
uri: http://127.0.0.1:9091
order: 9000
# 断言(判断哪些请求要被转发)
predicates:
- Path=/user/**
http://localhost:10010/user/fifindById?id=1
路由转发到http://localhost:9091/user/fifindById?id=1
/user
才会被路由谓词(predicates):当满足条件在进行路由转发
在 Spring Cloud Gateway 中谓词实现 GatewayPredicate 接口。其中类名符合: XXXRoutepredicateFactory
,其中 XXX 就是在配置文件中谓词名称。在上面示例中 Path=/demo/**
实际上使用的就是PathRoutePredicateFactory
。
所有的谓词都设置在 predicates 属性中,当设置多个谓词时取逻辑与条件,且一个谓 词只能设置一组条件,如果需要有个多条件,添加多个相同谓词。
表示路径满足/system/**同时包含参数 abc。
predicates:
- Path=/system/**
- Query=abc
支持正则表达式
jqk.
支持指定某个字段值
abc,jqk.
abc=jqka 或 abc=jqkw 能满足谓词条件。
predicates:
- Path=/system/**
- Query=abc,jqk.
测试:
http://localhost:9000/system/one?abc=jqk
完整版:
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/system/**
- Query=abc
filters:
- StripPrefix= 1
表示请求头中必须包含的内容。
如果 Header 只有一个值表示请求头中必须包含的参数。如果有两个值,第一个表示请求头必须包含的参数名,第二个表示请求头参数对应值。
predicates:
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
完整版:
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/system/**
- Query=abc
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
Method 表示请求方式。支持多个值,使用逗号分隔,多个值之间为 or 条件。
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/system/**
- Query=abc
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
- Method=GET,POST
允许访问的客户端地址
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/system/**
- Query=abc
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
- Method=GET,POST
- RemoteAddr=127.0.0.1
匹配请求中的Host信息。满足Ant模式。
?:匹配一个字符
*:匹配0个或多个字符
**:匹配0个或多个目录
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/system/**
- Query=abc
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
- Method=GET,POST
- RemoteAddr=127.0.0.1
- Host=127.0.0.1:9000
要求请求中包含指定的Cookie名和满足特定正则要求的值。
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/system/**
- Query=abc
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
- Method=GET,POST
- RemoteAddr=127.0.0.1
- Host=127.0.0.1:9000
- Cookie=age,.*
在指定时间点之前
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/system/**
- Query=abc
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
- Method=GET,POST
- RemoteAddr=127.0.0.1
- Host=127.0.0.1:9000
- Cookie=age,.*
- Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
在指定时间点之后
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/system/**
- Query=abc
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
- Method=GET,POST
- RemoteAddr=127.0.0.1
- Host=127.0.0.1:9000
- Cookie=age,.*
- Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
- After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
必须在设定的范围时间内,才能进行路由转发
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/system/**
- Query=abc
- Header=Connection,keep-alive
- Header=Cache-Control,max-age=0
- Method=GET,POST
- RemoteAddr=127.0.0.1
- Host=127.0.0.1:9000
- Cookie=age,.*
- Before=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
- After=2020-01-31T18:00:00.000+08:00[Asia/Shanghai]
- Between=2020-01-31T18:00:00.000+08:00[Asia/Shanghai], 2020-02-01T00:00:00.000+08:00[Asia/Shanghai]
设置服务转发的权重,用于限制某个的请求占比
语法:Weight=组名,负载均衡权重
suiyi:权重:20%
suiyi2:权重:80%
spring:
cloud:
routes:
- id: suiyi
uri: lb://demo-one
predicates:
- Path=/demo/**
- Weight=group,2
filters:
- StripPrefix=1
- id: suiyi2
uri: lb://demo-two
predicates:
- Path=/demo/**
- Weight=group,8
filters:
- StripPrefix=1
过滤器作为网关的其中一个重要功能,就是实现请求的鉴权。
执行顺序:
Spring Cloud Gateway 的 Filter 的执行顺序有两个:“pre” 和 “post”。“pre”和 “post” 分别会在请求被执行前调用和被执行后调用。
Gateway自带过滤器有几十个,常见自带过滤器有:
官网地址:
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.1.1.RELEASE/single/spring-cloud-gateway.html#_gatewayfilter_factories
过滤器名称 | 说明 |
---|---|
AddRequestHeader | 对匹配上的请求加上Header |
AddRequestParameters | 对匹配上的请求路由 |
AddResponseHeader | 对从网关返回的响应添加Header |
StripPrefix | 对匹配上的请求路径去除前缀 |
PrefixPath | 对匹配上的请求路径添加前缀 |
使用场景:
**过滤器类型:**Gateway有两种过滤器
对输出的响应设置其头部属性名称为i-love,值为itheima。
配置文件更新:
# 往响应过滤器中加入信息
- AddResponseHeader: i-love,itheima
全部配置:
spring:
cloud:
gateway:
# 全局过滤器配置
default-filters:
# 往响应过滤器中加入信息
- AddResponseHeader: i-love,itheima
- AddRequestParameters:
- AddRequestHeader:
- StripPrefix:
- PrefixPath:
结果展示:
在gateway中可以通过配置路由的过滤器PrefifixPath
实现映射路径中的前缀添加。可以起到隐藏接口地址的作用,避免接口地址暴露。
# 请求地址添加路径前缀过滤器
filters:
- PrefixPath=/user
完整配置:
spring:
cloud:
gateway:
routes:
- id: user-service-route # 路由id,可以随意写
# 代理服务地址;lb表示从Eureka中获取具体服务
uri: lb://user-service
# 路由断言,配置映射路径
predicates:
- Path=/**
# 请求地址添加路径前缀过滤器
filters:
- PrefixPath=/user
结果展示:
配置 | 访问地址 | 路由地址 |
---|---|---|
PrefifixPath=/user |
localhost:10010/findById?id=1 | localhost:9091/user/findById?id=1 |
PrefifixPath=/user/abc |
localhost:10010/findById?id=1 | localhost:9091/user/abc/findById?id=1 |
在gateway中通过配置路由过滤器StripPrefifix
,实现映射路径中地址的去除。通过StripPrefifix=1
来指定路由要去掉的前缀个数。
如:路径/api/user/1
将会被路由到/user/1
。
filters:
# 去除路径前缀过滤器
- StripPrefix=1
完整配置文件
spring:
cloud:
gateway:
routes:
- id: user-service-route # 路由id,可以随意写
# 代理服务地址;lb表示从Eureka中获取具体服务
uri: lb://user-service
# 路由断言,配置映射路径
predicates:
- Path=/**
# 请求地址添加路径前缀过滤器
filters:
# 去除路径前缀过滤器
- StripPrefix=1
访问效果:
配置 | 访问地址 | 路由地址 |
---|---|---|
StripPrefix=1 |
localhost:10010/api/user/findById?id=1 | localhost:9091/user/findById? id=1 |
StripPrefix=2 |
localhost:10010/aa/api/user/findById? id=1 | localhost:9091/user/findById? id=1 |
添加请求头参数,参数和值之间使用逗号分隔
filters:
- StripPrefix= 1
- AddRequestHeader=MyHeader,jqk
添加请求表单参数,多个参数需要有多个过滤器。
filters:
- AddRequestParameter=name,bjsxt
- AddRequestParameter=age,123
对指定响应头去重复。
语法:DedupeResponseHeader=响应头参数 响应头参数,strategy
可选参数 strategy 可取值:
RETAIN_FIRST
:默认值,保留第一个
RETAIN_LAST
:保留最后一个。
RETAIN_UNIQUE
:保留唯一的,出现重复的属性值,会保留一个。例如有两个 My:bbb 的属性,最后会只留一个。
filters:
- StripPrefix= 1
- DedupeResponseHeader=My Content-Type,RETAIN_UNIQUE
实现熔断时使用,支持 CircuitBreaker 和 Hystrix 两种
可以添加降级时的异常信息
限流过滤器
重定向。
有两个参数,status 和 url。其中 status 应该 300 系列重定向状态码
删除请求头参数
删除响应头参数
删除请求参数
重写请求路径
重写响应头参数
如果项目中使用Spring Security和Spring Session整合时,会使用到此属性。
具有权限验证时,建议的头信息内容。
设置重试次数
功能和StripPrefix类似。
请求的最大大小。包含maxSize参数,可以有单位'KB'或'MB'默认为B。
修改请求体内容
修改响应体内容
当请求路径为
/red/blue
时,会将/blue
发送给下游。
spring:
cloud:
routes:
- id: system
uri: lb://system
predicates:
- Path=/red/{segment}
filters:
- SetPath=/{segment}
替换请求参数头。
替换相应头参数
设置相应状态码
放行:
//12. 放⾏
return chain.filter(exchange);
拦截:
//7. 响应中放⼊返回的状态吗, 没有权限访问
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//8. 返回
return response.setComplete()
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Configuration
public class MyGlobalFileter implements GlobalFilter, Ordered {
/**
* 自定义过滤器规则
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("-----------------全局过滤器MyGlobalFilter---------------------");
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (StringUtils.isBlank(token)) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 定义过滤器执行顺序
* 返回值越小,越靠前执行
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
@Component
public class IPFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("经过IP过滤器");
ServerHttpRequest request = exchange.getRequest();
InetSocketAddress remoteAddress = request.getRemoteAddress();
System.out.println("请求的IP地址为:" + remoteAddress.getHostName());
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
}
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class UrlFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("经过url过滤器");
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println("请求的url为:" + path);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 2;
}
}
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.support.GatewayToStringStyler;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
public class MyRouteGatewayFilterFactory extends AbstractGatewayFilterFactory<MyRouteGatewayFilterFactory.Config> {
public MyRouteGatewayFilterFactory() {
super(MyRouteGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("在这个位置写点东西传递进来的 name: " + config.getName() + ",age:" + config.getAge());
return chain.filter(exchange);
}
@Override
public String toString() {
return GatewayToStringStyler.filterToStringCreator(MyRouteGatewayFilterFactory.this).append("name", config.getName()).toString();
}
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
public static class Config {
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Config() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
添加到配置文件中(application.yaml)
spring:
application:
name: sysgateway
cloud:
routes:
# 自定义配置
- id: myFilter
uri: lb:/jqk
predicates:
- Path=/project/**
filters:
- StripPrefix=1
- name: MyRoute
args:
# 传递的参数(name、age)
name: hello
age: 12
案例:
请求地址:http://localhost:9999/ribbon-app-service/getArgs?name=admin&age=20
自动转发到:http://ribbon-app-service/getArgs?name=admin&age=20
配置文件
spring:
application:
name: sysgateway
cloud:
gateway:
discovery: # 配置网关发现机制
locator: # 配置处理机制
enabled: true # 开启网关自动映射处理机制
lower-case-service-id: true # 开启微服务名称小写转换。Eureka对服务名管理默认全大写。
# 路由到执行IP
uri: http://127.0.0.1:9091
# 根据服务名称进行路由(从配置中心获取指定IP)
uri: lb://user-service
测试日志:
这次gateway进行路由时,会利用Ribbon进行负载均衡访问。日志中可以看到使用了负载均衡器。
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # ⽀持的⽅法
- GET
- POST
- PUT
- DELETE
// 将开始时间放入请求中
exchange.getAttributes().put("startTime", System.currentTimeMillis());
// 计算执行耗时
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute("startTime");
Long executeTime = System.currentTimeMillis() - startTime;
}));
完整版:
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Component
public class UrlFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("经过url过滤器");
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
System.out.println("请求的url为:" + path);
exchange.getAttributes().put("startTime", System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute("startTime");
if (startTime != null) {
long executeTime = System.currentTimeMillis() - startTime;
log.info("请求时间:{}", exchange.getRequest().getURI().getRawPath() + "d" + executeTime + "ms");
}
}));
}
@Override
public int getOrder() {
return 2;
}
}
exchange
代码信息// 获取请求头信息
HttpHeaders headers = exchange.getRequest().getHeaders();
String first = exchange.getRequest().getHeaders().getFirst("Content-type");
ServerHttpRequest build = exchange.getRequest().mutate().header("Content-type","json").build();
// 获取请求方式
String name = exchange.getRequest().getMethod().name();
// 获取请求URL地址信息
String host = exchange.getRequest().getURI().getHost();
String path1 = exchange.getRequest().getURI().getPath();
String rawPath = exchange.getRequest().getURI().getRawPath();
// 获取请求属性信息
exchange.getAttributes().put("startTime",123123);
Long startTime1 = exchange.getAttribute("startTime");
// 获取返回体
ServerHttpResponse response = exchange.getResponse();
限流,当我们的系统 被频繁的请求的时候,就有可能 将系统压垮,所以 为了解决这个问题,需要在每⼀个微服务中做限流操作,但是如果有了⽹关,那么就可以在⽹关系统做限流,因为所有的请求都需要先通过⽹关系统才能路由到微服务中。
令牌桶算法是⽐较常⻅的限流算法之⼀,⼤概描述如下:
pom.xml
依赖spring cloud gateway
默认使⽤redis
的RateLimter
限流算法来实现。所以我们要使⽤⾸先需要引⼊redis
的依赖。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redis-reactiveartifactId>
dependency>
在GatewayApplicatioin
引导类中添加如下代码,KeyResolver
⽤于计算某⼀个类型的限流的KEY
也就是说,可以通过KeyResolver
来指定限流的Key
。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端发现服务
public class DemoGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(DemoGatewayApplication.class, args);
}
// 定义一个KeyResolver
@Bean
public KeyResolver ipKeyResolver() {
return new KeyResolver() {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
};
}
}
修改application.yml中配置项,指定限制流量的配置以及REDIS的配置。
filters:
- PrefixPath=/test
- name: RequestRateLimiter
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
burstCapacity
:令牌桶总容量。
replenishRate
:令牌桶每秒填充平均速率。
key-resolver
:⽤于限流的键的解析器的 Bean 对象的名字。它使⽤ SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。
通过在 replenishRate
和中设置相同的值来实现稳定的速率 burstCapacity
。设置 burstCapacity
⾼于时,可以允许临时突发 replenishRate
。在这种情况下,需要在突发之间允许速率限制器⼀段时间(根据 replenishRate
),因为2次连续突发将导致请求被丢弃( HTTP 429 - Too Many Requests
) key-resolver: "#{@userKeyResolver}"
⽤于通过SPEL表达式
来指定使⽤哪⼀个KeyResolver
。
如上配置:
表示 ⼀秒内,允许 ⼀个请求通过,令牌桶的填充速率也是⼀秒钟添加⼀个令牌。
最⼤突发状况 也只允许 ⼀秒内有⼀次请求,可以根据业务来调整 。
spring:
application:
name: sysgateway
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': # 匹配所有请求
allowedOrigins: "*" #跨域处理 允许所有的域
allowedMethods: # ⽀持的⽅法
- GET
- POST
- PUT
- DELETE
routes:
- id: goods
uri: lb://goods
predicates:
- Path=/goods/**
filters:
- StripPrefix= 1
- name: RequestRateLimiter #请求数限流 名字不能随便写
args:
key-resolver: "#{@ipKeyResolver}"
redis-rate-limiter.replenishRate: 1
redis-rate-limiter.burstCapacity: 1
- id: system
uri: lb://system
predicates:
- Path=/system/**
filters:
- StripPrefix= 1
# 配置Redis 127.0.0.1可以省略配置
redis:
host: 192.168.200.128
port: 6379
server:
port: 9101
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka
instance:
prefer-ip-address: true