第18篇-用ElasticSearch索引MongoDB,一个简单的自动完成索引项目
我的Elasticsearch系列文章,逐渐更新中,欢迎关注
0A.关于Elasticsearch及实例应用
00.Solr与ElasticSearch对比
01.ElasticSearch能做什么?
02.Elastic Stack功能介绍
03.如何安装与设置Elasticsearch API
04.如果通过elasticsearch的head插件建立索引_CRUD操作
05.Elasticsearch多个实例和head plugin使用介绍
06.当Elasticsearch进行文档索引时,它是怎样工作的?
10.Kibana科普-作为Elasticsearhc开发工具
11.Elasticsearch查询方法
15.使用Django进行ElasticSearch的简单方法
16.关于Elasticsearch的6件不太明显的事情
17.使用Python的初学者Elasticsearch教程
18.用ElasticSearch索引MongoDB,一个简单的自动完成索引项目
19.Kibana对Elasticsearch的实用介绍
20.不和谐如何索引数十亿条消息
21.使用Django进行ElasticSearch的简单方法
另外Elasticsearch入门,我强烈推荐ElasticSearch新手搭建手册和这篇优秀的REST API设计指南 给你,这两个指南都是非常想尽的入门手册。
关于全文搜索
如今,在任何网站或应用程序中都具有搜索功能已经很普遍。这通常发生在具有大量信息要提供给用户的平台上。从拥有数千种不同类别产品的电子商务网站,到拥有数千篇文章的博客或新闻网站。每当客户/用户/阅读者访问此类网站时,他们都会自动趋向于找到一个搜索框,在其中可以键入查询以找到所需的特定文章/产品/内容。糟糕的搜索引擎会导致用户沮丧,他们很可能永远不会再回到我们的网站。
全文搜索为您每天在网站上使用的所有搜索框提供强大的功能,以查找所需的内容。每当您想在Amazon产品数据库中找到该蝙蝠侠手机壳时,或者当您在Youtube上搜索玩有激光灯视频的猫时。当然,这个庞大的网站还依赖其他许多功能来增强搜索引擎的功能,但是所有搜索的基础都是全文索引。也就是说,让我们看看这篇文章是关于什么的。
MongoDB的局限性
如果您快速进行Google搜索,
MongoDB full text
则会在MongoDB文档中发现支持全文搜索。那么,为什么我们要麻烦学习像Elastic Search这样的新复杂技术,又为什么要在我们的系统架构中引入新的复杂性呢?让我们看一下MongoDB文本搜索支持以找出原因。
我将假定您已经安装了MongoDB,并且您已经了解它的基础知识。如果是这种情况,请继续打开控制台,然后运行
mongo
命令以访问MongoDB控制台并创建一个名为的数据库
fulltext
$ mongo
$ use fulltext
switched to db fulltext
我们的测试数据库将存储文章,因此让我们添加一个称为的集合
articles
$ db.createCollection('articles')
'{ "ok" : 1 }'
现在,让我们添加一些对测试有用的文档。我们将插入标题和段落作为内容的文章。我从《纽约时报》的《交易手册》的两篇文章中摘录了几段。
$ db.articles.insert({
... title: 'Yahoo sale to Verizon',
... content: 'The sale is being done in two steps. The first step will be the transfer of any assets related to Yahoo business to a singular subsidiary. This includes the stock in the business subsidiaries that make up Yahoo that are not already in the single subsidiary, as well as the odd assets like benefit plan rights. This is what is being sold to Verizon. A license of Yahoo’s oldest patents is being held back in the so-called Excalibur portfolio. This will stay with Yahoo, as will Yahoo’s stakes in Alibaba Group and Yahoo Japan.'
... })
WriteResult({ "nInserted" : 1 })
$ db.articles.insert({
... title: 'Chinese Group to Pay $4.4 Billion for Caesars Mobile Games',
... content: 'In the most recent example in a growing trend of big deals for smartphone-based games, a consortium of Chinese investors led by the game company Shanghai Giant Network Technology said in a statement on Saturday that it would pay $4.4 billion to Caesars Interactive Entertainment for Playtika, its social and mobile games unit. Caesars Interactive is controlled by the owners of Caesars Palace and other casinos in Las Vegas and elsewhere.'
... })
WriteResult({ "nInserted" : 1 })
现在我们有了文档,我们需要使用MongoDB文本索引对它们进行索引。因此,让我们在集合的
title
和
content
字段中创建一个文本索引
articles
$ db.articles.createIndex ({
... title:'text',
... content:'text'
... })
{ “ createdCollectionAutomatically”:
false,
“ numIndexesBefore”:1,
“ numIndexesAfter”:2,
“ok”:1
}
索引已创建,现在是时候进行一些搜索以查看其进展了,让我们来看一下!
$ db.articles.find( { $text: { $search: "chinese" } } )
{ "_id" : ObjectId("579e0a35c6d02e54ad6fe556"), "title" : "Chinese Group to Pay $4.4 Billion for Caesars Mobile Games", "content" : "In the most recent example in a growing trend of big deals for smartphone-based games, a consortium of Chinese investors led by the game company Shanghai Giant Network Technology said in a statement on Saturday that it would pay $4.4 billion to Caesars Interactive Entertainment for Playtika, its social and mobile games unit. Caesars Interactive is controlled by the owners of Caesars Palace and other casinos in Las Vegas and elsewhere." }
很好,看来一切正常,我们搜寻了这个词,
chinese
并将其与有关中文小组的文章相匹配。现在让我们为MongoDB设置难度。假设我们要构建一个自动完成输入(在用户输入内容时会推荐用户的输入之一)。为此,如果我搜索单词,我将假设MongoDB将返回同一文章
$ db.articles.find( { $text: { $search: "chi" } } )
这是MongoDB对全文搜索功能的最大限制之一。问题在于它在单词级别上为文档建立索引,因此无法通过使用文本索引来完成所谓的
partial matching
这就是匹配单词的部分部分。
在这一点上,一个功能更强大的文本索引平台很有用。在我们的案例中,我选择了Elastic Search,主要是因为文档非常有用,并且它提供了开箱即用的RESTful API端点的完整集合,使测试变得非常容易。
弹性搜索
我们正在尝试做的
我只想指出,这篇文章只是您可以通过Elastic Search实现的一个小巧的简单示例。有关于它的书,所以我不希望您认为Elastic Search仅对实现自动完成输入有用。我只是发现它是一个易于理解的示例,它展示了Elastic如何帮助进行MongoDB无法提供给我们的复杂搜索。
这篇文章的第二个目的是展示如何将现有的MongoDB文档导入到ElasticSearch中的全文索引文档中。同样,自动完成示例很小,因此也可以在一篇文章中进行解释。如果您发现文本索引世界很有趣,请继续阅读有关ElasticSearch的更多信息(
ES
从现在开始)及其丰富的功能。
我不会在这里解释如何安装ES,因为该过程非常简单。由于ES是基于Java构建的,因此只需确保已安装Java和
JAVA_HOME
变量集即可。
- 一旦安装了ES,我们将遵循以下总体过程:
- 为我们的文档创建索引。
使用名为的工具将我们的MongoDB集合导入ES
- mongo-connector
。
将
mongo-connector
- ES中创建的索引迁移到我们在步骤1中创建的索引。
试用我们的新索引,看看在保持
mongo-connector
运行的同时,如何始终为文档建立索引。
创建ES索引
那么……我们如何创建一个性能比内置MongoDB文本索引更好的索引?我们需要在ES中配置什么?我们必须定义ES所说的
Analysis Chain
。简而言之,我们插入索引中的每个文档所经过的管道将被索引。
分析器由分析器组成。分析器是用于获取文档,对其进行分析和修改并将其传递给下一个文档的过滤器。例如,可能有一个分析器删除了所谓的停用词,这是非常常见的词,它们不提供任何有用的索引信息,例如
第一个负责在标记字符串之前清理字符串,例如通过剥离HTML标签。第二个负责将其拆分为术语,例如通过将字符串拆分为空格。最后一个工作是修改术语以优化索引目的,例如,通过删除停用词或将所有术语改为小写。
ES提供了不同的分析器,这些分析器可作为创建自定义分析器的起点,以更好地满足任何索引需求。ES提供的替代方法之一称为
edge_ngrams
分析器。要了解什么是边缘n-gram,我们首先需要了解什么是n-gram。正如n-gram维基百科页面所指出的:
n-gram是来自给定文本或语音序列的n个项目的连续序列
因此,假设您有单词
blueberry
根据ES文档,我们可以查看内容:
边缘n-gram固定在单词的开头
这意味着对于
blueberry
边缘n元语法为:
[b, bl, blu, blue, blueb, bluebe, blueber, blueberr, blueberry]
看看我们要去哪里?如果您的单词
blueberry
的边缘n-gram被索引,则可以轻松创建自动完成搜索模块。因为如果用户
将不再匹配,则自动完成选项将消失。
因此,这条边缘n-gram绝对应该成为索引的一部分,这就是我们如何定义它
{
“ filter”:{
“ autocomplete_filter”:{
“ type”:“ edge_ngram”,
“ min_gram”:3,
“ max_gram”:20
}
}
}
因此,通过这个json对象,我们定义了一个
filter
称为“ autocomplete_filter” 的令牌过滤器()。而且,我们说这将是一个
edge_ngram
过滤器,过滤器的大小从3克到20克不等。我之所以使用3作为最小值的原因是,对于非常大的数据库,使用unigram会大大降低性能,因为很多文档都会与搜索匹配。这就是为什么许多具有自动完成功能的网站要求用户键入至少三个字符,直到他们可以提出替代方案为止。
现在我们已经定义了令牌过滤器,我们需要定义我们的自定义分析器:
{
“ analyzer”:{
“ autocomplete”:{
“ type”:“ custom”,
“ tokenizer”:“ standard”,
“ filter”:[
“ lowercase”,
“ autocomplete_filter”
]
}
}
}
在这里,我们定义了一个
analyzer
称为“自动完成” 的自定义,我们告诉ES这将是一个自定义分析器,它将使用
standard
令牌生成器,并设置两个过滤步骤:(
lowercase
这是不言自明的),然后设置我们的custom
autocomplete_filter
。
现在我们定义了过滤器和分析器,让我们创建索引。抓住控制台并执行以下
curl
命令:
$ curl -H 'Content-Type: application/json' \
-X PUT http://localhost:9200/fulltext_opt \
-d \
"{ \
\"settings\": { \
\"number_of_shards\": 1, \
\"analysis\": { \
\"filter\": { \
\"autocomplete_filter\": { \
\"type\": \"edge_ngram\", \
\"min_gram\": 3, \
\"max_gram\": 20 \
} \
}, \
\"analyzer\": { \
\"autocomplete\": { \
\"type\": \"custom\", \
\"tokenizer\": \"standard\", \
\"filter\": [ \
\"lowercase\", \
\"autocomplete_filter\" \
] \
} \
} \
} \
} \
}"
{"acknowledged":true}
将
fulltext_opt
在端点URL告诉ES创建一个名为像新指标。之所以选择该名称,是因为我们的MongoDB集合名为
fulltext
,并且当我们第一次将其导入ES时,
fulltext
将自动创建一个索引。稍后,我们将所有文档从
fulltext
移至优化
fulltext_opt
索引。
我们在
fulltext_opt
索引中要做的最后一件事是创建映射。映射只是文档组。我们将创建一个名为的映射
articles
,
title
并
content
在其上定义属性:
$ curl -H 'Content-Type: application/json' \
-X PUT http://localhost:9200/fulltext_opt/_mapping/articles \
-d \
"{ \
\"articles\": { \
\"properties\": { \
\"title\": { \
\"type\": \"string\", \
\"analyzer\": \"autocomplete\" \
}, \
\"content\": { \
\"type\": \"string\" \
} \
} \
} \
}"
{"acknowledged":true}
您可以看到我们仅将
autocomplete
分析器用于该
title
属性。由于我们应该将其用于自动完成功能,因此对文章内容建立索引是没有意义的(除非您希望向用户建议文章内容……这很奇怪)。
该
acknowledged: true
响应意味着我们的指数已成功创建和映射添加。现在是时候将文档从我们的MongoDB导入到其中了。
从MongoDB导入ES
要导入我们的文档,我可以简单地将它们手动插入到我们的ES索引中(我的文章集中只有两个文档。问题是,在现实生活中,我们希望MongoDB和我们的索引保持同步,以便在任何时候创建一个新文档插入后,同一文档将在ES中建立索引。
对我们来说幸运的是,有一个工具可以满足
mongo-connector
我们的需求。甚至更好的是,它支持弹性搜索。我不会在mongo-connector中跳得太深。您可以在上一个链接中找到很多有关其工作原理的详细信息。让我们坚持这样的想法,它将使用MongoDB中的文档并将它们放入我们的ES索引中。
您可以使用Python软件包管理器安装mongo-connector
pip
。您需要安装
elastic2-doc-manager
,它将提供将MongoDB中的内容复制到ElasticSearch 2.X中的支持。
pip install elastic2-doc-manager下一步是将MongoDB服务器作为副本集启动。如果您不知道MongoDB中的副本集是什么,我也不会对此深究:)。要将MongoDB作为副本集运行,只需
--replSet
在启动它时传递选项并给副本集命名(
rs0
在这种情况下):
$ rs.initiate()
{
"info2" : "no configuration explicitly specified -- making one",
"me" : "mbp-mauricio:27017",
"ok" : 0,
"errmsg" : "couldn't initiate : can't find self in the replset config"
}
您所要做的就是服从并打开mongo shell,然后运行
rs.initiate()
。尝试初始化副本集时,可能会看到此错误消息:
$ rs.initiate()
{
"info2" : "no configuration explicitly specified -- making one",
"me" : "mbp-mauricio:27017",
"ok" : 0,
"errmsg" : "couldn't initiate : can't find self in the replset config"
}
问题在于,
mbp-mauricio
在这种情况下,副本无法找到具有名称的计算机。您所要做的就是转到
/etc/hosts
文件并添加一个条目:
127.0.0.1 [您的计算机名称]MongoDB已启动并正在运行,现在让我们启动ES。进入您的ES安装目录并运行:
$ ./bin/elastic
一切就绪,该运行mongo-connector了。
$ mongo-connector -m 127.0.0.1:27017 -t 127.0.0.1:9200 -d elastic2_doc_manager
您可以使用自定义数据替换参数,这只是默认的localhost实现。因此,在这里,我们基本上告诉mongo-connector使用MongoDB数据
localhost:27017
并将其发送到在上运行的ES实例
localhost:9200
。所有这些都将通过使用来完成
elastic2_doc_manager
。一段时间后(取决于您拥有的MongoDB数据库的数量和大小),您应该能够在ES实例中看到新索引。就我而言,这几乎是即时的,因为我的
fulltext
数据库中只有两个文档。因此,如果调用相应的ES端点以列出索引,则应看到以下内容:
$ curl localhost:9200/_cat/indices?v
health status index pri rep docs.count docs.deleted store.size pri.store.size
yellow open fulltext 5 1 2 0 10.9kb 10.9kb
yellow open fulltext_opt 1 1 0 0 159b 159b
如果MongoDB实例中有其他数据库,则可能会有更多条目。mongo-connector的优点在于它是超级可配置的,因此您可以告诉它要从哪个数据库导入哪些集合。
在索引之间移动文档
因此,我们现在有了两个索引,一个索引是由mongo-connector创建的,该索引尚未优化,但有两个文档,另一个索引是优化的,但为空。我们现在要做的就是在索引之间复制文档。再说一次,对我来说,手动插入它们会更简单,因为我只有两个文档,但是实际应用程序中有成千上万个文档。
为此目的有一个很棒的工具,
elasticdump
它使这项任务非常容易。您可以通过NPM安装它:
$ npm install -g elasticdump
使用elasticdump,您可以将分析器,映射和数据从一个ES索引导入另一个索引(甚至导入json文件)。在我们的案例中,我们不在乎分析器和映射,我们将仅导入数据,因为分析器和映射已在
fulltext_opt
索引中定义。
$ elasticdump \
--input=http://localhost:9200/fulltext \
--output=http://localhost:9200/fulltext_opt
Mon, 01 Aug 2016 01:21:10 GMT | starting dump
Mon, 01 Aug 2016 01:21:10 GMT | got 2 objects from source elasticsearch (offset: 0)
Mon, 01 Aug 2016 01:21:10 GMT | sent 2 objects to destination elasticsearch, wrote 2
Mon, 01 Aug 2016 01:21:10 GMT | got 0 objects from source elasticsearch (offset: 2)
Mon, 01 Aug 2016 01:21:10 GMT | Total Writes: 2
Mon, 01 Aug 2016 01:21:10 GMT | dump complete
现在,如果再次运行ES中的索引查询,您应该会看到
docs.count
fulltext_opt索引已更改为2而不是0:
$ curl localhost:9200/_cat/indices?v health status index pri rep docs.count docs.deleted store.size pri.store.size yellow open fulltext 5 1 2 0 10.9kb 10.9kb yellow open fulltext_opt 1 1 2 0 159b 159b
就是这样,我们的文档从一个索引复制到另一个索引。现在,您可以根据需要删除mongo-connector创建的索引。最后一步是尝试我们的新索引,看看它是否真的支持我们的自动完成功能的部分匹配:
curl -H 'Content-Type: application/json' \
localhost:9200/fulltext_opt/articles/_search?pretty \
-d "{ \"query\": { \"match\": { \"title\": { \"query\": \"chi\", \"analyzer\": \"standard\" } } } }"{
"took" : 12,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.3125,
"hits" : [ {
"_index" : "fulltext_opt",
"_type" : "articles",
"_id" : "579e0a35c6d02e54ad6fe556",
"_score" : 0.3125,
"_source" : {
"content" : "In the most recent example in a growing trend of big deals for smartphone-based games, a consortium of Chinese investors led by the game company Shanghai Giant Network Technology said in a statement on Saturday that it would pay $4.4 billion to Caesars Interactive Entertainment for Playtika, its social and mobile games unit. Caesars Interactive is controlled by the owners of Caesars Palace and other casinos in Las Vegas and elsewhere.",
"title" : "Chinese Group to Pay $4.4 Billion for Caesars Mobile Games"
}
} ]
}
}
瞧!回到我们的文档。请注意,我们在查询中定义了要使用的特定分析器,并将其设置为标准分析器:
{
title: {
query: "chi",
analyzer: "standard"
}
}
如果不这样做,则由于我们使用自定义分析器查询索引,因此
autocomplete
默认情况下它将使用分析器,并使用查询文本的边缘n-gram进行查询。这将导致不必要的结果,因为我们要
chi
专门搜索文本,而不是
c或ch或chi
。这就是为什么我们必须将分析仪显式设置为标准分析仪的原因。
处理新的MongoDB插入
到目前为止,我们已使用mongo-connector将所有MongoDB集合的内容移至fulltext_opt索引。您可能还记得,唯一的问题是mongo-connector从MongoDB复制到具有相同数据库名称的索引。这意味着,如果我们像现在一样保持mongo-connector的运行,则插入数据库的所有新文档都将
fulltext
在ES的索引中进行索引,而不是优化的
fulltext_opt
。
解决此问题的方法是配置更多的mongo-connector命令。您可以在此处找到许多配置选项。我们将使用其中的两个:
namespaces.include
namespaces.mapping
命令行)。您可以看到如何通过json文件配置mongo-connector,在这里我将仅使用命令行参数方式。
该
-n
选项将告诉mongo-connector我们要索引MongoDB中的哪些集合。语法为
database_name.collection_name
。在我们的例子中,我们想索引
fulltext
数据库中的所有文章。因此,我们将传递这样的命令行参数:
-n fulltext.articles
选项将告诉mongo-connector应该将使用该
选项定义的集合中的所有文档放入哪个索引。因此,在本例中,我们希望将所有文章放入
fulltext_opt
索引中。我们还需要
提供要在ES中使用的类型,因此完整的参数应为:
-g fulltext_opt.articles
,因为我们希望将文章与文章类型一起存储在索引中。
就是这样,现在我们可以运行如下命令:
$ mongo-connector -m 127.0.0.1:27017 -t 127.0.0.1:9200 -d elastic2_doc_manager -n fulltext.articles -g fulltext_opt.articles
如果让mongo-connector保持运行,则所有新插入的内容也会在ES中建立索引。继续,在articles集合中插入一个新文档,然后向ES索引发送查询,该文档应返回。
结论
以创建自动完成兼容索引为借口,我们学习了如何将MongoDB与Elastic Search混合使用,并使两者与
mongo-connector模块保持同步。