Python脚本如何处理缓存数据不一致

wen python案例 28

本文目录导读:

Python脚本如何处理缓存数据不一致

  1. 缓存失效策略
  2. 缓存更新策略
  3. 并发控制策略
  4. 缓存预热与失效通知
  5. 高级解决方案:使用Redis
  6. 实用的缓存装饰器
  7. 最佳实践建议

在Python中处理缓存数据不一致问题,我为你整理了几种核心策略和具体实现方案:

缓存失效策略

设置过期时间(TTL)

import time
import threading
from functools import wraps
class TTLDict:
    def __init__(self, default_ttl=300):  # 默认5分钟
        self._data = {}
        self._expiry = {}
        self._lock = threading.Lock()
        self._default_ttl = default_ttl
    def set(self, key, value, ttl=None):
        if ttl is None:
            ttl = self._default_ttl
        with self._lock:
            self._data[key] = value
            self._expiry[key] = time.time() + ttl
    def get(self, key):
        with self._lock:
            if key not in self._data:
                return None
            if self._expiry[key] < time.time():
                del self._data[key]
                del self._expiry[key]
                return None
            return self._data[key]

缓存更新策略

主动更新(Write-Through Cache)

class WriteThroughCache:
    """写操作同时更新缓存和数据库"""
    def __init__(self, database):
        self.cache = {}
        self.db = database
        self._lock = threading.Lock()
    def get(self, key):
        # 先查缓存
        if key in self.cache:
            return self.cache[key]
        # 缓存未命中,从数据库加载
        with self._lock:
            if key in self.cache:  # 双重检查
                return self.cache[key]
            value = self.db.get(key)
            if value is not None:
                self.cache[key] = value
            return value
    def set(self, key, value):
        # 同时更新数据库和缓存
        with self._lock:
            self.db.set(key, value)
            self.cache[key] = value

延迟更新(Write-Back Cache)

import asyncio
from collections import defaultdict
class WriteBackCache:
    """先更新缓存,异步写入数据库"""
    def __init__(self, database, sync_interval=5):
        self.cache = {}
        self.db = database
        self.dirty_keys = set()  # 标记脏数据
        self._lock = asyncio.Lock()
        # 启动后台同步任务
        asyncio.create_task(self._sync_task(sync_interval))
    async def get(self, key):
        async with self._lock:
            return self.cache.get(key)
    async def set(self, key, value):
        async with self._lock:
            self.cache[key] = value
            self.dirty_keys.add(key)
    async def _sync_task(self, interval):
        """定期将脏数据同步到数据库"""
        while True:
            await asyncio.sleep(interval)
            async with self._lock:
                for key in self.dirty_keys:
                    if key in self.cache:
                        await self.db.set(key, self.cache[key])
                self.dirty_keys.clear()

并发控制策略

使用版本号或时间戳

class VersionedCache:
    def __init__(self):
        self.cache = {}
        self.versions = {}
        self._lock = threading.RLock()
    def get_with_version(self, key):
        """获取缓存和版本号"""
        with self._lock:
            value = self.cache.get(key)
            version = self.versions.get(key, 0)
            return value, version
    def set_with_version(self, key, value, expected_version):
        """
        只有当版本号匹配时才更新
        返回是否更新成功
        """
        with self._lock:
            current_version = self.versions.get(key, 0)
            if current_version != expected_version:
                return False  # 版本冲突,更新失败
            self.cache[key] = value
            self.versions[key] = current_version + 1
            return True

CAS(Compare and Swap)操作

import threading
class CASCache:
    def __init__(self):
        self._data = {}
        self._locks = {}
        self._global_lock = threading.Lock()
    def _get_lock(self, key):
        with self._global_lock:
            if key not in self._locks:
                self._locks[key] = threading.Lock()
            return self._locks[key]
    def compare_and_swap(self, key, old_value, new_value):
        """
        原子操作:比较当前值,只有匹配时才更新
        """
        lock = self._get_lock(key)
        with lock:
            current_value = self._data.get(key)
            if current_value == old_value:
                self._data[key] = new_value
                return True
            return False
    def get(self, key):
        return self._data.get(key)

缓存预热与失效通知

基于事件的缓存失效

import asyncio
from typing import Callable, Dict, Set
class CacheInvalidationSystem:
    def __init__(self):
        self.subscribers: Dict[str, Set[Callable]] = {}
    def subscribe(self, cache_key: str, callback: Callable):
        """订阅某个key的失效通知"""
        if cache_key not in self.subscribers:
            self.subscribers[cache_key] = set()
        self.subscribers[cache_key].add(callback)
    def invalidate(self, cache_key: str):
        """通知所有订阅者某个key已失效"""
        if cache_key in self.subscribers:
            for callback in self.subscribers[cache_key]:
                try:
                    callback(cache_key)
                except Exception as e:
                    print(f"Cache invalidation callback failed: {e}")
    async def invalidate_async(self, cache_key: str):
        """异步版本"""
        if cache_key in self.subscribers:
            tasks = []
            for callback in self.subscribers[cache_key]:
                if asyncio.iscoroutinefunction(callback):
                    tasks.append(callback(cache_key))
                else:
                    callback(cache_key)
            if tasks:
                await asyncio.gather(*tasks, return_exceptions=True)
# 使用示例
class MyApplication:
    def __init__(self):
        self.cache = {}
        self.invalidation = CacheInvalidationSystem()
        # 注册失效处理
        self.invalidation.subscribe("user:123", self._reload_user_data)
    def _reload_user_data(self, key):
        """重新加载用户数据到缓存"""
        user_id = key.split(":")[1]
        # 模拟重新加载
        self.cache[key] = self._load_from_db(user_id)
        print(f"Cache {key} refreshed")
    def update_user(self, user_id, data):
        """更新用户时触发缓存失效"""
        # 更新数据库
        self._update_db(user_id, data)
        # 发送失效通知
        self.invalidation.invalidate(f"user:{user_id}")

高级解决方案:使用Redis

Redis的缓存一致性方案

import redis
import json
class RedisConsistentCache:
    """使用Redis实现缓存一致性"""
    def __init__(self, redis_host='localhost', redis_port=6379):
        self.redis = redis.Redis(
            host=redis_host, 
            port=redis_port, 
            decode_responses=True
        )
    def get_with_fallback(self, key, db_func, ttl=300):
        """先查缓存,缓存miss则查数据库并更新缓存"""
        # 尝试从缓存获取
        value = self.redis.get(key)
        if value is not None:
            return json.loads(value)
        # 使用分布式锁防止缓存击穿
        lock_key = f"lock:{key}"
        if self.redis.setnx(lock_key, "1", ex=10):
            try:
                # 双重检查
                value = self.redis.get(key)
                if value is not None:
                    return json.loads(value)
                # 从数据库获取
                value = db_func()
                if value is not None:
                    self.redis.setex(key, ttl, json.dumps(value))
                return value
            finally:
                self.redis.delete(lock_key)
        else:
            # 等待其他线程完成
            import time
            time.sleep(0.1)
            return self.get_with_fallback(key, db_func, ttl)
    def set_consistent(self, key, value, db_func, ttl=300):
        """使用延迟双删策略保证最终一致性"""
        # 第一遍删除缓存
        self.redis.delete(key)
        # 更新数据库
        db_func(value)
        # 延迟一定时间后再次删除缓存
        import threading
        threading.Timer(0.5, lambda: self.redis.delete(key)).start()
        # 更新缓存
        self.redis.setex(key, ttl, json.dumps(value))

实用的缓存装饰器

from functools import wraps
import hashlib
def cached(ttl=300, maxsize=128):
    """带缓存功能的函数装饰器"""
    def decorator(func):
        cache = {}
        expiry = {}
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 生成缓存键
            key_parts = [func.__name__] + [str(arg) for arg in args] + \
                       [f"{k}={v}" for k, v in sorted(kwargs.items())]
            cache_key = hashlib.md5(":".join(key_parts).encode()).hexdigest()
            # 检查缓存是否有效
            if cache_key in expiry and expiry[cache_key] > time.time():
                return cache[cache_key]
            # 执行原函数
            result = func(*args, **kwargs)
            # 更新缓存
            cache[cache_key] = result
            expiry[cache_key] = time.time() + ttl
            # 简单的LRU清理
            if len(cache) > maxsize:
                oldest_key = min(expiry, key=expiry.get)
                del cache[oldest_key]
                del expiry[oldest_key]
            return result
        return wrapper
    return decorator
# 使用示例
@cached(ttl=60)
def get_user_data(user_id):
    # 模拟耗时数据库操作
    return {"id": user_id, "name": "User"}

最佳实践建议

  1. 选择合适策略

    • 读多写少:使用TTL策略
    • 写多读多:使用Write-Through
    • 一致性要求高:使用版本号或CAS
  2. 避免常见问题

    • 缓存穿透:使用布隆过滤器
    • 缓存雪崩:设置不同过期时间
    • 缓存击穿:使用分布式锁
  3. 监控和日志

    import logging
    class MonitoredCache:
        def __init__(self):
            self.logger = logging.getLogger(__name__)
            self.hits = 0
            self.misses = 0
        def get(self, key):
            value = self._get_from_cache(key)
            if value is not None:
                self.hits += 1
                self.logger.debug(f"Cache hit: {key}")
            else:
                self.misses += 1
                self.logger.warning(f"Cache miss: {key}")
            return value
  4. 测试策略

    • 单元测试缓存逻辑
    • 集成测试数据库和缓存的交互
    • 压力测试并发场景

根据你的具体业务场景,选择合适的策略组合,才能有效解决缓存数据不一致的问题。

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