严格意义来说,redis的事务和咱们理解的传统数据库(如mysql)的事务是不同的。java
Redis中的事务(transaction)是一组命令的集合。mysql
事务同命令同样都是Redis的最小执行单位,一个事务中的命令要么都执行,要么都不执行。
事务的原理是先将属于一个事务的命令发送给Redis,而后再让Redis依次执行这些命令。 redis
Redis保证一个事务中的全部命令要么都执行,要么都不执行。若是在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的全部命令都不会执行。而一旦客户端发送了EXEC命令,全部的命令就都会被执行,即便此后客户端断线也不要紧,由于Redis中已经记录了全部要执行的命令。 sql
除此以外,Redis的事务还能保证一个事务内的命令依次执行而不被其余命令插入。试想客户端A须要执行几条命令,同时客户端B发送了一条命令,若是不使用事务,则客户端B的命令可能会插入到客户端A的几条命令中执行。若是不但愿发生这种状况,也可使用事务。 数据库
事务的应用很是广泛,如银行转帐过程当中A给B汇款,首先系统从A的帐户中将钱划走,而后向B的帐户增长相应的金额。这两个步骤必须属于同一个事务,要么全执行,要么全不执行。不然只执行第一步,钱就凭空消失了,这显然让人没法接受。 json
和传统的mysql事务不一样的事,即便咱们的加钱操做失败,咱们也没法在这一组命令中让整个状态回滚到操做以前 多线程
若是一个事务中的某个命令执行出错,Redis会怎样处理呢?要回答这个问题,首先须要知道什么缘由会致使命令执行出错。 分布式
语法错误指命令不存在或者命令参数的个数不对。好比: 函数
redis>MULTI
OK
redis>SET key value QUEUED redis>SET key (error)ERR wrong number of arguments for 'set' command redis> errorCOMMAND key (error) ERR unknown command 'errorCOMMAND' redis> EXEC (error) EXECABORT Transaction discarded because of previous errors.
跟在MULTI命令后执行了3个命令:一个是正确的命令,成功地加入事务队列;其他两个命令都有语法错误。而只要有一个命令有语法错误,执行EXEC命令后Redis就会直接返回错误,连语法正确的命令也不会执行。ui
这里须要注意一点:
Redis 2.6.5以前的版本会忽略有语法错误的命令,而后执行事务中其余语法正确的命令。就此例而言,SET key value会被执行,EXEC命令会返回一个结果:1) OK。
运行错误指在命令执行时出现的错误,好比使用散列类型的命令操做集合类型的键,这种错误在实际执行以前Redis是没法发现的,因此在事务里这样的命令是会被Redis接受并执行的。若是事务里的一条命令出现了运行错误,事务里其余的命令依然会继续执行(包括出错命令以后的命令),示例以下:
redis>MULTI
OK
redis>SET key 1 QUEUED redis>SADD key 2 QUEUED redis>SET key 3 QUEUED redis>EXEC 1) OK 2) (error) ERR Operation against a key holding the wrong kind of value 3) OK redis>GET key "3"
可见虽然SADD key 2出现了错误,可是SET key 3依然执行了。
Redis的事务没有关系数据库事务提供的回滚(rollback)功能。为此开发者必须在事务执行出错后本身收拾剩下的摊子(将数据库复原回事务执行前的状态等,这里咱们通常采起日志记录而后业务补偿的方式来处理,可是通常状况下,在redis作的操做不该该有这种强一致性要求的需求,咱们认为这种需求为不合理的设计)。
你们可能知道redis提供了基于incr命令来操做一个整数型数值的原子递增,那么咱们假设若是redis没有这个incr命令,咱们该怎么实现这个incr的操做呢?
那么咱们下面的正主watch
就要上场了。
正常状况下咱们想要对一个整形数值作修改是这么作的(伪代码实现):
val = GET mykey val = val + 1 SET mykey $val
可是上述的代码会出现一个问题,由于上面吧正常的一个incr(原子递增操做)分为了两部分,那么在多线程(分布式)环境中,这个操做就有可能再也不具备原子性了。
研究过java的juc包的人应该都知道cas,那么redis也提供了这样的一个机制,就是利用watch
命令来实现的。
WATCH命令能够监控一个或多个键,一旦其中有一个键被修改(或删除),以后的事务就不会执行。监控一直持续到EXEC命令(事务中的命令是在EXEC以后才执行的,因此在MULTI命令后能够修改WATCH监控的键值)
具体作法以下:
WATCH mykey
val = GET mykey val = val + 1 MULTI SET mykey $val EXEC
和此前代码不一样的是,新代码在获取mykey的值以前先经过WATCH命令监控了该键,此后又将set命令包围在事务中,这样就能够有效的保证每一个链接在执行EXEC以前,若是当前链接获取的mykey的值被其它链接的客户端修改,那么当前链接的EXEC命令将执行失败。这样调用者在判断返回值后就能够获悉val是否被从新设置成功。
因为WATCH命令的做用只是当被监控的键值被修改后阻止以后一个事务的执行,而不能保证其余客户端不修改这一键值,因此在通常的状况下咱们须要在EXEC执行失败后从新执行整个函数。
执行EXEC命令后会取消对全部键的监控,若是不想执行事务中的命令也可使用UNWATCH命令来取消监控。
咱们实现的hsetNX这个功能是:仅当字段存在时才赋值。
为了不竞态条件咱们使用watch
和事务来完成这一功能(伪代码):
WATCH key isFieldExists = HEXISTS key, field if isFieldExists is 1 MULTI HSET key, field, value EXEC else UNWATCH return isFieldExists
在代码中会判断要赋值的字段是否存在,若是字段不存在的话就不执行事务中的命令,但须要使用UNWATCH命令来保证下一个事务的执行不会受到影响。