用信号量解决缓存中的Stampede问题

缓存惊慌

缓存过期导致对源(例如数据库)访问集中,从而增加了负载的问题被称为缓存击穿(Cache Stampede)。”Stampede”一词的日语意思是“许多人涌入”。

还可以称之为“缓存未命中风暴”、“雷鸣兽”、“狗堆”等不同名称。

本文将介绍解决 Cache Stampede 问题的方法,即将 memcached 的功能用作信号量,来控制对原始数据的访问。


为了整理问题,让我们回顾一下使用缓存的典型处理方式。

简单的现金

通常情况下,缓存具有有效期限。当缓存过期时,将不使用缓存,而是访问源(如数据库)获取新结果,并将其保存到缓存,并返回响应。

大致上的实施将如下所述。

# キャッシュからデータを取得する
data = get_cache(key)

if not data:
  # キャッシュがなければデータベースから取得する
  data = get_data_from_database(...)

  # 新しいデータをキャッシュに保存する
  set_cache(key, data, ttl=60)

# ユーザーにレスポンスを返す
send_response(data)

如果访问量很少

如果访问量较少,这不是个问题。因为如下所述,访问到源站的流量很少。

Normal.png

当访问量较大时

如果有大量访问,并且缓存过期了,那么会有多次访问去数据库。这是因为缓存过期和新缓存保存之间存在时间差。如果数据库处理较慢,那么在缓存过期的时候会增加负载。

Stampede.png

应对措施

在应对措施中有几种方法可供选择。

    • ロック

 

    • 別プロセスでの更新

 

    確率的な早期の期限切れ

本文将介绍使用一种名为信号量的锁来解决问题的方法。

只更新一次

当缓存失效时,只需一个用户访问数据库并创建新的缓存,其他用户则等待新缓存即可,这样可以减轻对原始数据的负载。

Smart.png

信号灯

在这种情况下,可以通过使用信号量来控制访问以实现。

什么是信号量?

用于控制多个执行单元对”共享资源”的访问。某个资源是一个记录”可用数量”的记录,在使用或释放时,记录会被”安全地重写”,与等待资源可用的操作相结合。

然而,在Web服务中,应用程序在多台服务器上运行。即使在同一台服务器上,也可能通过多个进程运行。因此,由于每个访问不是在同一操作系统进程上进行,所以无法使用操作系统上的(编程语言中的)信号量。

在解决这个问题时,我们可以通过实现一个“缓存系统”来实现从每个访问中共享使用。因此,我们在这里引入信号量。

添加

memcached的add命令只在“项不存在时保存”数据。换句话说,只有单个客户端能够保存数据到该键。

通过将添加成功视为“获取信号量”,可以使单个客户端执行缓存更新。

:这是一个理解和运用中文的机会。

# キャッシュからデータを取得する
data = memcache.get(key_for_data)

if data:
  # キャッシュがある場合はセマフォの獲得を試みる
  ret = memcache.add(key_for_semaphore, 1, ttl=60)

  if ret:
    # セマフォを獲得した場合は新しいキャッシュを作る
    data = get_data_from_database(...)
    memcache.set(key_for_data, data, ttl=120)

else:
  # 本当にキャッシュ切れしてしまった場合は、どうしようもないのでオリジンへアクセスします
  memcache.add(key_for_semaphore, 1, ttl=60)
  data = get_data_from_database(...)
  memcache.set(key_for_data, data, ttl=120)

return data

由于只有一个人可以做到这一点,因此对Origin的访问通常只限于一个人。

到期日

在memcache中可以设置项目的过期时间。因此,可以通过过期时间来释放信号量。在Web服务中,HTTP请求的处理是立即返回的,无法显式地释放信号量。但是由于项目的过期时间,它们会自动释放,不会忘记释放。

此外,信号量的有效期限应该比数据的有效期限短。这样做可以在缓存数据仍然存在时触发对源站的访问和新缓存的创建,并且未获得信号量的客户端可以直接使用缓存数据。

在上述的例子中,key_for_semaphore会在数据之前过期(ttl=60)。尽管从memcache.get中可以获得数据,但是最开始成功调用memcache.add并访问源服务器的客户端会使用memcache.set来存储新数据(ttl=120)。当然,缓存也有可能因为除了有效期的其他原因而消失,所以可能会进入else部分,但总体上我认为它可以正常运行。

其他

以下是各种软件在Stampede攻击防范方面的功能:

– Rails的ActiveSupport::Cache::Store竞态条件超时设置(race_condition_ttl)
– nginx的proxy_cache_lock功能
– Varnish的Grace模式
– Fastly功能

请参考以下链接:
– http://techblog.yahoo.co.jp/infrastructure/cache-reducing-origin-request/
– https://en.wikipedia.org/wiki/Cache_stampede

广告
将在 10 秒后关闭
bannerAds