Hono 路由基准测试指南:RegExpRouter / TrieRouter 与主流 HTTP 路由器的同场竞技
2026/9/10 12:45:12 网站建设 项目流程

Hono 路由基准测试指南:RegExpRouter / TrieRouter 与主流 HTTP 路由器的同场竞技

【免费下载链接】honoWeb framework built on Web Standards项目地址: https://gitcode.com/GitHub_Trending/ho/hono

本指南以 Hono 仓库中的 benchmarks/routers/README.md 为核心,系统讲解 Hono 自带的路由基准测试项目的完整结构:如何安装依赖、如何在 Node.js 与 Bun 下运行基准、测试覆盖了哪些路由器与哪些路由形态,以及 Hono 三大内置路由器(RegExpRouter、TrieRouter、PatternRouter)在测试中的接入方式与底层实现。读完本文,你将能够独立复现该基准、读懂其输出,并理解 Hono 路由匹配机制与 SmartRouter 自动选型原理。

一、这个基准测试在测什么

HTTP 路由是 Web 框架的核心部件:每次请求到来,框架都要根据请求方法与 URL 路径找到对应的处理器。路由匹配的速度直接影响框架的整体吞吐,因此对路由器做基准测试是衡量框架性能的重要环节。

Hono 仓库中的benchmarks/routers目录提供了一个独立的基准测试项目,目标非常聚焦:在完全相同的路由集合与请求样本下,对比当前最常用的 HTTP 路由器的匹配性能。README 中列出的被测对象包括:

  • find-my-way(Fastify 使用的路由器)
  • express(Express 自带路由)
  • koa-router
  • koa-tree-router
  • trek-router
  • @medley/router
  • Hono RegExpRouter(正则表达式路由)
  • Hono TrieRouter(字典树路由)

README 还特别注明,该基准项目深受 delvedor/router-benchmark 的启发,采用 MIT 许可证发布。

需要说明的是,README 中列出的清单是 8 个路由器,而实际基准脚本 src/bench.mts 中注册了 12 个被测对象——除了上述对象外,还加入了 Hono 的第三个内置路由器 PatternRouter,以及 radix3、memoirist、rou3 三个新兴路由器。README 维护滞后于脚本演进,这是从源码结构可以推断的细节。

二、安装与运行:三个命令跑起来

该基准项目使用 Bun 作为包管理与脚本运行工具,运行方式非常简洁。在benchmarks/routers目录下依次执行:

# 1. 安装依赖(锁定版本,保证结果可复现) bun install --frozen-lockfile # 2. 在 Node.js 运行时下运行基准 bun run bench:node # 3. 在 Bun 运行时下运行基准 bun run bench:bun

对应的脚本定义在 benchmarks/routers/package.json 中:

{ "scripts": { "bench:node": "tsx ./src/bench.mts", "bench:bun": "bun run ./src/bench.mts", "bench-includes-init:node": "tsx ./src/bench-includes-init.mts", "bench-includes-init:bun": "bun run ./src/bench-includes-init.mts" } }

可以看到:

  • bench:node通过tsx在 Node.js 下直接执行 TypeScript 编写的基准脚本src/bench.mts
  • bench:bun则用 Bun 自身的运行时执行同一份脚本;
  • 项目还提供了bench-includes-init:*两个变体脚本,用于测量把路由注册(初始化)时间一并计入的基准,下文第五节会详细展开。

这种双运行时设计是刻意为之:同一份基准在 Node.js 与 Bun 下各跑一遍,可以对比同一批路由器在不同运行时上的表现差异。依赖清单(见 package.json)中,被测路由器全部以 npm 包形式引入,基准框架选用的是轻量的 mitata,开发依赖仅有tsx(用于 Node 下运行 TypeScript)。

三、基准数据:12 条路由、7 类场景

3.1 路由注册集(路由表)

所有路由器在开始测速前,都要先注册同一份路由表。这份路由表定义在 src/tool.mts 中,共 12 条路由,覆盖了真实业务中常见的路由形态:

export const routes: Route[] = [ { method: 'GET', path: '/user' }, { method: 'GET', path: '/user/comments' }, { method: 'GET', path: '/user/avatar' }, { method: 'GET', path: '/user/lookup/username/:username' }, { method: 'GET', path: '/user/lookup/email/:address' }, { method: 'GET', path: '/event/:id' }, { method: 'GET', path: '/event/:id/comments' }, { method: 'POST', path: '/event/:id/comment' }, { method: 'GET', path: '/map/:location/events' }, { method: 'GET', path: '/status' }, { method: 'GET', path: '/very/deeply/nested/route/hello/there' }, { method: 'GET', path: '/static/*' }, ]

这份路由表刻意设计了多种匹配难度:短静态路径(/user)、共享前缀的静态路径(/user/comments/user/avatar)、带命名参数的动态路径(:id:username)、深层嵌套的长静态路径,以及通配符路径(/static/*)。Route类型限定了方法仅为GET | POSTRouterInterface则统一了所有被测对象的接口——每个路由器只需暴露namematch(route)两个成员,见 src/tool.mts。

3.2 匹配场景(请求样本)

基准脚本 src/bench.mts 定义了 7 组请求场景,每组都是对路由表的"命中式"访问:

场景名请求命中路由
short staticGET /user/user
static with same radixGET /user/comments/user/comments
dynamic routeGET /user/lookup/username/hey/user/lookup/username/:username
mixed static dynamicGET /event/abcd1234/comments/event/:id/comments
postPOST /event/abcd1234/comment/event/:id/comment
long staticGET /very/deeply/nested/route/hello/there长静态路径
wildcardGET /static/index.html/static/*

每组场景下,12 个路由器都会被逐一测量匹配耗时。基准末尾还有一个 "all together" 分组(src/bench.mts),在单次迭代中依次匹配全部 7 组请求,衡量路由器面对混合流量时的综合表现。

3.3 测量框架

基准使用 mitata 驱动:summary+group+bench三层结构组织场景,run()启动测量。每个被测路由器都被封装成RouterInterface,其match只做纯匹配、不做任何处理器调用(handler是一个空函数,见 src/tool.mts),从而把"路由匹配本身"与"业务处理"彻底解耦,保证对比的是纯路由查找性能。

一个值得注意的细节:脚本开头对medleyRouter单独做了一次预热调用(medleyRouter.match({ method: 'GET', path: '/user' }),见 src/bench.mts),因为 @medley/router 是惰性编译的,首次查找会触发内部索引构建,预热可避免把初始化开销混入测速结果。

四、12 个被测路由器是如何接入的

每个路由器都有一个独立的适配文件,负责把各自不同的注册/查找 API 统一到RouterInterface上。这些适配文件同时是了解各家路由器 API 差异的绝佳材料。

4.1 Hono 三兄弟:RegExpRouter / TrieRouter / PatternRouter

src/hono.mts 从仓库源码直接引入 Hono 的三个内置路由器实现:

import { PatternRouter } from '../../../src/router/pattern-router/index.ts' import { RegExpRouter } from '../../../src/router/reg-exp-router/index.ts' import { TrieRouter } from '../../../src/router/trie-router/index.ts'

然后通过createHonoRouter工厂函数,用router.add(route.method, route.path, handler)注册路由、用router.match(route.method, route.path)执行匹配,统一命名并导出:

export const regExpRouter = createHonoRouter('RegExpRouter', new RegExpRouter()) export const trieRouter = createHonoRouter('TrieRouter', new TrieRouter()) export const patternRouter = createHonoRouter('PatternRouter', new PatternRouter())

这三个路由器的设计思路可以从源码中一窥究竟:

  • RegExpRouter(src/router/reg-exp-router/):构建阶段把所有路由编译成一组优化的正则表达式,匹配时用正则一次性定位。它追求的是极致的匹配速度,但注册阶段成本较高;
  • TrieRouter(src/router/trie-router/):基于字典树(radix tree)结构逐段匹配路径,注册成本低,匹配性能稳定,动态参数支持灵活;
  • PatternRouter(src/router/pattern-router/):以路径模式为键做映射查找,实现最为简单直接。

三个路由器实现了 Hono 统一的 Router 接口,add/match的签名完全一致,所以基准脚本可以用完全相同的代码驱动它们——这正是 Hono 路由层可插拔设计的直接体现。

4.2 第三方路由器适配要点

其他路由器的适配文件各自处理了 API 差异,其中几个细节尤其能体现各家实现的特点:

  • express(src/express.mts):Express 的路由并不存在独立的"查找"接口,适配层只能模拟一次完整的handle()调用。它的name被明确标注为'express (WARNING: includes handling)',提醒读者:Express 的测量结果包含了部分请求处理开销,与其他路由器不是严格同口径。另外,由于 Express 5 使用的 path-to-regexp v8 要求通配符必须命名,注册时代码会把/*替换为/*splat
  • koa-router(src/koa-router.mts):同样受 path-to-regexp v8 约束做了通配符替换;其match()返回的是匹配结果(仅匹配,不含处理),与 express 的测量口径不同;
  • koa-tree-router(src/koa-tree-router.mts):采用on()/find()的 radix tree API,通配符被替换为*foo
  • trek-router(src/trek-router.mts):add()/find()API,且handler()在注册时就被调用了一次并传入返回值;
  • @medley/router(src/medley-router.mts):先register(path)拿到按方法存放处理器的store,再把处理器按方法写入;查找时find(path)后从store中按方法取值;
  • find-my-way(src/find-my-way.mts):on()/find()API,与 Fastify 生产环境同款实现。

这些适配细节说明:基准虽然用统一的接口驱动所有路由器,但每个适配器都在忠实还原该路由器"真实场景下的最小匹配路径",尽可能做到公平对比。

五、包含初始化的基准:注册成本同样重要

纯匹配基准只回答了"路由表建好之后查得快不快",但实际应用中,路由表的构建成本同样不可忽视——尤其是对 Hono 这种"按需编译"的框架。为此项目提供了第二个基准脚本 src/bench-includes-init.mts。

该脚本与主基准的核心区别在于:每次迭代都先完整注册 12 条路由、再执行一次匹配,即把路由注册(初始化)时间计入总耗时。被测对象包括:

  • RegExpRouter(每次迭代现场new并注册全部路由)
  • PreparedRegExpRouter(复用预构建的编译参数,见下方说明)
  • TrieRouter
  • LinearRouter(src/router/linear-router/ 中的线性扫描路由器)
  • MedleyRouter、FindMyWay、KoaTreeRouter、TrekRouter 等第三方实现

脚本开头的buildInitParams值得特别关注:

const preparedParams = buildInitParams({ paths: routes.map((r) => r.path), })

它来自 src/router/reg-exp-router/index.ts,作用是把路由表中所有路径预编译为共享的正则参数。PreparedRegExpRouter在构造时直接接收这些预编译参数,省去了每次构建时重复编译路径的开销——这是 Hono 路由层在初始化性能上的一个优化点,也解释了为什么 Hono 框架本身可以在请求到达时才构建路由器:SmartRouter 会延迟到首次匹配时才确定并构建实际使用的路由器。

六、从基准到生产:SmartRouter 的自动选型

基准脚本手动实例化了三个 Hono 路由器,而在 Hono 框架的实际使用中,路由器的选择由SmartRouter自动完成。理解基准中三个路由器的差异后,再看 src/router/smart-router/router.ts 会格外清晰:

export class SmartRouter<T> implements Router<T> { #routers: Router<T>[] = [] #routes?: [string, string, T][] = [] match(method: string, path: string): Result<T> { const routers = this.#routers const routes = this.#routes const len = routers.length let i = 0 let res for (; i < len; i++) { const router = routers[i] try { for (let i = 0, len = routes.length; i < len; i++) { router.add(...routes[i]) } res = router.match(method, path) } catch (e) { if (e instanceof UnsupportedPathError) { continue } throw e } this.match = router.match.bind(router) this.#routers = [router] this.#routes = undefined break } ... this.name = `SmartRouter + ${this.activeRouter.name}` return res as Result<T> } }

从源码结构可以推断 SmartRouter 的工作机制:

  1. 注册阶段,所有路由先暂存在内部数组中;
  2. 首次收到匹配请求时,按优先级依次尝试把路由灌入候选路由器(通常是 RegExpRouter 优先);
  3. 如果某个路由器抛出了UnsupportedPathError(说明存在它无法表达的路由模式,例如 RegExpRouter 不支持的复杂路径),则跳过它尝试下一个;
  4. 一旦某个路由器成功完成注册与首次匹配,SmartRouter 就把自己的match方法绑定为该路由器的match,后续请求直接走选定路由器,选型开销只发生一次。

也就是说,Hono 的默认路由层结合了"RegExpRouter 的速度"与"TrieRouter 的兼容性兜底",而基准中的 RegExpRouter、TrieRouter、PatternRouter 正是 SmartRouter 候选池中的成员——这正是为什么本基准对 Hono 使用者有直接参考价值:不同路由器在 7 类路由场景下的相对表现,直接影响框架在实际路由表上的匹配性能。

七、运行与解读基准的注意事项

  1. 先装 Bun:本基准以 Bun 为运行基础(bun installbun run),Node 运行模式只是借助tsx加载器执行同一份.mts脚本;
  2. 结果的可比性:同一份基准建议在 Node.js 与 Bun 下各跑一遍再比较;跨场景比较时要留意 express 的口径差异(含处理开销);
  3. 输出解读:mitata 会按场景分组输出各路由器每次匹配的平均耗时与 ops/sec,数值越低/越高代表匹配越快;"all together" 分组看的是混合流量下的综合表现;
  4. 扩展到自己的路由表:如果你关心的是 Hono 在自己业务路由表上的表现,可以直接修改 src/tool.mts 中的routes数组,替换为业务真实路由后重跑基准;
  5. 底层源码研读入口:三个路由器的实现分别在 src/router/reg-exp-router/、src/router/trie-router/、src/router/pattern-router/;线性扫描版本在 src/router/linear-router/;路由统一接口定义在 src/router.ts;UnsupportedPathError的触发场景可以在 src/router/reg-exp-router/router.ts 中检索到。

八、小结

benchmarks/routers是一个麻雀虽小、五脏俱全的路由性能对比项目:它以一份精心设计的路由表为公共输入,用统一的接口驱动 12 个路由器,在 7 类请求场景下分别测量纯匹配耗时与"含注册"的完整耗时,并支持在 Node.js 与 Bun 双运行时下复现。对 Hono 使用者而言,它不仅是理解 RegExpRouter / TrieRouter / PatternRouter 性能特性的最佳入口,也是理解 SmartRouter 自动选型逻辑(先快后稳、一次定型的延迟构建策略)的直观注脚。项目采用 MIT 许可证,可以放心参考其基准脚本设计思路,用于构建你自己的路由性能评估流程。

【免费下载链接】honoWeb framework built on Web Standards项目地址: https://gitcode.com/GitHub_Trending/ho/hono

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询