Commit f36394a4 authored by Bas de Nooijer's avatar Bas de Nooijer

Updated loadbalancer plugin to use endpoints

Unittests not yet updated...
parent b76a148e
...@@ -3,16 +3,17 @@ require('init.php'); ...@@ -3,16 +3,17 @@ require('init.php');
htmlHeader(); htmlHeader();
// create a client instance and get loadbalancer plugin instance // create a client instance and create endpoints
$client = new Solarium\Client($config); $client = new Solarium\Client($config);
$loadbalancer = $client->getPlugin('loadbalancer'); $endpoint1 = $client->createEndpoint('local1'); //normally you would add endpoint specific settings...
$endpoint2 = $client->createEndpoint('local2');
$endpoint3 = $client->createEndpoint('local3');
// apply loadbalancer settings // get loadbalancer plugin instance and add endpoints
$optionsSolrOne = array('host' => '127.0.0.1', 'port' => 8983); $loadbalancer = $client->getPlugin('loadbalancer');
$optionsSolrTwo = array('host' => '127.0.0.1', 'port' => 7574); $loadbalancer->addEndpoint($endpoint1, 100);
$loadbalancer->addServer('solr1', $optionsSolrOne, 100); $loadbalancer->addEndpoint($endpoint2, 100);
$loadbalancer->addServer('solr2', $optionsSolrTwo, 200); $loadbalancer->addEndpoint($endpoint3, 1);
$loadbalancer->addServer('solr3', $optionsSolrTwo, 1);
// create a basic query to execute // create a basic query to execute
$query = $client->createSelect(); $query = $client->createSelect();
...@@ -22,26 +23,26 @@ for($i=1; $i<=8; $i++) { ...@@ -22,26 +23,26 @@ for($i=1; $i<=8; $i++) {
$resultset = $client->select($query); $resultset = $client->select($query);
echo 'Query execution #' . $i . '<br/>'; echo 'Query execution #' . $i . '<br/>';
echo 'NumFound: ' . $resultset->getNumFound(). '<br/>'; echo 'NumFound: ' . $resultset->getNumFound(). '<br/>';
echo 'Server: ' . $loadbalancer->getLastServerKey() .'<hr/>'; echo 'Server: ' . $loadbalancer->getLastEndpoint() .'<hr/>';
} }
// force a server for a query (normally solr 3 is extremely unlikely based on it's weight) // force a server for a query (normally solr 3 is extremely unlikely based on it's weight)
$loadbalancer->setForcedServerForNextQuery('solr3'); $loadbalancer->setForcedEndpointForNextQuery('local3');
$resultset = $client->select($query); $resultset = $client->select($query);
echo 'Query execution with server forced to solr3<br/>'; echo 'Query execution with server forced to local3<br/>';
echo 'NumFound: ' . $resultset->getNumFound(). '<br/>'; echo 'NumFound: ' . $resultset->getNumFound(). '<br/>';
echo 'Server: ' . $loadbalancer->getLastServerKey() .'<hr/>'; echo 'Server: ' . $loadbalancer->getLastEndpoint() .'<hr/>';
// test a ping query // test a ping query
$query = $client->createPing(); $query = $client->createPing();
$client->ping($query); $client->ping($query);
echo 'Loadbalanced ping query, should display a loadbalancing server:<br/>'; echo 'Loadbalanced ping query, should display a loadbalancing server:<br/>';
echo 'Ping server: ' . $loadbalancer->getLastServerKey() .'<hr/>'; echo 'Ping server: ' . $loadbalancer->getLastEndpoint() .'<hr/>';
// exclude ping query from loadbalancing // exclude ping query from loadbalancing
$loadbalancer->addBlockedQueryType(Solarium\Client::QUERY_PING); $loadbalancer->addBlockedQueryType(Solarium\Client::QUERY_PING);
$client->ping($query); $client->ping($query);
echo 'Non-loadbalanced ping query, should not display a loadbalancing server:<br/>'; echo 'Non-loadbalanced ping query, should not display a loadbalancing server:<br/>';
echo 'Ping server: ' . $loadbalancer->getLastServerKey() .'<hr/>'; echo 'Ping server: ' . $loadbalancer->getLastEndpoint() .'<hr/>';
htmlFooter(); htmlFooter();
\ No newline at end of file
...@@ -300,7 +300,7 @@ class Client extends Configurable ...@@ -300,7 +300,7 @@ class Client extends Configurable
* Get an endpoint by key * Get an endpoint by key
* *
* @param string $key * @param string $key
* @return string * @return Endpoint
*/ */
public function getEndpoint($key = null) public function getEndpoint($key = null)
{ {
......
...@@ -43,6 +43,7 @@ namespace Solarium\Plugin\Loadbalancer; ...@@ -43,6 +43,7 @@ namespace Solarium\Plugin\Loadbalancer;
use Solarium\Core\Plugin; use Solarium\Core\Plugin;
use Solarium\Core\Exception; use Solarium\Core\Exception;
use Solarium\Core\Client\Client; use Solarium\Core\Client\Client;
use Solarium\Core\Client\Endpoint;
use Solarium\Core\Client\HttpException; use Solarium\Core\Client\HttpException;
use Solarium\Core\Query\Query; use Solarium\Core\Query\Query;
use Solarium\Core\Client\Request; use Solarium\Core\Client\Request;
...@@ -52,14 +53,14 @@ use Solarium\Core\Client\Response; ...@@ -52,14 +53,14 @@ use Solarium\Core\Client\Response;
* Loadbalancer plugin * Loadbalancer plugin
* *
* Using this plugin you can use software loadbalancing over multiple Solr instances. * Using this plugin you can use software loadbalancing over multiple Solr instances.
* You can add any number of servers, each with their own weight. The weight influences * You can add any number of endpoints, each with their own weight. The weight influences
* the probability of a server being used for a query. * the probability of a endpoint being used for a query.
* *
* By default all queries except updates are loadbalanced. This can be customized by setting blocked querytypes. * By default all queries except updates are loadbalanced. This can be customized by setting blocked querytypes.
* Any querytype that may not be loadbalanced will be executed by Solarium with the default adapter settings. * Any querytype that may not be loadbalanced will be executed by Solarium with the default endpoint.
* In a master-slave setup the default adapter should be connecting to the master server. * In a master-slave setup the default endpoint should be connecting to the master endpoint.
* *
* You can also enable the failover mode. In this case a query will be retried on another server in case of error. * You can also enable the failover mode. In this case a query will be retried on another endpoint in case of error.
* *
* @package Solarium * @package Solarium
* @subpackage Plugin * @subpackage Plugin
...@@ -78,11 +79,11 @@ class Loadbalancer extends Plugin ...@@ -78,11 +79,11 @@ class Loadbalancer extends Plugin
); );
/** /**
* Registered servers * Registered endpoints
* *
* @var array * @var array
*/ */
protected $servers = array(); protected $endpoints = array();
/** /**
* Query types that are blocked from loadbalancing * Query types that are blocked from loadbalancing
...@@ -94,33 +95,33 @@ class Loadbalancer extends Plugin ...@@ -94,33 +95,33 @@ class Loadbalancer extends Plugin
); );
/** /**
* Key of the last used server * Last used endpoint key
* *
* The value can be null if no queries have been executed, or if the last executed query didn't use loadbalancing. * The value can be null if no queries have been executed, or if the last executed query didn't use loadbalancing.
* *
* @var null|string * @var null|string
*/ */
protected $lastServerKey; protected $lastEndpoint;
/** /**
* Server to use for next query (overrules randomizer) * Endpoint key to use for next query (overrules randomizer)
* *
* @var string * @var string
*/ */
protected $nextServer; protected $nextEndpoint;
/** /**
* Presets of the client adapter * Default endpoint key
* *
* These settings are used to restore the adapter to it's original status for queries * This endpoint is used for queries that cannot be loadbalanced
* that cannot be loadbalanced (for instance update queries that need to go to the master) * (for instance update queries that need to go to the master)
* *
* @var array * @var string
*/ */
protected $adapterPresets; protected $defaultEndpoint;
/** /**
* Pool of servers to use for requests * Pool of endpoint keys to use for requests
* *
* @var WeightedRandomChoice * @var WeightedRandomChoice
*/ */
...@@ -138,7 +139,7 @@ class Loadbalancer extends Plugin ...@@ -138,7 +139,7 @@ class Loadbalancer extends Plugin
* *
* @var array * @var array
*/ */
protected $serverExcludes; protected $endpointExcludes;
/** /**
* Initialize options * Initialize options
...@@ -152,8 +153,8 @@ class Loadbalancer extends Plugin ...@@ -152,8 +153,8 @@ class Loadbalancer extends Plugin
{ {
foreach ($this->options AS $name => $value) { foreach ($this->options AS $name => $value) {
switch ($name) { switch ($name) {
case 'server': case 'endpoint':
$this->setServers($value); $this->setEndpoints($value);
break; break;
case 'blockedquerytype': case 'blockedquerytype':
$this->setBlockedQueryTypes($value); $this->setBlockedQueryTypes($value);
...@@ -205,138 +206,131 @@ class Loadbalancer extends Plugin ...@@ -205,138 +206,131 @@ class Loadbalancer extends Plugin
} }
/** /**
* Add a server to the loadbalacing 'pool' * Add an endpoint to the loadbalacing 'pool'
* *
* @param string $key * @param Endpoint|string $endpoint
* @param array $options
* @param int $weight Must be a positive number * @param int $weight Must be a positive number
* @return self Provides fluent interface * @return self Provides fluent interface
*/ */
public function addServer($key, $options, $weight = 1) public function addEndpoint($endpoint, $weight = 1)
{ {
if (array_key_exists($key, $this->servers)) { if(!is_string($endpoint)) {
throw new Exception('A server for the loadbalancer plugin must have a unique key'); $endpoint = $endpoint->getKey();
}
if (array_key_exists($endpoint, $this->endpoints)) {
throw new Exception('An endpoint for the loadbalancer plugin must have a unique key');
} else { } else {
$this->servers[$key] = array( $this->endpoints[$endpoint] = $weight;
'options' => $options,
'weight' => $weight,
);
} }
// reset the randomizer as soon as a new server is added // reset the randomizer as soon as a new endpoint is added
$this->randomizer = null; $this->randomizer = null;
return $this; return $this;
} }
/** /**
* Get servers in the loadbalancing pool * Add multiple endpoints
* *
* @return array * @param array $endpoints
* @return self Provides fluent interface
*/ */
public function getServers() public function addEndpoints(array $endpoints)
{ {
return $this->servers; foreach ($endpoints AS $endpoint => $weight) {
$this->addEndpoint($endpoint, $weight);
}
return $this;
} }
/** /**
* Get a server entry by key * Get the endpoints in the loadbalancing pool
* *
* @param string $key
* @return array * @return array
*/ */
public function getServer($key) public function getEndpoints()
{ {
if (!isset($this->servers[$key])) { return $this->endpoints;
throw new Exception('Unknown server key');
}
return $this->servers[$key];
} }
/** /**
* Set servers, overwriting any existing servers * Clear all endpoint entries
* *
* @param array $servers Use server key as array key and 'options' and 'weight' as array entries
* @return self Provides fluent interface * @return self Provides fluent interface
*/ */
public function setServers($servers) public function clearEndpoints()
{ {
$this->clearServers(); $this->endpoints = array();
$this->addServers($servers);
return $this;
} }
/** /**
* Add multiple servers * Remove an endpoint by key
* *
* @param array $servers * @param Endpoint|string $endpoint
* @return self Provides fluent interface * @return self Provides fluent interface
*/ */
public function addServers($servers) public function removeEndpoint($endpoint)
{ {
foreach ($servers AS $key => $data) { if(!is_string($endpoint)) {
$this->addServer($key, $data['options'], $data['weight']); $endpoint = $endpoint->getKey();
} }
return $this; if (isset($this->endpoints[$endpoint])) {
unset($this->endpoints[$endpoint]);
} }
/** return $this;
* Clear all server entries
*
* @return self Provides fluent interface
*/
public function clearServers()
{
$this->servers = array();
} }
/** /**
* Remove a server by key * Set multiple endpoints
* *
* @param string $key * This overwrites any existing endpoints
* @return self Provides fluent interface *
* @param array $endpoints
*/ */
public function removeServer($key) public function setEndpoints($endpoints)
{ {
if (isset($this->servers[$key])) { $this->clearEndpoints();
unset($this->servers[$key]); $this->addEndpoints($endpoints);
}
return $this;
} }
/** /**
* Set a forced server (by key) for the next request * Set a forced endpoints (by key) for the next request
* *
* As soon as one query has used the forced server this setting is reset. If you want to remove this setting * As soon as one query has used the forced endpoint this setting is reset. If you want to remove this setting
* pass NULL as the key value. * pass NULL as the key value.
* *
* If the next query cannot be loadbalanced (for instance based on the querytype) this setting is ignored * If the next query cannot be loadbalanced (for instance based on the querytype) this setting is ignored
* but will still be reset. * but will still be reset.
* *
* @param string|null $key * @param string|null|Endpoint $endpoint
* @return self Provides fluent interface * @return self Provides fluent interface
*/ */
public function setForcedServerForNextQuery($key) public function setForcedEndpointForNextQuery($endpoint)
{ {
if ($key !== null && !array_key_exists($key, $this->servers)) { if(!is_string($endpoint)) {
throw new Exception('Unknown server forced for next query'); $endpoint = $endpoint->getKey();
}
if ($endpoint !== null && !array_key_exists($endpoint, $this->endpoints)) {
throw new Exception('Unknown endpoint forced for next query');
} }
$this->nextServer = $key; $this->nextEndpoint = $endpoint;
return $this; return $this;
} }
/** /**
* Get the ForcedServerForNextQuery value * Get the ForcedEndpointForNextQuery value
* *
* @return string|null * @return string|null
*/ */
public function getForcedServerForNextQuery() public function getForcedEndpointForNextQuery()
{ {
return $this->nextServer; return $this->nextEndpoint;
} }
/** /**
...@@ -418,15 +412,15 @@ class Loadbalancer extends Plugin ...@@ -418,15 +412,15 @@ class Loadbalancer extends Plugin
} }
/** /**
* Get the key of the server that was used for the last query * Get the key of the endpoint that was used for the last query
* *
* May return a null value if no query has been executed yet, or the last query could not be loadbalanced. * May return a null value if no query has been executed yet, or the last query could not be loadbalanced.
* *
* @return null|string * @return null|string
*/ */
public function getLastServerKey() public function getLastEndpoint()
{ {
return $this->lastServerKey; return $this->lastEndpoint;
} }
/** /**
...@@ -451,28 +445,19 @@ class Loadbalancer extends Plugin ...@@ -451,28 +445,19 @@ class Loadbalancer extends Plugin
$adapter = $this->client->getAdapter(); $adapter = $this->client->getAdapter();
// save adapter presets (once) to allow the settings to be restored later // save adapter presets (once) to allow the settings to be restored later
if ($this->adapterPresets == null) { if ($this->defaultEndpoint == null) {
$this->adapterPresets = array( $this->defaultEndpoint = $this->client->getEndpoint()->getKey();
'host' => $adapter->getHost(),
'port' => $adapter->getPort(),
'path' => $adapter->getPath(),
'core' => $adapter->getCore(),
'timeout' => $adapter->getTimeout(),
);
} }
// check querytype: is loadbalancing allowed? // check querytype: is loadbalancing allowed?
if (!array_key_exists($this->queryType, $this->blockedQueryTypes)) { if (!array_key_exists($this->queryType, $this->blockedQueryTypes)) {
return $this->getLoadbalancedResponse($request); return $this->getLoadbalancedResponse($request);
} else { } else {
$options = $this->adapterPresets; $endpoint = $this->client->getEndpoint($this->defaultEndpoint);
$this->lastServerKey = null; $this->lastEndpoint = null;
// apply new settings to adapter
$adapter->setOptions($options);
// execute request and return result // execute request and return result
return $adapter->execute($request); return $adapter->execute($request, $endpoint);
} }
} }
...@@ -484,21 +469,19 @@ class Loadbalancer extends Plugin ...@@ -484,21 +469,19 @@ class Loadbalancer extends Plugin
*/ */
protected function getLoadbalancedResponse($request) protected function getLoadbalancedResponse($request)
{ {
$this->serverExcludes = array(); // reset for each query $this->endpointExcludes = array(); // reset for each query
$adapter = $this->client->getAdapter(); $adapter = $this->client->getAdapter();
if ($this->getFailoverEnabled() == true) { if ($this->getFailoverEnabled() == true) {
for ($i=0; $i<=$this->getFailoverMaxRetries(); $i++) { for ($i=0; $i<=$this->getFailoverMaxRetries(); $i++) {
$options = $this->getRandomServerOptions(); $endpoint = $this->getRandomEndpoint();
$adapter->setOptions($options);
try { try {
return $adapter->execute($request); return $adapter->execute($request, $endpoint);
} catch(HttpException $e) { } catch(HttpException $e) {
// ignore HTTP errors and try again // ignore HTTP errors and try again
// but do issue an event for things like logging // but do issue an event for things like logging
$e = new Exception('Maximum number of loadbalancer retries reached'); $this->client->triggerEvent('LoadbalancerEndpointFail', array($endpoint->getOptions(), $e));
$this->client->triggerEvent('LoadbalancerServerFail', array($options, $e));
} }
} }
...@@ -508,31 +491,30 @@ class Loadbalancer extends Plugin ...@@ -508,31 +491,30 @@ class Loadbalancer extends Plugin
} else { } else {
// no failover retries, just execute and let an exception bubble upwards // no failover retries, just execute and let an exception bubble upwards
$options = $this->getRandomServerOptions(); $endpoint = $this->getRandomEndpoint();
$adapter->setOptions($options); return $adapter->execute($request, $endpoint);
return $adapter->execute($request);
} }
} }
/** /**
* Get options array for a randomized server * Get a random endpoint
* *
* @return array * @return Endpoint
*/ */
protected function getRandomServerOptions() protected function getRandomEndpoint()
{ {
// determine the server to use // determine the endpoint to use
if ($this->nextServer !== null) { if ($this->nextEndpoint !== null) {
$serverKey = $this->nextServer; $key = $this->nextEndpoint;
// reset forced server directly after use // reset forced endpoint directly after use
$this->nextServer = null; $this->nextEndpoint = null;
} else { } else {
$serverKey = $this->getRandomizer()->getRandom($this->serverExcludes); $key = $this->getRandomizer()->getRandom($this->endpointExcludes);
} }
$this->serverExcludes[] = $serverKey; $this->endpointExcludes[] = $key;
$this->lastServerKey = $serverKey; $this->lastEndpoint = $key;
return $this->servers[$serverKey]['options']; return $this->client->getEndpoint($key);
} }
/** /**
...@@ -543,11 +525,7 @@ class Loadbalancer extends Plugin ...@@ -543,11 +525,7 @@ class Loadbalancer extends Plugin
protected function getRandomizer() protected function getRandomizer()
{ {
if ($this->randomizer === null) { if ($this->randomizer === null) {
$choices = array(); $this->randomizer = new WeightedRandomChoice($this->endpoints);
foreach ($this->servers AS $key => $settings) {
$choices[$key] = $settings['weight'];
}
$this->randomizer = new WeightedRandomChoice($choices);
} }
return $this->randomizer; return $this->randomizer;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment