有状态与无状态架构的速成课程

banq


有状态架构与无状态架构是那些表面上看似简单但对于您正在构建的任何软件都具有重要影响的架构选择之一。

有状态架构具有记忆功能。它保存有关客户端会话的信息,并使用该历史记录来通知响应和操作。想象一下,它就像一个记得你平时点的咖啡师:“今天还是平时点的吗,亚历克斯?”

无状态架构具有健忘症。每个请求的处理都不需要了解之前的请求。它就像一台自动售货机——它不关心你是谁,也不关心你上次买了什么;它只响应你当前的输入。

有状态架构:优点和缺点
优点:

  • 丰富的用户体验:系统记住用户偏好和历史记录
  • 更简单的客户端逻辑:服务器处理大部分状态管理
  • 复杂状态的可能速度更快:无需在每次请求时传输状态

缺点:
  • 扩展难题:服务器实例需要以某种方式共享状态
  • 需要会话亲和性:必须将用户路由回同一台服务器,或者必须同步状态
  • 故障恢复:如果服务器崩溃,用户状态可能会丢失

无状态架构:优点和缺点
优点:

  • 水平扩展:轻松添加服务器,无需担心状态
  • 弹性:任何服务器都可以处理任何请求
  • 简单:更少的活动部件和故障模式

缺点:
  • 更繁重的请求:每个请求都需要包含所有必要的上下文
  • 客户端复杂性:客户端上进行更多状态管理
  • 性能:可能需要多个请求才能完成复杂的操作

真实世界的例子
有状态的示例
1.电子商务购物车
当你在亚马逊或任何大型零售商网上购物时,你的购物车会在你浏览不同产品类别时保持不变。这是状态架构实践的完美示例:

  • 服务器会记住你添加到购物车中的商品
  • 如果你离开去浏览更多产品,购物车会保留你的选择
  • 即使您关闭浏览器并稍后再回来(通过 cookies 或帐户登录),您的购物车商品仍然存在
  • 这种持久状态创造了一种无缝的购物体验,而这种体验在完全无状态的设计中会显得繁琐

2. 视频流服务
Netflix 和 YouTube 是有状态应用程序的优秀示例:

  • 它们会追踪你视频的播放位置
  • 当您返回时,即使在不同的设备上,您也可以从停止的位置继续播放
  • 您的观看记录会影响推荐
  • 音量、字幕设置和播放速度等偏好设置都会被记住

Netflix 维护复杂的用户状态,不仅包括基本的观看历史,还包括有关观看习惯的详细指标,以创造高度个性化的体验。

3. 消息应用程序
WhatsApp、Slack 和其他消息传递平台严重依赖状态架构:

  • 跨会话保留消息历史记录
  • 跟踪消息的已读/未读状态
  • 监控并共享用户状态(在线/离线状态)
  • 在聊天之间切换时,活动对话仍保留在上下文中

当您离开后打开 Slack 时,它会准确地知道您查看过哪些消息、哪些是新消息、哪些频道有活动以及您需要处理哪些@提及——所有这些都是状态信息。

无状态示例
1. RESTful API
公共天气 API 是无状态设计的典型例子:

  • 每次请求天气数据时,您都必须提供您的位置
  • API 不会“记住”你之前的请求
  • 每个请求都包含满足该请求所需的所有信息
  • 这使得 API 具有高度可扩展性——数百万个请求可以分布在服务器群中,而无需会话跟踪

2.内容分发网络(CDN)
Cloudflare 和 Akamai 等 CDN 以无状态方式运行:

  • 当您从 CDN 请求图像、JavaScript 文件或视频时,每个请求都会被独立处理
  • CDN 不需要了解你之前的请求
  • 这使得 CDN 可以将请求路由到最近的边缘服务器,而无需维护用户会话
  • 其结果是极高的可扩展性和性能

3.无服务器函数
AWS Lambda、Azure Functions 和 Google Cloud Functions 被设计为无状态的:

  • 每次函数调用都从头开始
  • 除非明确存储在外部,否则执行之间没有持久性
  • 这实现了大规模并行化和自动扩展
  • 您只需为使用的计算时间付费,没有闲置资源

例如,图像处理 Lambda 函数可能会接收图像、调整其大小并将结果存储在 S3 中,但它并不了解之前处理过的图像。

状态架构中的常见模式
1. 粘性会话
许多电子商务平台和网上银行系统都使用粘性会话:

  • 一旦用户连接到服务器 A,负载均衡器就会确保其所有后续请求都转到服务器 A
  • 这使得用户的会话数据无需复杂的同步即可访问
  • 但是,如果服务器 A 发生故障,用户的会话将丢失,他们可能需要再次登录


2. 集中式会话存储
许多可扩展的 Web 应用程序使用 Redis 或 Memcached 进行会话管理:

  • 用户会话数据存储在中央高速内存数据存储中
  • 应用服务器将会话视为外部资源而不是本地状态
  • 如果服务器发生故障,用户可以将中断路由到另一台服务器
  • 会话继续,因为新服务器可以访问相同的会话存储

这创建了一种混合方法——应用程序服务器保持无状态,而会话状态被外部化。Twitter、LinkedIn 和大型电子商务平台等公司通常会以额外的复杂性来实现这种模式:
  • 会话数据通常被分区或分片到多个缓存实例中以实现可扩展性
  • 短期身份验证令牌可以与较长的会话数据一起使用
  • 关键会话数据可能会定期保存到数据库中以确保持久性
  • 并非所有用户状态都保存在会话中——有些可能会按需获取

无状态架构中的常见模式
1.基于令牌的身份验证
许多现代 API 和单页应用程序都使用 JSON Web Tokens (JWT):

  • 初始身份验证后,服务器将发出签名的 JWT
  • 客户端在后续每个请求中都包含此令牌
  • 服务器验证令牌但不存储会话信息
  • 这允许在没有服务器端状态的情况下进行身份验证

例如,当您登录到 Trello 或 Notion 等现代 Web 应用程序时,您的浏览器会存储 JWT 并将其包含在每个 API 请求中,从而允许您在服务器无需维护会话的情况下保持身份验证。

2. 幂等操作
像 Stripe 这样的支付处理器使用幂等操作来确保可靠性:

  • 每个付款请求都包含一个唯一的幂等密钥
  • 如果多次发送相同的请求(由于网络问题),则付款仅处理一次
  • 服务器可以验证请求是否已被处理,而无需维护会话状态
  • 这可以防止重复收费,同时保持无状态架构

何时使用每种方法
在以下情况下选择有状态:

  • 用户体验在很大程度上取决于个性化和环境
  • 实时应用程序需要持久连接(游戏、协作编辑)
  • 复杂的工作流程跨越多个交互(多步骤表格、结帐流程)
  • 会话安全至关重要(银行应用程序、管理仪表板)

在以下情况下选择无国籍:
  • 全球规模和高可用性是主要关注点
  • 您的应用程序需要处理不可预测的流量高峰
  • 部署简单性和基础设施成本是首要考虑因素
  • 你正在构建供许多不同客户端使用的公共 API

这不是“非此即彼”的问题,而是“该去哪里”的问题

这是架构讨论中经常被忽略的事实:几乎每个成功的现代应用程序都使用混合方法。真正的架构决策不是在有状态或无状态之间进行选择 - 而是确定哪些组件应该是有状态的,哪些应该是无状态的。

聪明的架构师从状态边界和状态所有权的角度来思考:

Spotify 的状态架构:

  • 无状态:搜索 API、目录浏览、身份验证
  • 状态:实时流媒体会话、播放位置、播放列表管理
  • 为何有效:大量发现功能可水平扩展,而个性化功能可保持上下文

Gmail 的州分布:
  • 无状态:电子邮件传递管道、身份验证、联系人管理
  • 状态:已读/未读跟踪、草稿撰写、用户界面偏好设置
  • 为何有效:核心电子邮件基础设施可处理大规模电子邮件,而用户特定功能可保留上下文

现代银行应用程序的状态分区:
  • 无状态:公共信息 API、初始身份验证、交易处理
  • 状态:欺诈检测模式、会话上下文、合规性监控
  • 其工作原理:关键安全功能可维护必要的上下文,同时实现标准操作的扩展

战略问题:

  1. 哪些数据需要在请求之间持久保存?
  2. 该状态应该存在哪里?(客户端、CDN 边缘、应用服务器、数据库等)
  3. 状态选择对性能有何影响?
  4. 状态位置如何影响安全态势?
  5. 当状态存储组件发生故障时会发生什么?

这些问题可能是你在工作中的设计评审中,或者在系统设计面试中面试官可能会问到的问题。

正如我们所见,将架构决策框定为“有状态与无状态”是错误的。真正的技巧在于确定系统的哪些部分应该保持状态,哪些部分应该保持无状态。

精心地在组件之间分配状态是基于特定需求的:

  • 无状态,需要水平扩展和弹性
  • 需要上下文Context保留和用户连续性的有状态
  • 需要同时满足以下条件的外部状态存储

状态管理只是架构的一个方面。它与同步与异步通信、单片与微服务、以及领域驱动设计等决策以及塑造系统的其他关键选择相关。

banq注: