All Projects → ackintosh → Ganesha

ackintosh / Ganesha

Licence: mit
🐘 A Circuit Breaker pattern implementation for PHP applications.

Projects that are alternatives of or similar to Ganesha

Istio
Connect, secure, control, and observe services.
Stars: ✭ 28,970 (+7444.27%)
Mutual labels:  microservices, microservice, circuit-breaker
Tree Gateway
This is a full featured and free API Gateway
Stars: ✭ 160 (-58.33%)
Mutual labels:  microservices, microservice, circuit-breaker
Nxplorerjs Microservice Starter
Node JS , Typescript , Express based reactive microservice starter project for REST and GraphQL APIs
Stars: ✭ 193 (-49.74%)
Mutual labels:  microservices, microservice
Express Gateway
A microservices API Gateway built on top of Express.js
Stars: ✭ 2,583 (+572.66%)
Mutual labels:  microservices, microservice
Krakend Ce
KrakenD Community Edition. Make your binary of KrakenD API Gateway
Stars: ✭ 245 (-36.2%)
Mutual labels:  microservices, microservice
Tenso
Tenso is an HTTP REST API framework
Stars: ✭ 167 (-56.51%)
Mutual labels:  microservices, microservice
Cote
A Node.js library for building zero-configuration microservices.
Stars: ✭ 2,180 (+467.71%)
Mutual labels:  microservices, microservice
Quickperf
QuickPerf is a testing library for Java to quickly evaluate and improve some performance-related properties
Stars: ✭ 231 (-39.84%)
Mutual labels:  microservices, microservice
Dapr
Dapr is a portable, event-driven, runtime for building distributed applications across cloud and edge.
Stars: ✭ 16,274 (+4138.02%)
Mutual labels:  microservices, microservice
Manba
HTTP API Gateway
Stars: ✭ 3,000 (+681.25%)
Mutual labels:  microservice, circuit-breaker
Brakes
Hystrix compliant Node.js Circuit Breaker Library
Stars: ✭ 255 (-33.59%)
Mutual labels:  circuit-breaker, fault-tolerance
Sentinel
A powerful flow control component enabling reliability, resilience and monitoring for microservices. (面向云原生微服务的高可用流控防护组件)
Stars: ✭ 18,071 (+4605.99%)
Mutual labels:  microservice, circuit-breaker
Event Sourcing Jambo
An Hexagonal Architecture with DDD + Aggregates + Event Sourcing using .NET Core, Kafka e MongoDB (Blog Engine)
Stars: ✭ 159 (-58.59%)
Mutual labels:  microservices, microservice
Space Cloud
Open source Firebase + Heroku to develop, scale and secure serverless apps on Kubernetes
Stars: ✭ 3,323 (+765.36%)
Mutual labels:  microservices, microservice
Kratos
A modular-designed and easy-to-use microservices framework in Go.
Stars: ✭ 15,844 (+4026.04%)
Mutual labels:  microservices, microservice
Nodejs Microservice Starter
🌱 NodeJS RESTful API Microservice Starter
Stars: ✭ 220 (-42.71%)
Mutual labels:  microservices, microservice
Spring Cloud Alibaba
Spring Cloud Alibaba provides a one-stop solution for application development for the distributed solutions of Alibaba middleware.
Stars: ✭ 20,934 (+5351.56%)
Mutual labels:  microservices, circuit-breaker
Go Micro Boilerplate
The boilerplate of the GoLang application with a clear microservices architecture.
Stars: ✭ 147 (-61.72%)
Mutual labels:  microservices, microservice
Edward
A tool for managing local microservice instances
Stars: ✭ 152 (-60.42%)
Mutual labels:  microservices, microservice
Event Stream Processing Microservices
Using Spring Cloud Stream and Spring State Machine to create event-driven microservices
Stars: ✭ 255 (-33.59%)
Mutual labels:  microservices, microservice

Ganesha

Ganesha is PHP implementation of Circuit Breaker pattern which has multi strategies to avoid cascading failures and supports various storages to record statistics.

ganesha

Latest Stable Version Tests Coverage Status Scrutinizer Code Quality Minimum PHP Version

If Ganesha is saving your service from system failures, please consider donating to this project's author, Akihito Nakano, to show your ❤️ and support. Thank you!

Sponsor @ackintosh on GitHub Sponsors


This is one of the Circuit Breaker implementation in PHP which has been actively developed and production ready - well-tested and well-documented. 💪 You can integrate Ganesha to your existing code base easily as Ganesha provides just simple interfaces and Guzzle Middleware behaves transparency.

If you have an idea about enhancement, bugfix..., please let me know via Issues. ✨

Table of contents

Are you interested?

Here is an example which shows you how Ganesha behaves when a failure occurs.
It is easily executable. All you need is Docker.

Unveil Ganesha

# Install Composer
$ curl -sS https://getcomposer.org/installer | php

# Run the Composer command to install the latest version of Ganesha
$ php composer.phar require ackintosh/ganesha

Usage

Ganesha provides following simple interfaces. Each method receives a string (named $service in example) to identify the service. $service will be the service name of the API, the endpoint name, etc. Please remember that Ganesha detects system failure for each $service.

$ganesha->isAvailable($service);
$ganesha->success($service);
$ganesha->failure($service);
// For further details about builder options, please see the `Strategy` section.
$ganesha = Ackintosh\Ganesha\Builder::withRateStrategy()
    ->adapter(new Ackintosh\Ganesha\Storage\Adapter\Redis($redis))
    ->failureRateThreshold(50)
    ->intervalToHalfOpen(10)
    ->minimumRequests(10)
    ->timeWindow(30)
    ->build();

$service = 'external_api';

if (!$ganesha->isAvailable($service)) {
    die('external api is not available');
}

try {
    echo \Api::send($request)->getBody();
    $ganesha->success($service);
} catch (\Api\RequestTimedOutException $e) {
    // If an error occurred, it must be recorded as failure.
    $ganesha->failure($service);
    die($e->getMessage());
}

Three states of circuit breaker

(martinfowler.com : CircuitBreaker)

Ganesha follows the states and transitions described in the article faithfully. $ganesha->isAvailable() returns true if the circuit states on Closed, otherwise it returns false.

Subscribe to events in ganesha

  • When the circuit state transitions to Open the event Ganesha::EVENT_TRIPPED is triggered
  • When the state back to Closed the event Ganesha::EVENT_CALMED_DOWN is triggered
$ganesha->subscribe(function ($event, $service, $message) {
    switch ($event) {
        case Ganesha::EVENT_TRIPPED:
            \YourMonitoringSystem::warn(
                "Ganesha has tripped! It seems that a failure has occurred in {$service}. {$message}."
            );
            break;
        case Ganesha::EVENT_CALMED_DOWN:
            \YourMonitoringSystem::info(
                "The failure in {$service} seems to have calmed down :). {$message}."
            );
            break;
        case Ganesha::EVENT_STORAGE_ERROR:
            \YourMonitoringSystem::error($message);
            break;
        default:
            break;
    }
});

Disable

If disabled, Ganesha keeps to record success/failure statistics, but Ganesha doesn't trip even if the failure count reached to a threshold.

// Ganesha with Count strategy(threshold `3`).
// $ganesha = Ackintosh\Ganesha\Builder::withCountStrategy() ...

// Disable
Ackintosh\Ganesha::disable();

// Although the failure is recorded to storage,
$ganesha->failure($service);
$ganesha->failure($service);
$ganesha->failure($service);

// Ganesha does not trip and Ganesha::isAvailable() returns true.
var_dump($ganesha->isAvailable($service));
// bool(true)

Reset

Resets the statistics saved in a storage.

$ganesha = Ackintosh\Ganesha\Builder::withRateStrategy()
    // ...
    ->build();

$ganesha->reset();

Strategies

Ganesha has two strategies which avoids cascading failures.

Rate

$ganesha = Ackintosh\Ganesha\Builder::withRateStrategy()
    // The interval in time (seconds) that evaluate the thresholds.
    ->timeWindow(30)
    // The failure rate threshold in percentage that changes CircuitBreaker's state to `OPEN`.
    ->failureRateThreshold(50)
    // The minimum number of requests to detect failures.
    // Even if `failureRateThreshold` exceeds the threshold,
    // CircuitBreaker remains in `CLOSED` if `minimumRequests` is below this threshold.
    ->minimumRequests(10)
    // The interval (seconds) to change CircuitBreaker's state from `OPEN` to `HALF_OPEN`.
    ->intervalToHalfOpen(5)
    // The storage adapter instance to store various statistics to detect failures.
    ->adapter(new Ackintosh\Ganesha\Storage\Adapter\Memcached($memcached))
    ->build();

Note about "time window": The Storage Adapter implements either SlidingTimeWindow or TumblingTimeWindow. The difference of the implementation comes from constraints of the storage functionalities.

[SlidingTimeWindow]

  • SlidingTimeWindow implements a time period that stretches back in time from the present. For instance, a SlidingTimeWindow of 30 seconds includes any events that have occurred in the past 30 seconds.
  • Redis adapter and MongoDB adapter implements SlidingTimeWindow.

The details to help us understand visually is shown below:
(quoted from Introduction to Stream Analytics windowing functions - Microsoft Azure)

[TumblingTimeWindow]

The details to help us understand visually is shown below:
(quoted from Introduction to Stream Analytics windowing functions - Microsoft Azure)

Count

If you want use the Count strategy use Builder::buildWithCountStrategy() to build an instance.

$ganesha = Ackintosh\Ganesha\Builder::withCountStrategy()
    // The failure count threshold that changes CircuitBreaker's state to `OPEN`.
    // The count will be increased if `$ganesha->failure()` is called,
    // or will be decreased if `$ganesha->success()` is called.
    ->failureCountThreshold(100)
    // The interval (seconds) to change CircuitBreaker's state from `OPEN` to `HALF_OPEN`.
    ->intervalToHalfOpen(5)
    // The storage adapter instance to store various statistics to detect failures.
    ->adapter(new Ackintosh\Ganesha\Storage\Adapter\Memcached($memcached))
    ->build();

Adapters

APCu

The APCu adapter requires the APCu extension.

$adapter = new Ackintosh\Ganesha\Storage\Adapter\Apcu();

$ganesha = Ackintosh\Ganesha\Builder::withRateStrategy()
    ->adapter($adapter)
    // ... (omitted) ...
    ->build();

Note: APCu is internal to each server/instance, not pooled like most Memcache and Redis setups. Each worker's circuit breaker will activate or reset individually, and failure thresholds should be set lower to compensate.

Redis

Redis adapter requires phpredis or Predis client instance. The example below is using phpredis.

$redis = new \Redis();
$redis->connect('localhost');
$adapter = new Ackintosh\Ganesha\Storage\Adapter\Redis($redis);

$ganesha = Ackintosh\Ganesha\Builder::withRateStrategy()
    ->adapter($adapter)
    // ... (omitted) ...
    ->build();

Memcached

Memcached adapter requires memcached (NOT memcache) extension.

$memcached = new \Memcached();
$memcached->addServer('localhost', 11211);
$adapter = new Ackintosh\Ganesha\Storage\Adapter\Memcached($memcached);

$ganesha = Ackintosh\Ganesha\Builder::withRateStrategy()
    ->adapter($adapter)
    // ... (omitted) ...
    ->build();

MongoDB

MongoDB adapter requires mongodb extension.

$manager = new \MongoDB\Driver\Manager('mongodb://localhost:27017/');
$adapter = new Ackintosh\Ganesha\Storage\Adapter\MongoDB($manager, 'dbName', 'collectionName');

$ganesha = Ackintosh\Ganesha\Builder::withRateStrategy()
    ->adapter($adapter)
    // ... (omitted) ...
    ->build();

Customizing storage keys

If you want customize the keys to be used when storing circuit breaker information, set an instance which implements StorageKeysInterface.

class YourStorageKeys implements StorageKeysInterface
{
    public function prefix()
    {
        return 'your_prefix_';
    }

    // ... (omitted) ...
}

$ganesha = Ackintosh\Ganesha\Builder::withRateStrategy()
    // The keys which will stored by Ganesha to the storage you specified via `adapter`
    // will be prefixed with `your_prefix_`.
    ->storageKeys(new YourStorageKeys())
    // ... (omitted) ...
    ->build();

Ganesha ❤️ Guzzle

If you are using Guzzle (v6 or higher), Guzzle Middleware powered by Ganesha makes it easy to integrate Circuit Breaker to your existing code base.

use Ackintosh\Ganesha\Builder;
use Ackintosh\Ganesha\GuzzleMiddleware;
use Ackintosh\Ganesha\Exception\RejectedException;
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;

$ganesha = Builder::withRateStrategy()
    ->timeWindow(30)
    ->failureRateThreshold(50)
    ->minimumRequests(10)
    ->intervalToHalfOpen(5)
    ->adapter($adapter)
    ->build();

$middleware = new GuzzleMiddleware($ganesha);

$handlers = HandlerStack::create();
$handlers->push($middleware);

$client = new Client(['handler' => $handlers]);

try {
    $client->get('http://api.example.com/awesome_resource');
} catch (RejectedException $e) {
    // If the circuit breaker is open, RejectedException will be thrown.
}

How does Guzzle Middleware determine the $service?

As documented in Usage, Ganesha detects failures for each $service. Below, We will show you how Guzzle Middleware determine $service and how we specify $service explicitly.

By default, the host name is used as $service.

// In the example above, `api.example.com` is used as `$service`.
$client->get('http://api.example.com/awesome_resource');

You can also specify $service via a option passed to client, or request header. If both are specified, the option value takes precedence.

// via constructor argument
$client = new Client([
    'handler' => $handlers,
    // 'ganesha.service_name' is defined as ServiceNameExtractor::OPTION_KEY
    'ganesha.service_name' => 'specified_service_name',
]);

// via request method argument
$client->get(
    'http://api.example.com/awesome_resource',
    [
        'ganesha.service_name' => 'specified_service_name',
    ]
);

// via request header
$request = new Request(
    'GET',
    'http://api.example.com/awesome_resource',
    [
        // 'X-Ganesha-Service-Name' is defined as ServiceNameExtractor::HEADER_NAME
        'X-Ganesha-Service-Name' => 'specified_service_name'
    ]
);
$client->send($request);

Alternatively, you can apply your own rules by implementing a class that implements the ServiceNameExtractorInterface.

use Ackintosh\Ganesha\GuzzleMiddleware\ServiceNameExtractorInterface;
use Psr\Http\Message\RequestInterface;

class SampleExtractor implements ServiceNameExtractorInterface
{
    /**
     * @override
     */
    public function extract(RequestInterface $request, array $requestOptions)
    {
        // We treat the combination of host name and HTTP method name as $service.
        return $request->getUri()->getHost() . '_' . $request->getMethod();
    }
}

// ---

$ganesha = Builder::withRateStrategy()
    // ...
    ->build();
$middleware = new GuzzleMiddleware(
    $ganesha,
    // Pass the extractor as an argument of GuzzleMiddleware constructor.
    new SampleExtractor()
);

Ganesha ❤️ OpenAPI Generator

PHP client generated by OpenAPI Generator is using Guzzle as HTTP client and as we mentioned as Ganesha ❤️ Guzzle, Guzzle Middleware powered by Ganesha is ready. So it is easily possible to integrate Ganesha and the PHP client generated by OpenAPI Generator in a smart way as below.

// For details on how to build middleware please see https://github.com/ackintosh/ganesha#ganesha-heart-guzzle
$middleware = new GuzzleMiddleware($ganesha);

// Set the middleware to HTTP client.
$handlers = HandlerStack::create();
$handlers->push($middleware);
$client = new Client(['handler' => $handlers]);

// Just pass the HTTP client to the constructor of API class.
$api = new PetApi($client);

try {
    // Ganesha is working in the shadows! The result of api call is monitored by Ganesha.
    $api->getPetById(123);
} catch (RejectedException $e) {
    awesomeErrorHandling($e);
}

Ganesha ❤️ Symfony HttpClient

If you are using Symfony HttpClient, GaneshaHttpClient makes it easy to integrate Circuit Breaker to your existing code base.

use Ackintosh\Ganesha\Builder;
use Ackintosh\Ganesha\GaneshaHttpClient;
use Ackintosh\Ganesha\Exception\RejectedException;

$ganesha = Builder::withRateStrategy()
    ->timeWindow(30)
    ->failureRateThreshold(50)
    ->minimumRequests(10)
    ->intervalToHalfOpen(5)
    ->adapter($adapter)
    ->build();

$client = HttpClient::create();
$ganeshaClient = new GaneshaHttpClient($client, $ganesha);

try {
    $ganeshaClient->request('GET', 'http://api.example.com/awesome_resource');
} catch (RejectedException $e) {
    // If the circuit breaker is open, RejectedException will be thrown.
}

How does GaneshaHttpClient determine the $service?

As documented in Usage, Ganesha detects failures for each $service. Below, We will show you how GaneshaHttpClient determine $service and how we specify $service explicitly.

By default, the host name is used as $service.

// In the example above, `api.example.com` is used as `$service`.
$ganeshaClient->request('GET', 'http://api.example.com/awesome_resource');

You can also specify $service via a option passed to client, or request header. If both are specified, the option value takes precedence.

// via constructor argument
$ganeshaClient = new GaneshaHttpClient($client, $ganesha, [
    // 'ganesha.service_name' is defined as ServiceNameExtractor::OPTION_KEY
    'ganesha.service_name' => 'specified_service_name',
]);

// via request method argument
$ganeshaClient->request(
    'GET',
    'http://api.example.com/awesome_resource',
    [
        'ganesha.service_name' => 'specified_service_name',
    ]
);

// via request header
$ganeshaClient->request('GET', '', ['headers' => [
     // 'X-Ganesha-Service-Name' is defined as ServiceNameExtractor::HEADER_NAME
     'X-Ganesha-Service-Name' => 'specified_service_name'
]]);

Alternatively, you can apply your own rules by implementing a class that implements the ServiceNameExtractorInterface.

use Ackintosh\Ganesha\HttpClient\HostTrait;
use Ackintosh\Ganesha\HttpClient\ServiceNameExtractorInterface;

final class SampleExtractor implements ServiceNameExtractorInterface
{
    use HostTrait;

    /**
     * @override
     */
    public function extract(string $method, string $url, array $requestOptions): string
    {
        // We treat the combination of host name and HTTP method name as $service.
        return self::extractHostFromUrl($url) . '_' . $method;
    }
}

// ---

$ganesha = Builder::withRateStrategy()
    // ...
    ->build();
$ganeshaClient = new GaneshaHttpClient(
    $client,
    $ganesha,
    // Pass the extractor as an argument of GaneshaHttpClient constructor.
    new SampleExtractor()
);

Companies using Ganesha 🚀

Here are some companies using Ganesha in production! We are proud of them. 🐘

To add your company to the list, please visit README.md and click on the icon to edit the page or let me know via issues/twitter.

The articles/videos Ganesha loves ✨ 🐘 ✨

Here are some articles/videos introduce Ganesha! All of them are really shining like a jewel for us. ✨

Articles

Videos

Run tests

We can run unit tests on a Docker container, so it is not necessary to install the dependencies in your machine.

# Start redis, memcached server
$ docker-compose up

# Run tests in container
$ docker-compose run --rm -w /tmp/ganesha -u ganesha client vendor/bin/phpunit

Requirements

  • Ganesha supports PHP 7.1 or higher.
  • An extension or client library which is used by the storage adapter you've choice will be required. Please check the Adapters section for details.

Author

Ganesha © ackintosh, Released under the MIT License.
Authored and maintained by ackintosh

GitHub @ackintosh / Twitter @NAKANO_Akihito / Blog (ja)

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].