source: sandbox/expresso-solr/expressoMail1_2/solrclient/library/Solarium/Plugin/Loadbalancer.php @ 7588

Revision 7588, 15.0 KB checked in by adir, 11 years ago (diff)

Ticket #000 - Adicionando a integracao de buscas com Solr na base a ser isnerida na comunidade

Line 
1<?php
2/**
3 * Copyright 2011 Bas de Nooijer. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 *    this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 *    this listof conditions and the following disclaimer in the documentation
13 *    and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 *
27 * The views and conclusions contained in the software and documentation are
28 * those of the authors and should not be interpreted as representing official
29 * policies, either expressed or implied, of the copyright holder.
30 *
31 * @copyright Copyright 2011 Bas de Nooijer <solarium@raspberry.nl>
32 * @license http://github.com/basdenooijer/solarium/raw/master/COPYING
33 * @link http://www.solarium-project.org/
34 *
35 * @package Solarium
36 */
37
38/**
39 * Loadbalancer plugin
40 *
41 * Using this plugin you can use software loadbalancing over multiple Solr instances.
42 * You can add any number of servers, each with their own weight. The weight influences
43 * the probability of a server being used for a query.
44 *
45 * By default all queries except updates are loadbalanced. This can be customized by setting blocked querytypes.
46 * Any querytype that may not be loadbalanced will be executed by Solarium with the default adapter settings.
47 * In a master-slave setup the default adapter should be connecting to the master server.
48 *
49 * You can also enable the failover mode. In this case a query will be retried on another server in case of error.
50 *
51 * @package Solarium
52 * @subpackage Plugin
53 */
54class Solarium_Plugin_Loadbalancer extends Solarium_Plugin_Abstract
55{
56
57    /**
58     * Default options
59     *
60     * @var array
61     */
62    protected $_options = array(
63        'failoverenabled' => false,
64        'failovermaxretries' => 1,
65    );
66
67    /**
68     * Registered servers
69     *
70     * @var array
71     */
72    protected $_servers = array();
73
74    /**
75     * Query types that are blocked from loadbalancing
76     *
77     * @var array
78     */
79    protected $_blockedQueryTypes = array(
80        Solarium_Client::QUERYTYPE_UPDATE => true
81    );
82
83    /**
84     * Key of the last used server
85     *
86     * The value can be null if no queries have been executed, or if the last executed query didn't use loadbalancing.
87     *
88     * @var null|string
89     */
90    protected $_lastServerKey;
91
92    /**
93     * Server to use for next query (overrules randomizer)
94     *
95     * @var string
96     */
97    protected $_nextServer;
98
99    /**
100     * Presets of the client adapter
101     *
102     * These settings are used to restore the adapter to it's original status for queries
103     * that cannot be loadbalanced (for instance update queries that need to go to the master)
104     *
105     * @var array
106     */
107    protected $_adapterPresets;
108
109    /**
110     * Pool of servers to use for requests
111     *
112     * @var Solarium_Plugin_Loadbalancer_WeightedRandomChoice
113     */
114    protected $_randomizer;
115
116    /**
117     * Query type
118     *
119     * @var string
120     */
121    protected $_queryType;
122
123    /**
124     * Used for failover mechanism
125     *
126     * @var array
127     */
128    protected $_serverExcludes;
129
130    /**
131     * Initialize options
132     *
133     * Several options need some extra checks or setup work, for these options
134     * the setters are called.
135     *
136     * @return void
137     */
138    protected function _init()
139    {
140        foreach ($this->_options AS $name => $value) {
141            switch ($name) {
142                case 'server':
143                    $this->setServers($value);
144                    break;
145                case 'blockedquerytype':
146                    $this->setBlockedQueryTypes($value);
147                    break;
148            }
149        }
150    }
151
152    /**
153     * Set failover enabled option
154     *
155     * @param bool $value
156     * @return self Provides fluent interface
157     */
158    public function setFailoverEnabled($value)
159    {
160        return $this->_setOption('failoverenabled', $value);
161    }
162
163    /**
164     * Get failoverenabled option
165     *
166     * @return boolean
167     */
168    public function getFailoverEnabled()
169    {
170        return $this->getOption('failoverenabled');
171    }
172
173    /**
174     * Set failover max retries
175     *
176     * @param int $value
177     * @return self Provides fluent interface
178     */
179    public function setFailoverMaxRetries($value)
180    {
181        return $this->_setOption('failovermaxretries', $value);
182    }
183
184    /**
185     * Get failovermaxretries option
186     *
187     * @return int
188     */
189    public function getFailoverMaxRetries()
190    {
191        return $this->getOption('failovermaxretries');
192    }
193
194    /**
195     * Add a server to the loadbalacing 'pool'
196     *
197     * @param string $key
198     * @param array $options
199     * @param int $weight Must be a positive number
200     * @return self Provides fluent interface
201     */
202    public function addServer($key, $options, $weight = 1)
203    {
204        if (array_key_exists($key, $this->_servers)) {
205            throw new Solarium_Exception('A server for the loadbalancer plugin must have a unique key');
206        } else {
207            $this->_servers[$key] = array(
208                'options' => $options,
209                'weight' => $weight,
210            );
211        }
212
213        // reset the randomizer as soon as a new server is added
214        $this->_randomizer = null;
215
216        return $this;
217    }
218
219    /**
220     * Get servers in the loadbalancing pool
221     *
222     * @return array
223     */
224    public function getServers()
225    {
226        return $this->_servers;
227    }
228
229    /**
230     * Get a server entry by key
231     *
232     * @param string $key
233     * @return array
234     */
235    public function getServer($key)
236    {
237        if (!isset($this->_servers[$key])) {
238            throw new Solarium_Exception('Unknown server key');
239        }
240
241        return $this->_servers[$key];
242    }
243
244    /**
245     * Set servers, overwriting any existing servers
246     *
247     * @param array $servers Use server key as array key and 'options' and 'weight' as array entries
248     * @return self Provides fluent interface
249     */
250    public function setServers($servers)
251    {
252        $this->clearServers();
253        $this->addServers($servers);
254        return $this;
255    }
256
257    /**
258     * Add multiple servers
259     *
260     * @param array $servers
261     * @return self Provides fluent interface
262     */
263    public function addServers($servers)
264    {
265        foreach ($servers AS $key => $data) {
266            $this->addServer($key, $data['options'], $data['weight']);
267        }
268
269        return $this;
270    }
271
272    /**
273     * Clear all server entries
274     *
275     * @return self Provides fluent interface
276     */
277    public function clearServers()
278    {
279        $this->_servers = array();
280    }
281
282    /**
283     * Remove a server by key
284     *
285     * @param string $key
286     * @return self Provides fluent interface
287     */
288    public function removeServer($key)
289    {
290        if (isset($this->_servers[$key])) {
291            unset($this->_servers[$key]);
292        }
293
294        return $this;
295    }
296
297    /**
298     * Set a forced server (by key) for the next request
299     *
300     * As soon as one query has used the forced server this setting is reset. If you want to remove this setting
301     * pass NULL as the key value.
302     *
303     * If the next query cannot be loadbalanced (for instance based on the querytype) this setting is ignored
304     * but will still be reset.
305     *
306     * @param string|null $key
307     * @return self Provides fluent interface
308     */
309    public function setForcedServerForNextQuery($key)
310    {
311        if ($key !== null && !array_key_exists($key, $this->_servers)) {
312            throw new Solarium_Exception('Unknown server forced for next query');
313        }
314
315        $this->_nextServer = $key;
316        return $this;
317    }
318
319    /**
320     * Get the ForcedServerForNextQuery value
321     *
322     * @return string|null
323     */
324    public function getForcedServerForNextQuery()
325    {
326        return $this->_nextServer;
327    }
328
329    /**
330     * Get an array of blocked querytypes
331     *
332     * @return array
333     */
334    public function getBlockedQueryTypes()
335    {
336        return array_keys($this->_blockedQueryTypes);
337    }
338
339    /**
340     * Set querytypes to block from loadbalancing
341     *
342     * Overwrites any existing types
343     *
344     * @param array $types Use an array with the constants defined in Solarium_Client as values
345     * @return self Provides fluent interface
346     */
347    public function setBlockedQueryTypes($types)
348    {
349        $this->clearBlockedQueryTypes();
350        $this->addBlockedQueryTypes($types);
351        return $this;
352    }
353
354    /**
355     * Add a querytype to block from loadbalancing
356     *
357     * @param string $type Use one of the constants defined in Solarium_Client
358     * @return self Provides fluent interface
359     */
360    public function addBlockedQueryType($type)
361    {
362        if (!array_key_exists($type, $this->_blockedQueryTypes)) {
363            $this->_blockedQueryTypes[$type] = true;
364        }
365
366        return $this;
367    }
368
369    /**
370     * Add querytypes to block from loadbalancing
371     *
372     * Appended to any existing types
373     *
374     * @param array $types Use an array with the constants defined in Solarium_Client as values
375     * @return self Provides fluent interface
376     */
377    public function addBlockedQueryTypes($types)
378    {
379        foreach ($types AS $type) {
380            $this->addBlockedQueryType($type);
381        }
382    }
383
384    /**
385     * Remove a single querytype from the block list
386     *
387     * @param string $type
388     * @return void
389     */
390    public function removeBlockedQueryType($type)
391    {
392        if (array_key_exists($type, $this->_blockedQueryTypes)) {
393            unset($this->_blockedQueryTypes[$type]);
394        }
395    }
396
397    /**
398     * Clear all blocked querytypes
399     *
400     * @return self Provides fluent interface
401     */
402    public function clearBlockedQueryTypes()
403    {
404        $this->_blockedQueryTypes = array();
405    }
406
407    /**
408     * Get the key of the server that was used for the last query
409     *
410     * May return a null value if no query has been executed yet, or the last query could not be loadbalanced.
411     *
412     * @return null|string
413     */
414    public function getLastServerKey()
415    {
416        return $this->_lastServerKey;
417    }
418
419    /**
420     * Event hook to capture querytype
421     *
422     * @param Solarium_Query $query
423     * @return void
424     */
425    public function preCreateRequest($query)
426    {
427        $this->_queryType = $query->getType();
428    }
429
430    /**
431     * Event hook to adjust client settings just before query execution
432     *
433     * @param Solarium_Client_Request $request
434     * @return Solarium_Client_Response
435     */
436    public function preExecuteRequest($request)
437    {
438        $adapter = $this->_client->getAdapter();
439
440        // save adapter presets (once) to allow the settings to be restored later
441        if ($this->_adapterPresets == null) {
442            $this->_adapterPresets = array(
443                'host'    => $adapter->getHost(),
444                'port'    => $adapter->getPort(),
445                'path'    => $adapter->getPath(),
446                'core'    => $adapter->getCore(),
447                'timeout' => $adapter->getTimeout(),
448            );
449        }
450
451        // check querytype: is loadbalancing allowed?
452        if (!array_key_exists($this->_queryType, $this->_blockedQueryTypes)) {
453            return  $this->_getLoadbalancedResponse($request);
454        } else {
455            $options = $this->_adapterPresets;
456            $this->_lastServerKey = null;
457
458            // apply new settings to adapter
459            $adapter->setOptions($options);
460
461            // execute request and return result
462            return $adapter->execute($request);
463        }
464    }
465
466    /**
467     * Execute a request using the adapter
468     *
469     * @param Solarium_Client_Request $request
470     * @return Solarium_Client_Response $response
471     */
472    protected function _getLoadbalancedResponse($request)
473    {
474        $this->_serverExcludes = array(); // reset for each query
475        $adapter = $this->_client->getAdapter();
476
477        if ($this->getFailoverEnabled() == true) {
478
479            for ($i=0; $i<=$this->getFailoverMaxRetries(); $i++) {
480                $options = $this->_getRandomServerOptions();
481                $adapter->setOptions($options);
482                try {
483                    return $adapter->execute($request);
484                } catch(Solarium_Client_HttpException $e) {
485                    // ignore HTTP errors and try again
486                    // but do issue an event for things like logging
487                    $e = new Solarium_Exception('Maximum number of loadbalancer retries reached');
488                    $this->_client->triggerEvent('LoadbalancerServerFail', array($options, $e));
489                }
490            }
491
492            // if we get here no more retries available, throw exception
493            $e = new Solarium_Exception('Maximum number of loadbalancer retries reached');
494            throw $e;
495
496        } else {
497            // no failover retries, just execute and let an exception bubble upwards
498            $options = $this->_getRandomServerOptions();
499            $adapter->setOptions($options);
500            return $adapter->execute($request);
501        }
502    }
503
504    /**
505     * Get options array for a randomized server
506     *
507     * @return array
508     */
509    protected function _getRandomServerOptions()
510    {
511        // determine the server to use
512        if ($this->_nextServer !== null) {
513            $serverKey = $this->_nextServer;
514            // reset forced server directly after use
515            $this->_nextServer = null;
516        } else {
517            $serverKey = $this->_getRandomizer()->getRandom($this->_serverExcludes);
518        }
519
520        $this->_serverExcludes[] = $serverKey;
521        $this->_lastServerKey = $serverKey;
522        return $this->_servers[$serverKey]['options'];
523    }
524
525    /**
526     * Get randomizer instance
527     *
528     * @return Solarium_Plugin_Loadbalancer_WeightedRandomChoice
529     */
530    protected function _getRandomizer()
531    {
532        if ($this->_randomizer === null) {
533            $choices = array();
534            foreach ($this->_servers AS $key => $settings) {
535                $choices[$key] = $settings['weight'];
536            }
537            $this->_randomizer = new Solarium_Plugin_Loadbalancer_WeightedRandomChoice($choices);
538        }
539
540        return $this->_randomizer;
541    }
542
543}
Note: See TracBrowser for help on using the repository browser.