映射是Elasticsearch中一个非常重要的概念,因为它定义了搜索的方式
引擎应处理文档及其字段。
搜索引擎执行以下两个主要操作:
- 索引:这是接收文档并对其进行处理的操作,并且将其存储在索引中
- 搜索:这是从索引中检索数据的操作
这两个部分是严格连接的; 索引步骤中的错误导致不必要或丢失的搜索结果。
Elasticsearch在索引级别具有显式映射。 索引时,如果映射是未提供,则创建一个默认值,并从数据字段中猜测结构文件组成的。 然后,此新映射将自动传播到所有群集节点。
默认类型映射具有合理的默认值,但是当您要更改时,他们的行为或自定义索引的其他几个方面(存储,忽略,完成等),您需要提供一个新的映射定义。
我们将研究记录映射的所有可能的映射字段类型组成。
本章将介绍以下:
- 使用显式映射创建
- 映射基本类型
- 映射数组
- 映射对象
- 管理嵌套对象
- 使用联接字段管理子文档
2.1 使用显式映射创建
如果我们将索引视为SQL世界中的数据库,则映射类似于表定义。
Elasticsearch能够了解您所使用的文档的结构索引(反射)并自动创建映射定义(明确映射创建)。
1.做好准备
需要一个正在运行的Elasticsearch安装,要执行这些命令,可以使用任何HTTP客户端,例如curl(https://curl.haxx.se/),postman(https://www.getpostman.com/)或其他类似的平台。 我建议使用Kibana控制台提供更好的代码完成功能Elasticsearch的字符转义。
2.怎么做…
您可以通过在Elasticsearch中添加新文档来显式创建映射。 对于
为此,我们将执行以下步骤:
1.像这样创建一个索引:
PUT test
获得回复如下:
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "test"
}
2.将文档放入索引,如以下代码所示:
PUT test/_doc/1
{
"name":"Paul",
"age":35
}
获得回复如下:
{
"_index" : "test",
"_type" : "_doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
3.使用以下代码获取映射:
GET test/_mapping
4.Elasticsearch自动创建的结果映射应为如下:
{
"test" : {
"mappings" : {
"properties" : {
"age" : {
"type" : "long"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
5.要删除索引,可以调用以下命令:
DELETE test
获得回复如下:
{
"acknowledged" : true
}
3.这个怎么运作…
第一个命令行创建一个索引,在此我们将配置类型/映射和插入文件。
第二个命令在索引中插入一个文档(我们将看到索引的创建在第3章,基本操作中的创建索引得文档操作在第3章,基本操作中的索引文档操作介绍)。
在文档索引阶段,Elasticsearch在内部检查_doc类型存在,否则将动态创建一个。
Elasticsearch读取映射字段的所有默认属性,并开始处理它们如下:
- 如果该字段已经存在于映射中,并且该字段的值为有效(匹配正确的类型),Elasticsearch无需更改当前的映射。
- 如果该字段已经存在于映射中,但是该字段的值为同的类型,它将尝试升级字段类型(即从整数升级为长)。 如果类型不兼容,则会引发异常,并且索引处理失败。
- 如果不存在该字段,它将尝试自动检测字段的类型。 它更新
具有新字段映射的映射。
4.还有更多…
在Elasticsearch中,按类型分隔文档是逻辑的,而不是物理的。 Elasticsearch核心引擎透明地对此进行管理。 从物理上来说,所有文件
类型使用相同的Lucene索引,因此它们之间没有完全分隔的,类型的概念纯属逻辑,由Elasticsearch强制执行。 用户不是对此内部管理感到困扰,但在某些情况下,数量的记录,这会影响读写性能记录,因为所有记录都存储在相同的索引文件中。
每个文档都有一个唯一的标识符,称为索引的UID,该标识符存储在
文档的特殊_uid字段。 这是通过将_id的文档类型。 在我们的示例中,_uid将是_doc#1。
可以在索引时间提供_id,也可以由_id自动分配Elasticsearch(如果缺少)。
创建或更改映射类型时,Elasticsearch自动传播将更改映射到集群中的所有节点,以便将所有分片对齐处理该特定类型
每个索引只能包含一个类型。 类型的名称Elasticsearch的先前版本可能会有所不同。 因为类型是在7.x中已弃用,最佳做法是调用_doc类型。
2.2 映射基本类型
使用显式映射可以更快地开始提取数据,使用无模式方法,而不必担心字段类型。 因此,取得更好的索引结果和性能,则需要手动定义映射。
微调映射带来了一些优势,例如:
- 减少磁盘上的索引大小(禁用自定义功能字段)
- 仅对需要的字段建立索引(一般加快)
- 预处理数据以进行快速搜索或实时分析(例如构面)
- 正确定义一个字段是否必须使用多个标记分析或被视为单个令牌
Elasticsearch允许您使用具有配置的广泛基础字段。
1.怎么做…
让我们使用类似eBay的商店的车间订单的半现实示例:
1.首先,我们定义一个表格:
Name | Type | Description |
---|---|---|
id | identifier | 订单识别码 |
date | date(time) | 订购日期 |
customer_id | id reference | 客户编号 |
name | string | 商品的名称 |
quantity | integer | 商品的数量 |
price | double | 商品的价格 |
vat | double | 商品的增值税 |
sent | boolean | 订单状态 |
2.我们的订单记录必须转换为Elasticsearch映射定义如下:
#前提是索引存在,不然会报错,解决办法:PUT test
PUT test/_mapping
{
"properties" : {
"id" : {"type" : "keyword"},
"date" : {"type" : "date"},
"customer_id" : {"type" : "keyword"},
"sent" : {"type" : "boolean"},
"name" : {"type" : "keyword"},
"quantity" : {"type" : "integer"},
"price" : {"type" : "double"},
"vat" : {"type" : "double", "index":"false"}
}
}
# 如果索引不存在就创建索引及其对应映射
PUT test
{
"mappings": {
"properties": {
"id":{"type": "keyword"},
"date":{"type": "date"},
"customer_id":{"type": "long"},
"name":{"type": "keyword"},
"quantity":{"type": "integer"},
"price":{"type": "double"},
"vat":{"type": "double","index":"false"},
"sent":{"type": "boolean"}
}
}
}
现在,可以将映射放入索引中了。 我们将在在第4章“基本操作”中,将映射放入索引配置中。
2.这个怎么运作…
字段类型必须映射到Elasticsearch基本类型之一,并且有关的选项
需要添加字段必须如何编制索引。
下表是映射类型的参考:
Type | ES-Type | Description |
---|---|---|
String, VarChar | keyword | 这是一个不可标记的文本字段:CODE001 |
String, VarChar,Text | text | 这是一个需要标记的文本字段:一个漂亮的文本 |
Integer | integer | 这是一个整数(32位):1、2、3或4 |
long | long | 这是一个长值(64位) |
float | float | 这是一个浮点数(32位):1.2或4.5 |
double | double | 这是一个浮点数(64位) |
boolean | boolean | 这是一个布尔值:true或false |
date/datetime | date | 这是日期或日期时间值:2013-12-25,2013-12-25T22:21:20 |
bytes/binary | binary | 这包括一些用于二进制的字节数据,例如文件或字节流 |
根据数据类型,可以向Elasticsearch给出明确的指令处理以进行更好的管理时。 最常用的选项是如下:
- store(默认为false):标记要存储在单独索引中的字段片段以便快速检索。 存储字段会消耗磁盘空间,但会减少磁盘空间计算是否需要从文档中提取(即在脚本中)和聚合)。 此选项的可能值为false和true。在聚合中,存储的字段比其他字段快。
- index:定义是否应为字段建立索引。 的此参数的可能值为true和false。 索引字段不是可搜索的(默认为true)。
- null_value:如果字段为null,则定义默认值。
- boost:用于更改字段的重要性(默认为1.0)。
- search_analyzer:这定义了要在搜索过程中使用的分析器。如果未定义,则使用父对象的分析器(默认为null)。
- analyser:这将设置要使用的默认分析器(默认为null)。
- include_in_all:这标志着当前字段要在特殊索引中
_all字段(包含所有字段的串联文本的字段)(默认真正)。 - norms:控制Lucene规范。此参数用于更好得分查询。如果该字段仅用于过滤,则最佳做法是禁用它以减少资源使用(对于分析字段默认为true,对于not_analyzed的结果为false)。
- copy_to:这允许您将一个字段的内容复制到另一个实现功能,类似于_all字段。
- ignore_above:这使您可以跳过更大的索引字符串比它的价值。这对于处理字段以进行精确过滤非常有用,聚合和排序。它还可以防止单个术语令牌变得太大并防止由于Lucene术语字节长度而导致的错误限制为32766(默认2147483647)。
Boost仅在术语级别上工作,因此主要用于术语,术语,并匹配查询。
3.还有更多…
在Elasticsearch的先前版本中,字符串的标准映射为字符串。 在版本5.x中,不建议使用字符串映射并将其迁移到关键字和文本映射。
- 在Elasticsearch 6.x版中,如使用显式映射创建配置中所示,字符串的显式推断类型是多字段映射:默认处理为文本。 此映射允许文本查询(即是,字词,匹配和跨度查询)。 在使用中提供的示例中显式映射创建配置,这就是名称。
- 关键字子字段用于关键字映射。 该字段可以使用用于精确的术语匹配以及聚合和排序。 在这个例子中使用显式映射创建食谱中提供的内容,所引用的字段为关键字名称。
仅适用于文本映射的另一个重要参数是term_vector(组成字符串的术语的向量。请参阅Lucene文档以了解有关更多详细信息,请参见http://lucene.apache.org/core/6_1_0/core/org/apache/lucene/index/Terms.html)。
term_vector可以接受以下值:
- no:这是默认值,跳过术语向量
- yes:这是存储字词向量
- with_offsets:这是带有标记偏移量(开始,结束的术语向量)
字符块中的位置) - with_positions:用于存储令牌在术语中的向量位置
- with_positions_offsets:存储所有项向量数据
词项向量允许快速突出显示,但由于存储其他文本信息而占用磁盘空间。 最佳做法是仅在需要突出显示的字段(例如标题或文档内容)中激活。
2.3 映射数组
数组或多值字段在数据模型中非常常见(例如多个电话号码,地址,姓名,别名等),但本机不支持传统的SQL解决方案。
在SQL中,多值字段要求创建必须连接的附件表收集所有值,导致记录基数降低时的影响性能很大
1.怎么做…
1.每个字段都自动作为数组进行管理。 例如,存储标签对于文档,映射将如下所示:
{
"properties" : {
"name" : {"type" : "keyword"},
"tag" : {"type" : "keyword", "store" : "yes"},
...
}
}
2.此映射对两个文档都有效。 以下是文档1的代码:
{"name": "document1", "tag": "awesome"}
3.以下是document2的代码:
{"name": "document2", "tag": ["cool", "awesome", "amazing"] }
2.这个怎么运作…
Elasticsearch透明地管理数组:如果声明一个a,则没有区别,由于其Lucene核心性质,因此是单一值或多值。
字段的多值是在Lucene中管理的,因此您可以将它们添加到具有相同字段名称的文档。 对于具有SQL背景的人来说,行为可能很奇怪,但这是NoSQL世界中的关键点,因为它减少了对联接查询的需求,并创建了不同的表来管理多值。 嵌入式对象数组的行为与简单字段相同。
2.4 映射对象
对象是基本结构(类似于SQL中的记录)。 Elasticsearch扩展了传统上使用对象,因此允许递归嵌入对象。
1.怎么做…
我们可以使用来重写上一个示例配置中找到的映射代码项目数据:
PUT test
PUT test/_mapping
{
"properties" : {
"id" : {"type" : "keyword"},
"date" : {"type" : "date"},
"customer_id" : {"type" : "keyword", "store" : "yes"},
"sent" : {"type" : "boolean"},
"item" : {
"type" : "object",
"properties" : {
"name" : {"type" : "text"},
"quantity" : {"type" : "integer"},
"price" : {"type" : "double"},
"vat" : {"type" : "double"}
}
}
}
}
或者
PUT test/_mapping?include_type_name=true
{
"properties" : {
"id" : {"type" : "keyword"},
"date" : {"type" : "date"},
"customer_id" : {"type" : "keyword", "store" : "yes"},
"sent" : {"type" : "boolean"},
"item" : {
"type" : "object",
"properties" : {
"name" : {"type" : "text"},
"quantity" : {"type" : "integer"},
"price" : {"type" : "double"},
"vat" : {"type" : "double"}
}
}
}
}
遇见问题:
Types cannot be provided in put mapping requests, unless the include_type_name parameter is set to t...
这个是因为elasticsearch7.0 之后不支持type导致的…
原因是由于写法是低版本的elasticsearch的,高版本要求传入一个include_type_name参数,值为true。所以加上一个参数即可。如下:
PUT 192.168.2.121:9200/cs_company/doc/_mapping?include_type_name=true
2.这个怎么运作…
Elasticsearch使用本机JSON,因此可以映射每个复杂的JSON结构进去。当Elasticsearch解析对象类型时,它将尝试提取字段和进程它们作为其定义的映射。如果不是,它将使用以下命令学习对象的结构反射。对象的最重要属性如下:
- properties:这是字段或对象的集合(我们可以考虑它们作为SQL世界中的列)。
- enabled:确定是否应处理对象。如果设置为false,对象中包含的数据未编制索引,因此无法被搜索(默认为true)。
- dynamic:这允许Elasticsearch向对象添加新的字段名称使用对插入数据值的反映。如果设置为false,当您尝试索引包含新字段类型的对象时,该对象将可能被拒绝。如果将其设置为strict,则当对象中存在新的字段类型时,引发错误,跳过索引过程。动态参数使您可以安全地更改文档结构(默认true)。
- include_in_all:这会将对象值添加到特殊的_all字段中(用于汇总所有文档字段的文本)(默认为true)。
最常用的属性是properties,它允许您映射Elasticsearch字段中的对象。
禁用文档的索引部分会减小索引的大小;但是,那无法搜索数据。换句话说,最终在磁盘上的文件较小,但是功能上要付出代价。
2.5 管理嵌套对象
有一种特殊类型的嵌入式对象:嵌套对象。 这样可以解决问题与Lucene索引架构有关,其中嵌入式对象的所有字段被视为单个对象。 在搜索过程中,在Lucene中无法区分值和同一多值中的不同嵌入对象数组。
如果考虑上一个订购示例,则无法区分商品名称和数量与相同的查询相同,就像Lucene将它们放在相同的Lucene中一样文档对象。 我们需要将它们编入不同的文档中,然后再加入它们。整个行程由嵌套对象和嵌套查询管理。
为什么要有嵌入对象:
这是因为Lucene底层其实没有内部对象的概念,所以ES会利用简单的列表储存字段名和值,将object类型的对象层次摊平,再传给Lucen。
如果需要索引对象数组并维护数组中每个对象的独立性,则应使用nested
数据类型而不是object
数据类型。 在内部,嵌套对象将数组中的每个对象作为单独的隐藏文档进行索引,这意味着每个嵌套对象都可以使用嵌套查询nested query
独立于其他对象进行查询:
参考案例:
#采用object的方式
curl -XPUT 'localhost:9200/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d'
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
#在内部其转换成一个看起来像下面这样的文档
{
"group" : "fans",
"user.first" : [ "alice", "john" ],
"user.last" : [ "smith", "white" ]
}
#采用nested
curl -XPUT 'localhost:9200/my_index?pretty' -H 'Content-Type: application/json' -d'
{
"mappings": {
"my_type": {
"properties": {
"user": {
"type": "nested"
}
}
}
}
}
curl -XPUT 'localhost:9200/my_index/my_type/1?pretty' -H 'Content-Type: application/json' -d'
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
'
1.怎么做…
嵌套对象定义为具有嵌套类型的标准对象。从映射对象配方的示例中,我们可以更改对象的类型嵌套如下:
PUT test
PUT test/_mapping
{
"properties" : {
"id" : {"type" : "keyword"},
"date" : {"type" : "date"},
"customer_id" : {"type" : "keyword", "store" : "yes"},
"sent" : {"type" : "boolean"},
"item" : {
"type" : "nested",
"properties" : {
"name" : {"type" : "text"},
"quantity" : {"type" : "integer"},
"price" : {"type" : "double"},
"vat" : {"type" : "double"}
}
}
}
}
2.这个怎么运作…
对文档建立索引后,如果将嵌入式对象标记为嵌套,则该对象为由原始文档提取,然后在新的外部索引中文档,保存在父文档附近的特殊索引位置。
在前面的示例中,我们重用了“映射对象”配方的映射,但是我们将项目的类型从对象更改为嵌套。 无需其他任何操作必须将嵌入式对象转换为嵌套对象。
嵌套的对象是特殊的Lucene文档,它们保存在父文档的数据-这种方法允许与父文档快速连接。
嵌套对象不能用标准查询搜索,只能用嵌套对象搜索。 他们不会显示在标准查询结果中。
嵌套对象的生命与其父对象有关:删除/更新父对象自动删除/更新所有嵌套子代。 改变父母的手段Elasticsearch将执行以下操作:
- 将旧文档标记为已删除
- 将所有嵌套文档标记为已删除
- 索引新文档版本
- 索引所有嵌套文档
3.还有更多…
有时,需要将嵌套对象的信息传播到其父对象或根对象。 这主要是为了建立有关父母的简单查询(例如术语查询而不使用嵌套查询)。 为了实现这个目标,有两个特殊的必须使用的嵌套对象的属性:
- include_in_parent:这样可以自动添加嵌套字段到直接父级
- include_in_root:这会将嵌套对象字段添加到根对象这些设置增加了数据冗余,但它们降低了某些查询的复杂性,从而提高性能。
2.6 通过联接管理子文档领域
在上一个cookbook中,我们了解了如何管理之间的关系具有嵌套对象类型的对象。 嵌套对象的缺点是它们的嵌套来自父母的依赖。 如果您需要更改嵌套对象的值,则可以需要重新索引父级(如果嵌套,这会带来潜在的性能开销对象变化太快)。 为了解决这个问题,Elasticsearch允许您定义子文件。
父子档的原因:
嵌套文档来说,父-子关系的主要优势有:
更新父文档时,不会重新索引子文档。
创建,修改或删除子文档时,不会影响父文档或其他子文档。这一点在这种场景下尤其有用:子文档数量较多,并且子文档创建和修改的频率高时。
子文档可以作为搜索结果独立返回。
1.怎么做…
在下面的示例中,我们有两个相关的对象:一个Order和一个Item。
它们的UML表示如下:
最终的映射应该是Order和Item的字段定义的合并,加上一个特殊字段(在此示例中为join_field),该字段接受父/子关系。
映射将如下所示:
PUT test1/_mapping
{
"properties": {
"join_field": {
"type": "join",
"relations": {
"order": "item"
}
},
"id": {
"type": "keyword"
},
"date": {
"type": "date"
},
"customer_id": {
"type": "keyword"
},
"sent": {
"type": "boolean"
},
"name": {
"type": "text"
},
"quantity": {
"type": "integer"
},
"vat": {
"type": "double"
}
}
}
前面的映射与前面的配方非常相似。
如果要存储联接的记录,则需要先保存父记录,然后再保存子文档信息这样:
PUT test1/_doc/1?refresh
{
"id": "1",
"date": "2018-11-16T20:07:45Z",
"customer_id": "100",
"sent": true,
"join_field": "order"
}
PUT test1/_doc/c1?routing=1&refresh
{
"name": "tshirt",
"quantity": 10,
"price": 4.3,
"vat": 8.5,
"join_field": {
"name": "item",
"parent": "1"
}
}
子项需要特殊管理,因为我们需要添加与父母id的关联。 此外,在对象中,我们需要指定父名称和它的ID。
2.这个怎么运作…
如果在同一索引中存在多个项目关系,则需要进行映射计算为所有其他映射字段的总和。
对象之间的关系必须在join_field中定义。只能有一个join_field进行映射; 如果你需要付出很多关系,可以在关系对象中提供它们。
子文档必须在父文档的同一分片中建立索引; 所以,当建立索引后,必须传递一个额外的参数,该参数用于路由(我们将看看如何做在下一章的索引文档配方中对此进行说明)。
当我们想要子文档不需要重新索引父文档时更改其值。 因此,它在建立索引,重新建立索引(更新)和删除。
3.还有更多…
在Elasticsearch中,我们有不同的方法来管理对象之间的关系,例如
如下:
- 用type = object嵌入:这是由隐式管理的Elasticsearch并认为嵌入是主文档的一部分。速度很快,但是您需要重新索引主文档以更改值嵌入式对象。
- 使用type = nested嵌套:这样可以更精确地搜索和使用对子项的嵌套查询对父项进行过滤。 一切正常对于嵌入式对象(查询除外)(必须使用嵌套查询搜索它们)。
- 外部子文档:这里,子是外部文档,使用join_field属性将它们绑定到父级。 他们一定是索引在与父级相同的分片中。 与父母的加入有点
比嵌套对象慢,因为嵌套对象在同一数据中Lucene索引中父级的块,并且它们已加载了父级,否则,子文档需要更多的读取操作。
选择如何从对象建模关系取决于您的应用程序场景。
还有另一种方法可以使用,但是在大数据文档中,它带来了表现不佳-连接关系脱钩。 您可以通过两个步骤来执行联接查询:首先,您收集子文档/其他文档的ID,然后搜索它们在他们父母的领域。