Open Dreamer:基于JAX/Flax的世界模型工程化实践指南
2026/7/28 2:18:12
pythonimport timeimport jsonimport redisimport pymysqlfrom typing import Optional, Dict, Anyclass CacheAsideManager: """ Cache-Aside 模式管理器 支持读时延迟加载、写时删除缓存、自动过期 """ def __init__(self, redis_host='localhost', redis_port=6379, mysql_host='localhost', mysql_user='root', mysql_password='password', mysql_db='test'): # 初始化 Redis 连接 self.redis_client = redis.Redis( host=redis_host, port=redis_port, decode_responses=True # 自动解码字节串为字符串 ) # 初始化 MySQL 连接池 self.mysql_conn = pymysql.connect( host=mysql_host, user=mysql_user, password=mysql_password, database=mysql_db, charset='utf8mb4', cursorclass=pymysql.cursors.DictCursor ) self.cache_ttl = 300 # 缓存过期时间 5 分钟 def get_user(self, user_id: int) -> Optional[Dict[str, Any]]: """ 读操作:Cache-Aside 延迟加载 1. 先查缓存 2. 如果未命中,查数据库 3. 将结果写入缓存(带 TTL) """ cache_key = f"user:{user_id}" # 步骤1:查询缓存 cached_data = self.redis_client.get(cache_key) if cached_data: print(f"[CACHE HIT] User {user_id} found in cache") return json.loads(cached_data) print(f"[CACHE MISS] User {user_id} not in cache, querying database...") # 步骤2:查询数据库 with self.mysql_conn.cursor() as cursor: sql = "SELECT id, username, email, created_at FROM users WHERE id = %s" cursor.execute(sql, (user_id,)) user_data = cursor.fetchone() if not user_data: print(f"[DB NOT FOUND] User {user_id} does not exist") return None # 步骤3:写入缓存(延迟加载) # 使用 JSON 序列化以支持复杂数据结构 serialized = json.dumps(user_data, default=str) # default=str 处理日期等类型 self.redis_client.setex(cache_key, self.cache_ttl, serialized) print(f"[CACHE SET] User {user_id} cached for {self.cache_ttl}s") return user_data def update_user(self, user_id: int, new_data: Dict[str, Any]) -> bool: """ 写操作:先更新数据库,再删除缓存 保证最终一致性:新数据写库后,删除旧缓存,下次读时加载新数据 """ print(f"[WRITE] Updating user {user_id} in database...") try: with self.mysql_conn.cursor() as cursor: # 步骤1:更新数据库 sql = """ UPDATE users SET username = %s, email = %s WHERE id = %s """ cursor.execute(sql, ( new_data.get('username'), new_data.get('email'), user_id )) self.mysql_conn.commit() print(f"[DB UPDATE] User {user_id} updated successfully") # 步骤2:删除缓存(关键步骤!) cache_key = f"user:{user_id}" deleted_count = self.redis_client.delete(cache_key) if deleted_count > 0: print(f"[CACHE DELETE] Cache for user {user_id} removed") else: print(f"[CACHE DELETE] No cached data for user {user_id}") return True except Exception as e: print(f"[ERROR] Failed to update user {user_id}: {str(e)}") self.mysql_conn.rollback() return False def close(self): """清理资源""" self.redis_client.close() self.mysql_conn.close()# 使用示例if __name__ == "__main__": manager = CacheAsideManager() # 读操作测试 user = manager.get_user(1001) print(f"Read user: {user}") # 再次读取(应该命中缓存) user_again = manager.get_user(1001) print(f"Read again (cached): {user_again}") # 写操作测试 success = manager.update_user(1001, { 'username': 'new_name', 'email': 'new@example.com' }) print(f"Update result: {success}") # 读取更新后的数据(从数据库加载新数据到缓存) updated_user = manager.get_user(1001) print(f"Updated user: {updated_user}") manager.close()### 示例2:高并发场景下的 Cache-Aside 优化(带防缓存击穿)pythonimport threadingimport randomimport timefrom functools import wrapsclass OptimizedCacheAside: """ 优化版 Cache-Aside:解决缓存击穿、雪崩问题 使用互斥锁防止缓存击穿(热点数据重建时) 使用随机 TTL 防止缓存雪崩 """ def __init__(self): # 模拟缓存和数据库(实际环境用 Redis/MySQL) self.cache = {} self.database = { 1: {'name': 'Alice', 'age': 25}, 2: {'name': 'Bob', 'age': 30}, 3: {'name': 'Charlie', 'age': 35}, } self.locks = {} # 每个 key 有独立的锁 self.lock = threading.Lock() self.cache_ttl_base = 300 # 基础 TTL 5 分钟 def _get_lock(self, key: str) -> threading.Lock: """获取或创建 key 对应的锁""" with self.lock: if key not in self.locks: self.locks[key] = threading.Lock() return self.locks[key] def get_user(self, user_id: int) -> dict: """ 读操作:带防缓存击穿机制的 Cache-Aside 使用互斥锁确保同一时间只有一个线程重建缓存 """ cache_key = f"user:{user_id}" # 步骤1:先查缓存(快速路径) if cache_key in self.cache: cached = self.cache[cache_key] if cached['expire_time'] > time.time(): print(f"[Thread-{threading.get_ident()}] Cache HIT for user {user_id}") return cached['data'] else: # 缓存过期,删除旧缓存 del self.cache[cache_key] # 步骤2:获取锁,防止缓存击穿 key_lock = self._get_lock(cache_key) acquired = key_lock.acquire(timeout=3) # 最多等 3 秒 if not acquired: # 如果超时未获取锁,直接查数据库(降级策略) print(f"[Thread-{threading.get_ident()}] Lock timeout, falling back to DB") return self._query_database(user_id) try: # 步骤3:二次检查缓存(防止其他线程已加载) if cache_key in self.cache: cached = self.cache[cache_key] if cached['expire_time'] > time.time(): print(f"[Thread-{threading.get_ident()}] Double-check: cache already loaded") return cached['data'] # 步骤4:查询数据库 print(f"[Thread-{threading.get_ident()}] Loading user {user_id} from database...") time.sleep(0.1) # 模拟数据库查询延迟 user_data = self._query_database(user_id) # 步骤5:写入缓存(带随机 TTL 防止雪崩) random_ttl = self.cache_ttl_base + random.randint(-60, 60) # ±1分钟随机 self.cache[cache_key] = { 'data': user_data, 'expire_time': time.time() + random_ttl } print(f"[Thread-{threading.get_ident()}] Cached user {user_id} with TTL {random_ttl}s") return user_data finally: key_lock.release() def _query_database(self, user_id: int) -> dict: """模拟数据库查询""" time.sleep(0.05) # 模拟 I/O 延迟 data = self.database.get(user_id) if data is None: raise ValueError(f"User {user_id} not found") return data.copy() def update_user(self, user_id: int, new_data: dict) -> bool: """ 写操作:更新数据库并删除缓存(保证一致性) """ # 步骤1:更新数据库 print(f"Updating user {user_id} in database...") time.sleep(0.1) # 模拟数据库写入延迟 self.database[user_id] = new_data # 步骤2:删除缓存 cache_key = f"user:{user_id}" if cache_key in self.cache: del self.cache[cache_key] print(f"Cache for user {user_id} deleted") return True# 并发测试def concurrent_read_test(): """模拟高并发读操作""" manager = OptimizedCacheAside() threads = [] # 创建 10 个线程同时读取同一个用户 for _ in range(10): t = threading.Thread(target=lambda: manager.get_user(1)) threads.append(t) print("Starting concurrent reads for user 1...") start_time = time.time() for t in threads: t.start() for t in threads: t.join() elapsed = time.time() - start_time print(f"All reads completed in {elapsed:.3f}s") print(f"Only one thread should have called the database")if __name__ == "__main__": concurrent_read_test()## 3. 关键设计要点与陷阱### 3.1 为什么写操作要删除缓存而不是更新?-删除更简单:避免复杂的数据同步逻辑-避免并发问题:如果更新缓存和数据库操作不是原子的,可能导致脏数据-延迟加载天然解决一致性:下次读操作会从数据库拉取最新数据### 3.2 最终一致性带来的问题-短暂的不一致窗口:更新数据库 -> 删除缓存期间,其他线程可能读到旧缓存-解决方案: - 对于强一致性要求的场景,可以使用"先删缓存后更新DB"(但可能引发更多问题) - 使用消息队列异步更新缓存 - 设置合理的缓存过期时间### 3.3 缓存雪崩与击穿防护| 问题 | 原因 | 解决方案 ||------|------|----------|| 缓存雪崩 | 大量缓存同时过期 | 随机 TTL、多级缓存 || 缓存击穿 | 热点数据过期后高并发请求 | 互斥锁重建缓存 || 缓存穿透 | 查询不存在的数据 | 布隆过滤器、空值缓存 |## 4. 性能优化实践python# 批量加载优化:pipeline 减少网络开销def batch_get_users(self, user_ids: list) -> dict: """批量获取用户,使用 Redis pipeline 优化""" pipe = self.redis_client.pipeline() # 步骤1:批量查询缓存 for uid in user_ids: pipe.get(f"user:{uid}") cache_results = pipe.execute() # 步骤2:找出未命中的 ID miss_ids = [] for i, result in enumerate(cache_results): if result is None: miss_ids.append(user_ids[i]) # 步骤3:批量查询数据库 if miss_ids: with self.mysql_conn.cursor() as cursor: format_strings = ','.join(['%s'] * len(miss_ids)) sql = f"SELECT * FROM users WHERE id IN ({format_strings})" cursor.execute(sql, miss_ids) db_results = cursor.fetchall() # 步骤4:批量写入缓存 pipe = self.redis_client.pipeline() for user in db_results: pipe.setex(f"user:{user['id']}", self.cache_ttl, json.dumps(user)) pipe.execute()## 5. 总结Cache-Aside 模式是应用层缓存管理的基石,它的优势在于:1.简单可靠:读时延迟加载、写时删除缓存的逻辑直观,容易理解和维护2.性能优秀:适合读多写少场景,能显著降低数据库负载(可实现 90%+ 的缓存命中率)3.灵活性高:可以针对不同数据设置不同 TTL、缓存策略但也要注意:-最终一致性不是银弹,对一致性要求极高的场景(如金融交易)需要额外处理-缓存击穿/雪崩需要提前设计防护机制-写操作后的缓存删除必须确保成功(可通过重试机制或异步删除)在实际项目中,建议将 Cache-Aside 作为默认缓存策略,配合布隆过滤器防穿透、互斥锁防击穿、随机 TTL 防雪崩,可以构建一个稳定高效的缓存系统。记住:缓存是系统加速器,但永远不能替代数据库作为数据权威来源。