Redis/Memcached高并发访问下的缓存失效时可能产生Dogpile效应(Cache Stampede效应)。看代码:
# redis read-through cache
conn = redis.Redis()
data = conn.get('cachekey')
if not data:
# long-running process
data = generateData()
conn.setex('cachekey', data, 10)
Dogpile 产生情况:
generateData()是耗时的运算过程或者复杂的数据库操作。当缓存失效或者redis-server不可用(服务器宕机/网络原因),此时恰好有大量的请求涌入,会直接穿透cache层从而导致CPU使用率或者数据库操作数短时间急剧攀升,可能会引发数据库/web服务器故障。
Solution:
1. 使用独立的进程\线程更新cache
实践:web应用中启动调度线程;单独进程Job(spring-boot)
// every ten seconds
@Scheduled(cron = "*/10 * * * * *")
现在我们的应用就是基于这样的方案,可靠,经历618考验^_^。
2. 使用”锁”
实践:cache失效,单一请求,其余等待;单一请求cache失效前更新
# redis read-through cache
conn = redis.Redis()
def get(key):
data = conn.get(key)
if data:
return data
# try lock
if conn.setnx('lock:' + key, 'locked'):
# long-running process
data = generateData()
conn.setex(key, data, 10)
conn.delete('lock:' + key)
return data
else:
# 'waiting & try get'
loop = 10
while loop > 0:
time.sleep(0.1)
data = conn.get(key)
if data:
print 'found'
return data
loop -= 1
return None
# redis read-through cache
conn = redis.Redis()
def get(key):
recache = 2
data = conn.get(key)
ttl = conn.ttl(key)
if ttl < recache and conn.setnx('lock:' + key, 'locked'):
print 'recache'
# long-running process
data = generateData()
conn.setex(key, data, 10)
conn.delete('lock:' + key)
# normal return
return data
这两者各有优势,看具体业务选取。我们应用涉及到实时或者重定向的功能采用的是第一种锁。
另外:按照具体的使用确定是否要对锁的冲突和异常进行特殊处理,代码中没有实现。
参考(力荐):http://www.linuxidc.com/Linux/2013-07/86960.htm (关于这次项目的实践,上线后才看到这篇文章,相见恨晚。本文只做实践,详解看这篇推荐。)
618值班顺利