Predis executes two commands using transactions, but one of them fails
P粉030479054
P粉030479054 2023-09-13 17:55:41
0
1
540

Description error

I'm using a redis list to make a limiter, and it works as expected most of the time, but recently I've discovered that there are some keys that don't have an expiration time. Ideally I would "rpush" the values into the list and set the expiry time in a transaction and I would also use "watch" before the transaction starts.

Reappearance

This bug does not reproduce in my local environment, even if I use jmeter to batch request related APIs, such as 500 requests in 1 second

Version:

Prediction: v2.1.2 PHP 7.4 Redis server 5.0.10

Code Example

$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; } } }

other

This is the expected operation in my local Redis server

Redis transactions are atomic. Atomic means either all commands are processed or no commands are processed. So in my case a key should have an expiration date.

P粉030479054
P粉030479054

reply all (1)
P粉113938880

Redis transactions are not such atomic transactions. They are atomic because no other process can access the key space while the transactions in the command are executing. If a command within a transaction fails, subsequent commands will be executed and will not be rolled back.

For example, let's execute a transaction that contains the wrong command:

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 the list exists and add some initial items to it. Then, within the transaction, we pop an item from the list, mistakenly try to add a new item, think that keymylistowns a collection, and then set keymylist. The first and third commands succeed, and finally,mylistsets the time to live. The second command fails. There is no rollback functionality built into Redis for this - your application needs to use optimistic locking via thewatchcommand... This is to detect changes by other processes before your transaction gets the changes your transaction wants to make The key has exclusive access to the server. It is not a rollback mechanism.

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

    Latest Downloads
    More>
    Web Effects
    Website Source Code
    Website Materials
    Front End Template
    About us Disclaimer Sitemap
    php.cn:Public welfare online PHP training,Help PHP learners grow quickly!