Modal 推出 Sticky Sessions:同一会话请求始终路由到同一容器
Serverless routing made sticky
Modal 发布 Sticky Sessions,基于 Modal Servers 让同一会话的所有请求固定路由到同一容器,用 @modal.sessioned() 装饰器配合 SDK 或 HTTP API 即可创建。
原文讲清了 Sticky Sessions 的路由、授权与弹性伸缩设计,以及语音智能体、共享房间和游戏等适用场景。
在 Modal,我们的无服务器计算为实时推理、训练和批处理任务提供支持,并随需求自动扩缩容。Sandboxes 提供了另一种模式,即由你显式控制其生命周期的容器。越来越多的负载需要介于两者之间的方案:在请求之间保持连续性的自动扩缩容无服务器计算。试想一个带有解码器状态、用于音频流的语音转文字模型,一个进行对话的语音代理,或者一个多人游戏。对于这些负载,用户在整个会话期间每次重连时都必须回到同一个容器。
我们最近推出了Sticky Sessions,它构建在我们低延迟的Modal Servers 之上。每个会话的请求都会落到同一个容器上。要使用 Sticky Sessions,只需用 @modal.sessioned() 装饰器创建一个 Modal Server,并通过 SDK 或 HTTP API 启动会话:
具有强亲和性的无服务器计算
Modal 支持通过 Web Functions 使用 WebSockets 已有一段时间。然而,WebSocket 连接绑定的是 Function 的“输入”,而不是容器——如果连接断开,重连时会创建一个新的输入,并可能落到不同的容器上。
诸如 Gradio 和 Streamlit 这类有状态的 Web UI 属于需要在请求与后端容器之间提供强亲和性保证的负载。每个请求都必须到达持有用户状态的进程。一种反模式的做法是设置 max_containers=1,它通过完全放弃扩缩容来保证粘性。
Modal Servers 已经提供了一种较弱的亲和性形式;共享相同 Modal-Affinity-Routing-Key 请求头的请求会以尽力而为的方式路由到同一个容器。该容器仍可能随时被缩容或替换,从而将后续请求路由到不同的容器。
Sticky Sessions 通过确保同一会话中的所有请求都落到同一个容器上,提供了更强的路由保证。
Modal Servers 为我们奠定了基础
Sticky Sessions 构建在我们为 Modal Servers 开发的基础设施之上。该基础设施的核心是我们自研的无状态代理(fprs)。该代理使用从持久存储协调而来的内存缓存路由状态,提供低延迟的路由和负载均衡。Servers 已经为我们提供了一种将 HTTP 流量路由到容器、管理容器生命周期以及根据需求扩缩容容器集群的方式。
Sticky Sessions 需要三项当前请求级路由无法提供的能力:
- 固定路由:会话中的每个请求都到达同一个容器。
- 会话生命周期:容器的主机跟踪哪些请求属于哪些会话,并确定每个会话何时变为空闲。
- 基于会话的自动扩缩容:容器集群根据活跃会话数而非请求数进行扩缩容。
让无服务器基础设施具备会话感知能力
从客户端的角度来看,Sticky Session 就像一根管道。一旦客户端创建了会话,该会话的所有后续请求都将被定向到同一个容器。
这看起来可能很简单,但 Modal 的无服务器基础设施是围绕相反的理念构建和优化的:即各个请求是相互独立的。在从会话(而不仅仅是请求)的角度审视 Modal Servers 时,我们必须从第一性原理出发来思考授权、负载均衡和自动扩缩容。
授权
默认情况下,Modal Servers 需要 Proxy Tokens。要共享 Sticky Sessions,你不应需要关闭身份验证或传递你的 Proxy Token。
相反,每个 sessions.start() 请求都会返回一个签名的会话令牌,用于授予对该会话的访问权限。在客户端之间共享此令牌,可让多个参与者访问房间、游戏或协作应用中的同一状态。
在代理中维护会话到容器的映射,需要确保该映射在重启后以及跨多个副本保持一致。为避免这一点,我们直接将路由元数据编码到令牌中。我们的代理会验证令牌,并将请求路由到持有该会话的容器。
负载均衡
由于我们不在代理中存储会话状态,会话状态的权威视图仅存在于拥有该会话的主机上。这意味着负载均衡器无法即时获得可用容量的全局视图。
为了发现可用容量,我们的负载均衡器会定期探测容器,以获取其可用并发数的快照。然后,负载均衡器结合最近的路由决策和探测到的快照来选择容器。这会将会话均匀分布到可用容器上,同时尽量减少向容量不足的容器发送浪费的往返请求。
自动扩缩容
Modal Servers 根据请求量自动扩缩容,目标是达到每个容器的请求并发目标。请求驱动的自动扩缩器假设请求流量可以在服务容器集群中均匀重新平衡。这种模型适用于无状态 Servers。然而,Sticky Sessions 的行为不同,因为会话流量是不可移动的。即使各会话的流量不同,每个会话的流量也必须落到同一个容器上。
这促使我们将会话作为并发单位,类似于带输入的 Modal Functions。一个容器可以配置为并发运行 1 个或多个会话。当并发数耗尽时,新会话会在队列中等待现有会话结束或新容器准备就绪。自动扩缩器会持续监控活动会话数和排队需求,以做出适当调整计算集群规模的扩缩容决策。
Sticky Sessions 带来了什么
Sticky Sessions 在 Servers 之上提供强大的路由和生命周期保证,使 Modal 能够为一套新的应用提供一流支持:
低延迟语音代理:Modal Phonon Voice Bot 通过 WebRTC 运行一个 Pipecat 代理,并使用 Gradium Phonon 文本转语音。每个通话都是一个 Sticky Session,被固定到一个容器,因此 WebRTC 信令会到达持有对等连接的容器。在音频通过 WebRTC 传输期间,该会话会保持容器处于活动状态。
共享房间:我们的会话聊天室示例为每个聊天室提供一个 Sticky Session 作为后端。每个使用会话令牌连接的客户端都会被路由到同一个容器。每个房间的消息都会广播给房间内的所有人。在聊天室会话的生命周期内,客户端可以断开并重新连接到同一个房间。
游戏:游戏应用将状态保存在内存中以保持连续性并减少延迟。Sticky Sessions 既支持保留进度,也支持托管多个并发玩家房间。
一个勇敢的、粘性的世界
在 Modal,我们喜欢使用 Modal。在追求无限规模和前沿性能的过程中,我们发现自己(大量地)在玩Curvytron。当然,我们在 Modal Servers 上基于 Sticky Sessions 托管了自己快速的(不再有输入延迟的借口!)us-east 部署。
和许多多人游戏一样,Curvytron 让玩家在同一个房间里实时互相竞争。借助 Sticky Sessions,玩家可以创建房间、分享游戏链接,并与其他玩家一起游玩!Modal 会根据活跃房间数量自动扩缩容。
特别感谢 Claudia Zhu、Richard Gong、Akshat Bubna、Michael Waskom 和 Ben Shababo 帮助塑造了这一功能!
如果你有兴趣为下一代软件构建基础基础设施,我们正在招聘。
来源:Modal 官方工程博客 · modal.com