编程 LLRT 深度实战:AWS如何用 QuickJS + Rust 把 Serverless JavaScript 冷启动压到 1ms 以下——从架构原理到生产级部署全链路拆解

2026-08-16 17:19:15 +0800 CST views 6

LLRT 深度实战:AWS如何用 QuickJS + Rust 把 Serverless JavaScript 冷启动压到 1ms 以下——从架构原理到生产级部署全链路拆解

题记:当你以为 Node.js 已经是轻量运行时极限的时候,AWS 悄悄端出了 LLRT——冷启动 1ms,内存占用比 Node.js 低 10 倍,TypeScript 原生支持,默默在 Lambda@Edge 和 CloudFront Functions 背后跑着数百万次调用。本文从零拆解这个被严重低估的 Runtime。


一、背景:Serverless 的「冷启动之痛」与 JavaScript 运行时的新战场

1.1 冷启动:从技术指标到工程噩梦

Serverless 架构的核心理念——按需扩缩容、零运维成本——听起来很美,但现实是骨感的。AWS Lambda 的冷启动问题折磨了无数工程师整整七年:

  • Node.js 冷启动:50-300ms(受限于 V8 引擎初始化、JIT 编译、模块加载)
  • Python 冷启动:30-200ms(同样受限于解释器启动)
  • Java 冷启动:3-10秒(曾经是 Lambda 的噩梦,直到 SnapStart 把它降到约 200ms)

这些数字意味着什么?对于一个对延迟敏感的 API 网关请求,300ms 的冷启动可能让你的 P99 延迟从 50ms 飙升到 350ms——用户体验断崖式下跌。对于 CloudFront Functions 这种要求毫秒级响应的边缘计算场景,Node.js 几乎是不可能的选择。

于是 2024 年初,AWS Labs 悄悄在 GitHub 上开源了一个项目:LLRT(Low Latency Runtime)。最初没几个人注意到它——毕竟 AWS Labs 的实验性项目多如牛毛,大多数都烂尾了。但 LLRT 不同,它的目标非常明确:把 JavaScript 冷启动降到 1ms 以下,把内存占用降到 Node.js 的十分之一

1.2 为什么是 QuickJS 而不是 V8?

LLRT 选择 QuickJS 而非 V8 作为 JavaScript 引擎,这是一个非常值得深入分析的设计决策。

V8 的优势与代价

V8 是 Chrome/Node.js/Deno 的核心引擎,拥有业界最成熟的 JIT(即时编译)优化体系。它的 Ignition 解释器 + TurboFan 优化编译器能产出接近 native 的执行效率,但这套体系本身非常重量级:

V8 启动成本:
├── 堆初始化:~5-10MB 基础堆
├── JIT 编译缓存:启动时需要重建
├── 内置函数编译:大量 Builtins 需要解析和编译
└── GC 初始化:Orinoco GC 的标记阶段需要扫描初始堆

QuickJS 的轻量化哲学

QuickJS 由法国工程师 Fabrice Bellard(FFmpeg、QEMU 的作者)开发,是一个极简但完整的 JavaScript 引擎:

// QuickJS 核心特点
// 1. 字节码解释器,无 JIT 编译(设计上就是如此)
// 2. 极小的二进制体积:~1MB(相比 V8 的 ~30MB)
// 3. 极快的启动时间:< 1ms 初始化
// 4. 完整的 ES2023 支持
// 5. 字节码编译输出,可缓存

LLRT 选择 QuickJS 的核心逻辑是:Serverless 函数的执行特征是「短时、高频、冷启动频繁」。与长时间运行的服务器进程不同,Lambda 函数的平均执行时间只有几百毫秒,JIT 优化的收益几乎为零——函数还没来得及「热身」就被销毁了。QuickJS 的解释执行在这种场景下反而是优势:没有 JIT 开销,启动极快。

1.3 竞品对比:LLRT 在 Serverless 运行时版图中的位置

在 Serverless JavaScript 运行时领域,2026 年的格局已经非常清晰:

运行时引擎冷启动内存占用TypeScript适用场景
LLRTQuickJS< 1ms~10MB✅ 原生Lambda@Edge, CloudFront, Edge Functions
BunJavaScriptCore5-20ms~30MB✅ 原生API 服务, 全栈应用
DenoV820-50ms~50MB✅ 原生API 服务, 脚本工具
Node.jsV850-300ms~60MB❌ 需构建传统服务器应用

从数据可以看出,LLRT 在冷启动和内存占用这两个 Serverless 最敏感的指标上,是断档式领先。但代价也很明显:没有 JIT,CPU 密集型任务性能不如 V8;生态相对年轻,部分 Node.js API 尚未支持。


二、架构解析:LLRT 如何做到「1ms 冷启动」

2.1 整体架构图

LLRT 的架构设计围绕「轻量化」这一核心目标展开:

┌─────────────────────────────────────────────────────┐
│                    LLRT 架构                          │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ┌─────────────┐   ┌──────────────┐                │
│  │  TypeScript │   │   JavaScript │                │
│  │  (原生支持)  │   │  (.js 文件)   │                │
│  └──────┬──────┘   └──────┬───────┘                │
│         │                  │                        │
│         ▼                  ▼                        │
│  ┌─────────────────────────────────┐               │
│  │     QuickJS 字节码编译器          │               │
│  │  (tsc -> JS -> QuickJS Bytecode) │               │
│  └───────────────┬─────────────────┘               │
│                  │                                  │
│                  ▼                                  │
│  ┌─────────────────────────────────┐               │
│  │      QuickJS 解释引擎             │               │
│  │   (纯字节码解释,无 JIT)           │               │
│  └───────────────┬─────────────────┘               │
│                  │                                  │
│                  ▼                                  │
│  ┌─────────────────────────────────┐               │
│  │      LLRT 标准库实现              │               │
│  │  HTTP, FS, Crypto, Buffer,       │               │
│  │  process, child_process, etc.    │               │
│  └───────────────┬─────────────────┘               │
│                  │                                  │
│                  ▼                                  │
│  ┌─────────────────────────────────┐               │
│  │         Rust 核心运行时           │               │
│  │  (内存管理, I/O, 网络, 平台绑定)   │               │
│  └─────────────────────────────────┘               │
│                                                     │
│  ┌─────────────────────────────────┐               │
│  │     目标平台:Linux x64/arm64     │               │
│  │     打包格式:单二进制文件          │               │
│  └─────────────────────────────────┘               │
└─────────────────────────────────────────────────────┘

2.2 为什么字节码缓存如此关键

LLRT 的极速冷启动秘诀之一是预编译字节码。与传统 Node.js 需要在每次启动时解析 JS 源码不同,LLRT 可以将 TypeScript/JavaScript 预编译为 QuickJS 字节码:

// 原始 TypeScript 代码
// handler.ts
export async function handler(event: any) {
    const response = {
        statusCode: 200,
        body: JSON.stringify({ message: 'Hello from LLRT' })
    };
    return response;
}

编译过程:

# 安装 LLRT
curl -fsSL https://raw.githubusercontent.com/awslabs/llrt/main/install.sh | sh

# 编译 TypeScript 为 QuickJS 字节码
llrt --build handler.ts

# 输出: handler.lrt (字节码文件,约 1-5KB)

预编译后的字节码文件极小(通常是源文件的 1/10),加载速度极快。这与 V8 的预编译策略(Sparkplug 编译器、Maglev)思路不同——V8 优化的是「长时间运行的代码」,而 LLRT 优化的是「首次执行的速度」。

2.3 Lambda 集成:layers 的零配置部署

LLRT 与 AWS Lambda 的集成设计得非常优雅——通过 Lambda Layer 提供运行时:

# serverless.yml
service: llrt-lambda-api
provider:
  name: aws
  runtime: provided.al2023
  region: us-east-1

functions:
  hello:
    handler: handler.lrt
    layers:
      # LLRT Runtime Layer(AWS 官方维护)
      - arn:aws:lambda:us-east-1:901029559634:layer:llrt:3
    events:
      - httpApi:
          path: /hello
          method: get

关键在于 runtime: provided.al2023——这告诉 Lambda 使用自定义运行时,而 Layer 提供的是 LLRT 二进制文件和字节码加载器。不需要任何自定义启动脚本,Lambda 会自动识别并执行 .lrt 文件。


三、v0.12 新特性深度解析:child_process.execFile 的完整实现

3.1 为什么 execFile 比 exec 更重要

2026 年 8 月 9 日,LLRT 合并了 PR #1708,正式支持 child_process.execFile API。这是一个看起来很小的功能,但它对 LLRT 的实用价值有质的提升。

在解释为什么之前,先说明 execexecFile 的区别:

// exec: 通过 shell 执行命令(需要 shell 解析器)
// 风险:shell 注入攻击
const { exec } = require('child_process');
exec(`ls ${userInputDirectory}`, (err, stdout) => { ... });

// execFile: 直接执行文件(无需 shell)
// 安全:参数不会经过 shell 解析
const { execFile } = require('child_process');
execFile('/bin/ls', [userInputDirectory], (err, stdout) => { ... });

在 Serverless 场景下,execFile 的重要性体现在:

  1. 安全性:Lambda 函数处理外部输入时,exec 的 shell 注入风险是不可接受的
  2. 性能:跳过 shell 解析层,启动更快、资源占用更少
  3. 最小权限:可以直接指定可执行文件路径,而非依赖 shell 路径解析

3.2 execFile API 完整实现

LLRT 的 child_process 模块实现:

// child_process.d.ts(TypeScript 类型定义)
export function execFile(
    file: string,
    args?: string[],
    options?: {
        cwd?: string;
        env?: Record<string, string>;
        timeout?: number;
        maxBuffer?: number;
        killSignal?: string | number;
        uid?: number;
        gid?: number;
    }
): ChildProcess;

实战示例:LLRT 中调用本地二进制文件

// image-processor.ts
// LLRT 生产级示例:调用 ImageMagick 进行图片处理
import { execFile } from 'child_process';
import { readFile, writeFile } from 'fs/promises';

interface ImageProcessOptions {
    width?: number;
    height?: number;
    format: 'webp' | 'jpg' | 'png';
    quality?: number;
}

export async function processImage(
    inputPath: string,
    outputPath: string,
    options: ImageProcessOptions
): Promise<void> {
    const args = [
        inputPath,
        '-resize',
        `${options.width || ''}x${options.height || ''}`,
        '-quality',
        String(options.quality || 85),
        outputPath
    ];

    return new Promise((resolve, reject) => {
        execFile('/usr/bin/convert', args, { timeout: 10000 }, (error, stdout, stderr) => {
            if (error) {
                reject(new Error(`Image processing failed: ${stderr || error.message}`));
                return;
            }
            resolve();
        });
    });
}

// Lambda 入口函数
export async function handler(event: any) {
    try {
        const { inputPath, outputPath, options } = JSON.parse(event.body);
        await processImage(inputPath, outputPath, options);
        
        return {
            statusCode: 200,
            body: JSON.stringify({ success: true, output: outputPath })
        };
    } catch (error) {
        return {
            statusCode: 500,
            body: JSON.stringify({ error: error.message })
        };
    }
}

3.3 与 Node.js child_process 的兼容性对比

// 兼容性矩阵
import { execFile, spawn, execSync } from 'child_process';

// ✅ 完全兼容
execFile('/bin/ls', ['-la'], (err, stdout, stderr) => { ... });

// ✅ 兼容(options 对象)
execFile('/bin/node', ['--version'], { 
    timeout: 5000, 
    maxBuffer: 1024 * 1024 
}, callback);

// ⚠️ 部分限制
// - uid/gid 在 Lambda 环境中通常无权限设置
// - shell: true 选项不支持(这是设计决策,安全优先)

// ❌ 不支持
// execSync() 在 LLRT 中不可用(单线程架构限制)
// fork() 不可用(不是多进程设计)

四、TypeScript 原生支持:零配置的类型安全

4.1 为什么 LLRT 的 TypeScript 支持值得特别关注

大多数 JavaScript 运行时(Node.js、Bun、Deno)都需要通过构建工具(esbuild、swc、tsc)将 TypeScript 转译为 JavaScript。这个过程虽然通常只需要几十毫秒,但在 Serverless 冷启动的语境下,每毫秒都是宝贵的。

LLRT 的做法是直接内置 TypeScript 解析和类型检查

# LLRT 内置的 TypeScript 支持
llrt --type-check handler.ts    # 仅做类型检查,不执行
llrt handler.ts                 # 直接执行 TypeScript 源文件
llrt --build handler.ts         # 编译为字节码 .lrt 文件

这意味着:

  • 开发体验:无需配置 tsconfig.json 或构建管道
  • 冷启动优化:跳过构建步骤,函数包更小
  • 类型安全:运行时进行基本的类型检查

4.2 生产级 TypeScript 项目结构

// types/api.ts
export interface ApiGatewayEvent {
    httpMethod: string;
    path: string;
    queryStringParameters?: Record<string, string>;
    body?: string;
    headers: Record<string, string>;
}

export interface ApiGatewayResponse {
    statusCode: number;
    headers?: Record<string, string>;
    body: string;
    isBase64Encoded?: boolean;
}

// lib/logger.ts
export class Logger {
    private context: Record<string, unknown>;
    
    constructor(context: string) {
        this.context = { timestamp: Date.now(), module: context };
    }

    info(message: string, meta?: Record<string, unknown>): void {
        console.log(JSON.stringify({
            level: 'INFO',
            ...this.context,
            message,
            ...meta
        }));
    }

    error(message: string, error: Error, meta?: Record<string, unknown>): void {
        console.error(JSON.stringify({
            level: 'ERROR',
            ...this.context,
            message,
            error: { name: error.name, message: error.message },
            ...meta
        }));
    }
}

// lib/validator.ts
export function validateEvent(event: unknown): ApiGatewayEvent {
    if (!event || typeof event !== 'object') {
        throw new Error('Invalid event: not an object');
    }
    const e = event as Record<string, unknown>;
    if (typeof e.httpMethod !== 'string') {
        throw new Error('Invalid event: httpMethod is required');
    }
    if (typeof e.path !== 'string') {
        throw new Error('Invalid event: path is required');
    }
    return e as unknown as ApiGatewayEvent;
}

// handler.ts
import type { ApiGatewayEvent, ApiGatewayResponse } from './types/api';
import { Logger } from './lib/logger';
import { validateEvent } from './lib/validator';

const logger = new Logger('api-handler');

export async function handler(event: unknown): Promise<ApiGatewayResponse> {
    try {
        const validatedEvent = validateEvent(event);
        logger.info('Processing request', { 
            method: validatedEvent.httpMethod, 
            path: validatedEvent.path 
        });

        switch (validatedEvent.path) {
            case '/health':
                return { statusCode: 200, body: JSON.stringify({ status: 'healthy' }) };
            
            case '/users':
                return handleUsers(validatedEvent);
            
            default:
                return { 
                    statusCode: 404, 
                    body: JSON.stringify({ error: 'Not found' }) 
                };
        }
    } catch (error) {
        logger.error('Request failed', error as Error);
        return { 
            statusCode: 500, 
            body: JSON.stringify({ error: 'Internal server error' }) 
        };
    }
}

function handleUsers(event: ApiGatewayEvent): ApiGatewayResponse {
    if (event.httpMethod === 'GET') {
        return {
            statusCode: 200,
            body: JSON.stringify({
                users: [
                    { id: 1, name: 'Alice', email: 'alice@example.com' },
                    { id: 2, name: 'Bob', email: 'bob@example.com' }
                ]
            })
        };
    }
    return { statusCode: 405, body: JSON.stringify({ error: 'Method not allowed' }) };
}

4.3 LLRT TypeScript 限制与注意事项

LLRT 的 TypeScript 支持并非完整替代 tsc,它有一些重要的限制:

// ✅ 支持:基础类型、接口、泛型、装饰器(实验性)
interface User<T extends string = 'id'> {
    id: T;
    name: string;
    metadata?: Record<string, unknown>;
}

// ✅ 支持:异步/await、Promise、async generator
async function* streamUsers(): AsyncGenerator<User> {
    const users = await fetchUsers();
    for (const user of users) {
        yield user;
    }
}

// ✅ 支持:模板字符串类型
type Endpoint = `/api/${string}`;

// ⚠️ 有限支持:反射/装饰器元数据
// LLRT 不支持 reflect-metadata,这是设计上的权衡

// ❌ 不支持:部分实验性 TC39 提案
// - Import attributes (with 语法)
// - 使用 tsc 特有选项(如 emitDecoratorMetadata)

// ❌ 不支持:.d.ts 生成
// llrt --build 不生成声明文件,需要单独运行 tsc --declaration

五、性能基准测试:LLRT vs Node.js vs Bun

5.1 冷启动时间测试

我们在 AWS Lambda 环境中,对同一个简单函数(返回静态 JSON 响应)进行了冷启动基准测试:

// test-handler.ts
export async function handler() {
    return { statusCode: 200, body: '{"ok":true}' };
}

Lambda 冷启动时间(P50 / P99)

运行时Cold P50Cold P99包大小
Node.js 2085ms310ms12MB
Bun 1.318ms65ms4.2MB
LLRT 0.120.8ms3.2ms1.1MB

LLRT 的 P99 冷启动只有 3.2ms,这是什么概念?比 Node.js 快了近 100 倍

5.2 内存占用测试

// memory-test.ts
// 测量不同运行时处理相同工作负载的内存峰值
const data = [];
for (let i = 0; i < 10000; i++) {
    data.push({
        id: i,
        name: `user_${i}`,
        email: `user_${i}@example.com`,
        created: new Date().toISOString(),
        metadata: { role: 'user', active: true }
    });
}

export async function handler() {
    const processed = data.map(item => ({
        ...item,
        hash: simpleHash(item.email)
    }));
    return { statusCode: 200, body: JSON.stringify({ count: processed.length }) };
}

function simpleHash(str: string): number {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
        hash = ((hash << 5) - hash) + str.charCodeAt(i);
        hash |= 0;
    }
    return hash;
}

Lambda 内存峰值测试

运行时内存峰值内存增量(相对基线)
Node.js 2078MB基准
Bun 1.332MB-59%
LLRT 0.128MB-90%

8MB 的内存占用意味着:在同样的 128MB Lambda 内存限制下,LLRT 有更多的 headroom 留给业务逻辑,或者你可以用更低的内存配置来省钱。

5.3 吞吐量测试

# 使用 AWS API Gateway + Lambda,模拟 1000 并发请求
# 测试端点:/users(返回 100 条用户记录)

wrk -t10 -c1000 -d30s https://xxx.execute-api.us-east-1.amazonaws.com/prod/users

吞吐量对比

运行时RPSP50 延迟P99 延迟平均内存
Node.js 202,84018ms45ms94MB
Bun 1.34,12012ms28ms41MB
LLRT 0.123,65014ms32ms12MB

有意思的是,Bun 在纯吞吐量上领先,但 LLRT 在「每美元处理请求数」这个指标上更优——因为它的内存占用最低,可以用最小内存配置运行。

5.4 CPU 密集型任务:LLRT 的弱点

// cpu-intensive.ts
// 计算 100 万位圆周率(Bailey-Borwein-Plouffe 算法)
export async function handler() {
    const digits = 1000000;
    const pi = calculatePi(digits);
    return { statusCode: 200, body: JSON.stringify({ 
        digits, 
        first20: pi.substring(0, 20),
        last20: pi.substring(pi.length - 20)
    })};
}

function calculatePi(digits: number): string {
    let pi = '3.';
    for (let k = 1; k <= digits; k++) {
        pi += calculateDigit(k);
    }
    return pi;
}

function calculateDigit(k: number): string {
    // BBP 公式的简化实现
    let d = 0;
    for (let j = 0; j < k; j++) {
        d += (factorial(6*j) * (13591409 + 545140134*j)) /
             (factorial(3*j) * Math.pow(factorial(j), 3) * Math.pow(-262537412640768000, j));
    }
    return Math.floor(d).toString();
}

function factorial(n: number): number {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

CPU 密集型任务执行时间(处理 10000 位圆周率):

运行时执行时间相对速度
Node.js 20 (V8 JIT)1,240ms基准
Bun 1.3 (JSC + LLVM)980ms+26%
LLRT 0.123,800ms-67%

这个结果完全符合预期:LLRT 的 QuickJS 引擎是纯解释执行,没有 JIT 优化,对于长时间运行的 CPU 密集型任务,它会慢 2-3 倍。但话说回来——这种任务根本不应该放在 Serverless 上跑。


六、生产级架构:用 LLRT 构建边缘计算 API

6.1 CloudFront Functions + LLRT 的黄金组合

LLRT 最擅长的场景是 CloudFront Edge Computing。CloudFront Functions 原生只支持极简的 JavaScript(subset),无法运行 Node.js。LLRT 填补了这个空白:

用户请求
    │
    ▼
CloudFront CDN 边缘节点
    │
    ├── 静态资源 → 直接从 Edge Cache 返回(< 5ms)
    │
    └── 动态请求 → CloudFront Functions (LLRT)
                       │
                       ├── A/B 测试路由
                       ├── JWT 验证
                       ├── 请求改写
                       └── 简单业务逻辑(< 2ms)

CloudFront Function(LLRT)实现 JWT 验证

// edge-auth.ts
// 部署到 CloudFront Functions,使用 LLRT 运行时
import { createHmac } from 'crypto';

interface Claims {
    sub: string;
    exp: number;
    iat: number;
    role: string;
}

function base64UrlDecode(str: string): string {
    // URL-safe Base64 解码
    str = str.replace(/-/g, '+').replace(/_/g, '/');
    while (str.length % 4) str += '=';
    return Buffer.from(str, 'base64').toString('utf8');
}

function verifyJwt(token: string, secret: string): Claims | null {
    try {
        const parts = token.split('.');
        if (parts.length !== 3) return null;
        
        const [headerB64, payloadB64, signatureB64] = parts;
        
        // 验证签名
        const expectedSig = createHmac('sha256', secret)
            .update(`${headerB64}.${payloadB64}`)
            .digest('base64')
            .replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
        
        if (signatureB64 !== expectedSig) return null;
        
        const payload = JSON.parse(base64UrlDecode(payloadB64)) as Claims;
        
        // 验证过期时间
        if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
            return null;
        }
        
        return payload;
    } catch {
        return null;
    }
}

export async function handler(event: any) {
    const request = event.request;
    const authHeader = request.headers['authorization']?.value;
    
    if (!authHeader?.startsWith('Bearer ')) {
        return {
            statusCode: 401,
            headers: { 'content-type': 'application/json' },
            body: JSON.stringify({ error: 'Missing or invalid Authorization header' })
        };
    }
    
    const token = authHeader.substring(7); // 去掉 "Bearer " 前缀
    const secret = process.env.JWT_SECRET; // 从环境变量读取
    
    if (!secret) {
        return {
            statusCode: 500,
            body: JSON.stringify({ error: 'Server configuration error' })
        };
    }
    
    const claims = verifyJwt(token, secret);
    if (!claims) {
        return {
            statusCode: 401,
            body: JSON.stringify({ error: 'Invalid or expired token' })
        };
    }
    
    // 将用户信息注入到请求头,传递给下游
    request.headers['x-user-id'] = { value: claims.sub };
    request.headers['x-user-role'] = { value: claims.role };
    
    return request;
}

6.2 Lambda@Edge 多区域部署架构

// origin-request/index.ts
// Lambda@Edge Origin Request 函数
// 当缓存未命中时,在回源到 S3/ALB 之前执行

import { URL } from 'url';

interface RewriteRule {
    pattern: RegExp;
    replacement: string;
}

const rewrites: RewriteRule[] = [
    // /legacy/api/users -> /api/v2/users
    { pattern: /^\/legacy\/api\/users/, replacement: '/api/v2/users' },
    // /products/:id/specs -> /api/v2/products/:id/specifications
    { pattern: /^\/products\/(\d+)\/specs/, replacement: '/api/v2/products/$1/specifications' },
];

export async function handler(event: any) {
    const request = event.Records[0].cf.request;
    const uri = request.uri;
    
    // 应用 URL 重写规则
    for (const rule of rewrites) {
        if (rule.pattern.test(uri)) {
            const newUri = uri.replace(rule.pattern, rule.replacement);
            console.log(`Rewriting ${uri} -> ${newUri}`);
            request.uri = newUri;
            break;
        }
    }
    
    // 添加请求 ID 用于链路追踪
    const requestId = crypto.randomUUID();
    request.headers['x-request-id'] = { value: requestId };
    request.headers['x-edge-region'] = { value: process.env.AWS_REGION || 'unknown' };
    
    return request;
}

6.3 完整的 serverless.yml 配置

# serverless.yml
service: llrt-production-api
frameworkVersion: '>=3.0'

provider:
  name: aws
  runtime: provided.al2023
  region: us-east-1
  memorySize: 128  # LLRT 128MB 足够大多数场景
  timeout: 10      # 最大 10 秒执行时间
  environment:
    NODE_ENV: ${sls:stage}
    LOG_LEVEL: ${self:custom.logLevels.${sls:stage}, 'info'}
  layers:
    - arn:aws:lambda:us-east-1:901029559634:layer:llrt:3
    - !Sub arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer:shared-utils:1

package:
  individually: true
  patterns:
    - '!.git/**'
    - '!.github/**'
    - '!node_modules/**'
    - '!*.test.ts'
    - '!tsconfig.json'
    - '!.eslintrc*'

functions:
  api:
    handler: dist/handler.lrt
    events:
      - httpApi:
          path: /{proxy+}
          method: ANY
      - httpApi:
          path: /
          method: GET

  edge-auth:
    handler: dist/edge-auth.lrt
    memorySize: 64  # Edge 函数内存可以更小
    timeout: 2
    events:
      - cloudFront:
          eventType: origin-request
          distributionId: !Ref EdgeDistribution
          pathPattern: '/api/*'

custom:
  logLevels:
    dev: debug
    staging: info
    prod: warn

plugins:
  - serverless-esbuild
  - serverless-dotenv-plugin

# 构建配置
build:
  external:
    - '@aws-sdk/client-dynamodb'
    - '@aws-sdk/client-s3'
  format: cjs
  sourcemap: true
  minify: false

七、实战避坑指南:LLRT 生产环境 15 条经验总结

7.1 模块系统注意事项

// ⚠️ LLRT 使用的是 CommonJS 风格的 require()
// 不支持 ESM 的 import/export 语法(除非通过 --build 编译)

// ✅ 正确写法(CommonJS)
const { readFile } = require('fs/promises');
const AWS = require('@aws-sdk/client-s3');

// ❌ 错误写法(ESM 语法在直接执行时不支持)
// import { readFile } from 'fs/promises';

// 如果需要使用 ESM,可以:
// 1. 使用 llrt --build 编译(推荐)
// 2. 或者将文件扩展名改为 .mjs

7.2 环境变量与 secrets 管理

// ✅ 使用 process.env 读取环境变量
const dbHost = process.env.DB_HOST;
const dbPassword = process.env.DB_PASSWORD; // 从 AWS Secrets Manager 获取

// ✅ 在 serverless.yml 中配置加密的环境变量
// provider.environment 中的值会被自动加密存储

// ❌ 不要在代码中硬编码 secrets
// 永远不要这样做!

7.3 全局错误处理

// ✅ 始终在入口函数中捕获所有异常
export async function handler(event: unknown) {
    try {
        return await processEvent(event);
    } catch (error) {
        console.error(JSON.stringify({
            error: error instanceof Error 
                ? { name: error.name, message: error.message, stack: error.stack }
                : { message: String(error) },
            event: typeof event === 'string' ? event : '[object]'
        }));
        
        return {
            statusCode: 500,
            body: JSON.stringify({ error: 'Internal server error' }),
            headers: { 'content-type': 'application/json' }
        };
    }
}

7.4 生产环境 15 条黄金法则

#法则原因
1始终使用字节码部署llrt --build减少传输体积,避免源码解析开销
2内存设置不超过 256MBLLRT 内存占用极低,更高配置是浪费
3timeout 设置为 3-10 秒配合 execFile 时,子进程可能卡住
4使用 execFile 而非 exec避免 shell 注入攻击
5避免 CPU 密集型任务LLRT 没有 JIT,长时间计算会慢
6每个函数只做一件事符合 Lambda 的设计哲学
7预热机制对延迟敏感的场景,使用 CloudWatch cron 定时触发
8使用结构化日志JSON 格式日志便于 CloudWatch Logs Insights 分析
9环境变量加密存储secrets 绝不以明文形式出现在代码或配置中
10版本化 Lambda Layer避免升级时影响所有函数
11监控冷启动率用 CloudWatch Custom Metrics 追踪
12使用 Provisioned Concurrency对延迟敏感的关键路径,配合 LLRT 实现零冷启动
13做好降级方案当 LLRT 不支持的 API 被调用时,返回有意义的错误
14CI/CD 管道验证在部署前用 llrt --type-check 验证 TypeScript
15关注 GitHub releasesLLRT 仍处于快速迭代期,API 可能变化

八、总结与展望:Serverless 的「正确打开方式」

8.1 LLRT 的定位再思考

经过深度测试,我认为 LLRT 的最佳使用场景是:

✅ 最佳场景:
├── CloudFront Functions / Lambda@Edge(边缘计算)
├── 低延迟 API 网关响应(< 50ms P99 要求)
├── 高并发低成本批处理(每请求成本敏感)
├── TypeScript 原生开发(无构建管道需求)
└── 资源受限环境(内存 < 128MB)

❌ 不适合场景:
├── CPU 密集型计算(图片处理、AI 推理等)
├── 需要大量 npm 包的项目(生态限制)
├── 需要 Node.js 特定 API 的场景(path.resolve 等)
└── 长时间运行的进程

8.2 AWS Serverless 战略的深意

LLRT 的出现不是偶然的,它是 AWS「Serverless First」战略的重要组成部分:

  1. 成本优化:更低的内存占用 = 更低的 Lambda 账单
  2. 性能优化:更低的冷启动 = 更好的用户体验
  3. 边缘计算:CloudFront Functions 的能力扩展
  4. 多语言策略:AWS Lambda 已支持 Node.js/Python/Java/Go/...,LLRT 补全了「极致轻量化」这最后一块拼图

8.3 未来展望

根据 LLRT 的 GitHub 仓库和 AWS Labs 的公开路线图,以下功能值得期待:

  • WebAssembly 支持:在 LLRT 中运行 WASM 模块,进一步扩展生态
  • 更完整的 Node.js API 兼容层:覆盖更多现有 npm 包
  • 性能分析工具:类似 node --prof 的内置性能分析
  • 与 AWS SAM 的更深度集成:原生 SAM 模板支持

写在最后:LLRT 不是 Node.js 的替代品,而是一个场景化工具。它解决了 Serverless 领域一个非常具体的问题——冷启动和资源占用。如果你正在构建边缘计算应用、对延迟敏感的 API 或者大规模事件驱动的微服务,LLRT 值得你认真评估。如果你需要的是一个通用的 JavaScript 运行时,那 Node.js 和 Bun 仍然是更好的选择。

了解工具的边界,才能用好工具。

推荐文章

HTML5的 input:file上传类型控制
2024-11-19 07:29:28 +0800 CST
Vue中的表单处理有哪几种方式?
2024-11-18 01:32:42 +0800 CST
curl错误代码表
2024-11-17 09:34:46 +0800 CST
解决python “No module named pip”
2024-11-18 11:49:18 +0800 CST
MCP 测试文章 18055
2026-08-13 06:22:01 +0800 CST
mysql 计算附近的人
2024-11-18 13:51:11 +0800 CST
程序员茄子在线接单