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

理解 Redis 事务-21(使用事务实现原子操)

使用事务实现原子操作

Redis 事务是一种在单个步骤中执行一组命令的机制。"要么全部,要么全部不"的方法确保了数据的一致性和完整性,尤其是在需要对相关数据进行多个操作时。没有事务,并发操作可能会导致竞争条件和不一致的数据状态。本课将探讨如何使用 Redis 事务来实现原子操作,保证事务中的所有命令要么全部执行,要么全部不执行。

理解 Redis 事务

Redis 事务提供了一种将多个命令组合为单个原子操作的方式。这意味着事务中的所有命令都是按顺序执行的,并且与其他客户端隔离。如果事务中的任何命令失败,整个事务将被回滚,以确保数据一致性。

MULTI, EXEC, 和 DISCARD 命令

Redis 事务的核心在于三个命令:MULTI, EXEC, 和 DISCARD

  • MULTI: 该命令标记事务块开始。服务器收到的所有后续命令都将排队在事务内执行,直到发出 EXEC 命令。
  • EXEC:该命令触发事务队列中所有命令的执行。Redis 将按照接收顺序执行命令,并返回一个结果数组。如果在执行阶段任何命令失败(例如,由于语法错误或数据类型不正确),执行会继续,错误将在结果数组中报告。
  • DISCARD:该命令取消事务,清空整个事务队列。不会执行任何命令,连接将恢复到正常状态。

示例:基本交易

让我们说明一个简单的交易,该交易递增两个计数器:counter1counter2

MULTI
INCR counter1
INCR counter2
EXEC

在这个例子中:

  1. MULTI 倡导交易。
  2. INCR counter1INCR counter2 被排队。
  3. EXEC 执行排队中的命令。

结果将是一个包含 counter1counter2 增量值的数组。

示例:与 DISCARD 的交易

现在,让我们看看 DISCARD 是如何工作的。

MULTI
INCR counter1
INCR counter2
DISCARD

在这种情况下,counter1counter2 将不会被递增,因为 DISCARD 命令取消了事务。

原子性在 Redis 事务中

Redis 事务在某种程度上提供了原子性,因为事务中的所有命令都是按顺序且独立执行的。然而,Redis 事务在传统数据库意义上并不提供真正的回滚功能。如果在 EXEC 阶段(例如,由于语法错误或操作了错误的数据类型)有命令失败,Redis 会继续执行队列中的剩余命令。错误会在结果数组中报告,但事务不会被完全回滚。

这种行为与传统 ACID(原子性、一致性、隔离性、持久性)数据库不同,后者在发生故障时会将整个事务回滚到初始状态。Redis 优先考虑性能和简单性,而非完全符合 ACID。

🚫 Redis 不支持回滚的原因

Redis 是单线程、无锁的,其设计目标是高性能和简洁性,而不是像传统数据库那样提供事务隔离和回滚机制(如 ACID 中的 Isolation 和 Durability)。

实现原子操作

Redis 事务对于实现原子操作特别有用,其中多个命令必须作为一个不可分割的整体来执行。这对于在并发环境中保持数据一致性至关重要。

场景:在不同账户之间转账

考虑一个场景,你需要将资金从一个账户转移到另一个账户。这涉及两个操作:减少源账户的余额和增加目标账户的余额。为确保数据一致性,这些操作必须原子性地执行。

这里是如何使用 Redis 事务来实现:

MULTI
DECRBY account1 100  # Subtract 100 from account1
INCRBY account2 100  # Add 100 to account2
EXEC

在这个例子中,如果 EXEC 命令成功,account1 将会减少 100,而 account2 将会增加 100。如果交易被中断或因任何原因失败,这两个操作都不会被执行,以确保资金不会丢失或重复。

场景:实现一个原子计数器

另一种常见的用例是实现一个原子计数器。假设你只想在计数器的当前值低于某个阈值时才进行递增。

MULTI
GET counter
INCR counter
EXEC

然而,这种方法不是原子的。另一个客户端可以在 GETINCR 命令之间增加计数器,从而可能超过阈值。要正确实现这一点,通常需要使用 Lua 脚本(将在下一章节中介绍)或使用 WATCH 命令进行乐观锁(如下文所述)。

使用 WATCH 进行乐观锁

WATCH 命令在 Redis 事务中提供了乐观锁的机制。它允许你监控一个或多个键的变化。如果在调用 EXEC 命令之前,任何被监控的键被修改,事务将被中止。

这是如何使用 WATCH 来实现带阈值的原子计数器:

WATCH counter
GET counter
# Check if the counter is below the threshold
IF counter < threshold THENMULTIINCR counterEXEC
ELSEUNWATCH
ENDIF

在这个例子中:

  1. WATCH counter 监控着 counter 键。
  2. GET counter 获取 counter 的当前值。
  3. 代码检查计数器是否低于阈值。
  4. 如果是,使用 MULTI 开始事务,使用 INCR counter 增加计数器,然后使用 EXEC 执行事务。
  5. 如果计数器不低于阈值,则调用 UNWATCH 命令停止监视该键。
  6. 如果另一个客户端在 WATCHEXEC 命令之间修改了 counter 键,事务将被中止,并且 EXEC 命令将返回 NULL。客户端可以重试该操作。

实现一个简单的速率限制器

速率限制是一种控制用户执行某些操作速率的技术。可以使用 Redis 事务来实现一个简单的速率限制器。

WATCH user:123:requests
GET user:123:requests
IF requests < limit THENMULTIINCR user:123:requestsEXPIRE user:123:requests expiration_timeEXEC
ELSEUNWATCH# Reject the request
ENDIF

在这个例子中:

  1. WATCH user:123:requests 监控特定用户的请求次数。
  2. GET user:123:requests 检索当前的请求数量。
  3. 如果请求数量低于限制,则开始一个事务。
  4. 该事务增加请求数量并为该密钥设置过期时间。
  5. 如果请求数量超过限制,请求将被拒绝。

🎯 场景目标:用户从账户 userAuserB 转账 100 元

  • 要求在并发环境中保证数据一致性

  • 防止 userA 在转账过程中余额被其他操作修改

✅ 实现步骤(Redis 乐观锁)

🧱 步骤说明

  1. WATCH userA:监视 userA 的余额

  2. GET userA:读取当前余额

  3. 业务逻辑判断:余额是否充足

  4. MULTI:开启事务

  5. DECRBY userA 100INCRBY userB 100:入队命令

  6. EXEC:提交事务,如果期间 userA 被其他客户端修改,EXEC 会失败


🧪 示例代码(使用 redis-cli 或客户端 SDK 执行)

WATCH userA            # Step 1: 监视 userA 的余额
GET userA              # Step 2: 读取余额(假设是 500)

假设返回结果是 500,则继续进行:

MULTI                  # Step 4: 开启事务
DECRBY userA 100       # Step 5: 扣减 userA
INCRBY userB 100       # 增加 userB
EXEC                   # Step 6: 提交事务

如果 WATCHuserA 没有被修改,EXEC 会成功执行两个命令。

如果在这期间有其他客户端修改了 userA 的值(即使只是 INCR 1 元),EXEC 会失败,整个事务不会执行。


📌 EXEC 返回值说明

  • 成功:[400, 100](表示执行了 DECRBYINCRBY

  • 失败:nil(说明 WATCH 检测到监控键被修改)


💡 实际使用建议(伪代码逻辑)

redis.watch("userA")
balance = redis.get("userA")
if int(balance) >= 100:pipe = redis.pipeline()pipe.multi()pipe.decrby("userA", 100)pipe.incrby("userB", 100)success = pipe.execute()if success:print("转账成功")else:print("余额在事务提交前被其他人改动,重试")
else:print("余额不足")

🧱 使用场景总结

使用场景Redis 乐观锁是否合适
用户余额扣减✅ 推荐使用
秒杀库存控制✅ 推荐使用
非强一致性场景(缓存等)❌ 不需要 WATCH
高并发写操作✅ 但注意避免死循环重试

相关文章:

理解 Redis 事务-21(使用事务实现原子操)

使用事务实现原子操作 Redis 事务是一种在单个步骤中执行一组命令的机制。"要么全部&#xff0c;要么全部不"的方法确保了数据的一致性和完整性&#xff0c;尤其是在需要对相关数据进行多个操作时。没有事务&#xff0c;并发操作可能会导致竞争条件和不一致的数据状…...

神经网络加上注意力机制,精度反而下降,为什么会这样呢?注意力机制的本质是什么?如何正确使用注意力机制?注意力机制 | 深度学习

在深度学习的发展中,注意力机制的引入曾被誉为一次划时代的技术飞跃。无论是在自然语言处理领域产生Transformer架构,还是在图像识别、语音识别和推荐系统等多个方向取得显著成效,注意力机制的价值似乎毋庸置疑。然而,在一些实际应用场景中,研究人员和工程师却发现:在传统…...

触控精灵 ADB运行模式填写电脑端IP教程

•ADB模式&#xff0c;如果你手机已经root则可以直接运行&#xff0c;无需安装电脑端。 •ADB模式&#xff0c;如果你手机没有root&#xff0c;那你可以windows电脑下载【极限投屏】软件&#xff0c;然后你的手机和电脑的网络要同一个wifi&#xff0c;然后把你电脑的ip地址填写…...

uniapp|实现多端图片上传、拍照上传自定义插入水印内容及拖拽自定义水印位置,实现水印相机、图片下载保存等功能

本文以基础视角,详细讲解如何在uni-app中实现图片上传→水印动态编辑→图片下载的全流程功能。 目录 引言应用场景分析(社交媒体、内容保护、企业素材管理等)uniapp跨平台开发优势核心功能实现​图片上传模块多来源支持:相册选择(`uni.chooseImage`)与拍照(`sourceType:…...

linux有效裁剪视频的方式(基于ffmpeg,不改变分辨率,帧率,视频质量,不需要三方软件)

就是在Linux上使用OBS Studio录制一个讲座或者其他视频&#xff0c;可能总有些时候会多录制一段时间&#xff0c;但是如果使用剪映或者PR这样的工具在导出的时候总需要烦恼导出的格式和参数&#xff0c;比如剪映就不支持mkv格式的导出&#xff0c;导出成mp4格式的视频就会变得很…...

服务器密码安全运维解决新思路:凭据管理SMS+双因素SLA认证结合的方案

引言&#xff1a;云服务器安全成本困局 在云计算渗透率突破60%的今天&#xff0c;中小企业正面临严峻的安全悖论&#xff1a;某权威机构数据显示&#xff0c;72%的云上数据泄露事件源于凭据管理不当&#xff0c;而传统安全解决方案的采购成本往往超过中小企业年利润的8%。这种…...

论文阅读笔记——In-Context Edit

ICEdit 论文阅读笔记 指令图像编辑现有方法的局限&#xff1a; 微调类方法&#xff08;InstructPix2Pix、Emu Edit、 Ultra Edit&#xff09;&#xff1a;需要大规模数据和算力、精度高但效率低且泛化性低&#xff1b;免训练方法&#xff08;Prompt-to-Prompt、 StableFlow&am…...

Debian 系统 Python 开发全解析:从环境搭建到项目实战

Debian 系统 Python 开发全解析:从环境搭建到项目实战 在当今数字化时代,Python 凭借其简洁易读的语法和强大的功能,成为了最受欢迎的编程语言之一。Debian 作为一款稳定、安全且开源的 Linux 操作系统,为 Python 开发提供了理想的环境。本文将详细介绍在 Debian 系统上进…...

Next.js 15 与 Apollo Client 的现代集成及性能优化

Next.js 15 与 Apollo Client 的现代集成及性能优化 目录 技术演进集成实践性能优化应用案例未来趋势 技术演进 Next.js 15 核心特性对开发模式的革新 Next.js 15 通过引入 App Router、服务器组件&#xff08;Server Components&#xff09;和客户端组件&#xff08;Clie…...

【后端高阶面经:MongoDB篇】41、MongoDB 是怎么做到高可用的?

一、MongoDB高可用核心架构&#xff1a;副本集&#xff08;Replica Set&#xff09;设计 &#xff08;一&#xff09;副本集角色与拓扑结构 1. 三大核心角色 角色职责描述资源占用选举权重数据存储Primary唯一接收写请求的节点&#xff0c;将操作日志&#xff08;Oplog&…...

IO Vs NIO

一、IO(传统阻塞式) 全称‌&#xff1a;Input/Output(输入/输出) 定义‌&#xff1a;Java 1.0 引入的基础 I/O 模型&#xff0c;基于流&#xff08;Stream&#xff09;的同步阻塞操作&#xff0c;线程在读写数据时会阻塞直到操作完成。 二、NIO(新式非阻塞式) ‌全…...

offset 家族和 client 家族

在前端开发中&#xff0c;offset 家族和 client 家族是用于获取元素尺寸和位置的重要属性集合。以下是对这两个家族相关知识点的系统总结&#xff1a; 一、offset 家族 核心属性 offsetWidth / offsetHeight 含义&#xff1a;元素的总尺寸&#xff0c;包含内容区、内边距、边…...

DMBOK对比知识点整理(4)

1.常见数据质量维度 常见数据质量维度(DMBOK-P353)质量维度...

day12 leetcode-hot100-21(矩阵4)

240. 搜索二维矩阵 II - 力扣&#xff08;LeetCode&#xff09; 1.暴力法O(m*n) 思路&#xff1a;两层for循环即可。 2.二分查找O(m*logn) 思路&#xff1a;每行都用二分查找,因为每行都是排好序的 class Solution {public boolean searchMatrix(int[][] matrix, int targe…...

Java基础 Day24

一、进程和线程 1、进程 &#xff08;1&#xff09;概念 进程 (Process) 是计算机中的程序关于某数据集合上的一次运行活动 是系统进行资源分配的基本单位 简单理解&#xff1a;程序的执行过程&#xff08;正在运行的应用程序&#xff09; &#xff08;2&#xff09;特性…...

提问:鲜羊奶是解决育儿Bug的补丁吗?

在育儿这个"系统工程"中&#xff0c;过度提醒就像冗余代码&#xff1a;"快写作业"&#xff08;重复调用&#xff09;、"多穿衣服"&#xff08;异常捕获&#xff09;、"别玩手机"&#xff08;进程阻断&#xff09;。羊大师技术育儿实验…...

关于数据仓库、数据湖、数据平台、数据中台和湖仓一体的概念和区别

我们谈论数据中台之前&#xff0c; 我们也听到过数据平台、数据仓库、数据湖、湖仓一体的相关概念&#xff0c;它们都与数据有关系&#xff0c;但他们和数据中台有什么样的区别&#xff0c; 下面我们将围绕数据平台、数据仓库、数据湖和数据中台的区别进行介绍。 一、相关概念…...

Hive 分桶(Bucketing)深度解析:原理、实战与核心概念对比

一、分桶的意义&#xff1a;比分区更细的粒度管理 1.1 解决分区数据不均匀问题 分区的局限性&#xff1a;分区基于表外字段&#xff08;如时间字段&#xff09;划分数据&#xff0c;但可能导致部分分区数据量过大&#xff0c;部分过小&#xff0c;无法进一步细化。 分桶的定…...

网络协议DHCP

DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;是一种网络协议&#xff0c;用于自动给网络中的设备分配 IP 地址、子网掩码、默认网关、DNS 服务器等网络配置参数。 ✅ 一、DHCP 的作用 自动为客户端分配网络信息&#xff0c;…...

什么是可重组机器人?

可重组机器人是一种具有高度灵活性和适应性的新型机器人系统&#xff0c;能够根据不同任务需求&#xff0c;快速改变自身结构和功能。下面我从概念、结构、特点、应用领域、发展趋势等方面&#xff0c;为你详细介绍&#xff1a; 概念&#xff1a;可重组机器人是由多个标准化、模…...

4、docker compose

1、介绍 Docker Compose 是 Docker 官方提供的容器编排工具&#xff0c;用于简化多容器应用的开发、部署和管理。它通过声明式配置文件&#xff08;YAML格式&#xff09;定义容器化应用的服务、网络、存储等组件及其依赖关系&#xff0c;使用户能够通过单一命令快速启动、停止…...

Node.js全局对象详解:console、process与核心功能

在Node.js开发中&#xff0c;全局对象是一类无需引入即可直接访问的对象&#xff0c;它们为开发者提供了与运行时环境、系统交互和调试相关的核心功能。本文将深入解析Node.js中两个最常用的全局对象 console 和 process&#xff0c;并简要介绍其他全局对象的作用。通过代码示例…...

测试策略:AI模型接口的单元测试与稳定性测试

测试策略:AI模型接口的单元测试与稳定性测试 在构建支持AI能力的系统中,开发者不仅要关注业务逻辑的正确性,也必须保障AI模型接口在各种环境下都能稳定运行。这就要求我们在开发阶段制定清晰的测试策略,从功能验证到性能保障,逐步推进系统可用性、可维护性与可扩展性的提…...

SQL里几种JOIN连接

数据信息&#xff1a; 员工表EMP 部门表DEPT 一、INNER JOIN&#xff08;内连接&#xff09; 作用&#xff1a;只返回两个表中完全匹配的行&#xff0c;相当于取交集。 场景&#xff1a;查询「有部门的员工信息」。 示例&#xff1a; SELECT 员工.姓名, 部门.部门名称 FR…...

基于通义千问的儿童陪伴学习和成长的智能应用架构。

1.整体架构概览 我们的儿童聊天助手将采用典型的语音交互系统架构,结合大模型能力和外部知识库: 2. 技术方案分解 2.1. 前端应用/设备 选择: 移动App(iOS/Android)、Web应用,或者集成到智能音箱/平板等硬件设备中。技术栈: 移动App: React Native / Flutter (跨平台…...

生产环境Mysql推荐配置参数

以下是针对生产环境的 MySQL 配置(my.cnf 或 mysqld.cnf)推荐配置及说明。请根据实际硬件资源(如内存、CPU、磁盘类型)和应用场景调整参数。 核心配置模板(InnoDB 优化) [mysqld] #---------------------- 基础设置 ---------------------- datadir = /var/lib…...

LVS-DR 负载均衡群集

目录 一、LVS-DR集群 1、LVS-DR 工作原理 2、数据包流向分析 3、LVS-DR 模式特点 二、直接路由模式&#xff08;LVS-DR&#xff09; 1、准备案例环境 2、配置负载调度器&#xff08;101&#xff09; &#xff08;1&#xff09;配置虚拟IP 地址&#xff08;VIP&#xff…...

理解并解决高丢包率问题,构建清晰流畅的实时音视频通话

丢包作为数字通信中的重要干扰因素&#xff0c;常常潜伏在表面之下&#xff0c;却严重影响性能&#xff0c;将清晰的对话变的模糊不清&#xff0c;将连贯的演示变的断断续续。因此&#xff0c;对音视频通话相关应用的开发者来说&#xff0c;理解丢包率非常重要。 什么是丢包&am…...

Ubuntu系统Todesk进度卡在100%

1 : 修改配置文件&#xff0c;关闭wayland sudo nano /etc/gdm3/custom.conf2 : 把#WaylandEnablefalse前的#号删掉 按图片删除注释 3 : 按Ctrl X &#xff0c;离开&#xff08;会问你要不要保存&#xff0c;输入 Y 回车保存&#xff09; 4 : 重启系统 或在命令行输入 r…...

[Dify] 如何应对明道云API数据过长带来的Token超限问题

在集成明道云与大型语言模型(LLM)如ChatGPT或本地部署的Dify时,开发者经常会面临一个核心问题:API获取的数据太长,超出LLM支持的Token数限制,导致无法直接处理。本文将深入探讨这个问题的成因,并提供几种可行的解决方案,包括分段处理、外部知识库构建等策略。 明道云AP…...