在 “用 PHP 构建自定义搜索引擎” 中,我结合使用 PHP 与开源 Sphinx 搜索引擎创建了文本密集型数据库查询的快速备选方法,例如 MySQL 中的 LIKE 和 MATCH(要获得与 Sphinx 相关的信息,请参阅 参考资料)。
Sphinx 非常易于安装和维护,并且功能十分强大。而且,Sphinx 的最新版本现在提供了一个本机 MySQL 引擎,不需要运行独立的 Sphinx 守护进程。V0.9.8(撰写本文时的最新版本)还添加了地理距离(geodistance)查询,用于查找在给定位置的某个距离范围内的记录;并添加了名为多查询(multi-query)的功能,它是在单一网络连接中绑定多个查询和结果集的优化功能功能。
Sphinx 一直以来不断改进,并且是购物站点、博客和许多其他应用程序的理想选择。根据 Sphinx 站点的数据,一个应用程序现在将索引 700 百万个文档,或者索引大约 1.2 百万兆字节数据。因此,我毫不犹豫地向您推荐 Sphinx。
但是,当应用程序或站点变得越来越流行、使用量逐步增加时,Sphinx 可能无法提供相应的几项功能。尤其是,Sphinx 仍然不能够自动复制或分发其索引,使其守护进程成为单点故障(作为解决方法,几台计算机可以索引同一个数据库,并且可以将这些系统聚合在一起)。Sphinx 不突出显示搜索结果(如 Google 在显示缓存页面时所做的操作),不保留或缓存最近搜索到的结果,并且不支持正则表达式 (regex) 或与日期相关的操作。
如果要寻找那些功能或者准备采用企业级解决方案,请考虑 Apache Software Foundation 的 Solr 项目。基于 Lucene 搜索引擎并且在 Apache Software License 许可下以开源形式提供,Solr 是(根据 Lucene 站点)“基于 Lucene Java™ 搜索库、配有 XML/HTTP 和 JSON API、命中结果突出显示、分面组配式搜索、缓存、复制和 Web 管理界面的开源企业搜索服务器”。
其中值得注意的是,大流量的 Web 站点、Netflix、Digg 和 CNET 的 News.com 和 CNET Reviews 使用 Solr 来增强搜索功能。由 Solr 驱动的公共站点的长串列表可以在 Solr 维基中找到(请参阅 参考资料)。
了解如何使用 Solr 和 PHP 创建搜索汽车零部件数据库的小型应用程序。虽然示例数据库只是包含一些记录,但是它轻轻松松就能包含数百万条记录。本文中使用的所有源代码均可从 下载 部分获得。
要将 Solr 与 PHP 结合使用,您必须安装 Solr,设计索引,准备 Solr 要索引的数据,载入索引,编写执行查询的 PHP 代码和显示结果。创建可搜索索引所需的大部分工作可以通过命令行执行。当然,Solr 的 PHP 编程接口也会影响索引的内容。
Solr 是用 Java 技术实现的。要运行 Solr 及其管理工具,您必须安装 Java V1.5 软件开发包 (Java 5 SDK)。几个提供商都提供了 Java V1.5 SDK —— 例如,Sun Microsystems、IBM® 和 BEA Systems —— 并且每个实现都能够驱动 Solr。只需选择适用于您的操作系统的 Java 包并遵循相应的说明来完成安装。
在许多情况下,安装 Java V1.5 就像运行自提取归档和接受许可证协议条款一样简单。归档中的脚本在几秒钟内就能完成所有大部分艰巨任务。其他操作系统(例如 Debian)将在 APT 系统库中提供 Java 5 SDK。例如,如果使用 Debian 或 Ubuntu,则可以用 sudo apt-get install sun-java5-jdk 安装 Java V1.5 软件。
APT 还将自动下载使用 Java 5 SDK 所需的所有依赖性,非常方便。
如果 Java 软件已经安装并且 Java 可执行文件已在 PATH 中,请运行 java -version 来确定拥有的 Java 代码。
在这里,让我们使用 Mac OS X V10.5 Leopard 操作系统作为演示的基础。Apple 的 Leopard 附带了 Java V1.5。只要对 Apache 的默认配置进行微小更改,Leopard 也可以运行 PHP 应用程序。在 Leopard 终端窗口中运行 java -version 将生成以下输出。
清单 1. 在 Leopard 终端窗口中运行
java -version
$ which java
/usr/bin/java
$ java -version
java version "1.5.0_13"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_13-b05-237)
Java HotSpot(TM) Client VM (build 1.5.0_13-119, mixed mode, sharing)
|
注:Leopard 将允许您在 /Applications/Utilities/Java 的 Java Preferences 应用程序中的 Java V1.4 与 V1.5 之间来回切换。如果 Leopard 安装显示 V1.4,则打开 Java Preferences 并按照图 1 所示更改设置。
图 1. Leopard 中的 Java Preferences 应用程序
要安装 Solr,请访问 Apache.org,单击 Resources > Download,选择一个方便访问的项目镜像,并且在所示文件夹内浏览选择 Solr V1.2 的 tarball(.tgz 文件)。下载将传输名称类似 apache-solr-1.2.0.tgz 的文件。用下列代码解压缩 tarball。
清单 2. 解压缩 tarball
$ tar xzf apache-solr-1.2.0.tgz
$ ls -F apache-solr-1.2.0
CHANGES.txt NOTICE.txt dist/ lib/
KEYS.txt README.txt docs/ src/
LICENSE.txt build.xml example/
|
在新创建的目录中,名为 dist 的文件夹包含绑定为 Java 归档 (JAR) 的 Solr 代码。子目录 example/exampledocs 包含已经格式化的数据示例 —— 通常为 XML 代码 —— 并且准备好供 Solr 索引。
example 目录包含一个完整的样例 Solr 应用程序。要运行它,只需用应用程序归档 start.jar 启动 Java 引擎。
清单 3. 启动 Java 引擎
$ java -jar start.jar
2007-11-10 15:00:16.672::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
2007-11-10 15:00:16.866::INFO: jetty-6.1.3
...
INFO: SolrUpdateServlet.init() done
2007-11-10 15:00:18.694::INFO: Started SocketConnector @ 0.0.0.0:8983
|
应用程序现在可以在 8983 端口上使用。启动浏览器并在地址栏中键入 http://localhost:8983/solr/admin/。这是用于管理 Solr 的接口(要停止 Solr 服务器,请在命令行中键入 Ctrl+C 组合键)。
但是 Solr 索引中还没有数据可供管理或查询。
Solr 非常灵活,支持创建有效索引的各种数据类型和规则。而且虽然 Solr 支持数据类型和规则十分广泛,但是如果标准组件不够用,还可以通过编写新的 Java 类进一步自定义 Solr。
给定一组数据类型和规则,您就可以创建一个 Solr 模式来描述数据和控制应当怎样构造索引。然后导出数据来匹配模式并将数据装入 Solr。Solr 将动态创建索引,在记录被创建、修改或删除时立即更新每个索引。
可以在 Apache.org 的 Solr 源代码库中找到默认的 Solr 模式。为供参考,下面显示了默认模式的代码片段。
清单 3. 默认的 Solr 模式代码片段
<schema name="example" version="1.1">
...
<fields>
<field name="id" type="string" indexed="true" stored="true" required="true" />
<field name="name" type="text" indexed="true" stored="true"/>
<field name="nameSort" type="string" indexed="true" stored="false"/>
<field name="cat" type="text" indexed="true" stored="true" multiValued="true"/>
...
</fields>
<uniqueKey>id</uniqueKey>
...
<copyField source="name" dest="nameSort"/>
...
</schema>
|
模式的大部分内容都无需加以说明,但是有一些方面需要注意:
- 如示,字段
id是字符串 (type="string") 并且应当被索引 (indexed="true")。它也是一个必需字段 (required="true")。使用此模式,载入 Solr 的每条记录必须为这个字段提供值。<uniqueKey>id</uniqueKey>修饰词进一步说明id字段必须惟一(Solr 不要求 ID 字段惟一;这只是在默认索引模式中建立的规则)。属性stored="true"表示id字段应当可检索。为什么不把
stored设为false?您可以使用不可检索的字段来以不同方式给结果排序,比方说使用nameSort,它是name字段的副本(最后一行中的copyField命令),但是行为不同。注意,nameSort是string,而name是text。默认索引模式将采取稍微不同的方式来处理这两种类型。 - 字段
cat是multiValued。记录可以为此字段定义多个值。例如,如果应用程序管理内容,则可以给一篇文章指定多个标题。您可以使用cat字段(或者自定义类似字段)来捕捉所有标题。
清单 4 显示了 example/exampledocs/ipod_other.xml 文件,该文件表示 iPod 附件分类中的两个条目。
清单 4. 为默认 Solr 索引模式格式化的数据
<add>
<doc>
<field name="id">F8V7067-APL-KIT</field>
<field name="name">Belkin Mobile Power Cord for iPod w/ Dock</field>
<field name="manu">Belkin</field>
<field name="cat">electronics</field>
<field name="cat">connector</field>
<field name="features">car power adapter, white</field>
<field name="weight">4</field>
<field name="price">19.95</field>
<field name="popularity">1</field>
<field name="inStock">false</field>
</doc>
<doc>
<field name="id">IW-02</field>
<field name="name">iPod & iPod Mini USB 2.0 Cable</field>
<field name="manu">Belkin</field>
<field name="cat">electronics</field>
<field name="cat">connector</field>
<field name="features">car power adapter for iPod, white</field>
<field name="weight">2</field>
<field name="price">11.50</field>
<field name="popularity">1</field>
<field name="inStock">false</field>
</doc>
</add>
|
add 元素是用于将封装记录添加到索引中的 Solr 命令。每条记录都将被捕捉到 doc 元素中,该元素将使用一组名为 field 的元素来指定字段值。字段 weight、price、inStock、manu、features 和 popularity 都是在默认 Solr 索引模式中定义的其他字段。features 字段拥有与 cat 相同的属性,但是语义不同:它列举了产品的功能,数量可能较多。
本例将索引汽车零部件集。每个汽车零部件都有多个字段,表 1 中显示了最重要的字段样例。第一列中列出了字段名。第二列将提供简要描述,而第三列将列出逻辑类型。第四列将显示用于表示数据的索引类型(按照 清单 5 的模式中的定义)。
表 1. 汽车零部件记录的字段
| 名称 | 描述 | 类型 | Solr 类型 |
|---|---|---|---|
| 部件号(惟一,强制) | 标识号 | 字符串 |
partno
|
| 名称 | 简要描述 | 字符串 |
name
|
| 型号(必需,多值) | 型号,例如 “Camaro” | 字符串 |
model
|
| 型号年份(多值) | 型号年份,例如 2001 | 字符串 |
year
|
| 价格 | 单价 | 浮点 |
price
|
| 库存 | 是否有存货 | 布尔 |
inStock
|
| 功能 | 零部件的功能 | 字符串 |
features
|
| 时间标记 | 活动记录 | 字符串 |
timestamp
|
| 重量 | 装运重量 | 浮点 |
weight
|
清单 3 显示了汽车零部件索引所使用的 Solr 模式部分。它大部分都是基于默认 Solr 模式。使用的具体字段 —— 名称和属性 —— 只是替换了在默认模式中找到的 fields 元素(如 清单 1 中所示)。
清单 5. 汽车零部件索引模式
<?xml version="1.0" encoding="utf-8" ?>
<schema name="autoparts" version="1.0">
...
<fields>
<field name="partno" type="string" indexed="true"
stored="true" required="true" />
<field name="name" type="text" indexed="true"
stored="true" required="true" />
<field name="model" type="text_ws" indexed="true" stored="true"
multiValued="true" required="true" />
<field name="year" type="text_ws" indexed="true" stored="true"
multiValued="true" omitNorms="true" />
<field name="price" type="sfloat" indexed="true"
stored="true" required="true" />
<field name="inStock" type="boolean" indexed="true"
stored="true" default="false" />
<field name="features" type="text" indexed="true"
stored="true" multiValued="true" />
<field name="timestamp" type="date" indexed="true"
stored="true" default="NOW" multiValued="false" />
<field name="weight" type="sfloat" indexed="true" stored="true" />
</fields>
<uniqueKey>partno</uniqueKey>
<defaultSearchField>name</defaultSearchField>
</schema>
|
针对以上字段,需要将汽车零部件数据库导出并格式化以上传到 Solr 中,如清单 6 所示。
清单 6. 为进行索引而格式化的汽车零部件数据库
<add>
<doc>
<field name="partno">1</field>
<field name="name">Spark plug</field>
<field name="model">Boxster</field>
<field name="model">924</field>
<field name="year">1999</field>
<field name="year">2000</field>
<field name="price">25.00</field>
<field name="inStock">true</field>
</doc>
<doc>
<field name="partno">2</field>
<field name="name">Windshield</field>
<field name="model">911</field>
<field name="year">1991</field>
<field name="year">1999</field>
<field name="price">15.00</field>
<field name="inStock">false</field>
</doc>
</add>
|
让我们安装新索引模式并把数据装入 Solr。首先,通过使用 Ctrl+C 组合键来停止 Solr 守护进程(如果它仍在运行)。在 example/solr/conf/schema.xml 中制作现有 Solr 模式的归档。接下来,通过清单 6 创建一个文本文件,将其保存到 /tmp/schema.xml 中,然后复制到 example/solr/conf/schema.xml 中。为清单 7 中所示的数据创建另一个文件。现在,您可以重新启动 Solr 并使用示例所提供的 posting 实用程序。
清单 7. 启用带有新模式的 Solr
$ cd apache-solr-1.2/example
$ cp solr/conf/schema.xml solr/conf/default_schema.xml
$ chmod a-w solr/conf/default_schema.xml
$ vi /tmp/schema.xml
...
$ cp /tmp/schema.xml solr/conf/schema.xml
$ vi /tmp/parts.xml
...
$ java -jar start.jar
...
2007-11-11 16:56:48.279::INFO: Started SocketConnector @ 0.0.0.0:8983
$ java -jar exampledocs/post.jar /tmp/parts.xml
SimplePostTool: version 1.2
SimplePostTool: WARNING: Make sure your XML documents are encoded in UTF-8,
other encodings are not currently supported
SimplePostTool: POSTing files to http://localhost:8983/solr/update...
SimplePostTool: POSTing file parts.xml
SimplePostTool: COMMITting Solr index changes...
|
成功!如果需要检验索引是否存在并包含两个文档,请将浏览器再次指向 http://localhost:8983/solr/admin/。您应当会在页面顶部看到 “(autoparts)”。如果看到了,请单击页面中间的查询框并键入 partno: 1 or partno: 2。
结果应当类似下面的内容:
3 on 10 0 partno: 1 OR partno: 2 2.2 true Boxster 924 Spark plug 1 25.0 2007-11-11T21:58:45.899Z 1999 2000 false 911 Windshield 2 15.0 2007-11-11T21:58:45.953Z 1991 1999 |
尝试其他一些查询。Lucene 维基中介绍了 Lucene 查询(Solr 中的搜索引擎)的语法(请参阅 参考资料)。
您还应当尝试再次编辑和载入数据。由于声明了 partno 字段惟一,因此重复上传同一个部件号将只用新记录替换旧索引记录。除了 add 命令之外,您还可以使用 commit、optimize 和 delete。最后一个命令可以按照 ID 删除一条特定记录,也可以通过查询删除多条记录。
最后,PHP 将参与到这个示例中。
至少有两个 PHP Solr API。最健壮的实现是 Donovan Jimenez 的 PHP Solr Client(请参阅 参考资料)。代码是在与 Solr 相同的条款下许可开发的,它有扩展文档,并且与 Solr V1.2 兼容。撰写本文时的最新版本是在 2007 年 10 月 2 日发布的。
Solr Client 将提供四个 PHP 类:
-
Apache_Solr_Service表示 Solr 服务器。使用这些方法来 ping 服务器,添加和删除文档,提交更改,优化索引,以及运行查询。 -
Apache_Solr_Document收录 Solr 文档。该类的方法将管理(关键字,值)对和多值字段。字段值可以通过直接解除引用来访问,例如$document->title = 'Something'; ... echo $document->title;。 -
Apache_Solr_Response封装 Solr 响应。这段代码依赖于json_decode()函数,该函数是 PHP V5.2.0 和更高版本附带的,也可以用 PHP Extension Community Library(PECL —— 请参阅 参考资料)来安装。 -
Apache_Solr_Service_Balancer将增强Apache_Solr_Service,允许您连接到一个分发中的多项 Solr 服务。本文中没有介绍该类。
下载 PHP Solr Client(请参阅 参考资料)并将其解压缩到工作目录中。切换到 SolrPhpClient。接下来,查看文件 Apache/Solr/Service.php。撰写本文时,第 335 行缺少了拖尾分号。编辑该文件并添加分号(如果有必要)。另请查看文件 Apache/Solr/Document.php。在第 112 行至第 117 行中应当读出如下内容。
if (!is_array($this->_fields[$key]))
{
$this->_fields[$key] = array($this->_fields[$key]);
}
$this->_fields[$key][] = $value;
|
更正文件后,可以在其他 PHP 库旁边安装 Apache 目录。
下列代码显示了连接 Solr 服务、向索引中添加两个文档和运行先前使用的零部件号查询的 PHP 应用程序。
清单 8. 用于连接、载入和查询 Solr 索引的样例 PHP 应用程序
<?php
require_once( 'Apache/Solr/Service.php' );
//
//
// Try to connect to the named server, port, and url
//
$solr = new Apache_Solr_Service( 'localhost', '8983', '/solr' );
if ( ! $solr->ping() ) {
echo 'Solr service not responding.';
exit;
}
//
//
// Create two documents to represent two auto parts.
// In practice, documents would likely be assembled from a
// database query.
//
$parts = array(
'spark_plug' => array(
'partno' => 1,
'name' => 'Spark plug',
'model' => array( 'Boxster', '924' ),
'year' => array( 1999, 2000 ),
'price' => 25.00,
'inStock' => true,
),
'windshield' => array(
'partno' => 2,
'name' => 'Windshield',
'model' => '911',
'year' => array( 1999, 2000 ),
'price' => 15.00,
'inStock' => false,
)
);
$documents = array();
foreach ( $parts as $item => $fields ) {
$part = new Apache_Solr_Document();
foreach ( $fields as $key => $value ) {
if ( is_array( $value ) ) {
foreach ( $value as $datum ) {
$part->setMultiValue( $key, $datum );
}
}
else {
$part->$key = $value;
}
}
$documents[] = $part;
}
//
//
// Load the documents into the index
//
try {
$solr->addDocuments( $documents );
$solr->commit();
$solr->optimize();
}
catch ( Exception $e ) {
echo $e->getMessage();
}
//
//
// Run some queries. Provide the raw path, a starting offset
// for result documents, and the maximum number of result
// documents to return. You can also use a fourth parameter
// to control how results are sorted and highlighted,
// among other options.
//
$offset = 0;
$limit = 10;
$queries = array(
'partno: 1 OR partno: 2',
'model: Boxster',
'name: plug'
);
foreach ( $queries as $query ) {
$response = $solr->search( $query, $offset, $limit );
if ( $response->getHttpStatus() == 200 ) {
// print_r( $response->getRawResponse() );
if ( $response->response->numFound > 0 ) {
echo "$query <br />";
foreach ( $response->response->docs as $doc ) {
echo "$doc->partno $doc->name <br />";
}
echo '<br />';
}
}
else {
echo $response->getHttpStatusMessage();
}
}
?>
|
首先,代码将连接到给定端口和路径上名为 Solr 的服务器,并将使用 ping() 方法来检验服务器是否可运行。
接下来,代码将把表示为 PHP 数组的记录转换为 Solr 文档。如果字段有单个值,则简单的访问程序将把(关键字,值)对添加到文档中。如果字段有多个值,则用特殊函数 setMultiValue() 把值列表赋给关键字。您可以看到此过程非常类似于 Solr 文档的 XML 表示。
作为一项优化,addDocuments() 将把多个文档插入索引。后续的 commit() 和 optimize() 函数将完成添加操作。
在底部,多个查询将从索引中检索数据。您可以通过两个函数查看结果:getRawResponse() 函数将生成完整的未解析结果,而 docs() 函数将返回带有指定访问程序的文档数组。
如果查询未能从 Solr 获得确认,代码将输出错误消息。空结果集将不产生输出。
Solr 令人难以置信地强大,并且使用 PHP API 可以迅速地与任何平台集成。但更棒的是,Solr 易于安装和运行,并且可以根据需要启用高级功能。最棒的是,Solr 是免费的。不用购买搜索引擎。省下您的钱,使用 Solr 吧。
访问 Solr Web 站点了解包括排序、分类结果和复制在内的高级配置的更多信息。Lucene Web 站点是另一个信息源,因为它是 Solr 系统下的搜索技术。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 样例 PHP 和 Solr 应用程序 | os-php-apachesolr.src.zip | 109KB | HTTP |
学习
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文。
- 阅读 “为 PHP 应用提速、提速、再提速!” 系列。
- 在 Solr 专家 Grant Ingersoll 所撰写的 “使用 Apache Solr 实现更加灵巧的搜索,第 1 部分:基本特性和 Solr 模式” 和 “第 2 部分: 用于企业的 Solr” 阅读关于 Solr 的详细介绍。
- 参阅 Martin Streicher 撰写的标题为 “用 PHP 构建自定义搜索引擎” 的文章,这篇文章介绍了经过良好调整的本地搜索系统。
- 访问位于 Apache.org 的 Solr 查找资源和有价值信息。
- 查阅 Solr 维基,这是保存了关于 Solr 的大量文档的主页。
- 搜索哪些 公共 Web 站点使用 Solr。
- 查看 Lucene 维基。
-
PHP.net 是 PHP 开发者的重要资源。
- 查阅 “PHP 推荐读物列表”。
- 浏览 developerWorks 上的全部 PHP 文章和PHP 教程。
- 查看 IBM developerWorks 的 PHP 项目资源 扩展 PHP 技巧。
- 收听针对软件开发人员的有趣访谈和讨论,一定要访问 developerWorks podcast。
- 要将数据库与 PHP 结合使用?查看 Zend Core for IBM,它是一个无缝的、可以立即使用、易于安装、支持 IBM DB2 V9 的 PHP 开发和生产环境。
- 随时关注 developerWorks 的 技术事件和网络广播。
- 查阅最近将在全球举办的面向 IBM 开放源码开发人员的研讨会、交易展览、网络广播和其他 活动。
- 访问 developerWorks 开放源码专区,获得丰富的 how-to 信息、工具和项目更新,帮助您用开放源码技术进行开发,并与 IBM 产品结合使用。
- 查看免费的 developerWorks On demand demo 观看并了解 IBM 及开源技术和产品功能。
获得产品和技术
- 从一个项目镜像中下载 Solr。
- 了解更多信息并下载 Sphinx 搜索引擎。
- 下载 Donovan Jimenez 的 PHP Solr Client。
- 访问 PECL 系统库,这是所有已知扩展和托管用于下载和开发 PHP 扩展的工具的第一站。
- 使用 IBM 试用软件,改进您的下一个开发项目,这些软件可以通过下载或从 DVD 中获得。
- 下载 IBM 产品评估版,并开始使用 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- 参与 developerWorks blog 并加入 developerWorks 社区。
- 加入 developerWorks PHP Forum: Developing PHP applications with IBM Information Management products (DB2, IDS)。
