Python脚本如何预防缓存雪崩问题

wen python案例 31

本文目录导读:

Python脚本如何预防缓存雪崩问题

  1. 设置随机过期时间
  2. 缓存预热(预加载)
  3. 互斥锁(加锁单线程重建缓存)
  4. 使用Redis分布式锁
  5. 多级缓存策略
  6. 限流与降级
  7. 综合应用示例

缓存雪崩是指大量缓存数据在同一时间过期失效,导致所有请求直接打到数据库,造成数据库压力骤增甚至崩溃,Python脚本预防缓存雪崩的常用策略如下:

设置随机过期时间

避免所有缓存使用相同的过期时间,在基础过期时间上增加随机偏移:

import random
import time
def get_random_expiry(base_ttl=3600):
    """基础过期时间上增加随机偏移(0-600秒)"""
    return base_ttl + random.randint(0, 600)
# 使用示例
cache.set(key, value, ex=get_random_expiry())

缓存预热(预加载)

在系统低峰期提前加载热点数据到缓存:

def warm_up_cache():
    """系统启动或低峰期预加载热点数据"""
    hot_keys = get_hot_keys_from_db()  # 从数据库获取热点key
    for key in hot_keys:
        if not cache.exists(key):
            value = db.query(key)  # 从数据库获取数据
            cache.set(key, value, ex=get_random_expiry())

互斥锁(加锁单线程重建缓存)

当缓存失效时,只允许一个线程去重建缓存,其他线程等待:

import threading
class CacheManager:
    def __init__(self):
        self.locks = {}
        self.lock = threading.Lock()
    def get_with_lock(self, key, rebuild_func, ttl=3600):
        # 尝试从缓存获取数据
        data = cache.get(key)
        if data is not None:
            return data
        # 获取或创建key对应的锁
        with self.lock:
            if key not in self.locks:
                self.locks[key] = threading.Lock()
            key_lock = self.locks[key]
        # 加锁重建缓存
        with key_lock:
            # 双重检查,防止其他线程已重建
            data = cache.get(key)
            if data is not None:
                return data
            # 从数据库重建缓存
            data = rebuild_func()
            cache.set(key, data, ex=ttl)
            return data

使用Redis分布式锁

对于分布式系统,使用Redis的SETNX实现分布式锁:

import redis
import time
r = redis.Redis()
def get_with_distributed_lock(key, rebuild_func, ttl=3600, lock_timeout=10):
    """使用分布式锁防止缓存雪崩"""
    data = r.get(key)
    if data:
        return data
    lock_key = f"lock:{key}"
    lock_value = str(time.time())
    # 尝试获取锁,设置过期时间防止死锁
    if r.setnx(lock_key, lock_value):
        r.expire(lock_key, lock_timeout)
        try:
            # 双重检查
            data = r.get(key)
            if data:
                return data
            # 重建缓存
            data = rebuild_func()
            r.setex(key, ttl, data)
            return data
        finally:
            # 释放锁(只释放自己的锁)
            if r.get(lock_key) == lock_value:
                r.delete(lock_key)
    else:
        # 获取锁失败,等待重试
        time.sleep(0.1)
        return get_with_distributed_lock(key, rebuild_func, ttl, lock_timeout)

多级缓存策略

使用本地缓存(如LRU)配合Redis缓存:

from functools import lru_cache
import redis
r = redis.Redis()
class MultiLevelCache:
    def __init__(self, local_ttl=300, redis_ttl=3600):
        self.local_ttl = local_ttl
        self.redis_ttl = redis_ttl
    # 本地缓存(一级缓存,过期时间短)
    @lru_cache(maxsize=1000)
    def get_local(self, key):
        return None  # 实际实现需要存储时间和值
    def get(self, key, rebuild_func):
        # 1. 尝试本地缓存
        data = self.get_local(key)
        if data:
            return data
        # 2. 尝试Redis缓存
        data = r.get(key)
        if data:
            self.set_local(key, data)  # 回写本地缓存
            return data
        # 3. 从数据库获取
        data = rebuild_func()
        # 4. 写入各级缓存
        self.set_local(key, data, self.local_ttl)
        r.setex(key, self.redis_ttl, data)
        return data

限流与降级

控制同时重建缓存的并发数,必要时降级:

from contextlib import contextmanager
import threading
class CircuitBreaker:
    def __init__(self, max_concurrent=5):
        self.semaphore = threading.Semaphore(max_concurrent)
        self.is_open = False
    @contextmanager
    def acquire(self):
        if self.is_open:
            raise Exception("Circuit breaker is open")
        acquired = self.semaphore.acquire(blocking=False)
        if not acquired:
            raise Exception("Too many concurrent requests")
        try:
            yield
        finally:
            self.semaphore.release()
circuit_breaker = CircuitBreaker()
def safe_get_data(key, rebuild_func):
    try:
        with circuit_breaker.acquire():
            return rebuild_func()
    except Exception:
        # 降级处理
        return get_stale_data(key)  # 返回过期数据或默认值

综合应用示例

import random
import time
from functools import wraps
def anti_cache_avalanche(ttl=3600, lock_timeout=10):
    """装饰器实现缓存雪崩防护"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = f"{func.__name__}:{args}:{kwargs}"
            # 启用熔断机制
            if hasattr(wrapper, 'breaker') and wrapper.breaker.is_open:
                return get_fallback_value(key)  # 返回降级数据
            # 尝试获取缓存(带随机过期)
            data = cache.get(key)
            if data:
                return data
            # 使用互斥锁重建缓存
            lock_key = f"lock:{key}"
            if cache.setnx(lock_key, 1, ex=lock_timeout):
                try:
                    # 双重检查
                    data = cache.get(key)
                    if data:
                        return data
                    # 执行原始函数获取数据
                    data = func(*args, **kwargs)
                    # 设置缓存(随机过期时间)
                    actual_ttl = ttl + random.randint(0, 600)
                    cache.set(key, data, ex=actual_ttl)
                    return data
                finally:
                    cache.delete(lock_key)
            else:
                # 等待并重试
                time.sleep(0.1)
                return wrapper(*args, **kwargs)
        return wrapper
    return decorator
# 使用示例
@anti_cache_avalanche(ttl=3600)
def get_user_info(user_id):
    # 从数据库获取用户信息
    return db.query(f"SELECT * FROM users WHERE id={user_id}")
  1. 过期时间分散化:使用随机过期时间
  2. 预加载机制:在低峰期预热热点数据
  3. 互斥锁保护:只允许单线程重建缓存
  4. 多级缓存配合:本地缓存 + Redis,内存溢出保护
  5. 熔断降级:当重建缓存压力过大时,返回过期数据或默认值
  6. 监控报警:监控缓存命中率、过期key数量、数据库压力等指标

根据系统规模和业务特性,选择适合的组合策略,并做好监控和压测。

抱歉,评论功能暂时关闭!