当前位置: 首页 > Web前端 > HTML5

手把手教你写Node.js中间件,实现服务端缓存(附demo源码)

时间:2023-04-05 00:02:18 HTML5

Express作为Node.js的框架,现在发展非常迅速。我非常喜欢它灵活且易于扩展的设计理念。尤其是框架的中间件架构设计:使得在应用中添加新功能更加规范,成本最小化。在这篇文章中,我将尝试写一个非常简单小巧的中间件来完成服务器端的缓存功能和性能优化。关于中间件谈到中间件,Express官网是这样解释的:“Express是一个功能极简的Web开发框架,它完全由路由和中间件组成:本质上,一个Express应用就是在调用各种中间件。”可能你使用过各种中间件进行开发,但是你可能不了解中间件的原理,也没有深入Express源码去探究它的实现,这里不打算长篇大论帮你分析,但是在使用层面上可以大致参考下图:建议有兴趣想深入分析的读者自己深入分析,有什么问题可以和我一起讨论,即使你不懂不打算深入,不影响大家理解下面的中间件写法关于服务端缓存缓存已经广泛用于提高页面性能,说到缓存,读者可能第一时间会想到:“客户端缓存,CDNcaching,servercaching...”。换个维度,也会想到“200(fromcache),expire,eTag...”等概念。当然,作为前端开发者,我们必须了解这些缓存概念,这些缓存概念是相对于特定用户的访问,性能优化体现在单个用户身上。比如我第一次打开页面A,用了很长时间,下次打开页面,由于缓存的作用,时间缩短了。但在服务器端,还有另一个维度。想一下这个场景:我们有一个静态页面B,这个页面的服务器端需要从数据库中获取一部分数据b1,一部分数据b2需要根据b1进行计算。一个高复杂度的操作最终可以“拼凑”出完整的需要返回的页面B,整个过程耗时2s。那么灾难就是user1打开页面需要2秒,user2打开页面需要2秒……而且这些页面都是内容完全一样的静态页面B。为了解决这个灾难,这个时候我们也需要一个缓存。这种缓存称为服务器端缓存。综上所述,服务器端缓存的目的其实就是为同一个页面请求返回(缓存)相同的页面内容。这个过程完全独立于不同的用户。上面的话有点啰嗦,可以参考英文表述更清楚:Serversidecache的目标是独立于客户端的请求,对同一个请求响应相同的内容。因此,如下图的demo,当第一个请求到达时,服务端需要5秒的时间来返回HTML;然后再次请求该页面会命中缓存,但无论哪个用户访问,只需几毫秒即可获取完整页面。Showmethecode&Demo其实上面说的缓存概念很简单,有一点后端经验的同学都能很好的理解。但是这篇文章除了科普基本概念外,更重要的是介绍一下Express中间件的思想,自己实现一个服务端缓存中间件。让我们开始工作吧!最终的Demo代码,欢迎访问其Github地址。我将使用npm上的memory-cache包来方便缓存读写。最终的中间件代码很简单:'usestrict'varmcache=require('memory-cache');varcache=(duration)=>{return(req,res,next)=>{letkey='__express__'+req.originalUrl||req.urlletcachedBody=mcache.get(key)if(cachedBody){res.send(cachedBody)返回}else{res.sendResponse=res.sendres.send=(body)=>{mcache.put(key,正文,持续时间*1000);res.sendResponse(body)}next()}}}为了简单起见,我使用请求URL作为缓存key:当它(缓存key)和它对应的value存在时,直接返回它的value值;当它(缓存key)及其对应的value值不存在时,我们会对Express的send方法做一层拦截:在最终返回前,存储key-value对。缓存的有效时间为10秒。最后,出于判断,我们的中间件将控制权传递给下一个中间件。最后使用并测试以下代码:app.get('/',cache(10),(req,res)=>{setTimeout(()=>{res.render('index',{title:'Hey',message:'Hellothere',date:newDate()})},5000)//setTimeout用来模拟一个慢处理请求})我用setTimeout模拟了一个长(5s)的操作。打开浏览器控制面板,发现缓存在10秒内过期:至于缓存中间件为什么要这样写,为什么next()是传递控制的中间件,不打算展开。有兴趣的读者可以看看Express源码。还有一些小问题。仔细看我们的页面,然后体验实现代码。可能细心的读者会发现一个问题:在之前的实现中,我们缓存了整个页面,并将date:newDate()传给了jade模板index.jade。然后在命中缓存的情况下,10秒内,页面不能动态刷新同步,直到10秒缓存过期。同时,什么时候可以使用上面的中间件做服务端缓存呢?当然,只能使用静态内容。此外,PUT、DELETE和POST操作不应被类似地缓存。同样,我们使用npm模块:memory-cache,它有以下优点和缺点:读写快速简单,不需要其他依赖;当服务器或进程挂掉时,缓存中的所有内容都会丢失。memcache将缓存的内容存储在自己进程的内存中,所以这部分内容不能在多个Node.js进程之间共享。如果这些缺点真的很重要,我们在实际开发中可以选择分布式缓存服务,比如Redis。您也可以在npm上找到它:由express-redis-cache模块使用。综上所述,在真实的开发场景中,服务器端缓存已经成为常识,但在Node.js的世界里,体验它的中间件思想,自己手动编写服务,也是一种乐趣。结合实践,我认为实际缓存整个页面(如demo)并不是推荐的做法(实际场景具体分析当时),而且使用请求url作为缓存key也是需要的经过考虑的。比如一个页面中的一些静态内容可能会在其他页面中被复用,复用就成了一个问题。在真实场景中,所有的设计和逻辑都要对自己的业务情况负责。谈需求变现,都是耍流氓。本demo简单轻量,有需要的读者可以访问其Github地址,欢迎玩转各种花样。快乐编码!PS:作者Github仓库和知乎问答链接,欢迎各种形式的交流。