在服务实例之间划分内存中的数据



最近在一次系统设计面试中,我被问到一个问题,城市被划分为几个区域,大约有100个区域的数据可用。api将zoneid作为输入,并返回该区域的所有餐厅作为响应。api的响应时间为50ms,因此区域数据保留在内存中以避免延迟。

如果区域数据大约为25GB,那么如果服务扩展到5个实例,则需要125GB的ram。

现在的要求是运行5个实例,但只使用25 GB的ram,并且在实例之间进行数据分割。

我相信要实现这一点,我们需要第二个应用程序,它将充当配置管理器来管理哪个实例保存哪个区域数据。实例可以从配置管理器服务中获取启动时要跟踪的区域。但我无法弄清楚的是,我们如何将对区域的请求重定向到保存其数据的正确实例,尤其是如果我们使用kubernetes。此外,如果持有部分数据的实例重新启动,那么我们如何跟踪它持有的区域数据

在多个节点上拆分数据集:听起来像是分片。

记忆中:面试官可能在问redis或类似的问题。

也许是这样:https://redis.io/topics/partitioning#different-分区的实现

Redis集群可能适合——请记住,当文档提到";客户端分区":客户端是一些redis客户端库,由后端加载,响应HTTP客户端/最终用户请求


回答您的评论:那么,我不确定他们在寻找什么。

将Java哈希映射与redis集群进行比较并不完全公平,因为其中一个绑定到JVM,而另一个实际上是分布式/分片的,这意味着至少要进行进程间通信,最有可能进行网络/非本地查询。

再说一遍,如果问题是要扩展一个不断增长的JVM:在某个时候,我们需要解决房间里的大象:如何保证数据一致性、正确的复制/分片,当成员宕机时该怎么办。。。?

使用Hazelcast的分布式哈希图可能更相关。有些人(hazelcast)认为它在繁重的写负载下更安全。其他人认为,从Hazelcast迁移到Redis有助于提高服务可靠性。我自己没有足够的Java背景,我不知道。

一般来说:当被问及Java时,你可能会说速度和可靠性在很大程度上取决于你的开发人员对他们正在做的事情的理解。在Java中,这意味着有很大的误差。而我们可以假设:如果他们问这样的问题,他们的工资单上可能有一些优秀的开发人员。

而分布式数据库(内存、磁盘、SQL或noSQL)。。。是一个相当复杂的主题,您需要掌握(在java之上)才能正确处理。

Adya在2019年将他们描述的广泛方法描述为LInK商店。Linked In memory键值存储允许支持丰富操作的应用程序对象在实例集群中进行分片。

我倾向于通过使用Akka实现一个有状态的应用程序来实现这一点(免责声明:在撰写本文时,我受雇于Lightbend,该公司雇佣了Akka的大多数开发人员,并为使用Akka的客户提供支持和咨询服务;正如我的SO历史所表明的,即使在我被Lightbend雇佣多年之前,我也会采用同样的方法)。

  • Akka集群允许一组运行应用程序的JVM以对等方式形成集群,并管理/跟踪成员身份的变化(包括检测已崩溃或被网络分区隔离的实例)

  • Akka Cluster Sharding允许由ID键控的有状态对象在集群中大致均匀分布,并根据成员身份更改进行重新平衡

这些有状态对象被实现为参与者:它们可以更新状态以响应消息,并且(因为它们一次处理一个消息),而不需要复杂的同步。

集群分片意味着负责一个ID的参与者可能存在于不同的实例上,因此这意味着集群之外的区域状态的某种持久性。为了简单起见,当负责给定区域的参与者启动时,它会从数据存储中初始化自己(可以是S3,可以是Dynamo或Cassandra或其他什么):在此之后,它的状态在内存中,因此可以直接从参与者的状态进行读取,而不是去底层数据存储。

通过引导所有写入通过集群分片,内存中的表示形式根据定义与写入保持同步。在某种程度上,我们可以说应用程序就是缓存:后备数据存储的存在只是为了让缓存能够在操作问题中幸存下来(因为只有在应对此类问题时,才需要读取数据存储,所以我们可以优化数据存储以进行写入与读取)。

集群分片依赖于无冲突的复制数据类型(CRDT)来将分片分配中的更改广播到集群的节点。例如,这允许任何实例处理任何碎片的HTTP请求:它只需将请求的重要部分的表示作为消息转发给碎片,碎片会将其分发给正确的参与者。

从Kubernetes的角度来看,实例是无状态的:不需要StatefulSet或类似的东西。pods可以查询Kubernetes API来查找其他pods并尝试加入集群。

*:我之前有一个相当坚定的观点,即事件来源是一种更好的持久性方法,但我现在将把这一点放在一边。

最新更新