软件设计中的有状态 (stateful) 与无状态 (stateless) 的取舍
2026年6月8日
不论在前端或后端,在做软件设计与开发时,不免会遇到要有状态 (stateful) 还是无状态 (stateless) 这个问题。在面试中也经常会有面试官问「XX 在设计上是有状态的还是无状态的?为什么?」,这里的 XX 可以带入许多不同的技术名词,例如认证 (auth) 上可以选有状态的 session-based 或无状态的 token-based,在通信 (communication) 上可以选有状态的 WebSockets 或无状态的 HTTP。
要能够回答好这个面试问题,或者在工作上做技术决策时要能选到合适的,就需要理解这两种选择会遇到的取舍。在这篇文章,我们会通过实际的案例,来讨论两者的区别与取舍。
从认证的角度看有状态与无状态
先前我们在《登录 (认证) 机制时常见的不同 tokens,彼此间有什么差异?》有谈过不同种的认证方式,其中提到在 90 年代的网页开发,session-based 是主流的认证方式。session-based 的认证方式会需要通过服务器端来管理 session,这种做法的问题在于,今天假如网站流量变大,想要多加几台服务器做水平扩展,就会有 session 要如何处理的难题。
其中一种解法,是确保同一个用户的请求都进到同个服务器 (俗称 sticky session 的做法);但是这种做法会让特定用户的状态依赖特定服务器。如果该服务器挂掉,而 session 又没有同步到共享存储,用户的请求虽然可以被导到别台服务器,却可能因为缺少原本的 session 状态而失败。这是一种潜在的单点故障问题 (single point of failure)。
举例来说,在电商结账流程,如果把商品加入购物车时,session 状态保存在 A 服务器,但结账时 A 服务器挂掉,所以请求被负载均衡器导向备用的 B 服务器,这时因为 B 服务器没有相关 session,可能导致购物车清空或直接报错 (备注:这里是以简化版的购物车暂存来说明状态绑在单台机器上的风险;实务上的电商系统通常会再把购物车状态放到共享存储、数据库或缓存中,避免只依赖单台服务器内存)。
又或者在线上表单的填写过程中,如果要维持填写记录。在填完部分数据后,状态暂存在 A 服务器的 Session 里。但这时如果 A 服务器挂掉,用户回来继续填写后续步骤,提交时请求进到别台服务器,前面填写的数据会就此消失,让整个流程必须从头来过,用户体验会很不理想。
假如我们从状态的角度来看,上面这种 session-based 的设计,会被称为有状态 (stateful) 的设计。但为什么这会被称为有状态?让我们进一步来谈相关的定义。
有状态与无状态的差别是什么?
在软件设计中,所谓的有状态,是指某一个组件 (例如上述例子中的服务器) 保有了与之交互所需的信息 (例如上述提到的购物车中的信息、填表暂存的信息)。而无状态则意味着,该组件本身不带任何信息,当需要用该组件时,就要带上完整的信息来跟该组件交互。
在日常生活中,也有类似的比喻。在早年医疗信息还不发达时,去诊所看医生会类似于有状态,多数人会去家里附近的诊所,而诊所医生的纸本病历有过去所有的记录,甚至有些人如果比较频繁看病,医生会特别熟悉,下次去看病时甚至不用拿出病历也能问诊。这种情况下,医生的看诊速度会很快;但如果今天换一家诊所,新诊所的医生没有过去的病历,问诊上可能会比较困难。
到了近代,许多医疗系统开始往电子病历与跨院所数据交换的方向发展,这在概念上更接近把状态从单一诊所的纸本病历,移到某种可被其他医疗单位查询的共享系统。因此,这种做法也更像是无状态的设计,不管在哪看诊,病历都是上传到云端,而不是放在诊所的纸本病历。医生都能马上调出过去的病历记录。这样一来,即使换诊所、换医生,新的医生还是能根据过去的病历来快速了解病人。
回到认证的案例中,在《登录 (认证) 机制时常见的不同 tokens,彼此间有什么差异?》我们有谈到另一种 token-based 的认证方式,是使用自包含的 token (例如 JWT)。这种设计可以让认证流程更接近无状态,因为服务器不一定要保存每个用户的 session,只要验证 token 的签名与内容即可。换句话说每一个请求都要自带足够的信息,让任何一台服务器都能独立处理。
具体的做法来说,服务器产生一个编码后的 token 给客户端,里面已经包含认证所需的信息。之后每次请求都把 token 带上,服务器不需要保存状态,只要验证 token 就够了。当状态不是绑定在某台服务器上,这样即使 A 服务器挂掉也不担心,换成 B 服务器一样可行。
有状态与无状态的取舍
在看完上述案例讨论后,让我们回到有状态与无状态的取舍讨论上。从上述看诊与认证的例子中,都可以看到有状态的设计好处是速度会更快一点,但是会比较难扩展,且如果遇到事故可能导致状态丢失。反之,无状态的设计,则是容易扩展,且如果出事故也比较不会因单一机器故障而遗失状态。
不过如果要从本质来看,其实所谓的无状态,并不是真的没有状态,而是协议或服务节点本身不保留「必须依赖前一次请求才能理解这一次请求」的状态;换句话说,不是无状态,而是把状态管理转移。以上述的认证来说,无状态的 token-based 认证,状态其实是被转移到客户端持有的 token;而看诊的例子来说,数字病历是把状态从本地的诊所转到云端。
所以在思考有状态与无状态时,其实不是真的在讨论有没有状态,而是状态该放在哪里、是否该在服务器端保存。让我们再通过另一个常见的例子,来加深对这个概念的理解。
HTTP 与 WebSocket 的状态设计比较
在前后端的沟通中,应用层的 HTTP 是很常见的协议选择,HTTP 是属于无状态的设计。HTTP 是由图灵奖得主,同时也是全球信息网 (WWW) 的发明者 Tim Berners-Lee 所提出的。在当时的时代背景下,硬件发展远不及现代,服务器用的 RAM 是 MB 量级,所以非常有限。在这种情况下,如果把 HTTP 设计成有状态的,服务器端就需要保存大量客户端状态,内存就会被占满,不利于规模化。从全球信息网的角度看,如果想要让网络扩展到全球的规模,有状态的设计将难以达成。
当把 HTTP 设计成无状态的,对服务器来说每次 HTTP 请求都是独立的;每次请求处理后,服务器就不再需要记住请求相关的信息,因此能把空间释放出来处理下个请求,让请求处理能有效扩展。
HTTP 的无状态设计能有效扩展来自客户端的单向请求,但是如果沟通变成双向,因为无状态的设计,就会让开发者必须额外找方法来处理。如果要用 HTTP 来处理双向沟通 (例如即时通信),因为服务器端没有记住状态,所以客户端往往必须通过轮询 (polling) 的方式不断发请求,过程中可能导致大量的浪费。
这也是为什么业界后来出现了 WebSocket 这种有状态的协议,服务器会存下需要维持连接本身与连接相关的上下文。
WebSocket 有状态的设计不仅减少浪费,在连接建立后,可以在同一条长连接上持续双向传输数据,避免用短轮询反复送出 HTTP 请求所带来的额外标头、请求/响应往返与轮询空转成本。此外,在连接的状态下,客户端与服务器端的信息传输更快。但因为服务器端需要管理连接的状态,所以从扩展的角度来说,到一定量后就会遇到瓶颈。
HTTP 与 WebSocket 没有哪一个好哪一个坏,更关键的是适用的场景。如果是一般的客户端请求,不在服务器维持状态能有效扩展,在多数的场景更适用;在服务器维持状态虽然限缩扩展能力,但是能减少请求的浪费与降低延迟,在需要实时且双向通信的场景会更适用。
阅读更多
如果对有状态 (stateful) 与无状态 (stateless) 的取舍这个主题感兴趣,我们在 E+ 成长计划中的主题文有谈到更多案例分析。对更深入了解这个主题,以及其他前后端开发、软件工程、AI 工程主题感兴趣的读者,欢迎加入有超过千位工程师选择的 E+ 一起成长 (链接)。