在SpringCloud微服务架构中,如果前端页面想访问后端服务,一般都是通过API网关实现的。API网关类似于一个反向代理,前端请求会通过网关的路由以负载均衡方式传递给微服务,但API网关通常还需要实现一部分业务逻辑,比如统一的认证鉴权、日志监控、接口缓存等。SpringCloud Gateway能够通过简单的路由配置按规则的实现API路由,它同时也是一个基于Spring WebFlux的Java工程,因此我们很容易通过Java代码对其业务逻辑进行扩展。
微服务API网关的实现方式有很多,最简单的Nginx/OpenResty其实就可以作为微服务网关;Kong则是基于OpenResty专门设计的一款微服务网关,也是一个很不错的选择;而SpringCloud中,官方给出的解决方案是SpringCloud Gateway;在下一代基于Istio服务网格的微服务架构中,我们不得不优先考虑基于Envoy实现的istio-ingressgateway
。
上述提到的方案中API网关的基本定位都是类似,只不过实现方式各有不同,在功能完成度上也各有优劣。总的来说,对于Java程序员SpringCloud Gateway是个很好的选择,它性能不错,而且我们很容易通过代码对其业务功能进行扩展。实际开发中,微服务网关的选择其实就见仁见智了,也不一定非要用SpringCloud的方案。
SpringCloud Gateway本质也是一个SpringBoot工程,我们需要创建一个Webflux工程并引入Gateway相关的依赖。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
不过这里我们还要知道,SpringCloud Gateway除了其本身的起步依赖,我们一般还至少需要引入服务发现功能和Actuator,这样API网关才能够通过Consul注册中心注册的服务名找到对应服务,我们根据需要引入即可。
注意:SpringCloud Gateway并不是基于Servlet实现的,而是基于Webflux这套Reactive Web架构实现的,因此和传统Web相关模块不能同时使用,开发时和传统SpringMVC工程也有一些区别。
在具体使用SpringCloud Gateway之前,我们还需要了解一些相关概念。
Route:路由,网关最基本的配置单元,由ID、URI、断言和过滤器组成。
Predicate:断言,其实就是决定是否处理一个HTTP处理请求的判断条件,只不过是通过配置的方式实现的,支持通过请求路径、请求头等方式进行配置。
Filter:过滤器,可以对通过API网关的HTTP请求和响应进行统一的拦截和修改。
其实以上概念很像OpenResty,路由和断言就像是Nginx中的location规则,过滤器则可以在OpenResty中使用Lua脚本实现。实际上,API网关大多都是类似的设计。另外要注意,这里所说的Filter与具体的Servlet规范无关,SpringCloud Gateway也不是基于Servlet实现的,这里的Filter是一个抽象的概念,表示请求和响应的拦截组件。
SpringCloud Gateway的路由匹配功能非常强大,它支持多种匹配方式,这里我们不会逐一列举演示,下面我们通过一些例子介绍SpringCloud Gateway中的路由基本配置方式。
spring.cloud.gateway.routes[0].id=order
spring.cloud.gateway.routes[0].uri=lb://order
spring.cloud.gateway.routes[0].predicates[0]=Path=/**
id
:路由ID,指定唯一名字即可,建议使用服务名。uri
:反向代理的地址,lb://order
表示以负载均衡方式路由到服务中心注册的order
服务。predicates
:断言,这里Path
断言是基于路径和通配符匹配的,这里我们匹配/**
,其实就是所有路径都匹配,除了Path还有很多其它判断方式,比如基于Header等,具体可以参考文档。断言支持配置多个,它们是AND关系。在SpringCloud微服务工程中,uri
我们一般写lb://
格式的服务名,不过实际上该处配置也支持直接写明类似Nginx中proxy_pass
配置的主机和端口,例子如下,这种方式实际上就相当于固定访问一个主机的端口,通常用于非注册中心管理的其它服务。
spring.cloud.gateway.routes[0].uri=http://127.0.0.1:8081
过滤器能够对请求和响应信息进行修改,因此主要分为前置和后置两种类型,下面介绍一些常用的Filter。
PrefixPath
过滤器能够对请求路径拼上特定的前缀,下面配置中,/**
会被拼为/api/**
:
spring.cloud.gateway.routes[0].id=order_queryOrderById
spring.cloud.gateway.routes[0].uri=lb://order
spring.cloud.gateway.routes[0].predicates[0]=Path=/**
spring.cloud.gateway.routes[0].filters[0]=PrefixPath=/api
Nginx中重写(Rewrite)功能是很强大的,Spring Cloud Gateway很容易实现基于正则表达式重写。下面配置中,我们使用RewritePath
过滤器将/sss/**
重写为了/api/**
:
spring.cloud.gateway.routes[0].id=order_queryOrderById
spring.cloud.gateway.routes[0].uri=lb://order
spring.cloud.gateway.routes[0].predicates[0]=Path=/sss/**
spring.cloud.gateway.routes[0].filters[0]=RewritePath=/sss/?(?<segment>.*),/api/$\{segment}
上面过滤器都是SpringCloud Gateway内置的过滤器,实际上SpringCloud Gateway也支持自定义全局过滤器。实际开发中,我们可以自定义全局过滤器实现接口认证、鉴权、限流等功能,下面是一个全局过滤器的例子。
package com.gacfox.demo.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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class GatewayGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("前置处理...");
Mono<Void> result = chain.filter(exchange);
log.info("后置处理...");
return result;
}
@Override
public int getOrder() {
return 0;
}
}
上面代码我们实现了一个全局过滤器,代码比较简单,这里就不多介绍了。