从 Node.js 到 Golang 的迁徙之路

本文讲的是从 Node.js 到 Golang 的迁徙之路,


由 Digg 的软件工程师 Alexandra Grant 所作,最初发表在 Medium

我在大学时期就开始涉猎 JavaScript 并会随便写一些网页。我把 JS 当作写 C 语言和 Java 时候的一种小憩,并且我认为它是一种相当受限制的语言,它一直在鼓吹能够实现一些令用户叹为观止的特效和动画。我第一次教人编程就是用的 JS,因为它简单易学、能快速给开发者以可见的结果。将它与 HTML 和 CSS 代码写到一起,就能得到一个网页,初学者对此爱不释手。

然后意想不到的事情发生了。两年前,我还在一个研究性质的岗位上从事服务端编程和安卓应用原型的开发的时候,Node.js 突然跃入了我的视野。后端的 JavaScript?谁会拿它当回事儿呢?充其量也就是尝试让服务端性能、扩展等方面的开发容易些罢了,但随之而来的是运行和扩展性能的下滑等等。或许那仅仅是我根深蒂固的开发者的怀疑论,当读到一些有关快速、简单和高产的东西的时候,我就总是会那样想。

然后是接踵而至的研究、报告、教程和附加项目,六个月之后我才意识到自从第一次读到 Node 以来,我一直在心无旁骛地研究它。它太简单了,尤其是当我每两个月就要开发新的创意的时候我更加意识到它的方便。但是 Node 并不仅仅是为应用原型和小项目而生的,甚至很多像 Netflix 这样成熟的公司也将业务分了一杯羹给 Node。霎时间,我手里拿着金刚钻看到了这世界充满了瓷器活儿。

很快又几个月过去了,我来到了现在的工作岗位,在 Digg 做一名后端开发人员。早在 2015 年四月我入职的时候,Digg 的运行栈主要是 Python,除了两个服务等着用 Node 来写入。当被分派去给一个经常出问题的服务填坑时,我甚至感觉无比激动。

我们问题重重的 Node 服务器承担着相当直接的使命。Digg 使用亚马逊的 S3 的云存储服务,S3 很出色但是不支持批量 GET 操作。为了不将所有的负荷都加到我们的 Python 服务器上,不让 Python 服务器每次都从 S3 请求超过 100 个 key,我们决定利用 Node 简单的异步代码模式和大并发处理来完成。于是 S3 的内容获取服务 Octo 诞生了。

Node Octo 除了偶尔的掉链子之外性能都很好。某天它需要处理一个网络峰值,每分钟的请求数量从 50 跃升至 200+,与此同时每个请求中 Octo 基本都要从 S3 获取大概 10-100 个 key,也就是说它可能每分钟有 20,000 次 S3 的 GET 请求。日志表明,网络峰值的时候服务器的性能会大大下降,但是问题在于它并不总是能恢复。就这样,每隔一周在 Octo 卡住并且失灵后,我们也卡在恢复 EC2 实例当中。

服务器请求有着严格的超时时间,接收到请求后的几毫秒时刻内,Octo 应该将从 S3 成功获取的信息返回给客户端并继续工作。然而,即使设置超时时间为最大值 1200 毫秒,Octo 在最坏的情况下还是会出现请求处理时间达到 10 秒之久。

Octo 的代码非常的不同步并且我们获取 S3 的 key 和 value 的方式非常激进,并且它还和两个中型的 EC2 实例交叉运行,后来我们增加到四个。

我将代码重写过三次,每次都更深层次地挖掘 Node 的优化、填坑并在性能上锱铢必较。我查看了流行的 Node 网站服务器框架的性能评估,比如 Express 和 Hapi,并和 Node 内置的 HTTP 模块做了比较。我移除了所有第三方的模块,尽管它们很好用但是会拖慢代码的执行,结果三次都遭遇了相同的问题。无论我多努力,我还是不能使得 Octo 走上正轨,也不能减少请求峰值时性能的下降。

最终一个理念浮现出来,我必须要从 Node 的 event loop 工作入手。如果你不了解 event loop,请查看 Node Source

Node 的 “event loop” 是处理高传送率方案的核心。那里充满了神迹和天马行空,也正是因为它才使得 Node 虽然是单线程却还能够允许后台处理任意数量的操作。

并没有多么神奇的 Event Loop 阻塞(X轴:时间/毫秒)

你能看到在我们对服务进行弹性恢复之后原本丢失的性能又回来了。

即使发现了 event loop 阻塞是罪魁祸首,那也只是说明了在一开始的时候性能滞后的原因。

大多数的开发人员都听过 Node 的非阻塞 I/O 模型,那非常棒因为它意味着所有的请求在异步处理的时候不会造成执行阻塞,也不会产生任何多余开销(像线程和进程)并且作为开发人员你能很幸福地不用管后台发生的事。然而,你要牢记 Node 是单线程的,那意味着没有并行执行的代码。I/O 或许不会阻塞服务器,但是你的代码会啊。如果我在代码中调用休眠 5 秒钟,那么服务器在这段时间将不会有任何响应。

形象化的 Event Loop:StrongLoop

那么非阻塞代码呢?当处理请求的时候,事件被触发,消息和各自的回调函数一同进入队列。想了解更加深入,请查看对此有着独到见解的 Carbon Five 的博文

在一个循环中,队列轮询下一个消息(每个轮询被称为一个“tick”),当遇到一个消息时,执行该消息的回调函数。这个回调函数的调用作为调用堆栈中的初始帧,并且因为 JavaScript 是单线程的,堆栈中所有调用的返回之前会停止进一步的消息轮询。并发的(同步的)函数调用会在堆栈中增加新的调用帧……

如果我们的 Node 服务只是需要返回触手可得的数据,那它处理接收的请求绰绰有余。但是相反,它一直等待着许多嵌套的回调函数,这完全依赖于 S3 的响应(而这有时会超级慢)。请求超时之后,事件和与其相关的回调函数会被置于超载消息队列中。然而,超时事件可能在 1 秒的时候发生,只有等当前队列的消息和其回调函数都执行完(这可能需要几秒钟)该事件的回调函数才会被处理。我能想象请求峰值时堆栈的状态,但事实上,我并不需要想象,只需一点点 CPU 的运行切面就能展示给我们相当生动的状态图像。对以上的长篇累牍我表示抱歉。

失败情况下的火焰图

先对火焰图做一个简单的介绍,y 轴代表堆栈中的帧的数量,每个函数是其下面的函数的子函数。x 轴代表样本的数量和持续时间。盒子的宽度表示在 CPU 上处理的时间,越宽就表示这个函数执行越慢或者它被调用地越频繁。现在你能从堆栈的深度看到 Octo 在巨大的峰值时的火焰图。想了解更多切面的信息和火焰图请点击这里

看到这些我醍醐灌顶,也许 Node.js 并不合适处理这项任务。CTO 和我促膝而谈,我们当然不想每隔一周就对 Octo 进行一次弹性恢复并且我们都对一项互联网上非常有前景的案例研究感兴趣。

如果这个标题没有足够悬念的话[原标题是:使用 Golang 每分钟处理百万请求。译者注],其主题是创建服务向 S3 发送 PUT 请求(有人遇到过同样的问题么?)。这已经不是第一次我们谈论要使用 Golang 了,而现在我们有了一个绝佳的测试对象。

我速成了 Golang 的课程,两周之后,我们搭建并运行了一个新 Octo 服务。我严格按照 Malwarebyte’s 的 Golang 文章中描述的那样搭建了一个激动人心的解决方案。该服务有一个工作池(worker pool)和一个托管(delegator),托管会将接收的工作分派给空闲的工作区(worker)。每一个工作区在自己的协程(goroutine)上工作,并且一旦任务完成它们将返回工作池,简单高效。立竿见影的结果好到让人惊讶地合不拢嘴。

良好的不温不火的状态

我们的服务平均响应时间几乎缩减了一半,我们的超时设置(S3 响应太慢,所以会有超时)也能够按部就班,并且网络峰值也只对服务造成了微小的影响而已。

蓝色的是 Node.js Octo | 绿色的是 Golang Octo

用 Golang 升级之后,我们很容易地就能每分钟处理 200 个请求,每天处理 150 万个 S3 内容获取。我们一开始运行在 Octo 上的那四台负载均衡实例怎样了?我们现在又所缩减到了两个。

自从过渡到 Golang 我们还没回顾过这段经历。尽管我们主要的堆栈工作是用的 Python(很有可能会一直是这样),但是我们也已经开始模块化处理我们的基础代码并在系统中用微服务去处理特殊的内容。除了 Octo,我们现在生产环境中还有另外 3 台 Golang 服务,它们给我们提供实时的消息系统并且为我们的内容提供重要的元数据。我们对这些最新版本的 Golang 代码库感到骄傲,DiggBot

我并不是为了说明 Golang 是解决我们疑难杂症的灵丹妙药。我们再三考虑了我们每项服务的需求,作为一个公司,我们努力地站在新技术的前沿并且会反躬自省,我们能做得更好吗?这将是一个持续进步的过程,我们将会再三调研并认真计划。

我可以很自豪地说,我们的 Octo 服务已经非常成功地运行了几个月(修复了一些 bug 除外),结局皆大欢喜,Digg 将继续前行。






原文发布时间为:2016年12月07日


本文来自合作伙伴掘金,了解相关信息可以关注掘金网站。

时间: 2024-09-17 16:19:50

从 Node.js 到 Golang 的迁徙之路的相关文章

从Node.js 转到 Go平台_Golang

在用 Node.js 建立了 Bowery 的第一个迭代版本后,我们在2014年2月切换到了 Go,我们的开发和部署速度也因此得到提升. 从那以后,我们整个团队都变成了专职的地鼠(译者注:Go 的吉祥物).Go 清晰明确的标准和更简便的工作流程让我们用 Go 用得很舒服.下面是我们热爱用 Go 进行工作的原因,你可以从中瞄一眼我们的地鼠洞. 容易编写跨平台代码 我们切换成 Go 的其中一个最大原因就是它是那么容易去为不同系统编译代码. 在 Bowery, 我们在建立一个能帮忙你和你的团队管理你们

为什么我从Python转战到Node.js

老生常谈?这些日子谁不是切换到Node呢?我就是其中之一,下面是我的理由. Python 2,抑或是Python 3? Python版本之间缺乏重点和运转是一个巨大的阵痛.是的,我知道很多库正在被转换或已经被转换过了.但是,一个接一个地缺乏重点以及明确的方向使得我对它的信任降到历史最低.我知道这和不想移动的社区有很大的关系,但开发人员对此不买账,尽管是社区驱动项目. Unicode支持 你有没有试过在Python中使用Unicode?TMD真心太痛苦了.是的,关于这个主题有很多这方面的文档,因此

将Node.js项目docker容器化并纳入kubernetes调度编排的实践

  简述 此文档以XXXLogApi-nj项目为例,讲解了将基于Node.js+Express开发的javascript项目容器化的过程.希望以后类似的项目可以以此为参照进行扩展. XXXLogApi-nj本身是一个微服务化的项目,其作用是为系统单纯的收集相关发布日志,以便能及时的展示给用户. ***这份文档的操作,开始于编码完成之后流程.不涉及GIT和JENKINS的等的操作. ***为保持职业操作,涉及公司信息的地方作了敏感化处理. ***在这个系列中,我同时作了spring boot, b

Node.js 服务端实践之 GraphQL 初探

0.问题来了 DT 时代,各种业务依赖强大的基础数据平台快速生长,如何高效地为各种业务提供数据支持,是所有人关心的问题. 现有的业务场景一般是这样的,业务方提出需求,然后寻找开发资源,由后端提供数据,让前端实现各种不同的业务视图.这样的做法存在很多的重复劳动,如果能够将其中通用的内容抽取出来提供给各个业务方反复使用,必然能够节省宝贵的开发时间和开发人力. 前端的解决方案是将视图组件化,各个业务线既可以是组件的使用者,也可以是组件的生产者.那么问题来了,前端通过组件实现了跨业务的复用,后端接口如何

聊天-ASP.net MVC的一个项目里可以使用Node.js做的应用吗?

问题描述 ASP.net MVC的一个项目里可以使用Node.js做的应用吗? 20C 是这样的,最近我们小组在做一个ASP.net MVC 的项目网页,里面有个即时网络聊天室的功能要实现,我看到用Node.js做好像很不错,但是就是不知道.net的项目可以使用吗?

Node.js selenium-webdriver

6.5. Node.js selenium-webdriver 6.5.1. 安装测试环境 6.5.1.1. Selenium Server 下载 Selenium Serverhttp://selenium-release.storage.googleapis.com/2.40/selenium-server-standalone-2.40.0.jar 启动 Selenium Server java -jar selenium-server-standalone-2.40.0.jar 6.5.

CentOS上安装Node.js和mongodb笔记

  CentOS上安装Node.js和mongodb笔记        这篇文章主要介绍了CentOS上安装Node.js和mongodb笔记,本文讲解了Python安装.Node.js安装.npm安装.mongodb驱动安装.mongodb数据库操作测试代码等内容,需要的朋友可以参考下 之前听说过Node.js,只是知道它可以应用于服务器端,但是对很多具体的东西并不了解.今天在QCon上听了袁锋的分享<Node.js脱离了浏览器的Javascript>之后,顿时有了想立刻试一下的冲动. No

node.js的multipart模块问题

问题描述 node.js的multipart模块问题 multipart模块已经安装好了,node.js代码中有一行var parser=new multipart.parser()为什么运行后该行代码会报错:multipart.parser is not a function 解决方案 Node.js工具模块Node.js 模块和包Node.js fs 模块 解决方案二: https://cnodejs.org/topic/4ffed8544764b729026b1da3

Node.js之异常处理

   记得刚刚开始学Node.js时自己尝试着写了一个简单的http服务器,跟以前接触过的php相比感觉更自由,编起码来也更爽了.但是某天发现稍微一个很小的错误就导致整个http进程挂掉了,顿时有种不靠谱的感觉啊,跟php比起来感觉Node.js容错能力确实弱了很多,起码一个php文件出错也不会导致所有的服务都挂掉.           后来接触到Node.js web开发框架后感觉也不是那么轻易就让整个进程都挂掉的,于是便想研究下Node.js究竟是如何来处理各种异常从而避免整个进程挂掉的.