应如何为高需求应用程序设置 node.js 堆栈?
How should a node.js stack for a high demand application be setup?
我目前正在开发一个超过 25000 人使用的 Node.js 堆栈应用程序,我们特别使用 Sails.js 框架,我们得到了 MongoDB
应用程序在具有 30GB RAM 的 EC2 实例上 运行,数据库在 Mongolab 基于 AWS 的集群上 运行,EC2 所在的同一区域。我们甚至获得了一个 1.5GB 存储空间的 Elastic Cache Redis 实例。
所以我们面临的主要和巨大问题是延迟。当我们达到并发用户请求应用程序的峰值时,我们会遇到多次超时并使应用程序运行超过 7.5GB 的 RAM,对 API 的 HTTP 请求需要超过 15 秒(这是不可接受的),甚至获得 502 和nginx 发送了 504 个响应。
我注意到 Mongo 写操作是我们的主要延迟问题,但是当出现需求高峰时,即使 GET 请求也需要很长时间。我无法访问生产服务器,我只有 pm2 的关键指标监控工具(实际上很棒)和 New Relic 警报。
所以,我想知道一些解决这些问题的路线图,也许应该提供更详细的信息,到目前为止,我可以说当用户不多时应用程序看起来很稳定。
要考虑哪些主要因素和设置?
到目前为止我知道我应该做什么,但我不确定细节或如何做。
恕我直言:
- 尽量缓存。
- 延迟Mongo数据库写入操作。
- 分离 Mongo 具有更高写入需求的数据库。
- 虚拟化?
- 调整节点设置。
关于优化代码,我发布了另一个 Whosebug 问题,其中包含一个示例 code patterns I'm following。
您对生产应用有什么建议和意见?
首先,确保您没有使用同步 I/O。如果您可以在 io.js
上 运行,则有 --trace-sync-io
标志 (iojs --trace-sync-io server.js
) 如果您使用带有以下控制台警告的同步代码,它会警告您:WARNING: Detected use of sync API
.
其次,找出您的 RAM 使用率如此之高的原因。如果是因为大量数据加载到内存中(XML解析,从MongoDB返回大量数据等),您应该考虑使用streams
。 V8 垃圾回收(Google 的 JavaScript VM 在 Node.js
/ io.js
中使用)如果您的内存使用率非常高,可能会导致速度下降。更多信息:Node.js Performance Tip of the Week: Managing Garbage Collection and Node.js Performance Tip of the Week: Heap Profiling
第三,尝试 Node.js clustering and MongoDB sharding。
最后,检查您是否正在使用或可以切换到 MongoDB
3.x。仅通过从 2.x 升级到 3.x.
,我们就观察到了一些显着的性能提升
对于 Mongodb 你可以使用 mongtop to see which databases are contested, 2.2+ uses per database locks, if database has write heavy workload reads will be affected as mongodb is using writer greedy locks
对于 node.js,您可以检查是否存在任何类型的事件循环延迟可以解释 API 请求延迟
(function getEventLoopDelay() {
var startTime = Date.now();
setTimeout(function() {
console.log(Math.max(Date.now() - startTime - 1000, 0));
getEventLoopDelay();
}, 100);
})();
基本上大部分要点都已经在答案中了。我只是总结一下。
要优化您的应用程序,您可以做几件主要的事情。
尝试从 node.js
移动到 io.js
它仍然有更好的性能和最新的前沿更新。 (但请仔细阅读实验性功能)。或者至少从 node.js
v10
到 v12
。有很多性能优化。
避免使用使用 I/O 运算或处理大量数据的同步函数。
从一个节点进程切换到clustering系统。
检查您的应用程序是否存在内存泄漏。我将 memwatch-next for node.js v12
and memwatch 用于 node.js v10
尽量避免保存数据到全局变量
使用缓存。对于应该可以在全球范围内访问的数据,您可以使用 Redis
或 Memcached
也是一个很好的商店。
避免将 async
与 Promises
一起使用。两个库都在做同样的事情。所以没有必要同时使用它们。 (我在你的代码示例中看到了)。
将async.waterfall
与async.parallel
方法结合起来。
例如,如果您需要从 mongo 获取一些仅与用户相关的数据,您可以获取用户,然后并行获取您需要的所有其他数据。
如果您正在使用 sails.js
,请确保它处于 production
模式。 (我假设你已经这样做了)
禁用所有不需要的挂钩。在大多数情况下,如果您的应用程序中不需要 Socket.io
,grunt
挂钩是 useless.And - 使用 .sailsrc
文件禁用它。类似于:
{
"generators":{
"modules":{}
},
"hooks":{
"grunt":假的,
"sockets": 错误
}
}
另一个可以禁用的挂钩是:i18n
、csrf
、cors
。但前提是您不在系统中使用它们。
禁用无用的全球化。在config/globals.js
。我假设默认情况下可以禁用 _
、async
、services
。只是因为 Sails.js
使用旧版本的 lodash
和 async
库和新版本有更好的性能。
手动将 lodash
和 async
安装到 Sails.js
项目中并使用新版本。 (看点11)
一些 "write to mongo" 操作可以在 return 向用户发送结果后进行。例如:您可以调用 res.view()
方法,该方法将在 Model.save()
之前向用户发送响应,但代码将继续 运行 所有变量,因此您可以将数据保存到 mongo 数据库。所以用户在写入操作期间不会看到延迟。
您可以使用 RabbitMQ 这样的队列来执行需要大量资源的操作。例如:如果您需要存储大数据集合,您可以将其发送到 RabbitMQ
和 return 响应给用户。然后在 "background" 处理广告存储数据中处理此消息。它还可以帮助您扩展应用程序。
我目前正在开发一个超过 25000 人使用的 Node.js 堆栈应用程序,我们特别使用 Sails.js 框架,我们得到了 MongoDB 应用程序在具有 30GB RAM 的 EC2 实例上 运行,数据库在 Mongolab 基于 AWS 的集群上 运行,EC2 所在的同一区域。我们甚至获得了一个 1.5GB 存储空间的 Elastic Cache Redis 实例。
所以我们面临的主要和巨大问题是延迟。当我们达到并发用户请求应用程序的峰值时,我们会遇到多次超时并使应用程序运行超过 7.5GB 的 RAM,对 API 的 HTTP 请求需要超过 15 秒(这是不可接受的),甚至获得 502 和nginx 发送了 504 个响应。
我注意到 Mongo 写操作是我们的主要延迟问题,但是当出现需求高峰时,即使 GET 请求也需要很长时间。我无法访问生产服务器,我只有 pm2 的关键指标监控工具(实际上很棒)和 New Relic 警报。
所以,我想知道一些解决这些问题的路线图,也许应该提供更详细的信息,到目前为止,我可以说当用户不多时应用程序看起来很稳定。
要考虑哪些主要因素和设置?
到目前为止我知道我应该做什么,但我不确定细节或如何做。
恕我直言:
- 尽量缓存。
- 延迟Mongo数据库写入操作。
- 分离 Mongo 具有更高写入需求的数据库。
- 虚拟化?
- 调整节点设置。
关于优化代码,我发布了另一个 Whosebug 问题,其中包含一个示例 code patterns I'm following。
您对生产应用有什么建议和意见?
首先,确保您没有使用同步 I/O。如果您可以在 io.js
上 运行,则有 --trace-sync-io
标志 (iojs --trace-sync-io server.js
) 如果您使用带有以下控制台警告的同步代码,它会警告您:WARNING: Detected use of sync API
.
其次,找出您的 RAM 使用率如此之高的原因。如果是因为大量数据加载到内存中(XML解析,从MongoDB返回大量数据等),您应该考虑使用streams
。 V8 垃圾回收(Google 的 JavaScript VM 在 Node.js
/ io.js
中使用)如果您的内存使用率非常高,可能会导致速度下降。更多信息:Node.js Performance Tip of the Week: Managing Garbage Collection and Node.js Performance Tip of the Week: Heap Profiling
第三,尝试 Node.js clustering and MongoDB sharding。
最后,检查您是否正在使用或可以切换到 MongoDB
3.x。仅通过从 2.x 升级到 3.x.
对于 Mongodb 你可以使用 mongtop to see which databases are contested, 2.2+ uses per database locks, if database has write heavy workload reads will be affected as mongodb is using writer greedy locks
对于 node.js,您可以检查是否存在任何类型的事件循环延迟可以解释 API 请求延迟
(function getEventLoopDelay() {
var startTime = Date.now();
setTimeout(function() {
console.log(Math.max(Date.now() - startTime - 1000, 0));
getEventLoopDelay();
}, 100);
})();
基本上大部分要点都已经在答案中了。我只是总结一下。
要优化您的应用程序,您可以做几件主要的事情。
尝试从
node.js
移动到io.js
它仍然有更好的性能和最新的前沿更新。 (但请仔细阅读实验性功能)。或者至少从node.js
v10
到v12
。有很多性能优化。避免使用使用 I/O 运算或处理大量数据的同步函数。
从一个节点进程切换到clustering系统。
检查您的应用程序是否存在内存泄漏。我将 memwatch-next for
node.js v12
and memwatch 用于node.js v10
尽量避免保存数据到全局变量
使用缓存。对于应该可以在全球范围内访问的数据,您可以使用
Redis
或Memcached
也是一个很好的商店。避免将
async
与Promises
一起使用。两个库都在做同样的事情。所以没有必要同时使用它们。 (我在你的代码示例中看到了)。将
async.waterfall
与async.parallel
方法结合起来。 例如,如果您需要从 mongo 获取一些仅与用户相关的数据,您可以获取用户,然后并行获取您需要的所有其他数据。如果您正在使用
sails.js
,请确保它处于production
模式。 (我假设你已经这样做了)禁用所有不需要的挂钩。在大多数情况下,如果您的应用程序中不需要
Socket.io
,grunt
挂钩是 useless.And - 使用.sailsrc
文件禁用它。类似于:{ "generators":{ "modules":{} }, "hooks":{ "grunt":假的, "sockets": 错误 } }
另一个可以禁用的挂钩是:i18n
、csrf
、cors
。但前提是您不在系统中使用它们。
禁用无用的全球化。在
config/globals.js
。我假设默认情况下可以禁用_
、async
、services
。只是因为Sails.js
使用旧版本的lodash
和async
库和新版本有更好的性能。手动将
lodash
和async
安装到Sails.js
项目中并使用新版本。 (看点11)一些 "write to mongo" 操作可以在 return 向用户发送结果后进行。例如:您可以调用
res.view()
方法,该方法将在Model.save()
之前向用户发送响应,但代码将继续 运行 所有变量,因此您可以将数据保存到 mongo 数据库。所以用户在写入操作期间不会看到延迟。您可以使用 RabbitMQ 这样的队列来执行需要大量资源的操作。例如:如果您需要存储大数据集合,您可以将其发送到
RabbitMQ
和 return 响应给用户。然后在 "background" 处理广告存储数据中处理此消息。它还可以帮助您扩展应用程序。