3. Elasticsearch Mapping

530 阅读11分钟

概念

Mapping,映射,相当于关系型数据库创建语句,定义文档字段及其类型、索引与存储方式。通常会涉及如下方面:
文档中哪些字段需要定义成全文索引字段。
文档中哪些字段定义为精确值,例如日期,数字、地理位置等。
文档中哪些字段需要被索引(能通过该字段的值查询文档)。
日期值的格式。
动态添加字段的规则定义等。

查看mapping

GET /phone/_mapping

Dynamic mapping 动态映射

动态映射时Elasticsearch的一个重要特性: 不需要提前创建iindex、定义mapping信息和type类型, 你可以 直接向ES中插入文档数据时, ES会根据每个新field可能的数据类型, 自动为其配置type等mapping信息, 这个过程就是动态映射(dynamic mapping)。

JSON data typeElasticsearch data type
nullNo field is added.
true or falseboolean
1234long 为什么不是integer?
123.4float
2018-10-10date
"hello world"text

为什么不是integer?
因为es的mapping_type是由JSON分析器检测数据类型,而Json没有隐式类型转换(integer=>long or float=> double),所以dynamic mapping会选择一个比较宽的数据类型。

PUT blog/_doc/1
{
    "blog_id": 10001,
    "author_id": 5520,
    "post_date": "2018-01-01",
	  "title": "my first blog",
	  "content": "my first blog in the website"
}
PUT blog/_doc/2
{
    "blog_id": 10002,
    "author_id": 5520,
    "post_date": "2018-01-02",
    "title": "my second blog",
    "content": "my second blog in the website"
}

GET blog/_mapping

GET blog/_search?q=2018
GET blog/_search?q=2018-01-01
GET blog/_search?q=post_date:2018
GET blog/_search?q=post_date:2018-01-01

_all

6.0版本开始, _all字段就被禁止使用了,建议我们使用copy_to实现相似的功能.

# first_name 和 last_name 字段中的值会被复制到 full_name 字段。
PUT /my_index
{
    "mappings": {
        "person": {
            "properties": {
                "first_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "last_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "full_name": {
                    "type":     "string"
                }
            }
        }
    }
}

确切值(Exact values)vs.全文文本(Full text)

exact value 精确匹配:在倒排索引过程中,分词器会将field作为一个整体创建到索引中

确切值是确定的,正如它的名字一样。比如一个date或用户ID,也可以包含更多的字符串比如username或email地址。
确切值“Foo”和“foo”就并不相同。确切值2014 和2014-09-15也不相同。

full text全文检索:分词、近义词同义词、混淆词、大小写、词性、过滤、时态转换等(normaliztion)

我们不会去询问这篇文档是否匹配查询?但是,我们会询问这篇文档和查询的匹配程序如何?换句话说,对于查询条件,这篇文档的相关性有多高?
我们很少确切的匹配整个全文文本。我们想在全文中查询包含查询文本的部分。不仅如此,我们还期望搜索引擎能理解我们的意图:
一个针对“UK”的查询将返回涉及“United Kingdom”的文档。
一个针对“jump”的查询同事能够匹配“jumped”,“jumps”,“jumping”甚至“leap”。 “johnny walker”也能匹配“johnnie walker”,“johnnie deep”及“Johnny Depp”。
“fox news hunting”能返回有关hunting on Fox News的故事,而“fox hunting news”也能返回关于fox hunting的新闻故事。

ES数据类型

核心类型

数字类型:

  • long, integer, short, byte, double, float, half_float, scaled_float
  • 在满足需求的情况下,尽可能选择范围小的数据类型。 字符串:string:
  • keyword:适用于索引结构化的字段,可以用于过滤、排序、聚合。keyword类型的字段只能通过精确值(exact value)搜索到。Id应该用keyword
  • text:当一个字段是要被全文搜索的,比如Email内容、产品描述,这些字段应该使用text类型。设置text类型以后,字段内容会被分析,在生成倒排索引以前,字符串会被分析器分成一个一个词项。text类型的字段不用于排序,很少用于聚合。
  • 在同一字段中同时具有全文本(text)和关键字(keyword)版本会很有用:一个用于全文本搜索,另一个用于聚合和排序。 date(时间类型):exact value
    布尔类型:boolean
    binary(二进制):binary
    range(区间类型):integer_range、float_range、long_range、double_range、date_range

复杂类型:

  • Object:用于单个JSON对象
  • Nested:用于JSON对象数组

地理位置:

  • Geo-point:纬度/经度积分
  • Geo-shape:用于多边形等复杂形状

特有类型:

  • IP地址:ip 用于IPv4和IPv6地址
  • Completion:提供自动完成建议
  • Tocken_count:计算字符串中令牌的数量
  • ......
  • www.elastic.co/guide/en/el…

Mapping parameters

  • analyzer 指定分析器(character filter、tokenizer、Token filters)
  • boost 对当前字段相关度的评分权重,默认1
  • coerce 是否允许强制类型转换 true “1”=> 1 false “1”=< 1
  • copy_to 将多个字段的值复制到组字段中,然后可以将其作为单个字段进行查询。
  • doc_values 为了提升排序和聚合效率,默认true,如果确定不需要对字段进行排序或聚合,也不需要通过脚本访问字段值,则可以禁用doc值以节省磁盘空间
  • dynamic 设置控制是否可以动态添加新字段 true:新检测到的字段将添加到映射中。(默认)
    false:新检测到的字段将被忽略。这些字段将不会被索引,因此将无法搜索,但仍会出现在_source返回的匹配项中。这些字段不会添加到映射中,必须显式添加新字段。
    strict:如果检测到新字段,则会引发异常并拒绝文档。必须将新字段显式添加到映射中。
  • eager_global_ordinals 用于聚合的字段上,优化聚合性能
  • enabled 是否创建倒排索引,可以对字段操作,也可以对索引操作,如果不创建索引,让然可以检索并在_source元数据中展示,谨慎使用,该状态无法修改
  • fielddata
  • fields
  • format 格式化
  • ignore_above 超过长度将被忽略
  • ignore_malformed 忽略类型错误
  • index_options 控制将哪些信息添加到反向索引中以进行搜索和突出显示。仅用于text字段
  • index_phrases 提升exact_value查询速度,但是要消耗更多磁盘空间
  • index_prefixes
  • index 是否对创建对当前字段创建索引,默认true,如果不创建索引,该字段不会通过索引被搜索到,但是仍然会在source元数据中展示
  • meta
  • normalizer
  • norms
  • null_value
  • position_increment_gap
  • properties
  • search_analyzer
  • similarity
  • store
  • term_vector
  • www.elastic.co/guide/en/el…

demo

# copy_to
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "first_name": {
        "type": "text",
        "copy_to": "full_name" 
      },
      "last_name": {
        "type": "text",
        "copy_to": "full_name" 
      },
      "full_name": {
        "type": "text"
      }
    }
  }
}
PUT my-index-000001/_doc/1
{
  "first_name": "John",
  "last_name": "Smith"
}
GET /my-index-000001/_search
{
  "query": {
    "match_all": {}
  }
}
GET my-index-000001/_search
{
  "query": {
    "match": {
      "full_name": { 
        "query": "John Smith"
      }
    }
  }
}

# coerce
DELETE  my-index-000001
PUT my-index-000001
{
  "mappings": {
    "properties": {
      "number_one": {
        "type": "integer"
      },
      "number_two": {
        "type": "integer",
        "coerce": false
      }
    }
  }
}
PUT my-index-000001/_doc/1
{
  "number_one": "10" 
}
PUT my-index-000001/_doc/2
{
  "number_two": 10
}
GET /my-index-000001/_search
{
  "query": {
    "match_all": {}
  }
}

倒排索引和正排索引

Elasticsearch 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
比如有两个文档:
1.The quick brown fox jumped over the lazy dog
2.Quick brown foxes leap over lazy dogs in summer

# 倒排索引
Term      Doc_1  Doc_2
-------------------------
Quick   |       |  X
The     |   X   |
brown   |   X   |  X
dog     |   X   |
dogs    |       |  X
fox     |   X   |
foxes   |       |  X
in      |       |  X
jumped  |   X   |
lazy    |   X   |  X
leap    |       |  X
over    |   X   |  X
quick   |   X   |
summer  |       |  X
the     |   X   |
------------------------
# 获得所有包含 brown 的文档的词
GET /my_index/_search
{
  "query" : {
    "match" : {
      "body" : "brown"
    }
  },
  "aggs" : {
    "popular_terms": {
      "terms" : {
        "field" : "body"
      }
    }
  }
}

查询部分简单又高效。
对于聚合部分,我们需要找到 Doc_1 和 Doc_2 里所有唯一的词项。 用倒排索引做这件事情代价很高: 我们会迭代索引里的每个词项并收集 Doc_1 和 Doc_2 列里面 token。这很慢而且难以扩展:随着词项和文档的数量增加,执行时间也会增加。

Doc values 通过转置两者间的关系来解决这个问题。倒排索引将词项映射到包含它们的文档,doc values 将文档映射到它们包含的词项:

Doc      Terms
-----------------------------------------------------------------
Doc_1 | brown, dog, fox, jumped, lazy, over, quick, the
Doc_2 | brown, dogs, foxes, in, lazy, leap, over, quick, summer
-----------------------------------------------------------------

获得每个文档行,获取所有的词项,然后求两个集合的并集。

doc values

Doc Values 是 "快速、高效并且内存友好"
Doc Values 是在索引时与 倒排索引 同时生成。也就是说 Doc Values 和 倒排索引 一样,基于 Segement 生成并且是不可变的。同时 Doc Values 和 倒排索引 一样序列化到磁盘,这样对性能和扩展性有很大帮助。Doc Values 通过序列化把数据结构持久化到磁盘,我们可以充分利用操作系统的内存,而不是JVM的Heap 。
Doc Values 默认对所有字段启用,除了analyzed strings。也就是说所有的数字、地理坐标、日期、IP 和不分析( not_analyzed )字符类型都会默认开启。
analyzed strings 暂时还不能使用 Doc Values。文本经过分析流程生成很多词条,使得 Doc Values 不能高效运行。
如果字段永远不会进行聚合和排序操作,可以禁用,不仅节省磁盘空间,还会提升索引的速度。

fielddata

与 doc values 不同,fielddata 构建于查询时,存活在JVM的Heap。

维度doc_valuesfielddata
创建时间index时创建使用时动态创建
创建位置磁盘内存(jvm heap)
优点不占用内存空间不占用磁盘空间
缺点索引速度稍低文档很多时,动态创建开销比较大,而且占内存

随着文档的增加,field data可能会产生OOM,es有fielddata的熔断器,通过内部估算得到一个需要的内存,如果超过,就会熔断,查询会被中止并返回异常。这都发生在数据加载 之前 ,也就意味着不会引起 OutOfMemoryException。

可用的断路器(Available Circuit Breakers)

Elasticsearch 有一系列的断路器,它们都能保证内存不会超出限制:

indices.breaker.fielddata.limit
fielddata 断路器默认设置堆的 60% 作为 fielddata 大小的上限。
indices.breaker.request.limit
request 断路器估算需要完成其他请求部分的结构大小,例如创建一个聚合桶,默认限制是堆内存的 40%。
indices.breaker.total.limit
total 揉合 request 和 fielddata 断路器保证两者组合起来不会使用超过堆内存的 70%。

随着ES版本的升级,对于doc_values的优化越来越好,索引的速度已经很接近fielddata了,而且我们知道硬盘的访问速度也是越来越快(比如SSD)。所以 doc_values 现在可以满足大部分场景,也是ES官方重点维护的对象。
doc values相比field data还是有很多优势的。所以 ES2.x 之后,支持聚合的字段属性默认都使用doc_values,而不是fielddata。

# 实例:
PUT /product2/_doc/1
{
    "name" : "xiaomi phone",
    "desc" :  "shouji zhong de zhandouji",
    "price" :  3999,
    "tags": [ "xingjiabi", "fashao", "buka" ]
}
PUT /product2/_doc/2
{
    "name" : "xiaomi nfc phone",
    "desc" :  "zhichi quangongneng nfc,shouji zhong de jianjiji",
    "price" :  4999,
    "tags": [ "xingjiabi", "fashao", "gongjiaoka" ]
}
PUT /product2/_doc/3
{
    "name" : "nfc phone",
    "desc" :  "shouji zhong de hongzhaji",
    "price" :  2999,
    "tags": [ "xingjiabi", "fashao", "menjinka" ]
}

PUT /product2/_doc/4
{
    "name" : "xiaomi erji",
    "desc" :  "erji zhong de huangmenji",
    "price" :  999,
    "tags": [ "low", "bufangshui", "yinzhicha" ]
}
PUT /product2/_doc/5
{
    "name" : "hongmi erji",
    "desc" :  "erji zhong de kendeji",
    "price" :  399,
    "tags": [ "lowbee", "xuhangduan", "zhiliangx" ]
}
# 查询
GET /product2/_search
{
  "query": {
    "match": {
      "name": "xiaomi"
    }
  },
  "aggs": {
    "tags_aggs": {
      "terms": {
        "field": "price"
      }
    }
  }
}

# 修改mapping
PUT /product2
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "desc": {
        "type": "text"
      },
      "price": {
        "type": "long"
      },
      "tags": {
        "type": "text",
        "fielddata": true
      }
    }
  }
}

批量操作

mget 批量查询

# 第一种方式
GET /_mget
{
  "docs": [
    {
      "_index": "product2",
      "_type": "_doc",
      "_id": "1"
    },
    {
      "_index": "phone",
      "_type": "_doc",
      "_id": "1"
    }
  ]
}
# 第二种方式
GET /product2/_mget
{
  "docs": [
    {
      "_id": "1"
    },
    {
      "_id": "2"
    }
  ]
}
# 第三种方式
GET /product2/_mget
{
   "ids" : [ "2", "1" ]
}

bulk:批量增删改

{ action: { metadata }}\n
{ request body        }\n
{ action: { metadata }}\n
{ request body        }\n
...

action/metadata 行指定 哪一个文档 做 什么操作
action 必须是以下选项之一:
create
如果文档不存在,那么就创建它。
index
创建一个新文档或者替换一个现有的文档。
update
部分更新一个文档。
delete
删除一个文档。
metadata 应该指定被索引、创建、更新或者删除的文档的 _index 、 _type 和 _id 。

POST /_bulk
{"delete":{"_index":"product2","_type":"_doc","_id":"1"}}
{"create":{"_index":"product2","_type":"_doc","_id":"6"}}
{"name":"hongmi2 erji","desc":"erji zhong de kendeji","price":99,"tags":["lowbee","xuhangduan","zhiliangx"]}
{"update":{"_index":"product2","_type":"_doc","_id":"2"}}
{"doc":{"price":"999"}} 

es乐观锁

(1)悲观锁:各种情况,都加锁,读写锁、行级锁、表级锁。使用简单,但是并发能力很低 (2)乐观锁:并发能力高,操作麻烦,每次no-query操作都需要比对version

GET /noble_test/_doc/1?refresh
返回结果:
{
  "_index" : "noble_test",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 12,
  "_seq_no" : 11,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "first_name" : "yanlk"
  }
}

PUT /noble_test/_doc/1?version=12
{
  "first_name":"yanlk"
}
返回结果:
{
  "_index" : "noble_test",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 13,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 12,
  "_primary_term" : 1
}

ElasticSearch完整目录

1. Elasticsearch是什么
2.Elasticsearch基础使用
3.Elasticsearch Mapping
4.Elasticsearch 集群原理
5.Elasticsearch Scripts和读写原理
6.Elasticsearch 分词器
7.Elasticsearch TF-IDF算法及高级查询
8.Elasticsearch 地理位置及搜索
9.Elasticsearch ELK