运行在 SSR 模式下的 Angular 应用的内存泄漏问题分析

运行在 SSR 模式下的 Angular 应用,为了避免服务器端和客户端两次调用同样的 API 引起屏幕的 Flickering 问题,通过都会使用Angular TransferState 服务将信息从服务器发送到客户端,其工作原理如下图所示:

首先在应用程序 app.module.ts 中导入 BrowserTransferStateModule

import { BrowserModule, BrowserTransferStateModule } from '@angular/platform-browser';
imports: [
  BrowserModule.withServerTransition({appId: 'my-app'}),
  BrowserTransferStateModule,
  ...
]

然后在服务器端模块 app.server.module.ts 中导入 ServerTransferStateModule

import { ServerModule, ServerTransferStateModule } from '@angular/platform-server';
imports: [
  AppModule,
  ServerModule,
  ServerTransferStateModule,
  ...
]

我们可以使用 makeStateKey 函数来创建一个键,以存储状态中的数据(将传递给浏览器)。 使用 this.state.get 从状态中获取数据,并使用 this.state.set 设置状态中的数据。 进行 API 调用时,使用之前调用 makeStateKey 创建的密钥将返回的数据存储在Angular state 中。

采用了 TransferState 服务的 Spartacus SSR 应用,在出现内存泄漏时通常有下列表现:

  • 用户请求响应时间增加
  • jsapps pods 频繁重启
  • 运行时出现如下日志:

(1) SSR rendering exceeded timeout 3000, fallbacking to CSR for /xyz

(2) [PM2][WORKER] Process 0 restarted because it exceeds --max-memory-restart value (current_memory=4009730048 max_memory_limit=3865051136 [octets])

(3) Rendering of /xyz was not able to complete. This might cause memory leaks!

如果出现了以上之一的症状,我们可以使用 Dynatrace 进行内存泄漏原因分析。

在 Dynatrace 中,可以从左侧导航菜单中的技术和进程选项和 Node.js 技术中找到每个 SSR pod。 进程组将具有包含您的应用程序 SSR 的主文件的名称,通常是 main.js 或 server.js。 单击后者将列出在指定时间范围内处于活动状态的 pod,允许我们访问任何单个 pod 的进程详细信息页面。

运行在 SSR 模式下的 Angular 应用的内存泄漏问题分析

在 high level 层面,仅通过查看 Dynatrace 中的 V8 堆内存图表就可以对内存泄漏的可能性做出明智的判断。 Node.js 中潜在内存泄漏的最明显迹象是:

  1. V8 堆内存出现峰值(sharp spike)
  2. 每次 pod 重新启动后,内存占用图都会再次出现峰值

通常情况下,如下图所示的锯齿模式(saw tooth)似乎表明应用程序健康,因为假设内存中的每个释放都是由于垃圾收集而发生的。 但是,如果您发现内存中的每个峰值之后都会有一个内存释放的行为,而该内存释放仅仅是因为 pod 的重启造成的,那么该应用很可能存在内存泄漏的问题。

如果我们增加系统的可用内存,但是 Dynatrace 里观测到的锯齿模式仍然存在,这种性能更能成为该应用存在内存泄漏的有力证据之一。

运行在 SSR 模式下的 Angular 应用的内存泄漏问题分析