缓存高流量站点时处理并发问题

时间:2014-04-25 21:36:28

标签: performance caching web concurrency scalability

我在接受采访时被问到这个问题:

对于流量较大的网站,有一种方法(比如getItems())会被频繁调用。为了防止每次都进入DB,结果将被缓存。但是,成千上万的用户可能正在尝试同时访问缓存,因此锁定资源不是一个好主意,因为如果缓存已过期,则调用DB,所有用户都会等待DB回应。处理这种情况的好策略是什么,以便用户不必等待?

我认为这对于大多数高流量网站来说是一种非常常见的情况,但我没有处理这些问题的经验 - 我有使用数百万条记录的经验,但不是数百万用户。< / p>

如何学习高流量网站所使用的基础知识,以便我在未来的访谈中更有信心?通常我会开始一个侧面项目来学习一些新技术,但是不可能在旁边建立一个高流量的站点:)

6 个答案:

答案 0 :(得分:6)

在面试中你被问到的问题是所谓的Cache miss-storm - 一个场景,其中许多用户触发缓存的重新生成,以这种方式命中数据库。

为了防止这种情况,首先必须设置软到期日期。让我们说硬到期日是1天,软1小时。硬件实际上是在缓存服务器中设置的,软件位于缓存值本身(或缓存服务器中的另一个密钥)。应用程序从缓存中读取,看到软时间已到期,提前1小时设置软时间并点击数据库。通过这种方式,下一个请求将看到已更新的时间并且不会触发缓存更新 - 它可能会读取过时的数据,但数据本身将处于重新生成的过程中。

接下来的一点是:你应该有缓存预热的程序,例如:而不是用户触发缓存更新,这是您的应用程序中预先填充新数据的过程。

最糟糕的情况是当您没有任何数据时,重新启动缓存服务器。在这种情况下,您应该尽可能快地填充缓存,以及预热程序可能发挥重要作用的地方。即使你没有在缓存中拥有一个值,它也是一个很好的策略来锁定&#34;缓存(将其标记为正在更新),只允许对数据库进行一次查询,并通过在给定超时后再次请求资源来处理应用程序

答案 1 :(得分:2)

缓存未命中风暴或缓存踩踏效应,是缓存无效时向后端发出的突发请求。

我处理过的所有高并发网站都使用了某种缓存前端。 Bein Varnish或Nginx,它们都具有微细和踩踏效应抑制。

只需google获取Nginx微缓存或Varnish踩踏效果,您就会发现大量现实世界的例子和解决方案来解决这类问题。

所有这些都可归结为当处于更新或过期状态时,您是否允许请求通过缓存到达后端。

通常可以主动刷新缓存,将所有请求保存到更新条目,然后从缓存中提供它们。

但是,总有一个问题“你应该使用什么样的数据进行缓存”,因为,你看,如果它只是纯文本文章,它得到编辑/更新,延迟缓存更新不是如果你的数据应该在数千个显示器上实时显示(实时游戏,金融服务等),那就太有问题了。

所以,正确的答案是,微型,抑制踩踏效应/缓存未命中风暴,当然,知道哪些数据要缓存时间,方式和原因。

答案 2 :(得分:1)

仅当数据使用者准备好获得陈旧日期(在合理范围内)时,才考虑缓存的特定数据类型。

在这种情况下,您可以定义无效/逐出/更新政策,以使您的数据保持最新(商业意义)

在更新时,您只需替换缓存中的数据项,所有新请求都将使用新数据进行响应

示例:股票信息系统。如果您不需要实时价格信息,保留缓存库存并通过昂贵的远程调用每隔X密耳/秒更新一次是合理的。

答案 3 :(得分:1)

你真的需要使缓存过期吗?您是否可以使用增量更新机制,使用该机制可以定期递增数据,这样您就不必使数据过期,而是定期刷新数据。

其次,如果你想防止太多用户一次性地追踪数据库,你可以在你的存储过程中有一个锁定机制(如果你的数据库支持它),这可以防止太多人同时访问数据库。此外,您可以在数据库中使用缓存机制,以便如果有人再次从数据库中请求完全相同的数据,您始终可以返回缓存值

某些应用程序还在应用程序和数据库之间使用第三个服务层来保护数据库免受此方案的影响。服务层确保您没有db

中的缓存未命中风暴

答案 4 :(得分:1)

答案是永远不会使缓存过期并定期拥有后台进程更新缓存。这可以避免等待和缓存未命中风暴,但为什么在这种情况下使用缓存呢?

如果您的应用程序因“缓存未命中”情况而崩溃,那么您需要重新考虑您的应用程序以及缓存所需的内存数据。对我来说,我会使用一个内存数据库,它在数据更改时定期更新或定期更新,而不是根本不更新,并避免上述情况。

答案 5 :(得分:1)

根据您的访问模式,您可能更好地使用某些分布式缓存存储库,memcached或其他存储库。 如果要将值存储在应用程序中,可以使用Cache implementation of Google's Guava library。 从编码的角度来看,你需要像

这样的东西
public V get(K key){
    V value = map.get(key);
    if (value == null) {
        synchronized(mutex){
            value = map.get(key);
            if (value == null) {
                value = db.fetch(key);
                map.put(key, value);
            }
        }
    }
    return value;
}

其中地图是ConcurrentMap,而互斥锁只是

private static Object mutex = new Object();

通过这种方式,每个丢失密钥只需要一个数据库请求。

希望它有所帮助! (并且不存储null,你可以创建一个墓碑值!)