Redis实战 - 5事务:multi、exec和watch

介绍

  1. redis的目标的是: 简洁,高效,因为事务自己就是一个很复杂的东西,全部咱们不能把事务作的太复杂。
  • DISCARD 取消事务,放弃执行事务块内的全部命令。
  • EXEC 执行全部事务块内的命令。
  • MULTI 标记一个事务块的开始。
  • UNWATCH 取消 WATCH 命令对全部 key 的监视。
  • WATCH key [key ...] 监视一个(或多个) key ,若是在事务执行以前这个(或这些) key 被其余命令所改动,那么事务将被打断。redis

    MULTI 和 EXEC

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

.net操练,在StackExchange.Redis又该怎么作?

更复杂的事实是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,由于存在
            }
        }

经过 When 的内置操做

还应该注意的是,Redis已经为咱们预料到了许多常见的场景(特别是:key/hash的存在,就像上面同样),还有单操做(single-operation)原子命令的存在。 经过 When 来访问,因此前面的示例也能够这样来实现:

var newId = CreateNewId();
bool wasSet = db.HashSet(custKey, "UniqueID", newId, When.NotExists);

注意:When.NotExists 会使用命令 HSETNX 而不会使用 HSET HSETNX:若是字段已经存在于哈希表中,操做无效。

相关文章
相关标签/搜索