Redis 事务机制之ACID属性

news/2024/7/7 22:46:18

事务属性

事务是对数据库进行读写的一系列操作。在事务执行时提供ACID属性保证: 包括原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

  • 原子性(Atomicity): 事务中多个操作要么全部成功,要么全部失败。
  • 一致性(Consistency): 数据库中的数据在事务执行前后是一致的。
  • 隔离性(Isolation): 一个事务的执行不被其他事务影响。
  • 持久性(Durability):数据库执行事务后,数据的修改被持久化保存。

Redis 中的事务如何工作:

  • Redis 事务允许在一个步骤中执行一组命令,Redis提供了事务相关的命令: MULTI 、 EXEC、 DISCARD、WATCH。
  • Redis 事务做了两个重要的保证:
    1)事务中的所有命令都被序列化并按顺序执行。另一个客户端发送的请求永远不会在执行Redis事务的过程中被服务。这保证了命令作为单个隔离操作执行。
    2)EXEC命令触发事务中所有命令的执行,因此,如果客户机在调用EXEC命令之前在事务的上下文中失去与服务器的连接,则不会执行任何操作,相反,如果调用EXEC命令,则执行所有操作。

原子性(Atomicity)

Redis事务机制是否能够保证原子性? 在事务期间,可能会遇到两种命令错误:

  • 命令可能无法排队,因此在调用EXEC之前可能会出现错误。例如,该命令可能在语法上是错误的(参数数量错误,命令名称错误,…),或者使用不存在的命令,或者可能存在一些关键条件,如内存不足条件(如果服务器使用maxmemory指令配置了内存限制)。
# 开启事务
192.168.88.11:6380> multi
OK

# 语法错误,redis不支持该命令
192.168.88.11:6380> sett key hello 
(error) ERR unknown command `sett`, with args beginning with: `key`, `hello`, 

# 正确命令,Redis命令入队
192.168.88.11:6380> incr counter 
QUEUED

# 执行事务,因之前命令有错,事务无法执行
192.168.88.11:6380> exec
(error) EXECABORT Transaction discarded because of previous errors.

# 键counter为空
192.168.88.11:6380> get counter
(nil)
  • 在调用EXEC之后,命令可能会失败,例如我们对具有错误值的键执行了操作(例如对字符串值调用列表操作)。即命令和操作的数据类型不匹配。但Redis实例并没有检查出错误。这种属于运行时错误,Redis在执行这些事务操作时就会报错。
    需要注意的是, 即使命令失败,队列中的所有其他命令也会被处理——Redis 不会停止命令的处理。
    需要注意的是, 即使命令失败,队列中的所有其他命令也会被处理——Redis 不会停止命令的处理。
    需要注意的是, 即使命令失败,队列中的所有其他命令也会被处理——Redis 不会停止命令的处理。
# 键 k1 为字符串类型
192.168.88.11:6380> type k1
string

# k1 当前值为 hello 
192.168.88.11:6380> get k1
"hello"

# 开启事务
192.168.88.11:6380> multi 
OK

# 对 k1 字符串类型调用列表操作: LPUSH,此时并不报错
192.168.88.11:6380> LPUSH k1 1
QUEUED

# 继续执行append操作
192.168.88.11:6380> APPEND k1 world
QUEUED

# 执行事务,第一个操作报错,第二个操作正常执行
192.168.88.11:6380> exec
1) (error) WRONGTYPE Operation against a key holding the wrong kind of value
2) (integer) 10

# k1 的傎已修改
192.168.88.11:6380> get k1
"helloworld"

Redis 不支持事务回滚,因为支持回滚会对 Redis 的简单性和性能产生重大影响。虽然 Redis 提供了 DISCARD 命令,DISCARD可用于中止事务。丢弃命令队列。 此时,不会执行任何命令,连接状态恢复正常。起不到回滚的效果。

DISCARD可用于中止事务。丢弃命令队列。不是回滚命令。
DISCARD可用于中止事务。丢弃命令队列。不是回滚命令。
DISCARD可用于中止事务。丢弃命令队列。不是回滚命令。

# 设置foo的值为1
192.168.88.11:6380> SET foo 1
OK

# 开启事务
192.168.88.11:6380> MULTI
OK

# 自增操作
192.168.88.11:6380> INCR foo
QUEUED

# 执行DISCARD 命令,放弃事务
192.168.88.11:6380> DISCARD
OK

# 再次读取foo值,值没有被修改
192.168.88.11:6380> GET foo
"1"
  • 如果在执行事务 EXEC 命令时,Redis实例或机器意外故障。导致事务执行失败。
    1)如果没有开启AOF,操作日志不会被记录,数据丢弃。
    2)如果有开启AOF,AOF 日志是仅追加日志,断电时也不会出现损坏问题。即使由于某种原因(磁盘已满或其他原因)日志以半写命令结束,redis-check-aof 工具也能够轻松修复它。

如果 AOF 被截断,该怎么办?
当 aof-load-truncated 启用时,AOF 中的最后一个命令可能会被截断。Redis 的最新主要版本无论如何都能够加载 AOF,只是丢弃文件中最后一个格式不正确的命令。在这种情况下,会有下面日志:此时事务操作不会再被执行,从而保证原子性。

2213:M 22 Feb 2024 14:40:08.204 # !!! Warning: short read while loading the AOF file !!!
2213:M 22 Feb 2024 14:40:08.204 # !!! Truncating the AOF at offset 237 !!!
2213:M 22 Feb 2024 14:40:08.204 # AOF loaded anyway because aof-load-truncated is enabled
2213:M 22 Feb 2024 14:40:08.204 * DB loaded from append only file: 0.000 seconds

当 aof-load-truncated 未启用时,Redis无法启动,会有下面的日志:

2302:M 22 Feb 2024 14:56:13.638 # Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.

需要使用 redis-check-aof 工具修复AOF 日志文件,把未完成的事务操作从 AOF 文件中删除。通过AOF 恢复实例后,事务操作不会再被执行,保证了原子性。

执行以下步骤进行恢复:

root@ubuntu-x64_01:/data/redis/data# cp appendonly.aof appendonly.aof.bak 

制作 AOF 文件的备份副本。

使用Redis附带的redis-check-aof 工具修复原始文件:

root@ubuntu-x64_01:/data/redis/data# redis-check-aof --fix appendonly.aof
0x              f5: Expected to read 6 bytes, got 0 bytes
AOF analyzed: size=245, ok_up_to=166, diff=79
This will shrink the AOF from 245 bytes, with 79 bytes, to 166 bytes
Continue? [y/N]: y
Successfully truncated AOF

所以Redis对事务原子性属性不同场景下会不同。具体如下:
1)如果命令语法错误、在命令入队就报错,事务将无法执行,保证了原子性。
2)如果命令本身正确,命令入队成功。但实际执行时报错。无法保证原子性。
3)如果EXEC命令执行时实例故障。此时有开启AOF,可以保证原子性。

综合上面场景:
当命令入队时没报错,实际执行时报错,运行时错误。不能保证原子性!!!
当命令入队时没报错,实际执行时报错,运行时错误。不能保证原子性!!!
当命令入队时没报错,实际执行时报错,运行时错误。不能保证原子性!!!

在这里插入图片描述

一致性(Consistency)

事务的一致性要看具体场景,事务在执行时可能会有错误命令、参数数量错误、实例故障等因素影响。

1) 调用EXEC之前出现错误
即命令入队时报错,例如,该命令可能在语法上是错误的(参数数量错误,命令名称错误,…),或者使用不存在的命令。这种情况下,事务本身不会被执行,可以保证一致性。

在这里插入图片描述
2)调用EXEC之后出现错误
在调用EXEC之后,命令可能会失败,例如,由于我们对具有错误值的键执行了操作(例如对字符串值调用列表操作),在这种情况下,有错误的命令不会执行,正确的命令成功执行。不影响数据的一致性。

在这里插入图片描述

  1. 执行事务 EXEC 命令实例故障
    如果在执行事务 EXEC 命令时,Redis实例或机器意外故障。导致事务执行失败。要根据是否持久化分场景分析:
    3.1)如果没有持久化(即没开启RDB和AOF),无持久化无数据。数据是一致的。
    3.2)如果有RDB持久化,事务执行时RDB快照不会执行。数据是一致的。
    3.3)如果有AOF持久化,事务可能没被记录或记录不完整(使用Redis附带的redis-check-aof 工具修复原始文件),数据是一致的。

综合上面场景:
当命令语法错误、命令执行错误 或 Redis发生意外故障场景下。Redis事务机制对一致性属性有保证的。
在这里插入图片描述

隔离性(Isolation)

事务是在EXEC命令后才能真正执行,EXEC命令之前,命令会先进入队列。 分两种场景来分析:

1)调用EXEC之前

事务在 EXEC 命令前执行,命令操作是暂存在命令队列,并没有真正执行,此时。隔离性使用 WATCH 机制来实现保证 。

WATCH用于为 Redis 事务提供检查和设置 (CAS) 行为。

监视键是为了检测针对它们的更改。如果在执行EXEC命令之前至少修改了一个被监视的键,那么整个事务将终止,EXEC将返回一个Null应答来通知事务失败。

比如,我们要对键 foo 加 10 操作:

只有当我们有一个客户端在给定时间内执行操作时,这才会可靠地工作。如果多个客户端同时尝试增加键,就会出现竞争条件。例如,客户端A和B将读取旧值,例如:1。两个客户端都将该值增加到11,并最终设置为键的值。所以最后的值是11而不是21。
在这里插入图片描述

此时,我们需要使用WATCH命令来确保检测旧值没有其它客户端修改过,如果修改了,存在竞争条件,在调用WATCH和调用EXEC之间的时间内,另一个客户端修改了val的结果,则事务将失败。否则事务就能正常执行。

我们只需要重复这个操作,希望这次不会出现新的竞争。这种形式的锁定称为乐观锁定。在许多用例中,多个客户端将访问不同的键,因此不太可能发生冲突,通常不需要重复操作。

# 客户端1 
192.168.88.11:6380> CLIENT ID
(integer) 13

192.168.88.11:6380> set key "java"
OK
 
192.168.88.11:6380> watch key 
OK
192.168.88.11:6380> multi
OK

192.168.88.11:6380> append key go
QUEUED
192.168.88.11:6380> exec
(nil)

192.168.88.11:6380> get key 
"javapython"

# -----------------------------------------------

# 客户端 2 
192.168.88.11:6380> CLIENT ID
(integer) 14

# 此步骤在 客户1 开启事务后 执行 
192.168.88.11:6380> append key python
(integer) 10

192.168.88.11:6380> get key 
"javapython"

在这里插入图片描述

2)调用EXEC之后

因为Redis主线程是单线程执行命令,EXEC命令执行后,Redis会先执行命令队列中的所有命令执行完。再处理其它客户请求操作命令。所以这种情况不会影响事务的隔离性。

在这里插入图片描述

综上场景,使用WATCH命令在事务执行前,检测它们的更改。如果在执行EXEC命令之前至少修改了一个被监视的键,那么整个事务将终止,EXEC将返回一个Null应答来通知事务失败,避免事务的隔离性被破坏。如果在EXEC命令之后执行,Redis会保证先把命令队列中的所有命令执行完。不会破坏事务的隔离性。

在这里插入图片描述

持久性(Durability)

事务的持久性取决于Redis持久化配置,如果Redis没有配置RDB和AOF持久化,无持久化无数据。无持久性保证。那么事务属性肯定得不到保证。如果配置了持久性,根据不同的场景分析如下:

1)如果有RDB持久化,事务执行后,RDB快照还未执行就故障。无持久性保证。
2)如果有AOF持久化,取决于三种配置(no、everysec、alway)都存在数据丢失的情况,无持久性保证。

综上,不管是哪种模式,持久性都无法保证。 Redis本身是内存数据库,持久性并不是必须的属性。

在这里插入图片描述

小结

1)Redis事务ACID属性可以保证一致性和隔离性。但是无法保证原子性、持久性(由于Redis本身是内存数据库),持久性并不是必须的属性。一般关注ACI属性。
2)Redis的原子性在使用时较复杂,请参考官方文档操作命令,要确保命令运行正确,否则原子性无法得到保证。
3)在使用事务时,可以结合 pileline 一次发出多个命令而无需等待每个命令的响应来提高性能、或使用LUA脚本。
4)事务还需要考虑其它的,比如redis中的操作是redis脚本,它是事务性的。Redis 事务做的所有事情,你也可以用脚本来做,而且通常脚本会更简单、更快。


http://lihuaxi.xjx100.cn/news/2045863.html

相关文章

二十八、图像的高斯模糊操作

项目功能实现&#xff1a;对一张图片进行高斯模糊操作 按照之前的博文结构来&#xff0c;这里就不在赘述了 更多的图像模糊操作原理可参考博文&#xff1a;七、模糊操作&#xff0c;里面有详细原理讲解&#xff0c;只不过代码是python写的。 一、头文件 gaussian_blur.h #p…

几种常见的配置文件格式,以及它们的优缺点和用法汇总

文章目录 1. INI2. XML3. JSON4. YAML5. TOML结论 配置文件是用于存储软件程序、操作系统或服务器等的设置的文件。不同的配置文件格式具有不同的结构和语法&#xff0c;选择哪种格式通常取决于具体的应用需求、易用性、可读性以及环境支持。以下是几种常见的配置文件格式&…

计算机网络实验七 利用 Java 开发网络应用程序

一、实验目的和要求 1)基本掌握利用 Java 开发环境调试应用程序的方法。 2)理解基于套接字开发网络应用程序的过程,深入理解 Ping 工作原理。 3)深入理解 HTTP 协议的格式和工作过程,理解 Web 代理服务器工作原理。 二、实验环境 1)运行 Windows 2008 Server/XP/7 操…

工厂安全智能巡检机器人系统开发及其对您的价值体现

在工厂生产环境中&#xff0c;安全生产一直是业主、采购商以及中间商们十分关注的焦点。为了提升工厂生产线的安全性、效率和可追溯性&#xff0c;一款工厂安全智能巡检机器人系统应运而生&#xff0c;旨在为需求方带来全新的生产安全管理模式。 自动巡检增加效率 传统的工厂…

Sublime Text4配置C#运行环境

这里写自定义目录标题 前言部署.NET环境Sublime Text4配置C#编译环境1. 下载插件 运行测试 前言 今天把家里的9年前的远古神机搬了出来&#xff0c;重装了个win7的精简版&#xff0c;本打算装个VScode测试一下是否能写C#代码&#xff0c;结果是可以的&#xff0c;但&#xff0…

Centos开机网卡自启动失败

问题背景 每次都要手动启动在这里插入代码片 解决方案: 关闭 NetworkManager 服务 systemctl disable NetworkManager systemctl stop NetworkManager重启就会发现网卡已经可以自动启动了

Vision Transfomer系列第二节---Tricks测试

目录 学习式和固定式位置编码测试dropout的作用测试block深度的作用测试embeding维度大小的作用测试多头的作用测试Overlap Patch的作用 学习式和固定式位置编码测试 主要测试无位置编码\可学习位置编码和固定式位置编码的训练效果: 其中固定式位置编码采用之前博客的正余弦位…

Flutter常用命令,持续更新

目录 前言 Flutter 常用命令 Dart 常用命令 adb 常用命令&#xff08;用于 Android 开发&#xff09; 前言 当在开发Flutter项目时&#xff0c;熟悉一些常用的命令是非常重要的。这些命令可以帮助你执行各种任务&#xff0c;从构建应用程序到调试和测试。以下是一些Flutte…