Node.js 服务性能翻倍的秘密(二)-深圳SEO优化公司
公众号矩阵
移动端
开发 前端
前一篇文章介绍了 fastify 通过 schema 来序列化 JSON,为 Node.js 服务提升性能的方法。今天的文章会介绍 fastify 使用的路由库,翻阅其源码(lib/route.js)可以发现,fastify 的路由库并不是内置的,而是使用了一个叫做 find-my-way 的路由库。

[[360400]]

前言

前一篇文章介绍了 fastify 通过 schema 来序列化 JSON,为 Node.js 服务提升性能的方法。今天的文章会介绍 fastify 使用的路由库,翻阅其源码(lib/route.js)可以发现,fastify 的路由库并不是内置的,而是使用了一个叫做 find-my-way 的路由库。

route.js

这个路由库的简介也很有意思,号称“超级无敌快”的 HTTP 路由。

README

看上去 fastify 像是依赖了第三方的路由库,其实这两个库的作者是同一批人。

author

如何使用find-my-way 通过 on 方法绑定路由,并且提供了 HTTP 所有方法的简写。

  1. const router = require('./index')() 
  2.  
  3. router.on('GET''/a', (req, res, params) => { 
  4.   res.end('{"message": "GET /a"}'
  5. }) 
  6. router.get('/a/b', (req, res, params) => { 
  7.   res.end('{"message": "GET /a/b"}'
  8. })) 

其实内部就是通过遍历所有的 HTTP 方法名,然后在原型上扩展的。

  1. Router.prototype.on = function on (method, path, opts, handler) { 
  2.   if (typeof opts === 'function') { 
  3.     // 如果 opts 为函数,表示此时的 opts 为 handler 
  4.     handler = opts 
  5.     opts = {} 
  6.   } 
  7.   // ... 
  8. for (var i in http.METHODS) { 
  9.   const m = http.METHODS[i] 
  10.   const methodName = m.toLowerCase() 
  11.   // 扩展方法简写 
  12.   Router.prototype[methodName] = function (path, handler) { 
  13.     return this.on(m, path, handler) 
  14.   } 

绑定的路由可以通过 lookup 调用,只要将原生的 req 和 res 传入 lookup 即可。

  1. const http = require('http'
  2.  
  3. const server = http.createServer((req, res) => { 
  4.   // 只要将原生的 req 和 res 传入 lookup 即可 
  5.   router.lookup(req, res) 
  6. }) 
  7.   
  8. server.listen(3000) 

find-my-way 会通过 req.method/req.url 找到对应的 handler,然后进行调用。

  1. Router.prototype.lookup = function lookup (req, res) { 
  2.   var handle = this.find(req.method, sanitizeUrl(req.url)) 
  3.   if (handle === null) { 
  4.     return this._defaultRoute(req, res, ctx) 
  5.   } 
  6.   // 调用 hendler 
  7.   return handle.handler(req, res, handle.params) 

路由的添加和查找都基于树结构来实现的,下面我们来看看具体的实现。

Radix Tree

find-my-way 采用了名为 Radix Tree(基数树) 的算法,也被称为 Prefix Tree(前缀树)。Go 语言里常用的 web 框架echo和gin都使用了Radix Tree作为路由查找的算法。

  • 在计算机科学中,基数树,或称压缩前缀树,是一种更节省空间的Trie(前缀树)。对于基数树的每个节点,如果该节点是确定的子树的话,就和父节点合并。

Radix Tree

在 find-my-way 中每个 HTTP 方法(GET、POST、PUT ...)都会对应一棵前缀树。

  1. // 方法有所简化... 
  2. function Router (opts) { 
  3.   opts = opts || {} 
  4.   this.trees = {} 
  5.   this.routes = [] 
  6.  
  7. Router.prototype.on = function on (method, path, opts, handler) { 
  8.   if (typeof opts === 'function') { 
  9.     // 如果 opts 为函数,表示此时的 opts 为 handler 
  10.     handler = opts 
  11.     opts = {} 
  12.   } 
  13.   this._on(method, path, opts, handler) 
  14.  
  15. Router.prototype._on = function on (method, path, opts, handler) { 
  16.   this.routes.push({ 
  17.     method, path, opts, handler, 
  18.   }) 
  19.   // 调用 _insert 方法 
  20.   this._insert(method, path, handler) 
  21.  
  22. Router.prototype._insert = function _insert (method, path, handler) { 
  23.   // 取出方法对应的 tree 
  24.   var currentNode = this.trees[method] 
  25.   if (typeof currentNode === 'undefined') { 
  26.     // 首次插入构造一个新的 Tree 
  27.     currentNode = new Node({ method }) 
  28.     this.trees[method] = currentNode 
  29.   } 
  30.   while(true) { 
  31.     // 为 currentNode 插入新的节点... 
  32.   } 

每个方法对应的树在第一次获取不存在的时候,都会先创建一个根节点,根节点使用默认字符(/)。

trees

每个节点的数据结构如下:

  1. // 只保留了一些重要参数,其他的暂时忽略 
  2. function Node(options) { 
  3.   options = options || {} 
  4.   this.prefix = options.prefix || '/' // 去除公共前缀之后的字符,默认为 / 
  5.   this.label = this.prefix[0]         // 用于存放其第一个字符 
  6.   this.method = options.method        // 请求的方法 
  7.   this.handler = options.handler      // 请求的回调 
  8.   this.children = options.children || {} // 存放后续的子节点 

当我们插入了几个路由节点后,树结构的具体构造如下:

  1. router.on('GET''/a', (req, res, params) => { 
  2.   res.end('{"message":"hello world"}'
  3. }) 
  4. router.on('GET''/aa', (req, res, params) => { 
  5.   res.end('{"message":"hello world"}'
  6. }) 
  7. router.on('GET''/ab', (req, res, params) => { 
  8.   res.end('{"message":"hello world"}'
  9. }) 

  

GET Tree

  1. Node { 
  2.   label: 'a'
  3.   prefix: 'a'
  4.   method: 'GET'
  5.   children: { 
  6.     a: Node { 
  7.       label: 'a'
  8.       prefix: 'a'
  9.       method: 'GET'
  10.       children: {}, 
  11.       handler: [Function
  12.     }, 
  13.     b: Node { 
  14.       label: 'b'
  15.       prefix: 'b'
  16.       method: 'GET'
  17.       children: {}, 
  18.       handler: [Function
  19.     } 
  20.   }, 
  21.   handler: [Function

如果我们绑定一个名为 /axxx 的路由,为了节约内存,不会生成三个 label 为x 的节点,只会生成一个节点,其 label 为 x,prefix 为 xxx。

  1. router.on('GET''/a', (req, res, params) => { 
  2.   res.end('{"message":"hello world"}'
  3. }) 
  4. router.on('GET''/axxx', (req, res, params) => { 
  5.   res.end('{"message":"hello world"}'
  6. }) 

 

GET Tree

  1. Node { 
  2.   label: 'a'
  3.   prefix: 'a'
  4.   method: 'GET'
  5.   children: { 
  6.     a: Node { 
  7.       label: 'x'
  8.       prefix: 'xxx'
  9.       method: 'GET'
  10.       children: {}, 
  11.       handler: [Function
  12.     } 
  13.   }, 
  14.   handler: [Function

插入路由节点

通过之前的代码可以看到, on 方法最后会调用内部的 _insert 方法插入新的节点,下面看看其具体的实现方式:

  1. Router.prototype._insert = function _insert (method, path, handler) { 
  2.   // 取出方法对应的 tree 
  3.   var currentNode = this.trees[method] 
  4.   if (typeof currentNode === 'undefined') { 
  5.     // 首次插入构造一个新的 Tree 
  6.     currentNode = new Node({ method }) 
  7.     this.trees[method] = currentNode 
  8.   } 
  9.  
  10.   var len = 0 
  11.   var node = null 
  12.   var prefix = '' 
  13.   var prefixLen = 0 
  14.   while(true) { 
  15.     prefix = currentNode.prefix 
  16.     prefixLen = prefix.length 
  17.     len = prefixLen 
  18.     path = path.slice(len) 
  19.     // 查找是否存在公共前缀 
  20.     node = currentNode.findByLabel(path) 
  21.     if (node) { 
  22.       // 公共前缀存在,复用 
  23.       currentNode = node 
  24.       continue 
  25.     } 
  26.     // 公共前缀不存在,创建一个 
  27.     node = new Node({ method: method, prefix: path }) 
  28.     currentNode.addChild(node) 
  29.   } 

插入节点会调用 Node 原型上的 addChild 方法。

  1. Node.prototype.getLabel = function () { 
  2.   return this.prefix[0] 
  3.  
  4. Node.prototype.addChild = function (node) { 
  5.   var label = node.getLabel() // 取出第一个字符做为 label 
  6.   this.children[label] = node 
  7.   return this 

本质是遍历路径的每个字符,然后判断当前节点的子节点是否已经存在一个节点,如果存在就继续向下遍历,如果不存在,则新建一个节点,插入到当前节点。

图片

tree

查找路由节点

find-my-way 对外提供了 lookup 方法,用于查找路由对应的方法并执行,内部是通过 find 方法查找的。

  1. Router.prototype.find = function find (method, path, version) { 
  2.   var currentNode = this.trees[method] 
  3.   if (!currentNode) return null 
  4.  
  5.   while (true) { 
  6.     var pathLen = path.length 
  7.     var prefix = currentNode.prefix 
  8.     var prefixLen = prefix.length 
  9.     var len = prefixLen 
  10.     var previousPath = path 
  11.     // 找到了路由 
  12.     if (pathLen === 0 || path === prefix) { 
  13.       var handle = currentNode.handler 
  14.       if (handle !== null && handle !== undefined) { 
  15.         return { 
  16.           handler: handle.handler 
  17.         } 
  18.       } 
  19.     } 
  20.     // 继续向下查找 
  21.     path = path.slice(len) 
  22.     currentNode = currentNode.findChild(path) 
  23.   } 
  24.  
  25. Node.prototype.findChild = function (path) { 
  26.   var child = this.children[path[0]] 
  27.   if (child !== undefined || child.handler !== null)) { 
  28.     if (path.slice(0, child.prefix.length) === child.prefix) { 
  29.       return child 
  30.     } 
  31.   } 
  32.  
  33.   return null 

查找节点也是通过遍历树的方式完成的,找到节点之后还需要放到 handle 是否存在,存在的话需要执行回调。

总结

本文主要介绍了 fastify 的路由库通过 Radix Tree 进行提速的思路,相比于其他的路由库通过正则匹配(例如 koa-router 就是通过 path-to-regexp 来解析路径的),效率上还是高很多的。

 

责任编辑:姜华 来源: 更了不起的前端
相关推荐

2020-12-14 15:40:59

Nodefastifyjs

2020-12-14 08:55:00

Node.js服务性框架

2019-07-09 14:50:15

Node.js前端工具

2013-11-01 09:34:56

Node.js技术

2015-03-10 10:59:18

Node.js开发指南基础介绍

2022-08-28 16:30:34

Node.jsDocker指令

2020-05-29 15:33:28

Node.js框架JavaScript

2021-12-25 22:29:57

Node.js 微任务处理事件循环

2020-10-12 08:06:28

HTTP 服务器证书

2022-08-22 07:26:32

Node.js微服务架构

2012-02-03 09:25:39

Node.js

2015-12-14 10:39:14

2015-11-04 09:18:41

Node.js应用性能

2011-11-01 10:30:36

Node.js

2011-09-02 14:47:48

Node

2011-09-08 13:46:14

node.js

2011-09-09 14:23:13

Node.js

2012-10-24 14:56:30

IBMdw

2011-11-10 08:55:00

Node.js

2009-11-05 10:45:58

WCF服务
后端
26299内容
全部话题

同话题下的热门内容

2024年优秀Web开发工具发展趋势总结一文搞懂epoll:高效I/O多路复用的核心技术实体到 DTO 转换!这七种方式性能差距太大,最后一个才是王者Java并发编程:深入理解Java线程状态短链跳转不再烦恼!SpringBoot 一键解决方案一文搞懂HashMap如何优雅处理哈希冲突日常工作,MQ的八种常用使用场景C++内存管理优化:打造你的高效内存池

相关专题 更多

2024年第十九届中国企业年终评选榜单揭晓
2024年第十九届中国企业年终评选榜单揭晓
如何发挥数据的最大力量?
如何发挥数据的最大力量?
2024-09-11 10:06:01
戴尔与AMD携手发布新一代服务器解决方案
戴尔与AMD携手发布新一代服务器解决方案
2024-12-24 16:34:07
我收藏的内容
点赞
收藏
分享

51CTO技术栈公众号

业务
速览
在线客服
媒体
51CTO CIOAge HC3i
社区
51CTO博客 鸿蒙开发者社区 AI.x社区
教育
51CTO学堂 精培 企业培训 CTO训练营

相关内容推荐

百度seo官网免费学习seo技术seo优化方案淘宝seo的概念seo相关性seo首页板材SEoseo具体是什么阳光seo长寿seo快排九龙坡SEO罗湖seo快速排名优化快排seo排名软件seo搜索引擎seo和网络推广哪个好网站seo费用seo优化中心seo和sem工资seo优化排名教程文军seoseo排名不知是网站排名sem和seo的区别品牌seo如何进行seo优化seo埋词基础seo学习seo做什么的云客seo付费seoseo的关键词seo关键字排名查询企业网站如何做seo平顶山seo焦大seo谷歌seo好做吗seo的排名优化站长工具seo808长沙seo教程seo免费课程视频seo优化按天收费胜达seoseo要多少钱企业网站seo优化怎么做seo每天的工作内容seo报价自动seo优化1页seo专业seo整站优化seo优化发外链seo建设seo优化的教程希望seo如何优化网站SEOseo引擎seo的博客seo关键词排名工具seo钟涛seo的待遇东营seo诊断公司seo哪里学seo实战培训seo8SEO43写seo赚钱seo工作内容淘宝seo软件上海seo排名优化渝北seo快排做seo搜索优化怎么样南宁seo专员利为汇seo视频教程cdn seoseo公司优化seo网站推广工具台州seo优化seo包括哪些外贸SEO优化技巧seo排名点击器seo快速排名优化电话海东SEOseo引擎优化价格国外seo网站中山百度seo效果中山seo推广优化北京seo营销公司seo优化心得seo白帽黑帽seo技术外包seo插件seo网赚宝典昆明seo服务张鹏阳seoseo搜索引擎优化推广seo piikeeseo策划案综合seo北京seo学习大连seo教学老虎seoseo刷关键词铜陵SEO排名seo优化价格seo高清教程广州网站seo宝安seo排名sem seo区别seo三字经南宁学seoseo教程教程外贸网站seo博客黑帽seo优化seo构建关键词优化排名佳选火28星长沙seo外包公司济宁seo优化公司seo主要是做什么的seo最新教程seo优化是否赚钱易企SEO网址seo西安百度seo优化seo 重庆做seo的方法seo排名工具如何网络渠道郑州seo服务顶峰seo臭seoseo能吃吗快速优化seo软件在线咨询野狼seo怎么用seo优化论坛seo设置seo优化基础教程seo1漫画seo快速上排名seo制作国美seo济南seo小武义乌seo优化学习seo心得SEO项目经理招聘贴吧seo上海seo排名网站seo培训seo网址优化衡水seo新浪博客seo汕尾seoseo网站怎么做seo解说seo从零开始学习seo优化刷哈尔滨seo云优化莆田seo优化网站优化seo方案seo优化管理卢松松seo北京seo推广网站seo排名优化软件批发小云seo山东seo网络培训seo有什么技巧seo前景怎么样seo预处理耒阳seo光明关键词排名成都seo培训seo算法seo满山红杭州谷歌seo公司哪家好a5seo诊断赣州seo排名中山seo排名推广策划如何做网站seoseo推广员seo关键词外包seo站站平湖seo泉州网站seo临沂seo优化修改seoseo培训哪个好西安seo博客优化seo代码鞍山seoseo有前途吗seo7878企业网站seo学习seo需要什么武汉seo公司徐州seo培训天茗seo旺道seo工具怎样做seo优化关键词seo优化软件seo关键词如何设定Seo找客户谷歌seo外链工具seo网站权重seo外链发布关键词seo培训

合作伙伴

深圳SEO优化公司

龙岗网络公司
深圳网站优化
龙岗网站建设
坪山网站建设
百度标王推广
天下网标王
SEO优化按天计费
SEO按天计费系统