redis事务
multi exec discard 和watch 是redis事务的基础
事务可以一次执行多个命令,并且带有以下两个重要的保证:
1 事务是一个单独的隔离操作:事务中所有的命令都会序列化,按顺序地执行。事务在执行的过程中,不会被其它来的命令请求打断
2.事务是一个原子操作:事务中的命令要么全部地执行,要么全部都不执行
EXEC 命令负责触发并执行事务中所有命令:
1.如果客户端在使用multi 开启一个事务之后,却因为断线而没有成功执行EXEC,那么事务中所有命令都不会执行
2.另一方面,如果客户端成功在开启事务之后执行EXEC,那么事务中所有命令都会执行
例如 购票过程,
ticket-1 ,mobey-100
而票只有一张,如果在multi之后,跟EXEC之前,被别人买了,所以ticket为0
那我该如何监视这种情景,并不提交
悲观的想法:觉得所有的人都在跟我抢这个ticket,此时给ticket上锁,只有我能操作【悲观锁】
乐观的想法:我只需要注意这个ticket的值就可以了【乐观锁】
redis的事务中就是开启了乐观锁,只负责监听ticket有没有被改动
用法
multi 命令用户开启一个事务,它总是返回ok
multi 执行之后,客户端可以继续向服务端发送任意多条命令,这些命令不会立即执行,而是被放到一个队列中,当需要调用时,所有队列中的命令才会被执行
另一方面,通过调用discard,客户端会清除事务队列,并放弃执行事务
以下是一个事务例子, 它原子地增加了 foo
和 bar
两个键的值:
EXEC 命令的回复是一个数组, 数组中的每个元素都是执行事务中的命令所产生的回复。 其中, 回复元素的先后顺序和命令发送的先后顺序一致。
当客户端处于事务状态时, 所有传入的命令都会返回一个内容为 QUEUED
的状态回复(status reply), 这些被入队的命令将在 EXEC命令被调用时执行。
事务中的错误
使用事务可能会用到下面2种错误
1.事务在执行exec之前,入列的命令可能会有错,如果说,命令语法错误
2.命令在调用exec调用之后失败。比如事务命令可能处理了错误类型的键,把列表命令的键放到string中
对于发生在exec执行之前的错误,客户端的做法是检查命令入列得到的值,如果命令是queue,则入列成功,否则,入列失败,如果入列失败,则大部分事务都会停止并取消这个事务
对于发生在exec执行之后的错误,并没有对他们进行特别处理:即使事务中某个/某些事务发生错误,而其它的命令仍然会继续执行
为什么Redis不支持回滚
如果你有使用关系式数据的经验,那么“redis在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪
以下是这种做法的优点:
1.Redis命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中
2.因为不需要对回滚进行支持,所以Redis的内部保存简单且快速
放弃事务
当执行discard命令时,事务会被放弃,并且客户端会从事务状态中退出
使用check-and-set操作实现乐观锁
watch命令可以为redis事务提供check-and-set(cas)行为
被watch的键会被监视,并会发觉这些键是否被改动。如果至少一个被监视的键在exec执行之前被修改了,那么整个事务都会被取消,exec返回空多条批量回复来表示事务已经失效
举个例子,假设客户端有A和B 2个都读取了键原来的值,比如10,执行incr操作的时候,那么2个客户端都会将键的值设为11,但正确返回结果应该是12才对
有了watch,我们就可以轻松地解决这类问题了:
watch mykey
val=get mykey
val=val+1
multi
set mykey $val
exec
使用上面的代码,如果有了watch之后,exec执行之前,有其他客户端修改了mykey的值,那么当前客户端的事务就会失效。程序需要做的,就是不断重试这个操作,知道没有发生碰撞为止
这种形式的锁被称为乐观锁,它是非常强大的锁机制。并且因为大多数情况下,不同的客户端会访问不同的值,碰撞的情况一般都很少,所以通常并不需要重试