自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Node.js 服務(wù)性能翻倍的秘密(二)

開(kāi)發(fā) 前端
前一篇文章介紹了 fastify 通過(guò) schema 來(lái)序列化 JSON,為 Node.js 服務(wù)提升性能的方法。今天的文章會(huì)介紹 fastify 使用的路由庫(kù),翻閱其源碼(lib/route.js)可以發(fā)現(xiàn),fastify 的路由庫(kù)并不是內(nèi)置的,而是使用了一個(gè)叫做 find-my-way 的路由庫(kù)。

[[360400]]

前言

前一篇文章介紹了 fastify 通過(guò) schema 來(lái)序列化 JSON,為 Node.js 服務(wù)提升性能的方法。今天的文章會(huì)介紹 fastify 使用的路由庫(kù),翻閱其源碼(lib/route.js)可以發(fā)現(xiàn),fastify 的路由庫(kù)并不是內(nèi)置的,而是使用了一個(gè)叫做 find-my-way 的路由庫(kù)。

route.js

這個(gè)路由庫(kù)的簡(jiǎn)介也很有意思,號(hào)稱“超級(jí)無(wú)敵快”的 HTTP 路由。

README

看上去 fastify 像是依賴了第三方的路由庫(kù),其實(shí)這兩個(gè)庫(kù)的作者是同一批人。

author

如何使用find-my-way 通過(guò) on 方法綁定路由,并且提供了 HTTP 所有方法的簡(jiǎn)寫。

  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. })) 

其實(shí)內(nèi)部就是通過(guò)遍歷所有的 HTTP 方法名,然后在原型上擴(kuò)展的。

  1. Router.prototype.on = function on (method, path, opts, handler) { 
  2.   if (typeof opts === 'function') { 
  3.     // 如果 opts 為函數(shù),表示此時(shí)的 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.   // 擴(kuò)展方法簡(jiǎn)寫 
  12.   Router.prototype[methodName] = function (path, handler) { 
  13.     return this.on(m, path, handler) 
  14.   } 

綁定的路由可以通過(guò) lookup 調(diào)用,只要將原生的 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 會(huì)通過(guò) req.method/req.url 找到對(duì)應(yīng)的 handler,然后進(jìn)行調(diào)用。

  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.   // 調(diào)用 hendler 
  7.   return handle.handler(req, res, handle.params) 

路由的添加和查找都基于樹(shù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)的,下面我們來(lái)看看具體的實(shí)現(xiàn)。

Radix Tree

find-my-way 采用了名為 Radix Tree(基數(shù)樹(shù)) 的算法,也被稱為 Prefix Tree(前綴樹(shù))。Go 語(yǔ)言里常用的 web 框架echo和gin都使用了Radix Tree作為路由查找的算法。

  • 在計(jì)算機(jī)科學(xué)中,基數(shù)樹(shù),或稱壓縮前綴樹(shù),是一種更節(jié)省空間的Trie(前綴樹(shù))。對(duì)于基數(shù)樹(shù)的每個(gè)節(jié)點(diǎn),如果該節(jié)點(diǎn)是確定的子樹(shù)的話,就和父節(jié)點(diǎn)合并。

Radix Tree

在 find-my-way 中每個(gè) HTTP 方法(GET、POST、PUT ...)都會(huì)對(duì)應(yīng)一棵前綴樹(shù)。

  1. // 方法有所簡(jiǎn)化... 
  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 為函數(shù),表示此時(shí)的 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.   // 調(diào)用 _insert 方法 
  20.   this._insert(method, path, handler) 
  21.  
  22. Router.prototype._insert = function _insert (method, path, handler) { 
  23.   // 取出方法對(duì)應(yīng)的 tree 
  24.   var currentNode = this.trees[method] 
  25.   if (typeof currentNode === 'undefined') { 
  26.     // 首次插入構(gòu)造一個(gè)新的 Tree 
  27.     currentNode = new Node({ method }) 
  28.     this.trees[method] = currentNode 
  29.   } 
  30.   while(true) { 
  31.     // 為 currentNode 插入新的節(jié)點(diǎn)... 
  32.   } 

每個(gè)方法對(duì)應(yīng)的樹(shù)在第一次獲取不存在的時(shí)候,都會(huì)先創(chuàng)建一個(gè)根節(jié)點(diǎn),根節(jié)點(diǎn)使用默認(rèn)字符(/)。

trees

每個(gè)節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)如下:

  1. // 只保留了一些重要參數(shù),其他的暫時(shí)忽略 
  2. function Node(options) { 
  3.   options = options || {} 
  4.   this.prefix = options.prefix || '/' // 去除公共前綴之后的字符,默認(rèn)為 / 
  5.   this.label = this.prefix[0]         // 用于存放其第一個(gè)字符 
  6.   this.method = options.method        // 請(qǐng)求的方法 
  7.   this.handler = options.handler      // 請(qǐng)求的回調(diào) 
  8.   this.children = options.children || {} // 存放后續(xù)的子節(jié)點(diǎn) 

當(dāng)我們插入了幾個(gè)路由節(jié)點(diǎn)后,樹(shù)結(jié)構(gòu)的具體構(gòu)造如下:

  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

如果我們綁定一個(gè)名為 /axxx 的路由,為了節(jié)約內(nèi)存,不會(huì)生成三個(gè) label 為x 的節(jié)點(diǎn),只會(huì)生成一個(gè)節(jié)點(diǎn),其 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

插入路由節(jié)點(diǎn)

通過(guò)之前的代碼可以看到, on 方法最后會(huì)調(diào)用內(nèi)部的 _insert 方法插入新的節(jié)點(diǎn),下面看看其具體的實(shí)現(xiàn)方式:

  1. Router.prototype._insert = function _insert (method, path, handler) { 
  2.   // 取出方法對(duì)應(yīng)的 tree 
  3.   var currentNode = this.trees[method] 
  4.   if (typeof currentNode === 'undefined') { 
  5.     // 首次插入構(gòu)造一個(gè)新的 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.       // 公共前綴存在,復(fù)用 
  23.       currentNode = node 
  24.       continue 
  25.     } 
  26.     // 公共前綴不存在,創(chuàng)建一個(gè) 
  27.     node = new Node({ method: method, prefix: path }) 
  28.     currentNode.addChild(node) 
  29.   } 

插入節(jié)點(diǎn)會(huì)調(diào)用 Node 原型上的 addChild 方法。

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

本質(zhì)是遍歷路徑的每個(gè)字符,然后判斷當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn)是否已經(jīng)存在一個(gè)節(jié)點(diǎn),如果存在就繼續(xù)向下遍歷,如果不存在,則新建一個(gè)節(jié)點(diǎn),插入到當(dāng)前節(jié)點(diǎn)。

圖片

tree

查找路由節(jié)點(diǎn)

find-my-way 對(duì)外提供了 lookup 方法,用于查找路由對(duì)應(yīng)的方法并執(zhí)行,內(nèi)部是通過(guò) 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.     // 繼續(xù)向下查找 
  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 

查找節(jié)點(diǎn)也是通過(guò)遍歷樹(shù)的方式完成的,找到節(jié)點(diǎn)之后還需要放到 handle 是否存在,存在的話需要執(zhí)行回調(diào)。

總結(jié)

本文主要介紹了 fastify 的路由庫(kù)通過(guò) Radix Tree 進(jìn)行提速的思路,相比于其他的路由庫(kù)通過(guò)正則匹配(例如 koa-router 就是通過(guò) path-to-regexp 來(lái)解析路徑的),效率上還是高很多的。

 

責(zé)任編輯:姜華 來(lái)源: 更了不起的前端
相關(guān)推薦

2020-12-14 15:40:59

Nodefastifyjs

2020-12-14 08:55:00

Node.js服務(wù)性框架

2019-07-09 14:50:15

Node.js前端工具

2013-11-01 09:34:56

Node.js技術(shù)

2015-03-10 10:59:18

Node.js開(kāi)發(fā)指南基礎(chǔ)介紹

2022-08-28 16:30:34

Node.jsDocker指令

2020-05-29 15:33:28

Node.js框架JavaScript

2012-02-03 09:25:39

Node.js

2021-12-25 22:29:57

Node.js 微任務(wù)處理事件循環(huán)

2020-10-12 08:06:28

HTTP 服務(wù)器證書

2022-08-22 07:26:32

Node.js微服務(wù)架構(gòu)

2015-12-14 10:39:14

2015-11-04 09:18:41

Node.js應(yīng)用性能

2011-09-09 14:23:13

Node.js

2011-11-01 10:30:36

Node.js

2011-09-08 13:46:14

node.js

2011-09-02 14:47:48

Node

2012-10-24 14:56:30

IBMdw

2011-11-10 08:55:00

Node.js

2022-09-12 15:58:50

node.js微服務(wù)Web
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)