Skip to content

Commit

Permalink
Merge pull request #14 from celosauro/add_redis-cluster-adapter
Browse files Browse the repository at this point in the history
Added: RedisClusterAdapter
  • Loading branch information
leocarmo committed Dec 14, 2022
2 parents a3c72d7 + 7aaf538 commit 4f79df2
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 0 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ composer require leocarmo/circuit-breaker-php
## Adapters

- [Redis](#redis-adapter)
- [Redis Cluster](#redis-cluster-adapter)
- [Swoole Table](#swooletable-adapter)

### Redis Adapter
Expand All @@ -36,6 +37,26 @@ $circuit = new CircuitBreaker($adapter, 'my-service');

> See [this](examples/RedisAdapterExample.php) for full example
### Redis Cluster Adapter
Without use of [`multi`](https://redis.io/commands/multi/) command.
The first argument is a redis connection, the second is your product name, for redis namespace avoid key conflicts with another product using the same redis.

```php
use LeoCarmo\CircuitBreaker\CircuitBreaker;
use LeoCarmo\CircuitBreaker\Adapters\RedisClusterAdapter;

// Connect to redis
$redis = new \Redis();
$redis->connect('localhost', 6379);

$adapter = new RedisClusterAdapter($redis, 'my-product');

// Set redis adapter for CB
$circuit = new CircuitBreaker($adapter, 'my-service');
```

> See [this](examples/RedisClusterAdapterExample.php) for full example
### SwooleTable Adapter

```php
Expand Down
44 changes: 44 additions & 0 deletions examples/RedisClusterAdapterExample.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php declare(strict_types=1);

require_once __DIR__ . '/../vendor/autoload.php';

use LeoCarmo\CircuitBreaker\CircuitBreaker;
use LeoCarmo\CircuitBreaker\Adapters\RedisClusterAdapter;

// Connect to redis
$redis = new \Redis();
$redis->connect('localhost', 6379);

$adapter = new RedisClusterAdapter($redis, 'my-product');

// Set redis adapter for CB
$circuit = new CircuitBreaker($adapter, 'my-service');

// Configure settings for CB
$circuit->setSettings([
'timeWindow' => 60, // Time for an open circuit (seconds)
'failureRateThreshold' => 50, // Fail rate for open the circuit
'intervalToHalfOpen' => 30, // Half open time (seconds)
]);

// Check circuit status for service
if (! $circuit->isAvailable()) {
die('Circuit is not available!');
}

// Usage example for success and failure
function myService() {
if (rand(1, 100) >= 50) {
throw new RuntimeException('Something got wrong!');
}
}

try {
myService();
$circuit->success();
echo 'success!' . PHP_EOL;
} catch (RuntimeException $e) {
// If an error occurred, it must be recorded as failure.
$circuit->failure();
echo 'fail!' . PHP_EOL;
}
146 changes: 146 additions & 0 deletions src/Adapters/RedisClusterAdapter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php declare(strict_types=1);

namespace LeoCarmo\CircuitBreaker\Adapters;

class RedisClusterAdapter implements AdapterInterface
{
/**
* @var \Redis
*/
protected $redis;

/**
* @var string
*/
protected string $redisNamespace;

/**
* Set settings for start circuit service
*
* @param $redis
* @param string $redisNamespace
*/
public function __construct($redis, string $redisNamespace)
{
$this->checkExtensionLoaded();
$this->redis = $redis;
$this->redisNamespace = $redisNamespace;
}

protected function checkExtensionLoaded(): void
{
if (! extension_loaded('redis')) {
throw new \RuntimeException('Extension redis is required to use RedisAdapter.');
}
}

/**
* @param string $service
* @return bool
*/
public function isOpen(string $service): bool
{
return (bool) $this->redis->get(
$this->makeNamespace($service) . ':open'
);
}

/**
* @param string $service
* @param int $failureRateThreshold
* @return bool
*/
public function reachRateLimit(string $service, int $failureRateThreshold): bool
{
$failures = (int) $this->redis->get(
$this->makeNamespace($service) . ':failures'
);

return ($failures >= $failureRateThreshold);
}

/**
* @param string $service
* @return bool
*/
public function isHalfOpen(string $service): bool
{
return (bool) $this->redis->get(
$this->makeNamespace($service) . ':half_open'
);
}

/**
* @param string $service
* @param int $timeWindow
* @return bool
*/
public function incrementFailure(string $service, int $timeWindow): bool
{
$serviceName = $this->makeNamespace($service) . ':failures';

if (! $this->redis->get($serviceName)) {
$this->redis->incr($serviceName);
return (bool) $this->redis->expire($serviceName, $timeWindow);
}

return (bool) $this->redis->incr($serviceName);
}

/**
* @param string $service
*/
public function setSuccess(string $service): void
{
$serviceName = $this->makeNamespace($service);

$this->redis->del($serviceName . ':open');
$this->redis->del($serviceName . ':failures');
$this->redis->del($serviceName . ':half_open');
}

/**
* @param string $service
* @param int $timeWindow
*/
public function setOpenCircuit(string $service, int $timeWindow): void
{
$this->redis->set(
$this->makeNamespace($service) . ':open',
time(),
$timeWindow
);
}

/**
* @param string $service
* @param int $timeWindow
* @param int $intervalToHalfOpen
*/
public function setHalfOpenCircuit(string $service, int $timeWindow, int $intervalToHalfOpen): void
{
$this->redis->set(
$this->makeNamespace($service) . ':half_open',
time(),
($timeWindow + $intervalToHalfOpen)
);
}

public function getFailuresCounter(string $service): int
{
$failures = $this->redis->get(
$this->makeNamespace($service) . ':failures'
);

return (int) $failures;
}

/**
* @param string $service
* @return string
*/
protected function makeNamespace(string $service): string
{
return 'circuit-breaker:' . $this->redisNamespace . ':' . $service;
}
}
13 changes: 13 additions & 0 deletions tests/AdaptersTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php declare(strict_types=1);

use LeoCarmo\CircuitBreaker\Adapters\AdapterInterface;
use LeoCarmo\CircuitBreaker\Adapters\RedisClusterAdapter;
use LeoCarmo\CircuitBreaker\Adapters\SwooleTableAdapter;
use LeoCarmo\CircuitBreaker\CircuitBreaker;
use LeoCarmo\CircuitBreaker\Adapters\RedisAdapter;
Expand All @@ -19,6 +20,17 @@ public function testCreateRedisAdapter()
return $adapter;
}

public function testCreateRedisClusterAdapter()
{
$redis = new \Redis();
$redis->connect(getenv('REDIS_HOST'));
$adapter = new RedisClusterAdapter($redis, 'my-product');

$this->assertInstanceOf(AdapterInterface::class, $adapter);

return $adapter;
}

public function testCreateSwooleTableAdapter()
{
$adapter = new SwooleTableAdapter();
Expand All @@ -30,6 +42,7 @@ public function provideAdapters()
{
return [
'redis' => [$this->testCreateRedisAdapter()],
'redis-cluster' => [$this->testCreateRedisClusterAdapter()],
'swoole-table' => [$this->testCreateSwooleTableAdapter()],
];
}
Expand Down

0 comments on commit 4f79df2

Please sign in to comment.