乐观缓存并发设计模式

10

我有一个在服务器集群上运行的Web服务。这个Web服务会进行一些内部处理,然后可能会调用一个产生费用的外部服务。

我想要增加一些缓存,以便如果接收到相同的服务请求(这是肯定会发生的),那么就不必重复处理,既节省了处理时间/功率,也节省了在调用服务时产生的成本。

但是,在以下限制条件下如何管理此缓存我感到很困难:

  • 为了高可用性和可扩展性,该服务在多个Web服务器上运行
  • 该请求可能需要5秒钟才能响应,但在此期间,我可能已经收到2或3个相同的请求。

当在分布式环境中工作时,我该如何延迟执行其他服务调用,直到第一个已响应(因此可用于缓存)?

我考虑过使用前置代理模式并在代理中建立相同请求队列,这样当第一个请求返回时,它也可以将相同的响应返回给其他请求。这是正确的模式吗,还是有更好的并发模式来处理这种情况?


外部的 Web 服务也被集群化了吗?你对它有控制权吗?看起来它是一个很好的起点,你可以在它前面编写自己的包装器缓存 Web 服务。 - guido
这不在我的控制范围内。这是我们从供应商那里调用的服务。 - Codemwnci
就像在每个缓存场景中一样:你确定服务真的是无状态的吗?例如,“FetchCustomerDetailById”服务是不可缓存的,因为中间的“ChangeCustomerName”会使你的缓存失效。 - A.H.
啊不。它确实是无状态的。每个对外部调用的请求,如果给定相同的输入,则应保证相同的响应。 - Codemwnci
2个回答

6
您可以:
  1. 计算请求的加密哈希值
  2. 检查该哈希值是否已存在于数据库中,并返回结果
  3. 将哈希值与“待定结果”状态存储在数据库中
  4. 调用网络服务并更新数据库中的行结果。

第二步中,如果哈希值已经存在于数据库中,并且状态为“待定结果”,则您可以每X毫秒轮询数据库,并在结果可用时最终返回结果。

当然,魔鬼在于细节,因为您需要决定出现错误时应采取的措施:

  • 对于所有后续相同的请求,您是否返回错误?
  • 是让等待线程重试调用网络服务吗?
  • 还是只返回错误,但仅限一段时间,然后重试?

我也想了解这些负评的原因。2个赞成票(其中一个是我的),2个反对票,但没有任何解释?你的解决方案有什么不好的地方,比起@fyr的呢? - Codemwnci
据我所知,fyr的解决方案是每个服务器都有一个缓存,并且避免使用数据库,因为它是单点故障。我假设您已经拥有一个中央数据库,因为99.9%的应用程序都有一个。因此,我设计了一种解决方案,所有服务器都将使用此中央数据库作为持久性缓存。 - JB Nizet
我认为这个方案会很好用。我们有一个非常强大的后端数据库,因此这种方法将最大限度地利用我们可用的资源。 - Codemwnci
@Codemwnci,这个解决方案也没有什么“不好”的地方,它基本上与其他解决方案非常相似。这个只是描述了一个非常技术性的解决方案,而我的则更加抽象。这也意味着JB Nizet描述了一个具体的解决方案,而我的则更加模糊,因为在缓存效率方面,它总是取决于系统的结构。对于每个系统来说,都没有完美的解决方案。 - fyr

2

1.) 该服务在多个Web服务器上运行,以实现高可用性和可扩展性。

将其简单地视为设计限制。这意味着不要在缓存查找方法中包含主机名。只要结果不依赖于主机,您就不会有问题。如果在具有相同服务和参数的HA环境中,hostA与hostB返回不同的内容,则我会认为这是设计缺陷。

如果您想保持系统冗余,则不应该有中央缓存。因为大多数情况下,“中央”解决方案是“单点故障”的代名词。如果您在应用程序服务器之间进行同步,则锁定也更加复杂。

您引入多少个缓存有些取决于缓存命中率和系统上可用的资源。最简单的解决方案是在每个服务实例级别上进行缓存。

2.) 请求可能需要最多5秒钟才能响应,但在此期间,我可能已经收到2或3个完全相同的请求。

这也是设计限制。您只需将缓存分为2个不同的步骤:

  1. 如果第一个线程进入缓存例程,请首先插入相同请求的键并锁定其值的访问权限
  2. 在处理完成并释放锁定后插入该值

但您还需要处理异常情况。

lockingconnection机制可以使用不同的策略进行实现

  • 同步-您只需创建一个互斥/信号量或其他内容,并锁定对关键部分的访问。这可能会导致一些请求处于等待状态,直到锁定消失
  • 异步-您可以实现某种轮询机制,如果线程遇到已锁定的关键部分(如同步处理中),则会生成一条消息,表示数据未准备好。这不会导致许多打开的连接,但会引入更多的复杂性。

您用于锁定对关键部分的访问权限的Mutex / Semaphore或其他结构可能取决于您为相同请求计算的唯一键(只要您不想串行访问您的服务)。


绝对的,所有主机将返回相同的结果。它们仅在不同节点上进行拆分以实现高可用性/可扩展性。但它产生的限制是我们不再处于串行领域,而是并行处理领域。因此,您建议使用锁定机制的中央缓存,可由所有节点访问。 - Codemwnci
缓存代理应该在负载均衡器之前;如果你让请求通过,那么你不妨让它运行,而不是让它在节点上等待。 - guido
@Codemwnci,我已经调整了我的帖子以回答这个问题。 - fyr
@guido 如果您将缓存机制放在负载均衡器之前,那么您不会失去高可用性吗? - Codemwnci

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接