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

NestJS集成googleapis服务致服务器Node.js内存溢出问题

NestJS集成googleapis后内存溢出问题排查与解决

问题背景

我有一个运行在约2GB内存的Ubuntu服务器(PM2托管)上的NestJS后端应用。添加GoogleIapService服务文件前应用运行正常,一旦添加该文件,Node就会崩溃并抛出错误:FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory。启动后内存持续增长至约950MB且不会回落。

环境信息

  • Node.js: v18.x(或其他版本)
  • NestJS: v10.x
  • googleapis: 最新版
  • 进程管理器:PM2(fork模式)
  • 服务器:Ubuntu,约2GB内存
  • 应用类型:REST + Socket.IO后端

服务代码

@Injectable()
export class GoogleIapService implements OnModuleInit, OnModuleDestroy {
  private androidPublisher;
  private googleAuth;
  private authClient;
  private isInitialized = false;
  private google;
  private cleanupTimeout: NodeJS.Timeout | null = null;

  constructor() {
    console.log(`Google IAP Initialized!`);
  }

  async onModuleInit() {
    if (this.isInitialized) return;

    try {
      this.google = await import('googleapis').then((m) => m.google);

      const serviceAccountJson = process.env.GOOGLE_SERVICE_ACCOUNT_JSON;
      if (!serviceAccountJson) {
        throw new Error('GOOGLE_SERVICE_ACCOUNT_JSON is not set');
      }

      const serviceAccountKey = JSON.parse(serviceAccountJson);

      this.googleAuth = new this.google.auth.GoogleAuth({
        credentials: serviceAccountKey,
        scopes: ['https://www.googleapis.com/auth/androidpublisher'],
      });

      this.authClient = await this.googleAuth.getClient();

      this.androidPublisher = this.google.androidpublisher({
        version: 'v3',
        auth: this.authClient,
      });

      this.isInitialized = true;
    } catch (err) {
      this.isInitialized = false;
      await this.forceCleanup();
      throw err;
    }
  }

  async onModuleDestroy() {
    await this.cleanup();
  }

  private async cleanup() {
    this.androidPublisher = null;
    this.authClient = null;
    this.googleAuth = null;
    this.google = null;
    this.isInitialized = false;
  }

  private forceCleanup() {
    this.androidPublisher = null;
    this.authClient = null;
    this.googleAuth = null;
    this.google = null;
    this.isInitialized = false;
  }
}

日志信息

<--- Last few GCs --->
Mark-Compact (reduce) ~953 MB
FATAL ERROR: Ineffective mark-compacts near heap limit
Allocation failed - JavaScript heap out of memory

已尝试的解决方法

  • 将Node堆内存上限提升至2GB(--max-old-space-size=2048)
  • 使用动态导入懒加载googleapis
  • 确保服务仅初始化一次
  • 实现OnModuleDestroy进行资源清理
  • 清除授权客户端、令牌、监听器及相关引用
  • 初始化失败时强制清理资源

核心疑问

为何在NestJS服务中仅导入并初始化googleapis就会导致小型服务器上内存持续增长并最终溢出?即便已做清理并提升堆内存仍未解决,这是googleapis/GoogleAuth的预期行为,还是我在长运行NestJS进程中的初始化方式有误?


问题分析与解决方案

1. googleapis库的内存开销特性

googleapis是包含所有Google API客户端的全量库,即便你只用到androidpublisher模块,动态导入时仍会加载整个库的核心结构与依赖,本身就会占用数百MB内存。在2GB内存的服务器上,加上NestJS、Socket.IO本身的内存占用,很容易触达Node的堆内存上限。

另外,GoogleAuth内部会维护令牌缓存、自动刷新令牌的定时器以及全局状态,这些对象可能存在内存引用无法被GC回收的情况,在长运行进程中会导致内存缓慢泄漏。

2. 初始化方式的潜在问题

你的代码虽做了清理,但存在几个关键问题:

  • 动态导入googleapis后,库的全局实例会留在Node的模块缓存中,即便把this.google设为null,这部分内存也无法释放。
  • GoogleAuth和authClient内部可能存在未被正确清理的事件监听器或定时器(比如令牌刷新任务),会阻止对象被垃圾回收。

3. 针对性解决方法

(1)仅导入需要的API模块

不要导入整个googleapis库,直接导入单个API客户端,大幅减少内存占用:

// 替换原动态导入逻辑
const { androidpublisher_v3 } = await import('googleapis/build/src/apis/androidpublisher/v3');
const { GoogleAuth } = await import('google-auth-library');

调整初始化代码:

this.googleAuth = new GoogleAuth({
  credentials: serviceAccountKey,
  scopes: ['https://www.googleapis.com/auth/androidpublisher'],
});
this.authClient = await this.googleAuth.getClient();
this.androidPublisher = new androidpublisher_v3.Androidpublisher({
  auth: this.authClient,
});

这种方式只会加载androidpublisher相关代码,内存占用可减少50%以上。

(2)优化GoogleAuth的资源管理

  • 显式停止authClient的令牌刷新定时器(针对JWT客户端):
  • 在cleanup方法中移除事件监听器:
private async cleanup() {
  // 停止令牌刷新定时器
  if (this.authClient?.stopRefreshTimer) {
    this.authClient.stopRefreshTimer();
  }
  // 移除所有事件监听器
  if (this.googleAuth?.removeAllListeners) {
    this.googleAuth.removeAllListeners();
  }
  this.androidPublisher = null;
  this.authClient = null;
  this.googleAuth = null;
  this.google = null;
  this.isInitialized = false;
}

(3)调整Node内存参数与PM2配置

服务器系统进程会占用部分内存,实际留给Node的内存不足2GB,建议调整内存参数并在PM2配置中明确指定:

{
  "apps": [
    {
      "name": "your-app",
      "script": "dist/main.js",
      "node_args": "--max-old-space-size=1536",
      "exec_mode": "fork"
    }
  ]
}

(4)延迟初始化

如果服务不是启动时就需要Google IAP功能,改为第一次调用相关接口时再初始化,避免启动时占用大量内存:

async getAndroidPublisher() {
  if (!this.isInitialized) {
    await this.onModuleInit();
  }
  return this.androidPublisher;
}

总结

内存溢出的核心原因是googleapis全量库的内存开销过大,加上长运行进程中部分资源未被正确回收。通过仅导入所需API模块、优化资源清理逻辑、调整内存参数这几个步骤,基本可以解决2GB服务器上的内存问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 15:05:04