百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

为什么 Redis 不支持事务回滚 redis不支持什么数据结构

wptr33 2024-12-22 21:12 71 浏览

Redis 具有两种自动执行多个操作的主要机制:MULTI/EXEC 事务和 Lua 脚本。 Redis 中事务的一大特点是缺乏回滚机制,这常常让新手感到困惑。 在我担任 Redis 开发倡导者期间,我与一些具有传统 SQL 背景的工程师交谈过,他们发现这令人不安,因此我想通过此博客分享我对此主题的看法,并认为在Redis中您不需要回滚。

MULTI/EXEC transactions

Redis 中的事务以 MULTI 命令开始。 一旦发送,连接就会切换模式,并且通过连接发送的所有后续命令将由 Redis 排队,而不是立即执行,但 DISCARD 和 EXEC 除外(这将分别导致事务中止或提交)。 提交事务意味着执行先前排队的命令。

MULTI
SET mykey hello
INCRBY counter 10
EXEC

事务(和 Lua 脚本)确保了两件重要的事情:

  1. 其他客户端不会看到部分状态,这意味着所有客户端都将在应用事务之前或之后看到状态。
  2. 如果节点发生故障,一旦 Redis 重新启动,它将重新加载整个事务,或者不从 AOF 文件中加载任何事务。

关于事务要记住的最后一件基本事情:即使启动了 MULTI 事务,Redis 仍将继续为其他客户端提供服务。 仅当通过调用 EXEC 提交事务时,Redis 才会短暂停止应用其他客户端的命令。 这与 SQL 数据库有很大不同,在 SQL 数据库中,事务利用 DBMS 内的各种机制来提供不同程度的隔离保证,并且客户端可以在执行事务时从数据库读取值。 在 Redis 中,事务是“一次性”的,换句话说,只是一次性执行的一系列命令。 那么,如何创建依赖于 Redis 中数据的事务呢? 为此,Redis 实现了 WATCH,这是一个执行乐观锁定的命令。

Optimistic locking with WATCH

让我从实战层面向您展示为什么在事务中无法从 Redis 读取值:

MULTI
SET counter 42
GET counter
EXEC

如果在redis-cli中运行这一系列命令,“GET counter”的回复将是“QUEUED”,并且只有调用EXEC才会返回值“42”,同时执行返回“OK” 设置命令。

要编写依赖于从 Redis 读取数据的事务,必须使用 WATCH。 一旦运行,该命令将确保只有在调用 EXEC 之前被监视的键没有更改的情况下才会执行后续事务。

例如,如果 INCRBY 不存在,这就是实现原子增量操作的方法:

WATCH counter
GET counter
MULTI
SET counter <the value obtained from GET + any increment>
EXEC

在此示例中,我们首先通过“counter”键创建一个 WATCH 触发器,然后获取其值。 请注意 GET 在我们启动事务主体之前是如何发生的,这意味着它将立即执行并返回键的当前值。 此时,我们使用 MULTI 启动事务,并通过在客户端计算“counter”的新值来应用更改。

如果多个客户端尝试同时对“counter”这个key执行相同的事务,则某些事务将被 Redis 自动丢弃。 此时,重试事务通常是客户端的工作。 这类似于 SQL 事务,较高的隔离级别有时会导致事务中止,从而使客户端需要重试它。

Lua scripts

虽然 WATCH 对于执行铰接事务非常有用,但当您需要执行依赖于 Redis 中数据的多个操作时,使用 Lua 脚本通常更容易、更高效。 使用 Lua 脚本,您可以将逻辑发送到 Redis(以脚本本身的形式),并让 Redis 在本地执行代码,而不是像我们在上面的示例中那样将数据推送到客户端。 这样做更快有几个原因,但最重要的一个原因是:Lua 脚本可以从 Redis 读取数据,而不需要乐观锁定。

这就是前一个事务作为 Lua 单行代码实现的方式:

EVAL "redis.call('SET', KEYS[1], tonumber(redis.call('GET', KEYS[1]) or 0) + tonumber(ARGV[1]))" 1 counter 42

在我看来,在一些合理的情况下,您可能会更喜欢使用乐观锁定的事务而不是 Lua:

  • 您的事务所依赖的键不会被频繁修改,这意味着您确信乐观锁定几乎永远不会中止事务。
  • 可能是,第三方服务编写的大量逻辑,因此没有简单的方法可以将该逻辑移动到 Lua 脚本。

除非这两点同时满足,否则都推荐使用 lua。

Errors in transactions

回顾一下:MULTI/EXEC 事务(没有 WATCH)和 Lua 脚本永远不会被 Redis 丢弃,而 MULTI/EXEC + WATCH 将导致 Redis 中止依赖于相应键被监视后更改的值的事务。 Lua 脚本比简单(即无 WATCH)事务更强大,因为它们还可以从 Redis 读取值,并且比“WATCH”事务更高效,因为它们不需要乐观锁定来读取值。

乐观锁的关键点在于,当 WATCHed key 发生更改时,当客户端使用 EXEC 提交事务时,整个事务将立即被丢弃。 Redis 有一个主要的单线程命令执行循环,因此当事务队列正在执行时,不会运行其他命令。 这意味着Redis事务具有真正的可序列化隔离级别,也意味着不需要回滚机制来实现WATCH。

但是当事务出现错误时会发生什么? 答案是Redis将继续执行所有命令并报告发生的所有错误。

更准确地说,Redis 可以在客户端调用 EXEC 之前捕获某些类型的错误。 一个基本的例子是明显的语法错误:

MULTI
GOT key? (NOTE: Redis has no GOT command and, after season 8, it never will)
EXEC

但并非所有错误都可以通过检查命令语法来发现,这些错误可能会导致事务行为异常。 举个例子:

MULTI
SET counter banana
INCRBY counter 10
EXEC

上面的示例将被执行,但 INCRBY 命令将失败,因为“counter”键不包含数字。 这种类型的错误只有在运行事务时才能发现(没关系,在这个简化的示例中,我们是设置错误初始值的人)。

此时此刻,人们可能会说回滚是件好事。 如果不是出于两个考虑,我可能会同意:

  1. 实现回滚所需的快照机制会产生相当大的计算成本。 这种额外的复杂性与 Redis 的理念和生态系统格格不入。
  2. 回滚无法捕获所有错误。 在上面的示例中,我们将“counter”设置为“banana”以显示明显的错误,但在现实世界中,以错误方式使用“counter”键的进程可能会删除它,或者放入一个 例如,信用卡号码。 回滚会增加相当多的复杂性,并且仍然无法完全解决问题。

第二点特别重要,因为它也适用于 SQL:SQL DBMS 提供了许多机制来帮助保护数据完整性,但即使它们也不能完全保护您免受编程错误的影响。 在这两个平台上,编写正确交易的负担仍然由您承担。

Rollbacks in SQL DBMSs

如果这似乎与您使用 SQL 数据库的体验相冲突,让我们看看「依靠报错来强制约束」与「依靠报错来保护数据免受代码中的错误影响」之间的区别。

SQL 中的常见做法是使用索引来实现对数据的约束,并依赖客户端的这些索引来确保正确性。 一个常见的示例是向“用户名”列添加“UNIQUE”约束,以确保每个用户具有不同的用户名。 此时,客户端将尝试插入新用户,并预计当另一个同名用户已存在时插入会失败。

这是 SQL 数据库的完全合法使用,但是依靠约束来实现应用程序逻辑与期望回滚来保护您免受事务逻辑本身的错误有很大不同。

在 AWS re:Invent 2019 上,当一位与会者问我“为什么 Redis 没有回滚功能?” 我的回答是基于:举例人们在 SQL 中使用回滚的原因。 在我看来,这样做的主要原因只有两个:

First reason to use rollbacks: concurrency

最常见的 SQL 数据库是多线程应用程序,当客户端请求高隔离级别时,DBMS 更愿意触发异常,而不是停止为所有其他客户端提供服务。 这对于 SQL 生态系统来说是有意义的,因为 SQL 事务是“喋喋不休的”:客户端锁定几行,读取一些值,计算要应用的更改,最后提交事务。

在 Redis 中,事务并不意味着具有交互性。 Redis 中主事件循环的单线程性质确保事务运行时不会执行其他命令。 这可确保所有事务真正可序列化,而不会违反隔离级别。 当事务使用乐观锁定时,Redis 将能够在执行事务队列中的任何命令之前中止事务,这不需要回滚。

Second reason to use rollbacks: leveraging index constraints

在 SQL 中,通常使用索引约束来实现应用程序中的逻辑。 我提到了 UNIQUE,但这同样适用于外键约束等。 前提是应用程序依赖于已正确配置的数据库并利用索引约束以有效的方式实现逻辑。 但我确信每个人都见过应用程序行为不当,例如,当有人忘记添加 UNIQUE 约束时。

虽然 SQL DBMS 在保护数据完整性方面做得很好,但您不能指望能够避免事务代码中的所有错误。 有一类重要的错误不会违反类型检查或索引约束。

Redis 没有内置索引系统(Redis 模块是另一回事,不适用于此)。 例如,要强制唯一性,您可以使用 Set(或等效)数据类型。 这意味着 Redis 中表达操作的正确方式看起来与 SQL 中的等效方式不同。 Redis的数据模型和执行模型与SQL有很大不同,相同的逻辑操作会根据平台以不同的方式表达,但应用程序必须始终与数据库的状态同步。

尝试 INCRBY 包含非数字值的键的应用程序与期望 SQL 架构与数据库上的内容不一致的应用程序相同。

Redis vs. SQL

如果您有 SQL 背景,您可能会对 Redis 中事务的工作方式感到惊讶,这是可以理解的。 鉴于 NoSQL 已经证明关系数据库并不是存储数据的唯一有价值的模型,因此不要错误地认为任何偏离 SQL 提供的功能本质上都是低劣的。 SQL 事务非常繁琐,基于多线程模型,并与其他子系统进行互操作,以在发生故障时利用回滚。 相比之下,Redis 事务更注重性能,并且没有索引子系统可用于强制执行约束。 由于这些差异,您在 Redis 中使用的事务编写“风格”与 SQL 有着根本的不同。

这意味着 Redis 中缺乏回滚并不会限制表达能力。 所有合理的 SQL 事务都可以重写为功能等效的 Redis 事务,但在实践中并不总是那么容易。 推理最初在 Redis 中的 SQL 中阐明的问题需要您以不同的方式思考数据,并且还需要考虑不同的执行模型。

最后,回滚确实有助于保护数据免受编程错误的影响,但它们并不是解决该问题的方法。 作为基于键值结构的多模型数据库,Redis 无法提供与 SQL 相同级别的“类型检查”便利性,但有一些技术可以帮助实现这一点,正如 Kyle Davis,Head of Developer Advocacy at Redis,在最近的博客文章中进行了解释: Bullet-Proofing Lua Scripts in RedisPy.

也就是说,无论是使用关系数据库还是使用 Redis,您的应用程序都需要与数据库中的内容同步。 对于 Redis 而言,回滚的效用不会超过性能和额外复杂性方面的成本。 如果您想知道 Redis 为何比其他数据库快得多,这也是一个原因。

相关推荐

[常用工具] git基础学习笔记_git工具有哪些

添加推送信息,-m=messagegitcommit-m“添加注释”查看状态...

centos7安装部署gitlab_centos7安装git服务器

一、Gitlab介1.1gitlab信息GitLab是利用RubyonRails一个开源的版本管理系统,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。...

太高效了!玩了这么久的Linux,居然不知道这7个终端快捷键

作为Linux用户,大家肯定在Linux终端下敲过无数的命令。有的命令很短,比如:ls、cd、pwd之类,这种命令大家毫无压力。但是,有些命令就比较长了,比如:...

提高开发速度还能保证质量的10个小窍门

养成坏习惯真是分分钟的事儿,而养成好习惯却很难。我发现,把那些对我有用的习惯写下来,能让我坚持住已经花心思养成的好习惯。...

版本管理最好用的工具,你懂多少?

版本控制(Revisioncontrol)是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史,方便查看更改历史记录,备份以便恢复以前的版本的软件工程技术。...

Git回退到某个版本_git回退到某个版本详细步骤

在开发过程,有时会遇到合并代码或者合并主分支代码导致自己分支代码冲突等问题,这时我们需要回退到某个commit_id版本1,查看所有历史版本,获取git的某个历史版本id...

Kubernetes + Jenkins + Harbor 全景实战手册

Kubernetes+Jenkins+Harbor全景实战手册在现代企业级DevOps体系中,Kubernetes(K8s)、Jenkins和Harbor组成的CI/CD流水...

git常用命令整理_git常见命令

一、Git仓库完整迁移完整迁移,就是指,不仅将所有代码移植到新的仓库,而且要保留所有的commit记录1.随便找个文件夹,从原地址克隆一份裸版本库...

第三章:Git分支管理(多人协作基础)

3.1分支基本概念分支是Git最强大的功能之一,它允许你在主线之外创建独立的开发线路,互不干扰。理解分支的工作原理是掌握Git的关键。核心概念:HEAD:指向当前分支的指针...

云效Codeup怎么创建分支并进行分支管理

云效Codeup怎么创建分支并进行分支管理,分支是为了将修改记录分叉备份保存,不受其他分支的影响,所以在同一个代码库里可以同时进行多个修改。创建仓库时,会自动创建Master分支作为默认分支,后续...

git 如何删除本地和远程分支?_git怎么删除远程仓库

Git分支对于开发人员来说是一项强大的功能,但要维护干净的存储库,就需要知道如何删除过时的分支。本指南涵盖了您需要了解的有关本地和远程删除Git分支的所有信息。了解Git分支...

git 实现一份代码push到两个git地址上

一直以来想把自己的博客代码托管到github和coding上想一次更改一次push两个地址一起更新今天有空查资料实践了下本博客的github地址coding的git地址如果是Gi...

git操作:cherry-pick和rebase_git cherry-pick bad object

在编码中经常涉及到分支之间的代码同步问题,那就需要cherry-pick和rebase命令问题:如何将某个分支的多个commit合并到另一个分支,并在另一个分支只保留一个commit记录解答:假设有两...

模型文件硬塞进 Git,GitHub 直接打回原形:使用Git-LFS管理大文件

前言最近接手了一个计算机视觉项目代码是屎山就不说了,反正我也不看代码主要就是构建一下docker镜像,测试一下部署的兼容性这本来不难但是,国内服务器的网络环境实在是恶劣,需要配置各种镜像(dock...

防弹少年团田柾国《Euphoria》2周年 获世界实时趋势榜1位 恭喜呀

当天韩国时间凌晨3时左右,该曲在Twitter上以“2YearsWithEuphoria”的HashTag登上了世界趋势1位。在韩国推特实时趋势中,从上午开始到现在“Euphoria2岁”的Has...