Welcome back! In previous lessons, you've learned how to build and execute transactions in Redis using PHP with the Predis library. This lesson will introduce you to the watch functionality in Redis, enabling conditional and controlled transactions. Such functionality is vital for scenarios where you need to monitor specific keys and ensure operations only execute when certain conditions are met. The lesson will focus on understanding how to monitor keys to control transaction execution.
Below is a code example of how to use the watch
command with Predis:
php1<?php 2 3require 'vendor/autoload.php'; 4 5use Predis\Client; 6 7// creating a client 8$client = new Client(); 9 10// creating another client simulating concurrent access to the same application 11// from different callers 12$otherClient = new Client(); 13 14// keys to be used for our values 15$key = "valueToMonitor"; 16$anotherKey = "valueNotMonitored"; 17 18// setting initial values 19$client->set($key, 0); 20$client->set($anotherKey, 0); 21 22// telling redis to watch a specific key 23$client->watch($key); 24 25// starting a transaction 26$client->multi(); 27 28// incrementing both key values by 50 29$client->incrby($key, 50); 30$client->incrby($anotherKey, 50); 31 32// a different client modifies the value under the watched key 33$otherClient->incrby($key, 23); 34 35// incrementing both key values by 50 again 36$client->incrby($key, 50); 37$client->incrby($anotherKey, 50); 38 39// calling the exec command to commit to the transaction 40$client->exec(); 41 42echo "Current value of key [", $key, "]: ", $client->get($key), "\n"; 43echo "Current value of key [", $anotherKey, "]: ", $client->get($anotherKey), "\n"; 44 45// Output: 46// Current value of key [valueToMonitor]: 23 47// Current value of key [valueNotMonitored]: 0
The example code demonstrates how to use the watch
command. Here's a detailed explanation:
-
Setup: The code begins by loading the Predis library with
require 'vendor/autoload.php';
. Two Redis clients are then created usingPredis\Client
. These simulate concurrent access to Redis. -
Key Initialization: Two keys,
$key
and$anotherKey
, are initialized with the value0
. These keys simulate data fields within Redis that are subject to transactions. -
Watch Command: The primary client initiates a
watch
on the key$key
using$client->watch($key);
. This instructs Redis to monitor this specific key for changes. -
Start Transaction: A transaction block is started by invoking
$client->multi();
. This allows all subsequent commands to be queued and executed as a single transaction. -
Increment Operations: Both keys are incremented by
50
using$client->incrby($key, 50);
and$client->incrby($anotherKey, 50);
. These commands are queued for transaction execution. -
Concurrent Modification: A simulated concurrent client (
$otherClient
) modifies the watched key,$key
, by23
using$otherClient->incrby($key, 23);
. -
Additional Increment: The primary client again queues increment commands for both keys with an additional
50
. -
Execute and Check: When
$client->exec();
is called, the transaction attempts to execute but fails to apply changes to$key
because it was altered by another client during the transaction. Note that even though the value under$anotherKey
wasn't subject to thewatch
command, it still didn't get updated by the calls done within a transaction. -
Output: The final output shows that
$key
has a value of23
(from the concurrent modification), whereas$anotherKey
remains at0
, reflecting the rollback of changes due to the watched condition.
This code illustrates the power of the watch
command to maintain data integrity by acknowledging concurrent data modifications, effectively preventing conflicts in a multi-client environment.
In addition to the standard approach shown earlier, the Predis
library also supports using the closure syntax for managing transactions with the watch
command. Here's how you can achieve the same functionality using a closure:
php1<?php 2 3require 'vendor/autoload.php'; 4 5use Predis\Client; 6 7// creating a client 8$client = new Client(); 9 10// creating another client simulating concurrent access to the same application 11// from different callers 12$otherClient = new Client(); 13 14// keys to be used for our values 15$key = "valueToMonitor"; 16$anotherKey = "valueNotMonitored"; 17 18// setting initial values 19$client->set($key, 0); 20$client->set($anotherKey, 0); 21 22try { 23 // using a closure to wrap the transaction logic 24 $client->transaction(function ($tx) use ($otherClient, $key, $anotherKey) { 25 $tx->watch($key); 26 // incrementing both key values by 50 27 $tx->incrby($key, 50); 28 $tx->incrby($anotherKey, 50); 29 30 // a different client modifies the value under the watched key 31 $otherClient->incrby($key, 23); 32 33 // incrementing both key values by 50 again 34 $tx->incrby($key, 50); 35 $tx->incrby($anotherKey, 50); 36 }); 37} catch (Exception $ex) { 38 echo "An error occurred while executing a transaction: ", $ex->getMessage(), "\n"; 39} 40// checking the result of transactions 41echo "Current value of key [", $key, "]: ", $client->get($key), "\n"; 42echo "Current value of key [", $anotherKey, "]: ", $client->get($anotherKey), "\n"; 43 44// Output: 45// An error occurred while executing a transaction: The current transaction has been aborted by the server. 46// Current value of key [valueToMonitor]: 23 47// Current value of key [valueNotMonitored]: 0
Notice above that we have enclosed the transaction
call within a try/catch
block. This is because the closure syntax actually throws an error if it doesn't manage to complete the transaction.
Watch
is a Redis command that allows you to monitor one or multiple keys for changes before executing a transaction. It's a mechanism to achieve optimistic locking by applying the test-and-set concept in computer science. The idea is to monitor the state of a key and execute changes only if the state remains the same throughout the operation. If another client modifies any of the watched keys, the transaction is aborted, preventing potential conflicts.
By using watch
with transactions, Redis provides a way to manage race conditions and ensure data integrity when multiple clients are involved in data updates. This enables the implementation of more precise and conditional logic in your transactions, accommodating real-world demands of concurrent programming.
Mastering the watch
functionality in Redis is crucial for several reasons:
- Optimized Data Integrity: Using
watch
ensures actions only occur under certain conditions, enhancing update safety. - Conditional Logic: This allows your transactions to proceed only if specific keys maintain expected values, adding sophistication and precision to operations.
- Effective Error Handling: Implementing
watch
prevents conflicts and manages errors when multiple clients simultaneously update data.
Utilizing watch
effectively in allows you to write more robust applications, guarding against race conditions and ensuring concurrent updates do not interfere with one another.
Ready to dive in and practice? Let's move to the application portion for hands-on experience and to solidify your understanding.