Node.js 核心知识点

392 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

介绍

Node.js 是一个构建在Chrome浏览器V8引擎上的JavaScript运行环境, 使用单线程事件驱动非阻塞I/O的方式实现了高并发请求,libuv为其提供了异步编程的能力。

架构组成

image.png

从这张图上我们可以看出,Node.js底层框架由Node.js标准库Node bindings、 底层库三个部分组成。

Node.js标准库

常用内置系统模块如下:

http

const http = require('node:http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, World!\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});
const http = require('node:http')

const server = http.createServer((req,res)=>{
    console.log('浏览器执行时的回调函数','node file的时候服务端日志')
    //request:请求 服务器接收的数据(输入)
    //response:响应 服务器发送出去的数据(输出)
    res.write('浏览器显示的内容')
    res.end()
})

//监听80端口
server.listen(8080)

assert

断言:一般检查参数用,程序异常报错时使用

const assert = require('node:assert')

assert(arguments.length===2,'必须输入两个参数')

buffer

处理二进制数据

fs

file system

//访问服务器文件
const http = require('node:http')
const fs = require('node:fs')

const server = http.createServer((req,res)=>{
    fs.readFile(`www${req.url}`,(err,data)=>{
        if(err){
            res.writeHeader(404);
            res.write('Not Found')
        }else{
            res.write(data)
        }
        res.end();
    })
    
})
server.listen(8080)

readFile先把所有数据都读到缓存中,然后回调,所以比较占内存,资源利用不充分,流可以很好解决这个问题,边读边发

const fs = require('node:fs')

const rs = fs.createReadStream('test1.png');
const ws = fs.createWriteStream('test2.png');

rs.pipe(ws);
rs.on('error',err=>{console.log('读取失败')});
ws.on('finish',()=>{写入完成});

c++ addons

c/c++插件在node里用

多进程

  • child processes
  • cluster
  • process

crypto

  • md5(单项散列)撞库
  • sha
const { createHash } = await import('node:crypto');

let obj = createHash('md5');
obj.update('123456');
obj.digest('hex');//16进制

OS

const os = require('node:os');

const cpu = os.cpus()

path

const path = require('node:path');

const str = path.dirname('/foo/bar/baz/asdf/focus.md');// Returns: '/foo/bar/baz/asdf'
const str2 = path.extname('/foo/bar/baz/asdf/focus.md');//Returns: '.md'

event

事件队列

querystring

const querystring = require('node:querystring');

querystring.parse('foo=bar&abc=xyz&abc=123')
//returns {foo: 'bar',abc: ['xyz', '123']}

querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' });
// Returns 'foo=bar&baz=qux&baz=quux&corge='

querystring.stringify({ foo: 'bar', baz: 'qux' }, ';', ':');
// Returns 'foo:bar;baz:qux'

url

const url = require('node:url')

const {pathname,query} = url.parse(req.url,true)//true/false:是否结构化query,非结构化是字符串

/*
https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash

{
  protocol: 'https:',
  slashes: true,
  auth: 'user:pass',
  host: 'sub.example.com:8080',
  port: '8080',
  hostname: 'sub.example.com',
  hash: '#hash',
  search: '?query=string',
  query: [Object: null prototype] { query: 'string' },
  pathname: '/p/a/t/h',
  path: '/p/a/t/h?query=string',
  href: 'https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash'
}
*/

网络

  • TCP NET
  • UDP

DNS

DNS(Domain Name System);将域名网址解析成ip地址

const dns = require('node:dns');
dns.resolve('baidu.com',(err,res)=>{
    console.log(err,res)
})
// null [ '110.242.68.66', '39.156.66.10' ]
const dns = require('dns');

dns.lookup('example.org', (err, address, family) => {
  console.log('address: %j family: IPv%s', address, family);
});
// address: "93.184.216.34" family: IPv4

stream

连续数据都是流:视频流、网络流、文件流、语音流

SSL/TLS

https基于ssl+http

zlib 压缩-gz

//将test.js压缩后写到test.js.gz
const fs = require('node:fs');
const zlib = require('node:zlib');

const rs = fs.createReadStream('test.js');
const ws = fs.createWriteStream('test.js.gz');
const gz = zlib.createGzip();

rs.pipe(gz).pipe(ws);

ws.on('finish',()=>{console.log('success')})

Node bindings

这一层可以理解为是javascript与C/C++库之间建立连接的, 通过这个桥,底层实现的C/C++库暴露给javascript环境,同时把js传入V8, 解析后交给libuv发起非阻塞I/O, 并等待事件循环调度;

基本原理见文末扩展

底层库

这一层主要有以下四块:

  • V8: Google推出的Javascript虚拟机,为Javascript提供了在非浏览器端运行的环境;
  • libuv:为Node.js提供了跨平台,线程池,事件池,异步I/O 等能力,是Nodejs之所以高效的主要原因;
  • C-ares:提供了异步处理DNS相关的能力;
  • http_parser、OpenSSL、zlib等:提供包括http解析、SSL、数据压缩等能力;

顺带看一下libuv的架构图,可见Nodejs的网络I/O文件I/ODNS操作、还有一些用户代码都是在libuv工作的。 image.png

基本原理见文末扩展

数据交互

GET

参数在url里,req.url获取,通过url.parse结构化

const http = require('node:http')
const url = require('node:url')

const server = http.createServer((req,res)=>{
    const {pathname,query} = url.parse(req.url,true)//true/false:是否结构化query,非结构化是字符串
    console.log(pathname,query)
    
    res.end()
})

server.listen(8080)

POST

const http = require('node:http')
const server = http.createServer((req,res)=>{
    let reqDataStr = '';
    
    req.on('data',(data)=>{
        reqDataStr += data;//大数据包转小包
    })
    
    req.on('end',()=>{
        const reqData = querystring().parse(reqDataStr)
        //对reqData操作
        ...
    })
    
})

server.listen(8080)

FILE

响应静态资源,file上传也是post,req是读取流对象(进水口),res是写入流对象(出水口),可以通过pipe串在一起,中间也可以增加压缩等操作

  • 读取流:如 fs.createReadStream、req
  • 写入流:如 fs.createWriteStream、res
  • 读写流:如 压缩、加密(输入且输出)
const http = require('node:http')
const fs = require('node:fs')
const zlib = require('node:zlib')

const server = http.createServer((req,res)=>{
    const rs = fs.createReadStream(`www${req.url}`)//localhost:8080/test.html 页面那所有静态资源
    res.setHeader('content-encoding','gzip')
    const gz = zlib.createGzip();

    rs.pipe(gz).pipe(res);
})

server.listen(8080)

node 缓存

1.缓存过程
S=server C=client
S->C:Last-Modifield(第一次请求的响应体)
C->S:If-Modifield-Since(第二次请求头)
S->C:304 not modifield(第二次响应体)

2.缓存策略(哪些需要缓存,缓存多长时间)
如果这个文件特殊不需要缓存: cache-control:nocache/nostore

node 垃圾回收(gc)

引用计数(引用一次+1,释放一次-1,到0时回收内存)

进程和线程

  • 进程有独立的空间、存储(类比工厂车间)
  • 同一个进程内的所有线程共享一套空间、代码(类比车间内的工人)

多进程(PHP、Node)

  • 成本高(慢)
  • 安全(进程间隔离)
  • 进程间通信麻烦
  • 代码简单

多线程(Java、C)

  • 成本低(快)
  • 不安全(线程要挂一块挂)
  • 线程之间通信容易
  • 写代码复杂

Node 多进程

Node.js 默认是单进程

多进程优势:

  • 安全
    • 死了一个还有三个
  • 性能高
    • cpu4核,能共同启动4个进程(多开了用不上),是单进程的4倍

分工

  • 主进程:负责派生子进程(cluster)
  • 子进程:负责干活

进程调度:一个满了用下一个

const http = require('node:http');
const cluster = require('node:cluster');
const os = require('node:os');
const process = require('process');

const cpu = os.cpus().length;//几核

if(cluster.isMaster){//主进程
    for(let i  = 0; i< cpu; i++){
        cluster.fork()//派生子进程
    }
    
    return
}

const server = http.createServer((req,res)=>{
    res.write(`子进程:${process.pid}`);
    res.end();
})

server.listen(8080)
console.log('服务开好了')

image.png

image.png

总结

  1. 普通程序不能创建进程,只有系统进程才能创建进程
  2. 进程是分裂出来的
  3. 分裂出来的两个进程执行同一套代码
  4. 父子进程可以共享句柄

数据库

分类

  1. 关系型数据库
  • Mysql:最常见最常用的,免费,性能高,安全性很高,容灾略差
  • Oracle:付费,一般金融、医疗机构使用,容灾能力强
  1. 文件型数据库
  • sqlite:简单,体积小,如通讯录
  1. 文档型数据库
  • MongoDB:直接存储异构数据,方便
  1. 非关系型数据库
  • NoSQL:没有复杂关系,对性能有极高要求
  1. 键值数据库
  • Redis(内存,多服务器串行,性能极高)、Memcached

SQL

  1. 增 INSERT
//INSERT INTO 表 (字段名) VALUES(值列表)
INSERT INTO user_table (ID,name,gender,score) VALUES(1001,'Focus','男',99)
  1. 删 DELETE
//DELETE FROM 表 WHRER 条件
DELETE FROM user_table WHERE ID=1
  1. 改 UPDATE
//UPDATE 表 SET 字段1=值1,字段2=值2,... WHERE 条件
UPDATE user_table SET score=100 WHERE ID=1
  1. 查 SELECT
//SELECT 字段列表 FROM 表 WHERE 条件
SELECT name,gender FROM user_table WHERE ID=1

应用

  1. 连接
mysql.createConnection({host,port,user,password,database})
  1. 查询(按主键查询性能高)
db.query('SQL语句',(err,data)=>{})
  1. 使用
//cnpm i my-sql
const mysql = require('mysql');

//连接
let db = mysql.createConnection({host:'localhost',user:'root',password:'',port:3309,database:'my_db'})

//查询
db.query('INSERT INTO user_table (ID,name,gender,score) VALUES(1001,'Jessica','',100)',(err,data)=>{
    if(err){
        return false
    }
    
    return data
})

扩展

2022年值得使用的Node.js框架

底层原理