Search code examples
phptransactionsredispredis

why I cannot get cached data in transaction of Redis?


I am writing a lottery program in PHP, because there would be large concurrent request for this program, I have limited number of each prize, 10 in this example. I don't want to see any prize exceed the stock. so I put the entire logic in transaction of Redis(I use predis(https://github.com/nrk/predis) as my PHP redis client), but it doesn't work, after more than 10 times requests to this program, I found more than 10 records in database that I could not understand. does anyone who knows the reason? very appreciate to your explanation, thank you!

here is my PHP code:

$this->load->model('Lottery_model');
$money = $this->_get_lottery();//which prize do you get 
if($money > 0){
    $key = $this->_get_sum_key($money);
    $dbmodel = $this->Lottery_model;
    // Executes a transaction inside the given callable block:
    $responses = $redis->transaction(function ($tx) use ($key, $money, $dbmodel){
        $qty = intval($tx->get($key));
        if($qty < 10){
            //not exceed the stock limit
            $dbmodel->add($customer, $money);  // insert record into db
            $tx->incr($key);
        }else{
            log_message('debug', $money . ' dollar exceed the limit');
        }
    });
    }else{
        log_message('debug', 'you are fail');
    }

after reading documentation about transaction of Redis, I know the usage of above code is totally wrong. then I modified it to below version, using optimistic lock and check-and-set.

$options = array(
    'cas' => true,      // Initialize with support for CAS operations
    'watch' => $key,    // Key that needs to be WATCHed to detect changes
    'retry' => 3,    
);
try{
    $responses = $redis->transaction($options, function ($tx) use ($key, $money, $username, $dbmodel, &$qty){
    $qty = intval($tx->get($key));
    if($qty < 10){
        $tx->multi();
        $tx->incr($key);
        $dbmodel->add($username, $money);// insert into mysql db
    }else{
        log_message('debug', $money . ' dollar exceed the limit');
    }
});
}catch(PredisException $e){
    log_message('debug', 'redis transaction failed');
}

But the problem is that the number of record in database exceeds the limitation of the prize, the total number saved in Redis won't be. what is the common solution to solve this kind of problem? do I have to lock INNodb table in this case?


Solution

  • You need to understand how Redis transactions work - in a nutshell, all commands making a transaction are buffered by the client (predis in your case) and then fired to the server all at once. Your code attempts to use the result of a read request (get) before the transaction had been executed. Please refer to the documentation for more details: https://redis.io/topics/transactions

    Either read the qty outside the transaction, and use WATCH to protect against competing updates, or move this logic in its entirety to a Lua script.