文章目录
- 题目背景
- 题目 1:Cache-Aside 读数据流程设计
- 题目描述
- 答案与解析
- 题目 2:Cache-Aside 写数据流程设计
- 题目描述
- 答案与解析:重点: 理解先更新库再删除缓存的原理
- 题目 3:多线程下的数据一致性问题
- 题目描述
- 答案与解析
题目背景
为了提升某购物网站的用户使用体验,采用数据库缓存技术将经常访问的商品信息存储在一个临时高速数据存储层中,这个临时存储层使得未来对这些数据的请求响应比通过访问主数据库更快。为了确保快速、及时获取最新数据,项目组决定采用缓存数据技术开展项目设计,经讨论,决定采用Cache-Aside策略作为缓存手段。
题目 1:Cache-Aside 读数据流程设计
题目描述
使用 Cache-Aside 缓存策略,当用户查询商品信息时,此请求会从应用程序服务器发送请求到数据库服务器,通过缓存系统处理,然后返回所请求的信息,请补充完善图1中 (1) ~ (5) 处的内容,协助设计师完成缓存系统中的数据读取模块的设计方案。
答案与解析
答案:
- (1)查询数据并判断是否命中
- (2)缓存命中,返回数据
- (3)缓存未命中,向数据库读数据
- (4)更新缓存
- (5)返回数据
核心概念解释:
Cache-Aside (旁路缓存) 读策略
💡通俗类比:图书馆借书
- 缓存= 你的书桌(拿取方便,但空间有限)
- 数据库= 图书馆书架(书全,但拿取慢)
- 读流程:
- 先看书桌上有没有((1) 查询缓存)。
- 如果有,直接看((2) 命中返回)。
- 如果书桌上没有,就得去书架上找((3) 读数据库)。
- 找到后,顺手把书带回书桌放好,方便下次看((4) 更新缓存)。
- 最后开始阅读((5) 返回数据)。
详细分析:
Cache-Aside 模式是最常用的缓存策略。其核心逻辑是“懒加载”:只有当数据被需要且缓存中没有时,才从数据库加载到缓存。
- 应用层先请求缓存。
- 缓存命中:直接返回数据,结束流程。
- 缓存未命中:应用层去查询数据库。
- 回填:应用层获取到数据后,并发起一个写操作将数据写入缓存。
- 返回:将数据返回给用户。
考点:Cache-Aside 模式、缓存读流程
题目 2:Cache-Aside 写数据流程设计
题目描述
使用 Cache-Aside 缓存策略,当用户更新商品信息时,此请求会从应用服务器发送至数据库服务器,并进行缓存处理,请补充完善图2中 (1) 和 (2) 处的内容,协助工程师完成缓存系统中的数据更新模块的设计方案。
答案与解析:重点: 理解先更新库再删除缓存的原理
答案:
- (1)写入数据(更新数据库)
- (2)删除缓存(失效缓存)
详细分析:
在 Cache-Aside 模式中,写操作的标准流程是:先更新数据库,然后删除缓存。
- 为什么是删除而不是更新缓存?
- 如果是更新缓存,在并发写的情况下,可能出现先后顺序错乱,导致缓存中保留的是旧数据(脏数据)。
- 删除缓存是一种"懒"操作,下次读取时自然会去数据库拉取最新的,保证了最终一致性。
- 为什么先更新数据库再删除缓存?
- 如果先删除缓存,再更新数据库。在两者之间,另一个线程读取数据,发现缓存空了,去读数据库(旧值),然后写入缓存。紧接着数据库更新完成。这样缓存里就一直是旧数据了。
- 虽然"先更库再删存"也存在极端并发问题(读操作在读旧值和写缓存之间,刚好插入了一个写操作),但概率极低,因为写缓存通常比查库快得多。
考点:Cache-Aside 模式、缓存与数据库一致性
题目 3:多线程下的数据一致性问题
题目描述
李工设计了多线程并发数据访问方案,采用线程1执行数据更新,线程2执行数据读取。王工认为这样的设计方案会存在数据不一致现象,请说明李工所设计的方案为什么会出现数据库与缓存中的数据不一致的情况,并给出3种或以上的解决方案。
答案与解析
答案:
原因分析:
根本原因在于 Cache-Aside 策略的非原子性。更新操作(写库+删存)和读取操作(读存+读库+写存)在多线程环境下无法保证严格的时序一致性,导致出现短暂的数据状态不一致窗口。
解决方案(任选3种):
- 锁机制:引入读写锁,保证更新期间无法读取。
- 异步队列方式同步:利用消息中间件(如 Kafka)进行重试或顺序处理。
- 通过数据库插件完成数据同步:如监听 MySQL Binlog 自动更新缓存。
- 延迟双删策略:先删缓存 -> 更新数据库 -> 休眠片刻 -> 再删缓存。
详细分析:
针对线程 1 写数据、线程 2 读缓存时可能出现的数据不一致问题,以下是几种主流解决方案的详细解析:
异步队列方式同步(消息中间件)
- 原理:利用 Kafka/RabbitMQ 等消息队列。当线程 1 更新完数据库后,发一个消息到队列。有一个消费者线程专门盯着队列,收到消息后去更新或删除缓存。
- 优点:解耦,保证最终一致性(这里做了trade off),且带有重试机制(如果删缓存失败可以重试)。
通过数据库插件完成数据同步(Binlog 监听)
- 原理:使用如 Canal 等工具,伪装成数据库的从库,监听主库的 Binlog(操作日志)。一旦发现数据库有变动,工具自动解析日志并去更新缓存。
- 优点:代码侵入性小,完全由后台服务自动完成同步,应用层不需要关心缓存更新逻辑。
锁机制(读写锁)
- 原理:在代码层加锁。更新数据时加写锁,此时任何人都不能读;读数据时加读锁。
- 优点:强一致性,绝对不会读到脏数据。
- 缺点:并发性能大大降低,锁竞争可能导致系统变慢。
延迟双删策略
- 原理:为了防止"先删缓存,再更库"期间被别人读了旧值又写回缓存。我们可以在更完库之后,等一会(延迟),再删一次缓存。确保把刚才那个空档期可能产生的脏数据删掉。
考点:并发控制、分布式系统一致性、缓存更新策略