微服务实践(六)Spring Cloud Gateway
文章目录
为什么需要网关
传统的单体架构中只有一个服务开放给客户端调用,但是微服务架构中是将一个系统拆分成多个微服务,那么作为客户端如何去调用这些微服务呢?如果没有网关的存在,只能在本地记录每个微服务的调用地址。
如果不使用网关,将会出现以下问题
- 复杂的路由转发:请求的路由转发逻辑将会在每个微服务内部实现,代码实现复杂,维护困难。
- 不可靠的负载均衡:每个微服务实例可能会采用不同的负载均衡策略,不能保证请求的可靠性和稳定性。
- 分散的服务聚合:服务聚合逻辑将分散在每个微服务内部实现,不能统一管理,带来更高的维护成本和风险。
- 安全性不高:如果每个微服务都实现身份验证和授权控制,将面临更高的安全风险。
- 无法统一的流量控制:如果每个微服务都实现流量控制,将面临更高的复杂度和不稳定性。
网关有哪些功能
网关是所有微服务的门户,路由转发仅仅是最基本的功能,除此之外还有其他的一些功能,比如:认证、鉴权、熔断、限流、日志监控等等。
网关的技术选型
Spring Cloud Gateway是一个基于Spring框架的API网关,它使用Spring WebFlux构建非阻塞API,提供动态路由、监控、限流、安全等特性。它支持多种方法的请求转发,包括WebSockets,同时还提供了自定义过滤器的接口,以便用户能够在请求和响应处理过程中添加自己的业务逻辑。
Spring Cloud Gateway 核心概念
Spring Cloud GateWay 最主要的功能就是路由转发,而在定义转发规则时主要涉及了以下三个核心概念,如下表。
核心概念 | 描述 |
---|---|
Route(路由) | 网关最基本的模块。它由一个 ID、一个目标 URI、一组断言(Predicate)和一组过滤器(Filter)组成。 |
Predicate(断言) | 路由转发的判断条件,我们可以通过 Predicate 对 HTTP 请求进行匹配,例如请求方式、请求路径、请求头、参数等,如果请求与断言匹配成功,则将请求转发到相应的服务。 |
Filter(过滤器) | 过滤器,我们可以使用它对请求进行拦截和修改,还可以使用它对上文的响应进行再处理。 |
注意:其中 Route 和 Predicate 必须同时声明。
路由(Route)
网关最基本的模块。它由一个 ID、一个目标 URI、一组断言(Predicate)和一组过滤器(GatewayFilter)组成。
spring:
cloud:
gateway:
routes:
- id: order-service
uri: http://localhost:8080
predicates:
## 当请求的路径为order、orders开头的时,转发到http://localhost:8080服务上
- Path=/order/**,/orders/**
断言工厂(Predicate Factory)
Spring Cloud Gateway包含许多内置的路由断言工厂。这些断言都匹配HTTP请求的不同属性。多个路由断言工厂可以通过 and
组合使用。
断言 | 示例 | 说明 |
---|---|---|
Path | - Path=/order/** | 当请求路径与 /order/** 匹配时,该请求才能被转发到对应uri配置的地址上 上。 |
Before | - Before=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] | 在 2021 年 10 月 20 日 11 时 47 分 34.255 秒之前的请求,才会被转发到对应uri配置的地址上。 |
After | - After=2021-10-20T11:47:34.255+08:00[Asia/Shanghai] | 在 2021 年 10 月 20 日 11 时 47 分 34.255 秒之后的请求,才会被转发到对应uri配置的地址上。 |
Between | - Between=2021-10-20T15:18:33.226+08:00[Asia/Shanghai],2021-10-20T15:23:33.226+08:00[Asia/Shanghai] | 在 2021 年 10 月 20 日 15 时 18 分 33.226 秒 到 2021 年 10 月 20 日 15 时 23 分 33.226 秒之间的请求,才会被转发到对应uri配置的地址上。 |
Cookie | - Cookie=name,c.biancheng.net | 携带 Cookie 且 Cookie 的内容为 name=c.biancheng.net 的请求,才会被转发到对应uri配置的地址上。 |
Header | - Header=X-Request-Id,\d+ | 请求头上携带属性 X-Request-Id 且属性值为整数的请求,才会被转发到对应uri配置的地址上。 |
Method | - Method=GET | 只有 GET 请求才会被转发到对应uri配置的地址上。 |
路由过滤器(GatewayFilter)
路由过滤器是针对指定路由进行的过滤器,可以使用Spring Cloud Gateway内置的路由过滤器,也可以使用自定义过滤器,以下截取官网前几个内置过滤器进行示例,更多内置过滤器请参考#gatewayfilter-factories
内置路由过滤器
名称 | 示例 | 说明 |
---|---|---|
AddRequestHeader | - AddRequestHeader=X-Request-Foo, Bar | 对于所有匹配的请求,这将向下游请求的头中添加 x-request-foo:bar 的header |
RemoveRequestHeader | - RemoveRequestHeader=X-Request-Foo | 这将在X-Request-Foo header被发送到下游之前删除它 |
AddResponseHeader | - AddResponseHeader=X-Response-Foo, Bar | 对于所有匹配的请求,这会将x-response-foo:bar 头添加到下游响应的header中 |
RemoveResponseHeader | - RemoveResponseHeader=X-Response-Foo | 这将在返回到网关client之前从响应中删除x-response-foo 头。 |
AddRequestParameter | - AddRequestParameter=red, blue | 对于所有匹配的请求,这将向下游请求添加red=blue 查询字符串 |
自定义路由过滤器
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class CustomGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// your custom logic here
return chain.filter(exchange);
}
@Override
public int getOrder() {
// define the order of the filter in the chain
return 0;
}
}
使用路由过滤器
配置如下:
spring:
cloud:
gateway:
routes:
- id: route1
uri: http://example.com
filters:
- name: CustomGatewayFilter
全局过滤器(GlobalFilter)
Spring Cloud Gateway具有全局过滤器的功能,全局过滤器是对所有路由生效的过滤器。可以使用全局过滤器来实现鉴权,限流,日志记录等功能。
示例代码:
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.concurrent.atomic.AtomicLong;
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
private static final AtomicLong REQUEST_COUNTER = new AtomicLong(0);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 身份验证
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
if (token == null || !token.equals("valid-token")) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
// 请求校验
if (!exchange.getRequest().getQueryParams().containsKey("key")) {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
// 请求限流
long requests = REQUEST_COUNTER.incrementAndGet();
if (requests > 100) {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
代码中实现了身份验证,请求校验和请求限流三个功能。对于身份验证,验证请求头中的Authorization
字段是否存在且值为valid-token
;对于请求校验,验证请求的参数中是否包含了key
字段;对于请求限流,使用一个静态的AtomicLong变量记录请求数,超过100的请求则返回429状态码。
网关的搭建
搭建网关服务,首先我们需要创建一个Maven模块,建议选择一个空模块,否则可能会跟spring-boot-starter-web
依赖冲突,造成无法启动。
创建完一个空的Maven项目之后,在pom.xml导入以下依赖
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.0.RELEASE</version>
<!--nacos配置中心依赖-->
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<!--引入gateway 网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
注意:SpringCloud Gateway的版本跟Nacos的版本最好一致,与SpringBoot版本相兼容,以免出现意想不到的问题。可以参照下面的版本说明
版本说明
1. 新建GatewayApplication启动类。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
2. 新建yml配置文件并添加以下配置:
server:
port: 9000
spring:
application:
name: gateway #服务名称
cloud:
nacos:
config:
namespace: ${nacos.namespace}
server-addr: ${nacos.server-addr}
file-extension: yaml
#服务发现配置
discovery:
server-addr: ${nacos.server-addr}
namespace: ${nacos.namespace}
gateway:
routes:
- id: order-service # 路由id,自定义,只要唯一即可
uri: lb://order-center-server # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/order-server/** # 这个是按照路径匹配,只要以/order-server/开头就符合要求
filters:
- RewritePath=/order-server/(?<segment>.*), /$\{segment} #重写路径,将/order-server/{something}重写为/{something}
nacos:
server-addr: 127.0.0.1:8848
namespace: a506f87b-88c7-4fb9-bea4-c59881e140e8
3. 启动GatewayApplication会自动将服务注册到Nacos上,接下来就可以使用lb://服务名代替服务地址了,它会自动从注册中心读取服务列表,如果有集群则会自动进行负载均衡。其实现原理是基于 The ReactiveLoadBalancerClientFilter
(一个全局过滤器)进行实现。
4.启动order-center、gateway服务,可以看到已经将服务注册到了Nacos。这时候访问网关的域名会自动转发到对应路由的服务上去。

5.请求网关的order-sever服务,可以看到将请求成功进行了转发。

Leave a Reply