Source for file Balancer.php

Documentation is available at Balancer.php

  1. <?php
  2. /**
  3.  * @copyright Copyright 2007 Conduit Internet Technologies, Inc. (http://conduit-it.com)
  4.  * @license Apache Licence, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
  5.  *
  6.  *  Licensed under the Apache License, Version 2.0 (the "License");
  7.  *  you may not use this file except in compliance with the License.
  8.  *  You may obtain a copy of the License at
  9.  *
  10.  *      http://www.apache.org/licenses/LICENSE-2.0
  11.  *
  12.  *  Unless required by applicable law or agreed to in writing, software
  13.  *  distributed under the License is distributed on an "AS IS" BASIS,
  14.  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15.  *  See the License for the specific language governing permissions and
  16.  *  limitations under the License.
  17.  *
  18.  * @package Apache
  19.  * @subpackage Solr
  20.  * @author Donovan Jimenez <djimenez@conduit-it.com>, Dan Wolfe
  21.  */
  22.  
  23. require_once('Apache/Solr/Service.php');
  24.  
  25. /**
  26.  * Reference Implementation for using multiple Solr services in a distribution. Functionality
  27.  * includes:
  28.  *     routing of read / write operations
  29.  *     failover (on selection) for multiple read servers
  30.  */
  31. {
  32.     protected $_createDocuments = true;
  33.  
  34.     protected $_readableServices = array();
  35.     protected $_writeableServices = array();
  36.  
  37.     protected $_currentReadService = null;
  38.     protected $_currentWriteService = null;
  39.  
  40.     protected $_readPingTimeout = 2;
  41.     protected $_writePingTimeout = 4;
  42.     
  43.     // Configuration for server selection backoff intervals
  44.     protected $_useBackoff = false;        // Set to true to use more resillient write server selection
  45.     protected $_backoffLimit = 600;        // 10 minute default maximum
  46.     protected $_backoffEscalation = 2.0;     // Rate at which to increase backoff period
  47.     protected $_defaultBackoff = 2.0;        // Default backoff interval
  48.  
  49.     /**
  50.      * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
  51.      *
  52.      * NOTE: inside a phrase fewer characters need escaped, use {@link Apache_Solr_Service::escapePhrase()} instead
  53.      *
  54.      * @param string $value 
  55.      * @return string 
  56.      */
  57.     static public function escape($value)
  58.     {
  59.         return Apache_Solr_Service::escape($value);
  60.     }
  61.  
  62.     /**
  63.      * Escape a value meant to be contained in a phrase for special query characters
  64.      *
  65.      * @param string $value 
  66.      * @return string 
  67.      */
  68.     static public function escapePhrase($value)
  69.     {
  70.         return Apache_Solr_Service::escapePhrase($value);
  71.     }
  72.  
  73.     /**
  74.      * Convenience function for creating phrase syntax from a value
  75.      *
  76.      * @param string $value 
  77.      * @return string 
  78.      */
  79.     static public function phrase($value)
  80.     {
  81.         return Apache_Solr_Service::phrase($value);
  82.     }
  83.  
  84.     /**
  85.      * Constructor. Takes arrays of read and write service instances or descriptions
  86.      *
  87.      * @param array $readableServices 
  88.      * @param array $writeableServices 
  89.      */
  90.     public function __construct($readableServices array()$writeableServices array())
  91.     {
  92.         //setup readable services
  93.         foreach ($readableServices as $service)
  94.         {
  95.             $this->addReadService($service);
  96.         }
  97.  
  98.         //setup writeable services
  99.         foreach ($writeableServices as $service)
  100.         {
  101.             $this->addWriteService($service);
  102.         }
  103.     }
  104.  
  105.     public function setReadPingTimeout($timeout)
  106.     {
  107.         $this->_readPingTimeout = $timeout;
  108.     }
  109.  
  110.     public function setWritePingTimeout($timeout)
  111.     {
  112.         $this->_writePingTimeout = $timeout;
  113.     }
  114.     
  115.     public function setUseBackoff($enable)
  116.     {
  117.         $this->_useBackoff = $enable;
  118.     }
  119.  
  120.     /**
  121.      * Generates a service ID
  122.      *
  123.      * @param string $host 
  124.      * @param integer $port 
  125.      * @param string $path 
  126.      * @return string 
  127.      */
  128.     protected function _getServiceId($host$port$path)
  129.     {
  130.         return $host ':' $port $path;
  131.     }
  132.  
  133.     /**
  134.      * Adds a service instance or service descriptor (if it is already
  135.      * not added)
  136.      *
  137.      * @param mixed $service 
  138.      *
  139.      * @throws Exception If service descriptor is not valid
  140.      */
  141.     public function addReadService($service)
  142.     {
  143.         if ($service instanceof Apache_Solr_Service)
  144.         {
  145.             $id $this->_getServiceId($service->getHost()$service->getPort()$service->getPath());
  146.  
  147.             $this->_readableServices[$id$service;
  148.         }
  149.         else if (is_array($service))
  150.         {
  151.             if (isset($service['host']&& isset($service['port']&& isset($service['path']))
  152.             {
  153.                 $id $this->_getServiceId((string)$service['host'](int)$service['port'](string)$service['path']);
  154.  
  155.                 $this->_readableServices[$id$service;
  156.             }
  157.             else
  158.             {
  159.                 throw new Exception('A Readable Service description array does not have all required elements of host, port, and path');
  160.             }
  161.         }
  162.     }
  163.  
  164.     /**
  165.      * Removes a service instance or descriptor from the available services
  166.      *
  167.      * @param mixed $service 
  168.      *
  169.      * @throws Exception If service descriptor is not valid
  170.      */
  171.     public function removeReadService($service)
  172.     {
  173.         $id '';
  174.  
  175.         if ($service instanceof Apache_Solr_Service)
  176.         {
  177.             $id $this->_getServiceId($service->getHost()$service->getPort()$service->getPath());
  178.         }
  179.         else if (is_array($service))
  180.         {
  181.             if (isset($service['host']&& isset($service['port']&& isset($service['path']))
  182.             {
  183.                 $id $this->_getServiceId((string)$service['host'](int)$service['port'](string)$service['path']);
  184.             }
  185.             else
  186.             {
  187.                 throw new Exception('A Readable Service description array does not have all required elements of host, port, and path');
  188.             }
  189.         }
  190.         else if (is_string($service))
  191.         {
  192.             $id $service;
  193.         }
  194.  
  195.         if ($id && isset($this->_readableServices[$id]))
  196.         {
  197.             unset($this->_readableServices[$id]);
  198.         }
  199.     }
  200.  
  201.     /**
  202.      * Adds a service instance or service descriptor (if it is already
  203.      * not added)
  204.      *
  205.      * @param mixed $service 
  206.      *
  207.      * @throws Exception If service descriptor is not valid
  208.      */
  209.     public function addWriteService($service)
  210.     {
  211.         if ($service instanceof Apache_Solr_Service)
  212.         {
  213.             $id $this->_getServiceId($service->getHost()$service->getPort()$service->getPath());
  214.  
  215.             $this->_writeableServices[$id$service;
  216.         }
  217.         else if (is_array($service))
  218.         {
  219.             if (isset($service['host']&& isset($service['port']&& isset($service['path']))
  220.             {
  221.                 $id $this->_getServiceId((string)$service['host'](int)$service['port'](string)$service['path']);
  222.  
  223.                 $this->_writeableServices[$id$service;
  224.             }
  225.             else
  226.             {
  227.                 throw new Exception('A Writeable Service description array does not have all required elements of host, port, and path');
  228.             }
  229.         }
  230.     }
  231.  
  232.     /**
  233.      * Removes a service instance or descriptor from the available services
  234.      *
  235.      * @param mixed $service 
  236.      *
  237.      * @throws Exception If service descriptor is not valid
  238.      */
  239.     public function removeWriteService($service)
  240.     {
  241.         $id '';
  242.  
  243.         if ($service instanceof Apache_Solr_Service)
  244.         {
  245.             $id $this->_getServiceId($service->getHost()$service->getPort()$service->getPath());
  246.         }
  247.         else if (is_array($service))
  248.         {
  249.             if (isset($service['host']&& isset($service['port']&& isset($service['path']))
  250.             {
  251.                 $id $this->_getServiceId((string)$service['host'](int)$service['port'](string)$service['path']);
  252.             }
  253.             else
  254.             {
  255.                 throw new Exception('A Readable Service description array does not have all required elements of host, port, and path');
  256.             }
  257.         }
  258.         else if (is_string($service))
  259.         {
  260.             $id $service;
  261.         }
  262.  
  263.         if ($id && isset($this->_writeableServices[$id]))
  264.         {
  265.             unset($this->_writeableServices[$id]);
  266.         }
  267.     }
  268.  
  269.     /**
  270.      * Iterate through available read services and select the first with a ping
  271.      * that satisfies configured timeout restrictions (or the default)
  272.      *
  273.      * @return Apache_Solr_Service 
  274.      *
  275.      * @throws Exception If there are no read services that meet requirements
  276.      */
  277.     protected function _selectReadService($forceSelect false)
  278.     {
  279.         if (!$this->_currentReadService || !isset($this->_readableServices[$this->_currentReadService]|| $forceSelect)
  280.         {
  281.             if ($this->_currentReadService && isset($this->_readableServices[$this->_currentReadService]&& $forceSelect)
  282.             {
  283.                 // we probably had a communication error, ping the current read service, remove it if it times out
  284.                 if ($this->_readableServices[$this->_currentReadService]->ping($this->_readPingTimeout=== false)
  285.                 {
  286.                     $this->removeReadService($this->_currentReadService);
  287.                 }
  288.             }
  289.             
  290.             if (count($this->_readableServices))
  291.             {
  292.                 // select one of the read services at random
  293.                 $ids array_keys($this->_readableServices);
  294.                 
  295.                 $id $ids[rand(0count($ids1)];
  296.                 $service $this->_readableServices[$id];
  297.             
  298.                 if (is_array($service))
  299.                 {
  300.                     //convert the array definition to a client object
  301.                     $service new Apache_Solr_Service($service['host']$service['port']$service['path']);
  302.                     $this->_readableServices[$id$service;
  303.                 }
  304.                 
  305.                 $service->setCreateDocuments($this->_createDocuments);
  306.                 $this->_currentReadService = $id;
  307.             }
  308.             else
  309.             {
  310.                 throw new Exception('No read services were available');
  311.             }
  312.         }
  313.  
  314.         return $this->_readableServices[$this->_currentReadService];
  315.     }
  316.  
  317.     /**
  318.      * Iterate through available write services and select the first with a ping
  319.      * that satisfies configured timeout restrictions (or the default)
  320.      *
  321.      * @return Apache_Solr_Service 
  322.      *
  323.      * @throws Exception If there are no write services that meet requirements
  324.      */
  325.     protected function _selectWriteService($forceSelect false)
  326.     {
  327.         if($this->_useBackoff)
  328.         {
  329.             return $this->_selectWriteServiceSafe($forceSelect);
  330.         }        
  331.         
  332.         if (!$this->_currentWriteService || !isset($this->_writeableServices[$this->_currentWriteService]|| $forceSelect)
  333.         {
  334.             if ($this->_currentWriteService && isset($this->_writeableServices[$this->_currentWriteService]&& $forceSelect)
  335.             {
  336.                 // we probably had a communication error, ping the current read service, remove it if it times out
  337.                 if ($this->_writeableServices[$this->_currentWriteService]->ping($this->_writePingTimeout=== false)
  338.                 {
  339.                     $this->removeWriteService($this->_currentWriteService);
  340.                 }
  341.             }
  342.             
  343.             if (count($this->_writeableServices))
  344.             {
  345.                 // select one of the read services at random
  346.                 $ids array_keys($this->_writeableServices);
  347.                 
  348.                 $id $ids[rand(0count($ids1)];
  349.                 $service $this->_writeableServices[$id];
  350.             
  351.                 if (is_array($service))
  352.                 {
  353.                     //convert the array definition to a client object
  354.                     $service new Apache_Solr_Service($service['host']$service['port']$service['path']);
  355.                     $this->_writeableServices[$id$service;
  356.                 }
  357.  
  358.                 $this->_currentWriteService = $id;
  359.             }
  360.             else
  361.             {
  362.                 throw new Exception('No write services were available');
  363.             }
  364.         }
  365.  
  366.         return $this->_writeableServices[$this->_currentWriteService];
  367.     }
  368.     
  369.     /**
  370.      * Iterate through available write services and select the first with a ping
  371.      * that satisfies configured timeout restrictions (or the default).  The
  372.      * timeout period will increase until a connection is made or the limit is
  373.      * reached.   This will allow for increased reliability with heavily loaded
  374.      * server(s).
  375.      *
  376.      * @return Apache_Solr_Service 
  377.      *
  378.      * @throws Exception If there are no write services that meet requirements
  379.      */    
  380.     
  381.     protected function _selectWriteServiceSafe($forceSelect false)
  382.     {        
  383.         if (!$this->_currentWriteService || !isset($this->_writeableServices[$this->_currentWriteService]|| $forceSelect)
  384.         {
  385.             if (count($this->_writeableServices))
  386.             {
  387.                 $backoff $this->_defaultBackoff;
  388.                 
  389.                 do {
  390.                     // select one of the read services at random
  391.                     $ids array_keys($this->_writeableServices);
  392.                     
  393.                     $id $ids[rand(0count($ids1)];
  394.                     $service $this->_writeableServices[$id];
  395.                 
  396.                     if (is_array($service))
  397.                     {
  398.                         //convert the array definition to a client object
  399.                         $service new Apache_Solr_Service($service['host']$service['port']$service['path']);
  400.                         $this->_writeableServices[$id$service;
  401.                     }
  402.     
  403.                     $this->_currentWriteService = $id;
  404.                                         
  405.                     $backoff *= $this->_backoffEscalation;
  406.                     
  407.                     if($backoff $this->_backoffLimit)
  408.                     {
  409.                         throw new Exception('No write services were available.  All timeouts exceeded.');
  410.                     }
  411.                     
  412.                 while($this->_writeableServices[$this->_currentWriteService]->ping($backoff=== false);
  413.             }
  414.             else
  415.             {
  416.                 throw new Exception('No write services were available');
  417.             }
  418.         }
  419.  
  420.         return $this->_writeableServices[$this->_currentWriteService];
  421.     }
  422.  
  423.     public function setCreateDocuments($createDocuments)
  424.     {
  425.         $this->_createDocuments = (bool) $createDocuments;
  426.  
  427.         if ($this->_currentReadService)
  428.         {
  429.             $service $this->_selectReadService();
  430.             $service->setCreateDocuments($createDocuments);
  431.         }
  432.     }
  433.  
  434.     public function getCreateDocuments()
  435.     {
  436.         return $this->_createDocuments;
  437.     }
  438.  
  439.     /**
  440.      * Raw Add Method. Takes a raw post body and sends it to the update service.  Post body
  441.      * should be a complete and well formed "add" xml document.
  442.      *
  443.      * @param string $rawPost 
  444.      * @return Apache_Solr_Response 
  445.      *
  446.      * @throws Exception If an error occurs during the service call
  447.      */
  448.     public function add($rawPost)
  449.     {
  450.         $service $this->_selectWriteService();
  451.  
  452.         do
  453.         {
  454.             try
  455.             {
  456.                 return $service->add($rawPost);
  457.             }
  458.             catch (Exception $e)
  459.             {
  460.                 if ($e->getCode(!= 0//IF NOT COMMUNICATION ERROR
  461.                 {
  462.                     throw $e;
  463.                 }
  464.             }
  465.  
  466.             $service $this->_selectWriteService(true);
  467.         while ($service);
  468.         
  469.         return false;
  470.     }
  471.  
  472.     /**
  473.      * Add a Solr Document to the index
  474.      *
  475.      * @param Apache_Solr_Document $document 
  476.      * @param boolean $allowDups 
  477.      * @param boolean $overwritePending 
  478.      * @param boolean $overwriteCommitted 
  479.      * @return Apache_Solr_Response 
  480.      *
  481.      * @throws Exception If an error occurs during the service call
  482.      */
  483.     public function addDocument(Apache_Solr_Document $document$allowDups false$overwritePending true$overwriteCommitted true)
  484.     {
  485.         $service $this->_selectWriteService();
  486.  
  487.         do
  488.         {
  489.             try
  490.             {
  491.                 return $service->addDocument($document$allowDups$overwritePending$overwriteCommitted);
  492.             }
  493.             catch (Exception $e)
  494.             {
  495.                 if ($e->getCode(!= 0//IF NOT COMMUNICATION ERROR
  496.                 {
  497.                     throw $e;
  498.                 }
  499.             }
  500.  
  501.             $service $this->_selectWriteService(true);
  502.         while ($service);
  503.         
  504.         return false;
  505.     }
  506.  
  507.     /**
  508.      * Add an array of Solr Documents to the index all at once
  509.      *
  510.      * @param array $documents Should be an array of Apache_Solr_Document instances
  511.      * @param boolean $allowDups 
  512.      * @param boolean $overwritePending 
  513.      * @param boolean $overwriteCommitted 
  514.      * @return Apache_Solr_Response 
  515.      *
  516.      * @throws Exception If an error occurs during the service call
  517.      */
  518.     public function addDocuments($documents$allowDups false$overwritePending true$overwriteCommitted true)
  519.     {
  520.         $service $this->_selectWriteService();
  521.  
  522.         do
  523.         {
  524.             try
  525.             {
  526.                 return $service->addDocuments($documents$allowDups$overwritePending$overwriteCommitted);
  527.             }
  528.             catch (Exception $e)
  529.             {
  530.                 if ($e->getCode(!= 0//IF NOT COMMUNICATION ERROR
  531.                 {
  532.                     throw $e;
  533.                 }
  534.             }
  535.  
  536.             $service $this->_selectWriteService(true);
  537.         while ($service);
  538.         
  539.         return false;
  540.     }
  541.  
  542.     /**
  543.      * Send a commit command.  Will be synchronous unless both wait parameters are set
  544.      * to false.
  545.      *
  546.      * @param boolean $waitFlush 
  547.      * @param boolean $waitSearcher 
  548.      * @return Apache_Solr_Response 
  549.      *
  550.      * @throws Exception If an error occurs during the service call
  551.      */
  552.     public function commit($optimize true$waitFlush true$waitSearcher true$timeout 3600)
  553.     {
  554.         $service $this->_selectWriteService();
  555.  
  556.         do
  557.         {
  558.             try
  559.             {
  560.                 return $service->commit($optimize$waitFlush$waitSearcher$timeout);
  561.             }
  562.             catch (Exception $e)
  563.             {
  564.                 if ($e->getCode(!= 0//IF NOT COMMUNICATION ERROR
  565.                 {
  566.                     throw $e;
  567.                 }
  568.             }
  569.  
  570.             $service $this->_selectWriteService(true);
  571.         while ($service);
  572.         
  573.         return false;
  574.     }
  575.  
  576.     /**
  577.      * Raw Delete Method. Takes a raw post body and sends it to the update service. Body should be
  578.      * a complete and well formed "delete" xml document
  579.      *
  580.      * @param string $rawPost 
  581.      * @return Apache_Solr_Response 
  582.      *
  583.      * @throws Exception If an error occurs during the service call
  584.      */
  585.     public function delete($rawPost)
  586.     {
  587.         $service $this->_selectWriteService();
  588.  
  589.         do
  590.         {
  591.             try
  592.             {
  593.                 return $service->delete($rawPost);
  594.             }
  595.             catch (Exception $e)
  596.             {
  597.                 if ($e->getCode(!= 0//IF NOT COMMUNICATION ERROR
  598.                 {
  599.                     throw $e;
  600.                 }
  601.             }
  602.  
  603.             $service $this->_selectWriteService(true);
  604.         while ($service);
  605.         
  606.         return false;
  607.     }
  608.  
  609.     /**
  610.      * Create a delete document based on document ID
  611.      *
  612.      * @param string $id 
  613.      * @param boolean $fromPending 
  614.      * @param boolean $fromCommitted 
  615.      * @return Apache_Solr_Response 
  616.      *
  617.      * @throws Exception If an error occurs during the service call
  618.      */
  619.     public function deleteById($id$fromPending true$fromCommitted true)
  620.     {
  621.         $service $this->_selectWriteService();
  622.  
  623.         do
  624.         {
  625.             try
  626.             {
  627.                 return $service->deleteById($id$fromPending$fromCommitted);
  628.             }
  629.             catch (Exception $e)
  630.             {
  631.                 if ($e->getCode(!= 0//IF NOT COMMUNICATION ERROR
  632.                 {
  633.                     throw $e;
  634.                 }
  635.             }
  636.  
  637.             $service $this->_selectWriteService(true);
  638.         while ($service);
  639.         
  640.         return false;
  641.     }
  642.  
  643.     /**
  644.      * Create a delete document based on a query and submit it
  645.      *
  646.      * @param string $rawQuery 
  647.      * @param boolean $fromPending 
  648.      * @param boolean $fromCommitted 
  649.      * @return Apache_Solr_Response 
  650.      *
  651.      * @throws Exception If an error occurs during the service call
  652.      */
  653.     public function deleteByQuery($rawQuery$fromPending true$fromCommitted true)
  654.     {
  655.         $service $this->_selectWriteService();
  656.  
  657.         do
  658.         {
  659.             try
  660.             {
  661.                 return $service->deleteByQuery($rawQuery$fromPending$fromCommitted);
  662.             }
  663.             catch (Exception $e)
  664.             {
  665.                 if ($e->getCode(!= 0//IF NOT COMMUNICATION ERROR
  666.                 {
  667.                     throw $e;
  668.                 }
  669.             }
  670.  
  671.             $service $this->_selectWriteService(true);
  672.         while ($service);
  673.         
  674.         return false;
  675.     }
  676.  
  677.     /**
  678.      * Send an optimize command.  Will be synchronous unless both wait parameters are set
  679.      * to false.
  680.      *
  681.      * @param boolean $waitFlush 
  682.      * @param boolean $waitSearcher 
  683.      * @return Apache_Solr_Response 
  684.      *
  685.      * @throws Exception If an error occurs during the service call
  686.      */
  687.     public function optimize($waitFlush true$waitSearcher true)
  688.     {
  689.         $service $this->_selectWriteService();
  690.  
  691.         do
  692.         {
  693.             try
  694.             {
  695.                 return $service->optimize($waitFlush$waitSearcher);
  696.             }
  697.             catch (Exception $e)
  698.             {
  699.                 if ($e->getCode(!= 0//IF NOT COMMUNICATION ERROR
  700.                 {
  701.                     throw $e;
  702.                 }
  703.             }
  704.  
  705.             $service $this->_selectWriteService(true);
  706.         while ($service);
  707.         
  708.         return false;
  709.     }
  710.  
  711.     /**
  712.      * Simple Search interface
  713.      *
  714.      * @param string $query The raw query string
  715.      * @param int $offset The starting offset for result documents
  716.      * @param int $limit The maximum number of result documents to return
  717.      * @param array $params key / value pairs for query parameters, use arrays for multivalued parameters
  718.      * @return Apache_Solr_Response 
  719.      *
  720.      * @throws Exception If an error occurs during the service call
  721.      */
  722.     public function search($query$offset 0$limit 10$params array())
  723.     {
  724.         $service $this->_selectReadService();
  725.  
  726.         do
  727.         {
  728.             try
  729.             {
  730.                 return $service->search($query$offset$limit$params);
  731.             }
  732.             catch (Exception $e)
  733.             {
  734.                 if ($e->getCode(!= 0//IF NOT COMMUNICATION ERROR
  735.                 {
  736.                     throw $e;
  737.                 }
  738.             }
  739.  
  740.             $service $this->_selectReadService(true);
  741.         while ($service);
  742.         
  743.         return false;
  744.     }
  745. }

Documentation generated on Tue, 02 Sep 2008 10:45:03 -0400 by phpDocumentor 1.4.0