fiber采用radix树(压缩前缀树)实现路由匹配,时间复杂度稳定为o(k)(k为路径长度),10万路由下比gin快40%、内存低65%;支持参数节点与正则校验,strictrouting默认区分尾部斜杠。

Radix树是Fiber路由匹配的底层数据结构
Fiber不用正则、也不用线性遍历,它把所有注册路由构建成一棵压缩前缀树(Radix Tree),路径查找时间复杂度稳定在 O(k)(k 是路径长度),而不是路由数量。这意味着即使你注册了 10 万条路由,匹配一个 /api/v2/users/123 请求,也只比匹配 / 多走几步指针,不会变慢。
它和 Gin 的普通前缀树不同:Radix 树会合并连续无分支的路径段(比如 /api/v1 和 /api/v2 共享 /api/ 节点),节点更少、缓存更友好。实测在 10 万路由下,Fiber 比 Gin 匹配快 40%,内存占用低 65%。
你不需要手动操作这棵树——app.Get()、app.Post() 等方法内部自动调用 router.Add(),再经由 routeParser 解析路径并插入 Radix 树对应位置。
路径参数和正则路由怎么塞进Radix树里
Radix 树原生不支持通配符或正则,但 Fiber 在节点上做了扩展:每个节点额外携带一个 Params 字段([]string)和一个 routeParser 实例。当匹配到 /user/:id 这种带参数的路径时,Fiber 并不是靠正则去“扫描”,而是:
- 把
:id当作一个特殊子节点类型(类似param节点),挂载在/user/后面 - 遇到请求
/user/42,先精确匹配/user/,再进入该 param 节点,把42提取为id值,存入c.Params("id") - 正则路由如
/file/:name([a-zA-Z0-9]+)也是同理:解析器提前编译好正则,匹配 param 节点后额外校验字符串是否符合模式
注意:/user/:id 和 /user/:id? 是两种不同节点类型(前者必选,后者可选),它们在树中是分开存储的,不是靠运行时判断。
StrictRouting=true 为什么会让 /users 和 /users/ 变成两个路由
因为 Radix 树把路径当作完整字符串来建模。/users 和 /users/ 是两个不同的叶子路径,在树里就是两个独立节点。StrictRouting 默认开启,意味着框架拒绝自动补/删尾部斜杠——它不认为二者等价,也不做归一化。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
常见踩坑场景:
- 你只注册了
app.Get("/users", handler),但前端发的是GET /users/→ 404 - 你同时注册了
/users和/users/:id,又关了StrictRouting→ 请求/users/123可能被/users拦截(因树中/users/成了前缀),导致:id拿不到
解决方式不是简单关掉 StrictRouting,而是明确设计:要么统一注册 /users 和 /users/,要么加中间件做 301 重定向(比如把 /xxx/ → /xxx)。
ctx.Params() 和 ctx.Query() 底层来源完全不同
c.Params("id") 的值来自 Radix 树匹配过程中提取的路径段,是 routeParser 在进入 param 节点时直接拷贝的字节切片(零分配);而 c.Query("page") 是从 URL 查询字符串(?page=2&sort=asc)里 parse 出来的,走的是 fasthttp 内置的 Args 解析逻辑,两者完全不共享内存或缓存。
所以别指望 c.Params() 能拿到查询参数,也别用 strconv.Atoi(c.Params("id")) 做高频转换——虽然它快,但每次调用仍触发一次字符串转整数,若 ID 频繁使用,建议在中间件里一次性解析并存到 c.Locals。
真正影响性能的不是 API 名,而是你是否在 handler 里反复调用 c.Body() 或 c.FormValue():这些方法内部可能触发内存拷贝或解析,破坏 fasthttp 的零分配优势。










