如何解决缓存击穿?

简介: 缓存击穿是指热点数据失效时大量请求直接冲击数据库,可能导致系统崩溃。解决方案包括:永不过期策略避免缓存失效瞬间的穿透;互斥锁控制并发访问;热点预热提前刷新缓存;熔断降级在数据库压力大时返回默认值;二级缓存降低Redis压力。实际中常组合使用多种方案,如热点预热+互斥锁+熔断降级,以提升系统稳定性与性能。

解决缓存击穿问题的核心思路是防止热点key失效时大量请求直接访问数据库。以下是几种常见的解决方案及其实现要点:

1. 永不过期策略(逻辑过期)

原理

  • 缓存key不设置物理过期时间(TTL),而是在value中存储一个逻辑过期时间戳。
  • 访问时判断逻辑时间是否过期,若过期则异步更新缓存,同时返回旧数据。

优点

  • 完全避免缓存失效瞬间的穿透问题。
  • 适用于对数据实时性要求不高的场景(如商品详情、配置信息)。

实现示例

def get_data(key):
    data = redis.get(key)
    if data:
        # 检查逻辑过期时间
        if data["expire_time"] < time.time():
            # 异步更新缓存(如通过线程池或消息队列)
            thread_pool.submit(update_cache, key)
        return data["value"]
    else:
        # 缓存中没有数据(罕见情况),查库并更新
        return update_cache(key)

def update_cache(key):
    # 加锁防止并发更新
    with redis.lock(f"lock:{key}"):
        # 再次检查缓存是否已被其他线程更新
        if redis.get(key)["expire_time"] > time.time():
            return redis.get(key)["value"]
        # 查询数据库
        value = db.query(key)
        # 设置新值和逻辑过期时间(如30分钟后)
        redis.set(key, {
   
            "value": value,
            "expire_time": time.time() + 1800
        })
        return value

2. 互斥锁(分布式锁)

原理

  • 当缓存失效时,仅允许一个请求(通过获取锁)访问数据库,其他请求等待锁释放后从缓存获取数据。

优点

  • 保证数据库仅被一个请求访问,压力可控。
  • 实现简单,兼容性强。

缺点

  • 锁竞争可能导致部分请求等待,影响吞吐量。

实现示例

def get_data(key):
    data = redis.get(key)
    if data:
        return data

    # 尝试获取锁
    if redis.setnx(f"lock:{key}", "1", ex=10):  # 锁超时10秒
        try:
            # 查询数据库
            data = db.query(key)
            # 更新缓存(设置合理TTL)
            redis.set(key, data, ex=3600)
            return data
        finally:
            # 释放锁
            redis.delete(f"lock:{key}")
    else:
        # 未获取到锁,等待重试
        time.sleep(0.1)  # 短暂休眠避免频繁重试
        return get_data(key)  # 递归重试

3. 热点数据预热

原理

  • 通过定时任务或监控系统,在缓存过期前主动刷新热点key,确保缓存始终有效。

适用场景

  • 已知的热点数据(如首页banner、热门榜单),且有明确访问模式。

实现示例

# 定时任务(如每20分钟执行一次)
def refresh_hot_keys():
    hot_keys = ["product:1001", "product:1002", "category:hot"]  # 热点key列表
    for key in hot_keys:
        # 查询数据库
        data = db.query(key)
        # 更新缓存(设置TTL比刷新周期长)
        redis.set(key, data, ex=30*60)  # 30分钟过期,任务每20分钟执行一次

4. 熔断降级

原理

  • 当检测到数据库压力过大(如响应超时、错误率上升)时,临时返回默认值或旧数据,避免请求穿透到数据库。

工具

  • 可使用Sentinel、Hystrix等熔断框架实现。

实现示例

from circuitbreaker import CircuitBreaker

# 配置断路器:连续3次失败则开启熔断,5秒后尝试恢复
breaker = CircuitBreaker(fail_max=3, reset_timeout=5)

@breaker
def query_db(key):
    return db.query(key)  # 可能抛出异常的数据库查询

def get_data(key):
    data = redis.get(key)
    if data:
        return data

    try:
        # 尝试查询数据库(可能触发熔断)
        data = query_db(key)
        redis.set(key, data, ex=3600)
        return data
    except CircuitBreakerError:
        # 熔断开启,返回默认值或旧数据
        return {
   "message": "Service temporarily unavailable", "data": None}

5. 二级缓存(LocalCache + Redis)

原理

  • 在分布式缓存(如Redis)之上增加本地缓存(如Guava Cache、Caffeine),热点数据优先从本地读取,减少对Redis的访问。

优点

  • 降低网络延迟,进一步减轻Redis压力。

实现示例

import caffeine

# 初始化本地缓存(最大1000条,10分钟过期)
local_cache = caffeine.CacheBuilder.newBuilder() \
    .maximumSize(1000) \
    .expireAfterWrite(10, TimeUnit.MINUTES) \
    .build()

def get_data(key):
    # 优先从本地缓存获取
    data = local_cache.get_if_present(key)
    if data:
        return data

    # 从Redis获取
    data = redis.get(key)
    if data:
        # 放入本地缓存
        local_cache.put(key, data)
        return data

    # 缓存失效,查库并更新
    data = db.query(key)
    redis.set(key, data, ex=3600)
    local_cache.put(key, data)
    return data

方案选择建议

方案 适用场景 优点 缺点
永不过期 热点数据,对实时性要求不高 无击穿风险,实现简单 数据更新不及时
互斥锁 突发流量,需要严格控制数据库访问 保证数据库安全 吞吐量下降,实现复杂
热点预热 已知热点,访问模式稳定 无等待,性能最优 需要提前预测热点
熔断降级 保护数据库,应对极端情况 高可用性 数据可能不准确
二级缓存 高频本地访问,降低Redis压力 性能提升明显 内存占用增加,一致性问题

实际应用中,通常会组合多种方案(如热点预热 + 互斥锁 + 熔断降级),以应对不同场景下的缓存击穿风险。

目录
相关文章
|
2月前
|
缓存 数据库连接 数据库
缓存三剑客(穿透、击穿、雪崩)
缓存穿透指查询数据库和缓存中都不存在的数据,导致请求直接冲击数据库。解决方案包括缓存空对象和布隆过滤器。缓存击穿是大量请求访问同一个失效的热点数据,使数据库瞬间压力剧增,解决方法有提前预热、设置永不过期、加锁限流等。缓存雪崩是大量key同时失效,导致所有请求直达数据库,可通过引入随机过期时间缓解。三者分别对应单点爆破、全面崩塌等问题,需根据场景选择合适策略优化系统性能与稳定性。
175 0
|
1月前
|
缓存 监控 安全
告别缓存击穿!Go 语言中的防并发神器:singleflight 包深度解析
在高并发场景中,多个请求同时访问同一资源易导致缓存击穿、数据库压力过大。Go 语言提供的 `singleflight` 包可将相同 key 的请求合并,仅执行一次实际操作,其余请求共享结果,有效降低系统负载。本文详解其原理、实现及典型应用场景,并附示例代码,助你掌握高并发优化技巧。
160 0
|
2月前
|
缓存 NoSQL 数据库
什么是缓存击穿
缓存击穿是指热点缓存key突然失效,导致大量并发请求直接冲击数据库,造成巨大压力。常见于高并发场景,如热门商品信息失效时。解决方法包括设置热点key永不过期、使用分布式锁、预热数据、熔断降级等,以保障系统稳定性。
407 0
|
6月前
|
缓存 监控 NoSQL
Redis--缓存击穿、缓存穿透、缓存雪崩
缓存击穿、缓存穿透和缓存雪崩是Redis使用过程中可能遇到的常见问题。理解这些问题的成因并采取相应的解决措施,可以有效提升系统的稳定性和性能。在实际应用中,应根据具体场景,选择合适的解决方案,并持续监控和优化缓存策略,以应对不断变化的业务需求。
1117 29
|
11月前
|
存储 缓存 监控
缓存击穿、缓存穿透、缓存雪崩 3大问题,如何彻底解决?
【10月更文挑战第8天】在分布式系统中,缓存的使用极大地提高了系统的性能和响应速度。然而,缓存击穿、缓存穿透和缓存雪崩是三个常见的缓存相关问题,它们可能导致系统性能下降,甚至引发系统崩溃。本文将深入探讨这三个问题的成因、影响以及彻底的解决方案。
1248 1
|
11月前
|
缓存 NoSQL 关系型数据库
redis和缓存及相关问题和解决办法 什么是缓存预热、缓存穿透、缓存雪崩、缓存击穿
本文深入探讨了Redis缓存的相关知识,包括缓存的概念、使用场景、可能出现的问题(缓存预热、缓存穿透、缓存雪崩、缓存击穿)及其解决方案。
637 0
redis和缓存及相关问题和解决办法 什么是缓存预热、缓存穿透、缓存雪崩、缓存击穿
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
6月前
|
缓存 数据库
什么是缓存击穿 ? 怎么解决 ?
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大 解决方案 : ● 热点数据提前预热 ● 设置热点数据永远不过期。 ● 加锁 , 限流
|
10月前
|
缓存 NoSQL 数据库
缓存穿透、缓存击穿和缓存雪崩及其解决方案
在现代应用中,缓存是提升性能的关键技术之一。然而,缓存系统也可能遇到一系列问题,如缓存穿透、缓存击穿和缓存雪崩。这些问题可能导致数据库压力过大,甚至系统崩溃。本文将探讨这些问题及其解决方案。
|
11月前
|
消息中间件 缓存 NoSQL
大数据-49 Redis 缓存问题中 穿透、雪崩、击穿、数据不一致、HotKey、BigKey
大数据-49 Redis 缓存问题中 穿透、雪崩、击穿、数据不一致、HotKey、BigKey
172 2