Gateway
pring cloud gateway是spring官方基于Spring 5.0、Spring Boot2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供简单、有效和统一的API路由管理方式,Spring Cloud Gateway作为Spring Cloud生态系统中的网关,目标是替代Netflix Zuul,其不仅提供统一的路由方式,并且还基于Filer链的方式提供了网关基本的功能,例如:安全、监控/埋点、限流等。
工作原理
客户端向Spring Cloud Gateway发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关Web处理程序。该处理程序通过特定于请求的过滤器链运行请求。筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。所有“前置”过滤器逻辑均被执行。然后发出代理请求。发出代理请求后,将运行“后”过滤器逻辑。
引入POM文件
<!-- 注意,依赖中一定不要包含spring-cloud-starter-web依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- consul作注册中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- 健康监测 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- redis限流使用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
项目目录
- 新建Example(8080)和Example2(8081)模拟两个服务。
- Example的Controller内容:
@RestController
public class Controller {
@GetMapping("/a")
public String exampleController1(){
return "example - a";
}
@GetMapping("/aaa")
public String exampleController2(){
return "example - aaa";
}
@GetMapping("/aaa/a")
public String exampleController3(){
return "example - aaa - a";
}
}
- Example2的Controller内容:
@RestController
public class Example2Controller {
@GetMapping
public String controller(@ModelAttribute Model model) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "example2 GET " + model.toString();
}
@PostMapping
public String controllerPost(@RequestBody Model model) {
return "example2 POST " + model.toString();
}
@GetMapping("/a")
public String controllerA() {
return "example2 - a";
}
@GetMapping("/a/b")
public String controllerAB() {
return "example2 - a - b";
}
@GetMapping("/b")
public String controllerB() {
return "example2 - b";
}
@GetMapping("/example2/c")
public String controllerC(String name) {
return "example2 - c " + name;
}
}
- Example和Example2分别连接到Consul注册中心(省略连接配置文件)结果如图:
Gateway(8082)项目
-
路由:路由是网关最基础的部分,路由信息有一个ID、一个目的URL、一组断言和一组Filter组成。如果断言路由为真,则说明请求的URL和配置匹配。
-
断言:Java8中的断言函数。Spring Cloud Gateway中的断言函数输入类型是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的断言函数允许开发者去定义匹配来自于http request中的任何信息,比如请求头和参数等。
-
过滤器:一个标准的Spring webFilter。Spring cloud gateway中的filter分为两种类型的Filter,分别是Gateway Filter和Global Filter。过滤器Filter将会对请求和响应进行修改处理。
-
匹配规则:一组断言(predicates)全部满足会进行filters过滤结果转发到uri。
简单路由
spring:
application:
name: gateway
cloud:
consul:
enabled: true
host: localhost
port: 8500
discovery:
health-check-interval: 10s
# 是否检查自己的心跳,本地为false,方便测试
register-health-check: true
instance-id: ${spring.application.name}:${server.port}
register: true
prefer-ip-address: true
# health-check-critical-timeout: 30s
gateway:
# loadbalancer:
# # 当找不到对应实例的是否,是否http错误码为404,如果为false,则为503
# use404: true
# discovery:
# locator:
# # 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
# enabled: true
routes:
- id: example1
uri: lb://example1
predicates:
- Path=/aaa/**
输入:http://localhost:8082/aaa/a 则路由到:http://localhost:8080/aaa/a,输出:example – aaa – a
截取请求
spring:
cloud:
gateway:
routes:
- id: example1
uri: lb://exampleTwo
predicates:
- Path=/example1/aaa/**
filters:
# 截取路径位数
- StripPrefix=2
输入:http://localhost:8082/example1/aaa/a 则路由到:http://localhost:8081/a,输出:example2 – a
传入指定参数
spring:
cloud:
gateway:
routes:
- id: example2
uri: lb://exampleTwo
predicates:
- Path=/example2
filters:
# 截取路径位数
- StripPrefix=1
# 添加默认参数
- AddRequestParameter=name, Param
- AddRequestParameter=age, 1
输入:http://localhost:8082/example2 则路由到:http://localhost:8081,输出:example2 GET Model{name=‘Param’, age=1}
熔断
spring:
cloud:
gateway:
### 默认filters,全局生效
default-filters:
- name: defuaultHystrix
args:
name: defaultFallbackcmd
#调用网关/fallback的controller方法
fallbackUri: forward:/fallback
routes:
- id: example2
uri: lb://exampleTwo
predicates:
- Path=/example2
filters:
# 截取路径位数
- StripPrefix=1
# 添加默认参数
- AddRequestParameter=name, Param
- AddRequestParameter=age, 1
## 熔断
- name: Hystrix
args:
name: fallbackcmd
### fallback 时调用的方法 http://localhost:8001/fallback
#调用网关/fallback的controller方法
fallbackUri: forward:/fallback
hystrix:
command:
default: # 如果不是默认,可以写成name的值: fallbackcmd
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 #1s
输入:http://localhost:8082/example2 则路由到:http://localhost:8081,由于目标Controller方法睡眠1s所以触发熔断机制,跳转熔断连接:http://localhost:8082/fallback,输出:Hystrix。把熔断时间调大即可正常访问。
限流
确保redis在 3 以上的版本!!!
新建配置类:
@Configuration
public class UriKeyResolver implements KeyResolver {
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getURI().getPath());
}
}
spring:
cloud:
gateway:
routes:
- id: example2
uri: lb://exampleTwo
predicates:
- Path=/example2
filters:
# 截取路径位数
- StripPrefix=1
# 添加默认参数
- AddRequestParameter=name, Param
- AddRequestParameter=age, 1
## 限流
- name: RequestRateLimiter
args:
### 限流过滤器的 Bean 名称
key-resolver: '#{@uriKeyResolver}'
### 希望允许用户每秒处理多少个请求
redis-rate-limiter.replenishRate: 1
### 用户允许在一秒钟内完成的最大请求数
redis-rate-limiter.burstCapacity: 3
输入:http://localhost:8082/example2 则路由到:http://localhost:8081,访问正常输出:example2 GET Model{name=‘Param’, age=1}。频繁刷新则触发限流措施,页面显示:429
其它断言写法
- Header 属性匹配
如果请求头具有名为X-Request-Id其值与\d+正则表达式匹配的标头(即,其值为一个或多个数字),则此路由匹配。
predicates:
- Header=X-Request-Id, \d+
- Host 属性匹配
如果请求具有这种路由匹配Host用的头值www.somehost.org或beta.somehost.org或www.anotherhost.org。
predicates:
- Host=**.somehost.org,**.anotherhost.org
- Method 属性匹配
匹配GET请求
predicates:
- Method=GET
- 路由匹配
根据路由策略匹配
predicates:
- Path=/red/{segment},/blue/{segment}
- 根据参数匹配
如果请求包含green查询参数,则匹配。
predicates:
- Query=green
如果请求包含red开头(任意长度)且gree开头(长度为5)参数,则匹配。
predicates:
- Query=red.*, gree.
- 设置排序
匹配顺序按order指定的升序(值小优先)来依次匹配。
- id: example2
uri: lb://exampleTwo
order: 100
- id: example1
uri: lb://exampleTwo
order: 50
先匹配example1,如果不满足再匹配example2。
代码实现Filter
实现自定义Filter
public class HeaderFilter implements GatewayFilter {
//Mono: 包含0个或1个元素的异步序列
//Flux: 包含0个或多个元素的异步序列
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//向请求中添加指定的header
ServerHttpRequest changeRequest = exchange.getRequest()
.mutate()
.header("X-Request-red", "blue")
.build();
//将变更过的请求写入到exchange
ServerWebExchange webExchange = exchange.mutate()
.request(changeRequest)
.build();
//将变更过的exchange向下传递
return chain.filter(webExchange);
}
}
注入自定义FilterBean
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(ps -> ps.path("/**")
.filters(f -> f.filter(new HeaderFilter()))
.uri("http://localhost:8082")
.id("custom_filter_route"))
.build();
}
- 全局Filter
Global Filter不需要在任何具体路由规则中进行注册,只需在类上添加@Componment注解并实现GlobalFilter即可。
Filter工作原理
自定义三个Filter
public class OneFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("pre-1");
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
System.out.println("post-1");
})
);
}
}
public class TwoFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("pre-2");
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
System.out.println("post-2");
})
);
}
}
public class ThreeFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
System.out.println("pre-3");
return chain.filter(exchange).then(
Mono.fromRunnable(() -> {
System.out.println("post-3");
})
);
}
}
注入自定义FilterBean
@Bean
public RouteLocator someRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(ps -> ps.path("/**")
.filters(f -> f.filter(new OneFilter())
.filter(new TwoFilter())
.filter(new ThreeFilter()))
.uri("http://localhost:8082")
.id("custom_filter_route"))
.build();
}
根据注入顺序先执行OneFilter->TwoFilter->ThreeFilter
控制台输出:
pre-1
pre-2
pre-3
post-3
post-2
post-1
执行顺序