如何将 WebSockets 集成到您的堆栈中
您可以通过为客户端设置专用的 WebSocket 服务器来连接和接收更新来交付事件驱动的架构。然而,这种架构有几个缺点,包括需要管理和扩展服务器以及从该服务器向可能分布在世界各地的客户端发送更新所涉及的固有延迟。
要在全球范围内扩展您的硬件,您可以通过从云服务提供商处租用来跳过责任和拥有成本。例如,您可以使用 Amazon ECS 等托管云服务,也可以选择无服务器 WebSocket 解决方案。
“无服务器应用程序在没有人使用时无需花费任何运行成本,不包括数据存储成本。 ” 云 2.0:代码不再为王 - 无服务器已经废除了它
节省运营成本是考虑无服务器框架的一个很好的理由:您可以获得规模和弹性的好处,但您不必配置或管理服务器。
本文介绍了如何构建一个基本的无服务器 WebSockets 平台,非常适合聊天等简单应用程序,使用 AWS API Gateway 创建 WebSocket 端点和 AWS Lambda 用于连接管理和后端业务逻辑。
无服务器 WebSockets 平台的基本架构
构建一个简单的无服务器 WebSockets 平台相对简单,使用 AWS API Gateway 构建 WebSockets API 和用于后端处理的 Lambda 函数,将消息元数据存储在 Amazon DynamoDB 中。
AWS API 网关
AWS API Gateway 是一项用于创建和管理 API 的服务。您可以将其用作有状态代理来终止持久的 WebSocket 连接并在您的客户端和基于 HTTP 的后端(例如 AWS Lambda、Amazon Kinesis 或任何其他 HTTP 端点)之间传输数据。
AWS 拉姆达
AWS Lambda 是一种无服务器计算服务,让您无需预置或管理服务器即可运行代码。
例如,WebSocket API 可以调用 Lambda 函数来传递消息负载以供进一步处理,然后将结果返回给客户端。Lambda 函数仅在处理任何单个任务所需的时间内运行。对于客户端授权,连接请求必须包含凭据以供Lambda 授权方函数验证,该函数返回适当的 AWS IAM 访问权限策略。
示例:由无服务器 WebSockets 提供支持的聊天应用程序
按照惯例,让我们看一个示例聊天应用程序来说明无服务器 WebSockets 解决方案如何为基本的实时应用程序提供支持。首先,考虑建立和存储客户端 WebSocket 连接的顺序。
示例:由无服务器 WebSockets 提供支持的聊天应用程序
客户端设备建立到 AWS API Gateway 的单个 WebSocket 连接。每个活动的 WebSocket 连接在 API 网关中都有一个单独的回调 URL,用于将消息推送回相应的客户端。
连接消息被发送到 API Gateway Authorizer Lambda 函数,以确认设备具有正确的凭据来连接到服务。
连接消息和元数据在经过身份验证后会发送到单独的 AWS Lambda。
Lambda 处理程序检查元数据并将适当的信息存储在 AWS DynamoDB(一种 NoSQL 文档数据库)中。
在聊天应用程序中,用户可以参与涉及一个或多个参与者的大量对话。这是发布和订阅模式的典型用例。当用户向特定对话或频道发送消息时,聊天应用程序会将消息发布给订阅该频道的所有其他用户。该频道的每个订阅者都会在他们的应用程序中收到消息。
当聊天应用程序启动到 AWS API Gateway 的 WebSocket 连接时,需要有一种方法来确定该连接有权发布和订阅的通道。客户端代码必须在连接元数据中发送此信息。
现在让我们考虑用户发送消息和另一个用户接收消息的时间点:
用户发送一条消息,另一个用户接收到它
客户端发送聊天消息(包括元数据)以标识要在其上发布的特定频道。
收到后,API 网关将其发送到适当的 Lambda 函数。
调用时,Lambda 检查订阅该通道的连接,并向 API 网关发送每个订阅者的 ID 和消息负载。
API 网关使用每个订阅者连接的回调 URL 将有效负载发送到已建立的 WebSocket 连接上的客户端。
断开连接后,将调用 Lambda 函数来清理数据存储。
基本无服务器 WebSocket 的局限性
以上是使用无服务器 WebSockets 的实时事件驱动应用程序的简单示例。一些限制包括:
连接状态跟踪不扩展:AWS API Gateway 不跟踪连接元数据,这就是为什么需要使用 Lambda 函数将其存储在Amazon DynamoDB等数据库中,以便为每次连接/断开更新存储。对于大量连接的客户端,您会达到 Lambda 扩展限制。您可以改为使用 AWS Integration to API Gateway 直接调用 DynamoDB,但您需要注意相关的高级别数据库使用情况。
我们尚未解决的另一个方面是突然断开连接。如果客户端连接在没有警告的情况下断开,则活动 WebSocket 连接未正确清理。数据库可能会存储不再存在的连接标识符,这可能会导致效率低下。
避免僵尸连接的一种方法是通过使用心跳机制(例如 TCP keepalive 或ping/pong 控制帧)定期检查连接是否“活动” 。发送心跳会对系统的可扩展性和可靠性产生负面影响,尤其是在处理数百万个并发 WebSocket 连接时,因为它会给系统带来额外的负载。
您不能向所有连接的客户端广播消息:在这个简单的设计中,没有办法同时向多个连接发送消息,正如您对标准发布/订阅频道或主题所期望的那样。要通过将更新发布到数千个客户端来分散更新,您需要从数据库中获取连接 ID,然后对每个连接 ID 进行 API 调用,这不是一种可扩展的方法。您也可以考虑使用 Amazon SNS 将发布/订阅添加到您的应用程序,但仍然存在局限性和额外复杂性的缺点。
对于聊天应用程序示例,您可以假设典型频道的订阅者数量很少,除非它们用于与数千名用户的交互式实时流会话。其他可以容忍缺少广播的场景包括单独的更新通知和逐项工作的交互功能。如果频道上有大量订阅者接收最新的体育比分或新闻更新,则实时数据扇出用例无法支持此限制。
每秒允许的 WebSocket 连接数有限制:AWS API Gateway 设置每个区域限制为每个账户每秒 500 个新连接和 10,000 个并发连接。如果您计划扩展您的应用程序,这可能是不够的。
该解决方案绑定到单个 AWS 区域:AWS API Gateway 提供的 WebSocket 连接绑定到单个区域,由于地理位置远离该区域的客户端延迟,导致性能不佳。可以使用无服务器事件总线Amazon EventBridge进行跨区域事件路由以复制事件。
构建生产就绪的无服务器 WebSockets 平台的挑战
如果用于构建简单的实时消息传递平台,上述架构具有潜力,尽管我们已经看到它在规模上存在局限性。如果您需要一个生产就绪的系统,您将需要规模,但还需要考虑您的解决方案在性能、消息传递的完整性、容错和回退支持方面的可靠性,所有这些都可能很复杂且时间紧迫耗费解决。
表现
在分布式系统中,延迟会随着传播的距离而恶化,因此数据应通过托管数据中心和边缘加速点尽可能靠近用户。然而,仅仅最小化延迟是不够的。用户体验需要延迟差异最小,以确保可预测性。
消息完整性
当用户的连接断开并重新连接时,应用程序需要在断开连接之前以最小的阻力恢复连接。任何错过的消息都需要在不重复已处理的消息的情况下进行传递。整个体验需要完全无缝。
容错性和可扩展性
对服务的需求可能无法预测,并且规划和提供足够的容量具有挑战性。实时解决方案必须始终高度可用,以支持数据激增。面临的挑战是横向扩展并迅速吸收数百万个连接,而无需预先配置。
您的解决方案还必须具有容错能力,即使组件出现故障也能继续运行。如果某些组件丢失,则需要有多个组件能够维护系统。应该没有单点拥塞,没有单点故障。
回退传输
尽管有广泛的平台支持,但并非所有代理或浏览器都支持 WebSocket 协议,一些公司防火墙甚至会阻止特定端口,这会影响 WebSocket 访问。您可以考虑支持后备传输,例如 XHR 流、XHR 轮询或长轮询。
特征蠕变
在生产就绪系统中使用原始无服务器 WebSockets 的另一个挑战可能只有在产品的第一次迭代中成功使用它之后才会出现。虽然您已经添加了接收实时更新的基本功能,但功能蔓延通常意味着这些功能会引发对共享实时体验和协作功能的额外要求。
构建和维护专有的 WebSocket 解决方案以支持产品未来的实时需求可能具有挑战性。支撑系统的基础设施必须稳定可靠,需要经验丰富的工程师来构建和维护。开发团队可能会发现他们长期致力于支持实时解决方案,而不是专注于增强核心产品的功能。