Find file
Fetching contributors…
Cannot retrieve contributors at this time
77 lines (54 sloc) 3.63 KB

事务

multi commands

Redis 使用 multi、exec、discard 等命令实现简单事务,可以同时提交或者同时回滚,但不能处理部分的事务,事务过程中进程crash会导致部分数据写入Redis,而部分数据失败。实例重启并不会回滚本不该写入的数据。

不是传统意义上的事务,只是一次性执行多个语句,所以你无法读数据,也应用无法交互,你只能一股脑的写或者修改数据, 这个在 scripting 有所改观。

另外使用watch命令观察某些key,如果exec之前,这些key出现修改则回滚整个事务,否则提交事务,来达到乐观锁的作用。Watch配合setnx可以设计出媲美关系型数据库的分布式锁机制。

我们先来看multi,这个命令发送到服务端后,会修改 RedisClient 对象,把 client->flag 设置为 REDIS_MULTI。

    void multiCommand(RedisClient *c) { 
        if (c->flags & REDIS_MULTI) { 
            addReplyError(c,"MULTI calls can not be nested"); 
            return; 
        }
        c->flags |= REDIS_MULTI; 
        addReply(c,shared.ok); 
    }

从此之后,原本要执行的命令不再调用 call 函数,反而执行了 queueMultiCommand 函数,把命令保存到一个管道里。

    int processCommand(RedisClient *c) { 
        ...
        if (c->flags & REDIS_MULTI && 
            cmd->proc != execCommand && cmd->proc != discardCommand && 
            cmd->proc != multiCommand && cmd->proc != watchCommand) 
        { 
            queueMultiCommand(c,cmd);
            addReply(c,shared.queued);
        } else {
            if (server.vm_enabled && server.vm_max_threads > 0 && 
                blockClientOnSwappedKeys(c,cmd)) return REDIS_ERR; 
            call(c,cmd); 
        }

queueMultiCommand 函数是将接下来的命令和参数塞入 RedisClient 对象的一个命令数组 mstate 里。命令都是数组的形式添加到command里,count表示命令的个数,扩展使用的是realloc整个数组。

multi commands

当需要回滚的时候我们键入 discard 命令,该命令会清空上面的 multistate 并把count设置为 0,然后修改 c->flags 去掉 REDIS_MULTI状态。

如果提交命令则键入EXEC命令,EXEC会把multistate的命令拿出来依次执行。等会我们结合watch一块来讲解EXEC。

watch

来看 watch 命令,这个命令非常有用。我们假象一个这样的场景。如果没有 INCR 命令我们要自增加一个 key,我们会如何做?

    a = Redis.get(key)
    Redis.set(key, a+1)

看起来是个不错的方法,假设初值是 10,我们修改后应该为 11 的。如果在get、set之间另外一个client也执行了同样的操作也把 key 加 1。这样 key 本应该等于12,结果等于了 11。如何解决这种问题?

watch就是为此而生的。从watch开始到exec之间,一旦watch的key发生了变化,则提交失败,否则提交成功,从返回的结果里可以看出提交是否成功。代码如下

    >>> r = Redis.Redis("127.0.0.1", 6379, password="aliyundba") 
    >>> r.watch("a") 
    True
    >>> z = r.pipeline("a") 
    >>> z.set("a", 4) 
    <Redis.client.Pipeline object at 0xb7491d74> 
    >>> z.execute() 
    [True] 

我们做依次中

    >>> r.watch("a") 
    True 
    >>> z = r.pipeline("a") 
    >>> z.set("a", 5) 
    <Redis.client.Pipeline object at 0xb74be8c4> 
    >>> z.execute() 
    Redis.exceptions.WatchError: Watched variable changed.