在特定时间阈值内组合相同请求的可扩展方式

时间:2018-08-08 15:08:16

标签: java synchronization rx-java reactive-programming scalability

我有一个名为Service 1的应用程序,它可能对另一个应用程序发出很多相同的请求,称为Service2。例如,x个人使用Service 1并导致x个请求(与服务2完全相同的请求)。每个响应都缓存在服务1中。

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

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

1 个答案:

答案 0 :(得分:0)

FWIW,我对rx-java的经验非常有限,因此我不完全相信这对您的情况适用。这是我在Scala中使用过多次的解决方案,并且我知道Java本身确实具有允许使用相同方法的类似构造。

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

因此,假设您有一个调用的方法来从Service 2中获取响应并将其作为Future返回。作为示例,我们将说getPage,其中有一个参数,即整数,这是您要获取的页面。

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

这有一些优点。如果您的请求很慢,或者即使在很短的时间内仍然存在重复性很高的请求,则对服务1的许多请求都可以通过服务2的单个响应来处理。

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

如果您的服务2请求花费了50毫秒,并且您的响应被认为在5秒钟内有效,则返回响应时,在前50毫秒内发生在同一服务器上的所有请求都将在毫秒50处得到服务,并从该点开始剩下的4950毫秒已经可以访问响应了。

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

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


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