Cache-Aside 应用层主动管理缓存:读时延后加载,写时更新DB并删除缓存 最终一致性 读操作可能延迟 低 最常用,读多写少, ...
Cache-Aside 模式实战应用层主动管理缓存的完整指南1. 什么是 Cache-Aside 模式Cache-Aside旁路缓存是应用层主动管理缓存的最常用模式。它的核心思想是应用程序直接管理缓存的读写而不是通过缓存中间件自动处理。这种模式特别适合读多写少的场景例如用户信息查询、商品详情展示、配置数据读取等。### 核心流程-读操作先查缓存缓存未命中则查数据库然后将数据写入缓存延迟加载-写操作先更新数据库然后删除缓存确保数据一致性### 为什么选择 Cache-Aside- 实现简单对现有系统侵入性小- 能有效降低数据库负载提升读性能- 最终一致性可接受短暂的数据不一致## 2. 实战代码示例Python 实现### 示例1基础 Cache-Aside 实现带日志和超时机制pythonimport timeimport jsonimport redisimport pymysqlfrom typing import Optional, Dict, Anyclass CacheAsideManager: Cache-Aside 模式管理器 支持读时延迟加载、写时删除缓存、自动过期 def __init__(self, redis_hostlocalhost, redis_port6379, mysql_hostlocalhost, mysql_userroot, mysql_passwordpassword, mysql_dbtest): # 初始化 Redis 连接 self.redis_client redis.Redis( hostredis_host, portredis_port, decode_responsesTrue # 自动解码字节串为字符串 ) # 初始化 MySQL 连接池 self.mysql_conn pymysql.connect( hostmysql_host, usermysql_user, passwordmysql_password, databasemysql_db, charsetutf8mb4, cursorclasspymysql.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 fuser:{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, defaultstr) # defaultstr 处理日期等类型 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 fuser:{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(fRead user: {user}) # 再次读取应该命中缓存 user_again manager.get_user(1001) print(fRead again (cached): {user_again}) # 写操作测试 success manager.update_user(1001, { username: new_name, email: newexample.com }) print(fUpdate result: {success}) # 读取更新后的数据从数据库加载新数据到缓存 updated_user manager.get_user(1001) print(fUpdated 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 fuser:{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(timeout3) # 最多等 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(fUser {user_id} not found) return data.copy() def update_user(self, user_id: int, new_data: dict) - bool: 写操作更新数据库并删除缓存保证一致性 # 步骤1更新数据库 print(fUpdating user {user_id} in database...) time.sleep(0.1) # 模拟数据库写入延迟 self.database[user_id] new_data # 步骤2删除缓存 cache_key fuser:{user_id} if cache_key in self.cache: del self.cache[cache_key] print(fCache for user {user_id} deleted) return True# 并发测试def concurrent_read_test(): 模拟高并发读操作 manager OptimizedCacheAside() threads [] # 创建 10 个线程同时读取同一个用户 for _ in range(10): t threading.Thread(targetlambda: 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(fAll reads completed in {elapsed:.3f}s) print(fOnly 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(fuser:{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 fSELECT * 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(fuser:{user[id]}, self.cache_ttl, json.dumps(user)) pipe.execute()## 5. 总结Cache-Aside 模式是应用层缓存管理的基石它的优势在于1.简单可靠读时延迟加载、写时删除缓存的逻辑直观容易理解和维护2.性能优秀适合读多写少场景能显著降低数据库负载可实现 90% 的缓存命中率3.灵活性高可以针对不同数据设置不同 TTL、缓存策略但也要注意-最终一致性不是银弹对一致性要求极高的场景如金融交易需要额外处理-缓存击穿/雪崩需要提前设计防护机制-写操作后的缓存删除必须确保成功可通过重试机制或异步删除在实际项目中建议将 Cache-Aside 作为默认缓存策略配合布隆过滤器防穿透、互斥锁防击穿、随机 TTL 防雪崩可以构建一个稳定高效的缓存系统。记住缓存是系统加速器但永远不能替代数据库作为数据权威来源。

相关新闻

3步掌握HTTrack:将任何网站完整保存到本地的终极指南

3步掌握HTTrack:将任何网站完整保存到本地的终极指南

3步掌握HTTrack:将任何网站完整保存到本地的终极指南 【免费下载链接】httrack HTTrack Website Copier, copy websites to your computer (Official repository) 项目地址: https://gitcode.com/gh_mirrors/ht/httrack 你是否曾需要离线访问某个网站&#x…

2026/10/17 9:57:12 阅读更多 →
ComfyUI-Manager终极指南:三步打造你的AI绘画扩展中心

ComfyUI-Manager终极指南:三步打造你的AI绘画扩展中心

ComfyUI-Manager终极指南:三步打造你的AI绘画扩展中心 【免费下载链接】ComfyUI-Manager ComfyUI-Manager is an extension designed to enhance the usability of ComfyUI. It offers management functions to install, remove, disable, and enable various custo…

2026/10/18 23:52:15 阅读更多 →
一键备份QQ空间说说:GetQzonehistory终极指南,永久保存你的青春记忆

一键备份QQ空间说说:GetQzonehistory终极指南,永久保存你的青春记忆

一键备份QQ空间说说:GetQzonehistory终极指南,永久保存你的青春记忆 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 还记得那些年你在QQ空间留下的青春足迹吗&am…

2026/10/20 1:08:54 阅读更多 →

最新新闻

轻量化MES如何破解离散制造多品种小批量的生产乱象

轻量化MES如何破解离散制造多品种小批量的生产乱象

多品种、小批量的离散制造现场,经常是一片"看似忙碌、实则混乱"的景象。订单碎片化、工艺路线五花八门、物料配套总是差那么几件、车间主任的手机一天到晚响个不停,问的最多的就是"那批货到底干到哪了"。这种乱象我见过太多&#xf…

2026/10/20 5:13:00 阅读更多 →
Qt原生日志系统:多路输出、环形缓冲与跨平台调试方案

Qt原生日志系统:多路输出、环形缓冲与跨平台调试方案

简介:这是一套面向Qt中高级开发者调试与运维需求的日志系统实现方案,解决传统Qt默认输出(如控制台或调试器)无法持久化、可视化及远程协同的问题。资源完整封装了日志的本地文件保存(含自动过期清理)、GUI窗…

2026/10/20 5:13:00 阅读更多 →
AI辅助写专著:一套可复制的人机协作工作流

AI辅助写专著:一套可复制的人机协作工作流

写专著这件事,经历过的人都懂:不是“写不出来”的问题,而是“要写的东西太多、时间永远不够、改到最后看到自己的稿子都想吐”。尤其是学术类、技术类专著,动辄几十万字,从选题、搭框架、查文献、写初稿,到…

2026/10/20 5:13:00 阅读更多 →
C# WinForm离线人脸识别系统:LBPH活体检测+SQLite人脸库

C# WinForm离线人脸识别系统:LBPH活体检测+SQLite人脸库

简介:本资源是一套基于C#与OpenCV实现的实时人脸识别完整项目,面向具备基础C#编程能力的初学者及计算机视觉入门开发者,解决人脸检测、识别与界面交互的一体化实践需求,适用于课程设计、毕业设计或小型安防场景原型开发。压缩包共…

2026/10/20 5:13:00 阅读更多 →
REA:基于MCP让编码智能体读懂ELF/PE二进制文件

REA:基于MCP让编码智能体读懂ELF/PE二进制文件

编码智能体现在写代码、改bug、做重构都像模像样,可一遇到二进制文件就成了黑箱前打转的人。上周我帮同事排查一个段错误,日志里只有一条地址,他把对应的.so丢给AI助手,助手只回了一句"无法读取二进制文件"。这个叫 REA…

2026/10/20 5:13:00 阅读更多 →
生产者消费者模型实战:从wait/notify到BlockingQueue与线程池

生产者消费者模型实战:从wait/notify到BlockingQueue与线程池

1. 为什么说生产者消费者模型是并发编程的第一课做后端开发这几年,我越来越确信一件事:生产者消费者模型的实现水平,基本就是你并发编程水平的缩影。面试里问它,不是为了让你背一段 wait/notify 模板代码,而是想看你有…

2026/10/20 5:12:00 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →