Redis
事务实现
redis
单条命令是保证原子性的。但是事务不保证原子性。
Redis
事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行的过程中,会按照顺序执行!一次性、顺序性、排他性!执行一系列的命令。
Redis
事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行! EXEC
Redis
的事务:
- 开启事务(MULTI)
- 命令入队(…)
- 执行事务(EXEC)
1.开启事务
redis
通过执行MULTI
命令开启事务。MULTI
命令会将客户端状态的flags
属性中REDIS_MULTI
标识打开。
2.命令入队
当一个redis
客户端开启执行multi
命令开启事务后,服务器会根据这个客户端发送来的命令执行不同的操作。
-
如果客户端发送的命令为
MULTI
,EXEC
,WATCH
,DISCARD
中的其中一个,会立即执行这个命令。 -
如果发送的是这四个命令之外的其他命令,那么服务器并不会立即执行这个命令。会首先检查这个命令的格式是否正确,如果不正确,服务器会在客户端状态(
redisClient
)的flags
属性中关闭REDIS_MULTI
标识,并且返回错误信息给客户端如果正确就会将命令放入一个事务队列中,然后向客户端返回一个QUEUED
回复
3.事务执行
客户端发送EXEC
命令,服务器执行EXEC
命令逻辑。
-
如果客户端状态的
flags
属性不包含REDIS_MULTI
标识,或者包含REDIS_DIRTY_CAS
或者REDIS_DIRTY_EXEC
标识,那么就直接取消事务的执行。 -
否则客户端处于事务状态(
flags
有REDIS_MULTI
标识) ,服务器会遍历客户端的事务队列,然后执行事务队
列中的所有命令,最后将返回结果全部返回给客户端;
redis
不支持事务回滚机制,但是它会检查每一个事务中的命令是否错误。
Redis
事务不支持检查那些程序员自己逻辑错误。例如对String类型的数据库键执行对HashMap类型的操作!
正常执行事务
multi
-> 命令入队 ->exec
127.0.0.1:6379> multi #开启事务
OK
#命令入队
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) OK
3) OK
4) "v2"
放弃事务
discard
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard #放弃事务
OK
127.0.0.1:6379> get k4 #事务队列中命令都不会被执行
(nil)
编译型异常(代码有问题!命令有错!),事务中所有命令都不会被执行!
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k2 #错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec #执行的事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 #所有的命令都不会执行
(nil)
运行时异常(抛出的异常),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的!错误命令会抛出异常!
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 #执行的时候会失败
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
#虽然第一条命令报错了,但是其他命令依旧执行成功了。这里就说明了redis事务不保证原子性。
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
4.监控(watch
)
-
WATCH命令是一个乐观锁,可以为Redis事务提供check-and-set (CAS) 行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除) .之后的事务就不会执行,监控一直持续到EXEC命令。
-
MULTI命令用于开启一个事务, 它总是返回0K。MULTI执行之后, 客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令 被调用时,所有队列中的命令才会被执行。
-
EXEC: 执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值nil。
-
通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务,并且客户端会从事务状态中退出。
-
UNWATCH命令可以取消watch对所有key的监控。
Redis
监控测试
#正常执行成功!
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #监视money对象
OK
127.0.0.1:6379> multi #事务正常结束,期间数据没有发生变动,这个时候就正常执行成功
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用
watch
可以当做redis
的乐观锁操作
127.0.0.1:6379> watch money #监视 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
#此时另外一个线程执行修改money的命令
127.0.0.1:6379> exec #执行之前,另外一个线程修改了我们的值,这个时候就回导致事务执行失败!
(nil)
如果修改失败了,就先获取最新的值,再次监控。
127.0.0.1:6379> unwatch #1.如果发现事务执行失败,就先解锁
OK
127.0.0.1:6379> watch money #2.获取最新的值,再次监视 money。select version
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec #3.比对监视的值是否发生了变化,如果没有变化,那么可以执行成功,如果发生了变化就会执行失败。
1) (integer) 60
2) (integer) 40