My favorites | Sign in
Project Home Downloads Wiki Issues Source
Checkout   Browse   Changes    
 
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
<?php
/**
* This class implements the "Circuit Breaker" stability pattern for websites. The goal is to return immediately if a resource is down
* for an extended amount of time, this will allow user connections to not pool up waiting for time outs
*@author Jim Plush jiminoc@gmail.com
*/
class CircuitBreaker
{

/**
* public variable that lets the class know if log messages should be printed out to the screen, you could also change the log method
* to write to your general logfile or database
*/
public $logging = false;



/**
* holds the current application ID to query the status of, you should give each external web service or resource you make contact with
* a unique application ID so you can store the status for each one individually
*@param string
*/
protected $app_id;






/**
* constructor to instantiate the circuitbreaker, needs to query the current status
*/
public function __construct($app_id)
{
$this->app_id = $app_id;
$this->_getAppStatus();
}

/**
* method to call to see if the circuit for this application is open or closed, if closed that means all is well and operations can resume
* as normal. If open then too many failures have occurred and you need to fail immediatley
*/
public function isClosed()
{
if($this->status['state'] !== 'open') {
return true;
}
// get the number of seconds that have past since the circuit was opened to the current system time
$res = mysql_query("SELECT TIME_TO_SEC(TIMEDIFF(NOW(), last_open_time)) as diff FROM circuit_breaker WHERE app_id = '{$this->app_id}'");
$row = mysql_fetch_assoc($res);
if($row['diff'] > $this->status['timeout']) {
$this->_log('Timeout reached, now opening up the circuit halfway');
// we've past the timeout, open the circuit halfway to allow just one connection through
mysql_query("UPDATE circuit_breaker SET state = 'half', failure_count=threshold-1 WHERE app_id = '{$this->app_id}'");
return true;
}
return false;
}

/**
* used to indicate that an action was successful and we can update the status of our app
*/
public function success()
{
// operation was successful so reset this app back to happy state
mysql_query("UPDATE circuit_breaker SET state = 'closed', failure_count=0 WHERE app_id = '{$this->app_id}'");
}

/**
* used to indicate a call has timed out or has failed, increment the failures, if failures exceed threshold then we need to trip the circuit
*/
public function fail()
{
self::_getDb();
if(((int)$this->status['failure_count'] > (int)$this->status['threshold']) || $this->status['state'] === 'half' ) {
$this->_log('Too many failures! Now opening circuit to stop further calls');
mysql_query("UPDATE circuit_breaker SET state = 'open', last_open_time = NOW() WHERE app_id = '{$this->app_id}'");
$this->_notify(); // send email to administrator informing them of the outage
} else {
mysql_query("UPDATE circuit_breaker SET failure_count = failure_count+1 WHERE app_id = '{$this->app_id}'");
}
}


/**
* this should probably also be replaced by your general mail handler but it's here to keep things simple. Just sends an email to the admin
* letting them know the service is down, you'll obviously want to change the email address below
*/
protected function _notify()
{
mail('admin@site.com', "CIRCUIT BREAKER TRIPPED ON {$_SERVER['SERVER_NAME']}", "Application {$this->app_id} has failed to connect"
." over the recommended threshold, all communication is stopped with this app");
}

/**
* you should replace the contents of this method with your own database abstraction layer or configuration DB settings.
* This is only for demonstration purposes only
* It would also be advisable to actually use memcached over a DB for less overhead in maintaining status than a database
* I'm using a database here since that's what most people will have available.
*/
protected function _getAppStatus()
{
self::_getDb(); // connect to our test database

$app_id = mysql_real_escape_string($this->app_id);
if(!$result = mysql_query("SELECT * FROM circuit_breaker WHERE app_id = '{$app_id}'")) {
throw new Exception("CB Error: Querying for the application status failed");
}
if(!$this->status = mysql_fetch_assoc($result)) {
throw new Exception("No Application Found with an ID of {$app_id}");
}
}


/**
* replace this with a DB adapter class or however else you're getting your database connections
*/
static private function _getDb()
{
$conn = mysql_connect('localhost', 'root', 'root');
mysql_select_db('demo', $conn);
}


/**
* if logging is enabled then messages will be shown on the screen
*/
private function _log($msg)
{
if($this->logging) {
echo "{$msg}<br/>";
}
}

}
?>

Change log

r8 by JimInOC on Apr 8, 2009   Diff
updating
Go to: 
Project members, sign in to write a code review

Older revisions

r7 by JimInOC on Apr 8, 2009   Diff
updating
r6 by JimInOC on Apr 8, 2009   Diff
initial checkin
All revisions of this file

File info

Size: 4761 bytes, 136 lines

File properties

svn:eol-style
native
Powered by Google Project Hosting