nodejs for beginner

by lucifer@duiba

everything you should know before you dive into nodejs

About Me 

live slide

Agenda

why nodejs

what is nodejs

what can nodejs do

nodejs - the core

why nodejs

why not rails ,php, java and so on ?

nodejs is fast

based on v8 and libuv

nodejs擅于处理高并发(异步非阻塞)

阻塞IO非常影响性能

数据来源:https://gist.github.com/jboner/2841832

同步IO是主流开发模式

通过多进程或者多线程增加QPS

how about others ?

Easy To Learn

对前端来说

nodejs恐怕是最简单的全栈技术

nodejs是怎么处理IO的?

事件驱动

node中的众多模块都继承自EventEmitter。

 

事件并不是异步的。

非阻塞

 

非阻塞 != 异步

 

异步是非阻塞的核心。

 

libuv提供异步IO实现非阻塞

事件

事件驱动指的是事件是一切的基础

一次点击是一个事件

ajax是一个事件

一次文件读取是一个事件

一个请求响应是一个事件

......

非阻塞

A nonblocking call returns immediately with whatever data are available: the full number of bytes requested, fewer, or none at all.

An asynchronous call requests a transfer that will be performed in its whole(entirety) but will complete at some future time.

同步和异步关注的是消息通信机制

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态

一个IO操作分成两个步骤:发起IO请求和实际的IO操作,同步IO和异步IO的区别就在于第二个步骤是否阻塞

阻塞 非阻塞
同步 read
异步 select/poll/epoll aio

Event Loop in nodejs

nodejs 是什么

Node.js 不是一门语言也不是框架,它只是基于 Google V8 引擎的 JavaScript 运行时环境,同时结合 libuv 扩展了 JavaScript 功能,使得 JavaScript 能够同时具有 DOM 操作(浏览器)和 I/O、文件读写、操作数据库(服务器端)等能力,是目前最简单的全栈式语言。

nodejs 架构图

V8 解释并执行 JavaScript 代码(这就是为什么浏览器能执行 JavaScript 原因)

 

 

libuv 由事件循环和线程池组成,负责所有 I/O 任务的分发与执行

Node.js Bindings就是将 Chrome V8 等暴露的C/C++ 接口转成JavaScript api

Asynchronous I/O made simple.

libuv is a multi-platform support library with a focus on asynchronous I/O.

nodejs 可以做什么

nodejs 核心

global

process

EventEmiter

Stream

global

global.__filename
global.__dirname
global.module
global.require()
global.process(接下来讲)

process

process.pid
process.versions
process.arch
process.env
process.argv
process.memoryUsage()
process.cwd()
process.exit()
process.kill(pid)

EventEmiter

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});
myEmitter.emit('event');

事件是基于发布者/订阅者模式

 

发送/监听事件本质上是高阶函数

emitter.listeners(eventName)
emitter.on(eventName, listener)
emitter.once(eventName, listener)
emitter.removeListener(eventName, listener)

实例方法

Stream

“Streams are Node’s best and most misunderstood idea.”

— Dominic Tarr

$ grep -d recurse done ./bin | wc -l
  8

强大的linux管道

const grep = ... // A stream for the grep output
const wc = ... // A stream for the wc input
grep.pipe(wc)

流就是数据的集合 — 有点像数组一样. 不同之处在于流的数据不是一次到位的,而是连续的

buffer

 

buffer是一个很小的物理存储,通常是RAM。

在这里数据被收集起来,等待最终被发出并处理

 

如果binary data是乘客的话,

buffer就是公交车

buffer只能决定何时发车

一个例子🌰

vedio.on('data', chunk => {

    if (chunk.size > 1024 * 1024)  { // 1024 kb
        vedio.resume()
    } else {
        vedio.pause();
    }
})

无处不在的Stream

流提供了无需加载整个资源

即可进行处理的可能性

处理大数据的问题

  • 时间长
  • 文件超过buffer限制会溢出
const fs = require('fs');
const USER_NUM = 10;
const FILE_NAME = '~/Photoshop.dmg'
for (let i = 0; i < USER_NUM; i++) {
    fs.readFile(FILE_NAME, (err, data) => {
       if (err) {
             console.error(err)
         }
         console.log(data)
    })
}

const fs = require('fs');
const USER_NUM = 10;
const FILE_NAME = '~/Photoshop.dmg'

for (let i = 0; i < USER_NUM; i++) {
    const instream = fs.createReadStream(FILE_NAME);
    instream.on('data', console.log)
}

读取大文件的例子

流的类型

  • Readable
  • Writable
  • Duplex
  • Transform
const readable = getReadableStreamSomehow();
readable.on('data', (chunk) => {
  console.log(`Received ${chunk.length} bytes of data.`);
});

Readable Stream - Flowing Mode

$ node stdin.js

const myReadable = new MyReadable(dataSource);
myReadable.setEncoding('utf8');
myReadable.on('readable', () => {
  let chunk;
  while (null !== (chunk = myReadable.read())) {
    console.log(`Received ${chunk.length} bytes of data.`);
  }
});

Readable Stream - Non-Flowing Mode

Writable Stream

Pipe

var r = fs.createReadStream('file.txt')
var z = zlib.createGzip()
var w = fs.createWriteStream('file.txt.gz')
// Readable.pipe takes writable and returns destination
r.pipe(z).pipe(w)
Readable.prototype.pipe = function(writable, options) {
 this.on('data', (chunk) => {
   let ok = writable.write(chunk);
   // 背压,暂停
   !ok && this.pause();
 });
 this.on('end', () => {
     writable.end()
 })
 writable.on('drain', () => {
   // 恢复
   this.resume();
 });

 // 支持链式调用
 return this;
};

也就是说,当 write 返回 false 的时候,我们暂停读取流,当缓存的数据清空之后,我们再继续读取流,相当于我们根据输出流来对读取流做限流。反过来,如果写入流快于读取流,我们也可以对写入流限流。

Next

nodejs 集群与稳定性

Thanks

nodejs for beginner

By lucifer

nodejs for beginner

nodejs for beginner

  • 955