大家好呀,我是苍何。
这年头,大家都在开始卷简历了,我也看了很多同学的简历,其中有一个同学的简历,我印象最为深刻,他的项目经历中,写了自定义 Gateway 过滤器实现统计接口调用耗时,我瞬间就来兴趣了~
要知道利用微服务中的网关过滤器能力来统计接口调用耗时情况,其落地项目和设计思路是很考察对微服务架构整体掌握程度的。
那今天我们就来重点看一看微服务中的网关吧,还是以 PmHub 项目实践深入展开。
网关我觉得可以理解成是微服务系统的门卫,是微服务架构中一个关键的组件,负责管理和调控外部请求进入内部微服务的流量。为了更好理解,拿个生活中的例子来对比下:
一个大型的购物中心(微服务系统),里面有很多不同的商店(不同的微服务),比如服装店、餐馆、电影院等等。每个商店都有自己独立的入口,这样的好处是每个商店都可以独立运营。但是,如果每个顾客都直接去商店入口没有统一入口,会非常混乱。
而且,购物中心需要对每个商店的顾客流量进行管理,比如防止某些商店人满为患或者统一处理会员优惠等。
网关在微服务体系中的具体位置在哪儿呢?
可以看到,网关在负载均衡下的第一入口,也就是说,整个微服务系统,外部请求都必须要经过网关,可以说是整个系统的门卫了。那网关具体负责哪些呢?
总结来说,微服务的应用可能部署在不同机房,不同地区,不同域名下。此时客户端(浏览器/手机/软件工具)想要请求对应的服务,都需要知道机器的具体 IP 或者域名 URL,当微服务实例众多时,这是非常难以记忆的,对 于客户端来说也太复杂难以维护。
此时就有了网关,客户端相关的请求直接发送到网关,由网关根据请求标识解析判断出具体的微服务地址,再把请求转发到微服务实例。这其中的记忆功能就全部交由网关来操作了。
Cloud 全家桶中有个很重要的组件就是网关,在 1.x 版本中都是采用的 Zuul 网关;但在 2.x 版本中,Zuul 的升级一直跳票,SpringCloud 最后自己研发了一个网关 SpringCloud Gateway 替代 Zuul。所以新项目,我们就别再去用 Zuul 了,简历上也最好以 Gateway 为主。
所以目前用的主流的网关就是 SpringCloud Gateway 替代 Zuul 1.x 版网关。PmHub 中采用的也是自建 SpringCloud Gateway 的方式。
Spring Cloud Gateway是基于 Spring 生态系统之上构建的 API 网关,包括:Spring 5.x,Spring Boot 2.x 和 Project Reactor。Spring Cloud Gateway 旨在提供一种简单而有效的方法来路由到 API,并为它们提供跨领域的关注点,例如:安全性,监视/指标,限流等。
以下是官方原理图:
看官网介绍可知,Spring Cloud Gateway 三大核心组件分别是路由(Route)、断言(Predicate)、过滤器(Filter),构成了网关的必要功能。
web 前端请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
predicate就是我们的匹配条件。
filter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了。
路由是构建网关的基本模块,它由 ID,目标 URI,一系列的断言和过滤器组成,如果断言为 true 则匹配该路由。
在 PmHub 中网关的路由配置如下:
spring:
cloud:
gateway:
discovery:
locator:
lowerCaseServiceId: true
enabled: true
routes:
# 认证中心
- id: pmhub-auth
uri: lb://pmhub-auth
predicates:
- Path=/auth/**
filters:
# 验证码处理
- CacheRequestFilter
# - ValidateCodeFilter
- StripPrefix=1
# 代码生成
- id: pmhub-gen
uri: lb://pmhub-gen
predicates:
- Path=/gen/**
filters:
- StripPrefix=0
拿认证中心服务来说,id 取的就是 auth 在 nacos 注册的服务名,这样,请求网关的 URL 中带有「/auth/**」的请求都会被转发到认证中心这个服务上来。
在spring cloud gateway中配置uri有三种方式,包括
spring:
cloud:
gateway:
routes:
- id: pmhub-api
uri: ws://localhost:9090/
predicates:
- Path=/api/**
spring:
cloud:
gateway:
routes:
- id: pmhub-api
uri: http://localhost:9090/
predicates:
- Path=/api/**
其中 PmHub 中采用的是这种通过 Nacos 配置中心的配置方式。
spring:
cloud:
gateway:
routes:
- id: pmhub-api
uri: lb://ruoyi-api
predicates:
- Path=/api/**
断言可以理解为是匹配规则,比如在 PmHub 中配置的「 - Path=/auth/**」就代表所有符合这个路径的规则都会被转发到对应的服务上面来。可以看下官网介绍:
简而言之,Predicate 就是为了实现一组匹配规则,让请求过来找到对应的Route 进行处理。
Spring Cloud Gateway 创建 Route 对象时, 使用RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给Route。
- Spring Cloud Gateway包含许多内置的Route Predicate Factories。
- 所有这些断言都匹配 HTTP 请求的不同属性。
- 多个Route Predicate Factories可以通过逻辑与(and)结合起来一起使用。
路由断言工厂 RoutePredicateFactory 包含的主要实现类如图所示,包括Datetime、请求的远端地址、路由权重、请求头、Host 地址、请求方法、请求路径和请求参数等类型的路由断言。
当然了除了我们定义的规则,也是可以支持一下路由规则的自定义的,以下是一些常用的断言。
spring:
application:
name: pmhub-gateway
cloud:
gateway:
routes:
- id: pmhub-system-a
uri: http://localhost:9201/
predicates:
- Weight=group1, 8
- id: pmhub-system-b
uri: http://localhost:9201/
predicates:
- Weight=group1, 2
spring:
application:
name: pmhub-gateway
cloud:
gateway:
routes:
- id: pmhub-system
uri: http://localhost:9201/
predicates:
- After=2021-02-23T14:20:00.000+08:00[Asia/Shanghai]
spring:
application:
name: pmhub-gateway
cloud:
gateway:
routes:
- id: pmhub-system
uri: http://localhost:9201/
predicates:
- Query=username, abc.
spring:
application:
name: pmhub-gateway
cloud:
gateway:
routes:
- id: pmhub-system
uri: http://localhost:9201/
predicates:
- Path=/system/**
\d+值匹配正则表达式
spring:
application:
name: pmhub-gateway
cloud:
gateway:
routes:
- id: pmhub-system
uri: http://localhost:9201/
predicates:
- Header=X-Request-Id, \d+
当然了,内置的模板不满足需求,也是可以自定义断言规则的,方法也比较简单,按照以下套路即可:
- 要么继承 AbstractRoutePredicateFactory 抽象类
- 要么实现 lRoutePredicateFactory:接口
- 类开头任意取名,但是必须以 RoutePredicateFactory 后缀结尾
如下代码:
/**
* @auther canghe
* @create 2024-05-23 18:30
*/
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
{
public MyRoutePredicateFactory()
{
super(MyRoutePredicateFactory.Config.class);
}
@Validated
public static class Config{
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return new Predicate<ServerWebExchange>()
{
@Override
public boolean test(ServerWebExchange serverWebExchange)
{
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) return false;
//如果说参数存在,就和config的数据进行比较
if(userType.equals(config.getUserType())) {
return true;
}
return false;
}
};
}
}
网关中的过滤器,有点类似 SpringMVC 里面的拦截器 Interceptor 以及 Servlet 的过滤器,其中「pre」 和「post」分别会在请求被执行钱调用和被执行后调用,用来修改请求和响应信息。
过滤器也是面试中最常问的知识点,比如记录接口调用市场统计、限流、黑白名单等。
因文章篇幅有限,将在下一章单独细说细说,可见其重要程度了,也是 PmHub 项目框架的核心之一了。感兴趣可以持续关注。