当前位置: 首页 > 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…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

EtherNet/IP转DeviceNet协议网关详解

一&#xff0c;设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络&#xff0c;本网关连接到EtherNet/IP总线中做为从站使用&#xff0c;连接到DeviceNet总线中做为从站使用。 在自动…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?

uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件&#xff0c;用于在原生应用中加载 HTML 页面&#xff1a; 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

stm32wle5 lpuart DMA数据不接收

配置波特率9600时&#xff0c;需要使用外部低速晶振...

Matlab实现任意伪彩色图像可视化显示

Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中&#xff0c;如何展示好看的实验结果图像非常重要&#xff01;&#xff01;&#xff01; 1、灰度原始图像 灰度图像每个像素点只有一个数值&#xff0c;代表该点的​​亮度&#xff08;或…...