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

Posted

技术标签:

【中文标题】处理高流量站点缓存时的并发问题【英文标题】:Dealing with concurrency issues when caching for high-traffic sites 【发布时间】:2014-06-11 18:41:09 【问题描述】:

我在一次采访中被问到这个问题:

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

我认为这是当今大多数高流量网站的常见情况,但我没有处理这些问题的经验——我有处理数百万条记录的经验,但不是数百万用户。

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

【问题讨论】:

apachebench (ab) 是一个产生流量的串行请求工具。此外,grindr、wkr 和其他几个实现了可用于模拟高流量网站的并行流量生成器。 【参考方案1】:

Cache miss-storm 或 Cache Stampede Effect,是缓存失效时对后端的请求突发。

我处理过的所有高并发网站都使用了某种缓存前端。无论是 Varnish 还是 nginx,都具有微缓存和踩踏效应抑制。

只需在谷歌上搜索 Nginx 微缓存或 Varnish 踩踏效果,您就会找到大量此类问题的真实示例和解决方案。

一切都归结为当后端处于 Updating 或 Expired 状态时,您是否允许请求通过缓存到达后端。

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

但是,总是有“你应该缓存什么样的数据”的问题,因为,你看,如果它只是纯文本文章,它会得到编辑/更新,延迟缓存更新不是比您的数据准确地显示在数千台显示器(实时游戏、金融服务等)上更成问题。

所以,正确的答案是,微缓存,抑制踩踏效应/缓存未命中风暴,当然还要知道何时、如何以及为什么缓存哪些数据。

【讨论】:

【参考方案2】:

您可能会更好地使用一些分布式缓存存储库,如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();

通过这种方式,每个缺失的键只有一个对数据库的请求。

希望对您有所帮助! (并且不要存储空值,您可以创建一个墓碑值!)

【讨论】:

【参考方案3】:

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

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

【讨论】:

【参考方案4】:

您真的需要使缓存过期吗?您是否有一个增量更新机制,您可以使用它始终定期增加数据,这样您就不必使数据过期,而是继续定期刷新它。

其次,如果您想防止太多用户一次性访问数据库,您可以在存储过程中设置一个锁定机制(如果您的数据库支持),以防止太多人同时访问数据库.此外,你可以在你的数据库中拥有一个缓存机制,这样如果有人再次从数据库中请求完全相同的数据,你总是可以返回一个缓存值

一些应用程序还在应用程序和数据库之间使用第三个服务层来保护数据库免受这种情况的影响。服务层确保你在数据库中没有缓存未命中风暴

【讨论】:

【参考方案5】:

你在面试中被问到的问题是所谓的Cache miss-storm——一个很多用户触发缓存再生的场景,以这种方式打击数据库。

为了防止这种情况,首先您必须设置软到期日期和硬到期日期。假设硬到期日期是 1 天,软到期日期是 1 小时。硬是在缓存服务器中实际设置的,软是在缓存值本身(或在缓存服务器中的另一个键中)。应用程序从缓存中读取,发现软时间已过期,将软时间设置为提前 1 小时并命中数据库。这样下一个请求会看到已经更新的时间,不会触发缓存更新——它可能会读取过时的数据,但数据本身会处于重新生成的过程中。

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

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

【讨论】:

【参考方案6】:

仅当数据消费者准备好获取陈旧日期(在合理范围内)时,才考虑缓存的特定数据类型更糟糕。

在这种情况下,您可以定义 invalidation/eviction/update 政策以使您的数据保持最新(在商业意义上)

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

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

【讨论】:

以上是关于处理高流量站点缓存时的并发问题的主要内容,如果未能解决你的问题,请参考以下文章

大流量 高并发系统之限流特技

高并发系统设计 概述

高并发流量控制

高并发大流量站点架构简单思路

高并发限流策略

高并发系统之限流特技