分布式锁-缓存一致性问题-失效模式

news/2024/10/4 3:32:54/文章来源:https://blog.csdn.net/Flying_Fish_roe/article/details/142007344

分布式锁与缓存一致性问题:失效模式

在分布式系统中,缓存一致性问题是一个复杂且常见的挑战。尤其是当我们使用缓存来加速数据访问时,确保缓存与底层数据库之间的数据一致性变得尤为重要。在实际系统中,由于并发、缓存失效策略以及数据的异步处理,可能会导致缓存和数据库数据不一致,进而影响业务系统的正确性。

为了应对这一问题,分布式锁常常被用来保障在高并发场景下,多个节点对同一数据的更新操作不会发生冲突。分布式锁能够确保只有一个线程或进程能够执行特定的业务逻辑,而其他请求则需要等待锁释放。然而,即便使用了分布式锁,缓存与数据库之间依然可能出现不一致的情况,尤其是在涉及缓存更新的场景中。

1. 缓存与数据库的一致性

缓存与数据库一致性问题常见于以下场景:

  • 写操作时:当应用程序对数据库写入新的数据时,缓存中可能还存有旧数据,如果不及时更新或删除缓存,则可能出现脏读现象。
  • 读操作时:在高并发环境下,多个线程可能会同时访问缓存和数据库,缓存的数据可能已经过期或被更新,但依然被读出,导致脏数据返回给用户。
2. 常见的缓存更新策略

在分布式系统中,缓存一致性通常有以下几种策略:

  1. Cache Aside(旁路缓存模式)
       - :应用先从缓存中获取数据,如果缓存中没有命中,则从数据库加载数据,并将数据写入缓存。
       - :在更新数据时,先更新数据库,然后删除缓存。
       
       这种模式下的常见问题是:当多个并发请求同时操作缓存和数据库时,容易导致数据不一致的问题。

  2. Write Through(写穿缓存模式)
       - 数据的写操作同时写入缓存和数据库,以确保缓存与数据库的一致性。
       
       这种模式的缺点是:每次写操作都涉及缓存更新和数据库操作,可能会增加延迟。

  3. Write Behind(异步写缓存模式)
       - 数据首先写入缓存,缓存更新后异步更新数据库。
       
       此模式的风险在于:如果异步更新失败,数据可能会丢失,且在高并发场景下会出现一致性问题。

3. 失效模式:缓存不一致的典型场景

失效模式是指缓存与数据库出现短暂的不一致状态的典型场景。常见的情况包括:

  • 更新缓存失败:数据库更新成功,但缓存删除或更新失败,导致缓存中存有旧数据。
  • 缓存回源延迟:缓存数据失效后,如果多个请求同时回源数据库更新缓存,可能出现缓存击穿,甚至导致缓存和数据库不一致。
  • 并发读写问题:多个线程并发操作缓存和数据库时,可能导致缓存未及时更新或被覆盖。

4. 分布式锁与缓存一致性问题

在分布式系统中,分布式锁可以在一定程度上解决缓存与数据库的一致性问题,但它也有其局限性。典型的分布式锁与缓存不一致的场景如下:

4.1 更新数据库并删除缓存场景下的并发问题

让我们看一个常见的场景:应用在更新数据库后,会删除缓存,让下一个请求重新从数据库中获取数据并更新缓存。然而,在高并发环境下,这种方式容易产生缓存不一致问题:

  • 场景描述
      1. 线程 A 从缓存中读取到数据。
      2. 线程 B 更新了数据库,并删除了缓存。
      3. 线程 A 将读取到的旧数据重新写入缓存,覆盖了线程 B 的更新。

  • 导致的问题:线程 B 的更新被覆盖,缓存中的数据成了旧的脏数据。

解决方案:使用分布式锁保证更新数据库和缓存操作的顺序性。下面是具体的步骤:

  1. 获取分布式锁。
  2. 先更新数据库,再删除缓存。
  3. 释放分布式锁。

实现代码示例

import redis.clients.jedis.Jedis;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class CacheConsistencyService {private Jedis jedis;private Lock lock = new ReentrantLock();public CacheConsistencyService(Jedis jedis) {this.jedis = jedis;}// 更新数据库并删除缓存的操作public void updateData(String key, String newValue) {// 加锁,确保更新过程是原子性的lock.lock();try {// 更新数据库updateDatabase(key, newValue);// 删除缓存,确保下一次从数据库读取新的值jedis.del(key);} finally {// 释放锁lock.unlock();}}// 模拟数据库更新private void updateDatabase(String key, String newValue) {System.out.println("Updating database with key: " + key + " and value: " + newValue);}// 从缓存或数据库读取数据public String getData(String key) {// 先从缓存获取String value = jedis.get(key);if (value == null) {// 缓存不存在,从数据库加载value = loadFromDatabase(key);// 更新缓存jedis.set(key, value);}return value;}// 模拟从数据库读取数据private String loadFromDatabase(String key) {System.out.println("Loading data from database for key: " + key);return "DBValueFor" + key;}
}

解释

  • 使用 ReentrantLock 或 Redis 分布式锁来确保只有一个线程能执行更新操作,从而避免多个线程导致数据不一致问题。
  • 在锁的保护下,更新数据库后立即删除缓存,确保缓存中不会存有过期数据。
4.2 延迟双删策略

为了解决缓存和数据库的并发更新问题,另一种较为常见的策略是延迟双删策略

  • 策略描述:当更新数据库后,先删除缓存,等待一段时间后再次删除缓存,以避免并发情况下的缓存不一致问题。

  • 实现流程
      1. 更新数据库。
      2. 删除缓存。
      3. 等待 500ms(可根据业务情况调整)。
      4. 再次删除缓存,确保在并发读写情况下,缓存不会保存旧值。

延迟双删策略的代码实现

public class CacheServiceWithDelayedDeletion {private Jedis jedis;public CacheServiceWithDelayedDeletion(Jedis jedis) {this.jedis = jedis;}// 更新数据库并删除缓存public void updateData(String key, String newValue) {// 更新数据库updateDatabase(key, newValue);// 删除缓存jedis.del(key);// 延迟一段时间再次删除缓存try {Thread.sleep(500); // 等待500ms} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 再次删除缓存jedis.del(key);}// 模拟数据库更新private void updateDatabase(String key, String newValue) {System.out.println("Updating database with key: " + key + " and value: " + newValue);}
}

策略优点

  • 即使在高并发场景下,也能有效避免旧数据重新被写入缓存的情况。
      
    策略缺点
  • 延迟的时间窗口设置不当可能会影响缓存的准确性。时间过短,无法解决并发问题;时间过长,影响系统性能。
5. 分布式锁的实现

分布式锁可以使用 Redis 实现,常用的方式有:

  1. Redis 的 SETNX 命令:用于加锁。
  2. Redis 的 Lua 脚本:用于保证锁的原子性释放。
// 加锁
public boolean acquireLock(String key, String requestId, int expireTime) {String result = jedis.set(key, requestId, "NX", "EX", expireTime);return "OK".equals(result);
}// 释放锁
public boolean releaseLock(String key, String requestId) {String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +"return redis.call('del', KEYS[1]) " +"else return 0 end";Object result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(requestId));return "1".equals(result.toString());
}

注意事项

  • 使用 Redis 实现分布式锁时要确保锁的原子性,并设置合理的锁过期时间,避免死锁问题。
6. 总结

缓存一致性问题

在高并发分布式系统中是非常常见且棘手的问题。常见的解决方法包括使用分布式锁、延迟双删、互斥锁等手段来确保缓存与数据库的数据一致性。每种方案都有其适用场景和优缺点,开发者需要根据具体业务场景选择最合适的方案。

关键点:

  • 使用分布式锁保证缓存和数据库更新的顺序性。
  • 延迟双删策略确保在并发下缓存与数据库的最终一致性。
  • 根据业务需求选择适当的缓存更新策略,并结合实际场景优化锁的使用和缓存策略。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ldbm.cn/p/439950.html

如若内容造成侵权/违法违规/事实不符,请联系编程新知网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】MySQL基础

目录 什么是数据库主流数据库基本使用MySQL的安装连接服务器服务器、数据库、表关系使用案例数据逻辑存储 MySQL的架构SQL分类什么是存储引擎 什么是数据库 mysql它是数据库服务的客户端mysqld它是数据库服务的服务器端mysql本质:基于C(mysql&#xff09…

RTMP和WebRTC使用场景有哪些差别?

省流版先说结论 直播领域,RTMP和WebRTC各有优势。如果直播场景对延迟有一定要求,但更注重稳定性和兼容性,那么RTMP可能是一个更好的选择。如果直播场景需要极低的延迟,并且用户主要在浏览器环境下进行观看和互动,那么…

开源AI市场情况概览:2024年的现状与发展

开源AI的快速发展 开源AI领域的显著进展:2024年,开源AI迅速发展,带动了生成式AI领域的重大创新。得益于GitHub和Hugging Face等平台,研究与开发者社区推出了许多具有突破性的项目,这些项目取得了令人瞩目的成果。 贡献者与项目增长:2023年,GitHub上的贡献者数量增加了…

SpringBoot与Minio的极速之旅:解锁文件切片上传新境界

目录 一、前言 二、对象存储(Object Storage)介绍 (1)对象存储的特点 (2)Minio 与对象存储 (3)对象存储其他存储方式的区别 (4)对象存储的应用场景 三、…

根据NVeloDocx Word模板引擎生成Word(二)

前面讲到了根据“永久免费开放的E6低代码开发平台”的NVeloDocx Word模版引擎生成Word文件的基础取数方法,包括取本表单字段以及引用字段,详见《根据NVeloDocx Word模板引擎生成Word(一)》。 针对这种基本的取数方法,…

【Python知识宝库】正则表达式在Python中的应用:字符串模式匹配利器

🎬 鸽芷咕:个人主页 🔥 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想,就是为了理想的生活! 文章目录 前言一、正则表达式基础语法1. 普通字符2. 元字符 二、Python中的正则表达式模块1. re.match2. re.search3. re.finda…

mycat双主高可用架构部署-mycat安装

MySQL5.7服务器IP是192.168.31.209及192.168.31.210 1、安装Java运行环境 ELK搭建日志平台里面有Java安装,同样也可以采用yum安装 yum install -y java java -version 2、下载并解压mycat wget http://dl.mycat.org.cn/1.6.7.6/20220524101549/Mycat-server-1.…

记一次学习--内网穿透

目录 环境搭建 两张网卡如何配置 Ubuntu配置 渗透 ubuntu的拿下 centos的拿下 探测内网环境 fscan扫描 msf上马 渗透 拿下bage cms windows的拿下 ​编辑 使用fscan查看内网环境,发现了192.168.110.128这台设备 使用msf上马,现在这台机器是…

嵌入式Openharmony系统构建与启动详解

大家好,今天主要给大家分享一下,如何构建Openharmony子系统以及系统的启动过程分解。 第一:OpenHarmony系统构建 首先熟悉一下,构建系统是一种自动化处理工具的集合,通过将源代码文件进行一系列处理,最终生成和用户可以使用的目标文件。这里的目标文件包括静态链接库文件…

Shader 渲染路径

实际的游戏开发中,场景中的光源肯定是更多、更复杂的,如果只有一个平行光的处理,完全不能满足需求。处理更多的光源,我们就需要了解Unity底层是如何处理这些光源的。 1、渲染路径是什么 渲染路径(Rendering Path&…

【鸿蒙HarmonyOS NEXT】UIAbility的生命周期

【鸿蒙HarmonyOS NEXT】UIAbility的生命周期 一、环境说明二、UIAbility的生命周期三、示例代码加以说明四、小结 一、环境说明 DevEco Studio 版本: API版本:以12为主 二、UIAbility的生命周期 概念: HarmonyOS中的UIAbility是一种包含…

线性代数|机器学习-P36在图中找聚类

文章目录 1. 常见图结构2. 谱聚类 感觉后面几节课的内容跨越太大,需要补充太多的知识点,教授讲得内容跨越较大,一般一节课的内容是书本上的一章节内容,所以看视频比较吃力,需要先预习课本内容后才能够很好的理解教授讲…

Shire.run:Prompt 即代码到 Prompt 即程序,思考 Prompt 的无限可能性

TL;DR:https://shire.run/ 随着 Shire 的持续迭代,我们有了一些新的体会和感触,即 Prompt 不仅仅是一段提示词,而是可以直接执行的代码。而当是可执行的代码时,就是可执行、 可共享的智能体。因此&#xff…

从零开始搭建GPU深度学习环境(pytorch)

傻乎乎的我,突然发现我自己的笔记本电脑居然有gpu,这个电脑是我弟在2017年购入的。 电脑已经按照了cpu环境,现在增加gpu环境 参考torch的cpu版本和gpu版本有什么区别 torch与cuda版本_mob64ca13f6035c的技术博客_51CTO博客 前言&#xff1a…

线性调频信号脉冲压缩

注意,如果用线性调频信号频谱省略前面常数因子,得到的脉冲压缩信号也会有一个幅度的不同

微信小程序登录与获取手机号 (Python)

文章目录 相关术语登录逻辑登录设计登录代码 相关术语 调用接口[wx.login()]获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户在当前小程序的唯一标识(openid)、微信开放平台账号下的唯一标识&#xff0…

机器学习之 PCA降维

1.PCA 降维简介 主成分分析(Principal Component Analysis, PCA)是一种统计方法,用于在数据集中寻找一组线性组合的特征,这些特征被称为主成分。PCA 的目标是通过变换原始特征空间到新的特征空间,从而减少数据的维度&…

Pygame中获取鼠标按键状态的方法

在《Pygame中获取鼠标位置的方法》中提到,可以通过鼠标事件和mouse模块中的函数获取鼠标位置,这两种方法同样适用于获取鼠标按键状态。 1 通过鼠标点击事件获取鼠标按键状态 通过鼠标点击事件获取鼠标按键状态的代码如图1所示。 图1 鼠标点击事件获取鼠…

C++ | Leetcode C++题解之第390题消除游戏

题目: 题解: class Solution { public:int lastRemaining(int n) {int a1 1;int k 0, cnt n, step 1;while (cnt > 1) {if (k % 2 0) { // 正向a1 a1 step;} else { // 反向a1 (cnt % 2 0) ? a1 : a1 step;}k;cnt cnt >> 1;step …

Windows环境下VS2022编译 Speex 源码

Speex Speex 是一个开源的语音压缩格式,专为语音数据设计,提供了高压缩率的同时保持较低的比特率,适合网络传输。它采用了先进的编码算法,能够在保证声音质量的同时,大幅度降低文件大小,特别适用于实时通信…