如何在Redis中实现事务

如何在Redis中实现事务,第1张

事务介绍

事务(Transaction) ,是指作为单个逻辑工作单元执行的一系列 *** 作。事务必须满足ACID原则(原子性、一致性、隔离性和持久性)。

简单来说,事务可能包括1~N条命令,当这些命令被作为事务处理时,将会顺序执行这些命令直到完成,并返回结果,如果中途有命令失败,则会回滚所有 *** 作。

Redis事务使用总结:

Redis的事务机制允许同时执行多条指令,它是原子性 *** 作,事务中的命令要么全部执行,要么全部不执行,另外,事务中的所有指令都会被序列化,而且其开始执行过程中,不回被即时过来的指令所打断,其需要经历三个过程,分别为开始事务、命令入队以及执行事务。

·     相关命令

·     如何使用

·     脚本事务

·     遇到问题

·     例子演示

一、相关命令

1、MULTI

该命令用来开启事务,它总是返回ok结果,当其执行之后,客户端可以继续发送任意条数量的指令,这些指令不会立即被执行,而是被放到了队列中,直到EXEC被调用之后,所有命令才会被序列化执行。

2、EXEC

该命令负责触发并执行队列中所有的命令。

NOTE:

如果MULTI开启之后,因为某些原因没有成功执行EXEC,那么事务中所有的命令都不会被执行的。

3、DISCARD

该命令用来刷新事务中所有排队等待执行的指令,它总是返回ok结果,并且将服务连接状态恢复到正常。如果已经使用WATCH,那么其会将释放所有被WATCH的key。

4、WATCH

标记所有指定的key被监控起来,使其在事务中有条件的执行(乐观锁)。

NOTE:

A、WATCH使得EXEC命令需要有条件的执行,也就是事务只能在所有被监视的键没有被修改的前提下才能执行。另外,在EXEC被执行之后,所有的WATCH都会被取消。

B、UNWATCH手动取消对所有键的WATCH,如果执行了EXEC或者DISCARD,则不需要手动执行UNWATCH命令。

二、如何使用

Redis原生使用(Redis-cli):

127.0.0.1:6379>multi     // 事务开始的动作标志下面即为入队

OK

127.0.0.1:6379>set book-name "Thinking in Java"

QUEUED

127.0.0.1:6379>get book-name

QUEUED

127.0.0.1:6379>sadd tag "java" "Programming""Thinking"

QUEUED

127.0.0.1:6379>smembers tag

QUEUED

127.0.0.1:6379>exec     // 执行事务

1) OK

2) "Thinking in Java"

3) (integer) 3

4) 1) "Thinking"

2) "Programming"

3) "java"

127.0.0.1:6379>discard  // 事务已执行完毕 已经自动取消

(error) ERR DISCARD without MULTI

127.0.0.1:6379>multi

OK

127.0.0.1:6379>set book-name "Patterns in Java"

QUEUED

127.0.0.1:6379>get book-name

QUEUED

127.0.0.1:6379>sadd tag "Java" "Thinking""Programming"

QUEUED

127.0.0.1:6379>smembers tag

QUEUED

127.0.0.1:6379>discard  // 事务未执行 可以刷新队列指令状态 取消执行

OK

127.0.0.1:6379>exec     // 事务已经被取消不能再执行

(error) ERR EXEC without MULTI

三、脚本事务

Redis 2.6开始支持了脚本,而该脚本本身就是一种事务机制,所以任何在事务里可以完成的事,在脚本里面也能完成,并且使用脚本更简单些,并且速度也更快。不过因为事务提供了一种即使不使用脚本,也可以避免竞争条件的方法,并且事务本身的实现并不复杂,所以现在的使用也比较多,但不排除日后可能被替代或是占据主要地位的可能。

NOTE:

Redis为什么引入两种处理事务的方式?脚本功能是 Redis 2.6 才引入的,而事务功能则在更早之前就存在,所以 Redis 才会同时存在两种处理事务的方法。另外,事务脚本会在后续文章中总结介绍。

四、遇到问题

1、乐观锁实现

举个例子,假设我们需要原子性为某个键加1 *** 作(假设INCR不存在),那么应该是这样的执行语句:

SET mykey 1

val = GET mykey

val = val + 1

SET mykey ${val}

单个客户端访问 *** 作没有任何问题,如果是多个客户端同时访问mykey,就会产生资源共享访问问题,比如:现在有个两个客户端访问同一个键mykey,那么mykey的可能是2,但是我们期望的值应该是3才对,这个类似于高并发下的sync锁机制,所以我们需要使用WATCH来监控被共享的键mykey,如下:

WATCH mykey(可监控多个键)

val = GET mykey

val = val + 1

MULTI

SET mykey ${val}

EXEC

NOTE:

虽然大多情况下,多个客户端访问 *** 作同一个键的情况很少或没有,但是不能排除这个特殊情况,所以建议在有可能产生键共享的指令中使用WATCH在EXEC执行前对其监管。

2、Redis不支持回滚(Roll Back)

Redis的事务不支持回滚,这点不同于关系数据库中的事务,所以它的内部保持了简单且快速的特点。另外,Redis不支持回滚是这样考虑的:Redis事务中命令之所以会失败,是由于错误的编程所造成,通过事务回滚是不能回避这个根本问题。

NOTE:

Redis事务中命令执行失败,仍会继续执行后面的执行,在没有特殊干预前提下,直到执行完队列中所有指令为止。

3、使用事务可能遇到的问题

A、事务在执行 EXEC 之前,入队的命令可能会出错,举个例子:命令可能会产生语法错误(参数数量错误,参数名错误等),或者其他更严重的错误,比如内存不足(如果服务器使用maxmemory 设置了最大内存限制的话)。

B、事务在执行 EXEC 之前,举个例子:事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面等。

对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回QUEUED ,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。

从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。

在 Redis 2.6.5 以前, Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。而新的处理方式则使得在管道技术中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯即可。

至于那些在 EXEC 命令执行之后所产生的错误,并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。

五、例子演示

<?php

$redis = new \Redis()

$redis->connect('127.0.0.1',6379)

$result = array()

// 开启事务

$redis->multi()

// 添加指令到队列

$redis->set('book-name','Thinking in PHP!')

$redis->sAdd('tags','PHP','Programming','Thinking')

$bookname = $redis->get('book-name')

$tags = $redis->sMembers('tags')

// 执行事务

$redis->exec()

// 显示结果

echo '书名:'.$bookname.' 标签:'.$tags

?>

结果:

如果你了解过关系型数据库事务的话,相信这篇文章对你来说是很容易理解的了。具体什么是事务我就不说不多了,直接讲 Redis 事务相关的部分。

首先,我们先来看下,Redis 是怎么执行事务的。

show code:

一个事务的开始到结束会经过以下 3 个过程

结合上面的例子,用人话介绍这 3 个过程就是:

Redis 执行 multi 命令标志事务开始。

当客户端切换至事务状态后,服务端会将除了 exec、discard(取消事务,放弃执行事务块内的所有命令)、watch 和 multi 以外的命令放进一个先进先出的事务队列中。即上面例子的 2 个 set 命令会被放进队列,并返回 QUEUED 给客户端。

当客户端发送 exec 命令时,服务端会立即执行该命令。遍历这个客户端的事务队列,执行队列保存的所有命令。最后将执行命令所得结果返回给客户端。

两者最大区别就是 Redis 事务不支持回滚 。即使事务队列中某个命令在执行期间发生了错误,事务也会继续执行,直到事务队列中所有命令执行完成。

文字貌似不够直观,没事,看下面的例子你就马上明白了。

PS:如果客户端向事务入列一个错误的命令(比如输入一个不存在的命令,像:sett 命令),那么该事务将不被服务端执行。该情况是入队错误,上面例子是执行错误的情况。

提到 redis 事务,就不得不提 watch 命令了。

该命令是一个乐观锁,只能在客户端进入事务状态之前执行。

作用是 exec 命令执行之前,监视任何数量个键,并在 exec 命令执行时,检查被监视的键是否至少有一个已经被修改过。若是则拒绝执行事务,否则执行。

当 exec 执行完成后,这次事务也就结束了。

我们依旧来看一个简单的栗子:


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/bake/11388839.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-15
下一篇 2023-05-15

发表评论

登录后才能评论

评论列表(0条)

保存