可扩展的有状态服务

 2024-02-23 01:02:16  阅读 0

无状态服务( )一直被视为架构设计中的一条铁律,因为无状态服务可以轻松水平扩展,并且只需在负载均衡后通过添加节点就可以处理更多的请求。 然而,无状态服务并不完美。 缺点之一是数据层之间的请求延迟以及通过添加缓存来解决这种延迟带来的复杂性和一致性问题。

你有没有想过引入“有状态服务”()来解决这个问题? 如果你上网查一下,你会发现很少有人提到有状态服务,甚至没有一个条目。

是的,高级工程师。 她在会上发表了主题为“构建可扩展的状态服务”的演讲。 她不仅阐明了有状态服务的基本概念,还列举了微软、Uber等很多使用有状态服务的公司。 有一个案例可以作为证据。 也许她的演讲能给我们带来一些启示。

在继续阅读之前,需要了解一些基本的分布式系统概念。 如果你不熟悉这些概念,你不妨自己尝试一下:

我们先回顾一下众所周知的无状态服务:将所有数据放入数据库,可以在服务层进行水平扩展; 但是当流量不断增加的时候,总有一天会突破数据库处理能力的限制,所以我们对数据库进行shard()划分,或者转向NoSQL数据库,希望以牺牲强一致性为代价来提高可用性。 这时候就难免有些数据库逻辑会渗透到应用层。

另一方面,我们考虑无状态服务的流程:用户向服务层发送请求,处理请求的服务层节点A从数据库加载数据并返回给用户。 当用户发送下一个请求时,它可能会被分配给另一个请求。 A服务节点B。节点B需要去数据库重新加载数据。 此时,节点A的数据被丢弃。 这种重复加载数据的过程其实是非常浪费的,尤其是对于频繁交互的应用程序(比如游戏和一些交互的网络应用程序)。

的好处

首先要强调的是,它不是万能药。 在大多数场景下,我们仍然需要无状态服务和水平可扩展性。 但有状态服务确实有两个明显的优势:

数据(数据本地化)。 在有状态服务中,当客户端第一次发出请求时,服务仍然需要从数据库加载数据,但此后数据将保存在节点本地,并且该客户端的下一次请求必须由同一个节点处理。 这种模式节省了大量的数据网络传输。 即使数据库服务停止了,服务节点仍然可以独立处理请求。

图有状态服务具有更高的可用性(A)和更强的一致性(C)。 在CAP的三个属性中,我们只能选择CP系统或AP系统。 如果选择AP,就必须在一致性上做出让步。 但这种让步本身也有一定程度的区别。 对于有状态的服务,由于采用了(即一个用户的数据在固定的节点上),一致性会更强。 见下图(各个级别的一致性请自行检查):

一致性hash代码_一致性hashjava实现_一致性哈希代码

此外,还有另一个工程上的好处。 对于开发分布式系统的工程师来说,无需担心将数据加载到不同节点和缓存级别带来的一致性问题。 用户仅与一台服务器交互。 这种模型更容易理解,也更容易编程。

如何实现?

如何实现?

第一个明显的方法是长连接(HTTP 或 TCP)。 这种方法简单、容易实现,但缺点是不稳定。 一旦连接断开,就结束了。 另外,由于每个用户产生的负载不一样,一些节点会承受过大的负载(热点节点)。 因此,必须实施反向压力(Back)。 当某个节点无法承受负载时,可以有选择地断开该节点。 打开一些连接并让一些用户连接到其他负载较轻的节点。

更好的方法是实现集群内路由。 客户端仍然可以连接到任何节点,该节点负责将请求路由到包含用户数据的节点。 为了实现这个解决方案,集群需要具备两个能力:

一致性hashjava实现_一致性哈希代码_一致性hash代码

集群所有权

可以有多种类型:静态系统和一致性系统。

静态系统是最简单的。 您可以使用配置文件来维护每个节点的地址,然后将该文件部署在集群中的每个节点上。 缺点也很明显:运维困难、扩展困难。 当节点宕机或添加节点时,必须更新配置并重新启动整个集群。 这对于需要高可用性的服务来说是不可接受的。 动态集群所有权可以增加运维的灵活性。 该协议允许每个节点相互传播消息以维护集群状态。 每个节点都有自己独立的世界状态,并根据接收到的消息进行更新。 达到稳定状态后,所有节点将具有一致的世界状态。 但当出现网络中断、增加或减少节点时,节点的本地World State就会出错。 这涉及到一个设计权衡:因为不需要一致性协议的协调过程,所以这种方式更可用,但代价是代码需要处理路由到错误节点所带来的不确定性。 一致性协议。 目前有很多开源的一致性协议实现,例如/Etcd等,一致性系统可以保证集群所有权信息的准确性和及时更新,但问题是协调效率较低。 因此,除非绝对必要,否则不建议使用此解决方案。

负荷分配

为集群中的节点分配工作大致有以下三种方式:

1. 随机分配到任意节点。 这样,写请求可以由任何节点处理,但读请求需要向每个节点发出查询。 严格来说,情况并非如此,但在实践中往往效果很好。

2. 一致性哈希。 通过or的Hash值可以确定处理节点。 一致性哈希会将请求ID映射到一个圆上,在圆上顺时针移动,遇到的第一个节点就是处理该请求的节点(具体实现请查看)。 这种分配方式是确定性的,它带来的问题是节点热点:很多请求可能会分配到一个节点,导致节点过载。 由于确定性分配方法,负载无法转移到其他节点。 因此,每个节点的计算资源必须留有足够的(),以减少过载的可能性。

一致性哈希代码_一致性hashjava实现_一致性hash代码

3.分布式。 使用分布式节点来维护和查询请求分配。 这样,当一个节点宕机或过载时,负载可以改变并重新分配给其他节点。

真实案例场景

水肺潜水

一致性哈希代码_一致性hashjava实现_一致性hash代码

Scuba 是一个分布式内存数据库实现。 它采用静态集群所有权,负载分配采用随机分布策略,读请求需要查询集群中的每个节点。 因为在真实环境中,不可能保证所有节点同时在线,因此用户的读请求可能不会返回所有数据。 他们的做法是返回查询到的数据以及数据的完整性(百分比),这是由客户端决定的。 这些数据足够吗?

优步

一致性哈希代码_一致性hashjava实现_一致性hash代码

它是一种基于节点的应用层分片协议。 如果你仔细思考Uber的服务,你会发现它非常适合基于地理位置的分片处理,将某个位置的用户请求发送到固定的节点,并在这个节点上维护行程信息。 使用协议来维护集群所有权并使用一致的哈希来分配负载。 为了避免出现热节点问题,每个节点必须有足够的处理能力。

一致性hashjava实现_一致性哈希代码_一致性hash代码

它是一个基于Actor的分布式系统运行时,游戏《光环4》就是基于它开发的。 使用协议来维护归属信息,负载分配使用一致性Hash和DHT的组合。 用户的ID通过一致性哈希映射到节点。 该节点保存了用户对应的DHT,然后查询DHT来定位处理用户请求的Actor位置。 对这个项目感兴趣的人,尤其是从事游戏开发的人,可以关注这个开源项目。 您可以在文章末尾看到参考链接。

需要注意的问题

不受限制的数据结构

注意不要使用无约束队列和内存数据结构,有状态服务更容易出现问题,或者垃圾收集器也会出现问题。 在无状态服务中,许多数据结构随着请求的生命周期而产生和消失。 请求完成后内存会被垃圾回收,所以即使一段时间内内存不足也不会产生太大的影响。 但在有状态服务中,很多时间很长,会积累很多数据,代码必须防范这种可能。

内存管理

和上一篇一样,因为很多数据会长期驻留在内存中,这会对垃圾回收机制产生很大的影响:对于垃圾回收器来说,回收very old ()内存的成本相对来说是比较低的。高的。 您需要调整垃圾收集器的性能,或者干脆使用不需要GC的技术(例如C++)。

重新加载数据

大多数时候,有状态服务不需要重新加载数据。 有以下三种例外情况:

第一次连接。 此时加载数据往往比较耗时,而且所有数据都要从数据库中读取,所以为了用户体验,不要在第一次请求时加载太多数据; 另外,即使第一次请求加载数据超时,也不要停止加载,因为客户端总会再次连接,而第二次连接的响应速度会很快,因为数据已经读入本地内存了。 崩溃恢复,此时可能需要预加载一些数据。 部署新代码。 每次部署新代码时,都需要重新启动服务并加载所有数据。 对于使用确定性负载分布的场景来说,这可能是一个问题。 这里值得一提的是部署新代码时的一个巧妙技巧。 由于数据量巨大,重启服务后加载数据可能需要几个小时,这对于快速启动新代码非常不利。 他们采用了一种分离内存和进程生命周期的方法:当部署新代码时,原来的进程停止服务,开始向共享内存传输数据,然后退出。 新进程启动后,可以直接复制共享内存。 进入该计划并在几分钟内快速开始服务。 总而言之,关于集群所有权和负载分配策略,不存在“最佳”解决方案,需要根据需求进行权衡。 虽然这是一个相对较新的概念,但不必害怕尝试。 许多知名企业都有成功的经验。 阅读更多论文。 事实上,所讨论的许多概念并不是最近发明的。 本次演讲中的许多解决方案都来自 20 世纪 60 年代和 1970 年代的论文。 它们都是已经解决的问题。 不要重新发明轮子。

参考

/观看?v=

/会谈

时代的案例 - 高 -

案例

//4569/scuba--进入数据-/

超级节点

- -

/

对于使用 open 的游戏

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码