phpredis Retry
Overview
phpredis is a common SDK for accessing Redis using a PHP script. Phpredis is only capable of basic connection and interaction. In scenarios where packets may be lost in a complex cloud network, or Redis master/standby switchover can be triggered when hardware is faulty, automatic reconnection and retry are unavailable. This document provides a practice for reliability reconstruction of a PHP script.
Example default phpredis connection
Function connect of phpredis is used to establish a persistent connection to Redis. A key value is increased every 2s in a loop. Neither this example nor function connect uses a reconnection or retry mechanism. When master/standby switchover occurs, the program enters the abnormal state and exits.
#!/usr/bin/env php
<?php
try {
$redis = new Redis();
// Redis: 127.0.0.1:6379, timeout 2s, interval 100 ms
if (!$redis->connect("127.0.0.1", 6379, 2, null, 100)) {
throw new Exception("Redis server cannot be connected.");
}
// Delete the existing keys (counting from 0).
$redis->del("redisExt");
echo "Start the increase counter. [press Ctrl + C to exit]\n";
while (true) {
// Execute the increase and obtain the new value.
$newValue = $redis->incr("redisExt");
// Print the current value and time.
printf("[%s] redisExt = %d\n",
date('Y-m-d H:i:s'),
$newValue);
// Wait for 2s.
usleep(2000000);
}
} catch (Exception $e) {
// Error handling
echo "Error occurred:\n";
var_dump($e);
exit(1);
}
Solution
By default, phpredis does not automatically reconnect and retry. But they can be configured.
Common phpredis reconnection implementation:
- Use pconnect for persistent connection. However, this cannot avoid disconnection (such as Redis server restart and network fluctuation).
- Capture connection exceptions and attempt to reconnect and retry.
phpredis provides the following parameters to implement reconnection and retry. For details, see Table 1.
|
Parameter |
Description |
|---|---|
|
Built-in parameter of function connect |
Enables reconnection. Function connect is as follows: $redis->connect(host, port, timeout, reserved, retry_interval, read_timeout, others); Its parameters are described as follows:
|
|
OPT_MAX_RETRIES |
When a connection error (such as timeout or disconnection) occurs when a command is executed, phpredis immediately retries the command until maximum attempts are reached. Note that this retry is performed automatically, and is fundamentally implemented by a phpredis extension. The trigger conditions are specific errors (usually connection errors, such as a timeout). When the maximum attempts are reached, an exception is thrown. |
Proper algorithms and retry intervals are required to prevent retry storms caused by specific batch disconnections, and support quick retry in some disconnections. phpredis implements a backoff algorithm through parameter OPT_BACKOFF_ALGORITHM. Set it to Redis::BACKOFF_ALGORITHM_DECORRELATED_JITTER in most scenarios to randomize the backoff delay.
The optimized example is as follows. When Redis exceptions such as network faults and master/standby switchovers occur, the program can still retry operations, and output clear error information and exit safely during a fault.
#!/usr/bin/env php
<?php
try {
$redis = new Redis();
// Configure the Redis connection.
$redis->setOption(Redis::OPT_MAX_RETRIES, 3); // Set the maximum retries.
$redis->setOption(Redis::OPT_BACKOFF_ALGORITHM, Redis::BACKOFF_ALGORITHM_DECORRELATED_JITTER); // Set the backoff algorithm.
$redis->setOption(Redis::OPT_BACKOFF_BASE, 100); // Basic delay 100 ms.
$redis->setOption(Redis::OPT_BACKOFF_CAP, 2000); // Maximum delay 2,000 ms.
// Redis: 127.0.0.1:6379, timeout 2s, automatic retry interval 100 ms
if (!$redis->connect("127.0.0.1", 6379, 2, '', 100)) {
throw new Exception("Redis server cannot be connected.");
}
// Delete the existing keys (counting from 0).
$redis->del("redisExt");
echo "Start the increase counter. [press Ctrl + C to exit]\n";
$lastSuccess = time();
$consecutiveFails = 0;
while (true) {
try {
// Execute the increase and obtain the new value.
$newValue = $redis->incr("redisExt");
// Reset the failure count.
$consecutiveFails = 0;
$lastSuccess = time();
// Print the current value and time.
printf("[%s] redisExt = %d\n",
date('Y-m-d H:i:s'),
$newValue);
// Normally wait for 2s.
usleep(2000000);
} catch (RedisException $e) {
// Process consecutively failed.
$consecutiveFails++;
// Print error information.
printf("[%s] error: %s (consecutive failures %d )\n",
date('Y-m-d H:i:s'),
$e->getMessage(),
$consecutiveFails);
// Backoff wait: 100 ms, 200 ms, 400 ms, 800 ms ...
$waitTime = min(100 * pow(2, $consecutiveFails), 5000); // Max. 5s
usleep($waitTime * 1000);
// Consecutive failures exceed the threshold. Attempt to completely reconnect.
if ($consecutiveFails >= 5) {
echo "Completely reconnecting...\n";
try {
$redis->close();
$redis->connect("127.0.0.1", 6379, 2);
$consecutiveFails = 0; // Reset the failure count.
echo "Reconnected\n";
} catch (Exception $reconnectEx) {
echo "Reconnect failed: " . $reconnectEx->getMessage() . "\n";
}
}
// Forced exit after no successful operation for long.
if ((time() - $lastSuccess) > 60) {
throw new Exception("No successful operation for 60s. Script terminated.");
}
}
}
} catch (Exception $e) {
// Error handling
echo "\n Error occurred:\n";
echo "[" . date('Y-m-d H:i:s') . "] " . $e->getMessage() . "\n";
// Try recording the final counter value.
try {
if (isset($redis) && $redis->isConnected()) {
$finalValue = $redis->get("redisExt");
echo "Final counter value: " . ($finalValue ?: 'N/A') . "\n";
}
} catch (Exception $finalEx) {
echo "Obtain the final value failed: " . $finalEx->getMessage() . "\n";
}
exit(1);
}
Feedback
Was this page helpful?
Provide feedbackThank you very much for your feedback. We will continue working to improve the documentation.