Gin
~80k ★Martini 风格路由器,基数树路由
对于接受自定义 context 类型的团队,是最快上手的选择。生态丰富。很多 Go 团队的默认选项。
func(c *gin.Context) 框架对比
Gin、Echo、Fiber、Chi 和 Plumego——每个框架放弃了什么,换来了什么。这页是决策辅助工具,不是基准测试排行榜。
func(w, r),
再逐步剥离框架特有的 context 用法。
五个工具包都针对 Go HTTP 服务。差异在于设计哲学和使用范围,而不是在典型请求量下的原始性能。 先看结论一行,再跟着符合你团队实际约束的深度分析走。
Martini 风格路由器,基数树路由
对于接受自定义 context 类型的团队,是最快上手的选择。生态丰富。很多 Go 团队的默认选项。
func(c *gin.Context) 高性能极简 Web 框架
API 整洁,中间件生态不错,handler 里 return error 的设计符合 Go 习惯。自定义 context 的权衡可以接受时,是个扎实的选择。
func(c echo.Context) error Express 风格,基于 Fasthttp
基准测试吞吐量最高。代价是与 net/http 完全不兼容——没有 stdlib 中间件、没有 net/http handler、没有标准库生态。
func(c *fiber.Ctx) error 轻量惯用的 Go HTTP 服务路由器
极简主义者的选择。纯 stdlib,对响应格式、服务结构或模块稳定性零主张。如果你想自己搭建所有东西,Chi 是合适的起点。
func(w http.ResponseWriter, r *http.Request) 显式 HTTP 工具包 — stdlib only,Agent 就绪,v1.0.0
适合那些更看重路由 ownership、评审清晰度和共享服务骨架的团队,而不是框架便利性。如果团队在使用 AI 编码工具,这是唯一有机器可读 ownership 信号的选择。
func(w http.ResponseWriter, r *http.Request) 横向阅读一行,对比所有五个工具包的同一属性。矩阵不涵盖生态大小、社区活跃度或原始性能——这些取决于你的基准。它捕捉的是不随负载变化的结构性权衡。
| 属性 | Gin | Echo | Fiber | Chi | Plumego |
|---|---|---|---|---|---|
| stdlib handler 签名 func(w http.ResponseWriter, r *http.Request) | |||||
| net/http 中间件复用 已有 stdlib 中间件无需适配器 | |||||
| 零外部依赖 稳定内核仅用 Go 标准库 | |||||
| 内置响应 contract 成功与错误响应的类型化 JSON 封装 | |||||
| 规范服务骨架 bootstrap、路由和 handler 布局的参考应用 | |||||
| 显式模块稳定性层级 stable / beta / experimental 标签与采用指南 | |||||
| AI / Agent 就绪控制面 specs/task-routing.yaml 在代码打开前就对工作分类;specs/change-recipes/ 为 15 种修改类型定义有序步骤;module.yaml 约束每个包的范围;internal/checks/ 在 CI 中机械执行边界检查 |
Fiber 与 net/http 的不兼容性不是缺失的功能——这是换取更高吞吐量的刻意架构取舍。参见 Fiber 深度分析,了解这个权衡什么时候值得做。
自定义 context 类型(Gin、Echo、Fiber)破坏了与已有 net/http 中间件生态的兼容性。 采用这些框架后,每个 stdlib 中间件都需要适配器。用 Chi 和 Plumego,整个 net/http 世界都能直接使用。
// gin.Context — net/http 中间件需要适配器
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"id": id})
})
// 复用 net/http 中间件需要:
r.Use(gin.WrapH(myStdlibMiddleware)) // echo.Context + error 返回值
e.GET("/users/:id", func(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK,
map[string]any{"id": id})
}) // fasthttp — 与 net/http 完全不兼容
// 零个 net/http 中间件可以复用
app.Get("/users/:id", func(c *fiber.Ctx) error {
id := c.Params("id")
return c.JSON(fiber.Map{"id": id})
}) // 纯 stdlib — 但没有响应 contract
r.Get("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
// 每次都要自己构建 JSON 响应格式
json.NewEncoder(w).Encode(map[string]any{"id": id})
}) // stdlib 签名 + 类型化响应 contract
r.Get("/users/:id", func(w http.ResponseWriter, r *http.Request) {
id := router.Param(r, "id")
contract.WriteResponse(w, r, http.StatusOK, user, nil)
// → {"data": {...}, "meta": null}
})
// 所有 net/http 中间件直接可用
r.Use(middleware.RequestID)
r.Use(myOtelMiddleware)
Gin、Echo、Fiber 和 Chi 都把 JSON 响应格式留给你决定。这意味着每个团队、每个 handler 或每个项目的响应格式可能都微妙不同。
Plumego 的 contract 模块对成功和错误响应强制执行同一个封装。
Gin / Echo / Fiber / Chi — 各自决定格式
// 成功响应 — 每个团队格式不同
c.JSON(200, gin.H{
"data": user,
"success": true, // 有些团队加
"code": 0, // 有些团队加
})
// 错误响应 — 更加不一致
c.JSON(400, gin.H{
"error": "name required",
"message": "...", // 重复?缺失?
"code": 1001,
}) Plumego — 所有 handler 共用一套 contract
// 成功 — 始终是 {"data": ..., "meta": ...}
contract.WriteResponse(w, r, 200, user, nil)
// 错误 — 始终是 {"error": {"type": ..., "message": ...}}
contract.WriteError(w, r,
contract.NewErrorBuilder().
Type(contract.TypeRequired).
Field("name").
Message("name 是必填项").
Build())
// 每个 handler、每个服务的封装都一致 下表不是为了评出胜者。每行回答两个问题:这个框架擅长什么,以及什么情况下应该暂停考虑。 「什么时候不该选 Plumego」的诚实回答在最后。
这一节是 Plumego 自己的内容规则要求的。诚实的对比必须包含框架不合适的场景——不只是它赢的地方。
约定优先的团队
Plumego 把路由注册、中间件顺序和依赖 wiring 都明确地留在你的代码里。 如果团队更希望框架隐式管理结构,显式模型只会增加摩擦而没有额外价值。选 Gin 或 Echo 会更高效。
已有大型代码库
把成熟的 Gin 或 Echo 服务迁移到 Plumego,需要把所有 handler 从自定义 context 改成 net/http 签名。 如果服务运行正常、团队工作顺畅,这个迁移成本几乎不值得。留在你已有的框架上。
最大吞吐量
Plumego 使用 net/http 兼容的 trie 路由器,开销极小但不为零,比 Fiber 的 Fasthttp 基线多一点。 如果你有测量数据证明 HTTP 路由是瓶颈(不是数据库、不是业务逻辑),那 Fiber 才是对的话题。
现在就需要长期生产记录
Plumego 已有 v1.0.0 标签,9 个稳定根有兼容性承诺,但项目尚未积累 Gin 或 Echo 那样的长期生产记录。 如果"5年以上大规模验证"是硬性要求,Gin 或 Echo 在组织层面更安全。
这几个框架的基准对比在网上广泛流传。阅读时请保持审慎。
Fiber 注意事项
Fasthttp 通过复用 buffer 来避免标准库分配,在合成基准中产生可测量的吞吐量提升。 但如果你需要 net/http 中间件、使用标准库 HTTP 客户端对接同一服务,或者工作负载的瓶颈是数据库 I/O 而不是 HTTP 解析,这个提升就会消失。
Plumego 基准
Trie 路由器无反射、无全局状态,设计目标是与 net/http 性能相当。与 Gin、Echo、Chi 的可重现基准尚未发布,请关注 issue tracker 中的基准路线图。
通用建议
大多数 Go HTTP 服务的瓶颈在数据库往返、外部 API 调用或序列化——而不是路由器。 没有针对你的具体工作负载和目标规模的测量数据,纯粹基于性能选择框架很难站住脚。
切换框架的决策不只是 API 人机工程学问题——还涉及 handler 改写、中间件替换和测试基础设施的调整。 下表对从各框架迁移到 Plumego 所需工作量给出诚实估算。
迁移成本以现有 handler 和中间件的服务为基准。
从 reference/standard-service 全新搭建的服务没有迁移成本。
使用 go test -bench -benchmem -count=3 实测的顺序与并发调度数据。
路由:GET /users/:id,无中间件。linux/amd64,Intel Xeon @ 2.10 GHz。
包含完整 httptest 请求/响应周期。相对列以 Plumego 为基准(1.0×)。
路由开销很少是真正的瓶颈——数据库调用耗时 500 µs–5 ms,路由仅 1–5 µs。 选择框架时,以团队真正关注的因素为准。
http.ServeMux + PathValue())开销来源:per-request context 注入(480 B,5 次分配)、参数映射构建以及中间件链接线。对于耗时 1 ms 的 I/O handler,路由开销低于请求总耗时的 0.1%。
| 测试场景 | stdlib ns/op | Plumego ns/op | 倍数 |
|---|---|---|---|
| 静态路由 | 110 | 367 | 3.3× |
| 单 :param + 读取 | 186 | 807 | 4.3× |
| 四 :param 深路径 | 722 | 1036 | 1.4× |
| 70 条路由混合表 | 247 | 660 | 2.7× |
四参数深路径场景(1.4×)说明:随着路径复杂度增加,开销收敛——trie 遍历摊销到了更多工作量上。
复现:go test -bench=BenchmarkRouter -benchmem -count=3 ./reference/benchmark/...
| 框架 | ns/op | allocs/op | B/op | vs Plumego |
|---|---|---|---|---|
| Plumego 本项 | 4291 | 16 | 5715 | 1.0× |
| Chi | 4547 | 17 | 6036 | 1.06× |
| Gin 更快 | 3773 | 13 | 5331 | 0.88× |
| Echo 更快 | 3743 | 13 | 5331 | 0.87× |
| 框架 | ns/op | allocs/op | B/op | vs Plumego |
|---|---|---|---|---|
| Plumego 本项 | 663 | 7 | 592 | 1.0× |
| Chi | 1013 | 8 | 912 | 1.53× |
| Gin 更快 | 285 | 4 | 208 | 0.43× |
| Echo 更快 | 231 | 4 | 208 | 0.35× |
在同样兼容标准库的框架中(Chi 和 Plumego),Plumego 并发吞吐量领先 35%(663 vs 1,013 ns/op)。
Gin 和 Echo 在此测试中比 Plumego 快 ~2.5–3×,因为 sync.Pool context 消除了每次请求的内存分配——代价是使用与标准库中间件不兼容的自定义 context 类型。
如果 stdlib 兼容性对你的团队重要,最有意义的对比对象是 Chi,而不是 Gin 或 Echo。
复现方法:cd reference/benchmark && go test -bench=. -benchmem -count=3 ./...
如果 Plumego 符合你团队的工作模式,从 reference app 开始。如果另一个框架更合适,那就是正确的选择——这页存在是为了帮你决策,不是为了推销。
还不确定是否适合? 阅读诚实的权衡评估 →