DISCARD
取消事务,放弃执行事务块内的全部命令。EXEC
执行全部事务块内的命令。MULTI
标记一个事务块的开始。UNWATCH
取消 WATCH 命令对全部 key 的监视。WATCH
key [key ...] 监视一个(或多个) key ,若是在事务执行以前这个(或这些) key 被其余命令所改动,那么事务将被打断。redis
127.0.0.1:6379> multi OK 127.0.0.1:6379> lpush fruits orange QUEUED 127.0.0.1:6379> lpush fruits nut QUEUED 127.0.0.1:6379> lpush fruits apple QUEUED 127.0.0.1:6379> exec 1) (integer) 1 2) (integer) 2 3) (integer) 3
咱们发现命令是一块儿执行的,若是说个人某一条命令执行失败,会回滚吗?
答案是不会回滚
。看看下面的原子性。shell
事务的原子性是指要么事务所有成功,要么所有失败,那么 Redis 事务执行是原子性的么? 下面咱们来看一个特别的例子。并发
> multi OK > set books iamastring # 执行成功 QUEUED > incr books # 执行失败 QUEUED > set poorman iamdesperate # 执行成功 QUEUED > exec 1) OK 2) (error) ERR value is not an integer or out of range 3) OK > get books "iamastring" > get poorman "iamdesperate
上面的例子是事务执行到中间遇到失败了,由于咱们不能对一个字符串进行数学运算,事务在遇到指令执行失败后,后面的指令还继续执行,因此 poorman 的值能继续获得设置。
到这里,你应该明白 Redis 的事务根本不能算「原子性」
,而仅仅是知足了事务的「隔离性」,隔离性中的串行化——当前执行的事务有着不被其它事务打断的权利。app
# Watch
我在执行lpush的时候,lpush被其余人改变了。异步
需求:在写multi的时候,不能够有其余的命令更改 “队列”中的集合。分布式
出现并发问题,由于有多个客户端会并发进行操做。咱们能够经过 Redis 的分布式锁来避免冲突,这是一个很好的解决方案。分布式锁是一种悲观锁,那是否是可使用乐观锁的方式来解决冲突呢?
Redis 提供了这种 watch 的机制,它就是一种乐观锁。有了 watch 咱们又多了一种能够用来解决并发修改的方法。 watch 的使用方式以下:测试
127.0.0.1:6379> watch msg OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set msg "hello wolrd" QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> get msg "12345" 127.0.0.1:6379>
注意事项
Redis 禁止在 multi 和 exec 之间执行 watch 指令,而必须在 multi 以前作好盯住关键变量,不然会出错。ui
更复杂的事实是StackExchange.Redis使用的是多路复用器的方式。.net
咱们不能只让并发调用方发布 WATCH / UNWATCH / MULTI / EXEC / DISCARD:这应该是混合在一块儿的。因此一个额外的抽象被给出:另外会让使事情更简单准确:约束。约束是预约义测试包括 WATCH 某种类型的测试并对结果进行检查。若是全部的约束都经过了,那么要么是以 MULTI / EXEC 发布(从事务开始,到执行整个事务块);要么是以 UNWATCH 发布(取消 WATCH 命令对全部 key 的监视)。阻止命令于其它调用方被混合在一块儿;因此例子能够是:code
注意:从 CreateTransaction 返回的对象最后都是调用异步方法来执行命令(Execute方法最终也是调用ExecuteAsync,具体能够看源码):因为不知道每一个操做的结果,除非在 Execute 或 ExecuteAsync 操做完成后。若是操做没有被执行,全部的Task 将被标记为取消,不然在命令执行后你能够获取每一个正常的结果。
static void Transaction() { using (ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("localhost:6379")) { IDatabase db = redis.GetDatabase(); ITransaction tran = db.CreateTransaction(); //为该事务添加先决条件 //强制指定指定的哈希字段不能存在。 tran.AddCondition(Condition.HashNotExists("transactionDemo", "UniqueID")); tran.HashSetAsync("transactionDemo", "UniqueID","Unnn"); bool committed = tran.Execute(); Console.WriteLine(committed); //第1次执行,true,由于不存在,后面执行false,由于存在 } }
还应该注意的是,Redis已经为咱们预料到了许多常见的场景(特别是:key/hash的存在,就像上面同样),还有单操做(single-operation)原子命令的存在。 经过 When 来访问,因此前面的示例也能够这样来实现:
var newId = CreateNewId(); bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);
注意:When.NotExists 会使用命令 HSETNX 而不会使用 HSET HSETNX:若是字段已经存在于哈希表中,操做无效。