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

13

我在面试中遇到了这个问题:

对于一个高流量的网站,有一个方法(比如getItems()),该方法经常被调用。为了避免每次都去访问数据库,结果被缓存起来。然而,成千上万的用户可能会同时尝试访问缓存,所以锁定资源不是一个好主意,因为如果缓存已过期,将会向数据库发出调用,并且所有用户都需要等待数据库响应。那么如何处理这种情况,使得用户无需等待呢?

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

我该如何学习高流量网站使用的基础知识,以便在未来的面试中更加自信呢?通常,我会启动一个旁边的项目来学习一些新技术,但是在旁边建立一个高流量的网站是不可能的 :)


1
apachebench(ab)是一个串行请求工具,用于生成流量。此外,Grindr、Wkr和其他几个实现并行流量生成器的工具可以用来模拟高流量的网站。 - Marcel
6个回答

18
您在面试中遇到的问题被称为"缓存未命中风暴",即很多用户触发了缓存重新生成,从而访问数据库。
为了防止这种情况,首先必须设置软和硬过期日期。假设硬过期日期为1天,软过期日期为1小时。硬过期日期实际上是在缓存服务器中设置的,而软过期日期则是在缓存值本身(或缓存服务器中的另一个键)中设置的。应用程序从缓存中读取,在发现软时间已经过期时,将软时间设置为1小时,并访问数据库。这样,下一个请求将看到更新后的时间,并不会触发缓存更新——它可能会读取旧数据,但数据本身将处于再生过程中。
下一点是:您应该有缓存预热的程序,例如,而不是由用户触发缓存更新,应用程序中的进程预先填充新数据。
最糟糕的情况是,例如重启缓存服务器时,您没有任何数据。在这种情况下,您应该尽快填充缓存,这就是预热程序可能发挥关键作用的地方。即使缓存中没有值,"锁定"缓存(标记为正在更新),只允许查询一次数据库,并通过在给定超时后再次请求资源来处理应用程序。

2

如果您想要更好地使用分布式缓存库,例如Memcached或其他适合您的访问模式的库。

如果您想要在应用程序中存储值,则可以使用Google Guava库的缓存实现

从编码角度来看,您需要类似以下代码:

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;
}

这里的map是一个ConcurrentMap,而mutex只是个锁。

private static Object mutex = new Object();

这样,你每个缺失的键只需要向数据库发出一个请求。

希望对你有所帮助!(不要存储null值,可以创建墓碑值代替!)


2
缓存未命中风暴或缓存奔溃效应是指在缓存失效时向后端发送请求的突发性。所有高并发网站都使用某种形式的缓存前端,无论是Varnish还是Nginx,它们都具有微缓存和抑制奔溃效应的功能。只需搜索Nginx微缓存或Varnish奔溃效应,您就会找到大量实际问题的解决方案。这归结于当缓存处于更新或过期状态时是否允许请求通过缓存到达后端。通常可以主动刷新缓存,将所有请求保持在更新条目上,然后从缓存中提供服务。但是,总有一个问题:“您应该缓存哪种数据或不缓存哪种数据”,因为如果只是普通的文本文章进行编辑/更新,则延迟缓存更新并不像您的数据应该准确显示在成千上万个显示器上(实时游戏、金融服务等)那样有问题。因此,正确的答案是:微缓存,抑制奔溃效应/缓存未命中风暴,当然,还要知道何时、如何以及为什么缓存哪些数据。

1

如果数据使用者准备好接收过时的数据(在合理的范围内),那么仅考虑特定数据类型进行缓存会更糟。

在这种情况下,您可以定义失效/驱逐/更新策略,以使您的数据保持最新状态(在业务意义上)。

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

例如:股票信息系统。如果您不需要实时价格信息,则合理地在缓存中保存股票,并每X毫秒/秒使用昂贵的远程调用进行更新。


1
你真的需要清除缓存吗?你可以使用增量更新机制,定期递增数据,这样就不必清除数据,而是定期刷新它。
其次,如果你想防止太多用户同时访问数据库,你可以在存储过程中使用锁定机制(如果你的数据库支持),以防止太多人同时访问数据库。此外,你可以在数据库中使用缓存机制,以便如果有人再次从数据库请求完全相同的数据,你可以始终返回缓存值。
一些应用程序还使用第三个服务层,在应用程序和数据库之间保护数据库免受此场景的影响。服务层确保你没有缓存未命中风暴。

1
答案是永不过期缓存,并有一个后台进程定期更新缓存。这避免了等待和缓存未命中风暴,但在这种情况下为什么要使用缓存呢?
如果您的应用程序在“缓存未命中”场景中崩溃,则需要重新考虑您的应用程序以及缓存与所需的内存数据之间的区别。对于我来说,我会使用一个内存数据库,在数据更改或定期时更新它,而不是使用任何缓存,并避免上述情况。

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