LabHub
学习 学习路径 课程

Redis 与缓存

没设 TTL 的键会造出什么

在 LabHub 中继续学习

一句话总结

到期时间到来时不会立即发生。接近时删除(延迟删除),后台会抽取样本删除(主动到期)。

概念图: Stampfed · 提前重新计算 · 穿透 · 延迟双重删除

为什么需要这个?

Redis实例的内存经常会不断增长,但找不到原因的情况经常发生。一般来说,答案只有一条——混合了没有设置TTL的键。

问题是它看不见。现金代码大部分是SETEX使用,在某个地方SET只在用,SET key val更新时会销毁原有的TTL。第二个特别巧妙——SET删除现有TTL。要保持TTL的话KEEPTTL需要选项。

怎么行动

从到期机制开始,必须准确了解。Redis同时写两种方式。延迟删除在访问键时才会检查到期并删除。主动到期在后台随机抽取带有TTL的键作为样本,删除已到期的键。所以即使到期时间已经过去,但没有人访问的键也会在内存中保留一段时间。DBSIZE比预想的要大的原因就是这个。

达到maxmemory时,排斥政策会启动。选项有八种,但实际工作中重要的只有三个。

noeviction是基本值,如果达到限制值,就会拒绝使用。在使用缓存时,如果设置为这个值,有一天突然所有使用都失败。

allkeys-lru或者allkeys-lfu将所有的密钥视为驱逐对象。如果是纯粹的缓存用途的话,就是这个。作者的建议是allkeys-lfu这是因为比起最近性,频率更适合现金命中率。

volatile-lru只提取设置了TTL的键。在缓存和永久数据混在一起的实例时写入。但是,在这个设置中,如果在缓存键中省略TTL,该键将永远不会被提取,而消耗内存。

现金崩溃的三种方式

TTL设计是为了防止这三个。虽然名字相似,会让人混淆,但原因和对策是 不一样。

名字 怎么回事 怎么阻止
Stampede 人气限制量到期时,数千个请求同时发送到数据库 跳跃、锁定、提前重新计算
穿透(penetration) 继续查询没有的键 — 缓存中什么都没有,每次DB 没有也短时间缓存,bloom过滤器
事态(avalanche) 很多键一次性过期或快取服务器下线 震动、TTL分散、多层快取

Stampfed通过锁定来阻止。只有第一个请求才会进入数据库,其余的等待。

def get_or_load(key, loader, ttl=300):
    v = r.get(key)
    if v is not None:
        return v
    # SET NX 로 잠금 하나만 얻는다. 얻지 못한 요청은 잠깐 기다렸다 다시 읽는다.
    if r.set(f"lock:{key}", "1", nx=True, ex=10):
        try:
            v = loader()
            r.set(key, v, ex=ttl + random.randint(-30, 30))   # 지터
            return v
        finally:
            r.delete(f"lock:{key}")
    time.sleep(0.05)
    return r.get(key) or loader()      # 그래도 없으면 어쩔 수 없이 직접

更好的方法是提前重新计算。TTL结束之前,如果概率性地提前更新的话 没有到期瞬间本身消失。剩余TTL越短,提高更新概率的方式。 (probabilistic early expiration)被广泛使用。

穿透也会缓存“没有”。但是只有在需要很短的时间(30~60秒)后才会真正产生数据时 很快就会反映出来。

卡西和原件不一致的瞬间

如果有写入,就需要无效化,无效化有顺序问题。

❌ 캐시를 먼저 지우고 DB 를 쓴다
   1. DEL cache          2. (다른 요청이 옛 값을 읽어 캐시에 다시 넣는다)
   3. UPDATE db          → 캐시에 옛 값이 영원히 남는다

✅ DB 를 먼저 쓰고 캐시를 지운다 (cache-aside)
   1. UPDATE db          2. DEL cache
   → 사이에 읽은 요청은 옛 값을 보지만, 곧 사라진다

即便如此,也很罕见地会出错。要完全阻止的话,请延迟双重删除(使用后删除后, 几百毫秒后再删除一次)或写入更改日志(CDC)订阅。大多数 在服务中,较短的TTL是更便宜的答案——即使不符,也只比TTL不符。

在现场相遇的样子

在TTL中加入震动也很重要。发布后立即填充缓存会使TTL完全相同,一小时后全部同时过期。那一刻就是Stampede。ttl = base + random(-spread, +spread)一句话说来,到期时间会分散在时间轴上。

还有需要感谢。SCAN巡回TTL定期运行计数-1人的键(无到期)的脚本,当新来的代码遗漏了TTL时,会立即纠正。

下次实习要做的事情

挂上TTL确认后,SET再现炸毁这个TTL的陷阱,KEEPTTL阻止,改变驱逐政策,确认实际驱逐了基数,最后制作找到没有TTL的基数的审计脚本。