5月27日 15:43

Serverless 架构下如何管理状态?

Serverless 架构的核心特征是函数无状态——每次调用可能由不同实例执行,上一次的内存数据在下一次调用时完全不可见。这让状态管理成为 Serverless 应用设计中必须直面的问题。

为什么状态管理是 Serverless 的核心难题

传统服务器可以依赖进程内存、本地文件系统维持状态,但 Serverless 函数的运行环境随时可能被回收。具体来说有三个关键限制:

  • 实例不固定:两次调用大概率落在不同容器上,进程内变量无法复用
  • 生命周期短暂:函数执行时间受限(如 AWS Lambda 默认最长 15 分钟),冷启动后实例可能随时销毁
  • 并发不可控:同一函数可能同时运行数十个实例,本地状态无法在实例间共享

因此,任何需要跨调用持久化的数据,必须借助外部服务。

方案一:外部存储服务

这是最直接的思路——把状态从函数内部搬到托管存储。

数据库方案

关系型数据库(PostgreSQL、MySQL)适合结构化、强一致性的业务状态,如用户资料、订单记录。NoSQL 数据库(DynamoDB、MongoDB)则更适配高吞吐、灵活 Schema 的场景。

以 DynamoDB 为例,一条记录即可表示一个用户会话状态:

json
{ "userId": "u-1001", "sessionId": "s-abc123", "loginAt": "2026-05-27T10:00:00Z", "cartItems": ["item-A", "item-B"] }

DynamoDB 按读写计费,与 Serverless 按需付费模型天然匹配,且单表即可支撑百万级 QPS。

缓存方案

Redis 或 Memcached 适合高频读写、对延迟敏感的临时状态,如验证码、限流计数器、排行榜。需要注意缓存数据的过期策略,避免状态残留。

对象存储方案

S3、Azure Blob Storage 适合大文件和冷数据,如用户上传的图片、生成的报表文件。访问延迟较高,不适合热数据。

选择建议:热数据用 Redis,结构化数据用 DynamoDB/PostgreSQL,大文件用 S3,按数据特性分层存储。

方案二:会话管理

Web 应用中用户会话是最典型的跨调用状态,处理方式有三种路径:

JWT 无状态会话

将用户身份和权限信息编码在 Token 里,函数无需查库即可验证身份:

javascript
// Lambda 函数中验证 JWT const jwt = require("jsonwebtoken"); exports.handler = async (event) => { const token = event.headers.Authorization?.replace("Bearer ", ""); try { const payload = jwt.verify(token, process.env.JWT_SECRET); return { statusCode: 200, body: JSON.stringify({ userId: payload.sub }) }; } catch { return { statusCode: 401, body: "Unauthorized" }; } };

优点是完全无状态,水平扩展无障碍。缺点是 Token 一旦签发无法主动撤销,敏感操作仍需配合黑名单机制。

外部会话存储

将会话数据存入 Redis,以 sessionId 为键:

shell
SET session:abc123 '{"userId":"u-1001","role":"admin"}' EX 3600

每次请求先查 Redis 获取会话状态。这种方式支持主动过期和撤销,但引入了外部依赖。

Cookie 存储

将少量非敏感状态编码在客户端 Cookie 中,适合主题偏好、语言设置等场景。绝不要在 Cookie 中存放敏感信息。

方案三:工作流编排

当业务涉及多个步骤和长时间运行的任务,单纯靠函数链式调用会难以追踪状态。

Step Functions 状态机

AWS Step Functions 用声明式 JSON 定义状态流转:

json
{ "Comment": "订单处理流程", "StartAt": "ValidateOrder", "States": { "ValidateOrder": { "Type": "Task", "Resource": "arn:aws:lambda:...:validate", "Next": "ChargePayment" }, "ChargePayment": { "Type": "Task", "Resource": "arn:aws:lambda:...:charge", "Catch": [{ "ErrorEquals": ["PaymentFailed"], "Next": "Refund" }], "Next": "ShipOrder" }, "ShipOrder": { "Type": "Task", "Resource": "arn:aws:lambda:...:ship", "End": true }, "Refund": { "Type": "Task", "Resource": "arn:aws:lambda:...:refund", "End": true } } }

Step Functions 自动记录每一步的输入输出和执行状态,支持错误重试和补偿回滚,非常适合订单处理、数据管道等场景。

事件驱动方案

通过 EventBridge、SQS、Kafka 等消息中间件,以事件而非直接调用的方式在函数间传递状态:

  • 函数 A 完成后发布事件到 EventBridge
  • 函数 B 订阅事件并继续处理
  • 状态随事件体传递,不依赖共享存储

这种方式解耦性最好,但调试和链路追踪的复杂度较高。

方案四:临时与本地缓存

这些方案不适合持久化,但可以优化性能。

  • /tmp 目录:Lambda 提供 512MB–10GB 的临时空间,同一实例的多次调用可复用。但实例回收后数据丢失,不能当持久存储用
  • 进程内存缓存:全局变量在实例存活期间有效,适合缓存配置信息或数据库连接。注意这只能减少冷启动开销,不能保证数据在调用间持久
python
# Lambda 进程内存缓存示例(Python) import json import urllib.request _config = None # 实例级缓存 def get_config(): global _config if _config is None: # 首次调用时加载,后续调用复用 resp = urllib.request.urlopen("https://config-service/app-config") _config = json.loads(resp.read()) return _config def handler(event, context): config = get_config() return {"statusCode": 200, "body": json.dumps(config)}

如何选择合适的状态管理方案

根据场景选择,而非追求统一方案:

场景推荐方案理由
用户认证JWT + Redis 黑名单无状态验证,撤销时有兜底
购物车DynamoDB / Redis高频读写,数据量小
多步骤业务流程Step Functions内置状态追踪和错误恢复
文件上传处理S3 事件触发文件天然适合对象存储
配置信息缓存进程内存访问频率高,变更频率低
实时数据统计Redis + 定期落库内存计算快,持久化保安全

实践中的三个关键原则

第一,优先设计无状态函数。 函数只做计算,状态全部外置。这样函数可以随时被回收和重建,天然适配自动扩缩容。

第二,保证幂等性。 网络重试、事件重复投递在 Serverless 环境中很常见,函数必须对同一输入多次执行产生相同结果。常用手段是请求去重键(如订单号+操作类型)和条件写入(如 DynamoDB 的 ConditionExpression)。

第三,区分状态的生命周期。 临时状态用缓存,业务状态用数据库,流程状态用状态机,文件状态用对象存储。不要用 Redis 存长期业务数据,也不要用数据库做高频临时缓存。

掌握这些方案和选型逻辑,就能在面试中清晰回答 Serverless 状态管理的核心思路和落地策略。

标签:Serverless