当我上高中时, google只是一个名词,代表了一个非常庞大的数字。 今天,我们有时将google用作动词,与在线浏览和搜索同义,并且我们也用它来指代同名公司。 通常会调用“ Papa Google”作为几乎所有问题的答案:“只需使用Google!” 因此,应用程序用户希望能够搜索应用程序存储的数据(文件,日志,文章,图像等)。 对于软件开发人员而言,挑战在于如何快速,轻松地启用搜索功能,而又不会浪费太多的睡眠或金钱。
随着时间的流逝,用户查询变得越来越复杂和个性化,并且传递适当响应所需的许多数据本质上是非结构化的。 在曾经有一个SQL LIKE
子句足够好的地方,今天的用法有时需要复杂的算法。 幸运的是,许多开源和商业平台都满足了对可插拔搜索技术的需求,包括Lucene,Sphinx,Solr,Amazon的CloudSearch和Xapian。 Java开发2.0的这一部分引入了ElasticSearch,它是开放源代码搜索平台领域中的一个更新播放器。
首先,我将向您展示如何快速安装和配置ElasticSearch。 然后,我将向您展示如何定义搜索基础结构,添加可搜索的内容以及搜索该内容。 这些示例基于现有的应用程序(《 今日美国音乐评论》提要和API),但对于您正在构建的应用程序也可以很好地工作。 我们将使用ElasticSearch以及其他一些开源工具:cURL是用于处理HTTP URL的与平台无关的命令行工具,而Jest是为ElasticSearch构建的Java库,我们将使用它来捕获,存储,并操纵我们的数据。
使用ElasticSearch进行分布式搜索
ElasticSearch是许多开源搜索平台之一。 它的服务是为已经具有数据库和Web前端的应用程序提供附加组件(可搜索的存储库)。 ElasticSearch为您的应用程序提供搜索算法和相关基础架构。 您只需将应用程序数据上传到ElasticSearch数据存储中,并通过RESTful URL与之交互。 您可以通过cURL或Jest之类的库直接或间接地执行此操作。
ElasticSearch是可下载的应用程序。 一些基于云的平台已开始将其作为服务提供。 在本文中,我们将使用ElasticSearch作为可嵌入工具。
ElasticSearch的体系结构与其之前的体系有明显不同,因为它的构建明确考虑了水平缩放。 与某些其他搜索平台不同,ElasticSearch设计为分布式的。 随着云和大数据技术的兴起,此功能与之吻合得很好。 ElasticSearch建立在更稳定的开源搜索引擎之一Lucene之上,它的工作方式类似于无模式的JSON文档数据存储。 它的唯一目的是启用基于文本的搜索。
ElasticSearch易于安装并集成到您的应用程序中。 您可以使用RESTful API以您选择的语言与ElasticSearch进行交互。 它还带有由蓬勃发展的开源社区生产的大量语言适配器。
安装和配置ElasticSearch
由于ElasticSearch是基于Lucene构建的,因此其中的所有内容都可以归结为Java代码。 首先,只需下载最新版本的ElasticSearch ,将其取消存档,然后通过调用目标平台的启动脚本来启动它。 您会注意到,ElasticSearch提供了一系列配置,但是出于本文的目的,我们将坚持使用提供的默认值。 我们的示例并没有使节点彼此自动发现并创建集群(顺便说一句令人兴奋的功能),而是基于单个节点,该节点将充当文档数据库。
告诉我我喜欢什么
如前所述,用户期望能够搜索应用程序存储和处理的大多数数据。 因此,我们的工作示例所需的第一件事是一些数据。 为了使事情变得有趣,我们将使用《今日美国》中的数据,该数据可通过网站的API免费获得。 我将摘取《 今日美国》音乐评论的提要,并将其上传到ElasticSearch。 此过程通常称为索引编制 。
目前,USA Today的音乐评论未按特定流派或艺术家进行分类。 如果我要进行关联搜索 ,那就构成了挑战; 也就是说,如果我想为与我喜欢的其他艺术家相似的艺术家找到正面评价。 例如,我可能会搜索听起来像Buddy Guy的布鲁斯艺术家。
如果您想在我从《今日美国》中获取数据时跟着我,则需要在该站点上注册免费的开发人员密钥 。 完成此操作后,您可以通过RESTful URL访问API。 清单1显示了一个获取单个音乐评论的示例调用(请注意,您必须在代码中使用自己的开发人员密钥):
清单1.对“ 今日美国”音乐评论服务的API调用
curl-XGET 'http://api.usatoday.com/open/reviews/music/recent?count=1&api_key=your_key'
清单2显示了相应的JSON响应的样子:
清单2.服务的响应
{"APIParameters":
{"Count":"1","MinimumRating":"","MaximumRating":"","Artist":"",
"ArtistSearch":true,"Album":"",
"AlbumSearch":true,"Year":""},
"Found":1,"Albums":null,"Artists":null,
"MusicReviews":[
{"AlbumName":"Away From the World",
"ArtistName":"Dave Matthews Band",
"ReleaseDate":"",
"Rating":"3",
"DownloadSongs":"Mercy, Snow Outside, Drunken Soldier",
"ConsiderSongs":"",
"Reviewer":"Brian Mansfield",
"ReviewDate":"9/11/2012 10:11:00 AM",
"Brief":"...",
"WebUrl":"..."
}
]
}
因为我正在搜索可能喜欢的音乐,所以我希望捕获评论的至少三个部分: brief
(是音乐评论的核心), rating
和WebUrl
。 这使我可以查看个人评论,数字评分和URL,在其中可以自己查看音乐。
设置ElasticSearch索引
ElasticSearch使用RESTful Web界面进行交互。 我将使用命令行工具cURL访问该界面。 在将任何文档放入ElasticSearch之前,我需要创建一个index ,它类似于数据库表。 我将可搜索的文档(在本例中为音乐评论)存储在ElasticSearch索引中。 清单3展示了使用cURL创建ElasticSearch索引有多么容易。 (默认情况下,ElasticSearch捕获并索引您提供给它的每个文档。)
清单3.使用cURL创建一个ElasticSearch索引
curl -XPUT 'http://localhost:9200/music_reviews/'
接下来,我可以为文档的特定属性指定特定的映射。 系统会自动推断出特定属性。 例如,如果文档包含诸如name:'test'
类的值,ElasticSearch将推断name
属性为String
。 或者,如果文档具有属性score:1
,ElasticSearch将正确地猜测该score
是一个数字。
有时候,ElasticSearch确实会猜错,例如,对于格式为String
的日期。 在这些情况下,您可以指示ElasticSearch如何映射特定值。 在清单4中,我指示ElasticSearch将音乐评论的reviewDate
视为Date
而不是String
:
清单4.在music_reviews索引中的映射
curl -XPUT 'http://localhost:9200/music_reviews/_mapping' -d
'{"review": { "properties": {
"reviewDate":
{"type":"date", "format":"MM/dd/YY HH:mm:ss aaa", "store":"yes"} } } }'
清单4展示了通过cURL与ElasticSearch的RESTful AP交互有多么容易。
将数据捕获为POJO
我们已经定义了一个ElasticSearch索引并映射了一个特定的属性,因此现在该插入一些音乐评论了。 为此,我将使用一个名为Jest的Java API,它可以很好地处理Java对象序列化。 使用Jest,您可以获取普通的Java对象并将它们索引到ElasticSearch中。 然后,使用ElasticSearch的搜索API,您可以将搜索结果转换回Java对象。 自动POJO序列化非常方便,因为您不必处理ElasticSearch所需的底层JSON文档结构。
我将创建一个代表音乐评论的简单Java对象,然后使用Jest将其编入索引。 因为我最终从USA Today的API收到了音乐评论的JSON表示形式,所以我将编写一种工厂方法,该方法将JSON文档转换为我的对象。 我可以轻松地省略整个POJO步骤(并仅从USA Today索引纯JSON索引),但稍后我想向您展示如何自动将搜索结果转换为POJO。
清单5.代表音乐评论结果的简单POJO
import io.searchbox.annotations.JestId;
import net.sf.json.JSONObject;
public class MusicReview {
private String albumName;
private String artistName;
private String rating;
private String brief;
private String reviewDate;
private String url;
@JestId
private Long id;
public static MusicReview fromJSON(JSONObject json) {
return new MusicReview(
json.getString("Id"),
json.getString("AlbumName"),
json.getString("ArtistName"),
json.getString("Rating"),
json.getString("Brief"),
json.getString("ReviewDate"),
json.getString("WebUrl"));
}
public MusicReview(String id, String albumName, String artistName, String rating,
String brief,
String reviewDate, String url) {
this.id = Long.valueOf(id);
this.albumName = albumName;
this.artistName = artistName;
this.rating = rating;
this.brief = brief;
this.reviewDate = reviewDate;
this.url = url;
}
//...setters and getters omitted
}
请注意,在ElasticSearch中,每个索引文档都有一个id
,您可以将其视为主键。 您始终可以通过相应的id
获得特定文档。 因此,在Jest API中,我使用@JestId
批注将ElasticSearch文档id
与我的对象相关联,如清单5所示。 在这种情况下,我使用了USA Today API提供的ID。
JestClient
接下来,我将使用Jest调用USA Today API来返回评论集合,将这些JSON文档转换为MusicReview
对象,并将每个索引都索引到本地运行的ElasticSearch应用程序中。
从清单6中的Jest的API调用中可以看到,ElasticSearch旨在在集群中工作。 在这种情况下,我们只有一个要连接的服务器节点,但是值得注意的是,连接可以采用一列服务器地址。
清单6.使用Jest创建与ElasticSearch实例的连接
ClientConfig clientConfig = new ClientConfig();
Set<String> servers = new LinkedHashSet<String>();
servers.add("http://localhost:9200");
clientConfig.getServerProperties().put(ClientConstants.SERVER_LIST, servers);
一旦完全初始化了ClientConfig
对象,就可以创建JestClient
的实例,如清单7所示:
清单7.创建一个客户端对象
JestClientFactory factory = new JestClientFactory();
factory.setClientConfig(clientConfig);
JestClient client = factory.getObject();
通过指向我本地运行的ElasticSearch实例的连接,我准备从“ 今日美国”服务中获取一些(例如300个)音乐评论,并将它们编入索引。
清单8.在本地ElasticSearch实例中捕获音乐评论并将其编入索引
URL url =
new URL("http://api.usatoday.com/open/reviews/music/recent?count=300&api_key=_key_");
String jsonTxt = IOUtils.toString(url.openConnection().getInputStream());
JSONObject json = (JSONObject) JSONSerializer.toJSON(jsonTxt);
JSONArray reviews = (JSONArray) json.getJSONArray("MusicReviews");
for (Object jsonReview : reviews) {
MusicReview review = MusicReview.fromJSON((JSONObject) jsonReview);
client.execute(new Index.Builder(review).index("music_reviews")
.type("review").build());
}
注意清单8中 for
循环的最后一行。 这段代码接收我的MusicReview
POJO并将其索引到ElasticSearch中; 也就是说,它将POJO放置在music_reviews
索引中作为review
类型。 然后,ElasticSearch将使用此文档并对其进行一些认真的处理,以便以后可以搜索它的各个方面。
搜索非结构化数据
ElasticSearch的功能在于,它使您可以搜索非结构化数据。 非结构化数据的一个示例是音乐评论的简短部分:一段描述某些音乐的文本。 该摘要中包含大量数据,但是我们需要的是可以指示相似性的关键字。 这些关键字关联可以帮助搜索引擎仅返回用户正在寻找的结果。 在这种情况下,我正在根据自己喜欢的音乐寻找可能对我感兴趣的音乐。 因此,我将使用与描述某些我喜欢的音乐时所用的关键字相同的关键字来搜索描述过的音乐。
因此,例如,我可以在索引集合的brief
属性中搜索jazz一词(请注意,此搜索不区分大小写)。 在使用Jest运行搜索之前,我必须做一些事情。 首先,我必须通过QueryBuilder
类型创建一个词条查询。 然后,将其添加到Search
,它指向索引和类型。 还要注意,Jest从ElasticSearch获取JSON响应,并将其转换为MusicReview
的集合。
清单9.用Jest搜索
QueryBuilder queryBuilder = QueryBuilders.termQuery("brief", "jazz");
Search search = new Search(queryBuilder);
search.addIndex("music_reviews");
search.addType("review");
JestResult result = client.execute(search);
List<MusicReview> reviewList = result.getSourceAsObjectList(MusicReview.class);
for(MusicReview review: reviewList){
System.out.println("search result is " + review);
}
Java开发人员应该非常熟悉清单10中的搜索操作。 通过Jest使用POJO是一个简单的过程。 但是请注意,ElasticSearch是完全由RESTful驱动的,因此我们可以轻松地使用cURL进行相同的搜索,如下所示:
清单10.使用cURL搜索
curl -XGET 'http://localhost:9200/music_reviews/_search?pretty=true' -d
' {"explain": true, "query" : { "term" : { "brief" : "jazz" } }}'
JSON可能很难阅读,因此您始终可以将pretty=true
选项传递给任何搜索请求。 在清单10中,我还指定了ElasticSearch返回一个解释计划,说明如何执行搜索。 我通过向JSON文档中添加"explain":true
短语来实现此目的。
我在清单9和10中的搜索产生了10条结果(您的结果将根据您索引的文档数而有所不同)。 因此,此简单的搜索将300条评论缩减为10条,这可能是我感兴趣的。 但是请注意,评级范围为3.0到4.0。 一个更复杂的查询应该使我更接近我想听的顶级音乐。
添加范围和过滤器
在清单11中,我导入了一些方便的静态方法,这些方法使构建复杂的查询更加容易。 最终,我正在做一个查询,查找其brief
包含jazz一词且rating
在3.5到4.0之间的所有文档。 这将减少早期的搜索结果,并增加我找到适合我对爵士乐偏好的高品质音乐的机会。
清单11.使用Jest搜索范围和过滤器
import static org.elasticsearch.index.query.FilterBuilders.rangeFilter;
import static org.elasticsearch.index.query.QueryBuilders.filteredQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
//later in the code
QueryBuilder queryBuilder = filteredQuery(termQuery("brief", "jazz"),
rangeFilter("rating").from(3.5).to(4.0));
Search search = new Search(queryBuilder);
search.addIndex("music_reviews");
search.addType("review");
JestResult result = client.execute(search);
List<MusicReview> reviewList = result.getSourceAsObjectList(MusicReview.class);
for(MusicReview review: reviewList){
System.out.println("search result is " + review);
}
请记住,我可以使用cURL进行相同的精确搜索:
清单12.使用cURL搜索范围和过滤器
curl -XGET 'http://192.168.1.11:9200/music_reviews/_search?pretty=true' -d
'{"query": { "filtered" : { "filter" : { "range" : { "rating" :
{"from": 3.5, "to":4.0} } },
"query" : { "term" : { "brief" : "jazz" } } } }}'
最近的搜索进一步整理了我的搜索结果,并给我留下了一些有前途的专辑供您收听。 但是,如果我想更具体些怎么办? 之前,我提到我是布鲁斯吉他手Buddy Guy的粉丝。 因此,让我们看看如果将通配符添加到搜索中会发生什么,如清单13所示:
清单13.使用通配符搜索
import static org.elasticsearch.index.query.QueryBuilders.wildcardQuery;
//later in the code
QueryBuilder queryBuilder = filteredQuery(wildcardQuery("brief", "buddy*"),
rangeFilter("rating").from(3.5).to(4.0));
//see listing 12 for the template search and response
在清单13中,我正在寻找评分在3.5到4.0之间且摘要中包含buddy的任何评论。 我可能会得到一两个引用Buddy Guy的评论,在这种情况下,我几乎可以肯定会喜欢我听到的内容。 另一方面,我可以获得更多包含单词buddy的随机文档-这是通用通配符搜索的缺点。
在这种情况下,我的通配符得到了回报:我检索了两个文档,这些文档的评论表明蓝调风格的音乐受到了我最喜欢的吉他手的影响。 一天的工作还不错!
使用令牌分析器
在本文中,我就ElasticSearch的配置简化了一些事情。 我们尚未配置集群,也未真正更改其任何默认索引策略。 ElasticSearch可能比我已经展示的复杂得多。 例如,在定义索引映射时,可以配置如何索引特定字段。 如果需要,各种标记器策略将帮助您构建功能强大且复杂的搜索。 例如,在“今日美国” brief
元素的情况下,我们可以指定一个雪球分析器或一个关键字“ one”。 Snowball是一种令牌算法,可将单词转换为单词的基础,从而扩大了搜索范围。 (例如,将jazzy改为jazz 。)使用不同的分析器是微调应用程序搜索能力的绝佳方法。 使用ElasticSearch这样的搜索平台,这些选项就唾手可得,而无需您自己动手。
结论
搜索不再是可选的:它是大多数使用,生成或存储数据的应用程序的预期功能。 但是,并非所有人都希望成为搜索技术专家,特别是考虑到当今复杂搜索所基于的复杂算法范围很广。 了解现有的开源搜索平台可以为您节省大量时间和金钱,并使您可以花时间来微调软件的主要功能。
在这篇文章中。 我介绍了ElasticSearch,这是一个易于上手且可扩展的分布式搜索平台。 ElasticSearch的复杂性和易用性令人印象深刻,并且它对水平可伸缩性的支持为您的数据需求扩展提供了很多选择。 (最近没有人吗?)
翻译自: https://www.ibm.com/developerworks/java/library/j-javadev2-24/index.html