缓存

缓存是服务端的问题,前端没有办法控制缓存。前端可以通过pwa,实现离线存储,或者将资源存储在sessionStorage,localStorage

缓存分类

只要开启了,就不要请求服务器了(首页没办法强制缓存)

先对比一下,文件有没有变化,如果没有变化,返回304,有变化返回200

强制缓存

设置请求头

res.setHeader('Cache-Control', 'max-age=10'); // 设置10秒强缓存
// 和cache-control相同,低版本浏览器不支持cache,需要使用Expires兼容
res.setHeader('Expires', new Date(Date.now() + 10000).toGMTString());

协商缓存

第一次请求的时候,设置一个头 last-modified 最后修改时间
再次请求的时候,会带上这个时间 If-Modified-Since

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,es;q=0.6,fr;q=0.5,pt;q=0.4
Connection: keep-alive
Host: localhost
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3315.4 Safari/537.36
Accept-Ranges: bytes
Connection: Keep-Alive
Content-Length: 124
Content-Type: text/plain
Date: Sat, 27 Jan 2018 12:16:24 GMT
ETag: "7c-55f53907b74a4"
Keep-Alive: timeout=5, max=100
Last-Modified: Sat, 02 Dec 2019 04:03:14 GMT
Server: Apache/2.4.9 (Win64) PHP/5.5.12
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,es;q=0.6,fr;q=0.5,pt;q=0.4
Cache-Control: max-age=0
Connection: keep-alive
Host: localhost
If-Modified-Since: Sat, 02 Dec 2019 04:03:14 GMT
If-None-Match: "7c-55f53907b74a4"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3315.4 Safari/537.36
Connection: Keep-Alive
Date: Sat, 27 Jan 2019 12:17:57 GMT
ETag: "7c-55f53907b74a4"
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.9 (Win64) PHP/5.5.12

If-Modified-Since 第二次请求的时候,通过这个字段告诉服务器,说自己手里有一份,如果你那里的和我的一样,就用浏览器自己的,服务器响应状态码设置为304;If-Modified-Since后面的时间是文件最后修改的时间。

文件修改时间的设置过程: 第一次S->C的时候,服务器告诉浏览器文件最后的修改时间,第二次 C->S的时候 浏览器携带修改时间询问服务器。第三次S-C的时候,服务器决定返回200还是304。

缓存服务器实现

const http = require('http');
const fs = require('fs');
const url = require(''url');
http.creatServer((req, res) => {
    const { pathname } = url.parse(req.url);
    // 获取文件日期
    fs.stat(`www/${pathname}`, (err, stat) => {
      if (err) {
        res.writeHeader(404);
        res.write('Not Found');
        res.end();
      } else {
        if (req.headers['if-modified-since']) {
          const oDate = new Date(req.headers['if-modified-since']);
          const time_client = Math.floor(oDate.getTime() / 1000);
          const time_server = Math.floor(stat.mtime.getTime() / 1000);
          if (time_server > time_client) { // 服务器的文件时间大于客户端
            sendFileToClient();
          } else {
            res.writeHeader(304);
            res.write('Not Modified');
            res.end();
          }
        } else {
          sendFileToClient();
        }

        function sendFileToClient() {
          let rs = fs.createReadStream(`www/${pathname}`);
          res.setHeader('Last-Modifyed', state.mtime.toGMTString());
          rs.pipe(res);
          rs.on('error', err => {
            res.writeHeader(404);
            res.write('Not Found');
            res.end();
          })
        }
      }
    })
}).listen(8080);

获取文件信息

// 获取文件信息
fs.stat('./www/1.html', (err, stat) => {
  if (err) {
    console.log('获取文件信息失败');
  } else {
    console.log(stat);
  }
});

date.toUTCString(); 时间

// 在Last-Modified 响应头上面添加
res.setHeader('cache-control', 'no-cache');

缺陷:

  1. 如果文件没改,时间变了
  2. 时间精确到秒,可能会有问题

所以,现代缓存策略使用Etag算出文件唯一值,和修改时间配合

使用Etag原理,实体头,实体内容

根据文件内容,算出一个唯一的值,md5
Etag是最准确的,但耗性能

const crypto = require('crypto');
const md5 = crypto.createHash('md5');
const arr = [];
fs.createReadStream(abs).on('data', functon(data) {
    md5.update(data);
    arr.push(data);
}).on('end', function() {
    res.setHeader('Etag', md5.digest('base64'));
    res.end(Buffer.concat(arr));
})

If-None-Match 请求头 对应Etag 服务端设置了Etag头,再请求的时候会带上If-None-Match
不能对大文件进行etag

优化做法,一般根据文件大小 + 最后修改时间,生成一个etag

一般缓存的做法

如果浏览器访问服务器 会先加一个强制缓存,强制缓存5秒

过了5秒之后,会在发送请求对比缓存,先判断last-modified 再判断etag,如果都成立,返回304,强制缓存5秒

如果有变化 再返回新的文件,强制缓存5秒

pwd缓存

使用的是离线缓存,网路不通也可以访问

利用的是cache的api,保存在浏览器的cache中

多进程

node默认:单进程,单线程

性能高,复杂,考研程序员能力

性能略低,简单,对程序员要求低

一般多线程工作原理

主进程: 负责派生(创建)子进程,主进程越简单越好,防止崩溃。
子进程: 干活

多进程

对服务器来说,安全,性能高(更充分利用CPU)

  1. 普通程序不能创建进程,只有系统进程才能创建进程
  2. 进程是分裂出来的,只有主进程可以分裂。
  3. 分裂出来的两个进程,执行的是同一套代码
  4. 父子进程之间可以共享句柄, 端口就是一种句柄
const cluster = require('cluster');
// 分叉,
if (cluster.isMaster) { // 如果是主进程就分裂
    cluster.fork();
};

有几个cpu就开几个进程,不是越多越好

const cluster = require('cluster');
const os = require('os');

if (cluster.isMaster) {
    for (const i = 0; i < os.cpus().length; i++) {
        cluster.fork();
    }
    console.log('我是主进程');
} else {
    console.log('我是子进程')
}

主进程 = 守护进程
子进程 = 工作进程

一般子进程是用来干活的,主进程做管理。

const cluster = require('cluster');
const os = require('os');
const process = require('process');

if (cluster.isMaster) {
    for (const i = 0; i < os.cpus().length; i++) {
        cluster.fork();
    }
    console.log('我是主进程');
} else {
    console.log(process.pid);
    http.createServer((req, res) => {
        res.write('aaaa');
        res.end();
    }).listen(8080);
    console.log('端口号8080');
}

以此类推。最高效的方式。

进程的开销和调度非常的耗费性能,计算机的运算是很快的,当肉眼可见时就已经表示很慢了。

多进程不会造成死锁,死锁的意思是对文件的读写进行时,其它程序对文件的访问会限制。