-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathCFBalancePHP.php
288 lines (234 loc) · 8.54 KB
/
CFBalancePHP.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
<?php
namespace codefire;
/**
* CodeFire Load Balancer PHP Class Component
*
* @author Tyler Montgomery and Randy Hoover
*/
class CFBalancerPHP {
/** Webservice daemon hostname.
* This should be localhost under most cases. Running the service on a remove machine will
* skew the results and cause the load calculation to fail.
*/
const WEBSERVICE_HOST = "localhost";
/** Webservice daemon port
* Change this to the port that you are running the service on
*/
const WEBSERVICE_PORT = 44444;
/* API SPECIFICATION ------------------------------------------------------------
* The variables below are used to configure the API for the web service daemon.
* The expected data should be as follows:
* 0,17.download.codefi.re,1.01,8.72,*
* 0,21.download.codefi.re,0.91,4.78
*
* The line marked with an asterisk signifies the localhost performance counters.
* The data will then be split (on the comma) into an array.
* Use the constants below to define the format of this array.
*
*/
/** Stores timeout for the webservice in seconds
*/
const WEBSERVICE_TIMEOUT = 1;
/** Stores which array index contains the time since last check-in
*/
const WEBSERVICE_API_EXPIRE = 0;
/** Stores which array index contains the host cname
*/
const WEBSERVICE_API_CNAME = 1;
/** Stores which array index contains the cpu load
*/
const WEBSERVICE_API_CPULOAD = 2;
/** Stores which array index contains the net load
*/
const WEBSERVICE_API_NETLOAD = 3;
/** Stores which elements contains localhost signifier
*/
const WEBSERVICE_API_LOCALHOST_REF = 4;
//
// END API CONFIG
//
/** Multiplier (weight) to each CPU load index
*/
const CPU_LOAD_MULTIPLIER = 2.0;
/** Multiplier (weight) to each net load index
*/
const NET_LOAD_MULTIPLIER = 1.0;
/** Margin of error when comparing cpu loads
*/
const CPU_LOAD_MARGIN = 2.0;
/** Margin of error when comparing network loads
*/
const NET_LOAD_MARGIN = 10.0;
/** Stores the rolling debug log for timing purposes
*/
private $debugLog;
/** Emergency variable. True if this function is defunct and should not be used.
*/
private $dead;
/** For performance-tracking
*/
private $timer;
/** Stores the local node performance counters
*/
private $localhost;
//
// END VARIABLE DECLARATIONS
//
/** This initializes the CFBalancerPHP class.
*/
public function __construct() {
// Initialize timer value here. This allows for us to keep track of the performance of the application and diagnose any hangs.
$this->timer = microtime(true);
}
/** Returns an array of each server in the CFBalancer pool.
* @return string contains the server's internal IP, it's public CNAME, it's CPU load average, and it's current outgoing network rate.
*/
public function getNodeList() {
if ($this->dead) {
$this->debug(__FUNCTION__);
return null;
}
$this->debug(__FUNCTION__ . " - Opening webservice.");
$handle = $this->openWebService();
$nodeListRaw = null;
stream_set_timeout($handle, WEBSERVICE_TIMEOUT);
while (!$this->getArray(stream_get_meta_data($fp),"timed_out")) {
fwrite($handle, "L");
while (!feof($handle)) {
$nodeListRaw .= fread($handle, 128);
}
$this->debug(__FUNCTION__ . " - Got NodeList in raw form from server.");
//process raw nodelist into array
return $this->processNodeList($nodeListRaw);
}
//we're dead!
$this->debug(__FUNCTION__ . " failed, connection timed out. dying.");
$this->dead = True;
return null;
}
/** Allows us to access data returned as an array from a function.
* This is a workaround for php5.3 limitation
* @param array $array The array to operate on
* @param string $key The key to return from $array
* @return string value of $array at $key
*/
private function getArray(array $array, $key) {
return $arr[$key];
}
/** Helper function for sorting the nodeLists
* DEPRECATED!
* @param array $a The first array to compare
* @param array $b The second array to compare
* @param int $key The key to compare both arrays by
*/
function sortNodeList($a, $b, $key) {
return $a[$key] - $b[$key];
}
/** Processes the raw nodelist, into an array
* @param string $raw The raw node list in text form
* @return array Array of nodes other than localhost
*/
private function processNodeList($raw) {
//FIXME: need to add error handing for null NodeList
if ($raw != NULL && isset($raw) && !empty($raw)) {
$this->debug(__FUNCTION__ . " - Processing non-null nodeList.");
// break up the raw text into lines
$lines = explode("\r\n",$raw);
foreach ($lines as $lines) {
// split the raw list into an array per the API specification
$node = explode(",",$lines);
// check if the count of elements matches the signifier for localhost
if (count($node) == WEBSERVICE_API_LOCALHOST_REF) {
// stash local performance counter in dedicated variable
$localhost = $node;
}
// store this non-localhost new node in the nodeList
array_push($nodeList, $node);
}
return $nodeList;
}
else {
$this->debug(__FUNCTION___ . " failed, nodeList was null, empty, or not set.");
$this->dead = True;
return null;
}
}
/** Checks the pool of servers, determines the most available node, and redirects the client to the new node.
* Uses a "Location:" header to accomplish the transfer.
* @api
* @param string $postfix The URL to append to the CNAME to redirect to.
*/
public function checkAndRedirect($postfix) {
if ($this->dead) {
$this->debug(__FUNCTION__);
return null;
}
header("Location: http://". $this->getRedirectNode() ."/".$postfix);
}
/** Performs the comparison of servers in the pool to determine the most available node in the pool.
* @api
* @return string the CNAME of the node that is most available
*/
public function getRedirectNode() {
if ($this->dead) {
$this->debug(__FUNCTION__);
return null;
}
// FIXME still need to break up the nodes array into variables to calculate our values to do our math.
$nodes = $this->getNodeList();
// sort node list by expire time
usort($nodes, function($a, $b) {
return $a[WEBSERVICE_API_EXPIRE] - $b[WEBSERVICE_API_EXPIRE];
});
// now that we have sorted the array off of timeout, we need to assign a weighted sum of "usability" to each host.
$weights = array();
for ($index = 0;$index <= count($nodes); $index++) {
$weights[$index] = ($nodes[$index][WEBSERVICE_API_CPULOAD] * CPU_LOAD_MULTIPLIER) + ($nodes[$index][WEBSERVICE_API_NETLOAD] * NET_LOAD_MULTIPLIER) ;
}
// let's do the same for localhost
$localhost_index = ($this->localhost[WEBSERVICE_API_CPULOAD] * CPU_LOAD_MULTIPLIER) + ($this->localhost[WEBSERVICE_API_NETLOAD] * NET_LOAD_MULTIPLIER);
/*$low_node = array_keys($nodes, min($nodes));
if (($this->localhost - $low_node[WEBSERVICE_API_CPULOAD]) <= CPU_LOAD_MARGIN ){
}*/
return $redirCNAME;
}
/** Connects to the webservice port that CFBalancer exposes on localhost.
* After connecting to the webservice port, it returns a handle (fp) to that connection.
* @return resource a handle to the now-open webservice
*/
private function openWebservice() {
// open web service socket.
// if it takes too long, mark us as $dead, and say where we died.
$fp = fsockopen(WEBSERVICE_HOST, WEBSERVICE_PORT);
if(!$fp) {
$this->debug(__FUCNTION__ . " failed, unable to obtain a file pointer.");
$this->dead = True;
}
return $webserviceHandle;
}
/** Saves debug text along with timing data to a buffer for recall with getDebug() later.
* @param string $text The text to write to the debug buffer
*/
private function debug($text) {
if ($this->dead) {
$debugLog .= "[ ".microtime(true) - $timer."ms ] DEAD! - ".$text." was called. Ignoring. /r/n";
} else if (DEBUG) {
$debugLog .= "[ ".microtime(true) - $timer."ms ] ".$text."/r/n";
}
}
/** Prints the debugging buffer.
*/
public function debugPrint() {
if (DEBUG) {
// If debug is set, echo the log
echo($this->debugLog);
} else if (!$this->dead) {
// If the module isn't dead, tell us quickly and shut up.
echo("CFBalancePHP: debugging disabled. Nothing to display.");
} else {
// However, if we're dead, no matter what, print the damn log!
echo($this->debugLog);
}
}
}
?>