教你编写Node.js中间件,实现服务端缓存

Express 作为 Node.js
的框架,如今发展可谓如日中天。我很喜欢其灵活、易扩展的设计理念。尤其是该框架的中间件架构设计:使得在应用中加入新特性更加标准化、成本最小化。这篇文章,我会尝试编写一个非常简单、小巧的中间件,完成服务端缓存功能,进而优化性能。

关于中间件

说到中间件,Express 官网对它的阐述是这样的:

“Express 是一个自身功能极简,完全是路由和中间件构成一个web开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件。”

也许你使用过各种各样的中间件进行开发,但是可能并不理解中间件原理,也没有深入过 Express 源码,探究其实现。这里并不打算长篇大论帮您分析,但是使用层面上大致可以参考下图:

建议有兴趣、想深入的读者自己分析,有任何问题欢迎与我讨论。即便您不打算深入,也不会影响对下文中间件编写的理解。

关于服务端缓存

缓存已经被广泛应用,来提高页面性能。一说到缓存,可能读者脑海里马上冒出来:“客户端缓存,CDN 缓存,服务器端缓存......”。另一维度上,也会想到:“200(from cache),expire,eTag......”等概念。

当然作为前端开发者,我们一定要明白这些缓存概念,这些缓存理念是相对于某个具体用户访问来说的,性能优化体现在单个用户上。比如说,我第一次打开页面 A,耗时超长,下一次打开页面由于缓存的作用,时间缩短了。

但是在服务器端,还存在另外一个维度,思考一下这样的场景:

我们有一个静态页面 B,这个页面服务端需要从数据库获取部分数据 b1,根据 b1 又要计算得到部分数据 b2,还得做各种高复杂度操作,最终才能“东拼西凑”出需要返回的完整页面 B,整个过程耗时2s。

那么面临的灾难就是,user1 打开页面耗时2s,user2同样打开页面耗时2s......而这些页面都是静态页面 B,内容是完全一样的。为了解决这个灾难,这时候我们也需要缓存,这种缓存就叫先做服务端缓存(server-side cache)。

总结一下,服务端缓存的目的其实就是对于同一个页面请求,而返回(缓存的)同样的页面内容。这个过程完全独立于不同的用户。

上面的话有些拗口,可以参考英文表达更清晰:

The goal of server side cache is responding to the same content for the same request independently of the client’s request.

因此,下面展示的 demo 在第一次请求到达时,服务端耗费5秒来返回 HTML;而接下来再次请求该页面,将会命中缓存,不过是哪个用户访问,只需要几毫秒便可得到完整页面。

Show me the code & Demo

其实上文提到的缓存概念非常简单,稍微有些后端经验的同学都能很好理解。但是这篇文章除去科普基本概念外,更重要的就是介绍 Express 中间件思想,并自己来实现一个服务端缓存中间件。

让我们开工吧!

最终 Demo 代码,欢迎访问它的Github地址。

我将会使用 npm 上 memory-cache 这个包,以方便进行缓存的读写。最终的中间件代码很简单:


  1. 'use strict' 
  2.  
  3. var mcache = require('memory-cache'); 
  4.  
  5. var cache = (duration) => { 
  6.   return (req, res, next) => { 
  7.     let key = '__express__' + req.originalUrl || req.url 
  8.     let cachedBody = mcache.get(key) 
  9.     if (cachedBody) { 
  10.       res.send(cachedBody) 
  11.       return 
  12.     } else { 
  13.       res.sendResponse = res.send 
  14.       res.send = (body) => { 
  15.         mcache.put(key, body, duration * 1000); 
  16.         res.sendResponse(body) 
  17.       } 
  18.       next() 
  19.     } 
  20.   } 
  21. }  

为了简单,我使用了请求 URL 作为 cache 的 key:

  • 当它(cache key)及其对应的 value 值存在时,便直接返回其 value 值;
  • 当它(cache key)及其对应的 value 值不存在时,我们将对 Express send 方法做一层拦截:在最终返回前,存入这对 key-value。

缓存的有效时间是10秒。

最终在判断之外,我们的中间件把控制权交给下一个中间件。

最终使用和测试如下代码:


  1. app.get('/', cache(10), (req, res) => { 
  2.   setTimeout(() => { 
  3.     res.render('index', { title: 'Hey', message: 'Hello there', date: new Date()}) 
  4.   }, 5000) //setTimeout was used to simulate a slow processing request 
  5. })  

我使用了 setTimeout 来模拟一个超长(5s)的操作。

打开浏览器控制面板,发现在10秒缓存到期以内:

至于为什么 cache 中间件要那样子写、next() 为什么是中间件把控制权传递,我并不打算展开去讲。有兴趣的读者可以看一下 Express 源码。

还有几个小问题

仔细看我们的页面,再去体会一下实现代码。也许细心的读者能发现一个问题:刚才的实现我们缓存了整个页面,并将 date: new Date()
传入了 jade 模版 index.jade 里。那么,在命中缓存的条件下,10秒内,页面无法动态刷新来同步,直到10秒缓存到期。

同时,我们什么时候可以使用上述中间件,进行服务端缓存呢?当然是静态内容才可以使用。同时,PUT, DELETE 和 POST 操作都不应该进行类似的缓存处理。

同样,我们使用了 npm 模块:memory-cache,它存在优缺点如下:

  • 读写迅速而简单,不需要其他依赖;
  • 当服务器或者这个进程挂掉的时候,缓存中的内容将会全部丢失。
  • memcache 是将缓存内容存放在了自己进程的内存中,所以这部分内容是无法在多个 Node.js 进程之间共享的。

如果这些弊端 really matter,在实际开发中我们可以选择分布式的 cache 服务,比如 Redis。同样你可以在 npm 上找到:express-redis-cache 模块使用。

总结

在真实的开发场景中,服务端缓存已经成为 common sense,但是在 Node.js 的世界里,体会其中间件思想,自己手动编写服务,同样乐趣无穷。

与实践相结合,我认为真正缓存整个页面(如同 demo 那样)并不是一个推荐的做法(当时实际场景实际分析),同样使用请求 url 作为缓存的 key 也有待考虑。比如,页面中的一些静态内容可能会在其他页面中重复使用到,复用就成了问题。

作者:lucas_580e331d326b4

来源:51CTO

时间: 2024-11-01 15:53:28

教你编写Node.js中间件,实现服务端缓存的相关文章

把Node.js程序加入服务实现随机启动

  这篇文章主要介绍了把Node.js程序加入服务实现随机启动,本文使用qckwinsvc实现这个需求,讲解了qckwinsvc的安装和使用,需要的朋友可以参考下 如何开机就启动node.js程序 代码如下: npm install -g qckwinsvc 定位到安装目录,node_modules/.bin/ 运行如下命令: 代码如下: > qckwinsvc prompt: Service name: [name for your service] prompt: Service descr

使用coffeescript编写node.js项目的方法汇总_javascript技巧

Node.js 基于JavaScript编写应用,JavaScript是我的主要开发语言.CoffeeScript是编译为JavaScript的编程语言.其实CoffeeScript语言因其可以一对一的翻译为JavaScript的特性,使用起来也非常灵活.将其引入项目的方式也有很多种,在此,我将使用coffeescript编写node.js项目的方法做一个汇总. 直接使用coffee指令运行纯coffeescript项目 一般提起coffeescript,自然而然地会想到他是javascript

把Node.js程序加入服务实现随机启动_node.js

如何开机就启动node.js程序 复制代码 代码如下: npm install -g qckwinsvc 定位到安装目录,node_modules/.bin/ 运行如下命令: 复制代码 代码如下: > qckwinsvc prompt: Service name: [name for your service] prompt: Service description: [description for it] prompt: Node script path: [path of your nod

Node.js+Socket.IO实现的WebSocket群聊天室源码

首先上图上实例 聊天室地址:http://chat.52itstyle.com WebSocket简介 谈到Web实时推送,就不得不说WebSocket.在WebSocket出现之前,很多网站为了实现实时推送技术,通常采用的方案是轮询 (Polling)和Comet技术,Comet又可细分为两种实现方式,一种是长轮询机制,一种称为流技术,这两种方式实际上是对轮询技术的改进,这些 方案带来很明显的缺点,需要由浏览器对服务器发出HTTP request,大量消耗服务器带宽和资源.面对这种状况,HTM

Node.js开发Web后台服务

一.简介 Node.js 是一个基于Google Chrome V8 引擎的 JavaScript 运行环境.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效.Node.js 的包管理器 npm,是全球最大的开源库生态系统. 能方便地搭建响应速度快.易于扩展的网络应用,Node.js 使用事件驱动, 非阻塞I/O 模型而得以轻量和高效,非常适合在分布式设备上运行的数据密集型的实时应用. 官网:https://nodejs.org/en/中文:https://cnode

Node.js编写组件的三种实现方式_node.js

首先介绍使用v8 API跟使用swig框架的不同: (1)v8 API方式为官方提供的原生方法,功能强大而完善,缺点是需要熟悉v8 API,编写起来比较麻烦,是js强相关的,不容易支持其它脚本语言. (2)swig为第三方支持,一个强大的组件开发工具,支持为python.lua.js等多种常见脚本语言生成C++组件包装代码,swig使用者只需要编写C++代码和swig配置文件即可开发各种脚本语言的C++组件,不需要了解各种脚本语言的组件开发框架,缺点是不支持javascript的回调,文档和de

如何在2016年成为一个更好的 Node.js 开发者

如何在2016年成为一个更好的 Node.js 开发者 本文主要讨论一些进行Node.js开发的最佳实践和建议,这些建议不仅仅适合开发者, 还适合那些管理与维护Node.js基础架构的工作人员.遵循本文提供的这些建议, 能够让你更好的进行日常的开发工作. 使用ES2015 在2015年的夏天,ES2015的最终草案(即ES6)正式发布了.该版本为JavaScript语言增加了大量的新的语言特性,主要包括: 箭头函数 模版字符串 不定参数rest operator, argument spread

如何在2016年成为一个更好的Node.js开发者

本文主要讨论一些进行Node.js开发的最佳实践和建议,这些建议不仅仅适合开发者, 还适合那些管理与维护Node.js基础架构的工作人员.遵循本文提供的这些建议, 能够让你更好的进行日常的开发工作. 使用ES2015 在2015年的夏天,ES2015的最终草案(即ES6)正式发布了.该版本为JavaScript语言增加了大量的新的语言特性,主要包括: 箭头函数 模版字符串 不定参数(rest operator), argument spreading 生成器 promises maps, set

服务端数据校验及客户端js脚本验证集成处理初探

一.起源 在项目开发中数据有效性验证肯定是必须的,那么在哪里验证呢!?怎么去验证呢? 针对web项目而言,客户端验证+服务端的验证缺一不可,客户端的脚本验证用于提高用户体验! 服务端的验证主要是为了数据的安全性.合法性的验证! 但是我们在实施这两种验证的时候会发现有以下几个可能出现的问题: 1.客户端代码验证写起来相对比较烦琐,而且易出错!(主要是拼写错误,例如js方法名拼写错误) 2. 服务端的验证和客户端验证要保持一致性一样很烦琐.(客户验证输入不能超过10个字符,那后服 务器端也要相应的控