内容提要
本文介绍如何利用 Amazon DynamoDB 的租约机制构建弹性实时 WebSocket 工作节点集群:通过条件写入实现分布式锁,保证每个连接仅由一个工作节点持有;借助心跳续约检测故障,租约过期后由对账循环自动接管,实现秒级故障转移;支持优雅关闭以实现低停机部署,并利用 CloudWatch 指标驱动自动扩缩容。
延伸解读
租约机制的核心:条件写入与时钟同步
文章使用 DynamoDB 条件写入实现分布式锁,确保每个 WebSocket 连接仅由一个工作节点持有。租约过期判断依赖工作节点本地时钟,而非 DynamoDB 服务端时间,因此所有节点必须保持时钟同步。在 AWS Fargate 上,Amazon Time Sync Service 可将时钟偏差控制在几毫秒内,远小于默认 20 秒租约和 5 秒心跳的安全边际。若部署在 AWS 之外,需配置 NTP 并监控时钟漂移,或根据最大偏差增加租约时长,避免误判过期。
故障恢复时间与优雅关闭的差异
当工作节点意外终止时,租约在 20 秒后自然过期,对账循环默认每 60 秒运行一次,因此最坏恢复时间约为 80 秒。而优雅关闭时,节点主动将租约过期时间设为 0,其他节点在下一次对账周期即可接管,无需等待租约自然到期。两者最终都由健康节点重新获取连接,但优雅关闭显著更快,适合滚动部署和缩容场景。
成本主要来自心跳写入,可调优降低
架构中最大的成本驱动是心跳写入:每个活跃连接每 5 秒产生一次 update_item,消耗 1 WCU。文章给出估算:100 个连接按需模式月成本约 65 美元,2000 个连接约 1300 美元。若连接数持续较高,建议使用预置容量加 Auto Scaling。降低成本的调整包括:将心跳间隔从 5 秒延长至 10 秒并同步延长租约,可减半 WCU;延长对账间隔可减少 RCU;用 BatchGetItem 替代逐连接读取状态,也能显著降低读取消耗。
适用场景与扩展方向
该模式适用于需要管理大量长连接且要求高可用的系统,如实时转录、IoT 数据摄取、金融行情推送和直播流。文章建议在生产环境中用结构化日志和 CloudWatch 指标替代 print,并监控租约获取失败和重连事件。进一步可引入 AWS X-Ray 实现分布式追踪,并添加上游重放或基于偏移量的断点续传逻辑,以处理节点故障到恢复之间的数据缺口。
Q&A
如何用 DynamoDB 租约保证每个 WebSocket 连接只被一个工作节点持有?
通过 DynamoDB 的条件写入实现分布式锁。工作节点在获取租约时,使用 update_item 并设置 ConditionExpression 为 attribute_not_exists(lease_expires_at_ms) OR lease_expires_at_ms < :now,确保只有第一个写入的节点成功,其他节点收到 ConditionalCheckFailedException 后回退。
工作节点意外崩溃后,系统如何自动恢复连接?
崩溃的节点无法续约,租约在 LEASE_SECONDS(默认20秒)后过期。其他健康节点通过定期运行的对账循环(默认每60秒)查询 GSI 中 desired_state=STARTED 且 lease_expires_at_ms < now 的记录,发现孤儿连接后尝试重新获取租约并接管连接。最坏恢复时间约80秒。
优雅关闭如何减少部署期间的停机时间?
收到 SIGTERM 后,工作节点设置 shutdown_event 通知所有循环退出,然后并行关闭所有 WebSocket 连接并释放租约(将 lease_expires_at_ms 设为0)。其他节点在对账循环中立即发现这些已释放的租约并接管连接,无需等待租约自然过期,从而将连接恢复时间从分钟级缩短到秒级。
租约机制中如何检测工作节点是否仍然存活?
通过心跳续约机制。工作节点每隔 HEARTBEAT_EVERY 秒(默认5秒)调用 renew_lease 更新 lease_expires_at_ms,条件为 lease_owner = :w。如果续约失败(返回 False),说明租约已被其他节点获取,当前节点立即停止连接管理。
如何根据连接数自动扩缩容工作节点集群?
每个工作节点每30秒向 CloudWatch 发布自定义指标 ActiveConnections。Application Auto Scaling 使用目标跟踪策略,根据所有节点的平均 ActiveConnections 调整 ECS 任务数量。当平均值超过目标(如700)时扩容,缩容时通过优雅关闭释放租约,其他节点接管连接。
使用 DynamoDB 租约方案的主要成本是什么?如何优化?
主要成本来自心跳写入,每个活跃连接每次心跳消耗1 WCU。优化方法包括:增加心跳间隔(如从5秒到10秒)并相应增加租约时长;增加对账间隔(如从60秒到120秒);使用 BatchGetItem 批量检查 desired_state;对账查询使用最终一致性读取。对于高连接数场景,建议使用预置容量加自动扩缩容。
为什么不能只用 SQS 来协调 WebSocket 连接的所有权?
SQS 设计用于一次性任务分发,无法持续跟踪连接所有权、查询无主连接或存储连接状态(如 desired_state、ws_url、last_seq)。WebSocket 连接所有权是持续状态,需要租约续约和故障转移,因此必须使用 DynamoDB 作为持久化协调层,SQS 仅作为快速通知通道。
租约机制对时钟同步有什么要求?
租约过期判断依赖工作节点本地时钟生成的 epoch 毫秒时间戳,DynamoDB 使用调用方提供的 now 值。因此所有节点必须时钟同步。在 AWS Fargate 上,Amazon Time Sync Service 保证偏差在几毫秒内,远小于默认20秒租约和5秒心跳的安全边际。若在非 AWS 环境部署,需配置 NTP 并监控时钟漂移,或根据最大偏差增加租约时长。