美文网首页
[译] Redis 事务

[译] Redis 事务

作者: 小码弟 | 来源:发表于2019-02-17 12:23 被阅读0次

文章来自 Redis.io

事务

MULTI, EXEC, DISCARD, WATCH 是 Redis 事务的基础。它使得一组命令可以一步执行,并提供以下两点保证:

  • 事务中的全部命令都是串行、顺序执行的。来自其他客户端的请求不会打断正在运行的事务代码。这保证了事务里的命令独立运行。

  • 所有命令要么全被执行,要么一个也不执行,因此 Redis 事务也具有原子性。EXEC 命令触发执行所有事务命令。如果客户端在调用 MULTI 命令之前丢失连接,不会执行任何命令;同理,如果执行了EXEC, 那么所有命令都会被执行。如果使用了 append-only 文件,Redis 会调用一次 write(2) 系统函数来把本次事务持久化到磁盘。但是,如果 Redis 服务器崩溃或被硬件中断,那就有可能只有部分命令被记录下来。Redis 会在重启时检查这种情况,如果属实,它会报错并退出。使用 redis-check-aof 工具可以修复 append-only 文件,删除不完整的事务命令,使得服务器正常重启。

从 Redis-2.2 开始, Redis 额外提供了另一层保护,提供一种类似于 check-and-set(CAS) 操作的乐观锁机制。

用法

MULTI 标志事务的开始。这个命令总是回复 OK。之后用户可以指定多个命令。Redis 并不会立即执行它们,而是将其入队。执行EXEC后,所有命令立即被执行。
DISCARD 命令则清空整个队列,退出事务。

下面例子将 foo 和 bar 指示的值原子自增。

> MULTI
OK
> INCR foo
QUEUED
> INCR bar
QUEUED
> EXEC
1) (integer) 1
2) (integer) 1

从上面可以看出, EXEC 返回一系列回复。每个响应都是单个命令的回复,顺序也跟命令的顺序一致。
当 Redis 连接处于 MULTI 请求过程时,所有的命令都以 QUEUED 响应。入队命令待由 EXEC 后执行。

事务中的错误

事务有两种可能的错误:

  • 命令入队失败,因此它是 EXEC 执行之前的错误。如,命令语法有错(参数数量不对、命令名称不对等等),或者处于紧急情况,像内存溢出(如果服务器配置了 maxmemory)。

  • 调用 EXEC 后出错。如,对一个键执行错误类型的值操作(对字符串键执行列表命令)。

在执行 EXEC 之前,客户端可以通过命令的响应来察觉第一种错误: 如果返回 QUEUED, 说明正确入队; 否则返回错误信息。如果入队过程中出错,大多数 Redis 客户端终止这个事务。

但从 Redis-2.6.5 开始,服务器会记录手机命令时的错误,在执行 EXEC 时拒绝执行事务,自动丢弃这个事务。

Redis-2.6.5 之前的行为是,事务继续执行其他正确入队的命令,尽管 EXEC 发生过错误。后来的新行为使得 Redis 更容易将事务与流水线相结合。如此,整个事务可以一次性发送出去,一次性接受回复。

发生在 EXEC 调用之后的错误处理也没有什么特别的:尽管有的命令出错了,但照样执行正确入队的命令。

从协议角度理解更加清晰。下面这个例子中,尽管语法没错,但有一个命令不能执行成功。

Trying 127.0.0.1...
Connected to localhost
Escape character is '^]'.
MULTI
+OK
SET a 3
abc
+QUEUED
LPOP a
+QUEUED
EXEC
*2
+OK
-ERR Operation against a key holding the wrong kind of value

EXEC 批量返回两个字符串型响应,一个是 OK,一个是 -ERR。
值得注意的是,虽然有一个命令出错了,但是 Redis 仍然执行了其他正确入队的命令,即,Redis 并不是啥都没做。

再来一个例子,仍使用 Telnet 连接,表明尽可能快地报告语法错误。

MULTI
+OK
INCR a b c
-ERR wrong number of arguments for 'incr' command

由于语法错误, INCR 命名没有入队。

为什么 Redis 不支持回滚?

如果你有过关系型数据库的使用经验,可能会对 Redis 事务出错但仍执行部分命令的行为感到奇怪。

现在来理解一下这个行为:

  • Redis 命令出错的两种情况:语法不对(而且这种错误不会在入队过程中被发现),和执行类型不匹配的操作:也就是说,错误的命令通常是编程错误导致的,这种行为完全可以在开发环境下检查到,不会涉及生产环境。
  • Redis 天生简洁、快速,不需要回滚功能。

反对 Redis 不支持回滚的观点之一是说,它会带来bugs。但是,请注意,回滚并不能帮你避免编程错误。比如,一条语句将值自增2,而不是1,或者对错误的键执行自增,那么没有任何回滚机制能帮得上忙。鉴于没有办法完全保证免于编程错误,以及出错的 Redis 命令不可能进入生产环境,我们选择了不支持回滚功能,以实现更简单更快的数据库。

丢弃命令队列

DISCARD 可以用来终止一个事务。这样一来,不执行任何命令,连接状态转向正常。

> SET foo 1
OK
> MULTI
OK
> INCR foo
QUEUED
> DISCARD
OK
> GET foo
"1"

使用 check-and-set 的乐观锁

WATCH 为 Redis 事务提供了 check-and-set 行为支持。

被 WATCH 的键会被监测其可能的修改。如果在执行 EXEC 之前,至少有一个被 WATCH 的键被修改,那么整个事务将失效。EXEC 返回空回复告知事务失败。

比如我们需要原子性地自增一个键(假设还没有 INCR 这个命令)。

一开始可能会这么做

val = GET key
val = val + 1
SET key $val

这种做法只有当一个客户端在一定时间内执行时才有效。如果多用户几乎同时对这个键执行自增操作,将会产生竞态(race condition)。比如,客户端 A 和 B 都读到旧值 10。两个人都将这个值自增到 11。最终把新值赋给这个键。因此最这个键的值是11,而不是预期的12。

幸亏有 WATCH 命令,我们可以处理这一问题

WATCH key
val = GET key
val = val + 1
MULTI
SET key $val
EXEC

如果竞态发生,另一个客户端在 WATCHEXEC 之间修改了 key 的值,那么整个事务将失败。

重复这个操作,期望不产生新的竞态。这种锁称作乐观锁,在锁机制中很有用。在许多应用场景中,多客户端访问不同的键,因此冲突不太频繁发生,通常也就不需要重复执行这个操作。

解释 WATCH

到底什么是 WATCH 呢?这个命令给 EXEC 加了一层限制:限制 Redis 只有当被监测的键没有被修改时才执行事务。否则不会进入事务。(注意,如果你 WATCH 一个有过期时间的键,并在 WATCH 之后过期了,那么 EXEC 仍会有效,即事务不会失败)。

WATCH 可以多次使用。WATCH 的生命周期从被调用开始,到 EXEC 执行时结束。你可以一次给 WATCH 传多个键。

当执行 EXEC 后,所有的键都处于 UNWATCHED 状态,不管事务成功失败与否。当客户端连接断开时,所有的键也归为 UNWATCHED。

也可以用 WATCH (不带参数)来清空所有被监视的键,即取消所有被监视状态。如果我们一开始监视了一些键,自己想修改它们,而对其加上乐观锁,但后来改主意了,这时候执行 UNWATCH 来把连接置于正常状态,以便开启新的事务。

使用 WATCH 实现 ZPOP

实现 ZPOP 是解释 WATCH 用来创建原子操作的好例子。这个命令原子地从有序集合中弹出分值(score)最低的元素。下面是最简单的一种实现。

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC

如果 EXEC 失败了(返回空回复),重复执行。

Redis 脚本和事务

从定义上讲, Redis 脚本也具有事务性。所以通常对事务的操作也可以施加于脚本之上。脚本更简单,执行起来也更快。

之所以有这部分,是因为脚本在 Redis-2.6 才引入,而事务存在已久。但是短期内不会移除对事务的支持因为即使不借助 Redis 脚本,避免竞态也是有可能的,尤其是 Redis 事务的实现复杂性已经最低了。

但是,很有可能不久的将来, Redis 用户群只使用脚本。如果真是这样,我们会考虑把事务功能移除。

相关文章

  • [译] Redis 事务

    文章来自 Redis.io 事务 MULTI, EXEC, DISCARD, WATCH 是 Redis 事务的基...

  • redis系列(十):事务

    redis有事务么? redis官方说是有事务的。但这个事务不是我们普遍理解的mysql事务。 redis的事务不...

  • 九、Redis 事务

    Redis 事务 Redis事务描述: Redis事务允许在单个步骤中执行一组命令。以下是Redis事务的两个属性...

  • Redis事务

    redis事务机制 Redis事务与传统关系型事务的比较

  • Redis事务

    转载自Redis之Redis事务 Redis事务的概念: Redis 事务的本质是一组命令的集合。事务支持一次执行...

  • redis中的事物、消息订阅、持久化

    Redis 中的事务 Redis支持简单的事务 Redis与 mysql事务的对比 注: rollback与dis...

  • Redis简单操作记录

    Redis事务 1.Redis事务本质:一组命令的集合,加入队列,然后执行,执行完事务结束。 redis事务: ①...

  • JavaGuide知识点整理——Redis面试题总结(下)

    Redis事务 如何使用Redis事务? Redis可以通过multi,exec,discard和watch等命令...

  • redis 常用指令

    Redis 的事务 Redis 的事务处理与 RDBMS 的事务有一些不同。首先 Redis 不支持事务的回滚机制...

  • Redis学习笔记:事务

    Redis学习笔记:事务 原文链接:Redis学习笔记:事务 一、事务的描述 和MySQL一样,Redis中也有事...

网友评论

      本文标题:[译] Redis 事务

      本文链接:https://www.haomeiwen.com/subject/hewieqtx.html