开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2
天,点击查看活动详情
介绍
Node.js 是一个构建在Chrome浏览器V8引擎
上的JavaScript运行环境, 使用单线程
、事件驱动
、非阻塞I/O
的方式实现了高并发请求,libuv
为其提供了异步编程的能力。
架构组成
从这张图上我们可以看出,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/O
、DNS操作
、还有一些用户代码都是在libuv工作的。
基本原理见文末扩展
数据交互
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('服务开好了')
总结
- 普通程序不能创建进程,只有系统进程才能创建进程
- 进程是分裂出来的
- 分裂出来的两个进程执行同一套代码
- 父子进程可以共享句柄
数据库
分类
- 关系型数据库
- Mysql:最常见最常用的,免费,性能高,安全性很高,容灾略差
- Oracle:付费,一般金融、医疗机构使用,容灾能力强
- 文件型数据库
- sqlite:简单,体积小,如通讯录
- 文档型数据库
- MongoDB:直接存储异构数据,方便
- 非关系型数据库
- NoSQL:没有复杂关系,对性能有极高要求
- 键值数据库
- Redis(内存,多服务器串行,性能极高)、Memcached
SQL
- 增 INSERT
//INSERT INTO 表 (字段名) VALUES(值列表)
INSERT INTO user_table (ID,name,gender,score) VALUES(1001,'Focus','男',99)
- 删 DELETE
//DELETE FROM 表 WHRER 条件
DELETE FROM user_table WHERE ID=1
- 改 UPDATE
//UPDATE 表 SET 字段1=值1,字段2=值2,... WHERE 条件
UPDATE user_table SET score=100 WHERE ID=1
- 查 SELECT
//SELECT 字段列表 FROM 表 WHERE 条件
SELECT name,gender FROM user_table WHERE ID=1
应用
- 连接
mysql.createConnection({host,port,user,password,database})
- 查询(按主键查询性能高)
db.query('SQL语句',(err,data)=>{})
- 使用
//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
})