Elasticsearch使用一种叫做倒排索引(inverted index)的结构来做快速的全文搜索。倒排索引由在文档中出现的唯一的单词列表,以及对于每个单词在文档中的位置组成。
Elasticsearch中的数据可以大致分为两种类型:确切值 及 全文文本。确切值是确定的,正如它的名字一样。比如一个date或用户ID,也可以包含更多的字符串比如username或email地址。确切值"Foo"和"foo"就并不相同。确切值2014和2014-09-15也不相同。全文文本,从另一个角度来说是文本化的数据(常常以人类的语言书写),比如一篇推文(Twitter的文章)或邮件正文。
当在索引中处理数据时,我们注意到一些奇怪的事。有些东西似乎被破坏了:在索引中有12个tweets,只有一个包含日期2014-09-15,但是我们看看下面查询中的total hits。GET /_search?q=2014 # 12 个结果GET /_search?q=2014-09-15 # 还是 12 个结果 !GET /_search?q=date:2014-09-15 # 1 一个结果GET /_search?q=date:2014 # 0 个结果 !
映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型(string, number, booleans, date等)。分析(analysis)机制用于进行全文文本(Full Text)的分词,以建立供搜索用的反向索引。
search API有两种表单:一种是“简易版”的查询字符串(query string)将所有参数通过查询字符串定义,另一种版本使用JSON完整的表示请求体(request body),这种富搜索语言叫做结构化查询语句(DSL)查询字符串搜索对于在命令行下运行点对点(ad hoc)查询特别有用。例如这个语句查询所有类型为tweet并在tweet字段中包含elasticsearch字符的文档:GET /_all/tweet/_search?
《空搜索》一节告诉我们在集群中有14个文档匹配我们的(空)搜索语句。但是只有10个文档在hits数组中。我们如何看到其他文档?和SQL使用LIMIT关键字返回只有一页的结果一样,Elasticsearch接受from和size参数:size: 结果数,默认10from: 跳过开始的结果数,默认0如果你想每页显示5个结果,页码从1到3,那请求如下:GET /_search?size=5GET /_search?size=5&
你注意到空搜索的结果中不同类型的文档——user和tweet——来自于不同的索引——us和gb。通过限制搜索的不同索引或类型,我们可以在集群中跨所有文档搜索。Elasticsearch转发搜索请求到集群中平行的主分片或每个分片的复制分片上,收集结果后选择顶部十个返回给我们。
最基本的搜索API表单是空搜索(empty search),它没有指定任何的查询条件,只返回集群索引中的所有文档:GET /_search响应内容(为了编辑简洁)类似于这样:{ "hits" : { "total" : 14, "hits" : [ { "_index": "us", "_type": "tweet", "_id": "7", "_score": 1, "_source": …
到目前为止,我们已经学会了如何使用elasticsearch作为一个简单的NoSQL风格的分布式文件存储器——我们可以将一个JSON文档扔给Elasticsearch,也可以根据ID检索它们。但Elasticsearch真正强大之处在于可以从混乱的数据中找出有意义的信息——从大数据到全面的信息。这也是为什么我们使用结构化的JSON文档,而不是无结构的二进制数据。
当我们在《批量》一章中学习了批量请求后,你可能会问:“为什么bulk API需要带换行符的奇怪格式,而不是像mget API一样使用JSON数组?”为了回答这个问题,我们需要简单的介绍一下背景:批量中每个引用的文档属于不同的主分片,每个分片可能被分布于集群中的某个节点上。这意味着批量中的每个操作(action)需要被转发到对应的分片和节点上。
mget和bulk API与单独的文档类似。差别是请求节点知道每个文档所在的分片。它把多文档请求拆成每个分片的对文档请求,然后转发每个参与的节点。一旦接收到每个节点的应答,然后整理这些响应组合为一个单独的响应,最后返回给客户端。下面我们将罗列通过一个mget请求检索多个文档的顺序步骤:客户端向Node 1发送mget请求。
update API 结合了之前提到的读和写的模式。下面我们罗列执行局部更新必要的顺序步骤:客户端给Node 1发送更新请求。它转发请求到主分片所在节点Node 3。Node 3从主分片检索出文档,修改_source字段的JSON,然后在主分片上重建索引。如果有其他进程修改了文档,它以retry_on_conflict设置的次数重复步骤3,都未成功则放弃。
文档能够从主分片或任意一个复制分片被检索。下面我们罗列在主分片或复制分片上检索一个文档必要的顺序步骤:客户端给Node 1发送get请求。节点使用文档的_id确定文档属于分片0。分片0对应的复制分片在三个节点上都有。此时,它转发请求到Node 2。Node 2返回文档(document)给Node 1然后返回给客户端。
新建、索引和删除请求都是写(write)操作,它们必须在主分片上成功完成才能复制到相关的复制分片上。下面我们罗列在主分片和复制分片上成功新建、索引或删除一个文档必要的顺序步骤:客户端给Node 1发送新建、索引或删除请求。节点使用文档的_id确定文档属于分片0。它转发请求到Node 3,分片0位于这个节点上。
为了阐述意图,我们假设有三个节点的集群。它包含一个叫做bblogs的索引并拥有两个主分片。每个主分片有两个复制分片。相同的分片不会放在同一个节点上,所以我们的集群是这样的:我们能够发送请求给集群中任意一个节点。每个节点都有能力处理任意请求。每个节点都知道任意文档所在的节点,所以也可以将请求转发到需要的节点。
当你索引一个文档,它被存储在单独一个主分片上。Elasticsearch是如何知道文档属于哪个分片的呢?当你创建一个新文档,它是如何知道是应该存储在分片1还是分片2上的呢?进程不能是随机的,因为我们将来要检索文档。事实上,它根据一个简单的算法决定:shard = hash(routing) % number_of_primary_shardsrouting值是一个任意字符串,它默认是_id但也可以自定义。
在上一章,我们看到了将数据放入索引然后检索它们的所有方法。不过我们有意略过了许多关于数据是如何在集群中分布和获取的相关技术细节。这种使用和细节分离是刻意为之的——你不需要知道数据在Elasticsearch如何分布它就会很好的工作。这一章我们深入这些内部细节来帮助你更好的理解数据是如何在分布式系统中存储的。
现在你知道如何把Elasticsearch当作一个分布式的文件存储了。你可以存储、更新、检索和删除它们,而且你知道如何安全的进行这一切。这确实非常非常有用,尽管我们还没有看到更多令人激动的特性,例如如何在文档内搜索。但让我们首先讨论下如何在分布式环境中安全的管理你的文档相关的内部流程。
就像mget允许我们一次性检索多个文档一样,bulk API允许我们使用单一请求来实现多个文档的create、index、update或delete。这对索引类似于日志活动这样的数据流非常有用,它们可以以成百上千的数据为一个批次按序进行索引。bulk请求体如下,它有一点不同寻常:{ action: { metadata }}\n{ request body }\n{ action: { metadata }}\n{ request body }\n..
像Elasticsearch一样,检索多个文档依旧非常快。合并多个请求可以避免每个请求单独的网络开销。如果你需要从Elasticsearch中检索多个文档,相对于一个一个的检索,更快的方式是在一个请求中使用multi-get或者mget API。mget API参数是一个docs数组,数组的每个节点定义一个文档的_index、_type、_id元数据。
关注时代Java