你尚未登录,仅允许查看本站部分内容。请登录使用邀请码注册
zack

Nodejs 0.10 与 0.12 的差异点 0个回复 专栏 @ Nodejs

zack 发布于 3 年前

前阵子业务有需要从 0.10 升级到 0.12,就整理了一下差异点,刚好 Node.js 4.0 发布了,从 0.10 升级到 4.0 变化还蛮大的,目前也没这方面的文章,索性先把这篇文章拿出来,过阵子再把 0.12 与 4.0 的差异点贴上来。

性能方面

  • 多进程轮转法进行负载均衡

    已有翻译稿可供阅读:http://www.infoq.com/cn/articles/nodejs-cluster-round-robin-load-balancing
    总结来说,0.10的问题在于进程抢占新接进来的连接,导致多进程模块无法均匀分配,0.12 的版本由 master 进程负责接进连接,并通过轮转法转交给其他子进程。
    如果需要使用老办法,由操作系统进行调度,可以通过 cluster.schedulingPolicy 进行设置。

  • 虚拟机改进

    vm 被基于卓越的 Contextify native 模块完全重写,从而工作的更好,Contextify 所有的功能被加入到了核心模块中,并进行了优化。由于在沙箱中任何不被信任的 JS 代码片段的运行是隔离的,使得开发者能够创建或维护一个可以定义全局变量或安全地与进程进行通信的沙箱,意味着 Node APIs 被上下文化了。从理论上说,两个不同版本的 nodejs 可以被隔离运行,不相互干扰。也允许比如像 Javascript 线程这样不是 fork 出来的子进程,开发者可以在不同的线程上运行多个 nodejs vm 。

  • spawnSync/execSync

    execSync 同步执行子进程,特别是针对 shell 脚本的需求才加入的功能,spawnSync 就是把执行结果作为一个对象返回。具体见 http://www.infoq.com/cn/articles/nodejsv0.12-execsync-a-synchronous-api-for-child-processes 一文。

  • Profiling APIs

    0.10的问题:只能在启动时启用 profiling,heap dump 需要本地插件。
    0.12 增加了 api 以便启用 profiling,可以利用 node-inspector 进行性能分析和调试。

  • 提升TLS性能

    tls模块在Node.js v0.12中做了相当大的重构。
    在Node.js v0.10中,tls模块在net模块之上,作为对网络流量透明地加密和解密的传输流。从工程角度来看,这样分层是有必要的,但会导致开销-更多的内存转移以及在V8 VM中超出绝对必要的调入调出–并妨碍优化。
    所以在node.js v0.12中,直接用libuv重写了tls模块。现在它直接把接入的网络流量拉出来加密,无需通过中间层。
    尽管这种评测不太科学,但用空密码时表明现在TLS一般要快10%,并且用的内存更少。(我得说减少的内存占用可能部分是因为重构的内存管理,这是v0.12的另一项优化。)
    (还有,如果你想知道,空密码时不会对负载加密的密码;它们可以用来测量架构和协议的开销。)
    对于最终用户来说,tls模块的大部分变化都是透明的。其中最显眼的一个是现在TLS连接是从tls.TLSSocket,而不是tls.CryptoStream中得到的。

  • 提升Crypto性能

    几种加解密算法现在应该更快了,有时是快很多。先介绍下背景:
    Node.js中的加解密使用OpenSSL库实现的。OpenSSL中的算法有用C编写,并针对各种平台和架构的手动程序集版本。
    Node.js v0.10已经给一些东西用上了程序集版本,而v0.12又大范围扩充了这一范围。此外,现在在有CPU支持时用上了AES-NI,过去三四年生产的大多数x86处理器都支持。
    在Linux上,如果执行grep ^flags /proc/cpuinfo | grep -w aes找到任何匹配结果,那就说明你的系统支持AES-NI。不过像VMWare或VirtualBox之类的虚拟机管理程序可能会对客户机操作系统隐藏CPU的真正能力,包括AES-NI。
    启用AES-NI有个有趣的结果,即像AES128-GCM-SHA256这种工业强度的加解密现在比NULL-MD5这样的非加密密码还要快很多!

  • 减少垃圾收集的应激反应

    多情景重构的一个副作用是极大减少了Node.js核心中的持久句柄的数量。
    持久句柄是对V8堆上对象的强引用,防止对象在引用再次移除之前被垃圾收集器回收(按GC的说法,它是一个人造的GC根。)
    Node.js用持久句柄缓存经常使用的值,比如strings或object原型。然而持久句柄在垃圾收集器中需要一个特殊的后处理步骤,并且根据句柄数量有一个线性增长的开销。
    因为多情景清理的作用,大部分持久句柄或者被消掉,或者切换成一个更轻量的机制(被称为'永恒句柄';这个名字在暗示什么呢?)
    最终效果是你的程序在垃圾收集器内部所用时间更少了,把更多的时间用在了实际的工作上。现在在node –prof的输出中v8::internal::GlobalHandles::PostGarbageCollectionProcessing()出现的次数应该少了很多。

  • 更快的计时器/setImmediate()/process.nextTick()

    setTimeout()和它的朋友们现在用的时间源不仅更快,而且对时钟偏移免疫。这一优化在所有平台上都启用了,但在Linux上我们更进一步,直接从VDSO上读取当前时间,因此极大降低了gettimeofday(2)和clock_gettime(2)系统调用的次数。
    setImmediate()和process.nextTick()也给通常情况下的派发添加了快速的路径,可以见到性能上的调整。也就是说虽然这些函数已经相当快了,但它们现在更快了。


API / 组件

不兼容 API 的修改

  • V8 升级

    1. native 方法原型发生变化
    2. 大部分 V8 API 方法需要带上 v8::Isolate
    3. V8 string 方法可以声明编码类型
    4. v8::String::NewSymbol() and v8::String::NewUndetectable() 被移除
    5. v8::String::AsciiValue 被移除
    6. v8::HandleScope::Close() 被移除
    7. v8::Persistent 不再继承自 v8::Handle
    8. 弱持久句柄回调的原型发生变化
    9. v8::ThrowException() 现在变成 v8::Isolate::ThrowException()
    10. ES6 语法

    具体见:http://news.rednode.cn/item/53f9953e81b5e8487136551b

  • v8升级对开发者的影响

    1. 依赖的模块可能依赖旧的 V8 API,表现上比如 node-gyp 编译出错,模块的话,如旧版本的 node-postgres、node-memwatch (v0.2.2) and node-look (v0.1.3),但是 node-postgres (v4.3) 运行正常,所以通常使用最新的版本可以解决该问题,毕竟 node 0.12 的发布也有好几个月了,如果没有模块没有更新,相信作者也很快会推出新版本
    2. 对于那些使用了 v8 binding 的模块,需要用新 API 去替换旧 API 的调用
    3. 大部分模块不是直接调用 V8 API 的,而是通过 nan 模块,如果需要在 0.12 上运行,需要升级 nan 到最新的版本
  • Buffer

    1. 我们不再需要通过 Buffer::New(str, 100)->handle_ 来获取能传递给 js 的 Buffer 对象了,New 出来的就已经是 v8 对象了。
    2. node::Buffer::New(Handle)可以传可选的第二参数指定编码类型。
    3. 增加 node::Buffer::Use() API,使用传递过来的 char* 替代拷贝的方式
    4. 增加 node::Crypto::Certificate,处理那些以 SPKAC(Signed public key & challenges) 方式工作的 node::Crypto::Certificate::verifySpkac(Handle), node::Crypto::Certificate::exportPublicKey(Handle) & node::Crypto::Certificte::exportChallenge(Handle)
    5. SlowBuffer 用途已改变,返回一个 buffer 实例。
    6. buffer.parent 现在指向到通过smalloc.alloc分配的一个 object,而不是一个 buffer 实例, 并且只有当 buffer 是一片的时候。
    7. buffer.offset 现在是一个只读的原型属性。
    8. 增加 Buffer.alloc() 和 Buffer.dispose()。
    9. Buffer#fill() 被扩展成将所有传递过来的值进行填充
    10. (new Buffer('text\0!', 'ascii')).toString() 在 0.10 输出 'text !' 而在 0.12 输出 'text\u0000!'
    11. 当 chunk 为 buffer 类型时,buffer encoding 会调起可写流的 _write()
    12. child_process.spawn 的 customFds 选项已被弃用。可以用 stdio 替代,参考https://github.com/mochajs/mocha/pull/1364/files
  • 资源管理上,忽略了软限制值

    在 Unix 上,软限制被 nodejs 忽略了。
    这是因为默认的限制值太过低,而它强迫每个使用者需要去解决在某些方面上命中限制值的问题。对核心开发者来说通常是好事,但是现在由于 Yeoman, Grunt, Gulp, Bower 还有其他的前端热门工具的使用,也带来了大量的初学者。过低的限制造成了大量的支持问题,同时也对那些遇到 EMFILE 错误的初学者带来理解上的困惑。这修复了在某些方面上大家都会遇到在启动 node 时命中限制引起 crash 这一主要问题,而且还不是小补丁,但不会影响到使用者。这其中有大量的使用者不明白 ulimit 为何物,更少的人会知道如何去提高 node 的限制值。参考 https://github.com/joyent/node/issues/8704 的讨论,后续会提供相应的参数做设置。

  • Child Process

    child_process.spawn 的 customFds 选项已被弃用,可以使用 stdio 替代,参考 https://github.com/mochajs/mocha/pull/1364/files 一文

  • https

    0.10 的 SNICallback 只传了 servername 这一个参数并且返回 SecureContext 实例。
    0.12 可以传 servername 和 cb。然后当 ctx 是一个 SecureContext 实例时,SNICallback 会去调用 cb(null, ctx)。
    HTTPS 是建立在 TLS/SSL 之上的 HTTP 协议。所以,tls API 的修改也会影响到 HTTPS 模块。

  • 其他 API 变化

    rl.setPrompt(prompt)
    process.maxTickDepth 被移除
    当存在 write() 时,在下一个 tick 可写流会触发一个 finish 事件。

兼容 API 的修改

  • stream3 混合流

    stream1 提供了 push-stream 的方式,一旦数据开始读取,就会有源源不断的 data 事件被抛出,不管处理函数是否处理得过来。可以使用 pause() 和 resume() 来控制流,调用 pause() 将引起底层停止发送数据事件的发生(不是实际上流读取的暂停),如果写入速度跟不上读取速度的话,只写数据流内部的缓存会爆仓。
    0.10.x 引入了 Streams 2 API,pull-stream 的方式。默认情况下,stream 会使用“拉”模式,流总是从暂停状态开始的,不需要手动暂停,只有当管道 pipe 的末端真正需要数据的时候,数据才会从源头被取出,而当数据可用时,一个 readable 事件也会被抛出。Isaac 表示 stream2 的问题在于大范围重构导致存在不稳定的代码、在 http.js 内部引入了怪异的 non-stream 接口、没有被动监听(不支持 push 模式)、单向兼容模式。
    0.11 引入了 stream3,也就是在 streams2 和 stream1 的方式的混合流的实现。可以用你认为的方式工作,也就是 1 或 2 的方式。另外,当返回 data 事件,主动监听又可以开始工作,http 模块由于性能关系,去掉了一些没用的方法。同时在可写流的方式上也增加了 cork (塞住)支持,当流处于 corked 状态,写进流的数据会进行队列处理,直到流回到 uncorked 状态。这样 Node.js 可以将小数据写入组装成大数据写入,从而减少系统调用和 TCP 往返。而 http 模块已经进行升级,在发送 chunked 的请求和响应时,会使用 corked 模式。通过 strace 输出可以看到增加了 writev 调用,减少了 write 调用。

  • 虚拟机改进涉及的 API 变化

    vm.runInThisContext(code, [options])
    vm.createContext([sandbox])
    vm.isContext(sandbox)
    vm.runInContext(code, contextifiedSandbox, [options])
    vm.runInNewContext(code, [sandbox], [options])
    vm.createScript(code, [filename])
    new vm.Script(code, options)
    script.runInThisContext([options])
    script.runInContext(contextifiedSandbox, [options])
    script.runInNewContext([sandbox], [options])

  • 单进程中跑多个实例

    主要应用于嵌入式开发或本地附加组件开发。见 http://www.infoq.com/cn/articles/nodejs-v012-new-characteristic 一文详解。

  • 多进程上调试

    支持多进程上的 node-inspector 调试,以应对那些只在多进程上出现的问题,并且还提供了 strong-cli 更方便的工具

  • Smalloc

    Smalloc 是一个新的操作数组数据的实验性核心模块。 替换 SlowBuffer 用于分配 external memory。

  • Process

    事件: process.beforeExit,当 node 清空了事件轮询并且没有其他工作计划的才会触发,而 process.exit() 或者 uncaught exceptions 则不会触发该事件。
    process.exitCode

  • Util

    util.debuglog(section)
    自定义 Objects 对象的 inspect() 方法

  • EventEmitter

    EventEmitter.defaultMaxListeners,emitter.setMaxListeners(n) 设置一个分发器的最大 listener 数,而这个函数会立即设置所有EventEmitter 的当前值和默认值。要小心使用。
    请注意, emitter.setMaxListeners(n) 的优先级高于 EventEmitter.defaultMaxListeners

  • Buffer

    基类方法: Buffer.compare(buf1, buf2)
    buf.compare(otherBuffer)
    buf.toArrayBuffer()

  • Stream

    writable.cork()
    writable.uncork()
    writable._writev(chunks, callback)

  • Crypto

    crypto.setEngine(engine, [flags])
    cipher.getAuthTag()
    cipher.setAAD(buffer)
    decipher.setAuthTag(buffer)
    decipher.setAAD(buffer)
    crypto.createDiffieHellman(prime_length, [generator])
    crypto.createDiffieHellman(prime, [prime_encoding], [generator], [generator_encoding])
    diffieHellman.verifyError
    crypto.pbkdf2(password, salt, iterations, keylen, [digest], callback)
    crypto.pbkdf2Sync(password, salt, iterations, keylen, [digest])
    Certificate.verifySpkac(spkac)
    Certificate.exportChallenge(spkac)
    Certificate.exportPublicKey(spkac)

  • TLS

    Perfect Forward Secrecy
    类: tls.TLSSocket
    new tls.TLSSocket(socket, options)
    tls.createSecurePair(…)
    类: CryptoStream
    类: tls.CleartextStream
    事件: ‘OCSPRequest’

  • 文件系统

    fs.write(fd, buffer, offset, length[, position], callback)
    fs.write(fd, data[, position[, encoding]], callback)
    fs.writeSync(fd, buffer, offset, length[, position])
    fs.writeSync(fd, data[, position[, encoding]])

  • Path

    path.isAbsolute(path)

  • Net

    事件: ‘lookup’
    UDP / Datagram Sockets
    dgram.createSocket(options, [callback])

  • DNS

    dns.resolveSoa(hostname, callback)
    dns.getServers()
    dns.setServers(servers)

  • HTTP

    http.METHODS
    response.statusMessage
    new Agent([options])
    agent.maxFreeSockets
    agent.freeSockets
    agent.requests
    agent.destroy()
    request.flush()
    message.rawHeaders
    message.rawTrailers
    message.statusMessage

  • HTTPS

    server.setTimeout(msecs, callback)
    server.timeout

  • REPL

    Event: ‘reset’
    Child Process
    child_process.spawnSync(command, [args], [options])
    child_process.execFileSync(command, [args], [options])
    child_process.execSync(command, [options])

  • ZLIB

    zlib.flush([kind], callback)
    zlib.params(level, strategy, callback)
    Cluster
    cluster.schedulingPolicy


参考资料

[1] New Features Coming to Node.js v0.12
[2] Node.js V0.12新特性之性能优化
[3] What’s New in Node.js v0.12 – Performance Optimizations
[4] What's New in Node.js v0.12(油管)
[5] Breaking C++ APIs in Node.js v0.12
[6] Upcoming Non-breaking API Changes in Node v0.12
[7] API changes between v0.10 and v0.12

等待第一条回复
登录后回复,如无账号,请使用邀请码注册