背景
在web應用中讨韭,為了更好的拆分服務,一個較大的業(yè)務會拆分為多個自服務,每個子服務獨自完成部分功能罪治,眾多子服務一起支撐較大的業(yè)務瘦麸。
這就不可避免的遇到一個復雜的業(yè)務需要串行地調(diào)用多個子服務的場景,特別是一些業(yè)務邏輯上沒有依賴的部分箍鼓,這時候铐殃,如果可以有批量rpc調(diào)用,那就如虎添翼了是整。
好消息是舵盈,鳥哥開發(fā)的Yar就是支持并行rpc調(diào)用的框架筒愚,他是以擴展的形式提供并行RPC功能。據(jù)說在微博內(nèi)部已經(jīng)廣發(fā)使用了,然而楚堤,對于線上已經(jīng)在跑的業(yè)務岔乔,給編譯一個擴展嘿歌,運營成本還是挺高的茄唐。
思路
PHP原生的curl_multi*函數(shù)簇,也可以支持一次發(fā)送多個RPC調(diào)用的作用厨幻,使用方式為:
實現(xiàn)
<?php
/**
* 基于 curl_multi 的并行處理
*
* 使用示例
* $url1 = "http://baidu.com";
* $url2 = "http://sina.com";
*
* $objMulti = new Multi();
* $objMulti->init();
* $objMulti->register('url1', $url1);
* $objMulti->register('url2', $url2);
* $objMulti->call();
* $res = $objMulti->getResult('url1');
* var_dump($res);
* $res = $objMulti->getResult('url2');
* var_dump($res);
*/
class Multi {
/* default 500k */
const MAX_RESPONSE_SIZE = 512000;
/* max redirs */
const MAX_REDIRS = 1;
/*鏈接重試*/
const MAX_CONNECT_RETRY = 3;
/* 默認鏈接超時時間 */
const MAX_CONNECT_TIMEOUT = 1000;
/*默認超時時間*/
const MAX_TIME_OUT = 3000;
/*UTF8 字符編碼*/
const ENCODE_UTF8 = 'utf-8';
/* cURL 多個句柄 */
protected $_obj_multi_handler = null;
/* curl句柄列表 */
protected $_arr_curl_handler = array();
/* curl 參數(shù) */
protected $_arr_curl_options = array();
/* 調(diào)用返回的結(jié)果 */
protected $_arr_result = array();
/**
* @desc 初始化curl_multi句柄
* @author dayeshisir(dayeshisir@weidian.com)
* @date 2016-10-09
* @param
* @return
* @note
*/
public function init($config = array()) {
if (is_null($this->_obj_multi_handler)) {
$this->_obj_multi_handler = curl_multi_init();
}
$str_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
$str_referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '';
$this->_arr_curl_options = array(
CURLOPT_NOSIGNAL => 1, //注意棠隐,毫秒超時一定要設置這個
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_HEADER => false,
CURLOPT_MAXREDIRS => self::MAX_REDIRS,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT_MS => self::MAX_CONNECT_TIMEOUT,
CURLOPT_TIMEOUT_MS => self::MAX_TIME_OUT,
CURLOPT_USERAGENT => $str_user_agent,
CURLOPT_REFERER => $str_referer,
CURLOPT_ENCODING => '',
);
return true;
}
/**
* @desc 注冊待調(diào)用的服務
* @param key : string : 用來區(qū)分各個自調(diào)用
* @param url : string : url地址挖藏,可包含問號
* @param date : array : 入?yún)?shù)組,key-value對炸宵,post或get的參數(shù)
* @param request_type : string : 請求的類型,例如post,get
* @param config : array : 附加的配置铃将,具體數(shù)值例如:
* array(
* CURLOPT_COOKIE => string 設定HTTP請求中"Cookie: "部分的內(nèi)容奥此。多個cookie用分號分隔,分號后帶一個空格(例如丈牢, "fruit=apple; colour=red")垫桂。
* CURLOPT_REFERER => string 請求頭中"Referer: "的內(nèi)容
* CURLOPT_USERAGENT => string "User-Agent: "頭的字符串。如:“Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)”
* CURLOPT_HTTPHEADER => array header頭列表,如:array('host:http://abc.com','Content-type: text/plain')
* CURLOPT_TIMEOUT => int 超時時間,秒
* )
*
* @return resource
*/
public function register($key, $url, $data = array(), $config = array(), $request_type = 'post') {
if (null === $this->_obj_multi_handler) {
return false;
}
//初始化curl會話
$obj_ch = curl_init();
//curl參數(shù)
extract($this->_arr_curl_options);
$option = $this->_arr_curl_options;
//get 參數(shù)處理(url處理)
if ('get' === $request_type && !empty($data)) {
$url .= (strpos($url, '?') ? '&' : '?') . http_build_query($data);
}
$option[CURLOPT_URL] = $url;
//post 參數(shù)處理
if ('post' === $request_type && empty($data)) {
$option[CURLOPT_POSTFIELDS] = $data;
}
//其它參數(shù)合并
$option += $config;
//批量設置會話參數(shù)
curl_setopt_array($obj_ch, $option);
// 添加到curl句柄
if (0 !== curl_multi_add_handle($this->_obj_multi_handler, $obj_ch)) {
return false;
}
// 如果原來有相同的$key,則刪除之
if (isset($this->_arr_curl_handler[$key])) {
curl_multi_remove_handle($this->_obj_multi_handler, $this->_arr_curl_handler[$key]); //移除原curl句柄
curl_close($this->_arr_curl_handler[$key]); //關閉原curl會話
unset($this->_arr_curl_handler[$key]); //刪除原$k
}
$this->_arr_curl_handler[$key] = $obj_ch;
return true;
}
/**
* @desc 執(zhí)行批量請求,返回批量數(shù)據(jù)
* @param null
* @return array
*/
public function call() {
//無批量執(zhí)行句柄茶没,返回空數(shù)組
if (is_null($this->_obj_multi_handler)) {
return $this->_arr_result;
}
$active = null;
//執(zhí)行
do {
$mrc = curl_multi_exec($this->_obj_multi_handler, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
while ($active && $mrc == CURLM_OK) {
while(curl_multi_exec($this->_obj_multi_handler, $active) === CURLM_CALL_MULTI_PERFORM);
if (curl_multi_select($this->_obj_multi_handler) != -1) {
do {
$mrc = curl_multi_exec($this->_obj_multi_handler, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
//獲取返回結(jié)果
foreach ($this->_arr_curl_handler as $key => $curl_handler) {
$strResult = curl_multi_getcontent($curl_handler);
$this->_arr_result[$key] = json_decode($strResult, true);
curl_multi_remove_handle($this->_obj_multi_handler, $curl_handler);//移除句柄
curl_close($curl_handler);//關閉會話
}
curl_multi_close($this->_obj_multi_handler);//關閉批量會話
//清理數(shù)據(jù),恢復初始狀態(tài)辱揭,為下一次使用做好準備
$this->_obj_multi_handler = null;
$this->_arr_curl_handler = array();
return $this->_arr_result;
}
/**
* @desc 獲取結(jié)果
* @param key : string : 用來區(qū)分子服務的鍵值
* @return array
*/
public function getResult($key) {
return isset($this->_arr_result[$key]) ? $this->_arr_result[$key] : array();
}
}
$url1 = "http://www.baidu.com";
$url2 = "http://www.sina.com.cn";
$objMulti = new Multi();
$objMulti->init();
$objMulti->register('url1', $url1);
$objMulti->register('url2', $url2);
$objMulti->call();
$res = $objMulti->getResult('url1');
var_dump($res);
$res = $objMulti->getResult('url2');
var_dump($res);