Stateless vs Stateful:一次性彻底了解差异
Source: Dev.to
在设计应用的后端时,选择 Stateful(有状态)还是 Stateless(无状态)架构是最根本的决策之一。这一选择直接影响系统的扩展方式以及认证的处理方式。
Stateful
类比
想象你每天都去同一家咖啡馆。你一到门口,服务员已经微笑着说:
“早上好,奥塔维奥!要和往常一样的咖啡吗?”
服务员记得你,因为他把你的名字和常点的咖啡记在了记忆里。如果你只说“再来一杯”,他也能准确知道该准备什么,因为在交互之间 保持了状态。
概念
服务器与用户保持“会话”,把会话信息存储在自己的内存(RAM)或临时数据库中。
实际例子
PHP、Java(HttpSession)或 ASP.NET 中的经典会话,服务器在用户登录期间保存用户数据。
优缺点
- 优点: 直观,易于在小型(单体)应用中管理。
- 缺点: 水平扩展困难;需要使用 Sticky Sessions 或外部存储(如 Redis)让多实例共享状态。
代码示例(.NET)
// 服务器将名称保存在会话中(状态保存在服务器的 RAM 中)
HttpContext.Session.SetString("UsuarioNome", "Otávio");
// 要获取时,服务器从自己的内存中读取:
var nome = HttpContext.Session.GetString("UsuarioNome");
Stateless
类比
现在想象一家自助咖啡馆。你到达后,告诉服务员你的名字和咖啡种类,拿到饮料后服务员立刻忘记了你的信息。如果想再来一杯,你必须重新提供所有信息。
概念
服务器 不在请求之间保存任何客户端信息。处理任务所需的全部信息必须随请求一起发送(通常在 header 中)。
实际例子
现代的 REST API 和基于 JWT(JSON Web Token)的认证。
优缺点
- 优点: 高度可扩展,适合微服务和云环境。
- 缺点: 每次请求会稍微“重”,因为每次调用都要携带认证数据(token)。
代码示例(Spring Boot)
@GetMapping("/perfil")
public ResponseEntity getPerfil(@RequestHeader("Authorization") String token) {
// 服务器是“冷的”:不在 RAM 中维护已登录用户列表。
// 解码客户端发送的 JWT 以识别用户。
if (token != null && token.startsWith("Bearer ")) {
String jwt = token.substring(7);
String username = jwtService.extractUsername(jwt); // 从 payload 中提取 "sub"
return ResponseEntity.ok("Usuário identificado via Token: " + username);
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
对比
| 特性 | Stateful | Stateless |
|---|---|---|
| 存储位置 | 服务器(内存/RAM) | 客户端(Token/Cookie) |
| 可扩展性 | 困难(需要 Sticky Sessions 或外部存储) | 容易(面向云和微服务) |
| 真实案例 | PHP 会话、旧版购物车 | REST API、JWT、OAuth2 |
| 连接方式 | 保持对话历史 | 每次请求都是新对话 |
结论
现代 Web 开发的黄金法则是 优先使用 Stateless。在 AWS、Azure、Google Cloud 等环境,或使用 Docker/Kubernetes 时,无状态模型可以在不担心会话丢失的情况下轻松扩展到 10、100 台实例。
Stateful 仍然在某些遗留系统或非常特定的单体应用中有其用武之地,但大多数应用的现在和未来都是 Stateless。