Skip to content

Instantly share code, notes, and snippets.

@michael-grunder
Last active March 18, 2024 17:02
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save michael-grunder/ec1cd54b321c454d63864091ff288401 to your computer and use it in GitHub Desktop.
Save michael-grunder/ec1cd54b321c454d63864091ff288401 to your computer and use it in GitHub Desktop.
<?php
// php cluter-quick-check --host <host> --port <port> [--auth password]
function panicAbort($str_msg) {
fprintf(STDERR, "Error: $str_msg\n\n");
$bt = debug_backtrace();
fprintf(STDERR, "--- BACKTRACE ---\n");
foreach (debug_backtrace() as $frame => $frame_info) {
list ($file, $line, $function) = [
$frame_info['file'],
$frame_info['line'],
$frame_info['function'],
];
fprintf(STDERR, "[%d] %s:%d %s()\n", $frame, basename($file), $line, $function);
}
exit(1);
}
function panicClusterSlotsError($arr_slots, $str_context) {
$str_slots = print_r($arr_slots, true);
printWarning($str_slots);
panicAbort("Malformed CLUSTER SLOTS response ($str_context)");
}
function panicRedisError($str_msg, $arr_errors) {
fprintf(STDERR, "Redis Error: $str_msg\n");
foreach ($arr_errors as $str_error) {
if ($str_error) {
fprintf(STDERR, " Context: $str_error\n");
}
}
exit(-1);
}
function printWarning($str_msg) {
fprintf(STDERR, "Warning: $str_msg\n");
}
function printWarningArray($str_header, $arr_lines) {
fprintf(STDERR, "--- $str_header ---\n");
foreach ($arr_lines as $str_line) {
fprintf(STDERR, "$str_line\n");
}
}
function printUsage() {
fprintf(STDERR, "Usage: php cluster-quick-check.php --host <host> --port <port> [ --auth <auth>]\n");
exit(-1);
}
function getRedisConnection($str_host, $i_port, $str_auth) {
static $_arr_redis = [];
$str_hash = "$str_host:$i_port";
if (!isset($_arr_redis[$str_hash])) {
try {
$obj_r = new Redis(); $obj_r->connect($str_host, $i_port);
if ( ! $obj_r->isConnected()) {
panicAbort("Cannot connect to Redis at $str_hash");
}
if ($str_auth) $obj_r->auth($str_auth);
$arr_errors = [$obj_r->getLastError()];
if ( ! $obj_r->ping()) {
$arr_errors[] = $obj_r->getLastError();
panicRedisError("Can't connect/ping Redis at $str_hash", $arr_errors);
}
$_arr_redis[$str_hash] = $obj_r;
} catch(Exception $ex) {
panicAbort("getRedisConnection(): Can't connect to '$str_hash' (" . $ex->getMessage() . ')');
}
}
return $_arr_redis[$str_hash];
}
function checkClusterState($obj_r) {
$arr_info = array_filter(explode("\r\n", $obj_r->rawCommand('CLUSTER', 'INFO')));
foreach ($arr_info as $str_line) {
$arr_bits = explode(':', $str_line);
if (count($arr_bits) != 2) {
printWarning("Malformed CLUSTER INFO line: $str_line");
continue;
}
list ($key, $value) = $arr_bits;
if ($key == 'cluster_state') {
if ($value != 'ok') {
printWarningArray("CLUSTER INFO", $arr_info);
panicAbort("Cluster is not properly up!");
}
/* Redis thinks it's up */
return;
}
}
/* We shouldn't really get here but make sure anyway */
printWarningArray("CLUSTER INFO", $arr_info);
panicAbort("Cluster is not properly up!");
}
function checkClusterSlots($obj_r) {
$arr_slots = $obj_r->rawCommand('CLUSTER', 'SLOTS');
foreach ($arr_slots as $arr_slot) {
if (count($arr_slot) < 3) {
panicClusterSlotsError($arr_slots, 'node');
}
$sslot = array_shift($arr_slot);
$eslot = array_shift($arr_slot);
foreach ($arr_slot as $arr_info) {
if (count($arr_info) < 3) {
panicClusterSlotsError($arr_slots, 'info');
}
list($str_host, $i_port) = $arr_info;
echo "Checking [$sslot:$eslot] ($str_host:$i_port): ";
getRedisConnection($str_host, $i_port, $obj_r->getAuth());
echo "OK\n";
}
}
}
function getMovedNode($obj_r, $str_msg, &$str_host, &$i_port) {
$arr_bits = explode(' ', $str_msg);
if ($arr_bits[0] == 'MOVED' || $arr_bits[0] == 'ASKING') {
if (count($arr_bits) != 3) {
panicAbort("Malformed redirection!");
}
$arr_dest = explode(':', $arr_bits[2]);
if (count($arr_dest) != 2) {
panicAbort("Malformed destination node!");
}
list($str_host, $i_port) = $arr_dest;
$i_port = str_replace("\0", '', $i_port);
return true;
}
return false;
}
function sendClusterCommand($obj_r, $str_cmd, $arr_args) {
$tries = 10;
while($tries--) {
try {
call_user_func_array([$obj_r, $str_cmd], $arr_args);
if (($str_error = $obj_r->getLastError())) {
if (getMovedNode($obj_r, $str_error, $str_host, $i_port)) {
echo "Redirected to '$str_host:$i_port'\n";
$obj_r = getRedisConnection($str_host, $i_port, $obj_r->getAuth());
} else {
panicRedisError("Non MOVED error", [$str_error]);
}
}
return $obj_r;
} catch(Exception $ex) {
$str_msg = $ex->getMessage();
if (getMovedNode($obj_r, $str_msg, $str_host, $i_port)) {
echo "Redirected to '$str_host:$i_port'\n";
$obj_r = getRedisConnection($str_host, $i_port, $obj_r->getAuth());
} else {
panicAbort("Non MOVED Redis exception: " . $ex->getMessage());
}
}
}
panicAbort("Too many tries tring to find destination node!");
}
$arr_opt = getopt('', ['host:', 'port:', 'auth:']);
$str_host = $arr_opt['host'] ?? NULL;
$i_port = $arr_opt['port'] ?? NULL;
$str_auth = $arr_opt['auth'] ?? NULL;
if (!$str_host || !$i_port) {
printUsage();
}
/* Attempt to connect to the seed */
$obj_r = getRedisConnection($str_host, $i_port, $str_auth);
/* Step one, see if Redis thinks it's a cluster and if the cluster is OK */
echo "Checking general cluster INFO: ";
checkClusterState($obj_r);
echo "OK\n";
/* OK this seems good, now let's iterate over cluster slots */
checkClusterSlots($obj_r);
/* Finally let's set some data */
for($i = 0; $i < 10; $i++) {
$str_key = "phpredis-cluster-key:$i";
$str_val = "phpredis-cluster-val:$i";
echo "Attempting to set key '$str_key'\n";
$obj_r = sendClusterCommand($obj_r, 'SET', [$str_key, $str_val]);
echo "Success setting '$str_key'\n";
}
echo "Cluster seems OK\n";
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment