NodeParty 杭州站会议纪实:Jscex,SeaJS 与 MyFOX

  • 赵劼

2011 年 5 月 19 日

话题:.NET编程语言JavaScript架构DevOps语言 & 开发

​CNodeJS.ORG是由淘宝工程师志愿发起的关注 Node.js 技术的原创社区,致力于 Node.js 技术的研究和推广。5 月 14 日 CNodeJS.ORG 在浙江大学玉泉校区开展了第二次 NodeParty 技术交流活动,来自盛大和淘宝的工程师分享了他们在 Node.js 使用中的经验,思考及体会。主要话题涉及异步编程方式,模块管理与分布式的数据统计中间件等等。

第一场演讲是由盛大创新院的老赵带来的 Jscex 项目介绍。Node.js 的一大特点便是“无阻塞”编程,正是这种纯异步的的特质让它可以应对互联网服务对性能和伸缩性的要求。但老赵认为,程序员早已习惯于使用线性的方式表达一段算法,但异步编程却迫使我们使用大量回调函数,最终代码逻辑会被拆分地支离破碎。这样我们无法使用 while/for/do 来执行循环,使用 if…else 进行逻辑判断,或是使用 try…catch…finally 来处理异常,甚至简单的异步操作顺序执行也要用回调函数串联起来。因此,与传统编程相比,异步编程无疑会显著提高代码编写的复杂度。老赵在演讲里演示了一段冒泡排序的简单算法,在将其异步化(编写为动画)之后代码长度增加了不少,他同时表示,这里的问题并不仅仅是代码长度增加了,更关键的是代码的语义丢失严重。例如,在异步化之后几乎没有人能够轻易看出这是一段冒泡排序算法。

Jscex 便是为了解决这个问题而诞生的。从某种程度上说,Jscex 是 F# 中“计算表达式”特性的 JavaScript 移植,不过严格说来是受到该特性的启发,而针对 JavaScript 设计而实现。老赵表示,由于 JavaScript 中存在 break、continue 或是 return 这类中断执行流的语言特性,因此在 JavaScript 中实现“计算表达式”相对来说可能更复杂一些。Jscex 的核心是一个使用 JavaScript 编写的 JavaScript 编译器,它将一段普通的,线性的 JavaScript 函数编译为 monadic 形式,继而可以异步地执行。老赵提到,Jscex 的首要原则便是为 JavaScript 程序员保留一切习惯。Jscex 不是一门新语言,它支持几乎完整的 JavaScript 语法特性及语义,包括编程体验等方面可谓没有丝毫改变。Jscex 的 JIT 编译器会在运行时获取一段函数的源代码(通过其 toString 方法),解析成抽象语法树,再生成新的代码,因此它可以直接运行在所有支持 ECMAScript 3 的引擎上,包括 Web 页面,浏览器插件以及 Node.js 等等。使用 Jscex 后,程序员完全是以同步的方式编写异步代码。此外,Jscex 也提供了 AOT 编译器,可以在运行时之前编译代码。AOT 编译后后的代码可以独立于编译器脚本,十分适合生产环境上发布使用。

在会上老赵还演示了 Jscex 的一些使用方式,高级模式,还有它是如何解决基于事件的异步模型中的一些限制,性能分析,调试支持等等。Jscex 基于 BSD 开源协议发布,在它的 Github 页面上有更为详细的说明。此外,Jscex 在SNDACode站点上也有与英文内容保持同步的中文说明

第二场演讲由来自淘宝的前端工程师玉伯介绍他创建的 SeaJS 项目。在项目开始,他展示了苹果主页上的 HTML 代码,其中引入了 9 个零碎的 JavaScript 脚本文件,导致在 YSlow 里的评分很低。玉伯表示这种做法是十分不可取的,严重违反前端开发的优秀实践。接着他又展示了 Yahoo 在这方面的处理方式。Yahoo 页面上使用了大量的 JavaScript 模块,在服务器端合并输出,方式不那么优雅,复杂度也比较高。玉伯认为,Node.js 中的模块管理方式及 require 引入机制让他觉得十分惊艳。Node.js 使用CommonJS Module 1.0规范,每个 JavaScript 文件便是一个模块,每个模块中可以引入其他模块,并通过 exports 来输出需要对外暴露的成员。

不过玉伯同时提到,V8 引擎直接提供了相关接口,因此 Node.js 可以轻易让一个 JavaScript 文件在某个特定的上下文里执行,从而不会污染到全局,但是在浏览器环境中几乎做不到这一点。对于在浏览器中使用的模块加载器等方面,社区中也有过一些努力(例如 RequireJS),但实际使用中还是过于复杂。假如再同时考虑到脚本下载的阻塞,跨域问题,浏览器兼容性,与 CSS 结合等方面,这方面的问题几乎找不到完美的解决方案。因此业界也出现了一些思考,例如“是否一定要遵循 CommonJS 规范”等等。

玉伯的 SeaJS 也是一个模块加载器,它要求每个模块在编写时遵循一个简单的规范,这样便可以在页面中使用 require 引入该模块。绝大部分常用的类库 / 框架(例如 jQuery,MooTools,YUI,Backbone.js 等等)都可以通过简单的修改成为支持 SeaJS 加载的模块。SeaJS 中也提供了一些 Ant 脚本来辅助这方面工作。在演示中,玉伯将之前老赵的 Jscex 项目进行了模块化,还“尝试”着优化了苹果的主页。他表示,SeaJS 模块同样可以在 Node.js 里运行,做到“编写一次、到处执行”的效果。在演讲的问答环节中,听众提出了有关脚本依赖,重复加载、重复执行方面的问题,玉伯针对这些问题一一作了解答。

第三场演讲则是淘宝的朋春讲解的一个 Node.js 在淘宝中的使用案例:MyFOX。MyFOX 是一个数据处理中间件,负责从一个 MySQL 集群中提取数据,计算,并输出统计结果。用户提交一段 SQL 语句,MyFOX 根据该 SQL 命令的语义,生成各个数据库分片所需要执行的查询语句,并发送至各个分片,再将结果进行汇总和计算。MyFOX 的特点是 CPU 密集,无文件 IO,并只处理只读数据。起初 MyFOX 使用 PHP 编写,但遇到许多问题。例如 PHP 是单线程的,MySQL 又需要阻塞查询,因此很难并发请求数据,后来的解决方案是使用 nginx 和 dirzzle,并基于 HTTP 协议实现接口,并通过 curl_multi_get 命令进行请求。不过 MyFOX 项目组最终还是决定使用 Node.js 来实现 MyFOX。

朋春表示,选择 Node.js 有许多方面的原因,比如考虑了兴趣及社区发展,同时也希望可以提高并发能力,榨干 CPU。例如,频繁地打开和关闭连接会让大量端口处于等待状态,当并发数量上去之后,时常会因为端口不够用(处于 TIME_WAIT 状态)而导致连接失败。之前往往是通过修改系统设置来减少等待时间以绕开这个错误,然而使用连接池便可以很好地解决这个问题。此外,以前 MyFOX 会在某些缓存失效的情况下出现十分密集的访问压力,使用 Node.js 便可以共享查询状态,让某些请求“等待片刻”,以便系统重新填充缓存内容。

朋春在演讲中还阐述了 MyFOX 的结构设计,例如分层的引入,master、worker 及 router 组件的职责等等,对于服务的出错处理及监控重启等方面也提出了一些建议供大家参考。此外,在监控 Node.js 服务中彭春也观察到一些可能造成的问题,例如内存使用及 GC 对响应时间的影响,并提出了一些优化的设想。

目前,NodeParty 活动已经公开了三场演讲的幻灯片,在老赵博客上也提供了第一场演讲的中英文幻灯片

.NET编程语言JavaScript架构DevOps语言 & 开发