专栏首页网络创建一个DIY的APM监视Node.js中的Web应用程序的性能

创建一个DIY的APM监视Node.js中的Web应用程序的性能

Tl;dr

构建一个监视应用程序执行情况的工具不再是很困难了。Node.js中Async Hooks API和Performance Hooks API最近增加了两个,允许任何人只需一些代码就可以密切关注他们的应用程序的性能。这篇文章解释了构建这样一个工具的关键要素,从编写代码到以清晰的可视化报告收集的数据。

最终的项目在Github上可用,并具有以下特点:

1.一个简单的性能监控代理

2.基于Express和MongoDB的测试应用程序

介绍

在生产中运行Web应用程序时,性能很重要。缓慢的Web服务器提供了降级的用户体验,并可能威胁整个公司的业务。

为了充分了解Web应用程序如何在生产环境中运行,负载测试是不够的。即使像ab这样的工具可以提供服务器在特定负载下应答的速度,他们也不能告诉你瓶颈在哪里。

在本文中,我们将构建一个工具来监视在一个简单的Node.js应用程序应答HTTP请求时在MongoDB中花费多少时间。

开始之前,我们先来看看这个简单的Express / Mongoose应用程:源码请到https://github.com/sqreen/funAPM/blob/master/testApp/server.js.

另外,在本文中,我们将只使用async / await语法。

首先解决方案

显而易见的解决方案就是在数据库请求周围添加时间样本并记录下来。 这可能会诀窍,但是你将不得不改变你的代码在你想要的每个方法的执行之前和之后添加一个process.hrtime或一个新的Date()。

显然,这种做法不会扩展,因此不是一个可行的解决方案。

我们来重写一些方法

如果我们不想更改应用程序代码,则需要更改其依赖项的代码。

如果我们专注于我们的应用程序的一个更小的版本:

我们可以通过重写Cat.find方法来监视在MongoDB中花费的时间:

在这个代码中:

1.我们提取Cat对象的原型。 这是查找方法被定义的地方。

2.我们保留对原始版本的查找的参考。

3.我们用我们的自定义方法替换Cat原型上的find方法:

4.使用console.time / console.timeEnd方法记录原始方法的执行时间。

5.我们通过执行find.apply(this,arguments)来调用原始方法(这里引入apply和arguments)

当我们启动进程并在浏览器上输入http:// localhost:9090 / cats时,控制台显示:

但是,这个补丁会有几个问题:

1.我们不知道哪个HTTP请求负责这个调用。

2.执行时间只显示在控制台中,我们不存储它们,所以我们可以稍后操作它们。

Performance Hooks API

为了节省呼叫到外部服务的时间,我们将使用全新的(和实验性)Performance Hooks API。 它最近被James Snell添加到了Node.js中。 这个API符合W3C规范,因此和现代浏览器中的一样。

让我们编写一个包装函数来执行返回一个promise的函数:

每次调用方法时,我们都会为每个性能度量创建一个唯一的ID。这将确保两个定时操作之间不发生碰撞。

而不是直接覆盖每种方法,我们可以直接做:

Async Hooks API

Async Hooks API仍然是实验性的,但应该由Node.js 10(预计2018年4月)来稳定。这个API使我们能够在异步操作上设置钩子。

出于我们的目的,我们只需要这个API来跟踪负责代码执行的HTTP请求。一些包(如持续本地存储或区域的各种实现)提供了类似的功能。然而,由于这些模块仅基于userland代码,因此一些异步操作可能会被它们忽略, context 将会丢失(请参阅此处的示例)。

我们的钩子将会很简单:

1.当一个异步资源被创建时,如果它的父代有一个context,这个context将被传播到新的资源。

2.当调用destroy钩子时,我们删除资源和它的context之间的连接。

然后我们把它放到一个新的Async Hook中:

现在我们需要为每个HTTP请求创建一个新的context,并提供一种从任何地方访问当前context的方法。为了跟踪HTTP请求,我们将从Node.js core覆盖类Http.Server上的emit方法:

现在,对于Http.Server的所有实例,当使用请求事件调用emit方法时,会创建一个新的context,并与当前执行的AsyncId关联。

由于我们的Async Hook会将这个context传播给子资源,因此每次调用AsyncHooks.executionAsyncId()都会返回一个有效的context映射关键字。

我们来写一个简单的方法来包装这个:

建立一个代理

现在,我们拥有了构建适当代理的所有工具,以便将其注入Node.js应用程序中进行监视。

我们的代理将需要成为应用程序所需的第一个模块,以放置检测钩子(稍后解释)。 它可以通过调用:

在给你源码之前,我会分享我最后两个秘密:

1.为了覆盖一个模块,我们可以通过改变核心中的私有方法来改变需要的行为。这不是一个好的解决方案,但目前我还不知道有什么更好的方法来实现它。(详情见https://github.com/sqreen/funAPM/blob/master/apm/instrumentation/index.js)。新的加载器钩子API只与ES模块挂钩。

2.在node中有一个很好的选项,它允许我们在主模块之前加载模块。要利用这个选项,我们的代理将需要调用它的start方法。(详情见https://github.com/sqreen/funAPM/blob/master/apm/instrumentation/index.js)

所以我们的整个项目代理在github上可用。随意项目的核心代码,中心概念已经在本文中进行了解释。

如果你运行库中提供的testApp。将在目录中创建一个名为apm_logs.json的文件。其内容如下所示:

正如您所看到的,对于通过服务器的每个请求,代理记录了其持续时间和MongoDB操作的持续时间。为调试目的而保存的惟一标识符可以被忽略。

Bonus: Viewer

由于我们的代理正在生成JSON输出,所以我们应该能够以更加用户友好的方式显示时序数据。

使用d3.js和一个不错的时间线插件,我生成了一个网页,以更直观的方式显示代理所做的度量。在Node.js进程结束之后,会创建一个名为viewer.html的文件。

结论

在本文中,我们已经看到,构建现代Node.js应用程序的应用程序性能监视工具已经不复杂了,它使用了两个新的Node特性,Async Hooks API和Performance Hooks API。当然,这些新的API非常适合构建各种各样的好东西,作为一个Node开发者,更值得你更详细地了解它们。

结尾

商业工具的存在,并提供比我们简单的概念证明,包括更多的功能:

1.事件循环监视

2.内存监视

3.历史统计

4.垃圾收集监控

5.服务器负载监视

如果您在生产环境中运行Node.js应用程序,则可能需要查看它们提供的内容。

本文来自企鹅号 - webstack前端栈媒体

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 爬当当各分类下的五星图书

    报名了爬虫课程,断断续续学了两个星期,才看完第一章。虽然技术还很菜,但一些基本的东西能够爬取了,也想趁这次作业,来尝试一下这段时间学习的知识。 这次作业选择爬取...

    企鹅号小编
  • 每个程序员都该知道的五大定律

    定律 - 或称法则,可以指导我们并让我们在同伴的错误中学习。这篇文章中,我将介绍我每次设计或实现软件时出现在我脑海的五大定律。其中有些和开发有关,有些和系统组织...

    企鹅号小编
  • 阿尔法狗元以0比100打败它的哥哥,围棋九段棋手柯洁表示人类太多余了

    《自然》杂志在10月份刊登了谷歌DeepMind团队的新成果,名为AlphaGoZero(暂译阿尔法狗元)的一款人工智能程序,它在仅输入围棋规则、未输入任何人类...

    企鹅号小编
  • 物理自我的未来:控制论、身体交换和基因工程

    大数据文摘
  • vue cli3 搭建一个通用中台(完)

    yangdongnan
  • 前端面试(题二)

    用户3055976
  • R语言可视化——极坐标变换与衍生图表类型

    今天这篇内容会比较杂乱一点,因为会讲到ggplot函数中的一大类通过极坐标支持才能呈现出来的图表效果。 ggplot作图背后的图表哲学,没有给予饼图(以及衍生出...

    数据小磨坊
  • C#基础知识系列九(对IEnumerable和IEnumerator接口的糊涂认识)

       IEnumerable、IEnumerator到现在为止对这两个接口还是不太理解,不理解但是自己总是想着试着要搞明白,毕竟自己用的少,所以在此先记录一下。...

    aehyok
  • Salesforce被Gartner评为PaaS平台魔力象限领导者

    Salesforce连续第二年被Gartner评为PaaS平台的领导者。报告赞誉Salesforce1平台是一个值得信任的PaaS平台。

    臭豆腐
  • JS模块化开发的价值

    非模块化方式开发的痛苦 (1)命名冲突 起初,我们定义了一个通用功能的JS文件,例如 utils.js(其中有一个 each 函数),谁需要谁调用即可 但随着项...

    dys

扫码关注云+社区

领取腾讯云代金券