映射和分析
- 路由文档到分片
Elasticsearch是如何知道文档属于哪个分片的呢?
进程不能是随机的,它根据一个简单的算法决定
shard = hash(routing) % number_of_primary_shards
# routing 值是一个任意字符串,它默认是 _id 但也可以自定义。
# 这个 routing 字符串通过哈希函数生成一个数字,然后除以主切片的数量得到一个余数(remainder),余数的范围永远是 0 到 number_of_primary_shards - 1 ,这个数字就是特定文档所在的分片。
这也解释了为什么主分片的数量只能在创建索引时定义且不能修改:如果主分片的数量在未来改变了,所有先前的路由值就失效了,文档也就永远找不到了。
所有的文档API(get、 index、 delete、 bulk、 update、 mget)都接收一个routing参数,它用来自定义文档到分片的映射。
自定义路由值可以确保所有相关文档被保存在同一分片上。
- 主分片和复制分片如何交互
假设有三个节点的集群。它包含一个叫做 bblogs 的索引并拥有两个主分片。每个主分片有两个复制分片。相同的分片不会放在同一个节点上.
能够发送请求给集群中任意一个节点。每个节点都有能力处理任意请求。每个节点都知道任意文档所在的节点,所以也可以将请求转发到需要的节点。
发送所有请求给其中一个节点,这个节点我们将会称之为请求节点(requesting node)
当发送请求时,最好的做法是循环通过所有节点请求,这样可以平衡负载。
- 新建、索引和删除文档
新建、索引和删除请求都是写(write)操作,它们必须在主分片上成功完成才能复制到相关的复制分片上。
下面我们罗列在主分片和复制分片上成功新建、索引或删除一个文档必要的顺序步骤:
1. 客户端给 Node 1 发送新建、索引或删除请求。
2. 节点使用文档的 _id 确定文档属于分片 0 。它转发请求到 Node 3 ,分片 0位于这个节点上。
3. Node 3 在主分片上执行请求,如果成功,它转发请求到相应的位于 Node 1 和 Node 2 的复制节点上。当所有的复制节点报告成功, Node 3 报告成功到请求的节点,请求的节点再报告给客户端。
客户端接收到成功响应的时候,文档的修改已经被应用于主分片和所有的复制分片。你的修改生效了。
replication
复制默认的值是 sync 。这将导致主分片得到复制分片的成功响应后才返回。
如果设置 replication 为 async ,请求在主分片上被执行后就会返回给客户端。它依旧会转发请求给复制节点,但你将不知道复制节点成功与否。
这个选项不建议使用。默认的sync复制允许Elasticsearch强制反馈传输。 async复制可能会因为在不等待其它分片就绪的情况下发送过多的请求而使Elasticsearch过载。
consistency
默认主分片在尝试写入时需要规定数量(quorum)或过半的分片(可以是主节点或复制节点)可用。这是防止数据被写入到错的网络分区。规定的数量计算公式如下:
int( (primary + number_of_replicas) / 2 ) + 1
consistency允许的值为one(只有一个主分片),all(所有主分片和复制分片)或者默认的 quorum 或过半分片。
注意 number_of_replicas 是在索引中的的设置,用来定义复制分片的数量,而不是现在活动的复制节点的数量。如果你定义了索引有3个复制节点,那规定数量是:
int( (primary + 3 replicas) / 2 ) + 1 = 3
但如果你只有2个节点,那你的活动分片不够规定数量,也就不能索引或删除任何文档。
timeout
当分片副本不足时会怎样?Elasticsearch会等待更多的分片出现。默认等待一分钟。如果需要,你可以设置 timeout 参数让它终止的更早: 100表示100毫秒, 30s表示30秒。
新索引默认有1个复制分片,这意味着为了满足 quorum 的要求需要两个活动的分片。当然,这个默认设置将阻止我们在单一节点集群中进行操作。为了避开这个问题,规定
数量只有在 number_of_replicas 大于一时才生效。
- 检索文档
文档能够从主分片或任意一个复制分片被检索。
下面我们罗列在主分片或复制分片上检索一个文档必要的顺序步骤:
1. 客户端给 Node 1 发送get请求。
2. 节点使用文档的 _id 确定文档属于分片 0 。分片 0 对应的复制分片在三个节点上都有。此时,它转发请求到 Node 2 。
3. Node 2 返回文档(document)给 Node 1 然后返回给客户端。
对于读请求,为了平衡负载,请求节点会为每个请求选择不同的分片——它会循环所有分片副本。
可能的情况是,一个被索引的文档已经存在于主分片上却还没来得及同步到复制分片上。这时复制分片会报告文档未找到,主分片会成功返回文档。一旦索引请求成功返回给用户,文档则在主分片和复制分片都是可用的。
- 局部更新文档
update API 结合了之前提到的读和写的模式.
下面我们罗列执行局部更新必要的顺序步骤:
1. 客户端给 Node 1 发送更新请求。
2. 它转发请求到主分片所在节点 Node 3 。
3. Node 3 从主分片检索出文档,修改 _source 字段的JSON,然后在主分片上重建索引。如果有其他进程修改了文档,它以 retry_on_conflict 设置的次数重复步骤3,都未成功则放弃。
4. 如果 Node 3 成功更新文档,它同时转发文档的新版本到 Node 1 和 Node 2 上的复制节点以重建索引。当所有复制节点报告成功, Node 3 返回成功给请求节点,然后返回给客户端。
update API还接受routing 、 replication 、 consistency 和 timout 参数。
基于文档的复制
当主分片转发更改给复制分片时,并不是转发更新请求,而是转发整个文档的新版本。记住这些修改转发到复制节点是异步的,它们并不能保证到达的顺序与发送相同。
如果Elasticsearch转发的仅仅是修改请求,修改的顺序可能是错误的,那得到的就是个损坏的文档。
- 多文档模式
mget 和 bulk API与单独的文档类似。差别是请求节点知道每个文档所在的分片。它把多文档请求拆成每个分片的对文档请求,然后转发每个参与的节点。一旦接收到每个节点的应答,然后整理这些响应组合为一个单独的响应,最后返回给客户端。
下面我们将罗列通过一个 mget 请求检索多个文档的顺序步骤:
1. 客户端向 Node 1 发送 mget 请求。
2. Node 1 为每个分片构建一个多条数据检索请求,然后转发到这些请求所需的主分片或复
制分片上。当所有回复被接收, Node 1 构建响应并返回给客户端。
routing 参数可以被 docs 中的每个文档设置。
下面我们将罗列使用一个 bulk 执行多个 create 、 index 、 delete 和 update 请求的顺序步骤:
1. 客户端向 Node 1 发送 bulk 请求。
2. Node 1 为每个分片构建批量请求,然后转发到这些请求所需的主分片上。
3. 主分片一个接一个的按序执行操作。当一个操作执行完,主分片转发新文档(或者删除
部分)给对应的复制节点,然后执行下一个操作。一旦所有复制节点报告所有操作已成
功完成,节点就报告success给请求节点,后者(请求节点)整理响应并返回给客户端。
bulk API还可以在最上层使用 replication 和 consistency 参数, routing 参数则在每个请求的元数据中使用。
- 奇怪的格式
为什么 bulk API需要带换行符的奇怪格式,而不是像 mget API一样使用JSON数组?
批量中每个引用的文档属于不同的主分片,每个分片可能被分布于集群中的某个节点上。这意味着批量中的每个操作(action)需要被转发到对应的分片和节点上。
如果每个单独的请求被包装到JSON数组中,那意味着我们需要:
- 解析JSON为数组(包括文档数据,可能非常大)
- 检查每个请求决定应该到哪个分片上
- 为每个分片创建一个请求的数组
- 序列化这些数组为内部传输格式
- 发送请求到每个分片
这可行,但需要大量的RAM来承载本质上相同的数据,还要创建更多的数据结构使得JVM花更多的时间执行垃圾回收。
取而代之的,Elasticsearch则是从网络缓冲区中一行一行的直接读取数据。它使用换行符识别和解析action/metadata行,以决定哪些分片来处理这个请求。
这些行请求直接转发到对应的分片上。这些没有冗余复制,没有多余的数据结构。整个请求过程使用最小的内存在进行。
- 搜索——基本的工具
Elasticsearch不只会存储(store)文档,也会索引(indexes)文档内容来使之可以被搜索。
每个文档里的字段都会被索引并被查询。
映射(Mapping) :数据在每个字段中的解释说明
分析(Analysis) :全文是如何处理的可以被搜索的
领域特定语言查询(Query DSL) :Elasticsearch使用的灵活的、强大的查询语言
- 空搜索
最基本的搜索API表单是空搜索(empty search),它没有指定任何的查询条件,只返回集群索引中的所有文档:
GET /_search
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "web",
"_type" : "pageviews",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"views" : 2
}
}
]
}
}
hits
响应中最重要的部分是 hits ,它包含了 total 字段来表示匹配到的文档总数, hits 数组还包含了匹配到的前10条数据。
hits 数组中的每个结果都包含 _index
、 _type
和文档的 _id
字段,被加入到 _source
字段中这意味着在搜索结果中我们将可以直接使用全部文档。
每个节点都有一个 _score
字段,这是相关性得分(relevance score)。它衡量了文档与查询的匹配程度。默认的,返回的结果中关联性最大的文档排在首位;这意味着,它是按照 _score
降序排列的。这种情况下,我们没有指定任何查询,所以所有文档的相关性是一样的,因此所有结果的 _score
都是取得一个中间值 1.max_score 指的是所有文档匹配查询中_score
的最大值。
took
took 告诉我们整个搜索请求花费的毫秒数。
shards
_shards
节点告诉我们参与查询的分片数( total 字段),有多少是成功的( successful 字段),有多少的是失败的( failed 字段)。通常我们不希望分片失败,不过这个有可能发生。如果我们遭受一些重大的故障导致主分片和复制分片都故障,那这个分片的数据将无法响应给搜索请求。这种情况下,Elasticsearch将报告分片 failed ,但仍将继续返回剩余分片上的结果。
timeout
time_out 值告诉我们查询超时与否。一般的,搜索请求不会超时。如果响应速度比完整的结果更重要,你可以定义 timeout 参数为 10 或者 10ms (10毫秒),或者 1s (1秒)
GET /_search?timeout=10ms
Elasticsearch将返回在请求超时前收集到的结果。
超时不是一个断路器(circuit breaker)
需要注意的是 timeout 不会停止执行查询,它仅仅告诉你目前顺利返回结果的节点然后关闭连接。在后台,其他分片可能依旧执行查询,尽管结果已经被发送。
- 多索引和多类别
通过限制搜索的不同索引或类型,我们可以在集群中跨所有文档搜索。Elasticsearch转发搜索请求到集群中平行的主分片或每个分片的复制分片上,收集结果后选择顶部十个返回给我们。
通过定义URL中的索引或类型达到想搜索一个或几个自定的索引或类型的目的。
/_search:在所有索引的所有类型中搜索
/gb/_search:在索引 gb 的所有类型中搜索
/gb,us/_search:在索引 gb 和 us 的所有类型中搜索
/g*,u*/_search:在以 g 或 u 开头的索引的所有类型中搜索
/gb/user/_search:在索引 gb 的类型 user 中搜索
/gb,us/user,tweet/_search:在索引 gb 和 us 的类型为 user 和 tweet 中搜索
/_all/user,tweet/_search:在所有索引的 user 和 tweet 中搜索
当你搜索包含单一索引时,Elasticsearch转发搜索请求到这个索引的主分片或每个分片的复制分片上,然后聚集每个分片的结果。
搜索一个索引有5个主分片和5个索引各有一个分片事实上是一样的。
- 分页
和SQL使用 LIMIT 关键字返回只有一页的结果一样,Elasticsearch接受 from 和 size 参数:
size : 结果数,默认 10
from : 跳过开始的结果数,默认 0
如果你想每页显示5个结果,页码从1到3,那请求如下:
GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
注意:在集群系统中深度分页
为了理解为什么深度分页是有问题的,让我们假设在一个有5个主分片的索引中搜索。当我们请求结果的第一页(结果1到10)时,每个分片产生自己最顶端10个结果然后返回它们给请求节点(requesting node),它再排序这所有的50个结果以选出顶端的10个结果。
现在假设我们请求第1000页——结果10001到10010。工作方式都相同,不同的是每个分片都必须产生顶端的10010个结果。然后请求节点排序这50050个结果并丢弃50040个!
你可以看到在分布式系统中,排序结果的花费随着分页的深入而成倍增长。这也是为什么网络搜索引擎中任何语句不能返回多于1000个结果的原因。
- 简易搜索
search API有两种表单:一种是“简易版”的查询字符串(query string)将所有参数通过查询字符串定义,另一种版本使用JSON完整的表示请求体(request body),这种富搜索语言叫做结构化查询语句(DSL)。
查询字符串搜索对于在命令行下运行点对点(ad hoc)查询特别有用
例如这个语句查询所有类型为 tweet 并在 tweet 字段中包含 elasticsearch 字符的文档:
GET /_all/tweet/_search?q=tweet:elasticsearch
下一个语句查找 name 字段中包含 "john" 和 tweet 字段包含 "mary" 的结果。实际的查询只需要:
+name:john +tweet:mary
GET /_all/tweet/_search?q=name:john +tweet:mary
但是百分比编码(percent encoding)(译者注:就是url编码)需要将查询字符串参数变得更加神秘:
GET /_search?q=%2Bname%3Ajohn+%2Btweet%3Amary
"+" 前缀表示语句匹配条件必须被满足。类似的 "-" 前缀表示条件必须不被满足。所有条件如果没有 + 或 - 表示是可选的——匹配越多,相关的文档就越多。
_all
字段
返回包含 "mary" 字符的所有文档的简单搜索:
GET /_search?q=mary
当你索引一个文档,Elasticsearch把所有字符串字段值连接起来放在一个大字符串中,它被索引为一个特殊的字段 _all
。例如,当索引这个文档:
{
"tweet": "However did I manage before Elasticsearch?",
"date": "2014-09-14",
"name": "Mary Jones",
"user_id": 1
}
这好比我们增加了一个叫做 _all
的额外字段值:
"However did I manage before Elasticsearch? 2014-09-14 Mary Jones 1"
若没有指定字段,查询字符串搜索(即q=xxx)使用 _all
字段搜索。
_all
字段对于开始一个新应用时是一个有用的特性。之后,如果定义字段来代替_all
字段,搜索结果将更加可控。当_all
字段不再使用,可以停用它。
- 更复杂的语句
_all field
name 字段包含 "mary" 或 "john"
date 晚于 2014-09-10
_all 字段包含 "aggregations" 或 "geo"
查询字符串:
+name:(mary john) +date:>2014-09-10 +(aggregations geo)
编码后的查询字符串变得不太容易阅读:
?q=%2Bname%3A(mary+john)+%2Bdate%3A%3E2014-09-10+%2B(aggregations+geo)
简单(lite)查询字符串搜索惊人的强大。允许我们简洁明快的表示复杂的查询。这对于命令行下一次性查询或者开发模式下非常有用。
然而,查询字符串中一个细小的语法错误,像 - 、 : 、 / 或 " 错位就会导致返回错误而不是结果。
最后,查询字符串搜索允许任意用户在索引中任何一个字段上运行潜在的慢查询语句,可能暴露私有信息甚至使你的集群瘫痪。
因为这些原因,我们不建议直接暴露查询字符串搜索给用户,除非这些用户对于你的数据和集群可信。
映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型( string , number , booleans , date 等)。
分析(analysis)机制用于进行全文文本(Full Text)的分词,以建立供搜索用的反向索引。
- 映射及分析
GET /website/_mapping
{
"website" : {
"mappings" : {
"properties" : {
"date" : {
"type" : "date",
"format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
},
"tags" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"text" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"views" : {
"type" : "long"
}
}
}
}
}
Elasticsearch对字段类型进行猜测,动态生成了字段和类型的映射关系。返回的信息显示了 date 字段被识别为 date 类型。
_all
因为是默认字段所以没有在此显示,不过知道它是 string 类型。
date 类型的字段和 string 类型的字段的索引方式是不同的,因此导致查询结果的不同.
更大的区别在于确切值(exact values)(比如 string 类型)及全文文本(full text)之间。
这两者的区别才真的很重要 - 这是区分搜索引擎和其他数据库的根本差异。
- 确切值(Exact values) vs. 全文文本(Full text)
Elasticsearch中的数据可以大致分为两种类型:
确切值 及 全文文本。
确切值是确定的.
全文文本,从另一个角度来说是文本化的数据(常常以人类的语言书写).全文文本常常被称为 非结构化数据
为了方便在全文文本字段中进行这些类型的查询,Elasticsearch首先对文本分析(analyzes),然后使用结果建立一个倒排索引。
- 倒排索引
Elasticsearch使用一种叫做倒排索引(inverted index)的结构来做快速的全文搜索。
倒排索引由在文档中出现的唯一的单词列表,以及对于每个单词在文档中的位置组成。
切分每个文档的不同字段为单独的单词 (我们把它们叫做词(terms)或者表征(tokens),语句分词后的个体叫做这两个)
切分词时需要着重处理以下三种情况的词:
大小写,单复数(同根词),意思相同的(同义词)
索引文本和查询字符串都要标准化为相同的形式。这个标记化和标准化的过程叫做分词(analysis).
- 分析和分析器
分析(analysis)是这样一个过程:
首先,标记化一个文本块为适用于倒排索引单独的词(term)
然后标准化这些词为标准形式,提高它们的“可搜索性”或“查全率”
这个工作是分析器(analyzer)完成的。一个分析器(analyzer)只是一个包装用于将三个功能放到一个包里:
字符过滤器
首先字符串经过字符过滤器(character filter),它们的工作是在标记化前处理字符串。字符过滤器能够去除HTML标记,或者转换"&"为"and"。
分词器
下一步,分词器(tokenizer)被标记化成独立的词。一个简单的分词器(tokenizer)可以根据空格或逗号将单词分开(译者注:这个在中文中不适用)。
标记过滤
最后,每个词都通过所有标记过滤(token filters),它可以修改词(例如将 "Quick" 转为小写),去掉词(例如停用词像 "a"、"and"、"the"等等),或者增加词(例如同义词像 "jump"和"leap")
Elasticsearch提供很多开箱即用的字符过滤器,分词器和标记过滤器。这些可以组合来创建自定义的分析器以应对不同的需求。
内建的分析器
Elasticsearch还附带了一些预装的分析器,你可以直接使用它们。下面我们列出了最重要的几个分析器,来演示这个字符串分词后的表现差异:
"Set the shape to semi-transparent by calling set_trans(5)"
标准分析器
标准分析器是Elasticsearch默认使用的分析器。对于文本分析,它对于任何语言都是最佳选择(译者注:就是没啥特殊需求,对于任何一个国家的语言,这个分析器就够用了)。它根据Unicode Consortium的定义的单词边界(word boundaries)来切分文本,然后去掉大部分标点符号。最后,把所有词转为小写。产生的结果为:
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
简单分析器
简单分析器将非单个字母的文本切分,然后把每个词转为小写。产生的结果为:
set, the, shape, to, semi, transparent, by, calling, set, trans
空格分析器
空格分析器依据空格切分文本。它不转换小写。产生结果为:
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
语言分析器
特定语言分析器适用于很多语言。它们能够考虑到特定语言的特性。例如,english分析器自带一套英语停用词库——像and或 the这些与语义无关的通用词。这些词被移除后,因为语法规则的存在,英语单词的主体含义依旧能被理解。
english 分析器将会产生以下结果:
set, shape, semi, transpar, call, set_tran, 5
注意 "transparent" 、 "calling" 和 "set_trans" 是如何转为词干的。
当分析器被使用
当我们索引(index)一个文档,全文字段会被分析为单独的词来创建倒排索引。不过,当我们在全文字段搜索(search)时,我们要让查询字符串经过同样的分析流程处理,以确保这些词在索引中存在。
当你查询全文(full text)字段,查询将使用相同的分析器来分析查询字符串,以产生正确的词列表。
当你查询一个确切值(exact value)字段,查询将不分析查询字符串,但是你可以自己指定。
- 测试分析器
使用 analyze API来查看文本是如何被分析的。在查询字符串参数中指定要使用的分析器,被分析的文本做为请求体:
GET /_analyze
{
"analyzer" : "standard",
"text" : "Text to analyze"
}
结果中每个节点在代表一个词:
{
"tokens" : [
{
"token" : "text",
"start_offset" : 0,
"end_offset" : 4,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "to",
"start_offset" : 5,
"end_offset" : 7,
"type" : "<ALPHANUM>",
"position" : 1
},
{
"token" : "analyze",
"start_offset" : 8,
"end_offset" : 15,
"type" : "<ALPHANUM>",
"position" : 2
}
]
}
token是一个实际被存储在索引中的词。 position指明词在原文本中是第几个出现的。 start_offset和 end_offset表示词在原文本中占据的位置。
指定分析器
当Elasticsearch在你的文档中探测到一个新的字符串字段,它将自动设置它为全文string字段并用standard分析器分析。
通过映射(mapping)人工设置这些字段.
- 映射
为了能够把日期字段处理成日期,把数字字段处理成数字,把字符串字段处理成全文本(Full-text)或精确的字符串值,Elasticsearch需要知道每个字段里面都包含了什么类型。这些类型和字段的信息存储(包含)在映射(mapping)中。
索引中每个文档都有一个类型(type)。 每个类型拥有自己的映射(mapping)或者模式定义(schema definition)。一个映射定义了字段类型,每个字段的数
据类型,以及字段被Elasticsearch处理的方式。映射还用于设置关联到类型上的元数据。
核心简单字段类型
Elasticsearch支持以下简单字段类型:
String: string
Whole number: byte,short,integer,long
Floating point:float,double
Boolean: boolean
Date:date
当你索引一个包含新字段的文档——一个之前没有的字段——Elasticsearch将使用动态映射猜测字段类型,这类型来自于JSON的基本数据类型,使用以下规则:
JSON type Field type
Boolean: true or false "boolean"
Whole number: 123 "long"
Floating point: 123.45 "double"
String, valid date: "2014-09-15" "date"
String: "foo bar" "string"
这意味着,如果你索引一个带引号的数字"123",它将被映射为"string"类型,而不是"long"类型。然而,如果字段已经被映射为 "long" 类型,Elasticsearch将尝试转换字符串为long,并在转换失败时会抛出异常。
查看映射
可以使用 _mapping
后缀来查看Elasticsearch中的映射。
GET /website/_mapping
这展示给了我们字段的映射(叫做属性(properties)),这些映射是Elasticsearch在创建索引
时动态生成的:
{
"website" : {
"mappings" : {
"properties" : {
"date" : {
"type" : "date",
"format" : "yyyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis"
},
"tags" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"text" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"views" : {
"type" : "long"
}
}
}
}
}
错误的映射,例如把age字段映射为string类型而不是integer类型,会造成查询结果混乱。
要检查映射类型,而不是假设它是正确的!
自定义字段映射
自定义类型可以使你完成以下几点:
- 区分全文(full text)字符串字段和准确字符串字段(译者注:就是分词与不分词,全文的一般要分词,准确的就不需要分词,比如『中国』这个词。全文会分成『中』和
『国』,但作为一个国家标识的时候我们是不需要分词的,所以它就应该是一个准确的字符串字段)。 - 使用特定语言的分析器(译者注:例如中文、英文、阿拉伯语,不同文字的断字、断词方式的差异)
- 优化部分匹配字段
- 指定自定义日期格式(译者注:这个比较好理解,例如英文的 Feb,12,2016 和 中文的2016年2月12日)
Elasticsearch从5.X就引入了text和keyword,其中keyword适用于不分词字段,搜索时只能完全匹配,这时string还保留着。
到了6.X就彻底移除string了。另外,"index"的值只能是boolean变量了。
{
"tag": {
"type": "text",
"index": false
}
}
分析
默认的,Elasticsearch使用 standard 分析器,但是你可以通过指定一个内建的分析器来更改它,例如 whitespace 、 simple 或 english 。
{
"tag": {
"type": "text",
"analyzer": "english"
}
}
更新映射
可以在第一次创建索引的时候指定映射的类型。此外,也可以晚些时候为新类型添加映射(或者为已有的类型更新映射)。
你可以向已有映射中增加字段,但你不能修改它。如果一个字段在映射中已经存在,这可能意味着那个字段的数据已经被索引。如果你改变了字段映射,那已经被索引的数据将错误并且不能被正确的搜索到。
PUT /gb
{
"mappings": {
"properties": {
"tweet": {
"type": "text",
"analyzer": "english"
},
"date": {
"type": "date"
},
"name": {
"type": "text",
"index": false
},
"user_id": {
"type": "long"
},
"tag":{
"type": "keyword"
}
}
}
}
测试映射
可以通过名字使用 analyze API测试字符串字段的映射,对比这两个请求的输出:
GET /gb/_analyze
{
"field": "tweet",
"text" : "Black-cats"
}
{
"tokens" : [
{
"token" : "black",
"start_offset" : 0,
"end_offset" : 5,
"type" : "<ALPHANUM>",
"position" : 0
},
{
"token" : "cat",
"start_offset" : 6,
"end_offset" : 10,
"type" : "<ALPHANUM>",
"position" : 1
}
]
}
GET /gb/_analyze
{
"field": "tag",
"text" : "Black-cats"
}
{
"tokens" : [
{
"token" : "Black-cats",
"start_offset" : 0,
"end_offset" : 10,
"type" : "word",
"position" : 0
}
]
}
汇总说明:
type类型有如下几个:text,keyword,float,byte,date,geo_point,object,short,interger,half_float,nested,scaled_float
analyzer默认的是standard
index有如下几个:true,false
(keyword适用于不分词字段,搜索时只能完全匹配)
- 复合核心字段类型
多值字段
可以索引一个标签数组来代替单一字符串:
{"tag": ["search", "nosql"]}
对于数组不需要特殊的映射。任何一个字段可以包含零个、一个或多个值,同样对于全文字段将被分析并产生多个词。
言外之意,这意味着数组中所有值必须为同一类型。
如果你创建一个新字段,这个字段索引了一个数组,Elasticsearch将使用第一个值的类型来确定这个新字段的类型。
空字段
数组可以是空的。这等价于有零个值。事实上,Lucene没法存放 null值,所以一个null值的字段被认为是空字段。
这四个字段将被识别为空字段而不被索引:
"empty_string": "",
"null_value": null,
"empty_array": [],
"array_with_null_value": [null]
多层对象
对象(object)——在其它语言中叫做hash、hashmap、dictionary 或者 associative array.
内部对象(inner objects)经常用于在另一个对象中嵌入一个实体或对象。
内部对象的映射
Elasticsearch会动态的检测新对象的字段,并且映射它们为object类型,将每个字段加到properties字段下
事实上, type映射只是object映射的一种特殊类型,我们将object称为根对象。
内部对象是怎样被索引的
Lucene 并不了解内部对象。 一个 Lucene 文件包含一个键-值对应的扁平表单。 为了让Elasticsearch 可以有效的索引内部对象,将文件转换为以下格式:
{
"tweet": [elasticsearch, flexible, very],
"user.id": [@johnsmith],
"user.gender": [male],
"user.age": [26],
"user.name.full": [john, smith],
"user.name.first": [john],
"user.name.last": [smith]
}
对象-数组
内部对象数组
一个包含内部对象的数组如何索引。 我们有个数组如下所示:
{
"followers": [
{ "age": 35, "name": "Mary White"},
{ "age": 26, "name": "Alex Jones"},
{ "age": 19, "name": "Lisa Smith"}
]
}
此文件会如我们以上所说的被扁平化,但其结果会像如此:
{
"followers.age": [19, 26, 35],
"followers.name": [alex, jones, lisa, smith, mary, white]
}
{age: 35} 与 {name: Mary White} 之间的关联会消失,因每个多值的栏位会变成一个值集合,而非有序的阵列。
但是这样扁平化的数据结构会模糊之前数据之前的关系,关联内部对象可解决此类问题,我们称之为嵌套对象。