高度可用的API - 这个解决方案是好的吗?

时间:2012-05-20 11:33:45

标签: php http rest curl high-availability

我有一个从RESTful API检索数据的客户端。

我的要求是我希望API按指定的顺序排列。如果无法达到首选,则应使用下一个。

这样做的原因是某种负载平衡(apis返回指向应用程序中进一步使用的相同服务器的生成链接。)

所以最初我按照所需的顺序生成了APIS的URL。例如:

  1. http://first-api.example.com/method?param=value

  2. http://first-api.example.com/method?param=value

  3. http://first-api.example.com/method?param=value

  4. 现在我可以循环遍历这些网址,并从第​​一个响应成功的API返回结果。然而,这需要太长时间。 API可能需要很长时间才能响应(最多几秒钟)。如果加起来,整个过程的超时可能会出现。

    因此,我想等待一段可接受的时间(如果一切正常,API通常会成功响应)。 如果达到该时间,我想请求第二个API,但也要等待第一个API,以防它需要更长的时间来响应。

    经过一段时间后,第3个API也会被添加到池中

    我不想从一开始就请求每个API,因为每个请求都意味着我想尽可能避免的重要工作量。

    哪个API首先响应,才能完成工作。

    所以我创建了一个简单的类,它执行上面描述的curl_multi_exec。

    它传递了2种方法。一个一个地传递API URL的一个,一个用于评估响应是否成功,另一个用于启动流程并返回第一个成功响应的公共方法。

    包括测试类的类看起来像这样:

    <?
    class MultiGetProxy
    {
        private $url_src = null;
        private $response_evaluator = null;
        private $total_start = null;
        private $handles = array();
        private $mh = null;
        public function setUrlSource($callback)
        {
            $this->url_src = $callback;
            return $this;
        }
        public function setResponseEvaulator($callback)
        {
            $this->response_evaluator = $callback;
            return $this;
        }
        private function addNewHandle()
        {
            echo "adding new handle ... \n";
            $ch = curl_init();
    
            curl_setopt($ch, CURLOPT_URL, call_user_func($this->url_src));
            ;
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    
            $this->handles[] = $ch;
            curl_multi_add_handle($this->mh, $this->handles[count($this->handles) - 1]);
        }
        private function getRunning()
        {
            curl_multi_exec($this->mh, $running);
            return $running;
    
        }
        private function getResponse()
        {
            foreach ($this->handles as &$handle) {
                if ($content = curl_multi_getcontent($handle)) {
                    echo "one handle delivered content...\n";
                    if (!call_user_func($this->response_evaluator, $content)) {
                        echo "evluater said it was an error...\n";
                        curl_multi_remove_handle($this->mh, $handle);
                        if (!$this->getRunning())
                            $this->addNewHandle();
                        return false;
                    }
                    echo "content looks fine\n";
                    var_dump($content);
                    return $content;
    
                }
            }
        }
        public function exec()
        {
            $this->total_start = microtime(true);
            $this->mh          = curl_multi_init();
            $response          = false;
            $lastadd           = 0;
            while (!$response) {
                if (microtime(true) > $lastadd + 5.0) {
                    $this->addNewHandle();
                    $lastadd = microtime(true);
                    curl_multi_exec($this->mh, $running);
                }
                usleep(100000);
                curl_multi_exec($this->mh, $running);
                $response = $this->getResponse();
            }
            echo "do loop done here is some info:\n";
            var_dump(curl_multi_info_read($mh));
            // Handles schliessen
            foreach ($handles as &$handle) {
                curl_close($handle);
                curl_multi_remove_handle($mh, $handle);
            }
            curl_multi_close($mh);
            $total_run = microtime(true) - $total_start;
            echo "completed in: $total_run \n";
            echo "response is:\n";
            return $response;
    
        }
        public function getInfo()
        {
            $info               = array();
            $info["total_time"] = $this->total_run;
            $info["start"]      = $this->total_start;
    
        }
    
    }
    
    
    class Test
    {
        function isvalid($data)
        {
            $data = json_decode($data);
            if ($data->status > 500) { // error
                return false;
            }
            return true;
        }
        function getUrl()
        {
            static $urls = array();
            if (empty($urls)) {
                $urls[] = 'http://test.example.com/sleep.php?s=8&status=200&message=first';
                $urls[] = 'http://test.example.com/sleep.php?s=2&status=503&message=2nd';
                $urls[] = 'http://test.example.com/sleep.php?s=2&status=200&message=3rd';
                $urls[] = 'http://test.example.com/sleep.php?s=21';
                $urls[] = 'http://test.example.com/sleep.php?s=22';
                $urls[] = 'http://test.example.com/sleep.php?s=23';
                $urls[] = 'http://test.example.como/sleep.php?s=25';
                $urls[] = 'http://test.example.com/sleep.php?s=25';
            }
    
            $url = array_shift($urls);
            echo "returning $url \n";
            return $url;
    
    
    
        }
        public function test()
        {
            $mgp = new MultiGetProxy();
            $mgp->setUrlSource(array(
                $this,
                "getUrl"
            ));
            $mgp->setResponseEvaulator(array(
                $this,
                "isvalid"
            ));
            $result = $mgp->exec();
            echo $result;
        }
    }
    
    
    $test = new Test();
    $test->test();
    

    现在我的实际问题更多的是“征求意见”,因为我从来没有真正处理这些事情。

    它应该部署在高负载环境中,会有很多请求。

    睡觉时间也正常吗?

    我是否可能遇到tcp连接probelems?

    对不起,我不能更具体。我只是试图找到一个非常具体和个别问题的解决方案,这就是我想出来的。

1 个答案:

答案 0 :(得分:1)

构建位于API和代码之间的外观。 Facade负责决定使用哪个API。 Facade具有代表API服务器的HTTP对象。您的代码只是向外观发送和接收数据,而不关心它正在使用哪个服务器。

然后致力于提高立面的性能。使用模拟对象对其进行测试,该对象在您预期的那种时间之后返回数据。

interface Proxy {
  function exec();
}

abstract class AbstractProxy {

  public $response;

  public function hasResponse() {
    return !empty($this->response);
  }

}

class SingleProxy extends AbstractProxy implements Proxy {

  // the real code for connecting to the API

}

class MultiProxy extends AbstractProxy implements Proxy {

  public $singleProxies = array();
  public $delayTime = 1000000;

  public function addProxy(Proxy $proxy) {
    $this->singleProxies[] = $proxy;
  }

  public function exec() {
     foreach($singleProxies as $proxy) {
        $proxy->exec();
        usleep($this->delayTime);
        if($proxy->hasResponse() {
          return $proxy;
        } else {
          $proxy->cancel();
        }
     }
  }

}

class SingleProxyStub extends AbstractProxy implements Proxy {

  public $timeToRespond = 0;

  public function exec() {
    usleep($this->timeToRespond);
    $this->response = 'response';
  }

}

class ProxyTest() {

   public function runTest() {

     $slowProxy = new SingleProxyStub;
     $slowProxy->timeToResponse = 2000000; // 2 seconds

     $fastProxy = new SingleProxyStub;
     $fastProxy->timeToResponse = 500000; // 0.5 seconds

     $MultiProxy = new MultiProxy;
     $MultiProxy->singleProxies = array($slowProxy, $fastProxy);

     $startTime = microtime(true);
     $MultiProxy->exec();
     $endTime = microtime(false);
     log($startTime, $endTime);

   }

}