【译】Node.js 超时策略:完全手册


Node.js 是一个强大的平台,用于构建可扩展和高效的服务器端应用程序。Node.js 开发的一个重要特性是有效地管理超时。超时有助于处理各种情况,比如防止请求无限期挂起或在特定时间范围内执行任务。

让我们深入了解超时,了解超时的含义以及为什么它们对于 Node.js 应用程序是必要的。

Node.js 中的超时是什么?

超时允许开发人员控制应用程序中各种操作的持续时间。如果任务或操作超出了预定义的时间限制,Node.js 将会干预,终止或中断执行,从而防止潜在的瓶颈或性能问题。

为了防止某些操作无限期挂起,改善应用程序的响应能力,并优化资源分配,超时是必不可少的。它们在维护 Node.js 应用程序的可靠性、效率和响应能力方面发挥着重要作用。

超时的类型

有三种主要类型的超时对于 Node.js 应用程序的不同方面至关重要:

    1. 请求超时
    1. 任务超时
    1. 空闲超时

为什么超时对于 Node.js 应用程序至关重要?

防止阻塞操作

如果某个操作花费太长时间才能完成,它可能会阻塞事件循环,导致其他操作延迟执行或阻止新的请求被处理。超时通过强制执行执行的最大时间限制来防止这种阻塞操作。

改善应用程序的响应能力

通过为各种操作设置超时,Node.js 应用程序可以对用户请求和交互保持响应。例如,假设对外部 APIHTTP 请求响应时间过长。在这种情况下,超时可以防止应用程序无限期等待,而是适当处理超时事件,向用户返回适当的响应。

减少资源耗尽

长时间运行的操作可能会消耗大量资源,如内存、CPU 和网络带宽。通过强制执行超时,Node.js 应用程序可以限制分配给特定操作的资源,从而防止资源耗尽,并维持应用程序的整体稳定性和性能。

处理不可预测的场景

在分布式系统中可能发生网络延迟、数据库超时和其他意外问题。超时通过指定最大可接受的等待时间来处理这些情况。如果操作超出此时间限制,应用程序可以采取适当的操作,例如重试操作、返回错误消息或终止操作。

改善容错性

超时有助于提高 Node.js 应用程序的容错性,防止其在面对网络故障或不响应的外部服务等问题时变得无响应或进入无限等待状态。通过设置适当的超时,应用程序可以更有效地从短暂故障中恢复,并在不利条件下可靠地运行。

超时类型

在 Node.js 应用程序开发的不同方面,有三种主要类型的超时是必不可少的:

1. 请求超时

请求超时用于处理在指定时间范围内未收到响应的情况。这些超时是必要的,以确保系统在等待响应时不会无限期挂起,这可能会导致性能降低,甚至在某些情况下导致拒绝服务。

在 Node.js 中有两种主要类型的请求超时:

  • a. 接收请求超时
    接收请求超时适用于服务器端操作,特别是当Node.js服务器正在等待客户端发送请求时。
    当客户端建立与Node.js服务器的连接并发送请求时,服务器会启动一个计时器。如果服务器在指定的超时期限内未收到完整的请求,它将终止连接,并可能以错误响应或采取其他操作。
    接收请求超时是在HTTP服务器级别处理的,可以使用Express.js等框架或直接使用Node.js核心的 HTTP/HTTPS 模块来处理。
  • b. 发出请求超时
    发出请求超时适用于客户端操作,特别是当 Node.js 应用程序请求外部服务器或服务时。
    当 Node.js 应用程序向外部服务器发送 HTTP 请求时,它会启动一个计时器来等待响应。如果在指定的超时期限内未收到响应,则会终止客户端操作,应用程序可以相应地处理超时事件(例如,重试请求、记录错误等)。
    发出请求超时可以使用各种机制进行管理,例如在像 Axios 这样的库中设置超时选项,或者结合使用 setTimeout 函数与基于 Promise HTTP 请求库(例如 node-fetch)。
    实现请求超时
    在 Node.js 中实现请求超时,特别是在Express.js应用程序中,对于处理服务器响应时间过长的传入HTTP请求是很重要的。

实现请求超时的几种方法包括使用现有的库(如 express-timeout-handler)、创建自定义中间件,或者直接使用 Node.js 内置的 setTimeout 函数。

  • 使用 express-timeout-handler

使用 npmyarn 安装库

1
npm install express-timeout-handler

将其合并到 Express.js 应用程序中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const express = require('express');
const timeout = require('express-timeout-handler');

const app = express();

// 将请求超时设置为 30 秒
app.use(timeout.handler({ timeout: 30000 }));

// 超时的错误处理程序
app.use((err, req, res, next) => {
if (err.timeout) {
res.status(503).send('请求超时');
} else {
next(err);
}
});

// 启动服务器
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`服务器运行在端口 ${PORT}`);
});
创建自定义中间件:

定义一个设置传入请求超时的中间件函数

function requestTimeout(req, res, next) {
req.setTimeout(30000, () => {
res.status(503).send('请求超时');
});
next();
}
  • Express.js 应用程序中使用此中间件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const express = require('express');
    const bodyParser = require('body-parser'); // 导入 body-parser
    const app = express();

    // 为请求超时定义自定义中间件函数
    function requestTimeout(req, res, next) {
    req.setTimeout(30000, () => {
    res.status(503).send('请求超时');
    });
    next();
    }

    // 使用自定义中间件处理请求超时
    app.use(requestTimeout);

    // 使用 body-parser 解析 JSON 请求体
    app.use(bodyParser.json());

    // 定义路由...
    // 启动服务器
    const PORT = process.env.PORT || 3000;
    app.listen(PORT, () => {
    console.log(`服务器运行在端口 ${PORT}`);
    });
  • 直接使用 setTimeout

使用 setTimeout 为每个传入请求设置超时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const express = require('express');
const bodyParser = require('body-parser');
const app = express();

// 用于设置请求超时的中间件
app.use((req, res, next) => {
const timeout = setTimeout(() => {
res.status(503).send('请求超时');
}, 30000); // 将超时设置为 30 秒

// 如果请求在超时之前完成,则清除超时
res.on('finish', () => {
clearTimeout(timeout);
});

next();
});

// 解析 JSON 请求体
app.use(bodyParser.json());

// 其他中间件和路由
// 定义你的路由...

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`服务器

运行在端口 ${PORT}`);
});

2. 任务超时

任务超时指的是应用程序中具有预定义时间范围的操作或任务。这可能包括异步任务,如数据库查询、文件操作或计算任务。

通过设置任务超时,开发人员可以确保这些操作不会超出分配的时间限制,从而防止潜在的瓶颈或应用程序内的资源争用。

实现任务超时

对于异步操作,实现任务超时涉及设置任务完成的时间限制,并处理任务超出此时间限制的情况。可以使用 ‘setTimeout’ 来安排在指定延迟后运行的函数,使用 ‘clearTimeout’ 来取消已安排的函数(如果任务在分配的时间内完成)。

以下是针对各种异步操作实现任务超时的示例:

  • 数据库查询超时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const db = require('./db'); // 假设您有一个数据库模块

    function queryWithTimeout(query, timeout) {
    return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
    reject(new Error('查询超时'));
    }, timeout);

    db.query(query, (err, result) => {
    clearTimeout(timer);
    if (err) {
    reject(err);
    } else {
    resolve(result);
    }
    });
    });
    }

    // 示例用法:
    queryWithTimeout('SELECT * FROM users', 5000) // 将超时设置为 5 秒
    .then(result => console.log(result))
    .catch(error => console.error(error));
  • 文件操作超时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const fs = require('fs');

    function readFileWithTimeout(filePath, timeout) {
    return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
    reject(new Error('文件读取超时'));
    }, timeout);

    fs.readFile(filePath, (err, data) => {
    clearTimeout(timer);
    if (err) {
    reject(err);
    } else {
    resolve(data);
    }
    });
    });
    }

    // 示例用法:
    readFileWithTimeout('example.txt', 3000) // 将超时设置为 3 秒
    .then(data => console.log(data.toString()))
    .catch(error => console.error(error));
  • API 调用超时
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const axios = require('axios');

    function fetchDataWithTimeout(url, timeout) {
    return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
    reject(new Error('API 请求超时'));
    }, timeout);

    axios.get(url)
    .then(response => {
    clearTimeout(timer);
    resolve(response.data);
    })
    .catch(error => {
    clearTimeout(timer);
    reject(error);
    });
    });
    }

    // 示例用法:
    fetchDataWithTimeout('https://api.example.com/data', 8000) // 将超时设置为 8 秒
    .then(data => console.log(data))
    .catch(error => console.error(error));

    3. 空闲超时

    空闲超时管理应用程序中的空闲连接,特别是涉及连接的场景,如 Websockets TCP 服务器。在 Node.js 中,空闲超时使开发人员能够检测并终止已经保持不活动一段时间的空闲连接。

通过应用空闲超时,您可以最大限度地利用资源,防止资源耗尽,并提高应用程序的可扩展性和性能。

实现空闲超时

使用空闲超时有助于有效地管理资源,特别是在长期存在的连接(如 WebsocketsTCP 服务器)中,即使在不使用时连接仍保持打开状态。实现空闲超时允许服务器检测到不活动连接,并在一定的不活动期间后关闭它们,从而释放资源并优化性能。

下面是如何在各种情况下实现空闲超时的解释:

  • Websockets 空闲超时
    Websockets 是持续连接,即使客户端和服务器之间没有活动,它们也可以保持打开状态。为了管理 Websockets 中的空闲超时,您可以实现一种方法来跟踪最后活动时间,并在一段时间内没有活动时关闭连接。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const WebSocket = require('ws');

    const wss = new WebSocket.Server({ port: 8080 });

    // 为每个 Websockets 连接跟踪最后活动时间
    const connections = new Map();

    wss.on('connection', (ws) => {
    ws.on('message', () => {
    // 当收到消息时更新最后活动时间
    connections.set(ws, Date.now());
    });
    });

    // 检查空闲连接并关闭它们
    setInterval(() => {
    const now = Date.now();
    for (const [ws, lastActivityTime] of connections.entries()) {
    if (now - lastActivityTime > 60000) { // 如果空闲时间超过 1 分钟,则关闭连接
    ws.terminate();
    connections.delete(ws);
    }
    }
    }, 30000); // 每 30 秒检查一次
    在此示例中,服务器跟踪每个 Websockets 连接的最后活动时间。定期检查以检测空闲连接,并终止超过 1 分钟空闲的连接。
  • TCP 服务器空闲超时
    Websockets 类似,TCP 服务器也可能存在需要管理的空闲连接,以优化资源使用情况。您可以通过监视每个 TCP 连接上的活动并在一定的不活动时间后关闭不活动连接来实现空闲超时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const net = require('net');

const server = net.createServer((socket) => {
// 跟踪每个 TCP 连接的最后活动时间
let lastActivityTime = Date.now();

socket.on('data', () => {
// 当收到数据时更新最后活动时间
lastActivityTime = Date.now();
});

// 处理 socket 关闭事件
socket.on('close', () => {
console.log('Socket closed');
});

// 检查空闲连接并关闭它们
setInterval(() => {
const now = Date.now();
if (now - lastActivityTime > 60000) { // 如果空闲时间超过 1 分钟,则关闭连接
socket.end();
}
}, 30000); // 每 30 秒检查一次
});

server.listen(3000, () => {
console.log('TCP 服务器正在监听端口 3000');
});

在此示例中,TCP 服务器跟踪每个连接的最后活动时间。定期检查以检测空闲连接,超过 1 分钟处于空闲状态的连接将被关闭。

实现空闲超时有助于通过关闭不活动连接并为活动连接释放资源来防止资源浪费,从而优化处理长期连接的服务器的性能。根据您的具体用例和性能要求调整超时持续时间。

超时管理的最佳实践

以下是 Node.js 中超时管理的一些最佳实践:

1 设置合适的超时值:

根据应用程序的需求和预期的响应时间确定超时值。
考虑因素包括网络延迟、第三方服务响应时间以及操作的复杂性。
设置超时时要保守,以避免资源被阻塞时间过长,并确保它们不会过短以适应工作负载和网络条件的变化。

2 正确处理超时错误:

实现错误处理机制以正确处理超时错误。
为用户提供信息丰富的错误消息或记录超时错误以进行故障排除。
在适当的情况下考虑重试操作或在超时错误发生时为用户提供替代操作。

3 监控和调整超时设置:

持续监控应用程序的性能和负载,以确定可能需要调整超时的地方。
使用性能监控工具跟踪响应时间、错误率和超时情况。
根据实时数据动态调整超时设置,以优化性能并提供及时响应。
进行负载测试以模拟高流量条件,并验证不同场景下的超时设置。

4 使用具有内置超时支持的库和框架:

利用提供内置超时管理支持的库和框架。
例如,Express.js 中的中间件如 express-timeout-handler 可以处理请求超时,减少手动超时管理的需要。
选择受到积极维护且文档完善的第三方模块或库,以提高可靠性和与应用程序的兼容性。

5 实现断路器模式:

考虑谨慎实现断路器模式来处理超时和故障,特别是在分布式系统中。
断路器可以通过暂时停止向失败的服务发送请求并将流量重定向到备用服务或缓存数据来检测和防止重复的超时。
实现像 hystrix brakes 这样的断路器库来有效地管理 Node.js 应用程序中的超时和故障。

6 使用 Promises 和异步 API:

利用 Node.js 的异步特性,使用 Promises 或异步 API 来有效地管理超时。
Promises 允许您为异步操作设置超时,使用Promise.race()promise 解析和超时 promise 之间进行竞争。
对于I/O操作、网络请求和其他可能运行时间较长的任务,请使用异步 API,以避免阻塞事件循环并提高可伸缩性。

处理超时错误

以下是识别、调试和解决超时错误的一些关键点:

1 识别超时错误:

超时错误通常发生在操作花费的时间超过指定的超时持续时间时。
导致超时错误的常见场景包括网络连接缓慢、第三方服务不响应或阻塞 I/O 操作。
查找指示超时的错误消息,例如 ETIMEDOUTECONNRESET 或特定于您的应用程序或库的自定义错误消息。

2 记录超时错误:

实现日志记录方法以捕获超时错误进行调试和监控。
使用诸如 winston 的日志记录库或Node.js中的内置日志记录功能记录超时错误,以及相关的上下文信息,例如时间戳、请求参数和堆栈跟踪。
在适当的级别(例如错误或警告)记录超时错误,以确保它们易于识别并且可操作。

3 处理超时问题的策略:

查看应用程序日志以识别与超时错误相关的模式或趋势,例如一些特定的端点或操作始终经历超时。
监视系统资源(CPU、内存、磁盘 I/O),以识别导致超时的潜在瓶颈,例如高 CPU 使用率或内存不足。
使用诸如 Node.js 的内置--inspect标志或外部工具(如 node-clinic)分析应用程序性能,并识别需要改进的领域。
查看网络日志以识别导致超时的网络相关问题,例如网络延迟或数据包丢失。
评估第三方服务或依赖项的可靠性和性能问题,这些问题可能导致应用程序中的超时。

解决超时问题:

根据对应用程序性能和工作负载特征的分析调整超时设置。
优化应用程序代码以提高性能并减少超时的几率,例如优化数据库查询、实现缓存方法或使用异步编程模式。
为可能偶尔经历超时的重要操作实现重试方法,提供针对瞬态故障的灵活性。
考虑横向或纵向扩展应用程序资源,以处理增加的负载并减少高峰使用期间的超时几率。
与第三方服务提供商沟通,解决影响应用程序与其服务交互的性能或可靠性问题。
持续监控并根据持续的性能指标和用户反馈迭代超时管理策略。

结束语

总之,超时对于 Node.js 应用程序管理异步操作、网络交互和资源利用是必不可少的。

与超时相关的关键概念的总结:

正确的超时管理涉及根据应用程序需求设置适当的超时值、处理超时错误、根据性能指标监控和调整超时设置,并应用策略来

调试和解决超时问题。超时通常用于处理 HTTP 请求、异步操作、数据库查询、文件操作、API 调用、Websockets TCP 连接等场景。

Node.js 开发工作流程中有效地整合超时是构建健壮可靠应用程序的重要组成部分。它有助于防止资源耗尽、提高应用程序响应性,并改善用户体验。通过仔细实现和管理超时,您可以确保他们的 Node.js 应用程序能够灵活应对各种故障场景、为用户提供及时响应,并在不同的工作负载条件下保持最佳性能。

最后,构建健壮可靠的 Node.js 应用程序需要正确的超时管理。通过了解超时的重要性、实施超时管理的最佳实践,并持续监控和调整超时设置,您可以确保您的应用程序在各种使用场景下都具有弹性、响应灵敏且性能良好。