Cloud & IT Cert Prep · 课时

缓存策略:延迟加载与写入直达

实现延迟加载,在缓存未命中时填充缓存;实现写入直达,使缓存与每次数据库写入保持一致

第 3 / 4 课13 个步骤

缓存策略:延迟加载与写入直达 是 CoddyKit 上的免费 Cloud & IT Cert Prep 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Cloud & IT Cert Prep 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Cloud & IT Cert Prep 课程共包含 4 节课。

缓存策略为何重要

在应用程序与数据库之间加入缓存后,您需要采用缓存策略——一组规则,用于确定何时将数据放入缓存、何时从缓存读取数据,以及何时将数据驱逐出去。选择错误的策略会导致以下问题:过期数据(缓存返回已过时的值)、冷缓存未命中(缓存为空,每个请求都会访问数据库),或者缓存击穿(许多同时到达的请求请求同一个缺失的 Key,并同时访问数据库)。两种基础策略是 Lazy Loading 和写入直通。

Lazy Loading(旁路缓存):工作原理

Lazy Loading(也称为旁路缓存)是最常见的缓存模式。应用程序首先检查缓存。发生缓存命中时,数据会直接从缓存返回,这是一条快速路径。发生缓存未命中时,应用程序从数据库获取数据,将结果写入带有 TTL 的缓存,然后返回给调用方。缓存只会填充实际请求过的数据,因此称为“Lazy”。下一次请求相同 Key 时,就可以在缓存中找到该数据。

# Lazy loading pattern (Python with redis-py)
def get_user(user_id, redis_client, db):
    cache_key = f'user:{user_id}'

    # 1. Check cache
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)   # Cache HIT

    # 2. Cache MISS: fetch from DB
    user = db.query('SELECT * FROM users WHERE id = %s', user_id)

    # 3. Populate cache with TTL of 300 seconds
    redis_client.setex(cache_key, 300, json.dumps(user))

    return user

Lazy Loading:优点

Lazy Loading 有三个主要优点:只缓存请求过的数据——缓存不会被无人读取的数据填满,因此可以高效利用内存。冷缓存不会导致应用程序中断——发生缓存未命中时,应用程序会回退到数据库,因此即使 ElastiCache 重启或节点发生故障,应用程序仍可继续运行(但延迟会更高)。缓存始终会与数据库保持最终一致,因为即使遗漏了更新,过期数据也会通过 TTL 失效。

Lazy Loading:缺点

Lazy Loading 有三个主要缺点:缓存未命中的代价较高——一次命中只需执行一个操作,而未命中需要执行三个操作(检查缓存、读取数据库、写入缓存),因此冷请求的延迟更高。过期数据——数据库更新后,缓存仍会提供旧值,直到 TTL 到期或明确使该 Key 失效(这段时间称为缓存不一致窗口)。缓存击穿——如果热门 Key 到期,许多同时到达的请求都会发生缓存未命中,并同时访问数据库,可能使数据库不堪重负。

# Mitigating cache stampede with a probabilistic early expiration
# (Refresh the key before it expires to avoid simultaneous misses)
def get_with_stampede_protection(key, redis_client, db_fetch_fn, ttl=300):
    value = redis_client.get(key)
    ttl_remaining = redis_client.ttl(key)

    # Probabilistically refresh before expiry
    if value is None or (ttl_remaining < 30 and random.random() < 0.1):
        value = db_fetch_fn()
        redis_client.setex(key, ttl, json.dumps(value))
    return json.loads(value)

写入直通:工作原理

在写入直通策略中,每次写入数据库时,也会同时写入缓存。作为同一操作的一部分,应用程序同时写入缓存和数据库(或者由数据库触发缓存更新)。因此,缓存始终与数据库同步,不存在过期数据窗口。写入直通保证缓存中的数据始终是最新的,因此对于最近写入的数据,后续读取始终可以命中缓存。

# Write-through pattern (Python)
def update_user(user_id, user_data, redis_client, db):
    # 1. Write to database FIRST
    db.execute('UPDATE users SET name=%s WHERE id=%s',
                (user_data['name'], user_id))

    # 2. Update cache immediately (write-through)
    cache_key = f'user:{user_id}'
    redis_client.setex(cache_key, 3600, json.dumps(user_data))

    return user_data

# Every read is now a cache hit for recently updated data

写入直通:优点

写入直通的优点包括:缓存数据始终是最新的——每次写入都会更新缓存,因此不会有过期数据。读取始终很快——频繁写入的热门数据始终位于缓存中。读取不会发生缓存击穿——数据在读取前就已预先填充,因此最近写入的数据不会出现冷缓存未命中。这种策略非常适合读取密集且更新频繁的工作负载,例如产品目录、定价系统或用户资料缓存。

写入直通:缺点

写入直通的缺点包括:写入成本——每次写入都要执行两个操作(数据库 + 缓存),从而增加写入延迟。缓存污染——即使数据之后再也不会被读取,也会被写入缓存(写入一次但从未请求),浪费缓存内存。缓存重启会导致冷缓存——如果缓存集群重启,所有主动写入的数据都会丢失,必须通过写入操作或缓存预热流程重新填充缓存。请将写入直通与 TTL 结合使用,以避免很少读取的缓存数据无限增长。

结合 Lazy Loading 与写入直通

在实际生产系统中,许多系统会结合使用这两种策略:对更新频繁且读取频繁的数据(例如用户会话或当前价格)使用写入直通,对更新少但读取频繁的数据(例如产品描述或文章内容)使用Lazy Loading。请为两者设置适当的 TTL:写入直通 Key 的数据始终是最新的,因此可以设置较长的 TTL;Lazy Loading Key 则应设置较短的 TTL,以限制过期数据窗口。这种混合方式可以在最大化缓存命中率的同时,尽量减少数据过期。

# Hybrid: write-through for sessions, lazy loading for product data

# Write-through for session data (always fresh, critical)
def save_session(session_id, data, redis_client, db):
    db.upsert('sessions', session_id, data)
    redis_client.setex(f'session:{session_id}', 3600, json.dumps(data))

# Lazy loading for product catalog (infrequent updates OK)
def get_product(product_id, redis_client, db):
    cached = redis_client.get(f'product:{product_id}')
    if cached:
        return json.loads(cached)
    product = db.query('SELECT * FROM products WHERE id=%s', product_id)
    redis_client.setex(f'product:{product_id}', 86400, json.dumps(product))
    return product

TTL 设计原则

缓存数据的 Time-To-Live(TTL)决定了过期数据窗口的最大时长和缓存的内存占用。设计 TTL 时应考虑以下因素:数据更新频率(会话数据经常变化 → 使用较短的 TTL;静态内容很少变化 → 使用较长的 TTL)、对数据过期的容忍度(金融价格 → 使用非常短的 TTL;博客文章文本 → 使用数小时或数天),以及缓存内存容量(内存较少 → 使用较短的 TTL,以更快驱逐过期数据)。请始终设置 TTL——不要缓存没有过期时间的数据,否则缓存最终会被过期数据填满。

# TTL examples by data type

# API rate limit counter: 60 seconds
redis_client.setex(f'ratelimit:{ip}', 60, count)

# User session: 30 minutes
redis_client.setex(f'session:{id}', 1800, json.dumps(session))

# Product catalog: 24 hours
redis_client.setex(f'product:{id}', 86400, json.dumps(product))

# Stock price: 10 seconds
redis_client.setex(f'price:{symbol}', 10, price)

# Static site content: 7 days
redis_client.setex(f'page:{slug}', 604800, html_content)

更新时使缓存失效

除了完全依赖 TTL 到期外,您还可以在底层数据发生变化时明确使缓存 Key 失效(删除缓存 Key)。这样可以彻底消除过期数据窗口。常见模式包括:写入时删除(每次数据库更新后删除缓存 Key,下一次读取时通过 Lazy Loading 重新填充);事件驱动的失效处理(DynamoDB Streams 或 RDS 更改捕获机制触发 Lambda,删除受影响的 Key)。缓存失效是分布式系统中最难解决的问题之一;失效逻辑越简单,缓存就越可靠。

# Delete-on-write invalidation pattern
def update_product(product_id, new_data, redis_client, db):
    # Update the database
    db.execute('UPDATE products SET ... WHERE id=%s', (product_id,))

    # Invalidate the cache key
    redis_client.delete(f'product:{product_id}')

    # Also invalidate any list/search caches that may include this product
    redis_client.delete('products:list:page:1')
    redis_client.delete(f'products:category:{new_data["category_id"]}')

    # Next read will trigger lazy loading with fresh data

写入后置(写回)缓存

一种不太常见但功能强大的模式是写入后置(写回):应用程序只写入缓存,然后由缓存异步地在后台将数据刷新到数据库。这种方式可以实现极快的写入(仅写入内存),但如果缓存尚未完成刷新就发生故障,则可能丢失数据。写入后置适用于高频、大量写入的场景,前提是数据可以重建或能够接受少量损失——例如命中计数器、分析事件或游戏分数更新。ElastiCache 原生不支持写入后置;必须在应用程序层实现。

# Write-behind pattern: write to cache, flush to DB asynchronously
# Application writes:
# redis_client.incr('post:42:views')      # fast, in-memory only

# Background job (runs every 60 seconds):
def flush_view_counts(redis_client, db):
    for key in redis_client.scan_iter('post:*:views'):
        count = redis_client.getdel(key)  # atomic get-and-delete
        post_id = key.split(':')[1]
        db.execute('UPDATE posts SET views = views + %s WHERE id = %s',
                   (int(count), post_id))

快速检查

请测试您对本课 AWS 解决方案架构师(SAA-C03)相关概念的理解。

课程回顾

本课介绍了以下内容:Lazy Loading在读取未命中时填充缓存并高效利用内存,但在 TTL 到期前可能提供过期数据;写入直通在每次写入时更新缓存,可以避免过期数据,但会为未读取的数据浪费内存;明确失效在数据库更新时删除缓存 Key,以消除过期窗口。接下来,我们将使用 ElastiCache 探索会话存储和排行榜模式。

免费开始

用 AI 导师学习 Cloud & IT Cert Prep — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
150
课程
600

常见问题解答

「缓存策略:延迟加载与写入直达」课时是免费的吗?

是的 — 「缓存策略:延迟加载与写入直达」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Cloud & IT Cert Prep 课程的其余内容,请升级到 CoddyKit PRO。 Cloud & IT Cert Prep 课程共包含 4 节课。

「缓存策略:延迟加载与写入直达」这节课中我会学到什么?

实现延迟加载,在缓存未命中时填充缓存;实现写入直达,使缓存与每次数据库写入保持一致 你通过在浏览器中直接运行的动手代码来练习 Cloud & IT Cert Prep,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Cloud & IT Cert Prep 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Cloud & IT Cert Prep 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「缓存策略:延迟加载与写入直达」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Cloud & IT Cert Prep 课中编写并运行代码吗?

能。每节 Cloud & IT Cert Prep 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. Redis 与 Memcached:选择合适的引擎
  2. ElastiCache Redis 复制组与集群模式
  3. 缓存策略:延迟加载与写入直达
  4. 会话存储与排行榜模式
← 返回 Cloud & IT Cert Prep