8.3 API网关设计与实现¶
1. API网关概述¶
API网关是微服务架构的“流量入口管家”,负责统一接收客户端请求、处理共性需求(如认证、限流),并转发到后端服务。理解其定位是后续实现的基础。
1.1 API网关的定义与职责¶
- 定义:API网关(API Gateway)是位于客户端与后端微服务之间的中间层服务,提供统一的API入口,封装服务内部架构,为客户端提供定制化的API服务。
- 核心职责(“四统一+一保障”):
- 统一入口:客户端只需对接网关,无需感知后端多个微服务的存在。
- 统一认证:集中处理Token验证、权限检查,避免每个服务重复实现。
- 统一监控:收集所有请求的日志、耗时、错误率,便于问题排查。
- 统一协议转换:支持HTTP、gRPC、WebSocket等协议的转换(如客户端用HTTP,后端用gRPC)。
- 稳定性保障:通过限流、熔断、负载均衡保护后端服务,避免雪崩。
1.2 网关在微服务架构中的位置¶
微服务架构典型分层(从客户端到后端):
- 网关是“流量的唯一入口”:所有客户端请求必须经过网关,网关再根据请求路径/参数转发到对应的微服务。 - 网关是“微服务的保护壳”:隔离客户端与微服务,避免微服务直接暴露在公网,降低安全风险。1.3 网关 vs 反向代理 vs 负载均衡器¶
三者常被混淆,但定位和功能差异显著,对比如下:
| 特性 | API网关 | 反向代理(如Nginx) | 负载均衡器(如LVS) |
|---|---|---|---|
| 核心目标 | 微服务治理(认证、限流、路由) | 请求转发、静态资源缓存 | 流量分发(解决单节点压力) |
| 工作层级 | 应用层(HTTP/HTTPS,可解析请求体) | 应用层/传输层(可解析HTTP头部) | 传输层(TCP/UDP,不解析应用数据) |
| 服务感知能力 | 感知微服务(如服务名、版本) | 感知后端节点(如IP:Port) | 仅感知节点IP,无服务概念 |
| 典型使用场景 | 微服务架构的统一入口 | 单体服务的请求转发、SSL卸载 | 高并发场景下的节点负载分担 |
总结:反向代理和负载均衡器是网关的“基础能力组件”,网关在其之上增加了微服务特有的治理功能(如认证、限流)。
1.4 API网关的发展历程¶
- 1.0 阶段(单体网关):如早期的Zuul 1.x,基于同步IO模型,性能较低,适合中小规模服务。
- 2.0 阶段(高性能网关):如Zuul 2.x、Envoy,基于异步IO模型(Netty),支持高并发,引入“服务发现”“熔断”等特性。
- 3.0 阶段(云原生网关):如Istio(Sidecar模式)、Kong Gateway,支持K8s集成、动态配置、灰度发布,适配云原生环境。
2. API网关核心功能¶
API网关的核心价值在于“集中处理微服务的共性需求”,避免每个服务重复开发。以下是必须实现的核心功能:
2.1 请求路由与负载均衡¶
- 请求路由:网关根据“请求路径+服务映射规则”,将请求转发到对应的微服务。
例:/api/v1/user/*转发到“用户服务”,/api/v1/order/*转发到“订单服务”。 - 负载均衡:当后端微服务有多个实例时(如用户服务部署3台机器),网关需将请求均匀分发到实例,避免单实例过载。
常见算法:轮询(Round Robin)、加权轮询(按实例性能分配权重)、一致性哈希(解决会话保持问题)。
2.2 认证与授权¶
- 认证:验证客户端身份合法性,常用方式:
- Token认证(如JWT):客户端携带Token请求,网关验证Token有效性。
- OAuth2.0/OpenID Connect:支持第三方登录(如微信、支付宝登录)。
- 授权:验证客户端是否有权限访问某个接口,常用方式:
- 基于角色(RBAC):如“管理员”可访问
/api/admin/*,“普通用户”仅可访问/api/user/*。 - 基于权限列表:如用户A有权限访问
/api/v1/user/123(自己的用户信息),无权访问/api/v1/user/456。
2.3 限流与熔断¶
- 限流:防止客户端请求过多导致后端服务雪崩,常用策略:
- 令牌桶算法:网关按固定速率生成令牌,请求需获取令牌才能转发(支持突发流量)。
- 漏桶算法:请求按固定速率转发,多余请求排队或拒绝(平滑流量)。
- 熔断:当后端服务故障(如超时、错误率高)时,网关直接返回降级响应,避免请求持续打到故障服务,待服务恢复后再恢复转发。
典型实现:基于熔断器模式(Closed→Open→Half-Open→Closed)。
2.4 请求/响应转换¶
- 请求转换:适配客户端与后端服务的接口差异,如:
- 参数映射:客户端传
user_id,后端需要uid,网关自动转换参数名。 - 协议转换:客户端用HTTP请求,后端用gRPC服务,网关实现HTTP→gRPC转换。
- 响应转换:统一后端服务的响应格式,如所有接口返回:
2.5 监控与日志记录¶
- 监控:收集请求的关键指标,如:
- 流量指标:QPS(每秒请求数)、TPS(每秒事务数)。
- 性能指标:请求耗时(P95/P99延迟)、响应码分布(2xx/4xx/5xx)。
- 健康指标:后端服务的存活状态、错误率。
- 日志:记录每一次请求的详细信息,便于问题排查,如:
- 请求信息:客户端IP、请求方法、路径、参数。
- 转发信息:目标服务、转发耗时、响应码。
- 异常信息:认证失败、转发超时、服务熔断的原因。
2.6 缓存策略¶
- 作用:对高频读请求(如商品详情、用户信息)进行缓存,减少后端服务的访问压力。
- 实现方式:
- 本地缓存(如Go的
sync.Map):适合高频、小容量数据(如配置信息),优点是快,缺点是分布式环境下缓存不一致。 - 分布式缓存(如Redis):适合大容量、分布式场景,优点是缓存一致,缺点是有网络开销。
- 缓存策略:TTL(过期时间)、缓存穿透(布隆过滤器)、缓存击穿(互斥锁)、缓存雪崩(过期时间错开)。
3. 网关设计模式¶
不同业务场景需要不同的网关架构,以下是四种常用设计模式:
3.1 Backend for Frontend (BFF)模式¶
- 核心思想:为不同类型的客户端(Web、iOS、Android、小程序)设计专属的网关实例,适配客户端的接口需求。
- 场景举例:
- Web端BFF:需要返回完整的HTML页面数据,可能聚合“用户服务+商品服务+订单服务”的接口。
- App端BFF:只需返回JSON格式的核心业务数据,避免冗余字段,减少流量消耗。
- 优点:客户端接口定制化,减少冗余数据;缺点:增加网关实例数量,维护成本上升。
3.2 微网关模式¶
- 核心思想:每个微服务(或服务集群)对应一个小型网关,网关与服务紧密绑定,而非集中式网关。
- 架构图:
- 优点:网关轻量化,故障隔离(一个微网关挂了不影响其他服务);缺点:网关实例多,配置同步复杂。
- 典型产品:Istio(Sidecar模式,每个服务实例旁部署一个Proxy网关)。
3.3 网关聚合模式¶
- 核心思想:网关将多个微服务的接口“聚合”为一个接口,客户端只需调用一次网关,即可获取多个服务的数据。
- 场景举例:电商“商品详情页”需要:
- 商品基本信息(商品服务)
- 商品库存(库存服务)
- 商品评价(评价服务)
若用聚合模式,网关提供/api/v1/product/detail接口,内部调用3个服务,聚合结果后返回给客户端。 - 优点:减少客户端请求次数,降低网络开销;缺点:网关逻辑复杂,单次请求耗时可能增加(需等所有服务响应)。
3.4 网关卸载模式¶
- 核心思想:将微服务的“非业务逻辑”(如SSL卸载、压缩、日志)卸载到网关,让微服务专注于业务处理。
- 常见卸载任务:
- SSL卸载:网关处理HTTPS解密,后端服务只需处理HTTP请求,减少服务的CPU消耗。
- 数据压缩:网关对响应数据(如JSON、HTML)进行Gzip压缩,减少传输流量。
- 日志卸载:网关统一记录请求日志,微服务无需再写日志逻辑。
- 优点:简化微服务实现,提升整体性能;缺点:网关压力增大,需确保网关高可用。
4. Go语言实现API网关¶
Go语言的协程模型(Goroutine)和高性能网络库(net/http)非常适合实现API网关。本节基于Gin框架(高性能HTTP框架)实现网关核心功能。
4.1 基于Gin框架搭建网关基础骨架¶
首先创建一个最小化的网关:接收客户端请求,转发到后端微服务(以“用户服务”为例)。
步骤1:初始化项目与依赖¶
# 创建项目目录
mkdir go-gateway && cd go-gateway
# 初始化Go模块
go mod init github.com/your-name/go-gateway
# 安装依赖(Gin框架、HTTP客户端)
go get github.com/gin-gonic/gin
go get net/http/httputil
步骤2:完整代码实现(基础转发功能)¶
package main
import (
"net/http"
"net/http/httputil"
"net/url"
"github.com/gin-gonic/gin"
)
// 定义后端服务的URL(示例:用户服务部署在localhost:8081)
var userServiceURL, _ = url.Parse("http://localhost:8081")
// 反向代理处理器:将请求转发到目标服务
func reverseProxy(target *url.URL) gin.HandlerFunc {
// 创建反向代理实例
proxy := httputil.NewSingleHostReverseProxy(target)
// (可选)修改请求头(如添加网关标识)
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
// 添加网关标识,便于后端服务识别请求来源
req.Header.Set("X-Gateway-Name", "go-gateway")
req.Header.Set("X-Gateway-Version", "v1.0.0")
}
// (可选)修改响应头(如统一设置Cache-Control)
proxy.ModifyResponse = func(resp *http.Response) error {
resp.Header.Set("Cache-Control", "no-cache")
return nil
}
// 返回Gin处理器
return func(c *gin.Context) {
proxy.ServeHTTP(c.Writer, c.Request)
}
}
func main() {
// 1. 创建Gin引擎(开发环境用DebugMode,生产环境用ReleaseMode)
r := gin.Default() // Default()会自动添加Logger和Recovery中间件
// 2. 配置路由:将/api/v1/user/*转发到用户服务
r.Any("/api/v1/user/*path", reverseProxy(userServiceURL))
// 3. 启动网关服务(监听8080端口)
if err := r.Run(":8080"); err != nil {
panic("网关启动失败:" + err.Error())
}
}
步骤3:测试网关功能¶
- 启动后端“用户服务”:监听
8081端口,提供/api/v1/user/123接口(返回用户信息)。 - 启动网关:
go run main.go。 - 发送请求到网关:
- 预期结果:网关将请求转发到
http://localhost:8081/api/v1/user/123,并返回用户服务的响应。
4.2 中间件架构设计(认证/日志中间件示例)¶
Gin框架的中间件(Middleware)是实现网关“横切功能”(如认证、日志)的核心机制。中间件可分为“全局中间件”(所有请求生效)和“路由中间件”(指定路由生效)。
示例1:日志中间件(全局生效)¶
记录每一次请求的“客户端IP、请求方法、路径、耗时”:
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
// LoggerMiddleware 日志中间件
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 1. 请求前:记录开始时间、客户端IP、请求信息
startTime := time.Now()
clientIP := c.ClientIP()
method := c.Request.Method
path := c.Request.URL.Path
// 2. 执行后续中间件和路由处理函数
c.Next()
// 3. 请求后:记录耗时、响应码
costTime := time.Since(startTime)
statusCode := c.Writer.Status()
// 4. 输出日志(生产环境建议用日志库如zap,而非fmt)
fmt.Printf(
"[GATEWAY LOG] IP:%s, Method:%s, Path:%s, Status:%d, Cost:%s\n",
clientIP, method, path, statusCode, costTime,
)
}
}
func main() {
// 创建Gin引擎,添加全局日志中间件
r := gin.New() // 不用Default(),手动添加中间件
r.Use(LoggerMiddleware()) // 全局日志中间件
r.Use(gin.Recovery()) // 全局异常恢复中间件(防止panic导致网关崩溃)
// 路由配置(同4.1)
r.Any("/api/v1/user/*path", reverseProxy(userServiceURL))
// 启动服务
if err := r.Run(":8080"); err != nil {
panic("网关启动失败:" + err.Error())
}
}
示例2:JWT认证中间件(指定路由生效)¶
仅对需要认证的路由(如/api/v1/user/*)生效,验证客户端携带的JWT Token:
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// 1. 定义JWT密钥(生产环境需用环境变量或配置中心存储,避免硬编码)
const jwtSecret = "your-secret-key-123"
// JWTAuthMiddleware JWT认证中间件
func JWTAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 1. 获取Token:从Authorization头中提取(格式:Bearer <token>)
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "未携带认证Token"})
c.Abort() // 终止请求,不执行后续处理
return
}
// 2. 解析Token格式
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": Token格式错误"})
c.Abort()
return
}
tokenStr := parts[1]
// 3. 验证Token有效性
claims := &jwt.MapClaims{} // 存储Token中的自定义字段(如user_id、role)
token, err := jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(jwtSecret), nil
})
if err != nil || !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "Token无效或已过期"})
c.Abort()
return
}
// 4. Token有效:将用户信息存入上下文,供后续路由使用
c.Set("user_id", (*claims)["user_id"])
c.Set("role", (*claims)["role"])
// 5. 执行后续处理
c.Next()
}
}
func main() {
r := gin.New()
r.Use(LoggerMiddleware())
r.Use(gin.Recovery())
// 2. 为需要认证的路由添加JWT中间件
userGroup := r.Group("/api/v1/user")
userGroup.Use(JWTAuthMiddleware()) // 仅该分组下的路由需要认证
{
userGroup.Any("/*path", reverseProxy(userServiceURL))
}
// 3. 无需认证的路由(如登录接口)
r.POST("/api/v1/login", func(c *gin.Context) {
// 登录逻辑:验证用户名密码,生成JWT Token返回
// ...(此处省略登录逻辑)
token := generateJWT(123, "user") // 假设用户ID=123,角色=user
c.JSON(http.StatusOK, gin.H{"code": 200, "msg": "登录成功", "token": token})
})
r.Run(":8080")
}
// generateJWT 生成JWT Token(辅助函数)
func generateJWT(userID int, role string) string {
claims := jwt.MapClaims{
"user_id": userID,
"role": role,
"exp": jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), // 过期时间:24小时
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, _ := token.SignedString([]byte(jwtSecret))
return signedToken
}
4.3 动态路由与API版本管理¶
静态路由(硬编码在代码中)无法满足生产需求(如新增服务需重启网关),需实现动态路由(从配置文件/配置中心加载);同时支持API版本管理(如/api/v1/user和/api/v2/user对应不同版本的用户服务)。
实现方案:基于配置文件的动态路由¶
-
创建路由配置文件(
routes.yaml):routes: - path: "/api/v1/user/*" # 路由路径(支持通配符*) service: "user-service" # 服务名 upstream: "http://localhost:8081" # 后端服务地址(v1版本) needAuth: true # 是否需要认证 - path: "/api/v2/user/*" # v2版本路由 service: "user-service-v2" upstream: "http://localhost:8082" # 后端服务地址(v2版本) needAuth: true - path: "/api/v1/order/*" service: "order-service" upstream: "http://localhost:8083" needAuth: true - path: "/api/v1/public/*" # 公开接口(无需认证) service: "public-service" upstream: "http://localhost:8084" needAuth: false -
动态路由加载代码:
package main import ( "net/http" "net/http/httputil" "net/url" "os" "sync" "time" "github.com/gin-gonic/gin" "gopkg.in/yaml.v3" ) // 1. 定义路由配置结构体 type RouteConfig struct { Path string `yaml:"path"` // 路由路径 Service string `yaml:"service"` // 服务名 Upstream string `yaml:"upstream"` // 后端服务地址 NeedAuth bool `yaml:"needAuth"` // 是否需要认证 } type GatewayConfig struct { Routes []RouteConfig `yaml:"routes"` } // 2. 全局路由管理器(支持动态更新) var ( routeManager = &RouteManager{ routes: make(map[string]*RouteConfig), // 路径→路由配置 proxies: make(map[string]*httputil.ReverseProxy), // 服务名→反向代理 } ) type RouteManager struct { sync.RWMutex // 读写锁(保证并发安全) routes map[string]*RouteConfig proxies map[string]*httputil.ReverseProxy } // LoadConfig 从YAML文件加载路由配置 func (m *RouteManager) LoadConfig(filePath string) error { m.Lock() defer m.Unlock() // 读取配置文件 data, err := os.ReadFile(filePath) if err != nil { return err } // 解析YAML var config GatewayConfig if err := yaml.Unmarshal(data, &config); err != nil { return err } // 更新路由和反向代理 newRoutes := make(map[string]*RouteConfig) newProxies := make(map[string]*httputil.ReverseProxy) for _, route := range config.Routes { newRoutes[route.Path] = &route // 创建反向代理(复用相同服务的代理实例) if _, ok := newProxies[route.Service]; !ok { upstreamURL, _ := url.Parse(route.Upstream) proxy := httputil.NewSingleHostReverseProxy(upstreamURL) newProxies[route.Service] = proxy } } m.routes = newRoutes m.proxies = newProxies return nil } // MatchRoute 根据请求路径匹配路由配置 func (m *RouteManager) MatchRoute(path string) (*RouteConfig, *httputil.ReverseProxy) { m.RLock() defer m.RUnlock() // 匹配逻辑:优先精确匹配,再通配符匹配(简化版,生产环境需更复杂的匹配规则) for routePath, route := range m.routes { if strings.HasPrefix(path, strings.TrimSuffix(routePath, "*")) { proxy := m.proxies[route.Service] return route, proxy } } return nil, nil } // 3. 动态路由处理器 func dynamicRouteHandler(c *gin.Context) { // 1. 匹配路由 path := c.Request.URL.Path route, proxy := routeManager.MatchRoute(path) if route == nil || proxy == nil { c.JSON(http.StatusNotFound, gin.H{"code": 404, "msg": "路由不存在"}) return } // 2. 检查是否需要认证 if route.NeedAuth && c.GetHeader("Authorization") == "" { c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "需认证"}) return } // 3. 转发请求 proxy.ServeHTTP(c.Writer, c.Request) } func main() { // 1. 加载初始路由配置 if err := routeManager.LoadConfig("routes.yaml"); err != nil { panic("加载路由配置失败:" + err.Error()) } // 2. 启动定时任务:每30秒重新加载配置(实现动态更新) go func() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { if err := routeManager.LoadConfig("routes.yaml"); err != nil { fmt.Printf("重新加载配置失败:%v\n", err) } else { fmt.Println("重新加载配置成功") } } }() // 3. 配置网关路由(所有请求都走动态路由处理器) r := gin.Default() r.Any("/*path", dynamicRouteHandler) // 通配所有路径 // 4. 启动服务 if err := r.Run(":8080"); err != nil { panic("网关启动失败:" + err.Error()) } }
4.4 插件化架构实现(以认证/限流插件为例)¶
插件化架构的核心是“功能解耦”:将认证、限流、日志等功能封装为独立插件,网关可动态启用/禁用插件,无需修改核心代码。
1. 定义插件接口¶
首先定义统一的插件接口,所有插件需实现该接口:
package main
import "github.com/gin-gonic/gin"
// Plugin 插件接口
type Plugin interface {
Name() string // 插件名称(唯一标识)
Init() error // 插件初始化(如加载配置)
Handler() gin.HandlerFunc // 插件的Gin处理器(中间件逻辑)
Enable() bool // 是否启用插件
}
2. 实现JWT认证插件¶
package main
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// JWTPlugin JWT认证插件
type JWTPlugin struct {
secret string // JWT密钥
enable bool // 是否启用
}
// NewJWTPlugin 创建JWT插件实例
func NewJWTPlugin(secret string, enable bool) *JWTPlugin {
return &JWTPlugin{
secret: secret,
enable: enable,
}
}
// 实现Plugin接口
func (p *JWTPlugin) Name() string {
return "jwt-auth"
}
func (p *JWTPlugin) Init() error {
// 初始化逻辑:如验证密钥是否为空
if p.secret == "" {
return fmt.Errorf("jwt插件密钥不能为空")
}
fmt.Printf("插件[%s]初始化成功\n", p.Name())
return nil
}
func (p *JWTPlugin) Handler() gin.HandlerFunc {
return func(c *gin.Context) {
// 插件未启用,直接放行
if !p.enable {
c.Next()
return
}
// JWT认证逻辑(同4.2的JWTAuthMiddleware)
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "未携带Token"})
c.Abort()
return
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "Token格式错误"})
c.Abort()
return
}
claims := &jwt.MapClaims{}
token, err := jwt.ParseWithClaims(parts[1], claims, func(token *jwt.Token) (interface{}, error) {
return []byte(p.secret), nil
})
if err != nil || !token.Valid {
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "Token无效"})
c.Abort()
return
}
// 存储用户信息到上下文
c.Set("user_id", (*claims)["user_id"])
c.Next()
}
}
func (p *JWTPlugin) Enable() bool {
return p.enable
}
3. 实现令牌桶限流插件¶
package main
import (
"net/http"
"sync"
"time"
"github.com/gin-gonic/gin"
)
// TokenBucket 令牌桶结构体(限流核心)
type TokenBucket struct {
capacity int // 令牌桶容量(最大并发数)
rate int // 令牌生成速率(每秒生成多少令牌)
tokens int // 当前令牌数
lastReset time.Time // 上次重置时间
mu sync.Mutex // 互斥锁(保证并发安全)
}
// NewTokenBucket 创建令牌桶
func NewTokenBucket(capacity, rate int) *TokenBucket {
return &TokenBucket{
capacity: capacity,
rate: rate,
tokens: capacity, // 初始令牌数=容量
lastReset: time.Now(),
}
}
// Take 尝试获取一个令牌(返回true表示成功,false表示限流)
func (tb *TokenBucket) Take() bool {
tb.mu.Lock()
defer tb.mu.Unlock()
// 1. 计算从上次到现在应生成的令牌数
now := time.Now()
elapsed := now.Sub(tb.lastReset).Seconds()
newTokens := int(elapsed * float64(tb.rate))
tb.tokens = min(tb.tokens+newTokens, tb.capacity) // 不超过桶容量
tb.lastReset = now
// 2. 尝试获取令牌
if tb.tokens > 0 {
tb.tokens--
return true
}
return false
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
// RateLimitPlugin 限流插件
type RateLimitPlugin struct {
bucket *TokenBucket // 令牌桶
enable bool // 是否启用
}
// NewRateLimitPlugin 创建限流插件实例
func NewRateLimitPlugin(capacity, rate int, enable bool) *RateLimitPlugin {
return &RateLimitPlugin{
bucket: NewTokenBucket(capacity, rate),
enable: enable,
}
}
// 实现Plugin接口
func (p *RateLimitPlugin) Name() string {
return "rate-limit"
}
func (p *RateLimitPlugin) Init() error {
// 初始化逻辑:验证容量和速率是否合法
if p.bucket.capacity <= 0 || p.bucket.rate <= 0 {
return fmt.Errorf("限流插件容量和速率必须大于0")
}
fmt.Printf("插件[%s]初始化成功(容量:%d,速率:%d/秒)\n", p.Name(), p.bucket.capacity, p.bucket.rate)
return nil
}
func (p *RateLimitPlugin) Handler() gin.HandlerFunc {
return func(c *gin.Context) {
// 插件未启用,直接放行
if !p.enable {
c.Next()
return
}
// 尝试获取令牌:失败则返回429(Too Many Requests)
if !p.bucket.Take() {
c.JSON(http.StatusTooManyRequests, gin.H{
"code": 429,
"msg": "请求过于频繁,请稍后再试",
})
c.Abort()
return
}
c.Next()
}
}
func (p *RateLimitPlugin) Enable() bool {
return p.enable
}
4. 插件管理器(加载与注册插件)¶
package main
import (
"sync"
"github.com/gin-gonic/gin"
)
// PluginManager 插件管理器
var PluginManager = &pluginManager{
plugins: make(map[string]Plugin),
}
type pluginManager struct {
sync.RWMutex
plugins map[string]Plugin // 插件名称→插件实例
}
// RegisterPlugin 注册插件
func (m *pluginManager) RegisterPlugin(plugin Plugin) error {
m.Lock()
defer m.Unlock()
// 检查插件是否已注册
if _, exists := m.plugins[plugin.Name()]; exists {
return fmt.Errorf("插件[%s]已注册", plugin.Name())
}
// 初始化插件
if err := plugin.Init(); err != nil {
return fmt.Errorf("插件[%s]初始化失败:%v", plugin.Name(), err)
}
m.plugins[plugin.Name()] = plugin
return nil
}
// GetEnabledPlugins 获取所有启用的插件
func (m *pluginManager) GetEnabledPlugins() []gin.HandlerFunc {
m.RLock()
defer m.RUnlock()
var handlers []gin.HandlerFunc
for _, plugin := range m.plugins {
if plugin.Enable() {
handlers = append(handlers, plugin.Handler())
}
}
return handlers
}
// 5. 网关主函数(集成插件管理器)
func main() {
// 1. 注册插件
if err := PluginManager.RegisterPlugin(NewJWTPlugin("your-secret-key-123", true)); err != nil {
panic(err)
}
if err := PluginManager.RegisterPlugin(NewRateLimitPlugin(100, 10, true)); err != nil { // 容量100,速率10/秒
panic(err)
}
// 2. 创建Gin引擎,加载所有启用的插件(中间件)
r := gin.New()
r.Use(gin.Recovery())
r.Use(PluginManager.GetEnabledPlugins()...) // 加载插件中间件
r.Use(LoggerMiddleware()) // 加载自定义日志中间件
// 3. 配置动态路由(同4.3)
if err := routeManager.LoadConfig("routes.yaml"); err != nil {
panic(err)
}
r.Any("/*path", dynamicRouteHandler)
// 4. 启动服务
if err := r.Run(":8080"); err != nil {
panic("网关启动失败:" + err.Error())
}
}
5. 高级特性实现¶
5.1 服务发现集成¶
静态配置upstream(如http://localhost:8081)无法应对微服务动态扩缩容,需集成服务发现(如Etcd、Consul、Nacos),让网关自动感知后端服务的存活实例。
基于Etcd的服务发现实现¶
-
安装Etcd客户端依赖:
-
服务发现代码:
package main import ( "context" "fmt" "sync" "time" clientv3 "go.etcd.io/etcd/client/v3" ) // EtcdServiceDiscovery Etcd服务发现 type EtcdServiceDiscovery struct { client *clientv3.Client // Etcd客户端 serviceMap map[string][]string // 服务名→实例列表(如"user-service": ["http://10.0.0.1:8081", ...]) mu sync.RWMutex leaseID clientv3.LeaseID // 租约ID(保持客户端连接) ctx context.Context cancel context.CancelFunc } // NewEtcdServiceDiscovery 创建Etcd服务发现实例 func NewEtcdServiceDiscovery(endpoints []string) (*EtcdServiceDiscovery, error) { // 1. 连接Etcd client, err := clientv3.New(clientv3.Config{ Endpoints: endpoints, DialTimeout: 5 * time.Second, }) if err != nil { return nil, err } // 2. 创建租约(保持连接) lease := clientv3.NewLease(client) leaseResp, err := lease.Grant(context.Background(), 10) // 租约有效期10秒 if err != nil { return nil, err } // 3. 续租(防止租约过期) keepAliveChan, err := lease.KeepAlive(context.Background(), leaseResp.ID) if err != nil { return nil, err } // 4. 初始化服务发现 ctx, cancel := context.WithCancel(context.Background()) sd := &EtcdServiceDiscovery{ client: client, serviceMap: make(map[string][]string), leaseID: leaseResp.ID, ctx: ctx, cancel: cancel, } // 5. 启动协程处理续租响应(避免channel阻塞) go func() { for range keepAliveChan { // 续租成功,无需处理 } fmt.Println("Etcd租约已过期,重新连接...") }() return sd, nil } // WatchService 监听服务实例变化(新增/删除实例) func (sd *EtcdServiceDiscovery) WatchService(servicePrefix string) error { // 1. 前缀查询:获取所有以servicePrefix开头的键(如"/services/user-service/") resp, err := sd.client.Get(sd.ctx, servicePrefix, clientv3.WithPrefix()) if err != nil { return err } // 2. 初始化服务实例列表 sd.updateService(resp.Kvs, servicePrefix) // 3. 监听键变化(后续实例新增/删除时触发) watchChan := sd.client.Watch(sd.ctx, servicePrefix, clientv3.WithPrefix(), clientv3.WithPrevKV()) go func() { for watchResp := range watchChan { for _, event := range watchResp.Events { // 根据事件类型更新服务实例列表 switch event.Type { case clientv3.EventTypePut: // 新增/更新实例 sd.addService(string(event.Kv.Key), string(event.Kv.Value), servicePrefix) case clientv3.EventTypeDelete: // 删除实例 sd.removeService(string(event.PrevKv.Key), servicePrefix) } } } }() return nil } // updateService 从Etcd查询结果更新服务实例 func (sd *EtcdServiceDiscovery) updateService(kvs []*clientv3.KeyValue, prefix string) { sd.mu.Lock() defer sd.mu.Unlock() for _, kv := range kvs { serviceName := getServiceName(string(kv.Key), prefix) serviceAddr := string(kv.Value) sd.serviceMap[serviceName] = append(sd.serviceMap[serviceName], serviceAddr) } } // addService 新增服务实例 func (sd *EtcdServiceDiscovery) addService(key, value, prefix string) { sd.mu.Lock() defer sd.mu.Unlock() serviceName := getServiceName(key, prefix) // 避免重复添加 for _, addr := range sd.serviceMap[serviceName] { if addr == value { return } } sd.serviceMap[serviceName] = append(sd.serviceMap[serviceName], value) fmt.Printf("服务[%s]新增实例:%s\n", serviceName, value) } // removeService 删除服务实例 func (sd *EtcdServiceDiscovery) removeService(key, prefix string) { sd.mu.Lock() defer sd.mu.Unlock() serviceName := getServiceName(key, prefix) serviceAddr := string(key) // 从列表中删除实例 for i, addr := range sd.serviceMap[serviceName] { if addr == serviceAddr { sd.serviceMap[serviceName] = append(sd.serviceMap[serviceName][:i], sd.serviceMap[serviceName][i+1:]...) break } } fmt.Printf("服务[%s]删除实例:%s\n", serviceName, serviceAddr) } // GetServiceInstances 获取服务的所有实例 func (sd *EtcdServiceDiscovery) GetServiceInstances(serviceName string) []string { sd.mu.RLock() defer sd.mu.RUnlock() return sd.serviceMap[serviceName] } // Close 关闭服务发现 func (sd *EtcdServiceDiscovery) Close() { sd.cancel() // 撤销租约 sd.client.Revoke(context.Background(), sd.leaseID) // 关闭客户端 sd.client.Close() } // getServiceName 从Etcd键中提取服务名(辅助函数) // 例:键="/services/user-service/10.0.0.1:8081",前缀="/services/" → 服务名="user-service" func getServiceName(key, prefix string) string { key = strings.TrimPrefix(key, prefix) parts := strings.SplitN(key, "/", 2) if len(parts) > 0 { return parts[0] } return "" } // 集成到网关:修改动态路由的反向代理逻辑,从服务发现获取实例 func updateReverseProxyWithServiceDiscovery() { // 1. 初始化Etcd服务发现(Etcd节点地址) sd, err := NewEtcdServiceDiscovery([]string{"localhost:2379"}) if err != nil { panic(err) } defer sd.Close() // 2. 监听服务变化(服务前缀:"/services/") if err := sd.WatchService("/services/"); err != nil { panic(err) } // 3. 修改RouteManager的MatchRoute方法:从服务发现获取实例,实现负载均衡 // (简化版)在MatchRoute中,根据服务名从sd.GetServiceInstances获取实例列表, // 再用轮询算法选择一个实例,创建反向代理 }
5.2 健康检查机制¶
网关需要定期检查后端服务实例的健康状态,将不健康的实例从转发列表中移除,避免请求转发到故障服务。
实现方案:HTTP健康检查¶
package main
import (
"net/http"
"sync"
"time"
)
// HealthChecker 健康检查器
type HealthChecker struct {
serviceInstances map[string][]string // 服务名→实例列表(同服务发现)
healthyInstances map[string][]string // 服务名→健康实例列表
mu sync.RWMutex
checkInterval time.Duration // 检查间隔(如10秒)
ctx context.Context
cancel context.CancelFunc
}
// NewHealthChecker 创建健康检查器
func NewHealthChecker(checkInterval time.Duration) *HealthChecker {
ctx, cancel := context.WithCancel(context.Background())
return &HealthChecker{
serviceInstances: make(map[string][]string),
healthyInstances: make(map[string][]string),
checkInterval: checkInterval,
ctx: ctx,
cancel: cancel,
}
}
// UpdateServiceInstances 更新服务实例列表(从服务发现获取)
func (hc *HealthChecker) UpdateServiceInstances(serviceName string, instances []string) {
hc.mu.Lock()
defer hc.mu.Unlock()
hc.serviceInstances[serviceName] = instances
}
// Start 启动健康检查(定时执行)
func (hc *HealthChecker) Start() {
ticker := time.NewTicker(hc.checkInterval)
defer ticker.Stop()
for range ticker.C {
hc.checkAllServices()
}
}
// checkAllServices 检查所有服务的实例健康状态
func (hc *HealthChecker) checkAllServices() {
hc.mu.RLock()
defer hc.mu.RUnlock()
for serviceName, instances := range hc.serviceInstances {
// 并发检查每个实例(提高效率)
var wg sync.WaitGroup
healthy := make([]string, 0, len(instances))
for _, instance := range instances {
wg.Add(1)
go func(addr string) {
defer wg.Done()
if hc.checkInstance(addr) {
healthy = append(healthy, addr)
}
}(instance)
}
wg.Wait()
// 更新健康实例列表
hc.mu.Lock()
hc.healthyInstances[serviceName] = healthy
hc.mu.Unlock()
fmt.Printf("服务[%s]健康检查结果:总实例数=%d,健康实例数=%d\n", serviceName, len(instances), len(healthy))
}
}
// checkInstance 检查单个实例的健康状态(HTTP GET请求健康检查接口)
func (hc *HealthChecker) checkInstance(addr string) bool {
// 健康检查接口:后端服务需提供(如"/health")
healthURL := addr + "/health"
client := http.Client{
Timeout: 3 * time.Second, // 超时时间3秒
}
resp, err := client.Get(healthURL)
if err != nil || resp.StatusCode != http.StatusOK {
return false
}
defer resp.Body.Close()
return true
}
// GetHealthyInstances 获取服务的健康实例列表
func (hc *HealthChecker) GetHealthyInstances(serviceName string) []string {
hc.mu.RLock()
defer hc.mu.RUnlock()
return hc.healthyInstances[serviceName]
}
// Close 关闭健康检查
func (hc *HealthChecker) Close() {
hc.cancel()
}
5.3 灰度发布支持¶
灰度发布(Canary Release)是指将新版本服务的流量逐步从0%提升到100%,期间监控服务状态,避免全量发布导致故障。网关可基于“权重”或“用户标签”实现灰度发布。
基于权重的灰度发布实现¶
package main
import (
"sync"
"math/rand"
"time"
)
// CanaryManager 灰度发布管理器
type CanaryManager struct {
serviceCanary map[string]*CanaryConfig // 服务名→灰度配置
mu sync.RWMutex
random *rand.Rand
}
// CanaryConfig 灰度配置
type CanaryConfig struct {
Enable bool // 是否启用灰度
NewService string // 新版本服务地址(如"http://localhost:8082")
OldService string // 旧版本服务地址(如"http://localhost:8081")
CanaryRatio int // 灰度流量占比(0-100,如20表示20%流量走新版本)
}
// NewCanaryManager 创建灰度发布管理器
func NewCanaryManager() *CanaryManager {
return &CanaryManager{
serviceCanary: make(map[string]*CanaryConfig),
random: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
// UpdateCanaryConfig 更新服务的灰度配置
func (cm *CanaryManager) UpdateCanaryConfig(serviceName string, config *CanaryConfig) {
cm.mu.Lock()
defer cm.mu.Unlock()
cm.serviceCanary[serviceName] = config
}
// GetTargetService 根据灰度配置选择目标服务(旧版本/新版本)
func (cm *CanaryManager) GetTargetService(serviceName string) string {
cm.mu.RLock()
defer cm.mu.RUnlock()
config, exists := cm.serviceCanary[serviceName]
// 未启用灰度或配置不存在,走旧版本
if !exists || !config.Enable {
return config.OldService
}
// 基于权重选择:生成0-99的随机数,小于灰度占比则走新版本
randomNum := cm.random.Intn(100)
if randomNum < config.CanaryRatio {
fmt.Printf("服务[%s]灰度发布:流量走新版本(占比:%d%%)\n", serviceName, config.CanaryRatio)
return config.NewService
}
return config.OldService
}
// 集成到网关:在动态路由的MatchRoute中,调用GetTargetService选择目标服务
func integrateCanary() {
// 1. 初始化灰度管理器
cm := NewCanaryManager()
// 2. 配置用户服务的灰度发布(20%流量走新版本)
cm.UpdateCanaryConfig("user-service", &CanaryConfig{
Enable: true,
OldService: "http://localhost:8081", // 旧版本(v1)
NewService: "http://localhost:8082", // 新版本(v2)
CanaryRatio: 20, // 20%流量
})
// 3. 在反向代理中,根据灰度结果选择目标服务
// (修改reverseProxy函数,从cm.GetTargetService获取upstreamURL)
}
5.4 API版本管理(已整合到4.3动态路由)¶
5.5 WebSocket支持¶
Gin框架原生支持WebSocket,网关可转发WebSocket请求到后端服务(如实时聊天、消息推送服务)。
WebSocket转发实现¶
package main
import (
"net/http"
"net/http/httputil"
"net/url"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
// 1. WebSocket升级器(处理HTTP→WebSocket握手)
var wsUpgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // 允许跨域(生产环境需根据实际情况限制)
},
}
// 2. WebSocket反向代理(转发WebSocket请求)
func wsReverseProxy(target *url.URL) gin.HandlerFunc {
return func(c *gin.Context) {
// 1. 将HTTP请求升级为WebSocket
wsConn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
fmt.Printf("WebSocket升级失败:%v\n", err)
return
}
defer wsConn.Close()
// 2. 连接后端WebSocket服务
targetURL := *target
targetURL.Path = c.Param("path") // 传递路径参数
// 将HTTP URL转换为WebSocket URL(http→ws,https→wss)
if targetURL.Scheme == "http" {
targetURL.Scheme = "ws"
} else if targetURL.Scheme == "https" {
targetURL.Scheme = "wss"
}
// 3. 连接后端服务
backendConn, _, err := websocket.DefaultDialer.Dial(targetURL.String(), nil)
if err != nil {
fmt.Printf("连接后端WebSocket服务失败:%v\n", err)
return
}
defer backendConn.Close()
// 4. 双向转发消息(客户端→后端,后端→客户端)
done := make(chan struct{})
// 客户端→后端
go func() {
defer close(done)
for {
mt, msg, err := wsConn.ReadMessage()
if err != nil {
fmt.Printf("读取客户端消息失败:%v\n", err)
return
}
if err := backendConn.WriteMessage(mt, msg); err != nil {
fmt.Printf("发送消息到后端失败:%v\n", err)
return
}
}
}()
// 后端→客户端
go func() {
defer close(done)
for {
mt, msg, err := backendConn.ReadMessage()
if err != nil {
fmt.Printf("读取后端消息失败:%v\n", err)
return
}
if err := wsConn.WriteMessage(mt, msg); err != nil {
fmt.Printf("发送消息到客户端失败:%v\n", err)
return
}
}
}()
<-done
}
}
// 集成到网关:添加WebSocket路由
func main() {
r := gin.Default()
// WebSocket服务地址(如实时聊天服务)
wsServiceURL, _ := url.Parse("http://localhost:8085")
// WebSocket路由:/ws/*path
r.GET("/ws/*path", wsReverseProxy(wsServiceURL))
r.Run(":8080")
}
6. 性能优化¶
API网关作为流量入口,性能至关重要。以下是针对Go网关的关键优化手段:
6.1 连接池管理¶
频繁创建和关闭HTTP连接会消耗大量资源,通过连接池复用连接可显著提升性能。Go的http.Client默认支持连接池,需合理配置参数:
package main
import (
"net/http"
"time"
)
// 创建带连接池的HTTP客户端
func newHTTPClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
// 1. 连接池配置
MaxIdleConns: 100, // 连接池最大空闲连接数
MaxIdleConnsPerHost: 20, // 每个主机的最大空闲连接数
IdleConnTimeout: 30 * time.Second, // 空闲连接超时时间(超过则关闭)
// 2. 超时配置(避免长时间阻塞)
DialContext: (&net.Dialer{
Timeout: 5 * time.Second, // 拨号超时
KeepAlive: 30 * time.Second, // 保持连接超时
}).DialContext,
TLSHandshakeTimeout: 5 * time.Second, // TLS握手超时
},
Timeout: 10 * time.Second, // 客户端整体超时(包括连接、读取响应)
}
}
// 集成到反向代理:使用带连接池的客户端
func improvedReverseProxy(target *url.URL) gin.HandlerFunc {
client := newHTTPClient()
proxy := &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path = target.Path + req.URL.Path
},
Transport: client.Transport, // 使用自定义的Transport(带连接池)
}
return func(c *gin.Context) {
proxy.ServeHTTP(c.Writer, c.Request)
}
}
6.2 缓存策略优化¶
对高频读请求(如商品详情、配置信息)进行缓存,减少后端服务调用。以下是基于Redis的分布式缓存实现:
package main
import (
"context"
"encoding/json"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
)
// RedisCache Redis缓存客户端
var redisClient = redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // 无密码
DB: 0, // 默认DB
})
var ctx = context.Background()
// CacheMiddleware 缓存中间件(仅对GET请求生效)
func CacheMiddleware(expireTime time.Duration) gin.HandlerFunc {
return func(c *gin.Context) {
// 仅对GET请求缓存
if c.Request.Method != http.MethodGet {
c.Next()
return
}
// 1. 生成缓存Key(基于请求路径和参数)
cacheKey := "gateway:cache:" + c.Request.URL.String()
// 2. 尝试从Redis获取缓存
val, err := redisClient.Get(ctx, cacheKey).Result()
if err == nil {
// 缓存命中:直接返回响应
c.Writer.Header().Set("Content-Type", "application/json")
c.Writer.WriteHeader(http.StatusOK)
c.Writer.WriteString(val)
c.Abort() // 终止后续处理
return
}
// 3. 缓存未命中:执行后续处理,并重写响应Writer以记录响应
w := &responseRecorder{ResponseWriter: c.Writer}
c.Writer = w
c.Next()
// 4. 处理响应:将响应结果存入Redis(仅成功响应)
if c.Writer.Status() == http.StatusOK {
redisClient.Set(ctx, cacheKey, w.body.String(), expireTime)
}
}
}
// responseRecorder 重写ResponseWriter,记录响应内容
type responseRecorder struct {
gin.ResponseWriter
body *bytes.Buffer
}
func NewResponseRecorder(w gin.ResponseWriter) *responseRecorder {
return &responseRecorder{
ResponseWriter: w,
body: &bytes.Buffer{},
}
}
func (r *responseRecorder) Write(data []byte) (int, error) {
r.body.Write(data) // 记录响应内容
return r.ResponseWriter.Write(data)
}
// 集成到网关:为需要缓存的路由添加中间件
func main() {
r := gin.Default()
// 对商品详情接口添加缓存(过期时间5分钟)
productGroup := r.Group("/api/v1/product")
productGroup.Use(CacheMiddleware(5 * time.Minute))
{
productGroup.GET("/detail/:id", func(c *gin.Context) {
// 转发到商品服务
// ...
})
}
r.Run(":8080")
}
6.3 异步处理机制¶
网关的部分逻辑(如日志记录、监控指标上报)无需阻塞请求处理,可通过异步协程处理,减少请求耗时:
// AsyncLoggerMiddleware 异步日志中间件
func AsyncLoggerMiddleware() gin.HandlerFunc {
// 创建带缓冲的通道(避免协程泄漏)
logChan := make(chan string, 1000)
// 启动协程消费日志通道(后台处理)
go func() {
for logMsg := range logChan {
// 写入日志文件或日志系统(如ELK)
fmt.Println(logMsg)
}
}()
return func(c *gin.Context) {
startTime := time.Now()
clientIP := c.ClientIP()
method := c.Request.Method
path := c.Request.URL.Path
c.Next()
// 异步记录日志(不阻塞响应)
costTime := time.Since(startTime)
statusCode := c.Writer.Status()
logMsg := fmt.Sprintf(
"[ASYNC LOG] IP:%s, Method:%s, Path:%s, Status:%d, Cost:%s",
clientIP, method, path, statusCode, costTime,
)
// 通道未满则发送,否则丢弃(避免阻塞)
select {
case logChan <- logMsg:
default:
fmt.Println("日志通道已满,丢弃日志")
}
}
}
6.4 内存与CPU优化¶
- 内存优化:
- 避免频繁创建临时对象(如在循环中创建切片、字符串),可复用对象池(
sync.Pool)。 - 对大响应数据(如文件)使用流式处理,避免一次性加载到内存。
- CPU优化:
- 减少锁竞争:使用
sync.RWMutex(读写分离)代替sync.Mutex,适合读多写少场景。 - 避免全局变量竞争:将全局变量拆分为局部变量,或使用
atomic包处理原子操作。 - 编译优化:使用
go build -ldflags="-w -s"编译,去除调试信息,减小二进制文件体积,提升启动速度。
7. 主流API网关产品对比与选型¶
7.1 Kong vs Zuul vs Envoy¶
| 特性 | Kong Gateway | Netflix Zuul 2.x | Envoy Proxy |
|---|---|---|---|
| 开发语言 | Lua(基于OpenResty) | Java(基于Netty) | C++(基于Event-driven) |
| 性能 | 高(OpenResty优化) | 中(JVM开销) | 极高(C++异步IO,低延迟) |
| 生态与插件 | 丰富(官方插件>100个,社区活跃) | 中等(依赖Spring Cloud生态) | 丰富(Istio集成,云原生友好) |
| 配置方式 | Admin API、YAML、数据库 | 配置文件、Spring Cloud Config | 动态配置(xDS协议) |
| 服务发现集成 | 支持Etcd、Consul、K8s | 支持Eureka、Consul | 支持K8s、Etcd、Consul |
| 适用场景 | 中小规模微服务、快速迭代 | Java技术栈、Spring Cloud用户 | 云原生、高并发、低延迟场景 |
| 缺点 | Lua生态学习成本高 | JVM内存占用大,冷启动慢 | 配置复杂,学习曲线陡 |
7.2 开源 vs 商业解决方案¶
| 类型 | 优点 | 缺点 | 典型产品 |
|---|---|---|---|
| 开源解决方案 | 免费、可定制化、社区支持 | 需自行维护(部署、监控、升级) | Kong、Envoy、APISIX、Zuul |