网关介绍
问题
在 spring cloud 体系中我们通过 Eureka,Nacos 解决了服务注册,服务发现的问题,使⽤Spring Cloud LoadBalance解决了负载均衡的问题,使⽤ OpenFeign 解决了远程调⽤的问题.
但是当前所有微服务的接⼝都是直接对外暴露的,可以直接通过外部访问.为了保证对外服务的安全性,服务端实现的微服务接⼝通常都带有⼀定的权限校验机制.由于使⽤了微服务,原本⼀个应⽤的的多个模块拆分成了多个应⽤,我们不得不实现多次校验逻辑.当这套逻辑需要修改时,我们需要修改多个应⽤,加重了开发⼈员的负担.针对以上问题,常⽤的解决⽅案是使⽤API⽹关.
什么是 API 网关
API网关(简称网关)也是⼀个服务,通常是后端服务的唯⼀⼊⼝.它的定义类似设计模式中的Facade 模式 (⻔⾯模式,也称外观模式).它就类似整个微服务架构的⻔⾯,所有的外部客户端访问,都需要经过它来进⾏调度和过滤.
简单理解:我们在进行微服务开发时,每个微服务都提供了接口,但这些接口是用于进行业务处理的,不应该被暴露在外界,如果我们为每个微服务都设置权限管理,这是十分麻烦的。所以就需要 API 网关作为整个微服务架构的⻔⾯,所有的请求都要先交给 API 网关, API 网关校验后再通过服务注册中心将请求转发给对应的服务接口,由于 API 网关已经校验了,后续的微服务就不需要在校验请求了。
网关核⼼功能:
权限控制:作为微服务的⼊⼝,对⽤户进⾏权限校验,如果校验失败则进⾏拦截
动态路由:⼀切请求先经过⽹关,但⽹关不处理业务,⽽是根据某种规则,把请求转发到某个微服务
负载均衡:当路由的⽬标服务有多个时,还需要做负载均衡,限流:请求流量过⾼时,按照⽹关中配置微服务能够接受的流量进⾏放⾏,避免服务压⼒过⼤.
Spring Cloud Gateway
快速上手
我们通过以下的演⽰,先来了解⽹关的基本功能
创建网关项目
API ⽹关也是⼀个服务
引⼊⽹关依赖
由于 API ⽹关在接收并检验用户请求后要将请求转发给对应的服务,所以 API ⽹关也需要注册到 nacos 上,所以引入 nacos 的依赖, API ⽹关应当均衡的向服务转发请求,需要用到负载均衡,所以引入负载均衡工具 loadbalancer 的依赖
<!--⽹关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--基于nacos实现服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--负载均衡依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
编写启动类
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
添加 Gateway 的路由配置
创建 application.yml ⽂件,添加如下配置:
凡是需要接收外界请求的服务都需要在网关这里配置路由,这样网关才能将请求发给对应的服务
server:
port: 10030 # ⽹关端⼝
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
routes: # ⽹关路由配置
- id: product-service #路由ID, ⾃定义, 唯⼀即可
uri: lb://product-service #⽬标服务地址
predicates: #路由条件
- Path=/product/**
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
配置字段说明:
• id :⾃定义路由ID,保持唯⼀(推荐将其写为目标服务的名称)
• uri:⽬标服务地址,⽀持普通 URI及 lb:// 应⽤注册服务名称 .lb表⽰负载均衡,使⽤ lb:// ⽅ 式表⽰从注册中⼼获取服务地址.
• predicates: 路由条件,根据匹配结果决定是否执⾏该请求路由,上述代码中,我们把符合 Path 规则的⼀切请求,都代理到 uri 参数指定的地址.
当我们要为一个服务设置多个条件时,可以用 ,隔开
predicates:
- Path=/order/**,/feign/**
配置好以后我们就可以通过⽹关服务访问 product-service 和 order-service 了,10030 是网关的端口
http://127.0.0.1:10030/product/1
http://127.0.0.1:10030/order/1
Route Predicate Factories
Predicate
Predicate 是Java 8 提供的⼀个函数式编程接⼝,它接收⼀个参数并返回⼀个布尔值,⽤于条件过滤,请求参数的校验.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
//...
}
代码演⽰:
1. 定义⼀个 Predicate
class StringPredicate implements Predicate<String>{
@Override
public boolean test(String str) {
return str.isEmpty();
}
}
2. 使⽤这个 Predicate
public class PredictTest {
public static void main(String[] args) {
Predicate<String> predicate = new StringPredicate();
System.out.println(predicate.test(""));
System.out.println(predicate.test("bite666"));
}
}
3. 运⾏结果
4. Predicate 的其他写法
1)内置函数
public class PredictTest {
public static void main(String[] args) {
Predicate<String> predicate = new Predicate<String>(){
@Override
public boolean test(String s) {
return s.isEmpty();
}
};
System.out.println(predicate.test(""));
System.out.println(predicate.test("bite666"));
}
}
2)lambda写法
public class PredictTest {
public static void main(String[] args) {
Predicate<String> predicate = s -> s.isEmpty();
System.out.println(predicate.test(""));
System.out.println(predicate.test("bite666"));
}
}
5. Predicate 的其他⽅法
• isEqual(Object targetRef):⽐较两个对象是否相等,参数可以为Null
• and(Predicate other): 短路与操作,返回⼀个组成 Predicate
• or(Predicate other):短路或操作,返回⼀个组成 Predicate
• test(T t):传⼊⼀个 Predicate 参数,⽤来做判断
• negate():返回表⽰此 Predicate 逻辑否定的 Predicate
Route Predicate Factories(路由断言工厂)
Route Predicate Factories(路由断⾔⼯⼚,也称为路由谓词⼯⼚,此处谓词表⽰⼀个函数),在Spring Cloud Gateway中, Predicate 提供了路由规则的匹配机制.我们在配置⽂件中写的断⾔规则只是字符串,这些字符串会被 Route Predicate Factory读取并处理,转变为路由判断的条件.
⽐如前⾯章节配置的 Path=/product/** ,就是通过 Path 属性来匹配 URL 前缀是 /product 的请求.这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateF actory 来实现的. Spring Cloud Gateway 默认提供了很多 Route Predicate Factory,这些 Predicate会分别匹配 HTTP 请求的不同属性,并且多个 Predicate 可以通过 and 逻辑进⾏组合.
更多请参考:Route Predicate Factories :: Spring Cloud Gateway
代码演⽰:
在application.yml 中添加如下规则:
spring:
cloud:
gateway:
routes: # ⽹关路由配置
- id: product-service #路由ID, ⾃定义, 唯⼀即可
uri: lb://product-service #⽬标服务地址
predicates: #路由条件
- Path=/product/**
- After=2025-01-01T00:00:00.000+08:00[Asia/Shanghai]
增加限制路由规则:请求时间为 2025年1⽉1⽇之后
Gateway Filter Factories(⽹关过滤器⼯⼚)
Predicate 决定了请求由哪⼀个路由处理,如果在请求处理前后需要加⼀些逻辑,这就是 Filter(过滤器)的 作⽤范围了.
Filter分为两种类型: Pre 类型和 Post 类型.
Pre 类型过滤器:路由处理之前执⾏(请求转发到后端服务之前执⾏),在 Pre 类型过滤器中可以做鉴权, 限流等.
Post 类型过滤器:请求执⾏完成后,将结果返回给客户端之前执⾏
过滤器可有可⽆.
Spring Cloud Gateway 中内置了很多Filter,⽤于拦截和链式处理 web 请求.⽐如权限校验,访问超时等设定. Spring Cloud Gateway 从作⽤范围上,把 Filter 可分为 GatewayFilter 和GlobalFilter.
GatewayFilter: 应⽤到单个路由或者⼀个分组的路由上 GlobalFilter: 应⽤到所有的路由上,也就是对所有的请求⽣效.
GatewayFilter(过滤器)
GatewayFilter 同 Predicate 类似,都是在配置⽂件 application.yml 中配置,每个过滤器的逻辑都是固定的.⽐如 AddRequestParameterGatewayFilterFactory 只需要在配置⽂件中写 AddRequestParameter ,就可以为所有的请求添加⼀个参数,我们先通过⼀个例⼦来演⽰ GatewayFilter 如何使⽤.
快速上手
1. 在 application.yml 中添加 filter
server:
port: 10030 # ⽹关端⼝
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
discovery:
server-addr: 110.41.51.65:10020
gateway:
routes: # ⽹关路由配置
- id: product-service #路由ID, ⾃定义, 唯⼀即可
uri: lb://product-service #⽬标服务地址
predicates: #路由条件
- Path=/product/**
- After=2024-01-01T00:00:00.000+08:00[Asia/Shanghai]
filters:
- AddRequestParameter=userName, yulin
- id: order-service
uri: lb://order-service
predicates:
- Path=/order/**
该 filter 只添加在了 product-service 路由下,因此只对 product-service 路由⽣效, 也就是 对 /product/** 的请求⽣效.
该 filter 表示,将请求发送给服务之前,向服务中添加一个 userName 参数,值为 yulin
filters:
- AddRequestParameter=userName, yulin
GatewayFilter说明: Spring Cloud Gateway提供了的 Filter ⾮常多,下⾯列出⼀些常⻅过滤器的说明.
详细可参考官⽅⽂档:GatewayFilter Factories :: Spring Cloud Gateway
Default Filters
前⾯的 filter 添加在指定路由下,所以只对当前路由⽣效,若需要对全部路由⽣效,可以使⽤ spring.cloud.gateway.default-filters 这个属性需要⼀个 filter 的列表.
配置举例:
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default-Red, Default-Blue
- PrefixPath=/httpbin
GlobalFilter(全局过滤器)
GlobalFilter 是 Spring Cloud Gateway中的全局过滤器,它和 GatewayFilter 的作⽤是相同的. GlobalFilter 会应⽤到所有的路由请求上,全局过滤器通常⽤于实现与安全性,性能监控和⽇志记录等相关的全局功能.Spring Cloud Gateway 内置的全局过滤器也有很多,⽐如:
• Gateway Metrics Filter: ⽹关指标, 提供监控指标
• Forward Routing Filter: ⽤于本地 forword, 请求不转发到下游服务器
• LoadBalancer Client Filter: 针对下游服务,实现负载均衡.
• ...更多过滤器参考:Global Filters :: Spring Cloud Gateway
快速上手
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2. 添加配置
spring:
cloud:
gateway:
metrics:
enabled: true
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
shutdown:
enabled: true
3. 测试 http://127.0.0.1:10030/actuator,显⽰所有监控的信息链接
过滤器执⾏顺序
⼀个项⽬中,既有 GatewayFilter,⼜有 GlobalFilter 时,执⾏的先后顺序是什么呢?请求路由后,⽹关会把当前项⽬中的 GatewayFilter 和 GlobalFilter 合并到⼀个过滤器链(集合)中,并进⾏排序,依次执⾏过滤器.
每⼀个过滤器都必须指定⼀个 int 类型的 order 值,默认值为0,表⽰该过滤的优先级.order 值越⼩,优先级越⾼,执⾏顺序越靠前.
• Filter 通过实现 Order 接⼝或者添加 @Order 注解来指定 order 值.
• Spring Cloud Gateway 提供的 Filter 由 Spring 指定.⽤户也可以⾃定义 Filter,由⽤户指定.
• 当过滤器的 order 值⼀样时,会按照 defaultFilter > GatewayFilter > GlobalFilter 的顺序执⾏.
自定义过滤器
Spring Cloud Gateway提供了过滤器的扩展功能,开发者可以根据实际业务来⾃定义过滤器,同样⾃定义过滤器也⽀持 GatewayFilter 和 GlobalFilter 两种
自定义 GatewayFilter
自定义 GatewayFilter,需要去实现对应的接⼝ GatewayFilterFactory ,Spring Boot 默认帮我们实现的抽象类是 AbstractGatewayFilterFactory ,我们可以直接使⽤
定义GatewayFilter
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Slf4j
@Service
public class CustomGatewayFilterFactory extends
AbstractGatewayFilterFactory<CustomGatewayFilterFactory.CustomConfig>
implements Ordered {
public CustomGatewayFilterFactory() {
super(CustomConfig.class);
}
@Override
public GatewayFilter apply(CustomConfig config) {
/**
* Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain
chain)
* ServerWebExchange: HTTP请求-响应交互的契约, 提供对HTTP请求和响应的访问, 服
务器端请求属性, 请求实例,响应实例等, 类似Context⻆⾊
* GatewayFilterChain: 过滤器链
* Mono: Reactor核⼼类, 数据流发布者, Mono最多只触发⼀个事件, 所以可以把
Mono ⽤于在异步任务完成时发出通知.
* Mono.fromRunnable: 创建⼀个包含Runnable元素的数据流
*/
return ((exchange, chain) -> {
//在请求前的操作
log.info("[Pre] Filter Request, name:"+config.getName());
//在请求后的操作
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("[Post] Response Filter");
}));
});
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE; //配置优先级, order越⼤, 优先级越低
}
}
针对这个 Filter 的配置,使⽤ CustomConfig 定义
@Data
public static class CustomConfig {
private String name;
}
代码说明:
1. 类名统⼀以 GatewayFilterFactory 结尾,因为默认情况下,过滤器的 name 会采⽤该定义类的前缀.这⾥的 name=Custom (yml配置中使⽤)
2. apply⽅法中,同时包含 Pre 和 Post 过滤,then⽅法中是请求执⾏结束之后处理的
3. CustomConfig 是⼀个配置类,该类只有⼀个属性 name,和 yml 的配置对应
4. 该类需要交给 Spring 管理,所以需要加 @Service 注解
5. getOrder表⽰该过滤器的优先级,值越⼤,优先级越低.
配置过滤器
spring:
cloud:
gateway:
routes: # ⽹关路由配置
- id: product-service #路由ID, ⾃定义, 唯⼀即可
uri: lb://product-service #⽬标服务地址
predicates: #路由条件
- Path=/product/**
filters:
- name: Custom
args:
name: custom filter
⾃定义 GlobalFilter
GlobalFilter 的实现⽐较简单,它不需要额外的配置,只需要实现 GlobalFilter 接⼝,⾃动会过滤所有的 Filter.
定义GlobalFilter
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.Service;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Slf4j
@Service
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain
chain) {
//请求前执行的操作
log.info("[Pre] CustomGlobalFilter enter...");
//请求后执行的操作
return chain.filter(exchange).then(Mono.fromRunnable(()->{
log.info("[Post] CustomGlobalFilter return...");
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;//配置优先级, order越⼤, 优先级越低
}
}