我们公司把几个业务迁移到了Serverless架构,踩了不少坑,也积累了一些经验。

最开始,我们对Serverless充满了期待,觉得它能解决所有问题。但真正落地的时候,才发现有很多细节需要注意。本文分享Serverless的落地案例和配置详解,从基础到高级,包括函数计算配置、API网关配置、数据库配置、事件触发、冷启动优化、成本优化、监控告警,以及踩过的坑和经验总结。

一、什么是Serverless

1. 概念

Serverless,直译是"无服务器",但不是真的没有服务器,而是开发者不需要关心服务器。

开发者只需要写代码,部署到Serverless平台,平台会自动:

  • 分配资源
  • 弹性伸缩
  • 负载均衡
  • 容错恢复
  • 计费(按实际使用量计费)

Serverless的核心是:按需执行,按用计费,无需运维。

2. 核心组成

Serverless架构,主要由两部分组成:

  • FaaS(Function as a Service,函数即服务):把代码写成函数,按需执行
  • BaaS(Backend as a Service,后端即服务):数据库、存储、消息队列等托管服务

常见的Serverless平台:

  • 阿里云函数计算
  • 腾讯云SCF
  • AWS Lambda
  • 华为云FunctionGraph
  • 开源:OpenFaaS、Knative

3. 优势

Serverless的优势:

  • 无需运维:不用管服务器,专注写代码
  • 弹性伸缩:自动扩缩容,应对流量波动
  • 成本低:按实际使用量计费,不用不花钱
  • 开发快:专注业务逻辑,快速上线
  • 高可用:平台保证可用性和容错

二、落地案例一:图片处理服务

1. 业务背景

我们有一个图片处理服务,用户上传图片后,需要生成不同尺寸的缩略图。

传统架构:

  • 买几台服务器,部署图片处理服务
  • 高峰期不够用,低峰期浪费
  • 需要自己运维

Serverless架构:

  • 用户上传图片到对象存储
  • 对象存储触发函数
  • 函数处理图片,生成缩略图
  • 处理完,函数自动释放

2. 配置详解

函数配置:

functions:
  image-resize:
    handler: index.handler
    runtime: nodejs14
    memory: 512MB
    timeout: 30s
    environment:
      OUTPUT_BUCKET: my-output-bucket
    triggers:
      - type: oss
        bucket: my-input-bucket
        events:
          - oss:ObjectCreated:*

关键配置说明:

  • memory: 512MB:图片处理需要一定内存,512MB比较合适。内存越大,CPU越强,但费用越高。
  • timeout: 30s:图片处理一般几秒到几十秒,30秒够用。
  • oss trigger:对象存储触发,上传图片自动执行函数。

3. 代码示例

const sharp = require('sharp');
const OSS = require('ali-oss');

const client = new OSS({
  region: 'oss-cn-hangzhou',
  accessKeyId: process.env.ACCESS_KEY_ID,
  accessKeySecret: process.env.ACCESS_KEY_SECRET,
  bucket: process.env.OUTPUT_BUCKET
});

exports.handler = async (event, context) => {
  const evt = JSON.parse(event);
  const objectName = evt.events[0].oss.object.key;
  
  // 下载原图
  const result = await client.get(objectName);
  
  // 生成缩略图
  const sizes = [100, 200, 400, 800];
  for (const size of sizes) {
    const buffer = await sharp(result.content)
      .resize(size)
      .toFormat('webp')
      .toBuffer();
    
    await client.put(`thumb/${size}/${objectName}`, buffer);
  }
  
  return 'success';
};

4. 踩过的坑

  • 冷启动:第一次调用,函数需要初始化,延迟高。解决:预留实例,或者用 provisioned concurrency。
  • 依赖包大:sharp依赖很大,部署包超过限制。解决:用层(Layer)管理依赖,或者用容器镜像。
  • 超时:大图片处理时间长,超时。解决:增加超时时间,或者异步处理。
  • 并发限制:大量图片同时上传,并发不够。解决:申请提高并发限制,或者用消息队列削峰。

三、落地案例二:API后端服务

1. 业务背景

我们有一个小程序的后端API,用Serverless架构。

传统架构:

  • 买服务器,部署Node.js服务
  • 需要自己做负载均衡、弹性伸缩
  • 流量小的时候浪费,流量大的时候不够用

Serverless架构:

  • API网关接收请求
  • 转发到函数
  • 函数处理业务逻辑
  • 返回结果

2. 配置详解

函数配置:

functions:
  api:
    handler: app.handler
    runtime: nodejs14
    memory: 256MB
    timeout: 10s
    environment:
      DB_HOST: process.env.DB_HOST
      DB_USER: process.env.DB_USER
      DB_PASSWORD: process.env.DB_PASSWORD
    triggers:
      - type: api_gateway
        methods:
          - GET
          - POST
        paths:
          - /api/*

API网关配置:

  • 协议:HTTP/HTTPS
  • 认证方式:API Key 或 JWT
  • 限流:每秒100次
  • 超时:10秒
  • 跨域:配置CORS

3. 代码示例(Express适配)

const express = require('express');
const serverless = require('serverless-http');
const mysql = require('mysql2/promise');

const app = express();
app.use(express.json());

let pool;
async function getPool() {
  if (!pool) {
    pool = mysql.createPool({
      host: process.env.DB_HOST,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      database: 'mydb',
      connectionLimit: 10
    });
  }
  return pool;
}

app.get('/api/users', async (req, res) => {
  const pool = await getPool();
  const [rows] = await pool.query('SELECT * FROM users LIMIT 100');
  res.json(rows);
});

app.post('/api/users', async (req, res) => {
  const pool = await getPool();
  const { name, email } = req.body;
  const [result] = await pool.query(
    'INSERT INTO users (name, email) VALUES (?, ?)',
    [name, email]
  );
  res.json({ id: result.insertId });
});

module.exports.handler = serverless(app);

4. 踩过的坑

  • 数据库连接:函数每次执行都新建连接,数据库连接数爆炸。解决:用连接池,在函数外初始化,复用连接。
  • 冷启动:API请求延迟高。解决:预留实例,或者用单函数多路由,减少函数数量。
  • 状态管理:函数是无状态的,不能用内存存状态。解决:用Redis或数据库存状态。
  • 文件上传:API网关有请求大小限制。解决:大文件用对象存储,API只传URL。

四、落地案例三:定时任务

1. 业务背景

我们有一些定时任务,比如:

  • 每天凌晨统计数据
  • 每小时同步数据
  • 每周生成报表

传统架构:

  • 买一台服务器,跑crontab
  • 服务器不能关,浪费资源
  • 需要自己保证任务执行成功

Serverless架构:

  • 用定时触发器
  • 到时间自动执行函数
  • 执行完自动释放
  • 平台保证执行成功

2. 配置详解

functions:
  daily-report:
    handler: report.handler
    runtime: python3
    memory: 1024MB
    timeout: 300s
    triggers:
      - type: timer
        name: daily-report
        cron: '0 0 2 * * *'  # 每天凌晨2点
        payload: '{"type": "daily"}'
  
  hourly-sync:
    handler: sync.handler
    runtime: nodejs14
    memory: 256MB
    timeout: 60s
    triggers:
      - type: timer
        name: hourly-sync
        cron: '0 0 * * * *'  # 每小时

3. 踩过的坑

  • 超时:大数据量统计,超时。解决:增加超时时间,或者分批处理。
  • 失败重试:任务失败了,需要重试。解决:配置重试次数,或者自己实现重试逻辑。
  • 幂等性:任务重复执行,数据重复。解决:保证任务幂等,用唯一标识去重。
  • 依赖外部服务:外部服务挂了,任务失败。解决:加容错,失败告警,手动重试。

五、高级配置

1. 冷启动优化

冷启动是Serverless最大的痛点。优化方法:

  • 预留实例:提前初始化一些实例,随时待命。费用高,但延迟低。
  • 单函数多路由:把多个API放到一个函数里,减少函数数量,提高复用率。
  • 减少部署包大小:只打包必要的依赖,用层(Layer)管理公共依赖。
  • 初始化优化:把重的初始化逻辑放到函数外,只执行一次。
  • 运行时选择:选择启动快的运行时,如Node.js、Python,避免Java、.NET。
  • 容器镜像:用容器镜像部署,启动更快,环境更一致。

2. 成本优化

Serverless按用计费,但用不好也会很贵。

  • 内存优化:内存越大,CPU越强,费用越高。找到性能和成本的平衡点。
  • 超时优化:超时时间不要设太大,避免异常函数占用资源。
  • 并发控制:限制最大并发,避免异常流量导致费用暴增。
  • 日志优化:日志量大了也收费,合理设置日志级别。
  • 监控费用:设置费用告警,避免意外扣费。

3. 数据库连接优化

Serverless函数和数据库配合,最容易出问题的是连接数。

  • 连接池:在函数外初始化连接池,复用连接。
  • 数据库代理:用数据库代理(如RDS Proxy),管理连接池。
  • Serverless数据库:用支持Serverless的数据库,如Aurora Serverless。
  • 缓存:用Redis缓存,减少数据库查询。
  • 批量操作:减少数据库请求次数,批量操作。

4. 监控告警

Serverless的监控很重要。

  • 调用次数:监控函数调用次数,异常增长要告警
  • 执行时间:监控函数执行时间,超时要告警
  • 错误率:监控错误率,超过阈值要告警
  • 冷启动次数:监控冷启动,优化延迟
  • 费用:监控费用,异常扣费要告警
  • 并发:监控并发,接近上限要告警

六、Serverless的适用场景

Serverless不是银弹,不是所有场景都适合。

适合的场景:

  • 事件驱动:图片处理、文件处理、消息处理
  • API后端:小程序、移动端后端
  • 定时任务:数据统计、报表生成
  • 流量波动大:活动页、促销系统
  • 低频请求:管理后台、内部工具
  • 原型验证:快速验证想法

不适合的场景:

  • 长连接:WebSocket、长轮询
  • 高性能计算:需要持续高性能
  • 大文件处理:超过超时限制
  • 状态ful应用:需要持续保持状态
  • 对延迟极度敏感:冷启动延迟不可接受
  • 高并发稳态:一直高并发,Serverless反而贵

七、经验总结

1. 从小处着手

不要一上来就把所有业务都迁到Serverless。

  • 先选一个合适的场景,做试点
  • 试点成功了,再推广
  • 积累经验,再迁移核心业务

2. 做好监控

Serverless的监控,比传统架构更重要。

  • 因为你看不到服务器,只能靠监控
  • 出了问题,要靠监控定位
  • 费用异常,要靠监控发现

从第一天开始,就要做好监控和告警。

3. 注意冷启动

冷启动是Serverless的通病,要提前考虑。

  • 对延迟敏感的业务,用预留实例
  • 尽量减少函数数量,提高复用率
  • 优化初始化逻辑,减少冷启动时间

4. 数据库是关键

Serverless和数据库配合,最容易出问题。

  • 连接数管理
  • 连接池复用
  • 数据库代理
  • 缓存

数据库设计好了,Serverless才能用好。

5. 成本要监控

Serverless按用计费,用不好会很贵。

  • 设置费用告警
  • 定期分析费用
  • 优化内存和超时
  • 控制并发

不要等账单出来了才发现超支了。

6. 不要盲目跟风

Serverless很火,但不是所有业务都适合。

  • 先评估业务场景
  • 做技术验证
  • 算清楚成本
  • 再决定要不要用

不要为了用Serverless而用Serverless。

八、写在最后

Serverless,是云计算的一个重要方向。它让开发者不用关心服务器,专注业务逻辑,快速上线。

但它也不是银弹,有自己的适用场景和局限性。冷启动、数据库连接、成本控制、监控告警,这些都是落地时需要注意的问题。

我们公司用Serverless做了图片处理、API后端、定时任务等几个业务,整体效果不错。运维少了,弹性好了,成本也降了。但也踩了不少坑,花了不少时间优化。

2022年了,Serverless越来越成熟,工具越来越多,门槛越来越低。如果你有合适的业务场景,可以试试Serverless,它可能会给你带来惊喜。

最后,用一句话总结:"Serverless,不是银弹,但在合适的场景下,能让你事半功倍。从小处着手,做好监控,注意冷启动和数据库,控制成本,不要盲目跟风。"

愿你的Serverless落地,少踩坑,多顺利。