【问题标题】:Interleaving Watch Multi/exec on a single Redis connection. Expected or weird behavior?在单个 Redis 连接上交错 Watch Multi/exec。预期或奇怪的行为?
【发布时间】:2014-10-28 11:38:50
【问题描述】:

考虑一个前端应用程序,其中每个请求共享相同的 Redis 连接,我认为这是推荐的方式 (?)。

在这种情况下,我相信我看到了一些奇怪的 watch multi/exec 行为。具体来说,我希望两个事务中的一个由于乐观锁定失败(即:watch 守卫)而失败,但两者似乎都没有发脾气,但最终值错误。

为了说明,请参见下面的人为场景。它在 Node 中,但我相信这是一个普遍的事情。这会并行运行 2 个进程,它们都会更新一个计数器。 (它基本上实现了 Redis Docs 中看到的 Watch 的规范示例。

预期结果是第一个进程导致增量1,而第二个进程更新失败并返回null。相反,结果是两个进程都将计数器更新为 1。但是,一个基于陈旧的计数器,因此最终计数器的增量为 1 而不是 2。

    //NOTE: db is a promisified version of node-redis, but that really doesn't matter
    var db = Source.app.repos.redis._raw;
    Promise.all(_.reduce([1, 2], function(arr, val) {
        db.watch("incr");
        var p = Promise.resolve()
            .then(function() {
                return db.get("incr");
            })
            .then(function(val) { //say 'val' returns '4' for both processes.
                console.log(val);
                val++;
                db.multi();
                db.set("incr", val);
                return db.exec();
            })
            .then(function(resultShouldBeNullAtLeastOnce) {
                console.log(resultShouldBeNullAtLeastOnce);
                return; //explict end
            });
        arr.push(p);
        return arr;
    }, [])).then(function() {
        console.log("done all");
        next(undefined);
    })

在跟踪 Redis 的 MONITOR 命令时会看到结果交错:

    1414491001.635833 [0 127.0.0.1:60979] "watch" "incr"
    1414491001.635936 [0 127.0.0.1:60979] "watch" "incr"
    1414491001.636225 [0 127.0.0.1:60979] "get" "incr"
    1414491001.636242 [0 127.0.0.1:60979] "get" "incr"
    1414491001.636533 [0 127.0.0.1:60979] "multi"
    1414491001.636723 [0 127.0.0.1:60979] "set" "incr" "5"
    1414491001.636737 [0 127.0.0.1:60979] "exec"
    1414491001.639660 [0 127.0.0.1:60979] "multi"
    1414491001.639691 [0 127.0.0.1:60979] "set" "incr" "5"
    1414491001.639704 [0 127.0.0.1:60979] "exec"

这是预期的行为吗?使用多个 redis 连接会绕过这个问题吗?

【问题讨论】:

  • 您得到的“错误最终值”是什么? 5 还是 10?
  • incr 的值是4,在两个进程都增加它之后,值是5。该值是预期的,但第二个事务应该失败,因为incr 的值已从4 更改为5,因此第二个事务上的watch-guard 应该失败。这不会发生
  • @itamarHaber,可能就是这样。使用多个 Redis 连接,正确地导致 watch-guard 失败。

标签: concurrency redis


【解决方案1】:

回答我自己的问题:

这是预期的行为。第一个exec 取消关注所有属性。因此,第二个multi/exec 在没有看守的情况下通过。

它在docs 中,但相当隐蔽。

解决方案:使用多个连接,尽管 SO 上的一些答案明确警告了这一点,因为它(引用)“不应该被需要”。在这种情况下,它是必要的。

【讨论】:

    【解决方案2】:

    为时已晚,但对于以后阅读本文的人来说,Redis 不建议 Geert 建议的解决方案。

    每个连接一个请求 许多数据库使用 REST 的概念作为主要接口——将普通的旧 HTTP 请求发送到带有编码为 POST 的参数的端点。数据库获取信息并将其作为带有状态代码的响应返回并关闭连接。 Redis 应该以不同的方式使用——连接应该是持久的,并且您应该根据需要向长期连接发出请求。但是,善意的开发人员有时会创建连接、运行命令并关闭连接。虽然每个命令打开和关闭连接在技术上是可行的,但它远非最佳,并且不必要地降低了整个 Redis 的性能。
    使用 OSS 集群 API,到节点的连接由客户端根据需要维护,因此您将在任何给定时间打开到不同节点的多个连接。使用 Redis Enterprise,连接实际上是到一个代理,它在集群级别处理连接的复杂性。 TL;DR:Redis 连接旨在在无数操作中保持开放。 最佳实践替代方案:通过多个命令保持连接打开。

    解决这个问题的一个更好的解决方案是使用 lua 脚本并使您的操作集阻塞和原子化。 EVAL to run redis scripts

    【讨论】:

    • 有时 EVAL 是不够的。例如,使用redis作为一个数据库的缓存:你需要WATCH一个key,然后从数据库中读取,然后在redis中SET数据作为一个MULTI事务。否则,竞速客户端可能会更新源数据,并首先更新 redis,这意味着如果没有事务,其中一个或另一个最终会将陈旧数据写入缓存。任何使用 EVAL 运行的 lua 代码都将立即执行,没有机会安全地从源数据库中读取,其他客户端都没有这样做。
    • 解决打开连接,运行命令,然后关闭连接的问题。我们所做的是允许我们的 redis 客户端随着时间的推移打开一个连接池,直至达到“首选”数量,并且任何时候一些代码需要运行 MULTI 事务,它都会从池中获取连接。如果没有可用的连接,它可以将临时连接突发到最大限制,或者它将等待下一个可用连接。然而,大多数时候,有一个现成的池连接可用。
    • 感谢您提出这个问题,同意 EVAL 不足以解决@JeremyTM 提到的情况
    猜你喜欢
    • 2013-03-24
    • 2022-01-15
    • 2021-06-29
    • 1970-01-01
    • 2011-07-31
    • 1970-01-01
    • 1970-01-01
    • 2018-12-11
    • 2021-05-22
    相关资源
    最近更新 更多