熟悉关系型数据库的同学对应该对事务比较了解,简单的说:事务表示一组动作,要么全部执行,要么全部不执行。还是用经典的银行转帐来做说明,用户a要给用户b转一笔款,正确的执行流程是a账户减少指定金额,b账户增加指定金额。这两个动作要么同时执行,要么都不执行才能保证数据的准确性,以保证数据一致。
Redis事务理解和示例
Redis提供了“简单”的事务,将一组需要一起执行的命令放到multi和exec两个命令之间,multi代表事务开始,exec代表事务结束,他们之间的命令是原子顺序执行的。我们一转帐来进行模拟这个过程:
#设置用户a和b的额度都是100127.0.0.1:6379> set user:a:quota 100OK127.0.0.1:6379> set user:b:quota 100OK#执行转帐事务:a减掉20 b增加20127.0.0.1:6379> MULTIOK127.0.0.1:6379> DECRBY user:a:quota 20QUEUED127.0.0.1:6379> INCRBY user:b:quota 20QUEUED127.0.0.1:6379> EXEC1) (integer) 802) (integer) 120#查看执行结果127.0.0.1:6379> MGET user:a:quota user:b:quota1) "80"2) "120"可以看到decrby和incrby命令返回结果都是QUEUED,代表命令并没有真正执行,而是暂时保存在redis中,当exec命令执行之后转帐行为才算完成。如果要停止执行该事务可以使用discard命令代替exec命令即可。
事务中的两种问题处理
如果事务中的命令出现错误,Redis的出现机制也不尽相同。通常情况下可以分为一下两种错误类型:
1、命令错误
例如上面的命令中DECRBY 写成了DECRRBY,这种错误属于语法错误,会造成整个事务无法执行,值也不会发生变化。
127.0.0.1:6379> set user:a:quota 100OK127.0.0.1:6379> set user:b:quota 100OK127.0.0.1:6379> MULTIOK#错误的语法127.0.0.1:6379> DECRRBY user:a:quota 20(error) ERR unknown command `DECRRBY`, with args beginning with: `user:a:quota`, `20`, 127.0.0.1:6379> INCRBY user:b:quota 20QUEUED127.0.0.1:6379> EXEC(error) EXECABORT Transaction discarded because of previous errors.#结果并没有发生改变127.0.0.1:6379> MGET user:a:quota user:b:quota1) "100"2) "100"2、运行时错误
在处理a账户时候,把decrby写成了incrby,语法没有错误会执行127.0.0.1:6379> set user:a:quota 100OK127.0.0.1:6379> set user:b:quota 100OK127.0.0.1:6379> MULTIOK#错误的将decrby 写成了incrby127.0.0.1:6379> incrby user:a:quota 20QUEUED127.0.0.1:6379> INCRBY user:b:quota 20QUEUED127.0.0.1:6379> EXEC1) (integer) 1202) (integer) 120127.0.0.1:6379> MGET user:a:quota user:b:quota1) "120"2) "120"Redis中“乐观锁”
另外redis还支持锁定功能,有些应用场景在事务之前,确保事务中的key没有被其他客户端修改过才执行事务,否则不执行(类似乐观锁)。Redis提供了Watch命令解决这类问题。
看下面一个简单例子
时间点 客户端A 客户端B
t1 set watchkey "only_me" t2 watch watchkey t3 multi t4 append watchkey "second"t5 append watchkey jedis t6 exec t7 get watchkey 可以看到”客户端A“在执行multi之前执行了watch命令,”客户端B“在”客户端A“执行exec之前修改了key值,使得事务没有执行(通过exec结果是nil可以判定)试验流程如下:
127.0.0.1:6379> set watchkey "only_me"OK127.0.0.1:6379> WATCH watchkeyOK127.0.0.1:6379> multiOK127.0.0.1:6379> APPEND watchkey "first"QUEUED127.0.0.1:6379> exec(nil)127.0.0.1:6379> get watchkey"only_mesecond"在客户端A执行之前,新启动一个客户端执行如下命令
127.0.0.1:6379> APPEND watchkey "second"(integer) 13通过上面的示例可以看到,watchkey的最后结果是only_mesecond,事务内的‘append watch "first"’并没有执行。而客户端B的命令执行了。
总结
Redis提供了简单的事务,之所以说他简单是因为特不支持事务的回滚特性,同时无法实现命令直接爱嗯的逻辑关系计算,这也许你体现”keep it simple"的特性。
更多原文请查看我的技术博客 http://www.evenvi.com/index.p...