看得非常过瘾啊,对Yahoo有了新认识 :-)<br>关于使用firefox3.1,我建议你们可以试一下fennec里自带的xulrunner,应该是1.9.2a1/2pre的,可能比3.1好一点?<br>你们的分析是建立在DOM树的基础上?不知道你们的应用有没有根据重绘来判断页面热点或者类似的?<br>JIT可能没有打开chrome/content?<br>没有完全咬文嚼字看完,回去再拜读!<br><br><br><div class="gmail_quote">
2008/12/25 agentzh <span dir="ltr">&lt;<a href="mailto:agentzh@gmail.com">agentzh@gmail.com</a>&gt;</span><br><blockquote class="gmail_quote" style="margin: 0pt 0pt 0pt 0.8ex; border-left: 1px solid rgb(204, 204, 204); padding-left: 1ex;">
<div class="Ih2E3d">2008/12/25 wbi 畢 &lt;<a href="mailto:wbisearchina@hotmail.co.jp" target="_blank">wbisearchina@hotmail.co.jp</a>&gt;<br>&gt;<br></div><div class="Ih2E3d">&gt; &nbsp;Firefox 3.1 集群 是怎么个原理啊 能给介绍介绍不?<br><br></div>
=head1 NAME<br><br>List Hunter cluster - 这个是我们自己基于 Mozilla Firefox 3.1 做的深抓爬虫集群,名为 List Hunter 集群。目前是我们公司生活搜索引擎的一部分。<br>
<br>=head1 背景<br><br>在我们的生活搜索项目中,需要对网页进行深层次的识别和抽取。基于文本内容的分类我们目前采用的是美国雅虎基于最大熵的 DCP 系统。而对于网页结构方面的分类(即这个网页是列表页呢,还是详情页?),以及主体链接列表、主体区域抽取,则一直缺乏比较好的解决方案。我的同事尝试过通过纯粹的结构化的方法(如海维算法)进行识别,准确率只有 60%,而基于 SVM 这样的机器学习的方法,对网页类型比较敏感,如目标网页与训练集相差较多,则准确率迅速下降。<br>

<br>于是我尝试把网页显示时的视觉信息结合到海维算法以及块合并算法中。于是准确率和召回率分别达到了 90% 和 80%。这里的视觉信息主要包括一个网页区域的大小、形状、和在整个页面中的位置。更多的信息还包括字体、颜色等等。这样,便诞生了 List Hunter 插件。于是如何将 Firefox 插件做成一个大规模的集群用于生产,便成为了重要问题。<br><br>在下面这篇 blog 文章中我介绍了更多背景方面的细节以及 List Hunter 插件本身的情况:<br>

<br>&nbsp;&nbsp;&nbsp; <a href="http://blog.agentzh.org/#post-97" target="_blank">http://blog.agentzh.org/#post-97</a><br><br>=head1 集群的结构<br><br>该集群由四大部分组成:纯 Firefox 集群,Apache + mod_proxy + mod_disk_cache 集群,curl 预取器集群,和 OpenResty 集群。一共有十几台生产机&quot;全职&quot;或者&quot;兼职&quot;地参与了这个集群。下面逐一介绍一下哈:<br>

<br>=over<br><br>=item 纯 Firefox 集群<br><br>纯 Firefox 集群目前由 8 台 4 核的 redhat5 生产机组成。每台生产机运行 3 个 Firefox 3.1 进程实例。因为那 8 台机器同时服务于淘宝 VIP 搜索的商口图片显示接口(大约 600 万日 PV),所以我们没敢在这些机器上运行比较多的 Firefox 进程。<br><br>需要指出的是,Firefox 默认是&quot;进程复用&quot;的运行方式。即启动多次 firefox-bin 可执行程序,其实得到的还是单个 Firefox 进程。这种进程复用方式无法充分利用生产机的多核 CPU。因为在任意给定时刻,一个进程只能跑在一个核上,哪怕它是多线程的。为了让 Firefox 以多进程方式运行,需要:<br>

<br>=over<br><br>=item 1.<br><br>在调用 firefox-bin 程序时指定 -no-remote 命令行选项,或者设置环境变量 MOZ_NO_REMOTE=1<br><br>=item 2.<br><br>以不同的 profile 运行不同的 firefox-bin 进程(利用 -P 命令行选项)。<br><br>=back<br><br>我们平常看到的 Firefox 的主窗口并不启动,而以 chrome 方式单独运行 List Hunter 插件的界面,例如:<br>

<br>&nbsp;&nbsp;&nbsp; firefox -chrome chrome://listhunter/content/crawler.xul -P crawler2 -no-remote<br><br>以 chrome 方式运行的插件与 XULRunner 方式运行的 XUL 应用是很类似的。<br><br>由于 Firefox 3.1 还没有正式发布,我直接 checkout 官方 Mercurial 源码仓库内的最新版本,自己在我们的 redhat 生产机上编译的。我们目前几乎没有修改官方的 C++ 源代码,为了方便和官方版本保持同步。我们目前使用的是下面的 firefox 编译选项:<br>

<br>&nbsp; # My .mozconfig<br>&nbsp; mk_add_options MOZ_MAKE_FLAGS=&quot;-j2&quot;<br>&nbsp; mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/ff-opt<br>&nbsp; ac_add_options --enable-crypto --enable-feeds --disable-profilesharing --enable-rdf --enable-zipwriter --disable-tests --disable-gnomeui --disable-cookies --disable-canvas --disable-gnomeui --disable-inspector-apis --disable-mailnews --disable-mathml --disable-official-branding --enable-plaintext-editor-only --disable-postscript --disable-printing --disable-profilelocking --disable-safe-browsing --disable-startup-notification --disable-svg --disable-svg-foreignobject --disable-updater --disable-javaxpcom --disable-plugins --disable-crashreporter --disable-tests --disable-debug --enable-application=browser --build=i686-linux --disable-jsd --disable-ldap --enable-strip --disable-accessibility --disable-ogg --disable-dbus --disable-freetype2 --disable-optimize<br>

<br>这里能禁用的功能我们都禁用了,这里的 feeds, rdf, crypto 这三个都不能 disable,否则源码编译不通过,会报一些 .h 头文件找不到,呵呵。--disable-ogg 实际上也不起作用,但网上的材料看曾经有效过,呵呵。<br><br>事实上,目前我们还是给官方的源码打了一个 C++ 补丁,用于将 Error Console 中的 Errors 重定向到 stderr,这样方便我们在集群环境下通过 Firefox 进程的 log 文件捕捉和诊断一些异常。目前的补丁是下面这个样子:<br>

<br>&nbsp;&nbsp;&nbsp; <a href="http://agentzh.org/misc/191src.patch.txt" target="_blank">http://agentzh.org/misc/191src.patch.txt</a><br><br>值得一提的是,Firefox 进程本身是&quot;无头&quot;的,即它运行在 Xvfb 这个 X server 之上,只在内存里执行渲染,而不需要任何显示硬件的存在。这些 Firefox 进程本身是挂在我们自己的一个 Perl 写的进程监控脚本之下。该脚本来自我们的 Proc::Harness 模块:<br>

<br>&nbsp;&nbsp;&nbsp; <a href="http://svn.openfoundry.org/xulapp/trunk/demo/ListHunter/misc/Proc-Harness/" target="_blank">http://svn.openfoundry.org/xulapp/trunk/demo/ListHunter/misc/Proc-Harness/</a><br><br>Proc::Harness 会像 lighttpd 的 Fastcgi server 一样,维护一组指定数目的进程(通过 CPAN 上的 Proc::Simple 模块)。当子进程挂掉后立即重启,或者当子进程的 stderr/stdout 输出不再变化一段时限之后也杀之重启。Proc::Harness 脚本自身则是挂在了 deamontools 之下。<br>

<br>这些 Firefox 进程受安装在其中的 List Hunter 插件的完全控制。它们都是高度自治的 robot。它们内部有一个处理循环,一批一批地从 OpenResty 的 web service 接口取到 URL 任务,然后一个一个地在 Firefox 的 browser 组件里加载和分析,最后把分析到的结果一批一批地通过 OpenResty 提交之。<br><br>=item curl 预取爬虫集群与 Apache mod_proxy 集群<br>

<br>该集群目前布署了 6 台双核的 redhat4 生产机。每台机器都安装了两个集群组件,一是预取器,一是 Apache mod_proxy. 预取器的作用是通过 curl (准确地说是 WWW::Curl 模块)将网页的 HTML 和 CSS 通过 mod_proxy 预取一遍,这样这些请求的结果就可以在 mod_proxy 中通过 mod_disk_cache 缓存住。于是当纯 Firefox 集群再通过 mod_proxy 去抓这些 URL 时,mod_proxy 就可以直接把缓存后的结果直接返回给 Firefox 了。<br>

<br>预取器和 Firefox 进程是同时工作的,但对于一个 URL 任务而言,只有通过预取器预取过之后,Firefox 进程才会进行处理。所以实际构成了一个两道工序的流水线。这种调度是由 OpenResty 集群来完成的。<br><br>预取器目前是以一个叫为 WWW::Prefetcher 的 Perl 模块的形式来实现的:<br><br>&nbsp;&nbsp;&nbsp; <a href="http://svn.openfoundry.org/xulapp/trunk/demo/ListHunter/misc/WWW-Prefetcher/" target="_blank">http://svn.openfoundry.org/xulapp/trunk/demo/ListHunter/misc/WWW-Prefetcher/</a><br>

<br>虽然 mod_cache 提供了许多选项,但其缓存行为还是比较遵守 RFC 的 cache 要求的。因此我对 mod_cache 模块进行了许多修改,使之可以无条件地缓存住请求过的所有网页,而不论其 URL 是否有 querystring,也不论其 response header 中的要求是什么。我们对最新的 httpd 2.2.11 的补丁如下:<br><br>&nbsp;&nbsp;&nbsp; <a href="http://agentzh.org/misc/httpd-2.2.11.patch.txt" target="_blank">http://agentzh.org/misc/httpd-2.2.11.patch.txt</a><br>

<br>特别地,mod_disk_cache 指向的不是磁盘目录,而是 RAM 中开辟的 tmpfs 分区。由于我们这 6 台机器都是很旧的 IDE 硬盘,因此直接用磁盘作 cache 存储时,高并发条件下,每台机器的 load 都在 20 以上,根本无法忍受。后来换为 tmpfs 结果 htcacheclean 工具之后,机器负载就降到 0.1 以下了。<br><br>=item OpenResty集群<br><br>由于 OpenResty 的通用性,我们直接复用了同时服务于 <a href="http://yahoo.cn" target="_blank">yahoo.cn</a> 和口碑网的那个生产集群,(3 台 FastCGI 前端机和1 台 PL/Proxy 机器),所以我就没有布署新的机器。在服务于 Firefox 集群的 OpenResty 接口中通过 View API 暴露了若干的 PostgreSQL 函数,以完成整个 List Hunter 集群的任务调度和结果汇总。目前的实现中,我们通过 Pg 的 sequence 摸拟了一种循环任务队列,并通过计数器完成流水线中两道工序之间的相对同步。<br>

<br>相关的 Pg 函数、sequence、以及索引的定义在这里:<br><br>&nbsp;&nbsp;&nbsp; <a href="http://svn.openfoundry.org/xulapp/trunk/demo/ListHunter/misc/init-db.sql" target="_blank">http://svn.openfoundry.org/xulapp/trunk/demo/ListHunter/misc/init-db.sql</a><br>
<br>相关的 OpenResty 对象的定义则在这里:<br>
<br>&nbsp;&nbsp;&nbsp; <a href="http://svn.openfoundry.org/xulapp/trunk/demo/ListHunter/misc/init-resty.pl" target="_blank">http://svn.openfoundry.org/xulapp/trunk/demo/ListHunter/misc/init-resty.pl</a><br><br>=back<br><br>=head1 集群的性能<br>
<br>集群目前每小时的产出稳定在 10 万网页以上,一天的产出在 240 万以上。Firefox 机器的负载在 3 左右,Proxy 的负载在 0.1 以下。<br>
<br>JS 基准测试显示 Firefox 3.1 加载一个页面的平均延时是 200 ~ 300 ms,机房间的网络延时在 10 ~ 20 ms(因为网页已被 mod_cache 缓存住,故无到外网的网络开销), List Hunter 插件的 DOM 分析代码的用时在 200 ~ 300 ms。其他 OpenResty 开销再计入,大约 1 sec 处理一个页面。<br><br>=head1 已知瓶颈和缺陷<br><br>当 OpenResty 中的 URL 任务表的行数超过 100 ~ 200 万时,调度查询容易超过 PL/Proxy 的 10 秒限制。因此,我们目前采取&quot;流式&quot;的任务导入和导出方式。通过 cronjob 定期地向库中导入任务,并同时把完成了的任务及时移出。<br>

<br>Apache 的 mod_proxy 在高并发条件下不够稳定,而且限于 Apache 自身的体系结构,无法实现 proxy pipelining. 因此计划在未来集群规模进一步扩大时,改用 Squid. 当然了,Squid 很可能也需要进行修改才能满足我们这里的强制缓存一段指定时间的需求。<br><br>同时,受限于 Apache mod_cache 后端的非分布式,代理服务器的调度是在 Firefox 进程和 curl 预取进程中完成的,导致前端代码比较复杂,还带来了代理服务器列表的定时同步问题。因此,未来可以考虑为 Apache mod_cache 或者 Squid 添加 memcached 缓存后端的支持。这样代理前端的多台服务器可以实现对集群内其他部件的&quot;透明化&quot;。<br>

<br>=head1 TODO<br><br>=over<br><br>=item 1.<br><br>换用 Squid + memcached 作为缓存用正向代理<br><br>=item 2.<br><br>通过 XULRunner 而非 firefox -chrome 方式运行 List Hunter 插件。(需要为我的 XUL::App 框架添加 XULRunner 支持)<br><br>=back<br><br>=head1 与相似产品的异同<br>

<br>美国雅虎通过大量修改 Firefox 2 的 C++ 源代码,开发了叫为 HLFS 的爬虫集群,用于爬取 AJAX 网站的内容以及得到带有视觉信息的 DOM 树。他们将 Firefox 进程做成了 HTTP 代理的形式,对外部应用提供服务。<br><br>而我们的 List Hunter 集群中的 Firefox 进程则是高度自治的爬虫,它们自己从 OpenResty 中不断地批量取任务去完成。而外部应用则是批量地向 OpenResty 导入任务来让集群运转。由于 List Hunter 集群并没怎么修改 Firefox 的源代码,这使得我们可以很容易地与官方最新版本保持同步,从而第一时享受到官方优化带来的众多好处。<br>

<br>同时 List Hunter 集群本身是通用目的的,它可以作为各种 Firefox 插件的&quot;集群容器&quot;。换言之,这是一种将 Firefox 插件&quot;集群化&quot;的完整的框架。<br><br>由于 Firefox 插件开发本身已经通过我发布到 CPAN 的 XUL::App 框架得到了极大的简化,所以响应新的需求的成本是非常低的。<br><br>=head1 使用 Firefox 的利与弊<br>
<br>=over<br><br>=item 优点<br>
<br>Firefox 是世界级的浏览器。作为最复杂功能最丰富的 Internet 客户端之一,我们将之作为爬虫可以享受到和最终用户一样的丰富功能,无论是 AJAX 还是视觉信息都不是问题。<br><br>Firefox 有基于 XUL 和 chrome JS 的灵活的插件机制,极易扩展。事实上,Firefox 主界面自身就是一个大插件。同时,Gecko 是基于 XPCOM 组件方式的,因此可以很容易地使用 C/C++/Java 等语言开发 XPCOM 组件,然后再用 JavaSscript 把它们粘合在一起。于是乎,JavaScript 成为了像 Perl 一样的胶水类语言。<br>

<br>运行于 Gecko 之上的插件 JavaScript 拥有最高权限,这种 JS 可以访问磁盘文件,可以访问系统环境变量,可以使用原生的 XmlHttpRequest 对象发出跨域 AJAX 请求。<br><br>Firefox 的性能随着新版本的发布总会有戏剧性的变化。Firefox 3.1 中的 Gecko 引擎的渲染速度就比 3.0 中的快了好几倍(根据 List Hunter 回归测试集的 benchmark 结果,前者为平均 60 ms,后者则长达 200+ ms)。(Firefox 3.1 中 TraceMonkey 的 JIT 支持倒并没有给 List Hunter 中的 JS 带来可测量的性能提升。)<br>

<br>纯 JS 写的 Firefox 插件可以在 Win32/Linux/Mac 多种操作系统上即装即用,所以方便和编辑及产品经理沟通行为细节,方便演示。如若计算过于复杂,亦可使用 C++ 语言改写插件中的计算密集的部分。<br><br>=item 缺点<br><br>Firefox 是高耦合的软件,这与 Google Chrome 及 Safari 浏览器的核心 Webkit 形成了鲜明对比。这意味着,我们比较难于对 Firefox 进行深层次的裁剪,无法轻易地免除一些比较大的功能部件,也很难将其中的某一个大部件剥出来单独使用(当然了,SpiderMonkey 是少数几个例外之一)。<br>

<br>=back<br><br>=head1 AUTHOR<br><br>章亦春 (agentzh) C&lt;&lt; &lt;<a href="mailto:agentzh@yahoo.cn" target="_blank">agentzh@yahoo.cn</a>&gt; &gt;&gt;<br><br>=head1 LICENSE<br><br>Copyright (c) 2007-2008, Yahoo! China EEEE Works, Alibaba Inc. All rights reserved.<br>

<br>Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:<br><br>=over<br><br>=item *<br><br>Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.<br>

<br>=item *<br><br>Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.<br>

<br>=item *<br><br>Neither the name of the Yahoo! China EEEE Works, Alibaba Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.<br>

<br>=back<br><br>THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.<br>

<br>
<br>_______________________________________________<br>
China-pm mailing list<br>
<a href="mailto:China-pm@pm.org">China-pm@pm.org</a><br>
<a href="http://mail.pm.org/mailman/listinfo/china-pm" target="_blank">http://mail.pm.org/mailman/listinfo/china-pm</a><br></blockquote></div><br><br clear="all"><br>-- <br>&gt;: ~<br>