首页 > 解决方案 > 在特定时间阈值内组合相同请求的可扩展方式

问题描述

我有一个应用程序,称为服务 1,它可能会向另一个应用程序发出许多相同的请求,称为服务 2。例如,x 人使用服务 1 并导致 x 个请求(这是确切的相同的请求)到服务 2。每个响应都缓存在服务 1 中。

目前,我们有一个同步的方法来检查是否在某个时间阈值内发出了相同的请求。我们遇到的问题是,当服务器负载过重时,同步方法会锁定线程,kubernetes 无法执行活动检查,因此 kubernetes 会重新启动服务。我们想要防止重复请求的原因有两个:1)我们不想锤击服务 2,以及 2)如果我们已经发出请求,我们不想再次发出请求,只需等待结果已经回来了。

在不锁定和关闭服务器的情况下不发出重复请求的最快、最具可扩展性的解决方案是什么?

标签: javasynchronizationrx-javareactive-programmingscalability

解决方案


FWIW,我对 rx-java 的经验特别有限,所以我不完全相信这对你的情况有多适用。这是我在 Scala 中多次使用的解决方案,我知道 Java 本身确实有类似的构造,可以使用相同的方法。

我过去使用的一个对我来说效果很好的解决方案是使用 Futures。它并没有完全减少重复,但它确实删除了每个请求服务器的重复。该方法涉及使用 TTL 缓存,我们在其中存储了 Future 对象,该对象包含或将包含我们想要对其进行重复数据删除的请求的结果。它存储在一个键下,该键可以确定请求的唯一性,例如可能适用的不同参数。

因此,假设您有一个方法,您可以调用该方法从服务 2 获取响应并将其作为 Future 返回。作为一个例子,我们会说它getPage有一个参数,一个整数,它是你想要获取的页面。

当一个请求开始并且我们将要调用页码为 2 的 getPage 时,我们会检查缓存中是否存在像“getPage:2”这样的键。这不会包含第一个请求的任何内容,因此我们调用getPage(2)which 返回 a Future[SomeResponseObject]。我们将 TTL 缓存中的“getPage:2”设置为 Future 对象。当另一个可能产生重复请求的请求进入时,会发生相同的缓存检查,但是,Future缓存中已经有一个对象。我们得到这个未来,并添加一个响应监听器,当响应可用时调用,或者在 Scala 中,只需.map()在它上面。

这有几个优点。如果您的请求很慢,或者即使在很短的时间内也有高度重复的请求,那么对服务 1 的许多请求都由服务 2 的单个响应提供服务。

其次,一旦对服务 2 的请求返回,假设您有一个响应仍然有效的窗口,则响应已经立即可用,根本不需要请求。

如果您的服务 2 请求需要 50 毫秒,并且您的响应可以被视为有效 5 秒,则在前 50 毫秒内发生在同一台服务器上的所有请求都将在响应返回时的 50 毫秒得到服务,并且从那时起剩余的 4950 ms 已经可以访问响应。

正如我之前提到的,这里的有效性与正在运行的服务 1 实例的数量有关。任何时候重复请求的数量与正在运行的服务器数量成线性关系。

这是实现此目的的一种主要无锁方式。我看到的主要是因为 TTL 缓存本身需要一些同步来确保请求只启动一次,但根据我的经验,这从来都不是性能问题。


作为对此的扩展,如果服务 2 的响应时间较长,您可以潜在地使用类似 redis 的东西来缓存来自服务 2 的响应,并让您的getPage等价物首先检查 redis 缓存中的序列化响应(如果是,则写入一个过期值不在那里)。这允许您通过缓存更多全局值来进一步减少对服务 2 的请求,但是拥有第二个缓存层确实会增加一些复杂性和潜在的问题。


推荐阅读