redis读写一致问题
title: redis读写一致问题
date: 2025-05-18 11:11:31
tags: redis
categories: redis的问题方案
Redis读写一致问题
条件:
数据库此时的数据为10,redis此时的数据也为10
业务流程:
操作数据库使得数据库的数据为20,删除redis里面的数据保证读写一致
先删缓存,再操作数据库
出现读写不一致情况:
线程1(业务) | 线程2(并发线程) |
---|---|
删除缓存 | |
查询缓存,没有命中,查询数据库(数据库查到为10,下一步将10写入redis) | |
将10写入缓存 | |
更新数据库,将数据库中的数据改为20 |
最终情况
redis里面的数据 | 数据库里面的数据 |
---|---|
10 | 20 |
出现数据不一致情况
先操作数据库,再删除缓存
线程1(并发线程) | 线程2(业务线程) |
---|---|
查询缓存未命中,查询数据库(下一步:将缓存更新为10) | |
更新数据库 v=20 | |
删除缓存 | |
写入缓存数据10 |
最终情况:
redis数据 | 数据库数据 |
---|---|
10 | 20 |
两个方法选择原则
适用策略 | 典型场景 | 是否推荐使用延迟双删 |
---|---|---|
先删缓存 → 后更新数据库 | 高一致性业务(余额、库存) | ✅ 一定要延迟双删! |
先更新数据库 → 后删缓存 | 低一致性业务(资料、文章内容) | ❌ 可以不用延迟双删 |
解决方案:双写一致性
读操作没啥问题按照老流程
延时双删
问题 | 答案 |
---|---|
先删缓存还是先改数据库? | 先删缓存! 避免并发写入旧值 |
为什么删两次? | 防止“改库之后,又有人写了旧值到缓存” |
为什么要延迟删? | 给并发线程一个“写入脏缓存”的机会,然后再清理掉它 |
缺点:
问题点 | 延迟双删解决得了么? | 推荐改进方式 |
---|---|---|
并发窗口写入脏缓存 | ❌ 只能删最后一个 | 分布式锁 + 双删 / MQ |
延迟时间难控制 | ❌ 不可预测 | MQ 或 Canal 机制更精准 |
异步删除失败风险 | ❌ 会丢失删除 | 使用可靠任务队列 / Redis 持久化 |
操作复杂、代码维护困难 | ❌ 容易遗漏 key | 封装中间件、使用 AOP统一处理 |
给他加锁
读写都加锁
如图,程序运行串行化,性能低
引入共享锁和排他锁机制
共享锁:读锁readLock,加锁之后,其他线程可以共享读操作
排他锁:独占锁writeLock也叫,加锁之后,阻塞其他线程读写操作
代码Demo
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;public class UserService {private final RedissonClient redissonClient;private final RedisService redisService; // 你封装的 Redis 工具类private final UserRepository userRepository; // 你操作数据库的类public UserService(RedissonClient redissonClient, RedisService redisService, UserRepository userRepository) {this.redissonClient = redissonClient;this.redisService = redisService;this.userRepository = userRepository;}// 读操作:加“读锁”public User getUserById(Long userId) {String key = "user:" + userId;String lockKey = "lock:user:" + userId;RReadWriteLock rwLock = redissonClient.getReadWriteLock(lockKey);RLock readLock = rwLock.readLock();try {readLock.lock(5, TimeUnit.SECONDS); // 加读锁,防止同时写入User user = redisService.get(key); // 先查缓存if (user != null) {return user;}// 缓存未命中 → 查数据库并回写缓存user = userRepository.findById(userId);if (user != null) {redisService.set(key, user, 10, TimeUnit.MINUTES);}return user;} finally {readLock.unlock(); // 释放读锁}}// 写操作:加“写锁”public void updateUser(User user) {Long userId = user.getId();String key = "user:" + userId;String lockKey = "lock:user:" + userId;RReadWriteLock rwLock = redissonClient.getReadWriteLock(lockKey);RLock writeLock = rwLock.writeLock();try {writeLock.lock(10, TimeUnit.SECONDS); // 加写锁,防止并发读/写redisService.del(key); // 删除缓存(第一次)userRepository.save(user); // 更新数据库// 第二次删除可延迟做(避免并发写入旧值)Thread.sleep(500); // 模拟延迟redisService.del(key); // 延迟删除(第二次)} catch (InterruptedException e) {e.printStackTrace();} finally {writeLock.unlock(); // 释放写锁}}
}
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;public class UserService {private final RedissonClient redissonClient;private final RedisService redisService; // 你封装的 Redis 工具类private final UserRepository userRepository; // 你操作数据库的类public UserService(RedissonClient redissonClient, RedisService redisService, UserRepository userRepository) {this.redissonClient = redissonClient;this.redisService = redisService;this.userRepository = userRepository;}// 读操作:加“读锁”public User getUserById(Long userId) {String key = "user:" + userId;String lockKey = "lock:user:" + userId;RReadWriteLock rwLock = redissonClient.getReadWriteLock(lockKey);RLock readLock = rwLock.readLock();try {readLock.lock(5, TimeUnit.SECONDS); // 加读锁,防止同时写入User user = redisService.get(key); // 先查缓存if (user != null) {return user;}// 缓存未命中 → 查数据库并回写缓存user = userRepository.findById(userId);if (user != null) {redisService.set(key, user, 10, TimeUnit.MINUTES);}return user;} finally {readLock.unlock(); // 释放读锁}}// 写操作:加“写锁”public void updateUser(User user) {Long userId = user.getId();String key = "user:" + userId;String lockKey = "lock:user:" + userId;RReadWriteLock rwLock = redissonClient.getReadWriteLock(lockKey);RLock writeLock = rwLock.writeLock();try {writeLock.lock(10, TimeUnit.SECONDS); // 加写锁,防止并发读/写redisService.del(key); // 删除缓存(第一次)userRepository.save(user); // 更新数据库// 第二次删除可延迟做(避免并发写入旧值)Thread.sleep(500); // 模拟延迟redisService.del(key); // 延迟删除(第二次)} catch (InterruptedException e) {e.printStackTrace();} finally {writeLock.unlock(); // 释放写锁}}
}
中间件解决方案
异步通知保证数据的最终一致性
canal是基于mysql的主从同步来实现的
二进制日志(BINLOG)记录了所有的 DDL(数据定义语言)语句和 DML(数据操纵语言)语句,但不包括数据查询(SELECT、SHOW)语句。
相关文章:
redis读写一致问题
title: redis读写一致问题 date: 2025-05-18 11:11:31 tags: redis categories: redis的问题方案 Redis读写一致问题 条件: 数据库此时的数据为10,redis此时的数据也为10 业务流程: 操作数据库使得数据库的数据为20,删除redis里面的数据保证读写一致 先删缓存…...
Redis实现分布式锁的进阶版:Redisson实战指南
一、为什么选择Redisson? 在上一篇文章中,我们通过Redis原生命令实现了分布式锁。但在实际生产环境中,这样的基础方案存在三大痛点: 锁续期难题:业务操作超时导致锁提前释放不可重入限制:同一线程无法重复…...
标准库、HAl库和LL库(PC13初始化)
标准库 (Standard Peripheral Library) c #include "stm32f10x.h"void GPIO_Init_PC13(void) {GPIO_InitTypeDef GPIO_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);GPIO_InitStruct.GPIO_Pin GPIO_Pin_13;GPIO_InitStruct.GPIO_Mode GPIO_…...
第二章:安卓端启动流程详解与疑难杂症调试手册
想让一个安卓项目跑起来,从表面看无非就是:双击打开、连接真机、点击运行。 但是到了互动娱乐组件项目里,事情就变成了:点击运行→等待→黑屏→白屏→强制退出→LogCat爆炸→你怀疑人生。 本章就来系统性解决几个问题࿱…...
备份C#的两个类
GuestIP依赖项: using System.Data.SQLite; //这是第三方依赖项,要从nuget下载 static class GuestIP {public static void ReadLastGuestIP(string constr "Data Sourceguestip_log.db;"){using (var connection new SQLiteConnection(co…...
通过串口设备的VID PID动态获取串口号(C# C++)
摘要 本篇文章主要介绍分别通过C#和C++使用设备VID PID如何动态获取COM口 目录 1 简述 2 VID PID查看方式 3 C#实现通过串口设备的VID PID动态获取串口号 3.1 辅助类实现 3.2 调用实例 4 C++实现通过串口设备的VID PID动态获取串口号 4.1 辅助类实现 4.2 调用实例 1 简…...
C语言指针深入详解(二):const修饰指针、野指针、assert断言、指针的使用和传址调用
目录 一、const修饰指针 (一)const修饰变量 (二)const 修饰指针变量 二、野指针 (一)野指针成因 1、指针未初始化 2、指针越界访问 3、指针指向的空间释放 (二)如何规避野指…...
《P5283 [十二省联考 2019] 异或粽子》
题目描述 小粽是一个喜欢吃粽子的好孩子。今天她在家里自己做起了粽子。 小粽面前有 n 种互不相同的粽子馅儿,小粽将它们摆放为了一排,并从左至右编号为 1 到 n。第 i 种馅儿具有一个非负整数的属性值 ai。每种馅儿的数量都足够多,即小粽…...
C#自定义扩展方法 及 EventHandler<TEventArgs> 委托
有自定义官方示例链接: 如何实现和调用自定义扩展方法 - C# | Microsoft Learn 1.静态类 2.静态方法 3.第一参数固定为this 要修改的类型,后面才是自定的参数 AI给出的一个示例:没有自定义参数 、有自定义参数的 using System; using System.Colle…...
oracle 资源管理器的使用
14.8.2资源管理器的使用 资源管理器控制CPU资源使用说明: 第一种分配方法:EMPHASIS CPU 分配方法确定在资源计划中对不同使用者组中的会话的重视程度。CPU占用率的分配级别为从1 到8,级别1 的优先级最高。百分比指定如何将CPU 资源分配给每…...
(二十一)Java集合框架源码深度解析
一、集合框架概述 Java集合框架(Java Collections Framework, JCF)是Java语言中用于存储和操作数据集合的一套标准架构。它提供了一组接口、实现类和算法,使开发者能够高效地处理各种数据结构。 1.1 集合框架的历史演变 在Java 1.2之前,Java只有几种简…...
spark数据的提取和保存
Spark数据提取和保存 一、数据提取(读取数据) 1. 读取文件(文本、CSV、JSON等) scala // 读取文本文件 val textData spark.read.text("路径/文件.txt") // 读取CSV文件(带表头) val csvD…...
Graphics——基于.NET 的 CAD 图形预览技术研究与实现——CAD c#二次开发
一、Graphics 类的本质与作用 Graphics 是 .NET 框架中 System.Drawing 命名空间下的核心类,用于在二维画布(如 Bitmap 图像)上绘制图形、文本或图像。它相当于 “绘图工具”,提供了一系列方法(如 DrawLine、FillElli…...
vue3_flask实现mysql数据库对比功能
实现对mysql中两个数据库的表、表结构、表数据的对比功能, 效果如下图 基础环境请参考 vue3flasksqlite前后端项目实战 代码文件结构变化 api/ # 后端相关 ├── daos/ │ ├── __init__.py │ └── db_compare_dao.py # 新增 ├── routes/ │ ├── _…...
【数据结构】2-3-1单链表的定义
数据结构知识点合集 知识点 单链表存储结构 优点:不要求大片连续空间,改变容量方便;缺点:不可随机存取,要耗费一定空间存放指针 /*单链表节点定义*/ typedef struct LNode{ElemType data;struct LNode *next; }LNo…...
面试题总结一
第一天 1. 快速排序 public class QuickSort {public static void quickSort(int[] arr, int low, int high) {if (low < high) {// 分区操作,获取基准元素的最终位置int pivotIndex partition(arr, low, high);// 递归排序基准元素左边的部分quickSort(arr, …...
Ubuntu24.04下安装ISPConfig全过程记录
今天在网上看到ISPConfig,觉得不错,刚好手里又有一台没用的VPS,就顺手安装一个玩玩。具体安装步骤如下: 一、配置服务器hosts及hostname 【安装时候需要检查】 使用root账号登录VPS后 先安装vim编辑器,然后编辑hosts࿰…...
【NGINX】 -10 keepalived + nginx + httpd 实现的双机热备+ 负载均衡
文章目录 1、主架构图1.1 IP地址规划 2、web服务器操作3、配置nginx服务器的负载均衡4、配置keepalived4.1 master4.1 backup 5、测试双机热备5.1 两台keepalived服务器均开启5.2 模拟master节点故障 1、主架构图 1.1 IP地址规划 服务器IP地址web1192.168.107.193web2192.168.…...
NC016NC017美光固态芯片NC101NC102
NC016NC017美光固态芯片NC101NC102 在存储技术的演进历程中,美光科技的NC016、NC017、NC101与NC102系列固态芯片,凭借其技术创新与市场适应性,成为行业关注的焦点。本文将从技术内核、产品性能、行业动向、应用场景及市场价值五个维度&#…...
C++(22):fstream的一些成员函数
目录 1 遍历读取文件 1.1 eof()方法 2 读取文件大小 2.1 seekg() 2.2 tellg() 2.3 代码实例 3 存取文字 3.1 read() 3.2 write() 3.3 代码实例 3.3.1 存取文字 3.3.2 特殊方法存储 3.3.3 特殊方法读取 4 重载的输入输出 4.1 重载的输出 << 4.2 重载的输…...
【网络】Wireshark练习3 analyse DNS||ICMP and response message
ip.addr 172.16.0.100 && ip.addr 172.16.0.5 && (dns || icmp) 包号 22–31 之所以被选中,是因为在整个抓包文件里,与执行 ping cat.inx251.edu.au 这一事件相关的所有报文,恰好连续出现在第 22 到第 31 条记录中。具体分…...
GBS 8.0服装裁剪计划软件在线试用
1、全新升级内核8.0,分床更合理,铺布床数更少; 2、支持SS AUTONESTER排料引擎切换 3、支持ASTM AAMA及国产CAD(如布衣)导出的DXF,Prj文件等 4、核心引擎优化 拖料优化 省料优化 5、经实战对比人工&…...
顺 序 表:数 据 存 储 的 “ 有 序 阵 地 ”
顺 序 表:数 据 存 储 的 “ 有 序 阵 地 ” 线 性 表顺 序 表 - - - 顺 序 存 储 结 构顺 序 表 的 操 作 实 现代 码 全 貌 与 功 能 介 绍顺 序 表 的 功 能 说 明代 码 效 果 展 示代 码 详 解SeqList.hSeqList.ctest.c 总 结 💻作 者 简 介…...
#Redis黑马点评#(七)实战篇完结
目录 一 达人探店 1 发布探店笔记 2 查看探店笔记 3 点赞功能 编辑 4 点赞排行榜(top5) 编辑 二 好友关注 1 关注与取关 2 共同关注 3 Feed流实现关注推送 4 实现滚动分页查询 三 附近商店 1 GEO数据结构 2 附近商户搜索功能 四 用户…...
初始C++:类和对象(中)
概述:本篇博客主要介绍类和对象的相关知识。 1. 类的默认成员函数 默认成员函数就是用户没有显示实现,编译器会自动生成的成员函数称为默认成员函数。一个类,在不写任何代码的情况下编译器会默认生成以下六个默认函数,在六个默认…...
Java开发经验——阿里巴巴编码规范实践解析3
摘要 本文深入解析了阿里巴巴编码规范中关于错误码的制定与管理原则,强调错误码应便于快速溯源和沟通标准化,避免过于复杂。介绍了错误码的命名与设计示例,推荐采用模块前缀、错误类型码和业务编号的结构。同时,探讨了项目错误信…...
ChatGPT:OpenAI Codex—一款基于云的软件工程 AI 代理,赋能 ChatGPT,革新软件开发模式
ChatGPT:OpenAI Codex—一款基于云的软件工程 AI 代理,赋能 ChatGPT,革新软件开发模式 导读:2025年5月16日,OpenAI 发布了 Codex,一个基于云的软件工程 AI 代理,它集成在 ChatGPT 中,…...
iOS 内存分区
iOS内存分区 文章目录 iOS内存分区前言五大分区static、extern、const关键字比较conststaticextern与.h文件的关系extern引用变量extern声明 static和const联合使用extern和const联合使用 前言 笔者之前学习OC源码的时候,发现对于这里的几个static,extern,const的内容有遗忘,所…...
LWIP的Socket接口
Socket接口简介 类似于文件操作的一种网络连接接口,通常将其称之为“套接字”。lwIP的Socket接口兼容BSD Socket接口,但只实现完整Socket的部分功能 netconn是对RAW的封装 Socket是对netconn的封装 SOCKET结构体 struct sockaddr { u8_t sa_len; /* 长…...
【Linux笔记】——线程同步条件变量与生产者消费者模型的实现
🔥个人主页🔥:孤寂大仙V 🌈收录专栏🌈:Linux 🌹往期回顾🌹:【Linux笔记】——线程互斥与互斥锁的封装 🔖流水不争,争的是滔滔不息 一、线程同步的…...
Popeye
概览与定位 Popeye 是由 derailed 团队开源的 Kubernetes 集群资源 “Sanitizer”,它以只读方式扫描集群内的各种资源(如 Pod、Service、Ingress、PVC、RBAC 等),并基于社区最佳实践给出问题等级及修复建议,覆盖配置误…...
ES(ES2023/ES14)最新更新内容,及如何减少内耗
截至2023年10月,JavaScript(ECMAScript)的最新版本是 ES2023(ES14)。 ES2023 引入了许多新特性,如findLast、toSorted等,同时优化了性能。通过减少全局变量、避免内存泄漏、优化循环、减少DOM操作、使用Web Workers、懒加载、缓存、高效数据结构和代码压缩,可以显著降低…...
电子数据取证(数字取证)技术全面指南:从基础到实践
为了后续查阅方便,推荐工具先放到前面 推荐工具 数字取证基础工具 综合取证平台 工具名称类型主要功能适用场景EnCase Forensic商业全面的证据获取和分析、强大的搜索能力法律诉讼、企业调查FTK (Forensic Toolkit)商业高性能处理和索引、集成内存分析大规模数据处…...
【通用智能体】Serper API 详解:搜索引擎数据获取的核心工具
Serper API 详解:搜索引擎数据获取的核心工具 一、Serper API 的定义与核心功能二、技术架构与核心优势2.1 技术实现原理2.2 对比传统方案的突破性优势 三、典型应用场景与代码示例3.1 SEO 监控系统3.2 竞品广告分析 四、使用成本与配额策略五、开发者注意事项六、替…...
基于 STM32 的手持式安检金属探测器设计与实现
一、硬件设计:芯片与功能模块选型及接线 1. 主控芯片选型 芯片型号:STM32F103C8T6 核心优势: 32 位 Cortex-M3 内核,主频 72MHz,满足实时数据处理需求64KB Flash+20KB SRAM,支持程序存储与数据缓存丰富外设:2 路 USART、2 路 SPI、1 路 I2C、12 位 ADC,适配多模块通信…...
虚幻引擎5-Unreal Engine笔记之Default Pawn与GamMode、Camera的关系
虚幻引擎5-Unreal Engine笔记之Default Pawn与GamMode、Camera的关系 code review! 文章目录 虚幻引擎5-Unreal Engine笔记之Default Pawn与GamMode、Camera的关系1.Default Pawn与Camera的关系1.1. Default Pawn 是什么?1.2. Default Pawn 的主要组件1.3. Default…...
Spring源码主线全链路拆解:从启动到关闭的完整生命周期
Spring源码主线全链路拆解:从启动到关闭的完整生命周期 一文看懂 Spring 框架从启动到销毁的主线流程,结合原理、源码路径与伪代码三位一体,系统学习 Spring 底层机制。 1. 启动入口与环境准备 原理说明 Spring Boot 应用入口是标准 Java 应…...
飞帆控件:可配置post/get接口
先上链接: post_get_ithttps://fvi.cn/796看一下这个控件的配置: 当 url 有某个 get 参数时,例如某些接口回传的参数。使用这个接口会发生这些: 如果检测到 url 中有该 url 参数则继续执行选择是否从 url 中删除该参数将这个参数…...
Android 自定义悬浮拖动吸附按钮
一个悬浮的拨打电话按钮,使用CardViewImageView可能会出现适配问题,也就是图片显示不全,出现这种问题,就直接替换控件了,因为上述的组合控件没有FloatingActionButton使用方便,还可以有拖动和吸附效果不是更…...
Spring AI Alibaba集成阿里云百炼大模型应用
文章目录 1.准备工作2.引入maven依赖3.application.yml4.调用4.1.非流式调用4.2.流式调用 阿里云百炼推出的智能体应用、工作流应用和智能体编排应用,有效解决了大模型在处理私有领域问题、获取最新信息、遵循固定流程以及自动规划复杂项目等方面的局限,…...
UI-TARS本地部署
UI-TARS本地部署 UI-TARS本地部署 UI-TARS 论文(arXiv) UI-TARS 官方仓库:包含部署指南、模型下载链接及示例代码。 UI-TARS-Desktop 客户端:支持本地桌面应用的交互控制。 模型部署框架:vLLM本地部署 1.下载项目…...
如何利用内网穿透实现Cursor对私有化部署大模型的跨网络访问实践
文章目录 前言1.安装Ollama2.QwQ-32B模型安装与运行3.Cursor安装与配置4. 简单使用测试5. 调用本地大模型6. 安装内网穿透7. 配置固定公网地址总结 前言 本文主要介绍如何在Windows环境下,使用Cursor接入Ollama启动本地部署的千问qwq-32b大模型实现辅助编程&#x…...
Linux的进程概念
目录 1、冯诺依曼体系结构 2、操作系统(Operating System) 2.1 基本概念 编辑 2.2 目的 3、Linux的进程 3.1 基本概念 3.1.1 PCB 3.1.2 struct task_struct 3.1.3 进程的定义 3.2 基本操作 3.2.1 查看进程 3.2.2 初识fork 3.3 进程状态 3.3.1 操作系统的进程状…...
(10)python开发经验
文章目录 1 cp35 cp36什么意思2 找不到pip3 subprocess编码错误4 导出依赖文件包含路径5 使用自己编译的python并且pyinstall打包程序 更多精彩内容👉内容导航 👈👉Qt开发 👈👉python开发 👈 1 cp35 cp36什…...
什么是时间戳?怎么获取?有什么用
时间戳的定义 时间戳(Timestamp)是指记录某个事件发生的具体时间点,通常以特定的格式表示。它可以精确到秒、毫秒甚至更小的单位,用于标识某个时刻在时间轴上的位置。 获取时间戳的方法 在不同的编程语言中,获取时间…...
Zookeeper 入门(二)
4. Zookeeper 的 ACL 权限控制( Access Control List ) Zookeeper 的ACL 权限控制,可以控制节点的读写操作,保证数据的安全性,Zookeeper ACL 权 限设置分为 3 部分组成,分别是:权限模式(Scheme)、授权对象(…...
[创业之路-361]:企业战略管理案例分析-2-战略制定-使命、愿景、价值观的失败案例
一、失败案例 1、使命方面的失败案例 真功夫创业者内乱:真功夫在创业过程中,由于股权结构不合理,共同创始人及公司大股东潘宇海与实际控制人、董事长蔡达标产生管理权矛盾。双方在公司发展方向、管理改革等方面无法达成一致,导致…...
dijkstra算法加训上 之 分层图最短路
来几个分层图的题练习下哈 P4568 [JLOI2011] 飞行路线 P4568 [JLOI2011] 飞行路线 - 洛谷https://www.luogu.com.cn/problem/P4568 题目描述 Alice 和 Bob 现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在 n 个城市设有业务,设这…...
赋予AI更强的“思考”能力
刚刚!北大校友、OpenAI前安全副总裁Lilian Weng最新博客来了:Why We Think 原文链接:Why We Think by Lilian Weng 这篇文章关注:如何让AI不仅仅是“知道”答案,更能“理解”问题并推导出答案。通过赋予AI更强的“思…...
微服务项目->在线oj系统(Java版 - 1)
相信自己,终会成功 目录 C/S架构与B/S架构 C/S架构(Client/Server,客户端/服务器架构) 特点: 优点: 缺点: 典型应用: B/S架构(Browser/Server,浏览器/服务器架构&a…...