专栏Spring Cloud个人主页手握风云目录一、网关介绍1.1. 引入网关要解决的问题1.2. API 网关概念1.3. 主流网关产品二、Spring Cloud Gateway 核心使用2.1. 搭建网关服务2.2. 路由断言工厂2.3. 网关过滤器工厂2.4. 自定义过滤器一、网关介绍1.1. 引入网关要解决的问题在微服务技术体系中我们已经借助 Eureka、Nacos 实现服务的注册与发现依靠 Spring Cloud LoadBalancer 完成负载均衡通过 OpenFeign 实现服务之间的远程调用。但此时会暴露出一个现实问题所有微服务接口直接对外暴露外部客户端可以直接访问各个微服务。为保障接口访问安全业务系统需要增加权限校验逻辑。而微服务架构把原本单体应用拆分成了多个独立服务就会造成权限校验代码在每一个微服务中重复编写。一旦校验规则发生改动开发人员就需要修改、更新多个服务大大增加维护成本。针对该痛点行业普遍的解决方案就是引入 API 网关。我们可以用公司办公的场景来形象理解网关的价值。单体架构好比公司只有一名员工来访人员直接由这名员工完成身份核验并处理业务。演进到微服务架构后公司划分出多个独立部门每个部门都要自行对来访者做身份核实。这种模式不仅办事效率低下还加重各个部门人员的工作负担。为了解决该问题公司设立统一的前台岗位全部来访人员先由前台完成身份校验校验通过之后内部各个部门就直接信任来访者处理对应的业务。这里的前台就等价于微服务体系中的 API 网关。1.2. API 网关概念API 网关本身也是一个独立服务它是后端全部微服务的唯一对外入口设计思想对应门面Facade设计模式。网关充当整个微服务集群的门面角色外部客户端发起的所有请求都必须经过网关完成调度与过滤网关自身不实现具体业务逻辑。网关四大核心功能权限控制校验用户身份非法请求直接拦截。动态路由按照配置规则把请求转发给对应的后端微服务。负载均衡目标服务存在多个实例时分发请求。限流限制请求流量保护下游微服务防止被大流量压垮。1.3. 主流网关产品市面上存在多款成熟的开源网关产品包括 Nginx、Kong、Zuul 以及 Spring Cloud Gateway。其中 Zuul 是 Netflix 开源的网关组件可以和 Eureka、Ribbon、Hystrix 等组件协同工作在 Spring Cloud Finchley 版本之前是官方推荐网关。但 Netflix 在 2018 年宣布 Zuul 进入维护状态不再迭代新增功能。Spring Cloud Gateway 是 Spring 官方推出的全新网关组件基于 Spring、Spring Boot 开发设计目标就是用来替代 Zuul。它可以为微服务提供请求转发能力同时处理安全校验、监控统计、服务弹性这类横切业务。官方性能测试数据显示Spring Cloud Gateway 每秒处理请求数 RPS达到 Zuul1.X 版本的 1.6 倍性能表现更加优异。二、Spring Cloud Gateway 核心使用2.1. 搭建网关服务创建 gateway Maven 模块引入 3 个核心依赖dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-gateway/artifactId /dependency dependency groupIdcom.alibaba.cloud/groupId artifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId /dependency dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-loadbalancer/artifactId /dependency编写启动类package com.yang.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }application.yml 路由配置server: port: 10030 spring: application: name: gateway cloud: nacos: discovery: server-addr: 154.8.196.189:8848 gateway: metrics: enabled: true routes: - id: order-service # 路由唯一标识自定义 uri: lb://order-service/ # 目标服务地址lb://服务名代表从 Nacos 注册中心获取服务lb loadbalance 开启负载均衡 predicates: # 路由断言匹配请求条件匹配成功才转发 - Path/order/** - id: product-service uri: lb://product-service/ predicates: - Path/product/**测试接口http://127.0.0.1:10030/order/12.2. 路由断言工厂Predicate 是 Java 8 提供的函数式编程接口接收一个参数返回布尔类型结果主要用于条件过滤与请求参数校验。它定义了 test 抽象方法除此之外还提供 isEqual、and、or、negate 等默认方法可以实现对象相等判断、逻辑与、逻辑或、逻辑取反等操作。在代码编写的时候除了直接定义实现类之外还可以使用匿名内部类、Lambda 表达式、方法引用等多种方式来完成 Predicate 对象的创建。package predicate; import java.util.function.Predicate; public class StringPredicate implements PredicateString { Override public boolean test(String s) { return s null || s.isEmpty(); } }package predicate; import org.junit.Test; import java.util.function.Predicate; public class PredicateTest { Test public void test1() { PredicateString predicate new StringPredicate(); System.out.println(predicate.test()); System.out.println(predicate.test(hello)); } Test public void test2() { PredicateString predicate new PredicateString() { Override public boolean test(String s) { return s null || s.isEmpty(); } }; System.out.println(predicate.test()); System.out.println(predicate.test(hello)); } Test public void test3() { PredicateString predicate s - s null || s.isEmpty(); System.out.println(predicate.test()); System.out.println(predicate.test(hello)); } Test public void test4() { // 创建一个PredicateString对象判断字符串是否为null或空 PredicateString predicate s - s null || s.isEmpty(); // 打印negate()方法对空字符串的测试结果应为true System.out.println(predicate.negate().test()); // 打印negate()方法对非空字符串hello的测试结果应为true System.out.println(predicate.negate().test(hello)); } Test public void test5() { // 判断字符串是否等于aa PredicateString predicate1 s - aa.equals(s); // 判断字符串是否等于bb PredicateString predicate2 s - bb.equals(s); // predicate1 和 predicate2 都为 false所以 or 结果为false System.out.println(predicate1.or(predicate2).test()); // predicate1为true所以or结果为true System.out.println(predicate1.or(predicate2).test(aa)); } Test public void test6() { // 定义第一个Predicate判断字符串是否不为空且长度大于0 PredicateString predicate s - s ! null !s.isEmpty(); // 定义第二个Predicate判断字符串是否不为空且所有字符都是数字 PredicateString 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)); } }在 Spring Cloud Gateway 当中路由断言工厂 Route Predicate Factories 负责提供路由规则的匹配能力。我们在配置文件中书写的断言规则仅仅是字符串这些字符串会被对应的路由断言工厂读取解析最终转换成用于路由判断的条件逻辑。例如前面案例中用到的 Path/product/** 路径匹配规则底层就是由 PathRoutePredicateFactory 来实现解析。网关内置了大量不同的路由断言工厂分别可以针对 HTTP 请求的时间、Cookie、请求头、域名、请求方式、访问路径、客户端 IP 等各类属性进行匹配多条断言规则之间默认按照逻辑与and的关系进行组合只有全部条件满足当前路由才会生效。断言功能After / Before / Between按照时间匹配请求Cookie匹配 Cookie 键 正则表达式Header匹配请求头 key 与正则Host匹配请求 Host 域名Method匹配 HTTP 请求方式 GET/POST 等Path最常用匹配请求 URL 路径支持通配符/**RemoteAddr匹配客户端 IP 网段我们可以通过配置文件完成断言规则的添加与测试。比如给路由增加 After 时间断言限定只有指定时间之后发起的请求才能够被路由转发。如果客户端发起请求的时间不满足断言条件网关就不会匹配该路由访问接口时就会返回 404调整配置当中的时间参数让当前时间满足断言条件之后接口就可以正常访问请求会按照配置转发到对应的后端微服务。2.3. 网关过滤器工厂路由断言Predicate的作用是判定当前请求应该交由哪一条路由进行处理而如果需要在请求流转的前后追加自定义处理逻辑就需要依靠过滤器Filter来实现。过滤器按照执行时机可以划分为 Pre 前置过滤器和 Post 后置过滤器两大类Pre 类型过滤器会在请求转发到后端微服务之前执行鉴权、限流这类操作都适合放在这一阶段完成。Post 类型过滤器则是在后端服务处理完毕结果返回给客户端之前执行。我们可以用景区游玩的场景简单理解这个过程进入景区前的安检、验票、人流管控对应Pre过滤器游玩结束之后的评价操作对应Post过滤器过滤器属于可选组件并不是必须配置。Spring Cloud Gateway内置了大量过滤器用来对 Web 请求做拦截与链式处理可以实现权限校验、访问超时设置等多种功能。从作用范围上区分过滤器又分为GatewayFilter和GlobalFilter两种GatewayFilter作用于单个路由或者一组路由GlobalFilter 则会对项目中全部路由生效覆盖所有客户端请求。GatewayFilter 属于局部过滤器和路由断言 Predicate 一样大多在application.yml配置文件当中完成配置每一个过滤器都封装好了固定逻辑。比如AddRequestParameterGatewayFilterFactory我们只需要在配置中写上对应的过滤器名称就能够自动为请求添加参数。在实际使用时如果直接写在某一个路由的filters配置块下该过滤器就只会对当前这条路由生效。我们可以通过简单案例演示它的效果在product‑service路由下配置AddRequestParameter 过滤器为请求添加固定参数随后在下游product‑service服务的接口中接收并打印该参数重启网关与业务服务后发起调用就能够在业务服务控制台观察到网关传递过来的参数。spring: gateway: metrics: enabled: true routes: filters: - AddRequestParameteruserName, yangRequestMapping(/o1) public String o1(Integer id, String userName) { log.info(接收到过滤器添加的参数, userName:{}, userName); return productApi.p1(id); }测试接口http://127.0.0.1:10030/feign/o1?id1http://127.0.0.1:8080/users/user-id-here/permissionsSpring Cloud Gateway 提供了十分丰富的内置 GatewayFilter 工厂。常见内置 GatewayFilterAddRequestHeader / AddRequestParameter添加请求头、请求参数AddResponseHeader添加响应头RemoveRequestHeader / RemoveResponseHeader删除请求 / 响应头RequestRateLimiter限流Redis 令牌桶算法超限返回 429Retry后端异常自动重试RequestSize限制请求体大小超限返回 413。如果希望过滤器对所有路由都生效不需要在每一个路由下重复配置可以使用 default‑filters 默认过滤器配置写在 gateway 顶层节点下对全部路由统一生效。GlobalFilter 是网关的全局过滤器它和 GatewayFilter 的处理能力一致区别在于会作用于全部路由常常用来实现全局安全校验、性能监控、日志记录这类全局性业务。网关本身自带不少内置全局过滤器例如采集网关运行指标的 Gateway Metrics Filter、实现本地请求转发的 Forward Routing Filter、为下游服务做负载均衡的 LoadBalancer Client Filter。想要使用监控相关的全局过滤器需要引入actuator监控依赖在配置文件中开启网关 metrics 指标并且暴露对应的监控端点访问actuator地址就可以查看网关各类监控信息。引入依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency添加配置spring: gateway: metrics: enabled: true management: endpoints: web: exposure: include: * endpoint: health: enabled: true过滤器执行顺序规则每个过滤器有 order 整型值order 数值越小优先级越高执行越靠前可以通过Ordered接口或者Order注解设置。order 值相同时执行顺序default‑filters GatewayFilter GlobalFilter。网关会把所有过滤器合并为一条过滤器链排序后依次执行。2.4. 自定义过滤器Spring Cloud Gateway 提供了灵活的过滤器扩展能力开发者可以结合实际业务场景自定义过滤器逻辑自定义过滤器同样支持 GatewayFilter 和 GlobalFilter 两种类型分别适配局部路由生效和全局路由生效两种使用场景。自定义 GatewayFilter 需要实现 GatewayFilterFactory 接口Spring Boot 默认提供了 AbstractGatewayFilterFactory 抽象类开发者可以直接继承该抽象类简化开发。自定义类需要以 GatewayFilterFactory 作为类名后缀这是因为网关默认会取类名的前缀作为配置文件中引用的过滤器名称同时类需要添加 Service 注解交由 Spring 容器进行管理。除此之外还需要在类内部定义一个配置类用来接收 yml 配置文件中传入的参数。在重写的 apply 方法中可以完整实现过滤器的前置与后置处理逻辑。其中调用 chain.filter (exchange) 之前的代码属于 Pre 阶段会在请求转发到下游微服务之前执行通过 then 方法调用 Mono.fromRunnable 包裹的代码属于 Post 阶段会在下游服务处理完成、响应返回给客户端之前执行。方法中涉及的 ServerWebExchange 是 HTTP 请求与响应的交互契约封装了请求、响应的完整上下文信息GatewayFilterChain 则代表过滤器链用于驱动请求在过滤器之间流转。开发者还可以通过重写 getOrder 方法指定过滤器的优先级order 数值越大优先级越低执行顺序越靠后。完成代码开发后需要在 application.yml 的对应路由下配置该过滤器。配置时通过 name 指定过滤器的名称前缀通过 args 传入配置类对应的参数配置完成后该过滤器仅对当前路由生效。重启网关与下游业务服务访问对应接口观察控制台日志就可以看到 Pre 和 Post 阶段依次输出的日志信息验证自定义过滤器的执行效果。package com.yang.gateway; import lombok.Data; Data public class CustomConfig { private String name; }package com.yang.gateway; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; Component public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactoryCustomConfig implements Ordered { private static final Logger log LoggerFactory.getLogger(CustomGatewayFilterFactory.class); /** * ServerWebExchange: HTTP 请求-响应交互契约, 提供了对HTTP请求和响应的访问 * GatewayFilterChain: 过滤器链 * Mono: Reactor的核心类, 数据流发布者,Mono最多只能触发一个事件.可以把Mono用在异步完成任务时,发出通知 * chain.filter(exchange) 执行请求 * Mono.fromRunnable() 创建一个包含Runnable元素的数据流 */ public CustomGatewayFilterFactory() { super(CustomConfig.class); } Override public GatewayFilter apply(CustomConfig config) { return new GatewayFilter() { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info(Pre Filter, config:{} , config); return chain.filter(exchange).then(Mono.fromRunnable(() - { log.info(Post Filter...); })); } }; } Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } }spring: gateway: routes: filters: - AddRequestParameteruserName, yang - name: Custom args: name: test_custom自定义 GlobalFilter 的实现方式更为简洁它不需要额外定义配置类也无需在每个路由下单独配置。开发者只需要实现 GlobalFilter 接口与 Ordered 接口为类添加 Service 注解该过滤器就会自动应用到项目中的所有路由上适合用来实现全局鉴权、全链路日志、统一监控这类全局性的横切逻辑。自定义 GlobalFilter 的核心逻辑同样写在 filter 方法中和 GatewayFilter 一样通过 chain.filter (exchange) 划分前置处理与后置处理两个阶段结合 Mono.fromRunnable 完成后置逻辑的编写。重写 getOrder 方法可以调整全局过滤器在整个过滤器链中的执行顺序灵活控制不同过滤器的执行先后。重启网关服务后访问业务接口观察日志输出可以验证过滤器的执行顺序。当自定义 GatewayFilter 与自定义 GlobalFilter 的 order 值相同时前置阶段会先执行 GatewayFilter 的逻辑再执行 GlobalFilter 的逻辑后置阶段则顺序相反先执行 GlobalFilter 的后置逻辑再执行 GatewayFilter 的后置逻辑整体呈现出先进后出的过滤器链执行特征。package com.yang.gateway; 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; Slf4j Component public class CustomGlobalFilter implements GlobalFilter, Ordered { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info(Pre Global Filter); return chain.filter(exchange).then(Mono.fromRunnable(() - { log.info(Post Global Filter); })); } Override public int getOrder() { return Ordered.LOWEST_PRECEDENCE; } }
