Serverless 实践:从零构建云原生应用

一篇涵盖 Serverless 核心概念、主流平台选型和实战开发的完整指南,帮助你掌握无服务器架构的设计与落地。


目录


一、Serverless 概述

1.1 什么是 Serverless

Serverless(无服务器)是一种云计算执行模型,云提供商动态管理服务器资源分配,开发者只需关注业务代码。

┌─────────────────────────────────────────────────────────────────┐
│                     Serverless 架构演进                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  传统架构                  容器化                Serverless       │
│  ┌─────────────┐          ┌─────────────┐      ┌─────────────┐ │
│  │ 物理服务器   │    ──▶  │ 虚拟机/容器  │──▶  │ 函数/服务    │ │
│  │             │          │             │      │             │ │
│  │ - 购买硬件   │          │ - 管理OS    │      │ - 只写代码   │ │
│  │ - 运维机房   │          │ - 配置网络   │      │ - 按需付费   │ │
│  │ - 固定成本   │          │ - 调度容器   │      │ - 自动扩缩   │ │
│  │ - 手动扩容   │          │ - 部分弹性   │      │ - 事件驱动   │ │
│  └─────────────┘          └─────────────┘      └─────────────┘ │
│                                                                 │
│  运维负担:高 ──────────────▶ 中 ──────────────▶ 低            │
│  灵活性:低 ──────────────▶ 中 ──────────────▶ 高              │
│  成本优化:差 ──────────────▶ 中 ──────────────▶ 好            │
└─────────────────────────────────────────────────────────────────┘

1.2 Serverless 核心特点

特点说明收益
无需管理服务器云厂商管理基础设施降低运维成本
按需付费按执行次数和时长计费成本优化,按实际使用付费
自动弹性伸缩根据请求量自动扩缩容应对流量峰值无需预判
事件驱动由事件触发执行松耦合架构
无状态每次执行独立简化开发模型

1.3 Serverless 适用场景

适合的场景

  • API 后端:RESTful API、GraphQL 服务
  • 数据处理:ETL、实时数据流处理
  • 定时任务:定时调度、批处理作业
  • 事件处理:消息队列处理、文件上传处理
  • Webhook 处理:第三方服务回调
  • AI 推理:模型推理、图像处理

不适合的场景

  • 长时间运行任务:超过函数执行时间限制(通常15分钟)
  • 高延迟敏感:冷启动可能导致延迟
  • 复杂状态管理:有状态应用不适合
  • 高频低延迟:数据库连接池等难以复用

1.4 FaaS vs BaaS

类型说明示例
FaaS (Function as a Service)函数即服务,运行业务代码AWS Lambda、阿里云函数计算
BaaS (Backend as a Service)后端即服务,提供现成服务Firebase、Supabase、云数据库

完整的 Serverless 应用通常是 FaaS + BaaS 的组合。


二、主流 Serverless 平台对比

2.1 平台概览

平台提供商特点适用场景
AWS LambdaAWS最成熟、生态最完善企业级应用、AWS 生态
阿里云函数计算阿里云国内领先、中文文档国内业务、阿里云生态
腾讯云 SCF腾讯云微信生态集成小程序、微信相关业务
Azure FunctionsMicrosoft企业集成、.NET 支持企业应用、Microsoft 生态
Google Cloud FunctionsGoogle与 GCP 深度集成大数据、AI 应用
Cloudflare WorkersCloudflare边缘计算、全球分布全球业务、边缘场景

2.2 详细对比

维度AWS Lambda阿里云函数计算腾讯云 SCF
运行时支持Node.js、Python、Java、Go、.NET、RubyNode.js、Python、Java、PHP、Go、.NETNode.js、Python、Java、PHP、Go、.NET
执行时间限制15分钟10分钟(可调整)15分钟
内存配置128MB - 10GB128MB - 3GB128MB - 3GB
冷启动时间100ms - 1s100ms - 500ms100ms - 500ms
触发器API Gateway、S3、DynamoDB、SQSAPI网关、OSS、Table Store、MNSAPI网关、COS、CMQ、定时触发器
定价按请求+执行时间按请求+执行时间按请求+执行时间
免费额度100万请求/月100万请求/月100万请求/月

2.3 选型建议

场景推荐平台理由
海外业务AWS Lambda全球覆盖、生态成熟
国内业务阿里云函数计算网络延迟低、合规性好
小程序后端腾讯云 SCF微信生态深度集成
边缘计算Cloudflare Workers全球边缘节点
多云部署Serverless Framework统一开发体验

三、AWS Lambda 实战

3.1 创建第一个 Lambda 函数

控制台创建步骤
  1. 登录 AWS 控制台,进入 Lambda 服务
  2. 点击「创建函数」
  3. 选择「从头开始创作」
  4. 配置:函数名称、运行时(Node.js 18.x)、架构(x86_64)、执行角色
  5. 编写代码并部署
// index.mjs
export const handler = async (event) => {
  console.log('Event:', JSON.stringify(event, null, 2));
  
  const response = {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      message: 'Hello from Lambda!',
      input: event,
    }),
  };
  
  return response;
};
CLI 创建示例
# 创建项目目录
mkdir my-lambda && cd my-lambda

# 创建函数代码
cat > index.mjs << 'EOF'
export const handler = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify({ message: 'Hello Lambda!' }),
  };
};
EOF

# 打包
zip function.zip index.mjs

# 创建函数
aws lambda create-function \
  --function-name hello-world \
  --runtime nodejs18.x \
  --role arn:aws:iam::YOUR_ACCOUNT_ID:role/lambda-role \
  --handler index.handler \
  --zip-file fileb://function.zip

3.2 配置 API Gateway 触发器

# 创建 REST API
API_ID=$(aws apigateway create-rest-api --name 'My API' --query 'id' --output text)

# 获取根资源 ID
ROOT_ID=$(aws apigateway get-resources --rest-api-id $API_ID --query 'items[0].id' --output text)

# 创建资源
RESOURCE_ID=$(aws apigateway create-resource \
  --rest-api-id $API_ID \
  --parent-id $ROOT_ID \
  --path-part 'hello' \
  --query 'id' --output text)

# 创建方法并集成 Lambda
aws apigateway put-method \
  --rest-api-id $API_ID \
  --resource-id $RESOURCE_ID \
  --http-method GET \
  --authorization-type NONE

aws apigateway put-integration \
  --rest-api-id $API_ID \
  --resource-id $RESOURCE_ID \
  --http-method GET \
  --type AWS_PROXY \
  --integration-http-method POST \
  --uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:ACCOUNT_ID:function:hello-world/invocations

# 部署 API
aws apigateway create-deployment --rest-api-id $API_ID --stage-name prod

3.3 Lambda 最佳实践

代码组织结构
my-lambda-project/
├── src/
│   ├── handlers/
│   │   ├── get-user.js
│   │   └── create-user.js
│   ├── services/
│   │   └── user-service.js
│   └── utils/
│       └── response.js
├── tests/
├── serverless.yml
└── package.json
响应格式化工具
// src/utils/response.js
export const success = (data, statusCode = 200) => ({
  statusCode,
  headers: {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
  },
  body: JSON.stringify({ success: true, data }),
});

export const error = (message, statusCode = 500, code = 'ERROR') => ({
  statusCode,
  headers: {
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': '*',
  },
  body: JSON.stringify({ success: false, error: { code, message } }),
});

四、阿里云函数计算实战

4.1 使用 fun 工具创建项目

# 安装 fun
npm install -g @alicloud/fun

# 初始化项目
fun init -n my-fc-project alinode

# 项目结构
my-fc-project/
├── template.yml        # 资源定义
├── index.js           # 函数代码
└── package.json

# 本地调试
fun local invoke my-function

# 部署
fun deploy

4.2 函数计算配置示例

# template.yml
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
  my-service:
    Type: 'Aliyun::Serverless::Service'
    Properties:
      Description: My FC Service
      
    my-api:
      Type: 'Aliyun::Serverless::Function'
      Properties:
        Handler: index.handler
        Runtime: nodejs16
        CodeUri: ./
        MemorySize: 512
        Timeout: 60
        EnvironmentVariables:
          NODE_ENV: production
      Events:
        httpTrigger:
          Type: HTTP
          Properties:
            AuthType: ANONYMOUS
            Methods:
              - GET
              - POST

4.3 常用触发器配置

# 定时触发器
Events:
  timerTrigger:
    Type: Timer
    Properties:
      CronExpression: '0 0 2 * * *'
      Enable: true

# OSS 触发器
Events:
  ossTrigger:
    Type: OSS
    Properties:
      BucketName: my-bucket
      Events:
        - oss:ObjectCreated:*
      Filter:
        Key:
          Prefix: images/
          Suffix: .jpg

# MNS 消息队列触发器
Events:
  mnsTrigger:
    Type: MNSTopic
    Properties:
      TopicName: my-topic
      Region: cn-hangzhou

五、Serverless Framework 统一开发

5.1 安装与初始化

# 安装
npm install -g serverless

# 创建项目
serverless create --template aws-nodejs --path my-project

# 项目结构
my-project/
├── serverless.yml    # 配置文件
├── handler.js        # 函数代码
└── package.json

5.2 完整配置示例

# serverless.yml
service: my-serverless-api

frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs18.x
  region: us-east-1
  stage: ${opt:stage, 'dev'}
  
  environment:
    NODE_ENV: ${self:provider.stage}
    DB_HOST: ${ssm:/my-app/db-host}
  
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:GetItem
            - dynamodb:PutItem
          Resource: arn:aws:dynamodb:${self:provider.region}:*:table/my-table

functions:
  getUser:
    handler: src/handlers/users/get.handler
    events:
      - httpApi:
          path: /users/{id}
          method: get
    memorySize: 256
    timeout: 10
  
  createUser:
    handler: src/handlers/users/create.handler
    events:
      - httpApi:
          path: /users
          method: post

  processImage:
    handler: src/handlers/images/process.handler
    events:
      - s3:
          bucket: my-images-bucket
          event: s3:ObjectCreated:*
    memorySize: 1024
    timeout: 60

plugins:
  - serverless-offline

resources:
  Resources:
    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: users
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH

5.3 常用命令

# 本地开发
serverless offline

# 部署
serverless deploy --stage prod

# 部署单个函数
serverless deploy function -f getUser

# 查看日志
serverless logs -f getUser --stage prod --tail

# 调用函数
serverless invoke -f getUser --data '{"id": "123"}'

# 删除服务
serverless remove --stage prod

六、生产级最佳实践

6.1 冷启动优化

冷启动原因:函数首次调用需要初始化运行环境、加载代码和依赖。

策略说明效果
使用 Provisioned Concurrency预留并发实例完全消除冷启动
减少依赖精简 node_modules减少加载时间
使用轻量运行时Node.js/Python 优于 Java加快初始化
代码优化延迟加载非必需模块加快启动
保持函数热度定时 ping 函数避免容器回收
// 优化前:启动时加载所有依赖
const axios = require('axios');
const moment = require('moment');

export const handler = async (event) => {
  const response = await axios.get('https://api.example.com');
  return response.data;
};

// 优化后:按需加载
export const handler = async (event) => {
  const axios = require('axios');  // 只加载需要的
  const response = await axios.get('https://api.example.com');
  return response.data;
};

6.2 数据库连接管理

// 正确做法:复用连接池
const { Pool } = require('pg');
let pool = null;

const getPool = () => {
  if (!pool) {
    pool = new Pool({
      host: process.env.DB_HOST,
      database: process.env.DB_NAME,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      max: 2,  // 函数环境连接数限制
    });
  }
  return pool;
};

export const handler = async (event) => {
  const pool = getPool();
  const result = await pool.query('SELECT * FROM users');
  return result.rows;
};

6.3 错误处理与重试

class AppError extends Error {
  constructor(message, statusCode, code) {
    super(message);
    this.statusCode = statusCode;
    this.code = code;
  }
}

export const handler = async (event) => {
  try {
    const { id } = event.pathParameters;
    
    if (!id) {
      throw new AppError('Missing user ID', 400, 'MISSING_ID');
    }
    
    const user = await getUserById(id);
    
    if (!user) {
      throw new AppError('User not found', 404, 'NOT_FOUND');
    }
    
    return success(user);
    
  } catch (err) {
    console.error('Error:', err);
    return error(err.message, err.statusCode || 500, err.code || 'ERROR');
  }
};

6.4 监控与日志

const logger = {
  info: (message, data = {}) => {
    console.log(JSON.stringify({
      timestamp: new Date().toISOString(),
      level: 'INFO',
      message,
      requestId: process.env.AWS_REQUEST_ID,
      ...data,
    }));
  },
  error: (message, data = {}) => {
    console.error(JSON.stringify({
      timestamp: new Date().toISOString(),
      level: 'ERROR',
      message,
      requestId: process.env.AWS_REQUEST_ID,
      ...data,
    }));
  },
};

export const handler = async (event) => {
  logger.info('Handler started');
  
  try {
    const result = await processEvent(event);
    logger.info('Handler completed', { resultCount: result.length });
    return result;
  } catch (error) {
    logger.error('Handler failed', { error: error.message });
    throw error;
  }
};

6.5 安全最佳实践

实践说明
最小权限原则只授予函数必要的 IAM 权限
环境变量加密敏感信息使用 SSM Parameter Store 或 Secrets Manager
VPC 配置访问私有资源时放入 VPC
输入验证验证所有外部输入,防止注入攻击
依赖更新定期更新依赖,修复安全漏洞

七、总结

本文系统介绍了 Serverless 架构的核心知识和实践方法:

核心要点回顾

  1. Serverless 是趋势:无需管理服务器、按需付费、自动弹性伸缩
  2. 平台选型:根据业务场景选择合适的云平台
  3. 开发实践:掌握函数编写、触发器配置、本地调试
  4. 性能优化:冷启动优化、连接复用、代码精简
  5. 生产保障:错误处理、监控日志、安全防护

最佳实践清单

  • 函数保持单一职责,代码简洁
  • 复用连接池,避免每次创建
  • 按需加载依赖,减少冷启动时间
  • 使用结构化日志,便于排查问题
  • 配置重试策略,处理临时故障
  • 监控函数执行时间和内存使用
  • 定期更新依赖,修复安全漏洞
  • 敏感信息使用加密存储

下一步学习方向

  • Serverless 数据库(DynamoDB、MongoDB Atlas)
  • 事件驱动架构设计
  • Step Functions 编排复杂工作流
  • Serverless 安全最佳实践

Serverless 让开发者专注于业务逻辑,而非基础设施。从小场景开始尝试,逐步积累经验。

如果这篇文章对你有帮助,欢迎点赞收藏!有问题欢迎评论区讨论。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐