Gateway简介
SpringCloud Gateway是SpringCloud的一个全新项目,目标是取代Netflix Zuul。它基于Spring5.0+SpringBoot2.0+WebFlux(基于高性能的Reactor模式响应式通信框架Netty,异步非阻塞模型)等技术开发,性能盖于Zuul。微服务中的网关旨在为微服务架构提供一种简单有效的统一的API路由管理方式。
SpringCloud Gateway不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如鉴权、流量控制、熔断、路径重写、日志监控等。
网关在架构中的位置如下图:
Gateway核心概念
在网关路由过程中,一个请求要通过网关根据一定的条件匹配->匹配成功之后可以将请求转发到指定的服务地址。而在这个过程中,我们可以进行一些比较具体的控制(限流、日志、黑白名单),其中几个重要概念如下:
- 路由(route):网关最基础的部分,也是网关比较基础的工作单元。路由由一个ID、一个目标URL、一系列的断言和过滤器组成
- 断言(predicates):参考了Java8中的断言
java.util.function.Predicate
,开发人员可以匹配HTTP请求中的所有内容(包括请求头、请求参数等等),如果断言与请求匹配成功则继续路由 - 过滤器(filter):一个标准的Spring Web Filter。使用过滤器,可以再请求之前或请求之后执行相应的业务逻辑
其工作过程如下图:
Gateway应用
我们下面的网关模块的定义是在博文SpringCloud Netflix之Eureka Server中搭建的大框架下创建的,有需要的可以参考该博文的大框架的搭建。
我们在spring-cloud-demo模块下创建子模块gateway,其pom结构如下:
<?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">
<parent>
<artifactId>spring-cloud-demo</artifactId>
<groupId>com.rubin</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>gateway</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
</project>
编写启动类:
package com.rubin.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayBootstrap {
public static void main(String[] args) {
SpringApplication.run(GatewayBootstrap.class, args);
}
}
配置文件如下:
server:
port: 9200
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: service-resume-router
uri: lb://scn-service-resume
predicates:
- Path=/api-resume/**
filters:
- StripPrefix=1
- id: service-auto-deliver-router
uri: lb://scn-service-auto-deliver
predicates:
- Path=/api-auto-deliver/**
# 详情参考:https://docs.spring.io/spring-cloud-gateway/docs/2.2.10.BUILD-SNAPSHOT/reference/html/#configuring-route-predicate-factories-and-gateway-filter-factories
# 时间点后匹配
# - After=2017-01-20T17:42:47.789-07:00[America/Denver]
# 时间点前匹配
# - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
# 时间区间匹配
# - Between=2017-01-20T17:42:47.789-07:00[America/Denver],2017-01-21T17:42:47.789-07:00[America/Denver]
# 指定Cookie正则匹配指定值
# - Cookie=chocolate, ch.p
# 指定Header正则匹配指定值
# - Header=X-Request-Id, \d+
# 请求Host匹配指定值
# - Host=**.somehost.org,**.anotherhost.org
# 请求Method匹配指定请求⽅式
# - Method=GET,POST
# 请求路径正则匹配
# - Path=/red/{segment},/blue/{segment}
# 请求包含某参数
# - Query=green
# 请求包含某参数并且参数值匹配正则表达式
# - Query=red, gree.
# 远程地址匹配
# - RemoteAddr=192.168.1.1/24
filters:
- StripPrefix=1 # 可以去掉api-auto-deliver之后转发 数字代表去掉几层
- RequestTime=true
eureka:
instance:
hostname: 127.0.0.1
prefer-ip-address: true
# 配置中心读取的配置不能解析@。。@来标注的pom文件中的变量,只能写死或者配置在该配置文件中用${}的形式引用
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:1.0-SNAPSHOT
# 租约续约间隔时间,默认30秒
lease-renewal-interval-in-seconds: 30
# 租约到期,服务时效时间,默认值90秒,服务超过90秒没有发生⼼跳,EurekaServer会将服务从列表移除
lease-expiration-duration-in-seconds: 90
client:
service-url:
defaultZone: http://eureka-host:8761/eureka/,http://eureka-host:8762/eureka/
register-with-eureka: true
# 每隔多久拉取一次服务列表
registry-fetch-interval-seconds: 30
fetch-registry: true
# 配制了该项 回阻止将该实例注册为一个eureka client 默认是true 默认自动加入一个Maker类标记 所以引入eureka-client依赖之后 加不加@EnableEurekaClient都会默认注册进EurekaServer
# enabled: false
# springboot中暴露健康检查等断点接⼝
management:
endpoints:
web:
exposure:
include: "*"
# 暴露健康接口的细节
endpoint:
health:
show-details: always
该配置文件比较重要,我们下文会详细讲解。
做完以上几步之后,我们的网关就搭建成功了。
Gateway路由规则详解
SpringCloud Gateway帮我们内置了很多Predicates功能,实现了各种路由匹配规则(通过Header、请求参数等作为条件)匹配到对应的路由。
时间点后匹配
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
时间点前匹配
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
时间区间匹配
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver],
2017-01-21T17:42:47.789-07:00[America/Denver]
指定Cookie正则匹配指定值
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
指定Header正则匹配指定值
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
请求Host匹配指定值
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
请求Method匹配指定请求方式
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
请求路径正则匹配
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
请求包含某参数
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=green
请求包含某参数并且参数值匹配正则表达式
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=red, gree.
远程地址匹配
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
Gateway动态路由详解
Gateway支持自动从注册中心中获取服务列表并访问,即所谓的动态路由。实现的条件就是需要引入注册中心客户端的依赖并且动态配置路由规则的uri
属性为lb://服务名称
的形式,具体配置参考上述应用中的配置文件。
Gateway过滤器
Gateway过滤器简介
从过滤器的生命周期的角度来说,分为前置过滤器(路有前调用)和后置过滤器(路由响应后调用);从过滤器类型的角度,分为GatewayFilter(应用到单个路由上)和GlobalFilter(应用到所有路由上)两种。
例如内置的GatewayFilter:StripPrefixGatewayFilter可以去掉url中的占位后再进行转发,示例如下:
predicates:
- Path=/resume/**
filters:
- StripPrefix=1 # 可以去掉resume之后转发,配置为几就去掉几层前置占位
自定义GatewayFilter
我们自定义一个 GatewayFilter 用来记录请求转发的耗时,并使用日记打印,具体代码如下:
package com.rubin.gateway.filter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
@Slf4j
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
private static final String KEY = "openFlag";
private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY);
}
public RequestTimeGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
if (startTime != null && config.isOpenFlag()) {
StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
.append(": ")
.append(System.currentTimeMillis() - startTime)
.append("ms");
log.info(sb.toString());
}
}));
}
};
}
@Data
public static class Config {
private boolean openFlag;
}
}
将其配置在路由规则上并生效:
predicates:
- Path=/resume/**
filters:
- StripPrefix=1 # 可以去掉resume之后转发,配置为几就去掉几层前置占位
- RequestTime=true
自定义全局过滤器
我们定义一个黑名单过滤器来实现全局过滤器,代码如下:
package com.rubin.gateway.filter;
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.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.ArrayList;
import java.util.List;
/**
* 全局过滤器
*/
@Slf4j
@Component
public class BlackListFilter implements GlobalFilter, Ordered {
private static List<String> blackList = new ArrayList<>();
static {
// 模拟本机地址
blackList.add("0:0:0:0:0:0:0:1");
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 从上下文中取出request和response对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 从request对象中获取客户端ip
String clientIp = request.getRemoteAddress().getHostString();
// 拿着clientIp去黑名单中查询,存在的话就决绝访问
if (blackList.contains(clientIp)) {
// 拒绝访问,返回
response.setStatusCode(HttpStatus.UNAUTHORIZED); // 状态码
log.debug("=====>IP:" + clientIp + " 在黑名单中,将被拒绝访问!");
String data = "Request be denied!";
DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
return response.writeWith(Mono.just(wrap));
}
// 合法请求,放行,执行后续的过滤器
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
该过滤器不用配置,服务启动会自动生效,并会应用到在所有路由上。
Gateway高可用
网关作为非常核心的一个部件,如果挂掉会直接影响到整个系统的运行。在生产环境下,我们会部署多台网关实例来保证服务的高可用。
Gateway的高可用很简单,就是启动多个实例,使用上游的Nginx等代理服务器进行负载均衡的转发即可。Nginx的代理配置实例如下:
#配置多个GateWay实例
upstream gateway {
server 127.0.0.1:9002;
server 127.0.0.1:9003;
}
location / {
proxy_pass http://gateway;
}
以上就是网关的全部内容。欢迎小伙伴积极留言交流~~~
文章评论