1. 网关介绍
1.1 问题
之前,我们通过Eureka, Nacos解决了服务注册, 服务发现的问题, 使⽤Spring Cloud LoadBalance解决了负载均衡的问题, 使⽤OpenFeign解决了远程调⽤的问题.
但是当前所有微服务的接⼝都是直接对外暴露的, 可以直接通过外部访问. 为了保证对外服务的安全性, 服务端实现的微服务接⼝通常都带有⼀定的权限校验机制. 由于使⽤了微服务, 原本⼀个应⽤的的多个模块拆分成了多个应⽤, 我们不得不实现多次校验逻辑. 当这套逻辑需要修改时, 我们需要修改多个应⽤, 加重了开发⼈员的负担.
针对以上问题, ⼀个常⽤的解决⽅案是使⽤API⽹关.
举个例子,当一个要去某个公司时,前台会进行身份校验,如果没有前台,每个部门就要单独校验,效率低,流程多,前台就类似于API网关
1.2 什么是API网关
API⽹关(简称⽹关)也是⼀个服务, 通常是后端服务的唯⼀⼊⼝.
它的定义类似设计模式中的Facade模式 (⻔⾯模式, 也称外观模式). 它就类似整个微服务架构的⻔⾯, 所有的外部客⼾端访问, 都需要经过它来进 ⾏调度和过滤.
网关核心功能:
- 权限控制: 作为微服务的入口,对用户进行权限校验,如果校验失败则进⾏拦截
- 动态路由: ⼀切请求先经过⽹关, 但⽹关不处理业务, ⽽是根据某种规则, 把请求转发到某个微服务
- 负载均衡: 当路由的⽬标服务有多个时, 还需要做负载均衡
- 限流: 请求流量过⾼时, 按照⽹关中配置微服务能够接受的流量进⾏放⾏, 避免服务压⼒过⼤.
1.3 常见网关实现
Zuul
Zuul 是 Netflix 公司开源的⼀个API⽹关组件, 是Spring Cloud Netflix ⼦项⽬的核⼼组件之⼀,它可以 和 Eureka、Ribbon、Hystrix 等组件配合使⽤. 在Spring Cloud Finchley正式版之前, Spring Cloud推荐的⽹关是Netflix提供的Zuul(此处指Zuul 1.X). 然⽽Netflix在2018年宣布⼀部分组件进⼊维护状态, 不再进⾏新特性的开发. 这部分组件中就包含Zuul.
Spring Cloud Gateway
Spring Cloud Gateway 是Spring Cloud的⼀个全新的API⽹关项⽬, 基于Spring + SpringBoot等技术开发, ⽬的是为了替换掉Zuul. 旨在为微服务架构提供⼀种简单⽽有效的途径来转发请求, 并为他们提供横切关注点, ⽐如: 安全性, 监控/指标和弹性.
2. Spring Cloud Gateway
2.1 使用
2.1.1 创建网关项目
2.1.2 依赖
<!--⽹关--> <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>
2.1.3 启动类
2.1.4 添加Gateway的路由配置
server:port: 10030 #网关端口 spring:application:name: gateway #服务名称cloud:nacos:discovery:server-addr: 127.0.0.1:10020gateway:routes:- id: order-service #路由规则id,随便起,不重复uri: lb://order-service/ #路由到哪里,目标服务地址predicates:- Path=/order/**,/feign/** #条件- After=2016-01-20T17:42:47.789-07:00[America/Denver]- id: product-serviceuri: lb://product-service/predicates:- Path=/product/**
uri:
⽬标服务地址, ⽀持普通URI 及 lb://应⽤注册服务名称 . lb表⽰负载均衡, 使⽤ lb:// ⽅ 式表⽰从注册中⼼获取服务地址.
predicates:
路由条件, 根据匹配结果决定是否执⾏该请求路由, 上述代码中, 我们把符合Path规则的⼀切请求, 都代理到uri参数指定的地址.
2.1.5 测试
1. 通过⽹关服务访问product-service
http://127.0.0.1:10030/product/1001
url符合yml文件中配置的/product/**规则,路由转发到http://product-service/product/1001
2. 通过⽹关服务访问order-service
http://127.0.0.1:10030/order/1
2.2 Route Predicate Factories
2.2.1 Predicate
Predicate是Java 8提供的⼀个函数式编程接⼝, 它接收⼀个参数并返回⼀个布尔值, ⽤于条件过滤, 请求参数的校验.
①定义一个Predicate
java">/*** 判定字符串是否为空-true*/
public class StrPredicate implements Predicate<String> {@Overridepublic boolean test(String s) {return s == null || s.isEmpty();}
}
②测试
java">public class PredicateTest {@Testpublic void test(){Predicate<String> predicate = new StrPredicate();System.out.println(predicate.test(""));System.out.println(predicate.test("aa"));}
}
③其它用法
1)内置函数
java">/*** 匿名内部类*/@Testpublic void test2(){Predicate<String> predicate = new Predicate<String>(){@Overridepublic boolean test(String s) {return s==null||s.isEmpty();}};System.out.println(predicate.test(""));System.out.println(predicate.test("aa"));}
2)lambda写法
java"> /*** lambda表达式*/@Testpublic void test3(){Predicate<String> predicate = s -> s==null||s.isEmpty(); //Predicate<String> isEmpty = String::isEmpty;System.out.println(predicate.test(""));System.out.println(predicate.test("aa"));}
④其它方法
- isEqual(Object targetRef) :⽐较两个对象是否相等,参数可以为Null
- and(Predicate other): 短路与操作,返回⼀个组成Predicate
- or(Predicate other) :短路或操作,返回⼀个组成Predicate
- test(T t) :传⼊⼀个Predicate参数,⽤来做判断
- negate() : 返回表⽰此Predicate逻辑否定的Predicate
java">/*** 非*/@Testpublic void test4(){Predicate<String> predicate = s -> s==null||s.isEmpty();System.out.println(predicate.negate().test(""));System.out.println(predicate.negate().test("aa"));}/*** 或* 判定字符串为aa/bb*/@Testpublic void test5(){Predicate<String> predicate = s -> "aa".equals(s);Predicate<String> predicate2 = s -> "bb".equals(s);System.out.println(predicate.or(predicate2).test(""));System.out.println(predicate.or(predicate2).test("aa"));}/*** and* 判定字符串不为空且由数字组成,"12" "34"*/@Testpublic void test6(){Predicate<String> predicate = s -> s!=null && !s.isEmpty();Predicate<String> predicate2 = s -> s!=null && s.chars().allMatch(Character::isDigit);System.out.println(predicate.and(predicate2).test("aa"));System.out.println(predicate.and(predicate2).test("123"));}@Testpublic void test7(){System.out.println(ZonedDateTime.now());}
2.2.2 Route Predicate Factories
路由断⾔⼯⼚, 也称为路由谓词⼯⼚, 此处谓词表⽰⼀个函数,在Spring Cloud Gateway中, Predicate提供了路由规则的匹配机制.
我们在配置⽂件中写的断⾔规则只是字符串, 这些字符串会被Route Predicate Factory读取并处理, 转变为路由判断的条件.
⽐如前⾯配置的 Path=/product/** , 就是通过Path属性来匹配URL前缀是 /product 的请求.
Spring Cloud Gateway 默认提供了很多Route Predicate Factory, 这些Predicate会分别匹配HTTP请求的不同属性, 并且多个Predicate可以通过and逻辑进⾏组合.
比如:
After,这个⼯⼚需要⼀个⽇期时间(Java 的 ZonedDateTime对象), 匹配指定⽇期之后的请求
参考:
Route Predicate Factories :: Spring Cloud Gateway
2.2.3 演示
①添加Predicate规则
增加限制路由规则: 请求时间为2026年1⽉20⽇之后
测试:
2.3 Gateway Filter Factories(⽹关过滤器⼯⼚)
Predicate决定了请求由哪⼀个路由处理, 如果在请求处理前后需要加⼀些逻辑, 这就是Filter(过滤器)的作⽤范围了.
Filter分为两种类型: Pre类型和Post类型.
- Pre类型过滤器: 路由处理之前执⾏(请求转发到后端服务之前执⾏), 在Pre 类型过滤器中可以做鉴权, 限流等.
- Post类型过滤器: 请求执⾏完成后, 将结果返回给客⼾端之前执⾏.
举个例子:
去景区玩要先安检验票,如果当天人流量过多,就会限流---Pre过滤器
游玩之后,评价啊--Post过滤器
Spring Cloud Gateway 中内置了很多Filter, ⽤于拦截和链式处理web请求. ⽐如权限校验, 访问超时等设定.
Spring Cloud Gateway从作⽤范围上, 把Filter可分为GatewayFilter 和GlobalFilter.
- GatewayFilter: 应⽤到单个路由或者⼀个分组的路由上.
- GlobalFilter: 应⽤到所有的路由上, 也就是对所有的请求⽣效.
2.3.1 GatewayFilter
GatewayFilter 同 Predicate 类似, 都是在配置⽂件 application.yml 中配置,每个过滤器的逻辑都是固定的.
⽐如 AddRequestParameterGatewayFilterFactory 只需要在配置⽂件中写 AddRequestParameter , 就可以为所有的请求添加⼀个参数.
演示
①在application.yml中添加filter
该filter只添加在了 product-service 路由下, 因此只对 product-service 路由⽣效, 也就是对 / product/**的请求⽣效.
②接收参数并打印
在 product-service 服务中接收请求的参数,并打印出来
说明:
Spring Cloud Gateway提供的Filter⾮常多
详细可参考官⽅⽂档 GatewayFilter Factories
举个例子:
Retry=>针对不同的响应进⾏重试. 当后端服务不可⽤ 时, ⽹关会根据配置参数来发起重试请求.
retries:
重试次数, 默认为3
status:
HTTP请求返回的状态码, 针对指定状态码进⾏重试. 对应 org.springframework.http.HttpStatus
Default Filters
前⾯的filter添加在指定路由下, 所以只对当前路由⽣效, 若需要对全部路由⽣效, 可以使⽤Default Filters
2.3.2 GlobalFilter
GlobalFilter是Spring Cloud Gateway中的全局过滤器, 它和GatewayFilter的作⽤是相同的. GlobalFilter 会应⽤到所有的路由请求上, 全局过滤器通常⽤于实现与安全性, 性能监控和⽇志记录等相关的全局功能.
Spring Cloud Gateway 内置的全局过滤器也有很多, ⽐如:
- Gateway Metrics Filter: ⽹关指标, 提供监控指标
- Forward Routing Filter: ⽤于本地forword, 请求不转发到下游服务器
- LoadBalancer Client Filter: 针对下游服务, 实现负载均衡.
更多过滤器参考: Global Filters
使用
①依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency>
②配置
spring:cloud:gateway:metrics:enabled: true management:endpoints:web:exposure:include: "*"endpoint:health:show-details: alwaysshutdown:enabled: true
③测试
http://127.0.0.1:10030/actuator,显示所有监控的信息链接
2.4 过滤器执⾏顺序
⼀个项⽬中, 既有GatewayFilter, ⼜有 GlobalFilter时, 执⾏的先后顺序是什么呢?
请求路由后, ⽹关会把当前项⽬中的GatewayFilter和GlobalFilter合并到⼀个过滤器链(集合)中, 并进⾏排序, 依次执⾏过滤器.
每⼀个过滤器都必须指定⼀个int类型的order值, 默认值为0, 表⽰该过滤的优先级. order值越⼩,优先级越⾼,执⾏顺序越靠前.
- Filter通过实现Order接⼝或者添加@Order注解来指定order值.
- Spring Cloud Gateway提供的Filter由Spring指定. ⽤⼾也可以⾃定义Filter, 由⽤⼾指定.
- 当过滤器的order值⼀样时, 会按照 defaultFilter > GatewayFilter > GlobalFilter的顺序执⾏.
2.5 自定义过滤器
Spring Cloud Gateway提供了过滤器的扩展功能, 开发者可以根据实际业务来⾃定义过滤器, 同样⾃定义过滤器也⽀持GatewayFilter 和 GlobalFilter两种.
2.5.1 ⾃定义GatewayFilter
⾃定义GatewayFilter, 需要去实现对应的接⼝ GatewayFilterFactory , Spring Boot 默认帮我们实现的抽象类是 AbstractGatewayFilterFactory , 我们可以直接使⽤.
Ⅰ.定义
java">@Slf4j
@Component
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomConfig> implements Ordered {public CustomGatewayFilterFactory() {super(CustomConfig.class);}@Overridepublic GatewayFilter apply(CustomConfig config) {return new GatewayFilter() {/*** ServerWebExchange:HTTP请求和交互契约,提供了对HTTP请求和相应的访问* GatewayFilterChain:过滤器链* Mono:Reactor核心类,数据流发布者,最多只能触发一个事件* chain.filter(exchange):执行请求* Mono.fromRunnable():创建一个包含Runnable元素的数据流*/@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//Pre类型 执行请求 Post类型log.info("Pre Filter,config:{}",config); //pre类型代码逻辑return chain.filter(exchange).then(Mono.fromRunnable(()->{log.info("Post Filter..."); //Post}));}};}@Overridepublic int getOrder() {return Ordered.LOWEST_PRECEDENCE;}
}
Ⅱ.针对这个Filter的配置,使用CustomConfig定义
java">@Data
public class CustomConfig {private String name;
}
- 类名统⼀以GatewayFilterFactory结尾, 因为默认情况下, 过滤器的name会采⽤该定义类的前缀. 这⾥的name=Custom(yml配置中使⽤)
- apply⽅法中, 同时包含Pre和Post过滤, then⽅法中是请求执⾏结束之后处理的
- CustomConfig 是⼀个配置类, 该类只有⼀个属性name, 和yml的配置对应
- 该类需要交给Spring管理, 所以需要加 @Service 注解
- getOrder表⽰该过滤器的优先级, 值越⼤, 优先级越低.
Ⅲ.配置过滤器
Ⅳ.测试
2.5.2 ⾃定义GlobalFilter
GlobalFilter的实现⽐较简单, 它不需要额外的配置, 只需要实现GlobalFilter接⼝, ⾃动会过滤所有的 Filter
Ⅰ.定义
java">@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {log.info("pre...");return chain.filter(exchange).then(Mono.fromRunnable(()->{log.info("post...");}));}@Overridepublic int getOrder() {return Ordered.LOWEST_PRECEDENCE;}
}
Ⅱ.测试
从⽇志中,也可以看出来, 当GatewayFilter 和GlobalFilter 过滤器order⼀样时, 会先执⾏GatewayFilter.