REST’无状态’约束
REST的第二条约束:stateless
,也就是无状态
论文原文 5.1.3 Stateless
这条约束取自 3.4.3小节
stateless
约束了服务器不允许有会话状态,会话状态就是web开发者平常所说的状态,省略了会话一词
无状态提高了服务器的透明性、稳定性、伸缩性
论文中提到的做法是:"从客户端到服务端的每个请求必须包含理解该请求所必须的所有信息,不能利用任何存储在服务端的上下文,因此会话状态要全部保存在客户端 "
Session
在web系统中,无状态是完全能够实现的
但有状态的做法是如何发展起来的呢?
以最典型的"鉴权"为例
在实践中,我们使用状态最多的地方就是"鉴权"
如果采用无状态,传统做法是 必须让客户端的每次请求都携带账号和密码。但这样就需要每次都连接数据库验证密码,增加了数据库的压力
于是就有了Session。通常的做法是:用户登录 且 服务器鉴权成功后,服务器创建一个session对象,该对象有一个唯一的id(该id应当是无意义无规律的,否则会被恶意推测),叫做sessionId。session对象中保存用户的一些数据,如用户id、最后一次访问时间、超时时间等。
然后服务器将sessionId以cookie的形式发送给浏览器。因为浏览器发送请求时都会带上cookie,所以接下来浏览器的每次请求都会携带sessionId,服务器就可以根据sessionId确认用户已经成功登录,也可以获取到session对象存储的其它信息
如此一来,鉴权就变得又快又方便。
session对象存储在服务器程序的运行内存中,它就是服务器的状态
session大量使用在单体架构中(只有一台服务器),如小公司内部的管理系统,这种做法让系统结构更加简单
但是,因为session对象仅存在于某一台服务器的内存中,如果用户的请求被网关转发到了集群中的另一台服务器,就会找不到用户的状态(不能通过sessionId找到session对象),导致鉴权失败。
所以session降低了服务器的可伸缩性,随着用户量的增多,对服务器伸缩性的需求变大,这个缺点会越加明显。
如何解决session带来的缺陷呢?
从解决问题的角度出发,可分为 真·无状态 和 假·无状态 两种方案。
以下方案参考了这篇文章:集群/分布式环境下5种共享session处理策略
1. 真·无状态
真·无状态 的核心是服务器不直接保存状态
将状态(session对象中的信息)保存到一个独立于服务器的、可被所有服务器共享的地方。
比如保存在分布式缓存(如Redis)中,服务器通过网络请求调用
2. 假·无状态
假·无状态 的核心是保证用户的每次请求总能找到对应的状态
可分为两类做法:1. 粘性session机制 2. 状态一致化
- 粘性session机制。在业务网关处做设计,使同一个用户的每次请求都被转发到同一台服务器上,相当于把用户和这台服务器粘到了一块。
具体的设计方案可以是:将用户的sessionId设置为整数类型,请求到达网关时,对sessionId取余,除数为业务服务器的数量,余数为请求应当转发到的服务器编号。
如果用的是第三方业务网关(比如Nginx)有负载均衡功能,并且有提供粘性session功能,可以按照它们的官方文档设置 - 状态一致化,也叫session复制。将一个服务器上某状态的增删改操作产生的结果 同步到分布式中的所有服务器中,让所有的服务器都存储相同的状态
[附]无状态鉴权的最优做法:jwt
鉴权是几乎每个系统都会做的事
在没有jwt之前,通常在服务器创建session对象缓存用户的鉴权信息,浏览器通过发送sessionId表明用户的身份
jwt提供的轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权。
有了jwt之后,鉴权变得更加轻量和简单。它利用RSA非对称加密来实现。将原本session对象中的信息保存在jwt字符串中,该字符串由服务器鉴权成功后创建,浏览器每次请求时携带。
解密密钥只存在于服务器中,所以浏览器无法得知jwt字符串真正表达的信息。并且字符串一旦被修改,服务器也将无法获取有用的信息。所以jwt尽管把信息交由浏览器保管,但它是十分安全的
对于具体如何使用,贴一个不错的文章链接,这里就不做赘述:JWT实现无状态登录