在 Modal,我们的无服务器计算为实时推理、训练和批处理作业提供支持,并随需求自动扩展。沙箱提供另一种模式,即生命周期由你显式控制的容器。越来越多的负载需要介于两者之间的东西:在请求之间保持连续性的自动扩展无服务器计算。考虑一个语音转文本模型它带有音频流的解码器状态,一个语音智能体它维持一段对话,或者一个多人游戏。对于这些负载,用户在整个会话期间每次重连都必须回到同一个容器。
我们最近推出了粘性会话,构建在我们低延迟的Modal Servers之上。每个会话的请求都会落到同一个容器上。要使用粘性会话,只需用@modal.sessioned()装饰器创建一个 Modal Server,并用SDK或HTTP API启动会话:
具有强亲和性的无服务器
Modal 已经支持通过 Web Functions 的 WebSockets有一段时间了。然而,WebSocket 连接绑定的是 Function 的“输入”,而不是容器——如果连接断开,重连时会创建一个新的输入,并可能落到不同的容器上。
有状态的 Web UI,例如Gradio和Streamlit,是需要请求与后端容器之间强亲和性保证的负载。每个请求都必须到达持有用户状态的进程。一种反模式变通方法是设置max_containers=1,它通过完全放弃扩展来保证粘性。
Modal Servers 已经提供了一种更柔和的亲和性形式;共享相同Modal-Affinity-Routing-Key头的请求会以尽力而为的方式路由到同一个容器。该容器仍可能随时被缩容或替换,从而将后续请求路由到不同的容器。
粘性会话通过确保同一会话中的所有请求都落到同一个容器上,提供了更强的路由保证。
Modal Servers 为我们奠定了基础
粘性会话构建在我们为Modal Servers 开发的基础设施之上这一基础设施的核心是我们的自研无状态代理(fprs)。该代理利用从持久化存储中对账而来的内存缓存路由状态,提供低延迟的路由和负载均衡。服务器已经为我们提供了一种方式,可将 HTTP 流量路由到容器、管理容器生命周期,并根据需求扩缩容集群。
粘性会话需要三项当前请求级路由未能提供的能力:
- 固定路由:会话中的每个请求都到达同一个容器。
- 会话生命周期:容器所在主机跟踪哪些请求属于哪些会话,并判定每个会话何时变为空闲。
- 基于会话的自动扩缩容:容器集群按活跃会话数而非请求数扩缩容。
让无服务器基础设施具备会话感知能力
从客户端的视角看,粘性会话就像一根管道。一旦客户端创建了会话,该会话后续的每个请求都会被导向同一个容器。
这看似简单,但 Modal 的无服务器基础设施是围绕相反的理念构建和优化的:单个请求彼此独立。当从会话(而非仅请求)的视角审视 Modal Servers 时,我们必须从第一性原理出发,重新思考授权、负载均衡和自动扩缩容。
授权
Modal Servers 默认要求代理令牌。要共享粘性会话,你不应需要关闭身份验证或传递你的代理令牌。
取而代之的是,每个sessions.start()请求都会返回一个签名的会话令牌,用于授予对该会话的访问权限。在客户端之间共享此令牌,可让多个参与者访问房间、游戏或协作应用中的同一状态。
在代理中维护会话到容器的映射,需要保证该映射在重启和多个副本之间保持一致。为避免这一点,我们直接将路由元数据编码进令牌。我们的代理会验证令牌,并将请求路由到持有该会话的容器。
负载均衡
由于我们不在代理中存储会话状态,会话状态的权威视图只存在于拥有该会话的主机上。这意味着负载均衡器无法即时获得全局的可用容量视图。
为了发现可用容量,我们的负载均衡器会定期探测容器,以获取其可用并发数的快照。负载均衡器随后结合最近的路由决策和探测到的快照来进行容器选择。这样可以在可用容器之间均匀分布会话,同时尽量减少对已无容量容器的无效往返。
自动扩缩容
Modal Servers 根据请求量进行自动扩缩容,目标是达到每个容器的请求并发目标。这种由请求驱动的自动扩缩容假设请求流量可以在服务容器集群中均匀重新分配。该模型对无状态 Servers 效果良好。然而,Sticky Sessions 的行为有所不同,因为会话流量不可移动。每个会话的流量必须落到同一个容器上,即使各会话的流量大小不同也是如此。
这促使我们将会话作为并发单位,类似于带输入的 Modal Functions。一个容器可以配置为并发运行 1 个或多个会话。当并发用尽时,新会话会在队列中等待现有会话结束或新容器就绪。自动扩缩容会持续监控活跃会话数和排队需求,以做出能合理配置计算集群规模的扩缩容决策。
Sticky Sessions 带来的能力
Sticky Sessions 在 Servers 之上提供的强路由和生命周期保证,使 Modal 能够为一套新的应用提供一等支持:
低延迟语音智能体:该Modal Phonon Voice Bot运行一个Pipecat智能体,通过 WebRTC 使用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 帮助塑造了这一功能!
如果你有兴趣为下一代软件构建基础基础设施,我们正在招聘。