左耳听风_046_45_弹力设计篇之服务的状态
你好,我是陈浩网名猪耳朵house.那这节课呢我们要讲的内容是服务的状态。
我们在之前讲过的幂等设计中呢提到过为了过滤已经处理过的请求。
那其中呢就需要保存处理过的状态。
那为了把服务做成无状态的呢,我们就引入了第三方的存储。
而这一讲中呢我们来聊一聊服务的状态。
这个话题我认为呢只有清楚的了解了状态,这个事儿我们才有可能设计出更好啊或者更有弹力的系统架构。
那所谓状态呢,就是为了保留程序的一些数据或者上下文。
比如之前密等性设计中所说的,需要保留每一次请求的状态,或者像用户登录时的session.我们呢就需要这个session来判断这个请求的合法性。
还有一个业务流程中,需要让多个服务组合起来,形成一个业务逻辑的运行上下文context.那这些呢都是所谓的状态,我们的代码中呢基本上到处都是这样的状态。
我们首先来谈一谈无状态的服务。
那一直以来呢无状态的服务都被当做分布式服务设计的最佳实践和铁律。
因为无状态的服务对于扩展性和运维来说啊,实在是太方便了。
没有状态的服务可以随意的增加和减少节点,同样呢可以随意的搬迁。
而且呢无状态的服务可以大幅度降低代码的复杂度和八个数。
因为没有状态,所以呢也没有明显的副作用。
那基本上来说呢,无状态的服务和函数式编程的思维方式啊如出一辙。
在函数式编程呢有一个铁律,就是函数是无状态的那换一句话说呢就是函数是mutable不变的。
所有的函数呢只描述逻辑和算法,根本不保存数据啊,也不会修改输入的数据,而是把计算好的结果啊返回出去。
哪怕要把输入的数据重新拷贝一份,并且只做少量的修改。
但是呢现世界里啊是一定会有状态的那这些状态呢可能会表现在这几个方面。
一个是程序调用的结果,一个呢是服务组合下的上下文,还有呢就是服务的配置。
那为了做出无状态的服务,我们通常需要把状态呢保存在其他的地方。
比如说不太重要的数据呢可以放到REDIS中。
那重要的数据呢可以放到mysql中啊,或者像zookeeper,或者ETCD这样的高可用的强一致性的存储中,或者呢是分布式文件系统中。
于是呢我们为了做成无状态的服务啊,会导致这些服务啊需要耦合第三方有状态的存储服务。
那一方面呢是有依赖,另一方面呢也增加了网络开销,导致服务的响应时间啊也会变慢。
所以呢第三方的这些存储符啊,也必须要做成高可用高扩展的方式。
而且呢为了减少网络开销,还需要在无状态的服务中啊增加缓存机制。
但是呢下次这个用户的请求啊并不一定会在同一台机器,所以这个缓存啊需要在所有的机器上都创建,那这个呢也算是一种浪费吧。
那这种转移责任的玩法呢也催生出了对分布式存储的强烈需求。
正如之前分布式系统架构本质系列的文章,我们谈到了其中一个关键的技术状态和数据调度。
在这里呢我们提到过,因为数据层的scheme有很多,所以呢就很难做出一个放置四海接准的分布式存储系统。
那这个呢也是为什么无状态的服务需要依赖于像zookeeper或者ETCD这样的高可用的,有强意志的服务,或者依赖于底层的分布式文件系统。
而现在分布式数据库呢也开始将服务和存储分离啊,也是为了让自己的系统啊更有弹力。
以上所说的呢是无状态的服务。
那接着呢我们来讲一讲有状态的服务。
在今天看来呢,有状态的服务啊看上去的确比较反动。
但是呢我们也需要比较一下它和无状态服务的优点。
正如前面所说的,无状态服务呢在程序bug上和水平扩展上有非常优秀的表现。
但是他需要把状态存放在一个第三方的存储上,这样呢增加了网络开销。
而在服务之内的缓存呢,需要在所有的服务实力上都有,那这个呢是比较浪费资源的。
而有状态的服务呢有这些好处。
第一呢是数据的本地化,一方面状态和数据呢是本机保存。
那这样呢不但有更低的延时,而且对于数据密集型的应用来说啊,那这样呢会更快。
那第二呢是有更高的可用性和更强的一致性啊,也就是CAP原理中的a和c那为什么会这样呢?因为对于有状态的服务啊,我们需要让客户端传来的请求都必须保证它落在同一个实例上。
那这个呢就叫sticky session啊或者sticky connection.那这样一来呢,我们就完全不需要考虑数据要被加载到不同的节点上去,而且这样的模型呢更容易理解和实现。
可见最重要的区别就是无状态的服务需要我们把数据同步到不同的节点上。
而有状态的服务呢需要通过stick session做数据的分片。
那这种sticky session呢是怎么实现的呢?那最简单的实现啊,就是用持久化的长连接,就算是HTP协议啊,也要用长连接。
或者呢通过一个简单的哈希算法啊,比如通过UID求模的方式走一致性哈希的玩法啊,也可以方便呢做水平扩展。
但是呢这种方式啊也会带来问题,那就是节点的负载和数据呢并不会很均匀,尤其是长连接的方式连上了就不断了。
所以这种常年性的玩法一般都会有一种反向压力。
也就是说呢如果服务端成为了热点,那么就主动断炼积累。
那当然这种玩法也比较危险,需要客户端的配合,否则呢容易出bug.那如果要做到负载和数据均匀,我们就需要有一个原数据索引来映射后端服务实例和请求的对应关系。
我还需要一个路由节点。
那这个路由节点呢会根据原数据索引来路由,而这个元数据索引表呢会根据后端服务的压力来重新组织相关的映射。
那当然呢我们也可以把这个路由节点给去掉,让有状态的服务啊直接路由要做到这一点呢。
一般来说啊有两种方式,一种呢是直接使用配置,在节点启动的时候,把原数据呢读到内存当中。
但这样一来呢,增加或者减少节点啊都需要更新这个配置,会导致其他节点呢也一同要重新读入。
那另一种比较好的做法呢是使用gossip协议。
通过这个协议啊,在各个节点之间互相散播消息来同步原数据。
那这样新增或者减少节点集身内部呢可以很容易的重新分配,在有状态的服务上做自动化伸缩,是有些相关的真实案例的,比如facebook的sccuba.那这个呢是一个分布式的内存数据库,它使用了静态的方式啊,也就是前面的第一种方式。
而uber的rainpop呢,是一个开源的,基于note GS的,根据地理位置分片的路由请求的库,还有微软的audience.那hello four呢,就是基于他开发的,他使用了gotip协议一致性哈希和DHT技术相结合的方式。
用户呢通过其ID的一致性哈希算法映射到一个节点上,而这个节点呢保存在这个用户对应的DHT,再通过DHT定位到处理用户请求的位置。
那这个项目呢,也是开源的开源地址啊,我在文中给你了。
那关于可扩展的有状态服务呢,这里我强烈推荐twitter的美女工程师kitty marcaret的演讲,youtube视频building salleable stateful service.那最后呢我们来聊一聊服务状态的容错设计。
在容错设计中呢,服务状态是件非常复杂的事儿。
尤其是对于运维来说,因为你要调度服务啊,就需要调度服务的状态,迁移服务的状态啊,就需要迁移服务的数据。
在数据量比较大的情况下,这一点呢就变得更为困难了。
虽然啊有状态的服务的调度,通过sticy session算是一种方式。
但是我依然觉得理论上来说啊,虽然可以这么干,但实际在运维的过程中啊,这么干还是一件挺麻烦的事儿,不是很好的玩法。
那很多系统的高可用设计呢,都会采用数据在运行时就复制的方案,比如做keeper卡FAREDIS或者elastic search等等。
那在运行时进行数据复制呢,就需要考虑一致性的问题。
所以呢强意志性的系统啊一般都会使用两阶段提交。
那这个呢就要求所有的节点都需要有一致的结果。
那这个呢就是CAP里的CA系统,而有的系统呢采用的是大多数人员一致啊就可以了。
比如pixel算法,那这个呢是CP系统。
但是我们需要知道,即使这样当一个节点挂掉了之后,在另外一个地方重新恢复这个节点的时候,那这个节点呢需要把数据同步过来才能提供服务。
但是如果数据量过大,那这个过程可能会很漫长,那这个呢也会影响我们系统的可用性。
所以呢我们需要使用底层的分布式文件系统,对于有状态的数据呢,不但要在运行时进行多节点间的复制。
同时呢为了避免挂掉,还需要把数据啊持久化在硬盘上。
呃,这个硬盘呢可以是挂载到本地硬盘的一个外部分布式的文件卷。
那这样呢当几点挂掉以后,在另外一个宿主机上启动一个新的服务实例的时候,那这个服务呢就可以从远程把之前的文件系统啊挂载过来。
然后在启动的过程中呢,就装载好了大多数的数据,从而可以从网络其他界面上同步少量的数据。
那因此呢就可以快速的恢复和提供服务。
那这一点呢对于有状态的服务来说啊非常关键。
所以使用一个分布式文件系统呢是调度有状态服务的关键。
好了,我们来总结一下今天分享的主要内容。
首先呢我讲了无状态的复务,无状态的服务呢就像是一个函数一样,它会给出唯一确定的输出。
那它的好处呢是很容易运维和伸缩,但是呢需要底层有分布式的数据库来支持。
那接着呢我讲了有状态的服务,他们通过sticking session、一致性哈希和DHT等技术实现状态和请求的关联,并将数据同步到分布式数据库中。
利用分布式文件系统呢还能在节点挂掉时啊快速启动新的实例。
那下节课呢我们会讲述补偿事务啊,希望能对你有帮助,也欢迎你来分享一下你所实现的分布式服务是无状态的还是有状态的,都用到了哪些技术呢?文末呢我给出了分布式系统设计模式系列文章的目录,希望你能在这个列表里啊找到自己感兴趣的内容。