You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Express.js+ts-node API运行数小时后出现context deadline exceeded超时错误求助

Express API部署后几小时出现请求超时(context deadline exceeded)问题排查与修复

问题描述

我使用Express.js + ts-node构建API,通过NGINX与Docker部署。服务启动后30分钟至1小时内运行正常,但数小时后,所有来自外部应用及Postman的请求均返回错误:context deadline exceeded (Client.Timeout exceeded while awaiting headers)。

主API模块代码

import express, { Request, Response } from "express";
import cors from "cors";
import dotenv from "dotenv";
import path from 'path';
import * as http from 'http';

import { CheckerRequestBody, CheckerResponse } from "./types";
import { blacklistHandler } from "../src/accounts/accountsMonitor";
import helmet from "helmet";
import { asyncLogger } from "../config/appConfig";
import { AuthToken, BlacklistedWallet } from "../db/models/entity";
import { createAuthorizationMiddleware } from "./middleware";

dotenv.config({ path: path.resolve(__dirname, '../.env') });

const app = express();
const PORT = parseInt(process.env.PORT) || 3000;
const DEFAULT_API_PATH: string = "/api/v1/";

export class DataFromDB{
    public static authTokens: AuthToken[] = [];
    public static blacklistedAddresses: Set<string> =  new Set<string>();
}

app.use(express.json());
app.use(cors({
    origin: '*', 
    methods: ['GET', 'POST'], 
    allowedHeaders: ['Content-Type', 'Authorization'],
    credentials: false, 
}));
app.use(helmet());

app.use(DEFAULT_API_PATH, createAuthorizationMiddleware());

app.post(DEFAULT_API_PATH + 'check', async (req: Request<{}, {}, CheckerRequestBody>, res: Response<CheckerResponse>) => {
    try { 
        const body: CheckerRequestBody = req.body;
        const accountAddresses: string[] = body.accountAddresses;
    
        const result = accountAddresses.some(address => DataFromDB.blacklistedAddresses.has(address));
                
        if (result) {
            res.status(200).json({
                blacklisted: true,
                message: "Blacklisted wallet found"
            }); 
        } else {
            res.status(200).json({
                blacklisted: false,
                message: "Blacklisted wallet not found"
            });         
        }
    } catch (error) {
        res.status(500).json({
            blacklisted: false,
            message: "Error on server occurred"
        }); 
        asyncLogger.error(`Error on API occurred: ${error}`);
    }
});

async function getDataFromDatabase(){
    try{
        const blacklistedAddressesObjects: BlacklistedWallet[] = await blacklistHandler.getAllEntities(blacklistHandler.BlacklistedWalletRepository);
        DataFromDB.authTokens = await blacklistHandler.getAllEntities(blacklistHandler.AuthTokensRepository);
        DataFromDB.blacklistedAddresses = new Set(blacklistedAddressesObjects.map(({ accountAddress }) => accountAddress));
        asyncLogger.info(`Succesfully retrieved data from Database in API module.`);
    } catch(error){
        asyncLogger.error(`Error occurred while retrieving data from Database in API: ${error}`)
    }
}

const server = http.createServer(app);

setInterval(async () => {
    await getDataFromDatabase();
}, 1000);

server.listen(PORT,  async () => {
  asyncLogger.info('Server is currently running on port 3000.');
});

server.on('error', (error) => {
    asyncLogger.error(`Error occurred: ${error.message}`);
});

中间件代码

import { asyncLogger } from "../config/appConfig";
import { Request, Response, NextFunction } from "express";
import { DataFromDB } from "./schema";

export const createAuthorizationMiddleware = () => {
    return async (req: Request, res: Response, next: NextFunction) => {
        if (req.path.startsWith("/docs")){
            next();
        }

        const authHeader = req.get('Authorization');
        
        if (!authHeader) {
            res.status(403).json({
                blacklisted: false,
                message: "No Authentication token provided"
            });
            return; 
        }
        
        try {

            const foundToken = DataFromDB.authTokens.find((entry) => entry.token === authHeader);

            if (!foundToken) {
                res.status(403).json({
                    blacklisted: false,
                    message: "Invalid Authentication token"
                });
                return;
            }

            
            const currentDate = new Date();
            const expirationDate = new Date(foundToken.expirationDate);

            if (currentDate > expirationDate) {
                res.status(403).json({
                    blacklisted: false,
                    message: "Authentication token has expired"
                }); 
                return; 
            }

            next(); 
        } catch (error) {
            asyncLogger.error(`Error reading the auth tokens: ${error}`);
            res.status(500).json({
                blacklisted: false,
                message: "Error on server occurred"
            }); 
        }
    };
};

核心问题分析

  1. 高频数据库轮询耗尽连接:代码中用setInterval每秒执行一次数据库查询,频繁的请求会耗尽数据库连接池资源,导致后续API请求无法获取数据库连接,进而阻塞处理流程,最终引发超时。
  2. 静态数据更新的竞态条件:DataFromDB的静态属性被频繁替换,中间件读取数据时可能遇到不完整的更新,导致请求挂起。
  3. 中间件路径处理漏洞:/docs路径调用next()后未终止后续逻辑,可能触发不必要的认证检查,引发异常。
  4. 资源与超时配置缺失:Docker容器资源限制、NGINX超时配置不合理也可能导致请求提前超时。

修复方案

1. 降低数据库轮询频率

将1秒的轮询间隔调整为更合理的时间(比如5分钟,即300000毫秒),如果需要实时同步数据,建议改用数据库变更通知机制(如PostgreSQL的LISTEN/NOTIFY、MongoDB的Change Streams)替代轮询:

setInterval(async () => {
    await getDataFromDatabase();
}, 300000); // 5分钟一次

2. 原子化更新静态数据

先一次性获取所有数据库数据,再原子替换静态属性,避免竞态条件:

async function getDataFromDatabase(){
    try{
        // 并行获取数据,提升效率
        const [blacklistedAddressesObjects, authTokens] = await Promise.all([
            blacklistHandler.getAllEntities(blacklistHandler.BlacklistedWalletRepository),
            blacklistHandler.getAllEntities(blacklistHandler.AuthTokensRepository)
        ]);
        const newBlacklistedSet = new Set(blacklistedAddressesObjects.map(({ accountAddress }) => accountAddress));
        
        // 原子替换,避免中间读取到不完整数据
        DataFromDB.authTokens = authTokens;
        DataFromDB.blacklistedAddresses = newBlacklistedSet;
        
        asyncLogger.info(`Succesfully retrieved data from Database in API module.`);
    } catch(error){
        asyncLogger.error(`Error occurred while retrieving data from Database in API: ${error}`)
    }
}

3. 修复中间件路径逻辑

调用next()后添加return,终止后续认证代码执行:

if (req.path.startsWith("/docs")){
    next();
    return; // 跳过后续认证逻辑
}

4. 配置资源与超时

  • Docker:为容器分配足够的内存与CPU资源,避免资源耗尽。
  • NGINX:调整代理超时配置,避免NGINX提前断开连接:
location / {
    proxy_pass http://api;
    proxy_connect_timeout 60s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;
}
  • Express:添加全局请求超时中间件,防止请求长时间挂起:
import timeout from 'connect-timeout';

app.use(timeout('10s'));
app.use((req: Request, res: Response, next: NextFunction) => {
    if (!req.timedout) next();
});

内容的提问来源于stack exchange,提问作者slsforme

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 18:57:03