当前位置: 首页 > news >正文

【Redis】Redis的双写问题

在分布式系统中,双写问题通常是指数据在多个存储系统(例如数据库和缓存)中更新时出现的不一致性。这种问题在使用 Redis 作为缓存层时尤为常见。具体来说,当数据在数据库和 Redis 缓存中存在副本时,任何对数据的更新操作都需要在两个地方进行,即“双写”。这可能导致以下几种问题:

  1. 缓存数据和数据库数据不一致

    • 数据库更新成功,缓存更新失败。
    • 缓存更新成功,数据库更新失败。
    • 数据库和缓存的更新顺序不同步。
  2. 缓存击穿、穿透、雪崩

    • 缓存击穿:热点数据失效,大量请求同时访问数据库。
    • 缓存穿透:查询不存在的数据,直接穿透到数据库。
    • 缓存雪崩:大量缓存数据在同一时间失效,导致大量请求直接访问数据库。

解决双写问题的方法:

1. Cache Aside Pattern(旁路缓存模式)

这是最常用的缓存策略。流程如下:

  • 读操作
    1. 先从缓存中读取数据。
    2. 如果缓存中没有数据,从数据库中读取数据,然后将数据写入缓存。
  • 写操作
    1. 更新数据库。
    2. 使缓存中的数据失效或更新缓存。

示例代码

public class CacheAsidePattern {private RedisCache redisCache;private Database database;public Data getData(String key) {// 从缓存中读取数据Data data = redisCache.get(key);if (data == null) {// 如果缓存中没有数据,从数据库中读取数据data = database.get(key);// 将数据写入缓存redisCache.put(key, data);}return data;}public void updateData(String key, Data newData) {// 更新数据库database.update(key, newData);// 使缓存中的数据失效或更新缓存redisCache.delete(key);}
}

优点

  • 实现简单,常见的使用模式。
  • 读取效率高,避免了频繁访问数据库。

缺点

  • 在高并发场景下,可能会出现短暂的不一致性。
  • 数据在缓存过期和数据库更新的窗口期可能会不一致。

解决方案

  • 增加数据版本号或时间戳,确保数据一致性。
  • 使用合适的缓存失效策略,减少不一致窗口。

2. Write Through Cache(写通缓存)

原理

  • 读操作:与 Cache Aside Pattern 类似,从缓存中读取数据。
  • 写操作:直接更新缓存,缓存负责同步更新数据库。

示例代码

public class WriteThroughCache {private RedisCache redisCache;public void updateData(String key, Data newData) {// 更新缓存,并让缓存负责同步更新数据库redisCache.putAndUpdateDatabase(key, newData);}
}

优点

  • 确保缓存和数据库的一致性。
  • 写操作成功后,即保证了数据库和缓存的数据一致。

缺点

  • 写操作的延迟较高,因为每次写操作都需要同步更新数据库。
  • 复杂性较高,需要确保缓存的更新操作能正确同步到数据库。

解决方案

  • 通过批量更新和异步操作,减少单次写操作的延迟。

3. Write Behind Cache(写回缓存)

原理

  • 读操作:与前两种模式类似,从缓存中读取数据。
  • 写操作:更新缓存,由缓存异步地更新数据库。

示例代码

public class WriteBehindCache {private RedisCache redisCache;public void updateData(String key, Data newData) {// 更新缓存,并异步地更新数据库redisCache.putAndAsyncUpdateDatabase(key, newData);}
}

优点

  • 写操作的延迟较低,因为写操作主要集中在缓存中。
  • 提高了写操作的吞吐量。

缺点

  • 可能会出现数据丢失的风险(例如缓存宕机时未及时更新数据库)。
  • 数据最终一致性问题,需要额外处理。

解决方案

  • 使用可靠的消息队列系统来确保数据更新消息的送达和处理。
  • 定期同步缓存和数据库的数据,确保最终一致性。

4. 使用消息队列进行异步更新

原理

  • 读操作:与其他模式类似,从缓存中读取数据。
  • 写操作:更新缓存,并通过消息队列异步地更新数据库。

示例代码

public class CacheWithMessageQueue {private RedisCache redisCache;private MessageQueue messageQueue;public void updateData(String key, Data newData) {// 更新缓存redisCache.put(key, newData);// 发送异步消息更新数据库messageQueue.sendUpdateMessage(key, newData);}
}

消息队列处理器:

public class DatabaseUpdater {private Database database;public void onMessage(UpdateMessage message) {String key = message.getKey();Data newData = message.getData();// 更新数据库database.update(key, newData);}
}

优点

  • 提高了系统的可扩展性和性能。
  • 异步更新,降低写操作的延迟。

缺点

  • 需要处理消息队列的可靠性和数据一致性问题。
  • 增加了系统的复杂性,需要处理消息的幂等性和重复消费问题。

解决方案

  • 确保消息队列具有高可靠性和高可用性。
  • 使用幂等性设计,确保消息重复消费时不会导致数据不一致。

选择适当的策略

选择合适的策略取决于系统的具体需求和场景:

  • 一致性优先:选择 Cache Aside PatternWrite Through Cache。适用于对数据一致性要求较高的场景。
  • 性能优先:选择 Write Behind Cache 或使用消息队列进行异步更新。适用于对写操作性能要求较高的场景。
  • 混合策略:在实际应用中,可以结合使用不同的策略。例如,某些关键数据使用同步更新,非关键数据使用异步更新。

实际应用示例

假设我们有一个电商系统,需要处理商品库存的更新和查询。我们可以采用以下混合策略:

  1. 查询库存

    • 先从缓存中读取,如果缓存中没有数据,从数据库中读取并写入缓存。
  2. 更新库存

    • 更新数据库后,立即更新缓存(同步更新)。
    • 同时发送异步消息,通过消息队列异步地更新缓存,以应对高并发下的延迟问题。

示例代码

public class InventoryService {private RedisCache redisCache;private Database database;private MessageQueue messageQueue;public int getInventory(String productId) {// 从缓存中读取数据Integer inventory = redisCache.get(productId);if (inventory == null) {// 如果缓存中没有数据,从数据库中读取数据inventory = database.getInventory(productId);// 将数据写入缓存redisCache.put(productId, inventory);}return inventory;}public void updateInventory(String productId, int newInventory) {// 更新数据库database.updateInventory(productId, newInventory);// 更新缓存redisCache.put(productId, newInventory);// 发送异步消息更新缓存messageQueue.sendUpdateMessage(productId, newInventory);}
}

消息队列处理器:

public class InventoryUpdateProcessor {private RedisCache redisCache;public void onMessage(UpdateMessage message) {String productId = message.getKey();int newInventory = message.getData();// 更新缓存redisCache.put(productId, newInventory);}
}

通过这种混合策略,可以在保证数据一致性的同时,尽量提高系统的性能和可扩展性。根据具体的业务需求和场景,选择合适的缓存和数据库更新策略,是构建高性能、高可用分布式系统的重要一环。

相关文章:

【Redis】Redis的双写问题

在分布式系统中,双写问题通常是指数据在多个存储系统(例如数据库和缓存)中更新时出现的不一致性。这种问题在使用 Redis 作为缓存层时尤为常见。具体来说,当数据在数据库和 Redis 缓存中存在副本时,任何对数据的更新操…...

生气时,你的“心”会发生什么变化?孟德尔随机化分析猛如虎,结果都是套路...

“不生气不生气,气出病来无人替”,不少人遇事常这样宽慰自己。事实上,“气死”真不是危言耸听。越来越多的研究证明了情绪稳定对健康的重要性,那么,当情绪频繁波动时,我们的心血管究竟会发生什么变化&#…...

页面加载性能分析时,有哪些常见的性能瓶颈需要特别注意?

在进行页面加载性能分析时,以下是一些常见的性能瓶颈,需要特别注意: 长页面加载时间: 页面加载时间超过行业标准或用户期望,导致用户流失。 高 CPU 使用率: 某些脚本或操作导致 CPU 使用率飙升,…...

Scanner

Java 有一个 Scanner 类,用这个类可以接受键盘输入。 步骤: 导入该类所在的包(要使用一个类的话就必须先导入该类所在的包)创建该类的对象调用里面的功能 Scanner 有两套系统。 第一套系统: nextInt(); nextDoubl…...

vue3实现录音与录像上传功能

录音 <script setup lang"ts"> import { onMounted, reactive, ref } from vue; import useInject from /utils/useInject;const props: any defineProps<{params?: any; }>();const recObj: any reactive({blob: null, });const { $global, $fn } …...

PHP小方法

一、随机生成姓名 二、随机获取身份证 三、随机获取手机号 四、随机获取省 五、通过身份证获取生日和性别 六、通过身份证获取年龄 七、获取访问IP 八、获取访问URL地址 九、陆续增加 //一、随机生成姓名 function generateName(){$arrXing getXingList();$numbXing …...

gulimall-search P125 springboot整合elasticsearch版本冲突

一、问题 spring-boot.version 2.2.4.RELEASE,在gulimall-search pom.xml中添加elasticsearch.version 7.4.2后&#xff0c;发现出现如下问题&#xff1a;elasticsearch版本是springboot引入的6.8.6&#xff0c;没有变为7.4.2。 二、原因 在gulimall-search 的pom文件中&#…...

如何在Coze中实现Bot对工作流的精准调用(如何提高Coze工作流调用的准确性和成功率)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 工作流(workflow)📒📝 创建设计工作流📝 添加工作流📝 调用工作流⚓️ 相关链接 ⚓️📖 介绍 📖 在使用Coze平台创建智能Bot时,您可能会遇到一个常见问题:即便添加了正确的工作流,Bot却没有按照预期调用它们。…...

毫米波雷达阵列天线设计综合1(MATLAB仿真)

1 天线设计目标 毫米波雷达探测目标的距离、速度和角度&#xff0c;其中距离和角度和天线设计相关性较强。天线增益越高&#xff0c;则根据雷达方程可知探测距离越远&#xff1b;天线波束越窄&#xff0c;则角度分辨率越高&#xff1b;天线副瓣/旁瓣越低&#xff0c;则干扰越少…...

Freemarker

Freemarker简介 Freemarker是一个用Java语言编写的模板引擎&#xff0c;用于基于模板和数据生成文本输出。它可以用于生成HTML网页、XML文档、电子邮件、配置文件等任何格式的文本。Freemarker将业务逻辑与表示逻辑分离&#xff0c;使得开发人员可以专注于功能实现&#xff0c…...

基于Zero-shot实现LLM信息抽取

基于Zero-shot方式实现LLM信息抽取 在当今这个信息爆炸的时代&#xff0c;从海量的文本数据中高效地抽取关键信息显得尤为重要。随着自然语言处理&#xff08;NLP&#xff09;技术的不断进步&#xff0c;信息抽取任务也迎来了新的突破。近年来&#xff0c;基于Zero-shot&#x…...

【python】tkinter GUI编程经典用法,Label标签组件应用实战详解

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…...

国产操作系统上给麒麟虚拟机安装virtualbox增强工具 _ 统信 _ 麒麟 _ 中科方德

原文链接&#xff1a;国产操作系统上给麒麟虚拟机安装virtualbox增强工具 | 统信 | 麒麟 | 中科方德 Hello&#xff0c;大家好啊&#xff01;昨天给大家带来了一篇在国产操作系统上给VirtualBox中的Win7虚拟机安装增强工具的文章&#xff0c;今天我们将继续深入&#xff0c;介绍…...

(delphi11最新学习资料) Object Pascal 学习笔记---第14章泛型第3节(特定类约束)

14.3.2 特定类约束 ​ 如果您的泛型类需要使用某个特定子集的类&#xff08;特定层次结构&#xff09;&#xff0c;则可能需要根据给定基类指定约束。 ​ 例如&#xff0c;如果您声明&#xff1a; typeTCompClass<T: TComponent> class​ 则此泛型类的实例仅适用于组…...

【postgresql初级使用】视图上的触发器instead of,替代计划的rewrite,实现不一样的审计日志

instead of 触发器 ​专栏内容&#xff1a; postgresql使用入门基础手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 文章目录 inst…...

window.setInterval(func,interval)定时器

window.setInterval()是JavaScript中的方法&#xff0c;用于在指定的时间间隔重复执行某个函数或代码块。它接受两个参数&#xff0c;第一个参数是要执行的函数或代码块&#xff0c;第二个参数是时间间隔&#xff08;以毫秒为单位&#xff09;。 以下是使用window.setInterval…...

Einstein Summation 爱因斯坦求和 torch.einsum

Einstein Summation 爱因斯坦求和 torch.einsum flyfish 理解爱因斯坦求和的基本概念和语法&#xff0c;这对初学者来说可能有一定难度。对于不熟悉该表示法的用户来说&#xff0c;可能不如直接的矩阵乘法表达式易于理解。 整个思路是 向量的点积 -》矩阵乘法-》einsum 向…...

TCP攻击是怎么实现的,如何防御?

TCP&#xff08;Transmission Control Protocol&#xff09;是互联网协议族中的重要组成部分&#xff0c;用于在不可靠的网络上提供可靠的数据传输服务。然而&#xff0c;TCP协议的一些特性也使其成为攻击者的目标&#xff0c;尤其是DDoS&#xff08;Distributed Denial of Ser…...

Chrome DevTools开发者调试工具

Chrome DevTools 是一个功能强大的网页开发工具&#xff0c;集成在谷歌浏览器中&#xff0c;帮助开发者调试和优化网页应用。以下是详细的功能说明和使用技巧&#xff1a; 1. 打开 DevTools 快捷键&#xff1a;按下 F12 或 CtrlShiftI&#xff08;Windows/Linux&#xff09;或…...

产品创新管理:从模仿到引领,中国企业的创新之路

一、引言 在全球化竞争日益激烈的今天&#xff0c;科技创新已成为推动国家经济增长和社会进步的关键动力。中国自改革开放四十年来&#xff0c;在科技创新领域取得了举世瞩目的成就&#xff0c;从跟踪模仿到自主研发&#xff0c;再到自主创新、开放创新和协同创新并举&#xf…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

Axios请求超时重发机制

Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式&#xff1a; 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

高防服务器能够抵御哪些网络攻击呢?

高防服务器作为一种有着高度防御能力的服务器&#xff0c;可以帮助网站应对分布式拒绝服务攻击&#xff0c;有效识别和清理一些恶意的网络流量&#xff0c;为用户提供安全且稳定的网络环境&#xff0c;那么&#xff0c;高防服务器一般都可以抵御哪些网络攻击呢&#xff1f;下面…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...