MST

星途 面试题库

面试题:Node.js静态资源服务器的性能优化与缓存机制

假设你已经使用Node.js的文件系统搭建好了一个静态资源服务器,现在要对其进行性能优化。请阐述如何实现缓存机制,包括选择合适的缓存策略(如强缓存和协商缓存),以及在Node.js中如何通过设置HTTP头来实现这些缓存策略,同时说明如何处理缓存更新的情况。
49.3万 热度难度
前端开发Node.js

知识考点

AI 面试

面试题答案

一键面试

缓存策略

  1. 强缓存
    • 原理:浏览器在加载资源时,先在本地缓存中查找该资源,如果找到且未过期,则直接从缓存中加载,无需向服务器发送请求。
    • 适用场景:对于不经常变化的静态资源,如样式表、脚本、图片等,适合使用强缓存。
  2. 协商缓存
    • 原理:浏览器先向服务器发送请求,服务器根据请求头中的信息判断资源是否有更新。如果资源未更新,服务器返回304状态码,浏览器从本地缓存中加载资源;如果资源已更新,服务器返回200状态码及新的资源。
    • 适用场景:对于可能会偶尔更新的资源,适合使用协商缓存,这样既能利用缓存提高性能,又能保证获取到最新的资源。

在Node.js中设置HTTP头实现缓存策略

  1. 强缓存
    • 设置Cache - Control头
      const http = require('http');
      const fs = require('fs');
      const path = require('path');
      
      const server = http.createServer((req, res) => {
        const filePath = path.join(__dirname, 'public', req.url);
        fs.readFile(filePath, (err, data) => {
          if (err) {
            res.writeHead(404, {'Content - Type': 'text/plain'});
            res.end('Not Found');
          } else {
            // 设置强缓存,例如缓存1小时(3600秒)
            res.writeHead(200, {'Content - Type': 'text/html', 'Cache - Control':'max - age = 3600'});
            res.end(data);
          }
        });
      });
      
      const port = 3000;
      server.listen(port, () => {
        console.log(`Server running on port ${port}`);
      });
      
    • 设置Expires头Expires头是一个日期时间值,指定资源在浏览器缓存中的过期时间。不过,由于它依赖于客户端和服务器的时间同步,现在更推荐使用Cache - Control
      const http = require('http');
      const fs = require('fs');
      const path = require('path');
      
      const server = http.createServer((req, res) => {
        const filePath = path.join(__dirname, 'public', req.url);
        fs.readFile(filePath, (err, data) => {
          if (err) {
            res.writeHead(404, {'Content - Type': 'text/plain'});
            res.end('Not Found');
          } else {
            const oneHourFromNow = new Date(Date.now() + 3600 * 1000).toUTCString();
            res.writeHead(200, {'Content - Type': 'text/html', 'Expires': oneHourFromNow});
            res.end(data);
          }
        });
      });
      
      const port = 3000;
      server.listen(port, () => {
        console.log(`Server running on port ${port}`);
      });
      
  2. 协商缓存
    • ETag
      • 原理:ETag是资源的唯一标识符,由服务器生成。每次资源发生变化,ETag也会改变。浏览器在请求资源时,会在请求头中带上If - None - Match字段,其值为之前获取资源时服务器返回的ETag。服务器接收到请求后,对比If - None - Match的值与当前资源的ETag,如果相同,返回304状态码,否则返回200状态码及新的资源。
      • 实现
        const http = require('http');
        const fs = require('fs');
        const path = require('path');
        const crypto = require('crypto');
        
        const server = http.createServer((req, res) => {
          const filePath = path.join(__dirname, 'public', req.url);
          const fileStat = fs.statSync(filePath);
          const etag = crypto.createHash('md5').update(fileStat.mtime + fileStat.size).digest('hex');
          const ifNoneMatch = req.headers['if - none - match'];
        
          if (ifNoneMatch === etag) {
            res.writeHead(304);
            res.end();
          } else {
            fs.readFile(filePath, (err, data) => {
              if (err) {
                res.writeHead(404, {'Content - Type': 'text/plain'});
                res.end('Not Found');
              } else {
                res.writeHead(200, {'Content - Type': 'text/html', 'ETag': etag});
                res.end(data);
              }
            });
          }
        });
        
        const port = 3000;
        server.listen(port, () => {
          console.log(`Server running on port ${port}`);
        });
        
    • Last - Modified
      • 原理Last - Modified头表示资源的最后修改时间。浏览器在请求资源时,会在请求头中带上If - Modified - Since字段,其值为之前获取资源时服务器返回的Last - Modified。服务器接收到请求后,对比If - Modified - Since的值与当前资源的最后修改时间,如果相同,返回304状态码,否则返回200状态码及新的资源。
      • 实现
        const http = require('http');
        const fs = require('fs');
        const path = require('path');
        
        const server = http.createServer((req, res) => {
          const filePath = path.join(__dirname, 'public', req.url);
          const fileStat = fs.statSync(filePath);
          const lastModified = fileStat.mtime.toUTCString();
          const ifModifiedSince = req.headers['if - modified - since'];
        
          if (ifModifiedSince === lastModified) {
            res.writeHead(304);
            res.end();
          } else {
            fs.readFile(filePath, (err, data) => {
              if (err) {
                res.writeHead(404, {'Content - Type': 'text/plain'});
                res.end('Not Found');
              } else {
                res.writeHead(200, {'Content - Type': 'text/html', 'Last - Modified': lastModified});
                res.end(data);
              }
            });
          }
        });
        
        const port = 3000;
        server.listen(port, () => {
          console.log(`Server running on port ${port}`);
        });
        

处理缓存更新的情况

  1. 强缓存更新
    • 修改文件名:对于不经常变化的静态资源,可以在资源文件名中加入版本号或哈希值。例如,将styles.css改为styles_v1.0.cssstyles_abc123.css。当资源内容发生变化时,更新文件名,这样浏览器会把它当作新的资源请求,不会使用旧的缓存。
    • 控制缓存时间:合理设置Cache - Controlmax - age值。如果资源更新比较频繁,可以将max - age设置得较短,如几分钟或几小时;如果资源更新不频繁,可以设置较长的时间,如一天或一周。
  2. 协商缓存更新
    • 更新ETag或Last - Modified值:当资源内容发生变化时,重新计算ETag值(如通过文件内容的哈希计算),或者更新Last - Modified的时间戳。这样浏览器下次请求时,由于ETag或Last - Modified值与之前不同,会获取新的资源。