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

Node.js应用启动方式差异致S3流式解压文件损坏问题排查

问题背景

我遇到了一个异常问题:以下是我的Node.js函数,用于流式解压存储在AWS S3存储桶中的ZIP文件,无需将整个ZIP文件下载到本地文件系统,直接将压缩内容提取到本地。

流式解压函数

const streamUnzipFromS3ToLocal = async (
  objectBucket: string,
  objectKey: string,
  extractDir: string,
) => {
  try {
    const data = await S3.getFile(objectBucket, objectKey); // data.Body is a readable stream;
    try {
      await pipeline(data.Body, unzipper.Extract({ path: extractDir }));
    } catch (error) {
      logger.error(error);
    }
    logger.info(`${objectKey} is decompressed to ${extractDir}`);
  } catch (error) {
    logger.error(`Failed to stream unzip file ${objectKey}: ${error}`);
    throw new Error('StreamUnzipFromS3Error');
  }
};

S3文件获取函数

import { s3Client } from 'utils/AWS/s3Client';
import { GetObjectCommand} from '@aws-sdk/client-s3';

export const getFile = async (bucket_name, object_key) => {
  let res;
  const params = {
    Bucket: bucket_name,
    Key: object_key,
  };

  try {
    const data = await s3Client.send(new GetObjectCommand(params));
    res = data;
  } catch (err) {
    logger.error(`Failed when s3Controller getFile: ${err}`);
  }

  return res;
};

解压完成后,我通过执行md5sum extracted_files/00001.dcm获取提取文件的哈希值。

三种启动方式及结果

  • yarn start:提取的文件md5sum哈希值每次都不同(测试3次得到3个不同值),且用.dcm工具解析时提示文件损坏。
  • sudo yarn start:提取的文件哈希值一致,解析正常。
  • sudo systemctl start myapp.service:myapp.service配置如下,该方式提取的文件哈希值一致,解析正常。
[Unit]
Description=myapp backend api 
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/ubuntu/myapp/code/api
Restart=always
RestartSec=1
User=ubuntu
ExecStart=yarn start

[Install]
WantedBy=multi-user.target

yarn start对应的实际命令为:nodemon --exec ts-node --files src/index.ts

环境信息

ubuntu@ip-123-123-123-123:~/myapp/api$ sudo which node
/usr/bin/node
ubuntu@ip-123-123-123-123:~/myapp/api$ sudo node -v
v10.19.0

ubuntu@ip-123-123-123-123:~/myapp/api$ which node
/home/ubuntu/.nvm/versions/node/v21.2.0/bin/node
ubuntu@ip-123-123-123-123:~/myapp/api$ node -v 
v21.2.0

ubuntu@ip-123-123-123-123:~/myapp/api$ yarn -v 
1.22.19
ubuntu@ip-123-123-123-123:~/myapp/api$ which yarn 
/usr/bin/yarn

疑问

  1. 上述三种启动方式的差异是什么,导致第一种方式失败而另外两种正常?
  2. sudo yarn start与sudo systemctl start myapp.service的差异是什么?后者是否是以sudo权限、ubuntu用户执行yarn start?

解答

问题1:三种启动方式的差异及失败原因

三种方式的核心差异在于运行环境的Node.js版本、权限以及进程运行上下文:

1. yarn start的运行逻辑

  • 使用普通用户ubuntu的shell环境,调用nvm管理的v21.2.0版本Node.js,这是较新的非LTS版本,可能在stream管道处理、文件IO缓冲机制上存在未验证的bug,导致从S3流式读取的数据写入本地时出现截断、乱序或重复,最终文件损坏、哈希值不一致。
  • 同时,普通用户可能面临系统级IO资源限制(如文件描述符数量、缓冲区大小),进一步加剧流式写入的不稳定性。

2. sudo yarn start的运行逻辑

  • 以root权限运行,调用系统全局安装的v10.19.0版本Node.js(旧LTS稳定版)。root权限拥有更高的系统资源配额,且该版本的stream模块经过长期生产验证,流式处理逻辑稳定,不会出现数据写入异常。

3. sudo systemctl start myapp.service的运行逻辑

  • 虽然用sudo触发服务启动,但服务配置中指定了User=ubuntu,实际运行用户是ubuntu,但进程处于systemd的受控环境:
    • systemd使用标准化的系统环境变量,不继承用户shell中的nvm配置,默认调用系统全局的v10.19.0版本Node.js,避免了高版本的兼容性问题。
    • systemd会为进程设置稳定的资源限制和运行上下文,避免用户shell中的异常信号、资源竞争干扰流式写入。

总结:第一种方式失败的核心原因是使用了存在stream兼容问题的高版本Node.js(v21.2.0),而另外两种方式实际使用稳定的旧版本Node.js,同时运行上下文更可靠。

问题2:sudo yarn start与sudo systemctl start myapp.service的差异

1. 运行用户与权限

  • sudo yarn start:直接以root用户运行,拥有完全系统权限。
  • sudo systemctl start myapp.service:sudo仅用于触发systemd启动服务,服务实际以配置中的User=ubuntu运行,权限为ubuntu用户权限,并非root权限。

2. 运行上下文

  • sudo yarn start:依赖当前shell的环境,继承shell的所有变量(如nvm配置),关闭当前shell会导致进程终止。
  • sudo systemctl start myapp.service:进程由systemd托管,运行在独立的系统上下文,不依赖用户shell,会在后台持续运行,systemd还负责进程的自动重启、日志管理等。

3. Node.js版本

  • 两者均调用系统全局的v10.19.0版本Node.js,但原因不同:前者是root用户环境默认路径,后者是systemd的标准环境路径不包含nvm目录,无法调用v21.2.0。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 14:34:58