zoukankan      html  css  js  c++  java
  • Redis中的事务

    Redis的事务是与SQL数据库不同的。详细了解请参考文档,转述如下:

    Redis的事务:先以 MULTI 开始一个事务,然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务。当碰到命令:MULTI (标记一个事务块的开始),在该连接上的命令不会执行:它们会排队(调用方会得到每个队列的回复)。当遇到命令:EXEC(执行所有事务块内的命令),它们被应用到一个单独的单元中(比如:没有其它连接操作之间的那个时间段)。如果是命令 DISCARD(取消事务,放弃执行事务块内的所有命令) 而不是 EXEC,那么所有的操作都会不执行(回滚)。因为命令是在事务里面排队的,所以你不能改变内部事务。

    注意:Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

    • 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
    • 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

    一个事务从开始到执行会经历以下三个阶段:

    • 开始事务。
    • 命令入队。
    • 执行事务。
    Redis 事务命令
    • DISCARD 取消事务,放弃执行事务块内的所有命令。
    • EXEC 执行所有事务块内的命令。
    • MULTI 标记一个事务块的开始。
    • UNWATCH 取消 WATCH 命令对所有 key 的监视。
    • WATCH key [key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

    例如:在SQL数据库中你可能回做如下操作:

    // 仅在它没有唯一ID的时候,分配一个唯一的ID。确保事务中没有线程竞争
    var newId = CreateNewUniqueID(); // optimistic
    using(var tran = conn.BeginTran())
    {
        var cust = GetCustomer(conn, custId, tran);
        var uniqueId = cust.UniqueID;
        if(uniqueId == null)
        {
            cust.UniqueId = newId;
            SaveCustomer(conn, cust, tran);
        }
        tran.Complete();
    }

    在Redis中是怎么做的?

    在Redis事务中这简直是不可能的是:一旦事务被开启,你不能去获取数据 -- 你的操作是排队执行的。幸运的是,有另外两个命令可以帮助我们:WATCH和UNWATCH。

    WATCH{key} 命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断且回滚。EXEC 所做的和 DISCARD 一样(调用方一开始就能发现并重试)。那么你能做的是:使用命令:WATCH 某个键,以正常的方式,来检查给定键的数据,然后使用 MULTI/EXEC 命令执行你的改变。当你检查数据的时候,你会发现你实际上不需要事务,你可以用 UNWATCH 命令用于取消 WATCH 命令对所有 key 的监视。注意:在使用 EXEC 和 DISCARD 的时候,观察键也是可以重置的(如果执行EXEC 或者DISCARD,则不需要手动执行UNWATCH)。所以在Redis层,这只是概念上的:

    WATCH {custKey}
    HEXISTS {custKey} "UniqueId"
    (check the reply, then either:)
    MULTI
    HSET {custKey} "UniqueId" {newId}
    EXEC
    (or, if we find there was already an unique-id:)
    UNWATCH

    这可能看起来很奇怪:只有跨越单个操作时才可以使用 MULTI/EXEC 命令,但重要的是我们现在也可以使用 {custKey} 从所有其它的连接中来跟踪变更:如果其他人更改这个Key,那么事务会被终止。

    在StackExchange.Redis又该怎么做?

    更复杂的事实是StackExchange.Redis使用的是多路复用器的方式。

    我们不能只让并发调用方发布 WATCH / UNWATCH / MULTI / EXEC / DISCARD:这应该是混合在一起的。所以一个额外的抽象被给出:另外会让使事情更简单准确:约束。约束是预定义测试包括 WATCH 某种类型的测试并对结果进行检查。如果所有的约束都通过了,那么要么是以 MULTI / EXEC 发布(从事务开始,到执行整个事务块);要么是以 UNWATCH 发布(取消 WATCH 命令对所有 key 的监视)。阻止命令于其它调用方被混合在一起;所以例子可以是:

    var newId = CreateNewId();
    var tran = db.CreateTransaction();
    tran.AddCondition(Condition.HashNotExists(custKey, "UniqueID"));
    tran.HashSetAsync(custKey, "UniqueID", newId);
    bool committed = tran.Execute();
    // ^^^ 如果真: 该命令会被执行; 如果假: 那么会回滚。

    注意:从 CreateTransaction 返回的对象最后都是调用异步方法来执行命令(Execute方法最终也是调用ExecuteAsync,具体可以看源码):由于不知道每个操作的结果,除非在 Execute 或 ExecuteAsync 操作完成后。如果操作没有被执行,所有的Task 将被标记为取消,否则在命令执行后你可以获取每个正常的结果。

    通过 When 的内置操作

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

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

    注意:When.NotExists 会使用命令 HSETNX 而不会使用 HSET

    Lua

    你应该记住Redis 2.6及以上的版本支持Lua脚本,它可以描述为:一个常用的工具,使多个操作在服务器端的以一个原子单元执行。在使用Lua脚本的时候,由于不需要服务于其它的连接,所以它的行为更像是一个事务处理,但是没有 MULTI / EXEC那么复杂。这也避免了诸如调用方和服务器端之间带宽和延迟的问题。但是代价是在脚本执行的时候独占了服务器。

    在Redis层(假设 HSETNX 不存在)我们可以有如下实现:

    EVAL "if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end" 1 {custKey} {newId}

    在 StackExchange.Redis 是这样使用的:

    var wasSet = (bool) db.ScriptEvaluate(@"if redis.call('hexists', KEYS[1], 'UniqueId') then return redis.call('hset', KEYS[1], 'UniqueId', ARGV[1]) else return 0 end",
            new RedisKey[] { custKey }, new RedisValue[] { newId });

    注意:来自 ScriptEvaluate 和 ScriptEvaluateAsync 的响应是可变的,这依赖于你所写的脚本。响应结果可以被强制转换,在这个例子中是被转换为 bool 类型。

  • 相关阅读:
    转:Redis 3.2.1集群搭建
    转:GET和POST两种基本请求方法的区别
    web.xml中 /和/*的区别
    java main方法里调用mapper
    Java定时任务
    @Resource与@Autowired注解的区别
    解决Eclipse EE部署web项目在Tomcat webapp目录下没有工程文件的问题
    get方式中文参数乱码解决方法
    生成excel并发送给客户端
    java把汉字转换成拼音
  • 原文地址:https://www.cnblogs.com/carldai/p/5497038.html
Copyright © 2011-2022 走看看