项目的源码地址
Spring Cloud Alibaba 工程搭建(1)
Spring Cloud Alibaba 工程搭建连接数据库(2)
Spring Cloud Alibaba 集成 nacos 以及整合 Ribbon 与 Feign 实现负载调用(3)
Spring Cloud Alibaba Ribbon 负载调用说明(4)
Spring Cloud Alibaba 核心理论 CAP与BASE理论简单理解(5)
Spring Cloud Alibaba Sentinel 集成与限流实战(6)
什么是网关
我们从下面几个点来说明吧:
- API Gateway,是系统的唯⼀对外的⼊⼝,介于客户端和服务器端之间的中间层,处理⾮业务功能,提供路由请求、鉴权、监控、缓存、限流等功能。
- 统一接入,可以做智能路由,负载均衡,容灾处理,日志埋点等等
- 流量监控,限流处理与服务降级
- 安全防护,统一的鉴权处理,监控,机器网络隔离
主流的网关有哪些呢?
- zuul: 是Netflix开源的微服务网关,和 Eureka,Ribbon,Hystrix 等组件配合使用,依赖组件比较多,性能教差(这个是我百度之后搬过来的,我还没有用过这个 zuul)。
- nginx+lua:是⼀个⾼性能的HTTP和反向代理服务器,lua 是脚本语⾔,让Nginx执⾏Lua脚本,并且⾼并发、⾮阻塞的处理各种请求
- SpringCloud Gateway: Spring公司专⻔开发的⽹关,替代 zuul。
AlibabaCloud 全家桶还没对应的⽹关,我们就⽤ SpringCloud官⽅推荐的 Gateway。
Spring Cloud Gateway
spring 官方的说明文档,点击这里看看,官网上面有一句话,是说 Spring Cloud Gateway 是基于 Spring Boot 2.x,Spring WebFlux 和 Project Reactor 构建。
这里引用官方上面的两段文字,是需要我们引起注意的地方
Spring Cloud Gateway 是建立在 Spring Boot 2.x、 Spring WebFlux 和 Project Reactor之上。因此,你所熟悉的许多同步库(例如Spring Data和Spring Security)和模式在你使用Spring Cloud Gateway时可能不适用。如果你不熟悉这些项目,我们建议你在使用Spring Cloud Gateway之前,先阅读它们的文档,熟悉一些新概念。
Spring Cloud Gateway 需要 Spring Boot 和 Spring Webflux 提供的 Netty 运行时。它不能在传统的Servlet容器中工作,也不能以WAR的形式构建。
三个核心的术语
-
Route(路由): 网关的基本构件。它由一个ID、一个目的地URI、一个谓词(Predicate)集合和一个过滤器(Filter)集合定义。如果集合谓词为真,则路由被匹配。
-
Predicate(谓词): 这是一个 Java 8 Function Predicate。输入类型是 Spring Framework ServerWebExchange。这让你可以在HTTP请求中的任何内容上进行匹配,比如header或查询参数。
-
Filter(过滤器): 这些是 GatewayFilter 的实例,已经用特定工厂构建。在这里,你可以在发送下游请求之前或之后修改请求和响应。
这里我们先有一个眼熟,后面我们再具体操作。
工程搭建
新增 api-gateway 模块
这里我们按照之前搭建工程的方式创建一个 demo-api-gateway
的模块出来,具体的搭建方式看这里,搭建的方式是一样的,其实就是新建一个 module,这里就不赘述了。最后搭建完了之后,是这样子的结构:
增加模块内容
pom 文件依赖引入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
启动类
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
配置文件
这个 routes 节点是一个数组,是可以写多个的
server:
port: 8888
spring:
application:
name: api-gateway
cloud:
gateway:
routes: #数组形式
- id: demo-order #路由唯一标识
uri: http://127.0.0.1:9000 #想要转发到的地址
order: 1 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发
- Path=/demo-order/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀
这里先初步说明下配置的意思:
- 我们请求的地址如果是:
http://localhost:8888/demo-order/api/v1/video_order/list
,这个是我们网关的地址, - 访问网关之后,会转发到 这个地址上面去
http://localhost:9000/demo-order/api/v1/video_order/list
访问我们订单服务。
测试下
访问下:http://localhost:8888/demo-order/api/v1/video_order/list
然后我们再访问下:http://localhost:9000/api/v1/video_order/list
OK了,我们初步的测试就算通过了,那么我们就接着向下看把,
连接Nacos
在 pom 文件中增加 nacos 依赖
<!--添加 nacos 客户端-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
增加启动类开启支持
@EnableDiscoveryClient
修改配置文件
注意看下下面增加的部分
- nacos.discovery.server-addr
- gateway.routes.id.uri:这里使用了
lb://demo-order
负载的调用方式 - gateway.locator.enabled: 开启网关拉取 nacos 服务
server:
port: 8888
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes: #数组形式
- id: order-service #路由唯一标识
#uri: http://127.0.0.1:8000 #想要转发到的地址
uri: lb://demo-order #从nocas进行转发
order: 1 #优先级,数字越小优先级越高
predicates: #断言 配置哪个路径才转发
- Path=/order-server/**
filters: #过滤器,请求在传递过程中通过过滤器修改
- StripPrefix=1 #去掉第一层前缀
discovery:
locator:
enabled: true #开启网关拉取nacos的服务
logging:
level:
org.springframework.cloud.gateway: DEBUG
我们去订单服务里面增加一个接口,方便于测试
@RequestMapping("gateway")
private Map gateway(HttpServletRequest httpRequest) {
String serverInfo = httpRequest.getServerName() + ":"+ httpRequest.getServerPort();
return Map.of("title", "测试返回数据", "name", "返回名称", "serverInfo", serverInfo);
}
测试下
首先我们启动两个订单服务
然后我们看下 nacos 的面板,可以看到相关的服务都在上面了
开始请求:http://localhost:8888/order-server/api/v1/video_order/gateway
再次请求下:http://localhost:8888/order-server/api/v1/video_order/gateway
断言、过滤器实战
网关的配置项回顾
路由:是网关的基本单元,由ID、URI、一组Predicate、一组Filter组成,根据Predicate进行匹配转发
route组成部分
id:路由的ID
uri:匹配路由的转发地址
predicates:配置该路由的断言,通过 PredicateDefinition 类进行接收配置。
order:路由的优先级,数字越小,优先级越高。
交互流程
- 客户端向Spring Cloud Gateway发出请求
- 如果网关处理程序映射确定请求与路由匹配
- 则将其发送到网关Web处理程序
- 通过特定过滤器链运行,前置处理-后置处理
断言
什么是Gateway路由断言 ?
- Predicate 来源于Java8,接受输入参数,返回一个布尔值结果
- Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则
- 转发的判断条件,SpringCloud Gateway支持多种方式,常见如:Path、Query、Method、Header等
- 支持多个Predicate请求的转发是必须满足所有的Predicate后才可以进行路由转发
内置路由断言都是 RoutePredicateFactory 接口实现类,我们一起看下:
我们可以去访问下这里,具体的说明这里也有Route Predicate(路由谓词)工厂,我们这里就选几个出来看看
Before
比如,我们现在有一个需求接口需要在指定时间进行下线,过后不可以在被访问。那么我们可以使用 Before
Gateway 内置的路由接口定时下线实战。
Before
路由谓词工厂只需要一个参数,即 datetime
(这是一个java ZonedDateTime
)。这个谓词匹配发生在指定 datetime
之前的请求。
predicates:
- Before=2024-05-11T01:01:01.000+08:00
那么修改下把:
测试访问下,现在是可以访问的:
然后我们再修改下:
Before=2024-05-09T01:01:01.000+08:00
Query
query 参数表示再请求的时候一定要带上的字段,如果没有这个字段就是非法请求
访问请求
http://localhost:8888/order-server/api/v1/video_order/gateway
http://localhost:8888/order-server/api/v1/video_order/gateway?source=alipay
Filter 过滤器
这里我们再看下这个图,可以看到我们的 Filter,是一层层的过滤然后访问到 proxied service
,然后再一层层的经过 filter 返回
过滤器生命周期
PRE: 这种过滤器在请求被路由之前调用,一般用于鉴权、限流等
POST:这种过滤器在路由到微服务以后执行,一般用于修改响应结果,比如增加header信息、打点结果日志
网关过滤器分类
-
局部过滤器GatewayFilter:应用在某个路由上,每个过滤器工厂都对应一个实现类,并且这些类的名称必须以GatewayFilterFactory 结尾
-
全局过滤器:作用全部路由上
局部过滤器
内置很多局部过滤器,顶级接口 GatewayFilterFactory
全局过滤器
内置很多全局过滤器,顶级接⼝ GlobalFilter
自定义网关过滤器
第一步:先增加一个自定义的过滤器
import org.apache.commons.lang3.StringUtils;
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;
@Component
public class UserGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//写业务逻辑
String token = exchange.getRequest().getHeaders().getFirst("token");
//TODO 根据业务开发对应的鉴权规则, JWT
if(StringUtils.isBlank(token)){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
//继续往下执行
return chain.filter(exchange);
}
//数字越小,优先级越高
@Override
public int getOrder() {
return 0;
}
}
第二步: 接着重启服务
访问测试下:http://localhost:8888/order-server/api/v1/video_order/gateway?source=alipay
通过 postMan 访问,可以请求到数据
好了,我们总算是搞定了这个 Gateway 了,其实这个更多的就是配置,没有啥其他代码部分的东西。先理解配置部分东西,方便与工作中使用。