3. 网关扩展点概览:常见的扩展机制
做内部开发者平台,网关就是那个“守门人”。但它不是死板的铁门,更像一个乐高底座——你可以往上插各种功能模块。我今天聊的就是这些“插口”长什么样,什么时候用哪个。
我自己刚接触网关扩展时,也被一堆概念搞晕过:插件、过滤器、断言、拦截器……说白了,它们都是把自定义逻辑塞进请求处理流程的手段。但各有各的擅长领域。
3.1 插件:最常用的“搭积木”方式
插件机制,说白了就是在网关启动时加载一段独立模块。我见过的大部分团队,都是从插件开始接触网关扩展的。打个比方,网关核心就一个空架子,插件像USB设备——插上就能干活,拔掉也没事。
我在项目中用过最常见的插件类型:
- 认证插件:OAuth2、JWT验证、LDAP对接
- 限流插件:基于Redis的令牌桶、漏桶算法
- 日志插件:请求/响应日志,用于审计和调试
- 协议转换插件:比如将HTTP格式转为gRPC或Dubbo协议
曾有一次线上事故,某团队同时接了15个插件,其中一个日志插件有内存泄露。排查时靠逐个禁用插件才找到元凶。所以后来我定了一条规矩:生产环境插件不超过8个,超过就必须做性能压测。
3.2 过滤器:管好“请求/响应”的链
过滤器与插件有点像,但更“线性”。它们按顺序组成一条链,请求进门是顺序,响应出门是逆序。Spring Cloud Gateway里的过滤器就是典型。
我平时把过滤器分为三类:
- 前置过滤器:在路由之前做校验、添加Header、修改请求体。比如给所有请求添加一个X-Trace-Id。
- 路由过滤器:与具体路由绑定,比如只对某个路径的请求做重写。我在做版本灰度时常用这个。
- 后置过滤器:处理响应内容,比如压缩、加密、统一包装。
// 一个简单的后置过滤器示例(Kotlin风格)
filter {
order(Ordered.LOWEST_PRECEDENCE - 1)
filter(exchange, chain) {
val response = chain.filter(exchange).then(
Mono.fromRunnable {
val original = exchange.response.bufferFactory().wrap("{}".toByteArray())
// 这里做响应体修改
}
)
response
}
}
3.3 断言:网关的路由“决策者”
断言(Predicate)是网关中用来决定“这个请求走哪条路”的东东。它不修改请求,只返回true/false。你想想看,一个内部平台可能有几十个微服务,多版本共存、灰度发布、A/B测试,全靠断言来分流。
常见的内置断言:
| 断言类型 | 作用 | 举例 |
|---|---|---|
| Path | 匹配请求路径 | /api/v1/** |
| Header | 要求请求头存在且值匹配 | X-Version=v2 |
| Query | 要求查询参数匹配 | ?env=staging |
| Cookie | 匹配Cookie值 | gray=1 |
| Weight | 按权重随机路由(灰度) | group1=80, group2=20 |
3.4 拦截器:更底层的“钩子”
拦截器一般比过滤器更细粒度。它是跑在请求进入业务逻辑之前或之后,可以访问到方法级别的上下文。在Spring MVC里是HandlerInterceptor,在Dubbo Filter里是InvokerInterceptor。它经常用来干这些事:
- 透传用户身份到下游上下文
- 度量(Metrics)采集,比如每个接口的耗时
- 重试逻辑(配合RetryTemplate)
- 动态路由覆写
我个人习惯把拦截器用在需要“跨越多个插件或过滤器”的场景。举个例子:用户登录后,拦截器把userInfo塞进ThreadLocal,后续的限流插件、日志插件都能直接从上下文拿,而不用重复解析。
3.5 四种机制怎么选?一张表说清楚
| 特性 | 插件 | 过滤器 | 断言 | 拦截器 |
|---|---|---|---|---|
| 执行位置 | 全局/自定义 | 链式,前后置 | 路由前 | 方法前后 |
| 典型用途 | 认证、限流、日志 | 内容修改、校验 | 路由、灰度 | 上下文传递、度量 |
| 是否修改载荷 | 可以 | 可以 | 不可以 | 可以 |
| 执行顺序 | 插件内无序 | 链式有序 | 与,或组合 | 线性有序 |
| 性能影响 | 中(热加载开销) | 低 | 极低(纯逻辑) | 低 |
嗯,这就是网关扩展点的大致地图。下一回碰到具体场景,你就能对着这张表快速选型了。别忘了,扩展点只是工具,清楚业务逻辑才是核心。