-
Notifications
You must be signed in to change notification settings - Fork 0
/
RateBasedCircuitBreaker.php
121 lines (102 loc) · 3.32 KB
/
RateBasedCircuitBreaker.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?php
declare(strict_types = 1);
namespace Interrupt\CircuitBreakers;
use DateInterval;
use Interrupt\Contracts\RecordStrategyInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Clock\ClockInterface;
use Scale\Time\Hours;
class RateBasedCircuitBreaker extends AbstractCircuitBreaker {
/**
* Failure rate threshold, after which CircuitBreaker state goes from CircuitStateEnum::CLOSED
* to CircuitStateEnum::OPEN
* Default: 50%
*/
protected float $failureRate;
/**
* Lowest number of requests to start considering $failureRate threshold.
* Default: 10 requests
*/
protected int $minRequestCount;
public function __construct(
ClockInterface $clock,
CacheItemPoolInterface $cacheItemPool,
RecordStrategyInterface $recordStrategy,
DateInterval $coolDownInterval = new DateInterval('PT10S'),
float $failureRate = 0.5,
int $minRequestCount = 10
) {
parent::__construct($clock, $cacheItemPool, $recordStrategy, $coolDownInterval);
$this->failureRate = $failureRate;
$this->minRequestCount = $minRequestCount;
}
public function successful(string $serviceName): CircuitStateEnum {
$item = $this->cacheItemPool->getItem("interrupt/{$serviceName}");
if ($item->isHit() === false) {
$item->set(
[
'state' => CircuitStateEnum::CLOSED,
'record' => $this->recordStrategy,
'updatedAt' => $this->clock->now()
]
);
}
/**
* @var array{
* state: \Interrupt\CircuitBreakers\CircuitStateEnum,
* record: \Interrupt\Contracts\RecordStrategyInterface,
* updatedAt: \DateTimeImmutable
* }
*/
$data = $item->get();
$data['record']->mark($serviceName);
// half-open && success -> closed
if (
$data['state'] === CircuitStateEnum::HALF_OPEN
) {
$data['state'] = CircuitStateEnum::CLOSED;
$data['record']->clear($serviceName);
$data['record']->clear("{$serviceName}.failure");
}
$data['updatedAt'] = $this->clock->now();
$item->set($data);
$item->expiresAfter(Hours::IN_SECONDS);
$this->cacheItemPool->save($item);
return $data['state'];
}
public function failed(string $serviceName): CircuitStateEnum {
$item = $this->cacheItemPool->getItem("interrupt/{$serviceName}");
if ($item->isHit() === false) {
$item->set(
[
'state' => CircuitStateEnum::CLOSED,
'record' => $this->recordStrategy,
'updatedAt' => $this->clock->now()
]
);
}
/**
* @var array{
* state: \Interrupt\CircuitBreakers\CircuitStateEnum,
* record: \Interrupt\Contracts\RecordStrategyInterface,
* updatedAt: \DateTimeImmutable
* }
*/
$data = $item->get();
$totalCount = $data['record']->mark($serviceName);
$failCount = $data['record']->mark("{$serviceName}.failure");
// closed && failure over threshold -> open
// half-open && failure -> open
if (
($data['state'] === CircuitStateEnum::CLOSED && $failCount / $totalCount > $this->failureRate) ||
$data['state'] === CircuitStateEnum::HALF_OPEN
) {
$data['state'] = CircuitStateEnum::OPEN;
}
$data['updatedAt'] = $this->clock->now();
$item->set($data);
$item->expiresAfter(Hours::IN_SECONDS);
$this->cacheItemPool->save($item);
return $data['state'];
}
}