关键词总结:服务的状态、无状态的服务 Stateless、有状态的服务 Stateful
服务的状态
“状态”,就是为了保留程序的一些数据或是上下文。比如幂等性设计中所需要保留每一次请求的状态;用户登录时的 Session,需要这个 Session 来判断这个请求的合法性;业务流程中多个服务组合起来形成业务逻辑的运行上下文 Context。
无状态的服务 Stateless
无状态的服务是分布式服务设计的最佳实践和铁律。无状态的服务最容易进行添加和删除,便于扩展和运维。没有状态,所以也没有明显的“副作用”。
无状态的服务和“函数式编程”的思维方式如出一辙。函数式编程中,函数是无状态的,函数只描述其逻辑和算法,根本不保存数据,也不会修改输入的数据,而是把计算好的结果返回出去。
现实世界是一定会有状态的,这些状态可能有如下表现。
状态的表现形式
- 程序调用的结果。
- 服务组合下的上下文。
- 服务的配置。
为了做出无状态的服务,通常需要把状态保存到其他的地方。比如,Redis(不太重要的数据)、MySQL(重要的数据)或者 ZooKeeper/Etcd 或者 分布式文件系统。会导致这些服务需要耦合第三方有状态的存储服务(服务无状态,将状态转移到了第三方的存储中)。要求第三方的存储服务也必须要是高可用高扩展的。数据层的可选方案众多,所以,很难做出一个放之四海皆准的分布式存储系统。
无状态服务在程序 Bug 上和水平扩展上表现非常优秀,但是需要把状态存放在一个第三方存储上,增加了网络开销,服务内的缓存需要在所有的服务实例上都有(每次请求不会都落在同一个服务实例上),这是比较浪费资源的。
有状态的服务 Stateful
相对于无状态的服务有状态的服务有这些好处:
- 数据本地化(Data Locality)。 状态和数据是本机保存,延时更低速度更快
- 更高的可用性和更强的一致性。 CAP 理论中的 A 代表了可用性,C 代表了一致性。而有状态的服务本身就符合这两个条件。分布式服务中常见的两种是 AP(高可用) 和 CP(强一致),但鱼与熊掌不可兼得。
原因是: 有状态的服务,对于客户端传来的请求,都必需保证其落在同一个实例上,这叫 Sticky Session 或是 Sticky Connection(粘滞会话/会话保持)。这样完全不需要考虑数据要被加载到不同的节点上去。
无状态的服务: 把数据同步到不同的节点上。有状态的服务:通过 Sticky Session 做数据分片(同步有同步的问题,分片也有分片的问题,这两者没有谁比谁好,都有 trade-off)
Sticky Session 是怎么实现的
- 最简单的实现就是用持久化的长连接
这种方式会节点的负载和数据并不会很均匀,会出现服务端成为热点的问题,这时需要通过客户端进行断开操作(需要客户端的配合,否则容易出 Bug)。
- 比较好的做法是使用到 Gossip 协议
可参看 P2P 网络核心技术:Gossip 协议
通过这个协议在各个节点之间互相散播消息来同步元数据,新增或减少节点,集群内部可以很容易重新分配
服务状态的容错设计
运行时复制
在运行时进行数据复制,需要考虑一致性的问题,强一致性的系统一般会使用两阶段提交。运行时复制的方案有:Zookeeper、Kafka、Redis 或者 Elasticsearch 等。要求所有的节点都需要有一致的结果,这是 CAP 里的 CA 系统。
大多数人一致
比如 Paxos 算法,这是 CP 系统,系统采用的是大多数人一致就可以了。即使是这样,当一个节点挂掉了以后,其他地方重新恢复这个节点时,需要把数据同步过来才能提供服务(这个数据同步可能会很漫长)。需要使用底层的分布式文件系统。
分布式文件系统
有状态的数据不但在运行时进行多节点间的复制,同时为了保证挂掉后恢复,还需要把数据持久化在硬盘上,硬盘可以是挂载到本地硬盘的一个外部分布式的文件卷。节点挂掉重启新的服务实例时,这个服务可以从远程把之前的文件系统挂载过来,在启动的过程中就装载好了大多数的数据,只需要从网络其它节点上同步少量的数据就可以快速恢复和提供服务。使用一个分布式文件系统是调度有状态服务的关键。
参考资料:
左耳听风(极客时间)链接:
http://gk.link/a/10f5D
GitHub链接:
https://github.com/lichangke/LeetCode
CSDN首页:
https://me.csdn.net/leacock1991
欢迎大家来一起交流学习