acm-header
登录

ACM通信

实践

LinkedIn的节点:追求更薄、更轻、更快


基兰·普拉萨德,凯利·诺顿和特里·科塔

从右上顺时针方向:Kiran Prasad, LinkedIn;凯利诺顿,Homebase.io;以及Terry Coatta,海洋学习系统公司。

图片来源:Brian Greenberg / Andrij Borys Associates

回到顶部

Node.js是服务器端基于javascript的软件平台,用于构建可伸缩的网络应用程序,在过去几年里在许多开发人员中风靡一时,尽管它的流行也激怒了一些人,他们发布了大量负面的博客文章,指出它的缺陷。尽管Node是新的,未经测试,但它仍然赢得了更多的转换。

2011年,LinkedIn也加入了这一运动,选择在Node中重建其核心移动服务。这个一直依赖Ruby on Rails的专业社交网站正在寻求性能和可伸缩性的提高。由于广泛使用非阻塞原语和单线程事件循环,Node看起来很有前途。

2009年,Ryan Dahl(现就职于Joyent,是Node的发起人和维护者)创建了Node.js,不久之后,开发人员就抓住了它。因为Node使用JavaScript(一种与Web应用程序的客户端密切相关的语言),它为在客户端工作的开发人员在服务器端也工作相应的功能扫清了道路。

Kiran普拉萨德2011年,他以移动工程高级总监的身份加入领英,带领公司向Node转型。在服务器端,LinkedIn的整个移动前端现在完全是用Node构建的。普拉萨德承认,Node并不是适用于所有工作的最佳工具,但在分析了LinkedIn的系统后,普拉萨德和他的团队认为,要提高效率,需要一个事件驱动的系统。Node的另一个吸引力在于它既轻薄又支持对数据对象的直接操作。

普拉萨德对自己在LinkedIn移动服务领域的角色做好了充分准备,他已经在Palm和Handspring的WebOS平台上积累了多年的移动应用经验,此外还担任过移动Web软件的独立开发者(作为Sliced Simple的首席执行官和Aliaron的首席技术官)。

他在这里谈到了LinkedIn对Node.js的采用凯利诺顿而且特里Coatta.诺顿是第一批开发谷歌Web Toolkit (GWT)的软件工程师之一,之前与人共同创建了Homebase。Io公司开发下一代营销工具。

Coatta是海洋学习系统公司的首席技术官,该公司开发了一套针对海洋行业的学习管理系统。他曾任职于AssociCom、Vitrium Systems、GPS Industries和Silicon Chalk。

凯利诺顿:告诉我们是什么让LinkedIn决定使用Node.js。

KIRAN普拉萨德:我们运行的是一个基于Ruby on Rails过程的系统,很明显它无法按我们需要的方式扩展。我想,如果你愿意投入足够的资金,你总是可以扩大一个项目的规模,但显然这似乎不是正确的做法。此外,在移动模型中有很多微连接,我们可以看到基于过程的方法在Ruby on Rails堆栈中会遇到困难。

我们还注意到,Rails的性能受到了很大的冲击,因为我们做了大量的字符串操作,而我们使用的Ruby解释器版本很难对所有的小字符串对象进行垃圾收集。它也没有特别针对JSON (JavaScript对象表示法)翻译进行优化,这是我们的后端提供给我们的,也是我们的前端希望使用的。

显然,Ruby on Rails更像是一个Web堆栈,因为它的真正价值在于它为该结构提供的模板,以及它为应用程序和控制器提供的一些框架概念。但控制器和视图实际上会向下移动到客户端当你在做客户端渲染时,这就是在移动系统中发生的。

随着你在LinkedIn等网站上发现的更大、更大规模的堆栈,你也开始打破这种模式。也就是说,此时您并没有真正进入Active Record on Rails内部,因为您实际上是向下移动到其他服务器或服务中。这意味着中间层开始变得非常薄,并且真正专注于字符串操作。

所以当我们真正开始考虑这个问题时,我们说:“这感觉肯定不再那么合适了。它不是为我们现在想要实现的目标而设计的。”那么,我们可以用什么来代替它呢?它必须是有事件的,善于操纵字符串,轻便,快速,易于使用的东西。我们开始研究Ruby中的一些事件框架,比如EventMachine,以及Python中的Twisted。但是当我们第一次开始研究这个问题时,似乎没有主流的事件Java框架。当我们在2011年初进行分析时,由于某些原因它并没有出现在我们的雷达上。

诺顿:除了事件驱动之外,Node还有什么吸引人的地方?

普拉萨德:我们研究了Node,并对它运行了一些负载,这时它会向其他6个服务发送一个请求,获取数据,合并数据,然后以一种相当简单的方式弹出它。我们一直运行到大约50000个QPS(每秒查询)。在这个过程中,我们发现Node的速度大约是我们以前使用的Node的20倍,而且它的内存占用也更小。


基兰·普拉萨德:Node之所以这么快,这么好,是因为它又轻又薄。它几乎没有任何内容,因此您添加到其中的每一件小事、您想要与之一起使用的每一个额外Node模块都是有代价的。


显然,Node.js在技术方面之外还提供了其他好处。JavaScript是一种很多人都能理解并且很容易使用的语言。此外,Node当时得到了大量宣传,现在仍然如此,这并没有什么坏处。在某种程度上,这让我更容易招人。

特里COATTA:您提到您已经不再使用Ruby中的活动记录作为模型表示,但显然该模型必须以某种方式结束。为什么不选择使用模型已有的基础设施来处理中间件呢?

普拉萨德关于模型的问题是,它们实际上是围绕对象设计的。他们有属性;他们有方法;它们是结构化和静态类型的;你试图创造一个环境,让它们非常坚固,这样你就能确切地知道每个物体是什么。这正是我们第一次接触面向对象设计时学到的方法。

所以你会认为更面向对象的语言对于构建这样的系统是理想的。然后它会转移到视图控制器,在移动系统中,视图控制器会一直被推到客户端。那么问题来了:中间还剩下什么?基本上,你会发现这里只有一堆有效地操作数据哈希的函数,以便格式化它们。现在你只需要格式化和一点聚合。

我不确定我当时是否清晰地表达了我们为什么选择Node.js。现在,在使用了几年之后,很明显,在这个层中,它本质上是连接前端(现在实际上是在客户机中)和后端(恰好是数据模型)的粘合剂,函数类语言实际上是最适合的。在这一点上,我们用Java编写所有的后端内容,我们使用的所有Java堆栈都是基于过程的。

COATTA有了在中间层使用Node.js的经验,那么,您会考虑将它用于后端吗?

普拉萨德:现在,我们正致力于创建一个针对移动设备的数据存储,作为其中的一部分,我们分析了是否应该用Node.js和JavaScript来构建它。结果是团队想要更精确一点的东西。实际上,我试图避开经典的编程内容,比如采用更静态类型的方法,但团队肯定希望能够用特定的方法和属性定义对象,并保证没有人会搞砸它。他们不希望任何人接管原型或对它做任何事情。不过,从性能的角度来看,我愿意尝试一个事件驱动的框架。目前,我们正在使用Rest。Li在内部做一些后端,数据存储的东西,因为我们真的相信事件驱动的东西已经改变了我们的架构。

COATTA:就使用Node.js所观察到的性能加速而言,您是否也为其他正在考虑用于中间件层的语言构建了轻量级原型?

普拉萨德:我们使用Ruby和Python的事件框架EventMachine和Twisted做了一些原型。事实证明,就原始吞吐量而言,Node的速度是这两种方法的25倍。更令人兴奋的是,Node只花了两三个小时就写好了Node的原型,而写EventMachine和Twisted却要花上一两天的时间,因为我们需要下载更多的东西。

例如,您需要确保您使用的不是Python中的标准HTTP库,而是Async HTTP库。这是一种适用于所有领域的东西。无论我们想做什么,我们都不能使用标准的Python库。相反,我们必须使用特殊的Twisted版本。Ruby也是如此。与社区中的许多人一样,我们发现使用Node是多么容易,因为您需要的所有东西基本上都默认存在。此外,我们可以用Node更快地完成工作,这一点非常重要。那只是另一种形式的表演,对吧?开发人员的生产力肯定很重要。

内存占用也是一个因素。我们观察了vm(虚拟机)在每一种语言中的工作情况,V8 JavaScript引擎将其他一切都抛在了后面。我们用所有这些操作做了50000个QPS,我们在大约20MB到25MB的内存中运行。在EventMachine和Twisted中,仅仅加载所有必要的类来执行异步内容而不是标准内容大约需要60MB到80MB。在原始水平上,Node只需要大约一半的内存占用就能运行。

诺顿:在一个更加“模式化”的环境中,你会错过哪些东西?

普拉萨德:因为我们接受了JavaScript的函数性质,所以我们不认为我们首先必须将从后端传入的所有内容转换为一组对象和这些对象上的一组方法。这也意味着我们不需要整理这些对象的层次结构,子类是什么,基类是什么,所有这些东西是如何结构的,以及所有这些不同对象之间的关系是什么。但这正是在基于模型和对象的系统中需要做的事情。所以对我们来说,这更像是在说,“好,你要击中这三个端点,然后你要合并数据,弹出另一个对象。”

但实际上,它不是一个对象,而是一个散列,对吧?你消耗的是相邻的东西然后再拿出一些相邻的东西。这就像你有一个过滤器,你可以让一个流通过。数据通过,然后从另一边弹出,这让你完全绕过所有的思考对象是什么,它们如何工作,以及它们如何相互作用。这让我们更快地到达我们想要到达的地方。

现在我们做完了这些,我们要回去做一些函数。现在,他们想跟谁说话就跟谁说话。如果函数A想要与Profile、Companies和Jobs对话,然后合并它们,那么它就会继续与Profiles、Companies和Jobs对话。然后,如果函数B想要与Profiles, Companies和其他东西对话,它会直接执行这个操作。但是这两个函数a和b不会使用相同的函数接口来与Profiles或Companies通信。问题是,如果我们与公司沟通的方式存在漏洞,我就必须在两个地方进行修复。如果我想要添加日志记录,这样我就可以看到所有有人与公司通话的实例,事情并不是集中的,所以一切都可以很好地通过。虽然我们没有创建对象层,但我们开始认识到,至少对于RESTful api,我们需要创建一组函数,它们位于我们希望与之通信的每个资源类型的前面。这有点像代理接口。这是我们一路走来学到的东西之一,现在我们开始通过创建这个新的抽象层来解决这个问题。

COATTA:您提到重构代码以引入额外的层。如果考虑到您现在在重构方面所投入的时间,您是否认为创建代码库所花费的时间与采用其他方法所花费的时间大致相同?

普拉萨德:我猜不会,因为这不仅仅是编码本身需要多长时间。它还涉及到整个过程的其他方面。例如,当你写了你的应用程序,然后输入“Node”来运行应用程序,它只需要大约20100毫秒就会出现。在Ruby中,仅仅启动Rails控制台有时就需要1530秒。在任何情况下,我也不会严格地从编码的角度说Node存在缺陷。Node的设计更薄、更轻、更快。所以我每天所做的每一小步,每一个细微的差别最终都变得更快。

对于更结构化的语言,您必须考虑诸如编译时间和构建时间之类的问题。然后,您最终构建了本质上的热插拔环境,其中环境正在运行,但IDE也能够连接和操作运行时。您几乎必须这样做,因为启动、进行更改、然后再次启动需要很长时间。Node消除了所有这些问题,因为它的速度非常快。

是的,重构增加了编码时间,同时也降低了我们最初所享受的简单性,那时我们只是在大量地删减内容。但我认为,我们之所以能够实现整体效率,是因为Node非常轻薄,足以弥补我们不得不做的少量重构。

COATTA:如果你打算投入到另一个基于Node的项目中,你会尝试在最初构建更多的结构吗?

普拉萨德:我不认为这是一个特定于节点的问题。这更像是个人喜好的问题。我个人的观点是,前端UI代码通常只会持续1年半到2年。这些代码很少能持续5到10年之久。我认为,这样做的原因甚至与代码的质量没有太大关系,而是更多地受到技术发展的驱动,以及软件开发人员被鼓励以四年为周期工作的事实。您不断地让新的人员检查您的代码,我知道无论何时我查看任何东西,即使它只是一个表,我都可以构建一个更好的表。所以我相信自然的趋势是,每一年半到两年,一组新的人将会检查某些特定的代码库,并决定他们可以做得更好。

考虑到这一点,只要是模块化的,重写代码库的一整块,或者甚至重写整个代码库,花上一两个月的时间,可能比缓慢地演进代码库要快得多。所以,如果让我重新做这个项目,我可能会用同样的方法来做。在任何情况下,我更倾向于先构建一个项目,然后推出它,如果可以的话,再提取一个平台,而不是先构建一个完美的平台和所有组件,然后试图以正确的顺序将它们连接起来。我认为只有当你在生产中真正运行了一些东西,并且能够看到你的痛点时,你才能知道正确的顺序是什么。然后,您就可以知道实际需要在哪里提取库并开始为此做些什么了。

一旦决定转换为Node, Prasad的团队必须找出实现和维护的最佳方法。因为LinkedIn的移动服务团队在很大程度上已经习惯了Ruby on Rails,所以他们在设置Node.js结构时模仿了之前Rails结构的一部分,从而可以快速启动项目。这个团队很小,所以Prasad能够监视向JavaScript的过渡,并快速发现问题。

Node.js中的编程是事件驱动的,因此需要不同的方法。尽管涉及到少量的重构,但团队不需要创建新的抽象或附加函数。他们所做的大多是句法性质的。它们还摆脱了抽象层,这大大减少了代码库的大小。

诺顿:你说你喜欢直接投入到一个项目中,以最快的方式做事。我完全同意。事实上,我想说的是,我自己的哲学是,因为你真的不知道在一个项目中你需要优化什么,那么你真正应该着手优化的是你改变事物的能力。

所以我真的很好奇你是如何组织你的代码的,你用了什么实践让你一开始快速前进,然后随着你的项目的约束越来越清晰,继续快速前进。

普拉萨德:当时我们的很多人都是Ruby on Rails的开发人员,所以他们熟悉目录树结构和Rails世界中使用的术语。我们模仿了这个术语,这给了我们一个巨大的起步。Node非常简陋,很像Rails,这一点也很有帮助,但这也有点可怕,因为您不确定如何构造东西,而且没有指导来告诉您如何做。

我们做出的第二个有用的决定是将所有的控制器和视图放在客户端,而模型放在后端。从目录结构的角度来看,这意味着我们不会在模型结构或视图目录中显示任何文件。虽然我们用了这个术语控制器在美国,我们实际上更多地与格式化器合作,在某种意义上,你会输入一些东西,发出几个请求,格式化它,然后弹出它。

这意味着我们仍然使用旧的Rails结构,只是大多数目录实际上是空的,顺便说一下,空目录实际上是一件好事。我们会把目录留在那里这样人们就不会在那个目录中创建东西。它就像一个代码审查工具,它说:“如果我们开始在那个目录中创建东西,我们一定是真的做错了什么。”这只是我们想要如何使用Node的服务器设计模式。

我们最初做的另一件有用的事情是建立一个基本的测试环境和框架。Rails,以及Python的Django框架,在TDD(测试驱动开发)甚至BDD(行为驱动开发)上都非常强大,在那里你可以在填写所有代码之前编写测试和序列。当有一个测试框架的时候,它是一个非常有效的模型。实际上,我们使用已经存在的测试框架,并使用我们编写的一些脚本将其置于目录结构之上,以使两者进行交互。我们开始使用誓言,但三个月后我们用Mocha编写所有自己的测试。

你可以说,设置一个测试环境和框架是另一种代码模式,但它可能比使用任何其他语言都要困难——特别是在处理Node的事件方面。即使我们最终重构了代码,我们也不需要做任何重要的事情,比如提出新的抽象或任何额外的函数。我们所做的大部分工作本质上都是句法性质的。还有一堆函数性的东西,例如,Node的概念,在每个回调中第一个参数应该是Error,这是完全有意义的。

但是你不知道第二个第三个和第四个论点是什么。还是只有一个论点?还是五个论点?你如何处理这个,做回调之后会是什么样子?假设您现在想要更改回调签名。如何告诉正在调用函数并期望回调的每个人签名已经更改?我不知道这是节点特有的还是所有JavaScript都有的,但我们确实花了一段时间才弄清楚。

诺顿:你的团队是如何沟通界面边界的?习惯于使用类型的人不会愿意放弃接口,因为它们提供了一种具体的文档形式,基本上允许一个团队成员对另一个成员说,“这是我的意图。我料想你会用这种特别的方式称呼我。”更重要的是,如果这种调用方式最终不存在,这可能意味着它是一个您没有考虑过的用例,因此它可能无法工作。

普拉萨德:我认为代码内部的库之间以及客户机和服务器之间都有接口。对于客户端和服务器之间的接口,我们使用REST(具象化状态传输),并且我们有一个非常明确的模型,其中我们有由Node服务器返回的所谓“基于视图的模型”。我们只需要记录这些,然后说:“嘿,这是REST接口,这是我们支持的。”这基本上是一个版本化的接口结构。实际上,这是典型的REST。

在代码库中,我们大量使用模块系统。每个REST端点都有一个文件,该文件表示该端点的所有响应,以及映射到路由的模块的公共接口。你可以有很多你想要的函数,但是你所拥有的将会从那个模块中导出。通过这种方式,您实际上最终指定了要公开的函数集。这就是我们所使用的界面。


凯利·诺顿:我自己的哲学是,既然你不知道你真正需要优化的项目是什么,那么你真正应该着手优化的是你改变事物的能力。


然后,在结构上,我们做了一件非常简单的事情:模块内的每个函数,无论是公共的还是私有的,都用老派的C风格定义。你可以在注释中写上“私有”,然后列出一些私有函数。或者你可以添加一个注释,写上“公共”,然后列出你所有的公共功能。这就像你以前用C语言做的一样,对吧?你有一堆功能,你把私人的放在一组,公共的放在另一组。那么您的头文件实际上将公开您的公共接口。所有这些都发生在一个文件中。我们没有头文件,只有模块。Exports有效地充当了我们的标题。

诺顿:另一个重要的沟通考虑因素与帮助不习惯编写JavaScript的团队绕过他们可能遇到的雷区有关。

普拉萨德当我们开始做这件事的时候,我们的团队只有四个人,所以我可以监督每一次登记。每当我看到任何奇怪的事情,我就会询问它,并不一定是因为我认为它是错误的,而是因为我想知道我们为什么要使用这种特殊的模式。我们很容易弄清楚为什么事情会以某种方式进行,也很容易弄清楚我们下一步要做什么。现在我们的团队更大了,我们正在渗透我们所做选择背后的推理的细微差别,这无疑是更加困难的。现在,每当有新人加入我们的团队时,我们都会举办3到5天的新兵训练营,这让我们有机会解释:“我们是这样做的。我们知道这可能看起来有点奇怪,但这就是我们这样做的原因。”我认为这可能是暴露这些代码模式的最好方法。

COATTA:你认为这些代码模式中哪一个是最重要的?

普拉萨德:我们最终使用Step作为我们的流控制库。它就像一个超级简单的Stem,有两个主要结构。我们增加了一些,最后增加了第三个。基本上,Step具有瀑布回调的概念,这意味着您将一个函数数组传递给Step,然后它将按顺序调用每个函数,这样当第一个函数返回时,第二个函数将被调用,以此类推。Step保证了这个序列的顺序,所以即使函数是异步的,也就是说它将做一些事件,该函数将被传递回调,它必须调用一旦它完成它的事情。独立于每个函数是同步的还是异步的,瀑布将按顺序执行。


TERRY COATTA:如果你考虑到你现在在重构方面投入的时间,你是否认为你最终创建代码库所花的时间与你采用其他方法所花的时间大致相同?


Step还包括分组法和并行法。我们大量使用分组法。这意味着你可以给它一组函数,它会并行执行所有的函数,然后在所有函数执行完后返回。

有一个细微差别对我们来说非常重要。如果我们有一个包含三个函数的组,其中一个函数被破坏了,Step将不会捕获其他两个函数的响应。相反,它只会调用回调并说"抱歉,我出错了"

这种环境的缺点是,如果我调用6个东西,其中2个是必需的,4个是可选的,我不介意在特定的超时下等待所有的东西。但如果其中一个可选的东西出错了,那么整个block就会出错。这对我们来说并不理想。因此,我们创建了一个名为GroupKeep的函数,它遍历并执行所有操作,然后如果出现错误,它将把错误保存在一个数组中。这样,当调用出去进行回调时,就会有这个错误数组。根据错误的位置,您可以很好地了解它是与必需的内容相关还是与可选的内容相关。这使得编写可以在任何需要的地方继续流程的代码成为可能。

Node.js的轻薄特性比什么都更吸引Prasad和他的团队。这使得代码减少的范围被证明是巨大的,从60,000行减少到只有几千行。它们现在也基本上没有框架,因此消除了许多无关的代码。此外,Node的事件驱动方法需要更少的资源,并将更多的功能转移到客户端。最后,它采用了一种功能性的方法来去除抽象层。总之,这一切都是为了支持大量设备上的大量用户的实时支持。

COATTA:当您谈到您如何快速地启动并运行初始Node原型时,我怀疑您是否也实现了一些代码减少。

普拉萨德:绝对的。与最初的版本相比,我们的Node代码基数有了一些增长,但仍然有1,000到2,000行代码。相比之下,我们之前使用的Ruby代码库大约有60,000行代码。减少的最大原因是我们当前的代码库基本上是没有框架的,这意味着里面没有很多粗劣的东西。

第二大原因与我们现在采用的更函数化的方法有关,而不是面向对象的方法,这对我们来说是一个重要的转变。在Ruby中,自然倾向于创建一个对象,从本质上封装每种通信和类型。尽管Ruby实际上是一种函数式语言,但它的类和对象概念比JavaScript强得多。因此,在我们早期的代码库中,我们有许多抽象层和对象,它们是在更大的组件化、可重构性和可重用性的幌子下创建的。然而,回想起来,我们真的不需要这些。

代码减少的另一个重要原因是MVC(模型-视图-控制器)模型背后的动力,至少对于移动系统和基于web的系统来说是这样。以前,我们主要使用服务器端渲染。现在,随着模板和视图以及渲染转移到客户端,当然,很多代码已经消失了。随之而来的是一种新的信任和信念,即模型所在的后端是验证和所有其他更高级的事情将发生的地方。这意味着不需要重复检查,从而消除了另一大块代码。

诺顿:您在前面指出,导致您在Node.js中重写的一个见解是,您意识到您并不真的需要深入理解您正在操作的对象,这意味着您不需要大量更改这些对象。基本上,你可以做很多哈希的归并。您是否认为,通过使用散列映射原语,使用其他语言(甚至Ruby)也能达到同样的目的?

普拉萨德:可能是这样,但是如果你看看Ruby,你会发现Rails里面有很多额外的东西,而Node在其基础上有一个内置在二进制文件中的HTTP服务器方面和客户端方面。这意味着您不需要HTTP Node模块和HTTP侦听模块。

所以,是的,我想如果我们消除了所有的对象层次结构,只使用散列结构,我们可能就可以使用Ruby了。但你仍然需要监听HTTP并将其转换为控制器,这就需要你重新添加所有这些小的微层。虽然每个微层都提供了一堆不必编写的代码,但它也为必须编写的内容添加了一些要求,以便在框架中很好地工作。

COATTA如果你要和另一个即将承担类似项目的人交谈,你会指着什么说:“嘿,注意这个,否则你会有麻烦的”?

普拉萨德:流量控制。异常处理。虽然这并不是针对Node的,但我会说:“保持轻松。保持瘦。”我认为人们很自然地会说,“嗯,我需要一个做HTTP的东西,所以我就找一个做这个的模块,”然后,当他们真正需要的只是一个HTTP请求时,又有4000行代码掉到他们的环境中。相反,他们最终得到了这个超级的东西,给了他们这个和一大堆其他的东西。

基本上,Node之所以这么快、这么好,是因为它又轻又薄。它几乎没有任何内容,因此您添加到其中的每一件小事、您想要与之一起使用的每一个额外Node模块都是有代价的。

诺顿:对于那些已经在Node上启动了项目的公司,您认为他们应该在生态系统中添加哪三件事,以使他们的生态系统更加强大?

普拉萨德首先是一个好的IDE。InteliJ IDEA非常好,但除此之外,我还没有见过真正适合Node的优秀IDE和工具集。

第二是允许不断发展的性能分析和监视。Node的更好的操作监视会很好,但目前它本质上是一个黑箱,除非您将自己的监视钩子放入代码中。我希望看到Java VM中的JMX层提供的许多功能。通过这种方式,你可以得到一些非常有用的信息。

第三个是类似于Node的New Relic的东西,它可以检查Node系统正在做的所有事情,并真正理解你的应用程序,因此它可以为你提供瓶颈和减速的详细分解。那真是太棒了。

ACM队列的q戳相关文章
queue.acm.org

陶醉于约束
布鲁斯·约翰逊
http://queue.acm.org/detail.cfm?id=1572457

跳中多层编程
曼纽尔·塞拉诺和Gérard贝里
http://queue.acm.org/detail.cfm?id=2330089

高性能网站
Steve Souders
http://queue.acm.org/detail.cfm?id=1466450


©2014 0001 - 0782/14/02 ACM

允许为个人或课堂使用部分或全部作品制作数字或硬拷贝,但不得为盈利或商业利益而复制或分发,且副本在首页上附有本通知和完整的引用。除ACM外,本作品的其他组件的版权必须受到尊重。允许有信用的文摘。以其他方式复制、重新发布、在服务器上发布或重新分发到列表,都需要事先获得特定的许可和/或费用。请求发布的权限permissions@acm.org传真(212)869-0481。

数字图书馆是由计算机协会出版的。版权所有©2014 ACM, Inc.


评论


鲁道夫Olah

我很高兴有一篇文章是关于NodeJS和一些在CACM行业中使用的最新技术的。

如果能看到关于不同类型模型的优缺点的后续文章,那就太好了;流程、事件等。


显示1评论

登录为完全访问
»忘记密码? »创建ACM Web帐号
文章内容:
Baidu
map