Cache-Aside 应用层主动管理缓存:读时延后加载,写时更新DB并删除缓存 最终一致性 读操作可能延迟 低 最常用,读多写少, ...

发布时间:2026/11/11 17:50:55

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 防雪崩可以构建一个稳定高效的缓存系统。记住缓存是系统加速器但永远不能替代数据库作为数据权威来源。
延伸阅读

更多相关文章

2026/11/11 16:08:05

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/22 22:43:23

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/11/11 17:45:55

高级电工证高压证过期了不知道怎么复审延期?保姆级教程来了

高级电工证高压证过期了不知道怎么复审延期?保姆级教程来了 证书过期了不知道怎么复审延期,这事儿别慌,今天就给你讲明白。很多人拿到高级电工证高压证后,以为一劳永逸了,其实不然。证书有效期通常为6年,过了就要复审,不复审就等于无证上岗,轻则被单…

2026/11/11 17:45:55

轻轨需要考电工证吗?考前押题帮你一次性通过

轻轨需要考电工证吗?考前押题帮你一次性通过 怕考不过白交培训费?别慌,本文用真实案例帮你搞懂轻轨岗位到底要不要考电工证,还有考前押题怎么用,直接提高通过率。 轻轨岗位到底要不要考电工证? 轻轨需要考电工证吗?答案是:视岗位职责而定。…

2026/11/11 17:45:55

电工证特种作业报名千万别踩坑,怎么一次过才不亏

电工证特种作业报名千万别踩坑,怎么一次过才不亏 怕考不过白交培训费?这年头,电工证特种作业报名不光是想干这行的刚需,更是不少工头、班组长眼里的“命根子”。尤其是安阳劳务班组负责人,手上项目多,人手紧,要是电工证考不过,不光耽误工期,培训费还…

2026/11/11 17:45:55

电工作业守护电力怎么考?学历不高也能拿电子证书

电工作业守护电力怎么考?学历不高也能拿电子证书 你是不是也担心学历不够,考不了电工作业守护电力的电子证书?其实不用太焦虑,这个证书对学历并没有硬性要求,关键在于你是否愿意去了解流程和准备资料。…

2026/11/11 17:45:55

鹰潭考电工证在哪报名千万别踩坑,外省证能转吗?真实经验告诉你

鹰潭考电工证在哪报名千万别踩坑,外省证能转吗?真实经验告诉你 之前考的证在外省能不能转过来?这个问题,我见过太多人问,也踩过不少坑。特别是在鹰潭,很多电工、焊工、高处作业的小伙伴都面临证书转接、报考、复审的问题,稍有不慎,就可能耽误工作,甚…

2026/11/11 17:40:53

南充电工证有哪些怎么考?电子证书怎么查?别被中介坑了

南充电工证有哪些怎么考?电子证书怎么查?别被中介坑了 你是不是正愁不知道去哪报名南充电工证?怕被中介坑?别急,本文给你一套完整的报考流程,帮你从零开始搞定南充电工证怎么考、电子证书怎么查、复审怎么操作,还能避开那些坑。…

2026/11/11 12:42:59

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/11 0:02:26

考低压电工证通过率考试真题与备考技巧

低压电工证通过率高不高?学历不高也能考,值不值得考? 你是不是总听说电工证能帮你找高薪工作,但又担心自己学历不够,考不上?其实,低压电工证考试门槛并没有你想象中那么高,关键是方法对不对。这篇文章就告诉你, 考低压电工证通过率到底有多高 ,…

2026/11/11 0:02:26

低压电工证学时千万别踩坑,濮阳毕业生怎么避坑报名

低压电工证学时千万别踩坑,濮阳毕业生怎么避坑报名 你是不是也和我一样,刚毕业就听说低压电工证学时必须达标才能上岗?可一打听,发现报名机构五花八门,价格参差不齐, 不知道去哪报名怕被中介坑…

2026/11/10 20:08:15

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/10 10:29:37

SSM电子书店源码全解析:结构、原理、部署与排错实战

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

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑