Predis使用事务执行两个命令,但其中一个失败。

huangapple go评论58阅读模式
英文:

Predis using transaction to execute two commands, but one of them failed

问题

描述错误

我使用Redis列表来进行限制,大多数情况下都能正常工作,但最近我发现有些键没有过期时间。理想情况下,我会在一个事务中同时使用"rpush"将值推送到列表中并设置过期时间,并在事务开始前使用"watch"。

复现步骤

我在本地环境中未能复现此错误,即使我使用JMeter批量请求相关的API,例如每秒500次请求。

版本信息

Predis版本:v2.1.2
PHP版本:7.4
Redis服务器版本:5.0.10

代码示例

$redisClient->watch($key);
$current = $redisClient->llen($key);

// 事务开始
$tx = $redisClient->transaction();
if ($current >= $limitNum) {
    $redisClient->unwatch();
    return false;
} else {
  
    if ($redisClient->exists($key)) {
        $tx->rpush($key, $now);

        try {
             $replies = $tx->execute();
             return true;
        } catch (\Exception $e) {
            return false;
        }
    } else {
        // 使用事务将rpush和expire作为原子操作
        $tx->rpush($key, $now);
        $tx->expire($key, $expiryTime);

        try {
             $replies = $tx->execute();
             return true;
        } catch (\Exception $e) {
            return false;
        }
    }
}

其他

这是我本地Redis服务器中预期的操作:

Predis使用事务执行两个命令,但其中一个失败。

Redis事务是原子的。原子意味着要么所有命令都被处理,要么都不被处理。因此,在我的情况下,一个键应该具有过期时间。

英文:

Describe the bug

I use redis list to do a limiter, it works as expected most times, but recently I found that there are some keys without an expiry.Ideally, I "rpush" the value to the list and set expiry as well in one transaction, and I use "watch" as well before the transaction start.

To Reproduce

I haven't reproduced the bug in my local environment, even I use jmeter to request the related api in batch, for example 1 seconds 500 requests

Versions:

Predis: v2.1.2
PHP 7.4
Redis Server 5.0.10

Code sample

$redisClient->watch($key);
$current = $redisClient->llen($key);

// Transaction start
$tx = $redisClient->transaction();
if ($current >= $limitNum) {
    $redisClient->unwatch();
    return false;
} else {
  
    if ($redisClient->exists($key)) {
        $tx->rpush($key, $now);

        try {
             $replies = $tx->execute();
             return true;
        } catch (\Exception $e) {
            return false;
        }
    } else {
        // Using transaction to let rpush and expire to be an atomic operation
        $tx->rpush($key, $now);
        $tx->expire($key, $expiryTime);

        try {
             $replies = $tx->execute();
             return true;
        } catch (\Exception $e) {
            return false;
        }
    }
}

Others

here is the expected actions in my local redis server

Predis使用事务执行两个命令,但其中一个失败。

Redis transaction is atomic. Atomic means either all of the commands or none are processed. So one key should have a expiry in my case.

答案1

得分: 1

Redis事务不像那样是原子性的。它们在某种程度上是原子性的,即在执行您的命令中的事务期间,没有其他进程可以访问键空间。如果事务中的某个命令失败,那么后续的命令将被执行,而没有回滚。

例如,让我们执行一个包含错误命令的事务:

127.0.0.1:6379> exists mylist
(integer) 0
127.0.0.1:6379> lpush mylist a b c
(integer) 3
127.0.0.1:6379> multi
OK
127.0.0.1:6379> rpop mylist
QUEUED
127.0.0.1:6379> sadd mylist d
QUEUED
127.0.0.1:6379> expire mylist 300
QUEUED
127.0.0.1:6379> exec
1) "a"
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) (integer) 1
127.0.0.1:6379> ttl mylist
(integer) 297

在这里,我们检查列表是否存在,并向其添加一些初始项。然后,在一个事务中,我们从列表中弹出一个项,错误地尝试添加一个新项,认为键mylist保存了一个集合,然后设置键mylist的生存时间。第一个和第三个命令成功,最后,mylist的生存时间被设置。第二个命令失败。Redis中没有内置的回滚机制 - 您的应用程序需要使用watch命令进行乐观锁定,以检测其他进程在您的事务获得服务器的独占访问之前更改了事务想要更改的键。这不是一个回滚机制。

详情请参阅:https://redis.io/docs/interact/transactions/

英文:

Redis transactions aren't atomic like that. They are atomic in the sense that no other process has access to the keyspace while the transactions in your command execute. If a command in a transaction fails then subsequent commands will be executed and there isn't a rollback.

Example, let's do a transaction that has a bad command in it:

127.0.0.1:6379> exists mylist
(integer) 0
127.0.0.1:6379> lpush mylist a b c
(integer) 3
127.0.0.1:6379> multi
OK
127.0.0.1:6379> rpop mylist
QUEUED
127.0.0.1:6379> sadd mylist d
QUEUED
127.0.0.1:6379> expire mylist 300
QUEUED
127.0.0.1:6379> exec
1) "a"
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
3) (integer) 1
127.0.0.1:6379> ttl mylist
(integer) 297

Here we check if a list exists, and add some initial items to it. Then, in a transaction, we pop an item from the list, erroneously try to add a new item thinking the key mylist holds a set, then set the time to live on the key mylist. The first and third commands succeed and at the end, mylist has a time to live set. The second command fails. There is no rollback built into Redis for this - your application would need to use optimistic locking with the watch command... this is to detect other processes changing keys your transaction wants to change, before your transaction gets exclusive access to the server. It isn't a rollback mechanism.

Details: https://redis.io/docs/interact/transactions/

huangapple
  • 本文由 发表于 2023年7月10日 15:55:30
  • 转载请务必保留本文链接:https://go.coder-hub.com/76651748.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定