加入收藏 | 设为首页 | 会员中心 | 我要投稿 我爱资讯网 (https://www.52junxun.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

Opensearch PHP SDK协程兼容改造

发布时间:2022-10-13 11:02:29 所属栏目:PHP教程 来源:
导读:  摘要

  本文简单的介绍了协程的概念及基本原理,以及协程在PHP中的一种实现方案(PECL/Swoole)。最后,结合Opensearch PHP SDK的协程改造过程演示了具体的使用方法。

  协程

  与进程、线程一
  摘要
 
  本文简单的介绍了协程的概念及基本原理,以及协程在PHP中的一种实现方案(PECL/Swoole)。最后,结合Opensearch PHP SDK的协程改造过程演示了具体的使用方法。
 
  协程
 
  与进程、线程一样,协程是逻辑代码线之间隔离的一种方法。只不过进程和线程是由操作系统直接支持,并负责调度的;协程的粒度比线程更小,操作系统无法感知,因此调度工作必须由程序自己完成。
 
  从目标上来看,协程与epoll等模型基本一致:都是为了降低进程(线程)调度引发的频繁上下文切换的资源消耗,最终提高系统效率。使用epoll模型编写的代码大量使用回调函数(类似下面的伪代码):
 
  connect(uri, connected() {
   send(data, sent() {
   receive(received(response) {
   // ...
   });
   });
  })
  在实际编写中,一般不会使用这么深层次的函数嵌套结构,但是上例从侧面描述了异步代码的编写困境:效率高,阅读难。
 
  与epoll模型不同,协程代码不需要编写很多回调函数,代码逻辑看起来和同步代码一样:
 
  connect(uri);
  send(data);
  response = receive();
  // ...
  协程调度器完成了其中的调度工作:感知挂起php多线程,完成调度。
 
  协程的概念提出的很早,只是最近有些编程语言原生支持协程(如:Go)才使得其变得较为热门。PHP解释器对各种C类库的依赖较为严重,代码中大量使用同步方法。因此直接在Zend Engine中支持协程困难重重。好在有扩展开发人员编写了大量的实现代码,为我们解决了这个问题。
 
  PECL/Swoole
 
  PECL/Swoole是使用C/C++开发的PHP异步网络通讯扩展,提供异步非阻塞网络通讯支持。基于PECL/Swoole扩展,我们可以在PHP非线程安全模式下实现多线程的网络通讯,提高PHP程序的吞吐能力。
 
  自2.0开始,PECL/Swoole提供了原生的协程支持。开发者可以借助一整套新编写的类和方法实现单线程的基于协程的网络通讯。自4.0开始,PECL/Swoole重写了协程部分全部的代码,弃用了(未发布的3.0版本)基于微信C++协程库的对于协程的实现方案,自主实现了较为稳定的协程方案。
 
  下面的代码展示了如何通过PECL/Swoole实现简单的HTTP客户端请求(与PECL/Swoole版本无关):
 
  go(function() {
   $cli = new \Swoole\Coroutine\Http\Client('127.0.0.1', 9501);
  
   $cli->setHeaders(['Host' => 'localhost']);
   $cli->set(['http_proxy_host' => HTTP_PROXY_HOST, 'http_proxy_port' => HTTP_PROXY_PORT]);
  
   $result = $cli->get('/get?json=true');
   var_dump($cli->body);
  });
  代码中的匿名函数首先通过IP地址和端口号创建了HTTP客户端对象,然后分别设置了头信息和代理信息,最后通过GET方法获取URI的响应结果并输出。
 
  示例代码中的go()函数是PECL/Swoole协程实现的核心:在其中执行的代码全部受到协程调度器的管控,并在某个协程操作挂起时自动切换到其他协程待处理的代码段中。下面的伪代码展示了如何借助go()函数同时发出多个请求:
 
  for ($i=0; $i<10; ++$i) {
   go(function() use($i) {
   $response = request('/region');
   echo "#{$i}: " . $response . PHP_EOL;
   });
  }
  由于协程调度器的存在,代码不会在request()函数处停留,全部请求几乎同时发出。这就意味着获得响应的顺序也不会严格按照#0, #1, …的顺序进行:哪个请求先返回,哪个请求的的echo语句先被执行。
 
  当然,PECL/Swoole目前只支持其自制的、经过改造的网络通讯类,其他尚未改造的阻塞函数(或方法)无法被支持。
 
  改造手记
 
  与大部分的PHP编写的HTTP客户端程序一样,Opensearch PHP SDK使用cURL作为默认的HTTP请求工具。借助ext/curl,我们可以实现绝大多数的阻塞式的HTTP请求(包括HTTPS请求)。但是对于协程程序来说,这里就是需要重点改造的地方。
 
  1.改造原有代码
 
  在OpenSearch\Client\OpenSearchClient类中,我们找到了前辈们提取出的公用请求方法_curl():
 
   private function _curl($url, $items) {
   $method = strtoupper($items['method']);
   $options = array(
   CURLOPT_HTTP_VERSION => 'CURL_HTTP_VERSION_1_1',
   CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
   CURLOPT_TIMEOUT => $this->timeout,
   CURLOPT_CUSTOMREQUEST => $method,
   CURLOPT_HEADER => false,
   CURLOPT_RETURNTRANSFER => true,
   CURLOPT_USERAGENT => "opensearch/php sdk " . self::SDK_VERSION . "/" . PHP_VERSION,
   CURLOPT_HTTPHEADER => $this->_getHeaders($items),
   );
   if ($method == self::METHOD_GET) {
   $query = $this->_buildQuery($items['query_params']);
   $url .= preg_match('/\?/i', $url) ? '&' . $query : '?' . $query;
   } else{
   if(!empty($items['body_json'])){
   $options[CURLOPT_POSTFIELDS] = $items['body_json'];
   }
   }
   if ($this->gzip) {
   $options[CURLOPT_ENCODING] = 'gzip';
   }
   if ($this->debug) {
   $out = fopen('php://temp','rw');
   $options[CURLOPT_VERBOSE] = true;
   $options[CURLOPT_STDERR] = $out;
   }
   $session = curl_init($url);
   curl_setopt_array($session, $options);
   $response = curl_exec($session);
   curl_close($session);
   $openSearchResult = new OpenSearchResult();
   $openSearchResult->result = $response;
   if ($this->debug) {
   $openSearchResult->traceInfo = $this->getDebugInfo($out, $items);
   }
   return $openSearchResult;
   }
  上述代码的大致流程是:
 
  首先,我们需要提供一个可供用户切换的开关,便于协程开发者从cURL模式切换为Swoole模式:
 
   /** @var IHttpHandler */
   private $httpHandler = null;
   public function __construct($accessKey, $secret, $host, $options = array()) {
   // ...
   $this->httpHandler = new CUrlHttpHandler();
   // ...
   }
   public function setHttpHandler(IHttpHandler $httpHandler)
   {
   $this->httpHandler = $httpHandler;
   }
 

(编辑:我爱资讯网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!