🎬 HoRain 云小助手个人主页

⛺️生活的理想,就是为了理想的生活!


⛳️ 推荐

前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。

目录

⛳️ 推荐

核心架构模式:连接与逻辑分离

详细实现步骤(以 AWS 为例)

1. 组件选择

2. 工作流程与生命周期

优势与挑战

优势(为什么要在 Serverless 中使用 WebSocket?)

挑战与注意事项

简单代码示例(发送消息的 Lambda 函数)

总结


很好,这是一个非常贴合现代架构的问题。在 Serverless 架构中实现 WebSocket 长连接,其核心理念是 「连接与业务逻辑分离」

传统架构中,一个服务器实例会同时维护连接和处理业务。而 Serverless 模式下,连接管理由专门的托管服务(如 API Gateway)负责业务逻辑则由无状态、瞬态的 FaaS(如 Lambda)处理


核心架构模式:连接与逻辑分离

这个模式主要包含三个关键部分,其交互流程如下图所示:

flowchart TD
    Client[WebSocket 客户端] <--> |持久连接| AG[API Gateway<br>管理连接生命周期]
    AG --> |路由事件| Lambda[Lambda 函数<br>处理业务逻辑]
    Lambda --> |“postToConnection”| AG
    DynamoDB[(DynamoDB<br>存储ConnectionId)] <-.-> Lambda
  1. 托管连接层:专门负责维持与客户端的 WebSocket 长连接。

  2. 无状态计算层:负责处理所有业务逻辑。

  3. 持久化存储层:负责存储连接状态(ConnectionId)与应用程序状态。

下面我们以 AWS​ 的云服务为例,详细拆解其实现方案,其他云厂商(如 Azure、Google Cloud)原理类似。


详细实现步骤(以 AWS 为例)

1. 组件选择
  • WebSocket 连接管理Amazon API Gateway。它原生支持 WebSocket 协议,负责连接的建立、维持和断开,并将消息事件路由到后端。

  • 业务逻辑计算AWS Lambda。处理来自 API Gateway 的消息、业务逻辑,并负责向客户端发送消息。

  • 连接状态存储Amazon DynamoDB。一个快速、全托管的 NoSQL 数据库,用于存储每个连接的 connectionId及其上下文信息(如用户ID、房间号等)。

2. 工作流程与生命周期

API Gateway 将 WebSocket 的生命周期定义为几个特定的路由

  • $connect: 客户端建立连接时触发。

  • $disconnect: 客户端断开连接时触发。

  • $default: 当消息不匹配任何自定义路由时触发。

  • 自定义路由(如 sendmessage, joinroom): 根据消息内容路由到特定的处理逻辑。

完整流程如下:

  1. 连接建立

    • 客户端连接到 WebSocket URL(wss://your-api-id.execute-api.region.amazonaws.com/production)。

    • API Gateway 触发 $connect​ 路由,并调用绑定的 Lambda 函数。

    • Lambda 函数的工作: 将关键的连接信息(主要是 API Gateway 提供的唯一 connectionId)保存到 DynamoDB 表中。也可以在此进行身份验证(例如,验证连接 URL 中的查询字符串或 Header)。

  2. 消息处理

    • 客户端发送一条 JSON 消息:{ "action": "sendmessage", "data": "Hello" }

    • API Gateway 根据消息中的 action字段进行路由。例如,action: sendmessage会路由到名为 sendmessage的路由。

    • sendmessage路由触发对应的 Lambda 函数。

    • Lambda 函数的工作

      • 从 DynamoDB 中查询相关连接的 connectionId(例如,获取同一个聊天室的所有用户的 connectionId)。

      • 执行业务逻辑(如验证消息、保存到数据库等)。

      • 通过 API Gateway 管理 API​ 向一个或多个客户端发送消息。

  3. 向客户端发送消息(服务器推送)

    这是最关键的一步。Lambda 函数本身不保持连接,它如何发送消息?

    • Lambda 函数使用 AWS SDK 调用 ApiGatewayManagementApi.postToConnection()方法。

    • 该方法需要两个参数:

      • ConnectionId: 目标客户端的连接 ID。

      • Data: 要发送的消息内容。

    • API Gateway 服务接收到这个请求后,会找到对应的持久连接,将消息推送给客户端。

  4. 连接断开

    • 客户端断开连接。

    • API Gateway 触发 $disconnect​ 路由,调用绑定的 Lambda 函数。

    • Lambda 函数的工作: 从 DynamoDB 中删除该连接的 connectionId,并进行清理操作(如通知其他用户该用户已离线)。


优势与挑战

优势(为什么要在 Serverless 中使用 WebSocket?)
  1. 真正的弹性伸缩: API Gateway 和 Lambda 可以自动处理从零到数百万的并发连接,无需预置或管理服务器。你无需担心「我的服务器能撑住多少连接」。

  2. 成本效益: 传统架构需要始终运行服务器来维持连接,即使没有消息传递也在产生费用。Serverless WebSocket 的计费通常基于「连接分钟数」和「消息数量」,在空闲时段成本极低。

  3. 无运维负担: 无需打补丁、更新操作系统或管理集群。云服务商负责底层基础设施的可用性和可靠性。

挑战与注意事项
  1. 状态管理ConnectionId 是核心。你必须将其持久化到外部存储(如 DynamoDB)。存储设计至关重要(例如,如何根据房间号快速查询所有 ConnectionId)。

  2. 冷启动延迟: 当 Lambda 函数冷启动时,消息处理可能会产生几百毫秒的延迟。对于实时性要求极高的场景(如快节奏游戏),这可能是个问题。可以通过预置并发等方式缓解。

  3. API Gateway 限制: 注意服务本身的限制,例如每个连接的最长存活时间(目前是2小时),以及消息大小限制(32KB)。需要实现连接定期重连逻辑。

  4. 错误处理: 调用 postToConnection时,如果 connectionId已失效(客户端已断开但数据库未及时清理),会返回 410 状态码。Lambda 函数需要捕获这个错误并从数据库中删除无效的 connectionId


简单代码示例(发送消息的 Lambda 函数)

// 发送消息的 Lambda 函数(Node.js)
const AWS = require('aws-sdk');

// 注意:endpoint 需要是你的 WebSocket API 的地址
const apiGatewayManagementApi = new AWS.ApiGatewayManagementApi({
  endpoint: 'https://abc123.execute-api.us-east-1.amazonaws.com/production'
});
const dynamodb = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event) => {
  const connectionId = event.requestContext.connectionId;
  const body = JSON.parse(event.body);
  const message = body.data;

  // 1. 从 DynamoDB 获取需要接收此消息的所有 connectionId(例如,同房间用户)
  const roomUsers = await dynamodb.query({
    TableName: 'ConnectionsTable',
    IndexName: 'RoomIndex', // 假设有全局二级索引按房间号查询
    KeyConditionExpression: 'roomId = :roomId',
    ExpressionAttributeValues: { ':roomId': body.roomId }
  }).promise();

  // 2. 构建要发送的消息
  const postData = JSON.stringify({
    type: 'message',
    user: connectionId,
    data: message,
    timestamp: Date.now()
  });

  // 3. 向所有目标 connectionId 发送消息
  const postCalls = roomUsers.Items.map(async (item) => {
    try {
      await apiGatewayManagementApi.postToConnection({
        ConnectionId: item.connectionId,
        Data: postData
      }).promise();
    } catch (err) {
      if (err.statusCode === 410) { // 连接已断开
        console.log(`Found stale connection, deleting ${item.connectionId}`);
        await dynamodb.delete({
          TableName: 'ConnectionsTable',
          Key: { connectionId: item.connectionId }
        }).promise();
      } else {
        throw err;
      }
    }
  });

  // 等待所有发送请求完成
  await Promise.all(postCalls);

  return { statusCode: 200 };
};

总结

在 Serverless 架构中实现 WebSocket 的核心是:

  • 角色分离: 让 API Gateway 做它擅长的连接管理,让 Lambda 做它擅长的业务逻辑处理。

  • ConnectionId 是生命线: 通过 DynamoDB 等持久化存储来关联连接与用户/房间的关系。

  • 使用管理 API 进行推送: Lambda 函数通过调用云服务商提供的特定 API(如 postToConnection)来实现服务器推送。

这种架构完美体现了 Serverless 的优势,让你能以极低的运维成本和良好的可伸缩性,构建出功能强大的实时应用。

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐