服务的状态
目录
有状态服务和无状态服务是两种不同的服务架构,两者的不同之处在于对于服务状态的处理,本文将主要记录:什么是状态?不同的状态会对服务产生什么样的影响、以及为什么如今都倡导无状态服务?
¶概述
状态化的判断是指两个来自相同发起者的请求在服务器端是否具备上下文关系。基于此,有/无状态的应用程序特点如下:
简而言之,无状态服务不会记录服务状态,不同请求之间也是没有任何关系;而有状态服务则刚好相反,不同的请求之间是有关联关系的。判断一个服务状态性最简单的标识就是:两个来自相同发起者的请求在服务端是否具有上下文关系
RedHat 对于应用的状态具有下列的见解:
The state of an application (or anything else, really) is its condition or quality of being at a given moment in time–its state of being. Whether something is stateful or stateless depends on how long the state of interaction with it is being recorded and how that information needs to be stored.
应用(或其他任何事物)的状态是指它在特定时间的状况或品质,即当前应用的一种存在属性(运行中或宕机了)。要判断一个应用是有状态还是无状态的,取决于和这个应用交互的过程中,当前这种交互状态维持的时间以及是否需要在这个过程中需要存储的信息
¶服务的状态
下文将详细介绍有状态服务与无状态服务的定义,两者的区别。讨论为什么如今大家都呼吁无状态服务,对于现有的有状态服务如何无状态化?
¶有状态服务
😣 如今大家普遍不喜欢使用有状态服务,就是因为有状态服务存在以下的缺点:
尽管有状态服务存在许多缺陷,但是有状态服务仍然存在一些优点:
¶有状态服务的数据局部性
¶有状态服务的强一致性
有状态的服务通常会导致服务的强一致性。有状态的服务可以构建一种粘性的链接,也就是说客户端的请求总是会被路由到最初为之提供服务的服务器主机上。以此方式实现的服务,可以增加 AP 系统的一致性力度。这种强一致性模式包括线性随机访问内存和读你所写(Read your Write)。Werner Vogels 在他的文章中总结了这些内容
无状态的服务很容易的通过给后端添加服务器和前端的负载均衡实现横向的扩展。此类应用拥有叫做「 数据运送范例 」的方式,就是数据被请求时是来自后端的数据存储为请求提供,在未来的请求中,若相同的数据被请求时,是不会去关心这些请求是从哪个服务实例来的,因为服务实例是无状态的.McCaffrey 谈到在通信频繁的应用中这种架构简直是一种浪费,因为这些应用要在服务端与客户端之间频繁的通信,而且在此类应用中有状态的服务显然是一种更好的选择
¶有状态应用的粘性连接
粘性连接可以使用持久性的连接来实现,但是会带来负载在后端分布不均的问题,这就会导致客户端捆绑到服务器,而有些服务器不能得到充分利用,最终导致部分服务器负载过多。其中一个减轻此种后端压力的方法就是一旦达到某个阀值就拒绝再来的请求(提前拒绝请求)。
非粘性的服务还可以通过路由的逻辑来实现,这可以使得任何的客户端通过获得正确的路由来找到任何的服务器。此实现会带来两个问题,路由到集群成员(谁在我的集群中?)和工作分布(谁来做?)。集群成员可以是静态的也可以是动态的。后者可以通过使用 gossip 协议和共识系统来实现。工作分布则有更多的实现机制-随机替代、一致性哈希、以及分布式哈希表
McCaffrey 在他的演讲中列出了一些构建有状态服务的陷阱,其中包括没有绑定的数据结构导致的内存问题、类似长期的垃圾回收暂停和重载状态时出现的内存管理问题等。状态重载会在恢复和部署新代码时发生,这两者都会像第一次从数据库中获取数据那样付出高昂的代价。
¶图解有状态服务
程序做的事情本质上就是数据的移动和组合,以此来达到我们所期望的结果。其中如何移动、如何组合是由「 算法 」来定的。任何一个「 结果 」都是通过一系列的「 行动 」将最开始的「 原料 」进行加工、转化得来。
比如,你将常温的水,通过倒入水壶、通电加热等工作后将常温的水变成了 100 度的水,这是常见的烧水过程。这个过程需要好几道行动才能得到结果。
这个时候如果想降低这几道行动的成本,天然的想法就是提炼出反复要做的事情,将各个独立的行动并行化。在程序中的实现就是将一部分数据放到一个「暂存区」中以提供给相关的行动共用
但是如此一来,就导致了需要增加一道关系,以表示每一个行动与哪一个「暂存区」关联。一旦拥有了关联关系,此时行动就变成了「有状态」
共用同一个「暂存区」的多个行动所处的环境经常被称作「上下文」。「暂存区」里存的是「数据」,所以可以理解为有数据就等价于有状态。「数据」在程序中的作用范围分为「局部」和「全局」(对应局部变量和全局变量),因此「状态」其实也可以分为两种,一种是局部的「会话状态」,一种是全局的「资源状态」
因为有些服务端不单单负责运算,还会提供其自身范围内的「数据」出去,这些「数据」属于服务端完整的一部分,被称作「资源」。所以,理论上「资源」可以被每个「会话」来使用,因此是全局的状态
与「有状态」相反的是「无状态」,「无状态」意味着每次加工所需的原料全部由外界提供,服务端内部不做任何的「暂存区」。并且请求可以提交到服务端的任意副本节点上,处理结果都是完全一样的
有一类方法天生是「无状态」,就是负责表达移动和组合的「 算法 」。因为它的本质就是:接收「 原料 」「 加工 」并返回「 成果 」
如果想获得更好的伸缩性以及容错性,就需要尽量将「有状态」的处理机制改造成「无状态」的处理机制
¶有状态服务的无状态化处理
⛵ 「有状态」的处理过程是可以改造成「无状态」的处理过程对,具体的改造步骤如下:
这种方案的弊端会让网络数据包的体积更大一些
通过上述两步改造的目的都是为了尽量少出现类似下面的代码:
1 | int func(){ |
而是多出现这样的代码:
1 | int func(i){ |
要更好的做好「无状态」化的工作,基本依赖于在架构设计或者项目设计中的合理分层。尽量将会话状态相关的处理上浮到最前面的层,因为只有最前面的层才与系统使用者接触,如此一来,其它的下层就可以将「无状态」作为一个普遍性的标准去做。与此同时,由于会话状态集中在最前面的层,所以哪怕真的状态丢失了,重建状态的成本相对也小很多。比如三层架构的话,保证 BLL(业务逻辑层) 和 DAL(数据访问层) 都不要有状态,代码的可维护性大大提高
在这里,提到做分层的目的是为了说明,只有将 IO 密集型程序和 CPU 密集型程序分离,才是通往「无状态」真正的出路。一旦分离后,CPU 密集型的程序自然就是「无状态」了.如此也能更好的做「弹性扩容」。因为常见的需要「弹性扩容」的场景一般指的就是 CPU 负荷过大的时候
所以,最理想的状态存放点,要么在最前端,要么在最底层的存储层
¶运行有状态工作负载的挑战
运行有状态工作负载有多种挑战:
¶无状态服务
✨无状态服务特点
无状态服务的实例可能会因为一些原因停止或者重新创建(如扩容时)。这时,这些停止的实例里的所有信息(除日志和监控数据外)都将丢失(重启容器即会丢失)。因此如果您的容器实例里需要保留重要的信息,并希望随时可以备份以便于以后可以恢复的话,那么建议您创建有状态服务。
❓ 为什么网上主流的观点都在说要将方法多做成「无状态」的呢?
¶无状态服务是如何工作的
无状态架构意味着应用程序依赖于第三方存储,因为它不会在内存或磁盘上存储任何类型的状态。它请求的所有数据都必须从其他有状态服务(数据库)中获取或数据本身就存在于 CRUD 请求中
对于无状态应用,任何一个请求首先会发送到负载均衡器上,随后负载均衡器将请求负载到无状态服务的任何一个副本上,因为服务中的所有数据都存储在其他地方(通常是具有持久存储的数据库)所以不同副本上对于请求执行相同逻辑。由于状态信息与每个请求一起发送到服务器,服务器通过该服务器继续为请求提供服务。Load-balancer 不需要担心将请求路由到同一台服务器,真正实现了负载均衡。 其中 JSON Web Token (JWT) 广泛用于创建无状态应用程序
然而对于有状态应用是不同的,当有状态应用程序中的并发用户数量增加时,添加更多运行相同程序的服务器(横向扩展),并使用负载平衡器在这些服务器之间均匀分配负载。但是由于每个服务器「 记住 」每个登录用户的状态,因此有必要将负载均衡器配置为「粘性模式 」。粘性模式下,负载均衡器将每个用户的请求发送到响应该用户先前请求的同一台服务器。这违背了负载均衡的目的,此时就算对系统进行了水平扩展,也会由于粘性负载的关系,整个系统的负载并不均衡。
✨ 以下是无状态应用程序的 5 个主要优点:
¶无状态应用程序的最佳实践
无状态应用不惜一切代价避免 session,主要是以下几点原因:
如此也并不是说会话就是一个很糟糕的技术,会话仅对特定用例有用,例如 FTP(文件传输协议)。对于共享 Dropbox 等用例,有状态会话会增加额外的开销,而无状态则是完美的方式。会话功能使用 cookie 复制,在客户端缓存
¶为什么无状态服务很重要
如今倡导无状态简而言之就是为了让系统得到一种特性:伸缩性(scaliability)
❓ 以前有状态应用程序运行良好,为什么还需要无状态应用程序?
Facebook 不断使用无状态服务。当服务器使用 Facebook API 请求最近消息的列表时,它会发出一个带有令牌和日期的 GET 请求。响应独立于任何服务器状态,一切都以缓存的形式存储在客户端的机器上。类似地,调用 POST 命令,在不考虑服务器状态的情况下,在标头中传递带有授权/身份验证数据的复杂主体与上一个、当前和下一个请求没有关系。在无状态中,客户端不会等待来自服务器的同步。
¶如何采用无状态应用程序
⛵ 以下是采用无状态应用程序的 5 个步骤
¶总结
🆚 无状态服务和有状态服务主要有以下区别
无状态服务是指该服务的实例可以将一部分数据随时进行备份,并且在创建一个新的有状态服务时,可以通过备份恢复这些数据,以达到数据持久化的目的。有状态服务只能有一个实例,因此不支持「 自动服务容量调节 」。一般来说,数据库服务或者需要在本地文件系统存储配置文件或其它永久数据的应用程序可以创建使用有状态服务。要想创建有状态服务,必须满足几个前提:
有状态应用和流程是可以周而复始、反复发生的应用和流程,例如网上银行或电子邮件。这些操作是在先前的事务背景下执行的,当前事务可能会受到先前事务的影响。正因如此,有状态应用在每次处理用户的请求时都会使用相同的服务器。如果有状态事务被中断,其上下文和历史记录会被存储下来,这样就可以或多或少地从上次中断的地方继续。有状态应用会跟踪诸如窗口位置、设置首选项和近期活动等内容。我们可以把有状态事务视为与同一个人进行的定期对话。
有状态和无状态应用程序在互联网中无处不在,但是现代大部分软件是以无状态方式构建的,这是因为可伸缩性是如今大型系统非常重要的一个因素
¶无状态和有状态的容器管理
有状态现在已成为容器存储的主体,因此现在的问题从要不要使用有状态容器,变成了该如何使用这些有状态容器?
正因为这个原因,有状态应用越来越像无状态应用,无状态应用向有状态应用靠近的趋势,如:存在一个无状态的应用,它不需要长期存储,但允许服务器使用 Cookie 来跟踪同一客户端的请求。尽管无状态应用程序以不同的方式工作,但它们不会在服务器上存储任何状态。
它们使用 DB 来存储所有信息。DB 是有状态的,即它具有附加的持久存储。通常,用户请求使用凭据登录,LB 后面的任何服务器都会处理该请求,生成一个 auth 令牌,将其存储在 DB 中,然后将令牌返回给前端的客户端。下一个请求与令牌一起发送,现在,无论哪个服务器处理请求,它都会将令牌与数据库中的信息进行匹配,并授予用户登录权限。每个请求都是独立的,与前一个或下一个请求没有任何联系,就像 REST 一样。尽管无状态应用程序有一个额外的调用 DB 的开销,但这些应用程序在水平扩展方面非常出色,这对于可能拥有数百万用户的现代应用程序至关重要。
¶无状态主从集群设计
构建单机服务非常简单,但如果单机服务可靠性或性能不足,就需要多机器共同承担某项服务。集群化包含以下四种情况:
集群格式 | 含义 |
---|---|
无状态主备集群 | 仅有一台主机完成任务,且没有本地状态,其余从机机器待命,一旦主机宕机,从机选主成为主机 |
有状态主备集群 | 仅有一台主机完成任务,有本地状态,其余从机机器待命,一旦主机宕机,从机选主成为主机 |
无状态的主从集群 | 所有机器没有本地状态,理论上机器可以无限叠加,共同向外界提供同一服务。 |
有状态的主从集群 | 所有机器都有本地状态,共同向外界提供同一服务。一旦某台机器宕机,需要主机协调其他从机代理其本地状态的任务 |
¶结论
有时您必须构建有状态的服务,这不会自动损害您进行 SaaS 开发的准备。但是,您需要确保对有状态服务进行某种扩展,并计划备份和快速灾难恢复。虽然这几乎总是可能的,但工作量可能比在无状态微服务上获得更好结果所需的工作要多得多。
¶附录
RedHat
Stateful and Stateless Applications Best Practices and Advantages
分布式系统中的“无状态”和“有状态”详解
构建可伸缩的有状态服务
【高可用架构】理解有状态服务和无状态服务
Service statelessness principle
无状态服务 VS 有状态服务
PRAM consistency
Service statelessness principle
分布式基础 5-无状态主从集群设计