Node.js是一个服务器端JavaScript解释器,底层采用的还是libevent;它的目标是帮助程序员构建高度可伸缩的应用程序,目前对Node.js 的采用状况,Node.js 官方站点有一些罗列,但是相当不完整。如果你自己公司用到,也可以在 github 上提交自己的 pull-request 来更新这个文档。
https://github.com/joyent/node/wiki/Projects,-Applications,-and-Companies-Using-Node
其实到今天为止,很少有哪些大的互联网公司是和 Node.js 无关的。LinkedIn,Yahho,Paypal, eBay, Walmart 都在将既有的系统向 Node.js 迁移(https://www.quora.com/Node-js/What-companies-are-using-Node-js-in-production 翻墙看)。国内的淘宝、网易、百度等也都有很多项目运行在 Node.js 之上。
2011 年我开始接触 Node.js 的时候,npmjs.org 上只有不到 3,000 个 Node.js 的 packages,今天(2014-3-2)则有 61,897 个,这个数字还在快速增长中。
下面有两个链接,第一个是在讲 Walmart 这几年为什么以及如何迁移到 Node.js 上;第二个则为 eBay 是如何从 Node.js 的怀疑者转变为采用者。
《Announcing ql.io》 这篇文章的最后一段,列出了 eBay 为什么选择 Node.js。
每天都有几百个新的 packages 被发布到 npm 上,小到几行代码,大到万行代码的 Framework。一天有7百万次的包下载(安装到某台电脑上),对于单一开发框架的社区来说,用沸腾的海洋来形容并不过分。
以下应用领域和程序员不适合选择Node.js:
- 计算密集型应用。Javascript 的计算性能是很难和 C 语言代码相比的。当然,也有反例:http://onlinevillage.blogspot.jp/2011/03/is-javascript-is-faster-than-c.html,只不过不具有典型性。
- 需要精密控制内存的分配和释放的场景,如果用 Node.js 实现 Redis 数据库,虽然程序会简单不少,但是 JVM 对内存数据结构的精密控制能力是比不了用 C 语言纯手工打造的。
- 大量且需要频繁通过 C Binding 调用 C library 的情况。这种场景下,往返参数的 Marshal/Unmarshal 的成本可能会大于 C Library 带来的性能提升。
- 实时性要求很高的场景,例如:交换机或者工控机器人。这是因为所有通过垃圾回收机制来管理内存的系统都有可能在 GC 过程中产生停顿,从而影响响应速度,而且很难优化。
- 需要单一进程控制大内存的场景:v8 引擎的设计限制,在 32-bit下有 1GB 最大堆尺寸的限制,在 64-bit下是1.7GB。当然,由于 node.js buffer 的分配不是在 v8 的堆上,因此可以超过此限制。这个限制可以通过向 v8 引擎传递max_old_space_size 参数来超越,但是也会带来 GC 的性能退化。这一问题在几乎所有 GC Based 的系统下都存在。
- 不关心系统吞吐率或者不需要异步调用的场景:例如,自动化脚本,这些脚本不需要关心多用户并发访问的性能消耗。用 Python 这样的“胶水”语言写起来会更简单。
- 某些非通用场景:例如 nginx 对于静态 web server 或者 反向代理的场景是特别设计的,这些场景中 nginx 的性能比 Node.js 要好。
- 强类型强迫症:有些 Java 或者 .NET 过来的程序员会认为只有强类型语言和严格定义的类型系统是专业化的象征,构造这样的系统是架构师的使命,而动态语言只是玩具,只能用来做 Demo 或者前端开发。
- 团队成员难以理解或者接受函数式编程:Javascript 本质上更像函数式语言,有些程序员在理解和使用闭包、高阶函数等概念时总是不能习惯,这个问题在国内的开发团队中还挺普遍的。
- 回调式编程的不习惯:Node.js 的异步IO 大量依赖回调。回调让程序的执行出现了两条路径,出现故障时调用栈也很难理解。这对习惯了同步编程的程序员来说一开始确实是个坎。async, Q promise 等 package 可以缓解这个问题(在 ES6 的Generator 普及之前),不过这也带来了更陡峭的学习曲线。一般情况下,需要半年到一年的习惯过程,当然前提是多看,多写。随着越来越多的经验分享,这个过程也在不断地缩短。
除此之外的领域,或者没有上述问题的,都都可以享受到 Node.js 带来的生产力提升和稳定的性能保障。
性能的争议
不同开发环境间的性能对比从来都是有争议的话题。我只能说,当开发 Web 或 网络环境下的应用时,Node.js 靠以下几个方面来避免出现不必要的性能问题:
- Chrome V8,一个可靠的优秀的虚拟机(hidden classes 和 inline caching),让 Javascript 的运行速度进入了第一阵营(C++, Java, .NET)。
- 异步 IO 大大降低了线程数量,莫名其妙的死锁和等待的概率被降低了很多。大部分场景不用去考虑并发和同步锁,犯错误的机会少。而在 Python 中,异步 IO 并不是标准,并没有被贯穿到所有 Package 中,因此应用程序也就很难获得一致的性能保障。
- 非常轻巧的“内核”。Node.js 的模块分为 Core Modules 和 Userland 两部分。Core Modules 非常精简,只包括 TCP, HTTP, DNS, File System, child processes 和其他一些模块,这些网络库还只有异步版本。相对地,在 Userland 中却有着海量的 Packages。开发应用的时候,我们根据应用的需求来组合 Userland 的 Packages,使得我们的应用程序有机会在一个很低的资源消耗水平下运行(在《Announcing ql.io》中指出,一台开发服务器就可以支持 12 万活跃连接,平均每个连接消耗 2k 内存)。事实上,我开发的 WebSockte 应用在 Raspberry Pi 下都可以支持几百并发长连接(WebSocket)。和那些动辄上万个类的企业开发框架相比,这是一个巨大的优势。这种方式降低了出现问题的概率、查找问题的成本以及减少部署成本。
对 Javascript 的绝对性能的追求一直没有停顿(例如, Mozilla 的 asm.js )。而 Node.js 则在绝对性能的基础上,确保应用程序可以获得稳定和可预测的性能保障( Benchmark 和实际的应用运行往往是两回事)。
Node.js继承了JavaScript 的灵活性,优秀的JS库应当如何选择
可以在 npmjs.org 或者 google 上搜索关键词。如果类似的返回很多,则看其被其他 package 依赖的数量有多少。上 github 上查看 starred 和 forks 的数量,读 issues。
如果是“名人”(substack, visionmedia (TJ Holowaychuk), dominictarr, rvagg 等)写的 Packages 自然会被加分。
最后是把 Git Repo. Clone 或者 Fork 下来, 阅读且注释他们的源代码。这个过程也可以发现很多他们依赖的其他 Packages。这是一个蛮享受的过程,可以学到很多新知识和新的用法。
还有一些乱枪打鸟的方法:
- 在 Tweeter 上关注 @nodenpm,所有在 npm 上发布或者更新的 packages 都会在该 handle 上发布出来。在你的碎片时间没事可以刷刷这个,当然你需要 APN 翻墙。
- 关注一些推荐和评论账号:@dailyjs,@echojs 等。
- Changelog 会提供不错的开源信息汇总,其中包括 Node.js、Javascript 和 npm 栏目。
- Hacker News 则不会让你忽略软件行业的一些“大事”或者新概念。
一个项目开始前的研究阶段,我大约会浏览几十个 Packages,精读其中的5 ~ 10个。开发过程中则根据需要还会不断地发现和精读一些,这些都被我计入了项目的成本。
"自由选择,自己负责",在这个庞大的开发社区了不要指望有人能告诉你“标准答案”。每个人面临的问题域和知识背景都不一样,坚持多看,多试,多思考,享受获得新知识的过程比获得“标准答案”更重要。
在众多的成熟开发框架下为什么需要Node.js
在每一个特定的问题域,大家总是在尝试找到最优解。这个过程是没有终结的,就想最终也会有其他框架代替 Node.js 一样。
今天的 Web,是无数相互连接的 Web Services 组成的,这些连接的本质是异步的。Node.js 天生异步的特性和这个场景的匹配度相对其他开发框架要更高,因此实现起来也更自然。
除此之外,Node.js 的设计基本原则遵循了 《Unix 的编程艺术》,参见 Isaac Z. Schlueter (前任 Node.js 的Gatekeeper,目前负责 npm 的商业化) 的Blog: Unix Philosophy and Node.js。
npm 和 stream 就是上述哲学的产物。
npm
npm 是 Node.js 的包管理系统。包管理系统不是新东西,但是和 npm 的那些前辈和表兄弟不同的是:
- npm 直接集成在 Node.js 中,无需单独安装,发布,安装 packages 非常简单。
- npmjs.org 提供一个统一的入口,你可以看到每个 package 被哪些 packages 所依赖,你也可以一目了然地看到它依赖了誰,以及最近的下载次数。结合到 github 上的更新情况,基本上对一个 package 的基本情况你都能了解到。
- 约定俗成的发布规范:一个 git repo. 让你可以直接找到源代码;README.md 提供简要的说明让消费者能尽快用起来。
对于开发者来说,每一个 package 就是一个 "micro service",是最小重用单元。大部分的 package 只有几百行代码,甚至有些只有几行代码。这样的重用粒度是在其他社区难以想象的。
在 Node.js 的应用的开发过程中,编写 “一口尺寸”(bite-size)的 module 是推荐的编程方式。这也很方便你把这些小 module 封装为 package 分享到社区当中,而不用担心泄露“企业机密”。
npm 是每一个 Noder 的 "home",也是每一个 Node.js 应用的系统架构的一部分。
#p#
Stream
如果说,npm 提供了“开发时重用”的机制,那么 stream 的则提供了“运行时”不同组件之间的“重用”机制。stream 概念和 unix 中的 stream 对应,应用中的每一个 component 则对应 unix 的 filter。下面举一个实际的例子:
在某个应用中,我需要一个 API Server,它的客户端包括 Web Browser,iOS App., 以及网络中的其他 Server。Web Browser 和我们的 API Server 的通信基于 SockJS(当然你也可以选择 SocketIO,或者 Faye 等),它为浏览器兼容提供了适当的 “Fallback” 方案;对于 iOS App.来说,由于不需要考虑浏览器兼容,则采用基于标准的 RFC 6455 的纯 WebSocket 通信协议,这样实现起来更简单;而对于其他 Server 来说,局域网内则用 TCP,互联网上则用 TLS 来保证传输安全。
我在 Node.js 上是这么实现的:
- 利用 donde 构建一个通信无关的 RPC Server 来提供 API 服务。
- 用 Node.js Core Modules 中的 tcp, tls 创建 TCP/TLS Server 并监听,用第三方的 SockJS 和 websocket-stream 分别创建 SockJS 和 WebSocket 的 Server 并监听。
- 当 Client 连接到不同的端口,在 Server 上就会创建基于该协议的 Commnucation Stream,然后创建一个新的 dnode 实例,得到一个 dnode 的 Stream。最后将 Commnucation Stream 和 dnode Stream 像接水管一样接到一起即可。
- net.createServer(function(connStream){
- dnodeStream = dnode({ func1: function(){} });
- connStream.pipe(dnodeStream).pipe connStream;
- });
考虑到在不稳定的网络环境下的自动重连需求,也可以添加 reconnect。
不算你自己 RPC API 的实现逻辑,支持这么多的通信协议的 Server 框架只需要百十行代码,还加上了一定程度的异常处理。
tcp, tls, SockJS,或者 reconnect 的开发者并不能确定“消费者”是如何使用这些 Package 的,但是大家都支持 Stream 的接口,则让自己的 Package 能够被运用到更多的场景。
进一步,我们也可以多路复用一个底层的 Stream。我们把上面的例子再扩展一下:
在既有的通信连接上(connStream),除了提供 RPC API 之外,还需要添加分布式的状态同步功能,例如:通过 Scuttlebutt,完成 Client 与 Server 或 Server 与 Server 之间的常量数据自动同步,而不用为这些功能设计新的 RPC API。通过 mux-demux ,可以复用既有的网络通信 Stream(tcp, SockJS, Web Socket...),避免建立不必要的网络连接。
Stream 是 Node.js 的核心概念之一,其接口和工作方式被广泛地采用,为不同组件在运行时相互通信提供了最基本的支持。
在 Node.js 中,如何使用 Stream 可以用一本书的容量来描述,不是因为 Stream 的概念有多复杂,而是因为其组合方式非常丰富。
小结
三年前接触 Node.js,并且学习和采用,主要原因是因为 Node.js 在解决当今网络应用的问题时,提供了高性能、高可靠和低功耗的方法。高性能、高可靠和低功耗,不是在于 Node.js 做了什么,而是在于 Node.js 不做什么。Node.js 和 Javascript 的概念,在 Java 或者其他开发框架中都能找到对应的概念。但是 Node.js 仅保留了它认为最重要的部分作为 Core Modules,其他都让给了 User Land,这才是高性能、高可靠和低功耗的最本质的保障。
随着 Node.js 这三年的发展,今天使我浸淫其中的理由已经不是之前的那些特点了。
npm 建立了一个“人人为我,我为人人”社区,无论你是一个入门级的 Noder,还是一个多年的老兵,都在自觉或不自觉地从这个社区吸取营养,也在不断地回馈社区。在使用 npm 的过程中,你会很自然地发现,将自己的应用切割为尽量小的 Modules,发布为公有的 Packages,配上一个简单扼要的 README.md,反而是最有效率的系统架构方式。
Node.js 所遵循的 Unix 设计哲学,又提供了最简单有效的复用规范。简单有效,才会被大家自觉采用,采用得越多,重用的可能性就更大。以Express 4.0 ( MEAN 架构中的那个 'E' )为例,这么一个流行的 MVC Web Framework的核心代码只有 2,600 多行(不算测试,中间件和例子,但是包括注释)。
npm 和 github 一起,为今天的软件生产提供了新的生产关系,这也是当前 Node.js 超越其他社区的根本原因。不是单纯的性能,也不仅仅是因为动态语言,甚至不是因为大量熟悉 Javascript 的前端程序员(和后端程序员相比,由于缺少系统性的思维,前端 Javascript 程序员掌握 Node.js 未必有多少优势 ),而是以更加便捷的分享式开发为基础的生产关系实实在在地提升了软件生产力。
Node.js的驾驭能力
如果“复杂的后端程序” 等于 “庞大的继承树”,“强类型安全”,“精细的异常定义和处理”,那么 Node.js 当然无法驾驭。因为 Node.js 和 Java, .NET 相比,是一颗独立的“科技树”。原型继承、函数式编程、模块系统、回调...,这些概念和编程方式对习惯了 Java 以及 .NET 的程序员来说不仅仅是不熟悉,甚至一开始会产生“不舒服的”感觉。
从我的体验来看(Basic->C->VB->Delphi->.NET->Node.js),这种不舒服更多地来自于之前对严谨的类型系统的信仰。原本所谓的“架构师”,承担着整个应用或项目的类型系统的建设任务,对任何破坏类型一致性的行为都会自然而然的产生抵触情绪。要想掌握 Node.js,最好的方法是先从 Java, .NET 这颗“科技树”上爬下来,清空自己,然后重新爬 Node.js 这棵树。
程序员从 Java, .NET 可以学到面向对象和泛型这些重用手段,而在 Node.js 的世界中,当你接触到大量来自于完全不同背景的程序员所编写的 Packages 的时候,你也会意识到,不是每样东西都是“类”,重用也不一定都基于继承。虽然有人试图在 Node.js 中克隆之前自己熟悉的类型系统,但是更多的程序员则在不断尝试更优雅、简单的编写方式。在 Node.js 的开发过程中,没有所谓的“最佳实践”,类似的问题总会有人尝试不同的解决方法。对于一个勤于思考和反思的程序员,这是一个充满乐趣的过程。反之,如果你的团队是由缺少独立思考或者独立解决问题的程序员组成的,那么 Node.js 确实不适合。你需要用强类型语言搭好一个受限的框架,然后让体力型的队友去填空。
我们公司只有两个程序员,一个负责 iOS 开发,而我负责“复杂”后端程序和 Web Browser 开发。如果用 Java 或者 .NET 来开发,完成同样的功能需要至少三倍以上的人力。
Node.js能否统一前后端
完全统一既不可能,也没必要。再说这个所谓的“统一”与其放到 Node.js 脑袋上,不如送给 Javascript,因为 Javascript 用到的场景太多了。让我们看几个事实:
- Node.js 让我们可以用 Javascript 写后台程序
- Node.js 让我们写 Web Browser 前端:一方面,可以通过 Grunt 或者其他持续集成工具生成可发布的前端静态网站内容(例如: Bootstrap); 另一方面,也可以通过 Browserify 在前端代码中使用 Node.js 的 Modules,让前后台代码使用统一的代码基(例如:domready。很多 Node.js 的 Modules 本身就经过了浏览器兼容测试,可以同时运行在两端)。
- 用 Node.js 开发桌面应用,例如:https://github.com/rogerwang/node-webkit/wiki/List-of-apps-and-companies-using-node-webkit,列出了基于 node-webkit 的桌面应用列表。
- 即使在性能受限的移动设备中,我们也可以通过 Javascript Binding 将一部分应用逻辑用 Javascript 来实现,而 UI 的渲染还是 Native 的方式。这在很多游戏中已经被采用(http://www.zhihu.com/question/21130385)。只要用到 Javascript,或者说,随着 Javascript 代码基的扩大,npm based 的包管理方式就会通过 Browserify 的方式被慢慢引入。
除了第二个问题提到的那些不适合 Node.js 的地方,其他领域想彻底不碰 Node.js 是很难的。
Node.js发展方向
如果你的老板不让你碰 Node.js,你需要让他支付青春损失费。开个玩笑:)
投资在 Node.js 不会吃亏。
基于 Browserify 的贡献,前后台一致的代码基正在成为现实(在我的一个项目中已经如此,Web Client 通过 Node.js 的 stream 和后端传递数据 )。你可以看到在前端使用原本为后台写的Module,或者用写后端程序的方法写前端代码,例如:domready。浏览器中无需运行一个完整的 Node.js,只要打包好需要的 Modules 下载到浏览器执行即可。
在问题 5 中,大家已经看到 Node.js 在分布式计算领域的应用能力。 在问题 8 中,大家可以看到 Node.js 在客户端开发中所扮演的角色。
传统的数据库这一领域也在发生变化。通用的数据库系统在未来会慢慢“失宠”,“乐高积木”化的存储服务会流行起来。Hackers 们围绕着 LevelUp构建自己的存储引擎,从 key/value,到Graph DB;从基于 B 树的一维索引到基于 R-Tree 的多维索引;从能够在浏览器中运行的嵌入数据库到支持成千上万访问者,高可用的数据库系统;从支持两阶段提交的 Transaction 到,到支持实时增量的 Map-Reduce。在我的一个项目中已经开始采用这种方法,为特定的存储和查询需求构建特定的存储服务。这在以前是不可想象的,但是现在,也就是一个程序员的工作吧。
我是一个自己写程序的“产品经理”。每年有8个月是集中开发的时间,剩下的时间则是负责产品设计。关注的方向从 Web、Mobile App. 到后端系统。Node.js 给我提供了无数块“乐高积木”,让我可以拼装自己的玩具,这是很快乐的体验的过程。遗憾的就是时间不够多,有那么多东西没时间去了解,去体会其他人的奇思妙想。小公司也是对成本极度敏感的,如果没有 Node.js 很多东西连想都不敢想。