2019独角兽企业重金招聘Python工程师标准>>>
h3. 背景
在网校最初时,接入的网校就几家,而且课程数据量较小,为快速开发上线,就采取逐个遍历课程数据源,进行抓取,然后解析数据并处理,存入到数据库中。但是随着网校的接入, 且为展示更多课程数据信息,字段内容又不断调整,导致请求的数据变大,抓取的时间变得延长,顺序的处理流程,又容易造成如果某个数据源出错,导致剩下的网校课程数据无法抓取。
恰逢网校要开发详情页,需要调整字段,引入更多数据,抓取的当前框架有些耦合,特别是网络处理这些,不能支持多并发,担心随着数据量的增大,处理完后,会严重导致不断调整cron中的其他业务脚本,毕竟依赖最新的数据,如删除过 期的数据,向引擎推送数据等,未来是打算整合这些脚本,但提高数据抓取与处理的性能,还是必须做的。
h3. 数据依赖与并发
网校目前的各个数据源都是来自于各个教育机构,因而没有依赖性,这就在抓取数据时,方便并发处理;如果网校的数据源存在依赖,特别是某条数据源的请求依赖上次某条数据源的处理结果,则将不得不采用串行处理。即使并发,也可并发 请求完所有数据,然后再对数据进行处理,如图
![curl_multi在抓取数据中的并发实现_第1张图片](http://img.e-com-net.com/image/info8/954895c532d9484abbef39021d643bed.png)
但是会发现,这种方式并不能利用客户端等待服务端数据到达的空隙,更好的处理是,当某个数据源的数据到达时,就能立即处理,而不是等待所有的数据源的数据到达时,再逐个处理数据。在目前的网校课程数据源中,不同教育机构给出的课程 数差异很大,有些数据可能要持续两三分钟,才能请求完,而有些数据几秒内就可到达,因而谁到处理谁,能更好利用CPU。
h3. 并发的实现
在当前PHP并发的方法中,可以使用curl_multi_*系列的方法,也可利用其它人开发的专用并发框架,如鸟哥写的yar,考虑到学习成本与资源,就直接采用curl_multi_*方法,而且本身也有封装好的专门用于“谁到处理谁”的框架RollingCurl,
提供更好的网络控制处理,并提供相关的DEMO,但核心代码依然是curl_multi_*系列方法的处理:
{code:java}
do {
while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ;
if ($execrun != CURLM_OK)
break;
// a request was just completed -- find out which one
while ($done = curl_multi_info_read($master)) {
// get the info and content returned on the request
...
// send the return values to the callback function.
...
// start a new request (it's important to do this before removing the old one)
...
}
// remove the curl handle that just completed
curl_multi_remove_handle($master, $done['handle']);
}
// Block for data in / output; error handling is done by curl_multi_exec
if ($running)
curl_multi_select($master, $this->timeout);
} while ($running);
{code}
当调用curl_multi_add_handle添加curl句柄时,就会进入do-while大循环代码,首先执行curl_multi_exec,但是此方法,在do-while循环中被多次调用,PHP文档给此方法的解释是“处理在栈中的每一个句柄。
无论该句柄需要读取或写入数据都可调用此方法。”,也就是说第一次调用此方法是用于发出HTTP请求数据,当栈中的句柄还有数据需要传送时,就会返回 CURLM_CALL_MULTI_PERFORM,当返回CURLM_OK只是意味着数据传送完毕或者没有数据 可传送。此方法是不阻塞的,也就是说一旦栈中某个或多个curl句柄有数据读取或者写入,就可以调用此方法传送数据,并立即返回栈中句柄的活动状态。
url_multi_info_read方法是检查批处理句柄中某次传送数据结束的状态(当curl_multi_exec调用返回CURLM_OK),可能上次并没有数据可传送,则队列中并没有任何消息,则就会返回false;可能传送数
据出现错误,消息队列就会有某个批处理句柄出错的信息,当数据成功完成时,则返回的消息体中的msg字段值会为CURLMSG_DONE,其他值都不可用,表明出错。
如果没有后面的curl_multi_select方法上述程序也能正常运行,但是就会发现整个程序处于不停空转中,毕竟网络传输数据占用时间还是蛮长的,每次调用curl_multi_exec可能都是立即返回CURLM_OK,然后执行
curl_multi_info_read,又立即返回false,如此继续。而调用curl_multi_select,就会等待直到多个SOCKET中的某个处于活跃状态,不然就处于阻塞状态,直到超时为止,就不会出现程序忙空转状态。
在上述程序代码中,可以看到,当某次传送数据完成后,就会立即处理返回来的数据,而不是待所有句柄的请求数据都完成后,才继续处理数据,其实这种等待所有请求数据都完成时再处理的情形,也有很多应用场景,如某个页面的数据, 来自于多个源,只有组装在一起,才能渲染页面。到时只需要将调用curl_multi_info_read方法的那块代码移除,在do-while循环后添加数据处理的过程即可。
h5. 参考文献
[php curl document|http://www.php.net/manual/zh/ref.curl.php]
[rolling-curl|https://github.com/takinbo/rolling-curl]
[php-src|https://github.com/php/php-src]
[libcutl-multi API document|http://curl.haxx.se/libcurl/c/libcurl-multi.html]
[curl-src|https://github.com/bagder/curl]