Add randomized RPC endpoint pooling and failover
This commit is contained in:
parent
d1f13d2acb
commit
3e60837ec3
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Contractless\Rpc\Transport;
|
||||||
|
|
||||||
|
use Contractless\Rpc\Exception\TransportException;
|
||||||
|
|
||||||
|
interface EndpointHealthTrackerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Remove endpoints that should not receive requests at this time.
|
||||||
|
*
|
||||||
|
* @param list<string> $endpointIds
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
public function eligibleEndpointIds(array $endpointIds): array;
|
||||||
|
|
||||||
|
public function recordSuccess(string $endpointId): void;
|
||||||
|
|
||||||
|
public function recordFailure(
|
||||||
|
string $endpointId,
|
||||||
|
TransportException $error,
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
@ -25,8 +25,11 @@ final class EndpointPoolTransport implements TransportInterface
|
||||||
/**
|
/**
|
||||||
* @param array<string, TransportInterface> $transports
|
* @param array<string, TransportInterface> $transports
|
||||||
*/
|
*/
|
||||||
public function __construct(array $transports, private readonly bool $randomize = true)
|
public function __construct(
|
||||||
{
|
array $transports,
|
||||||
|
private readonly bool $randomize = true,
|
||||||
|
private readonly ?EndpointHealthTrackerInterface $healthTracker = null,
|
||||||
|
) {
|
||||||
if ($transports === []) {
|
if ($transports === []) {
|
||||||
throw new InvalidArgumentException('The RPC endpoint pool cannot be empty.');
|
throw new InvalidArgumentException('The RPC endpoint pool cannot be empty.');
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +58,14 @@ final class EndpointPoolTransport implements TransportInterface
|
||||||
): string {
|
): string {
|
||||||
$this->lastSuccessfulEndpointId = null;
|
$this->lastSuccessfulEndpointId = null;
|
||||||
$endpointIds = array_keys($this->transports);
|
$endpointIds = array_keys($this->transports);
|
||||||
|
if ($this->healthTracker !== null) {
|
||||||
|
$endpointIds = $this->healthTracker->eligibleEndpointIds($endpointIds);
|
||||||
|
}
|
||||||
|
if ($endpointIds === []) {
|
||||||
|
throw new TransportException(
|
||||||
|
'Every Contractless RPC endpoint is temporarily unavailable.',
|
||||||
|
);
|
||||||
|
}
|
||||||
if ($this->randomize && count($endpointIds) > 1) {
|
if ($this->randomize && count($endpointIds) > 1) {
|
||||||
shuffle($endpointIds);
|
shuffle($endpointIds);
|
||||||
}
|
}
|
||||||
|
|
@ -71,10 +82,12 @@ final class EndpointPoolTransport implements TransportInterface
|
||||||
$validateHandshake,
|
$validateHandshake,
|
||||||
);
|
);
|
||||||
$this->lastSuccessfulEndpointId = $endpointId;
|
$this->lastSuccessfulEndpointId = $endpointId;
|
||||||
|
$this->healthTracker?->recordSuccess($endpointId);
|
||||||
return $reply;
|
return $reply;
|
||||||
} catch (TransportException $error) {
|
} catch (TransportException $error) {
|
||||||
// Only transport failures move the same request to another
|
// Only transport failures move the same request to another
|
||||||
// endpoint. Protocol and validation errors remain final.
|
// endpoint. Protocol and validation errors remain final.
|
||||||
|
$this->healthTracker?->recordFailure($endpointId, $error);
|
||||||
$failures[] = "$endpointId: {$error->getMessage()}";
|
$failures[] = "$endpointId: {$error->getMessage()}";
|
||||||
$lastFailure = $error;
|
$lastFailure = $error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue