Antonio Santiago
Software engineer as profession and hobby. A citizen of the world. Author of The Book of OpenLayers 3 and OpenLayers Cookbook.
...the capacity to recover quickly from difficulties; toughness.
Long time ago Netflix developed Hystrix, a Java library to make Netflix API more resilient.
On a system with 30 dependencies, each with a 99.99% uptime, in 1 billion request means 3,000,000 are failures.
The system receives requests that depends on third party services to be resolved (database, API, queue, ...)
If one of the dependencies is down the requests is active until a timeout is triggered
More request arrives and are waiting the response of a system that is failing.
Circuit breaker pattern
Circuit breaker pattern
use Odesk\Phystrix\AbstractCommand;
class PrintNameCommand extends AbstractCommand
{
// Metrics will be grouped by this name
protected $commandKey = "print_name_command";
private $name;
public function __construct($name)
{
$this->name = $name;
}
/**
* This function is called internally by Phystrix, only if the request is allowed
*/
protected function run()
{
return 'Hello ' . $this->name;
}
/**
* Optionally we implemented a fallback method.
*/
protected function getFallback()
{
return 'Hello no-name :(';
}
}
Custom command implementation:
$phystrixFactory = ...; // Must be a CommandFactory reference
// Create a command instance passing parameters to the constructor
$command = $phystrixFactory->getCommand('PrintNameCommand', 'Batman');
$result = $command->execute(); // Hello Batman
Command invokation:
phystrix-bundle: Easily integrate Phystrix within Symfony2 applications.
odesk_phystrix:
default:
# Whether fallback logic of the phystrix command is enabled
fallback: false
circuitBreaker:
# How many failed request it might be before we open the circuit (disallow consecutive requests)
errorThresholdPercentage: 5
# If true, the circuit breaker will always be open regardless the metrics
forceOpen: false
# If true, the circuit breaker will always be closed, allowing all requests, regardless the metrics
forceClosed: false
# How many requests we need minimally before we can start making decisions about service stability
requestVolumeThreshold: 1
# For how long to wait before attempting to access a failing service
sleepWindowInMilliseconds: 5000
metrics:
# This is for caching metrics so they are not recalculated more often than needed
healthSnapshotIntervalInMilliseconds: 1000
# The period of time within which we the stats are collected
rollingStatisticalWindowInMilliseconds: 50000
# The more buckets the more precise and actual the stats and slower the calculation.
rollingStatisticalWindowBuckets: 10
# Request cache, if enabled and a command has getCacheKey implemented caches results within current http request
requestCache: false
# Request log collects all commands executed within current http request
requestLog: true
Your source repository:
class UserRepository implements UserRepositoryInterface
{
private $databaseClient;
public function __construct(SomeDatabaseClient $databaseClient)
{
$this->databaseClient = $databaseClient;
}
public function find($userId)
{
return $this->databaseClient->findMagically($userId);
}
}
This is a trait:
trait PhystrixCommandExecutorTrait
{
private function executeCommand($callback)
{
/** @var PhystrixCommand $command */
$command = $this->commandFactory->getCommand(
PhystrixCommand::class,
$callback,
static::COMMAND_NAME
);
return $command->execute();
}
}
This is the phystrixized repository:
class PhystrixUserRepository implements UserPhystrixRepositoryInterface
{
use PhystrixCommandExecutorTrait;
const COMMAND_NAME = 'user_repository';
private $commandFactory;
private $userRepository;
public function __construct(CommandFactory $commandFactory, UserRepositoryInterface $userRepository)
{
$this->commandFactory = $commandFactory;
$this->userRepository = $userRepository;
}
public function find($userId)
{
$callback = function () use ($userId) {
return $this->userRepository->find($userId);
};
return $this->executeCommand($callback);
}
}
data: {
"type": "HystrixCommand",
"name": "PlaylistGet",
"group": "PlaylistGet",
"currentTime": 1355239617628,
"isCircuitBreakerOpen": false,
"errorPercentage": 0,
"errorCount": 0,
"requestCount": 121,
...
}
phystrix-dashboard: Utilities to create a page that serves monitoring information to Hystrix-Dashboard.
<?php
namespace MyBundle\Controller;
use Odesk\PhystrixDashboard\MetricsEventStream\ApcMetricsPoller;
use Odesk\PhystrixDashboard\MetricsEventStream\MetricsServer;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Zend\Config\Config;
class PhystrixController extends Controller
{
/**
* Streaming response to return Phystrix statistics.
*
* @Route("/phystrix_status", name="phystrix_status")
*/
public function statusAction()
{
// Made this end point accesible from local host in production environment
if ('prod' === $this->get('kernel')->getEnvironment()) {
if (isset($_SERVER['HTTP_CLIENT_IP'])
|| isset($_SERVER['HTTP_X_FORWARDED_FOR'])
|| !(in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', 'fe80::1', '::1')) || php_sapi_name() === 'cli-server')
) {
header('HTTP/1.0 403 Forbidden');
exit('You are not allowed to access this file.');
}
}
$response = new StreamedResponse();
$response->setCallback(function () {
// Get phystrix data configuration
$configData = $this->getParameter('phystrix.configuration.data');
$config = new Config($configData);
$metricsPoller = new ApcMetricsPoller($config);
$metricsServer = new MetricsServer($metricsPoller);
$metricsServer->run();
});
$response->send();
}
}
By Antonio Santiago
Avoid the disaster and protect your systems.
Software engineer as profession and hobby. A citizen of the world. Author of The Book of OpenLayers 3 and OpenLayers Cookbook.