关于 REST 中的“无状态”的一个疑问。

2020-12-24 00:39:54 +08:00
 mitu9527

下面的内容引自 REST 论文原文:

We next add a constraint to the client-server interaction: communication must be stateless in nature, as in the client-stateless-server (CSS) style of Section 3.4.3 (Figure 5-3), such that each request from client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. Session state is therefore kept entirely on the client.

重点是最后一句,翻译过来就是“因此,会话状态完全保存在客户端上”。虽然出自官方,但我对此还是有疑问:

把会话数据存在客户端,也就是使用所谓的“客户端会话”真的算是“无状态”么?

我又去读了很多关于“无状态”的文章、帖子和回复,我看到不少人说这种确实是“无状态”,但我个人总觉得不对。客户端会话数据也是由服务端生成,最终也是由服务端读取,只不过没有被保存在服务端,而是被保存到了客户端,所以它和常见的“服务端会话”的区别只是存储的地方不一样(这种“去中心化”确实有它的好处,但缺点也不少),但作用完全是一样的,难道不是么?

把会话数据从客户端传给服务端,然后服务端读取传过来的会话数据并进行使用,这不也是在读取本次交互过程(或者说会话)中前面某个请求所产生的数据么?那请求和请求之间还算是互相独立么?说好的“人生只如初见”呢?

现在的我觉得如果要“无状态” ,就绝对不应该有“会话”这个概念,不论是“服务端会话”还是“客户端会话”,都不应该存在,感觉使用“客户端会话”来符合“无状态”有点像掩耳盗铃似的。

最后在重复一遍问题:把会话数据存在客户端,也就是使用所谓的“客户端会话”真的算是“无状态”么?

5427 次点击
所在节点    程序员
82 条回复
acmore
2020-12-24 10:51:46 +08:00
孔子说过,只写 Code 不读论文会迷茫,只读论文不写 Code 是有害的。实践一下就能明白为什么会这样了。
以及你先前以为的无状态是哪哪都不存状态,这从数学上就是不可能的,一定要有一个状态。只不过服务端的状态牵制了很多东西,所以放在客户端干净利索。
no1xsyzy
2020-12-24 10:55:44 +08:00
@mitu9527 就好像说 serverless 是不是真的没有服务器了?
stateless 是为了服务水平扩展设计的概念。stateless 就是说你可以把服务器任意部署吊销,但只要有一个实例存在就不会导致服务发生故障。
icyalala
2020-12-24 11:02:52 +08:00
人家说的 stateless-server 是有上下文含义的。
你非要把 state 含义扩大化,然后把人家批判一番。。
mitu9527
2020-12-24 11:02:55 +08:00
@kop1989 我当然知道要有状态,我也知道放在服务端不对,应该放在客户端,但是我觉得这些状态不应该由服务端生成和读取,应该由客户端自己生成本次交互过程的状态并存在本地存储,然后客户端发起请求时,从本地存储中取出来直接告知服务端,而不是“我把交互状态都发给你了,你自己看着选用吧”。
mitu9527
2020-12-24 11:05:31 +08:00
@acmore 状态放在客户端没毛病,但是状态不应该“由服务端生成,然后放在客户端”,应该“由客户端生成,然后放在客户端”。
mitu9527
2020-12-24 11:07:51 +08:00
@icyalala 我没有否定,也不敢轻易否定,但是确实有质疑,质疑一下没毛病吧。
libook
2020-12-24 11:10:22 +08:00
“In computing, a stateless protocol is a communications protocol in which no session information is retained by the receiver, usually a server.”——Wikipedia: Stateless protocol

首先,“无状态”是个简称,实际指的是“Server 无状态”,即在抽象意义上,Server 在处理请求的时候不依赖 Server 当前的状态。
举个依赖 Server 当前状态的例子就是传统的 Session 模式:用户的登录会话信息保存在 Server 上,Client 上仅保存 Session ID,Client 发送携带 Session ID 的请求到 Server,Server 读取本地的 Session 内容来确定用户当前的状态,再处理请求。
而无状态就是 Server 在接收到请求之后不需要读取自己在用户之前的请求所留下来的 Session 数据,仅凭借 Client 请求中携带的信息就可以完成用户状态的判断。

拿生活举例说明,比如一家理发店:
有状态模式每位理发师会记住自己服务过的每位顾客的喜好,每来一位顾客,都要问其以前是哪位理发师为其服务的,然后看那位理发师是否能继续为这位顾客理发,如果那位理发师抽不开身,就可以把顾客的喜好情况讲给另一位空闲的理发师,让这位理发师来为顾客理发;
无状态模式就是理发师不需要记住顾客的喜好,每位顾客来了就可以分配一位空闲的理发师,然后需要顾客把需求交代清楚,理发师按照需求进行此次理发服务。

无状态好处有很多,主要的几点:
1. Client 的请求对 Server 一视同仁,理论上此服务集群的任何一个 Server 随时都有能力正确处理 Client 的请求。
2. Server 对 Client 的请求一视同仁,处理每个请求不需要参考同一用户前几次发了什么次请求,仅考虑当前请求包含了哪些信息。
3. 服务集群不需要考虑是否要同步各个 Server 的状态,因为 Server 无状态可同步。
4. 负载均衡不需要考虑引导请求到集群中特定的 Server,可以放心地按照实际负载分布来分发请求。
进一步产生的好处为:
1. 服务器程序设计简单,每个请求的处理过程是独立的。不会遇到请求与请求之间的逻辑关系而导致程序复杂。由此接口设计模式可以很简单、清晰。
2. 服务集群性能是和机器数量等比相关的,性能不够加机器就行,不会因为加机器而产生额外的开销。

当然,无状态设计也会有一些代价,比如每个请求都需要携带较多的状态数据,而 Server 可能也需要每次收到请求都执行一遍对请求中状态数据的解析。

系统架构上的概念都是抽象意义上的,比如无状态概念里的“Server”指的不是我们一般说的物理意义上的服务器计算机,而是在系统架构上处理业务请求的那部分程序,这其中甚至都不包括底层数据库,但可能包括或不包括实际实施中使用的一些中间件。
所以在尝试理解系统架构概念的时候,要先理解概念里的每个名词究竟代表的是什么,再看实际实施过程中,这个名词指的是整个系统的哪些部分。
acmore
2020-12-24 11:13:42 +08:00
@mitu9527 考虑这样的场景,如果状态里保存的有这个账户的权限信息,且这信息是要用非对称算法加密的。那么如果客户端生成状态就意味着权限的控制和私钥都要交给客户端,这样是不可以的。最终的控制还是要由服务端完成,客户端只是走个过场。
mitu9527
2020-12-24 11:17:08 +08:00
@libook 我和你的观点整体上是类似的。
libook
2020-12-24 11:37:24 +08:00
@mitu9527 在主题内容中,你其实太纠结于“无状态”本身的含义了,但 REST 的作者可不是这么想的,你看论文的前几个章节,他只是在以往的 API 设计方法中遇到了一些痛点,然后希望借助“无状态”的思路来解决这些痛点而已,实现纯粹的“无状态”本身并不是 REST 所讨论的核心问题。
就像我们现在在程序上所说的“异步”,实际上是为“同步”需求服务的,我希望多个业务逻辑能同步执行,所以需要技术栈具备异步处理的能力,即在一个业务正在处理的过程中可以同时响应另一个业务(有什么活就做什么活,不等着)。在生活中,“我一边打电话一边敲代码”这种在语言表达上不能被称为“异步”(虽然职业病会首先想到“异步”),而是我在同步做两件事情(我有异步做事的能力,可以随时在想下一行代码和下一句回答之间切换)。
lihongming
2020-12-24 11:43:55 +08:00
@mitu9527 客户端应该保存哪些数据并没有一个标准,甚至都没有最佳实践,看你自己的需要和取舍。

就 AWS 的身份验证服务( Cognito )所使用的 JWT 来看,它并没有把权限放到客户端,但放了用户的角色和用户组,你可以参考一下。
mitu9527
2020-12-24 12:25:39 +08:00
@lihongming 你说到 AWS 的情况,我猜测可能是 AWS 引入了 RBAC 模型,并且角色与会话绑定了,才会引发不得不这么做。

比如,我有一个账户,即属于运营人员角色,又属于某种管理人员角色,但运行时角色只能二选一,即角色运行时互斥,那就要这么做了。此时,如果我有两个浏览器,都用这个账号登陆,一个浏览器登陆后选择运营人员,一个浏览器登陆后选择管理人员,那用这种把 JWT 当作客户端会话的方法就很合适。

但是反过来,不在 JWT 中放入角色,也可以轻松实现同样的目的。比如服务端通过一个接口返回可选的角色,客户端展现,然后用户选择,客户端再把用户的选择(即所谓的“状态”)保存在客户端本地存储中,后面每次请求带上该角色(状态)即可。

这两种方法,都把状态存在了客户端而不是服务端,这是是没问题的。但区别是一个是服务端发起的保存和读取操作,另外一个是客户端自己发起保存和读取操作。很类似,但我个人确实觉得服务端发起和保存和读取操作,比较别扭,可能是我思维有点固化,没转变过来吧。
Bromine0x23
2020-12-24 12:48:26 +08:00
@mitu9527 我的意思是作者在论文中所说的 Stateless 指的是仅服务端无状态的 client-stateless-server (CSS) 风格,而不是从字面理解的服务端和客户端都无状态。要从实际定义理解而不是望文生义
zliea
2020-12-24 12:50:49 +08:00
核心概念#47 说的很明白

用户 /认证信息、组织、角色在 REST 里都是资源。
但在实际情况中,而且这些在业务系统中并不完全适合放在 url 里进行传输,因此放在 header(cookie)里。
zliea
2020-12-24 12:58:39 +08:00
@ztechstack 而且在实际架构中,甚至可以将认证的步骤提前,放在网关等外部层去处理。
tomoya92
2020-12-24 13:59:37 +08:00
@Jooooooooo #1 面试官喜欢问
hantsy
2020-12-24 14:20:53 +08:00
对于用户这一块,很多 IDP 算是有状态的,因为它有管理在线用户的功能,提供了 /logout 去删除 token 之类的。
charlie21
2020-12-24 14:34:59 +08:00
呃你看见真实情况觉得真实情况把理论给否定了,可理论根本不是你理解的那样 。。
dzdh
2020-12-24 14:37:04 +08:00
你把无状态理解成不间断的状态保持
lihongming
2020-12-24 14:37:51 +08:00
@mitu9527 嗯,你对 AWS 的分析正好证明了我说的话——没有标准,看你的需要和取舍

希望你能真正理解好这句话,而不是在那些没用的概念上纠结

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/738435

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX