八、分词器#
ES官网分词器模块 https://www.elastic.co/guide/en/elasticsearch/reference/6.2/analysis.html
8.1、什么是分词器?#
我们使用分词器可以将一段话拆分成一个一个的单词,甚至可以进一步对分出来的单词进行词性的转换、时态的转换、单复数的转换的操作。
为什么使用分词器呢?
你想一个doc那么长,成千上万字。为了对它进行特征的提取,分析。就得把它还原成组成它的词条。这样会提高检索时的召回率,让更多的doc被检索到。
8.2、分词器的组成#
character filter:
在一段文本在分词前先进行预处理,比如过滤html标签, 将特殊符号转换成123..这种 阿拉伯数字等特殊符号的转换。
tokenizer:
进行分词、拆解句子、记录词条的位置(在当前doc中占第几个位置term position)及顺序。
token filter:
进行同义词的转换,去除同义词,单复数的转换等等。
ES内置的分词器:
- standard analyzer(默认)
- simple analyzer
- whitespace
- language analyzer(特定语言的分词器,English)
另外比较受欢迎的中文分词器为IK分词器,这个分词器的插件包、安装方式我都整理成文档了,公众号后台回复:es即可领取。
8.3、修改Index使用的分词器#
PUT /my_index { "settings":{ "analysis":{ "analyzer":{ "es_std":{ # 指定分词器的类型是:standard "type":"standard", # 指定分词器的停用词:_english_ "stopwords":"_english_" } } } } }
九、mapping#
9.1、认识mapping#
看到这里你肯定知道了,我们想往ES中写数据是需要一个index的。其实我们在往ES中PUT数据之前是可以手动创建Mapping,这里的mapping其实好比你搞一个java类,做一次对数据结构的抽象,比如name 的类型是String,age的类型是Integer。
就好比下面这样:
PUT my_index { # 指定index的primary shard数量以及 replicas的数量 “settings”:{ "number_of_shards":1, "number_of_repicas":0 }, # 关键字,我们手动自定my_index中的mapping "mappings": { "my_index": { # index的名称 "properties": { # 关键字,mapping的属性,字段 "my_field1": { # 相当于Java中的 String my_field1 "type": "text", "analyzer":"english"# 指定分词器,说明这个字段需要分词建立倒排索引 }, "my_field2": { # 相当于Golang中的 var my_field2 float "type": "float", # 指定是否要分词。analyzed表示要,not_analyzed表示不要 "index":"not_analyzed" }, "my_field3": { "type": "scaled_float", "scaling_factor": 100 } } } } }
1、mapping json中包含了诸如
properties
、matadata(_id,_source,_type)
、settings(analyzer)
以及其他的settings。2、我们把上面的json中的properties部分称为:root object
3、自己创建mapping一般是为了更好的控制各个字段的数据类型,包括使用到的分词器。
4、另外注意:field的mapping只能新增,不能修改。
你也可以在往ES中PUT数据之前不创建任何Mapping,ES会自动为我们生成mapping。就像下面这样,自动生成的mapping信息叫做dynamic mapping,下文中我们还会详细讲这个dynamic
PUT my_index/_doc/1 { "title": "This is a document" }
9.2、查看mapping#
# 查看某个index下的某个type的mapping GET /index/_mapping/type # 查看某个index的mapping GET /index/_mapping
9.3、dynamic mapping (动态mapping)#
就像下面这样,我们直接往ES中PUT数据,ES在为我们创建index时就会自动生成dynamic mapping。其实用大白话讲就是ES自动推断你往它里面存的json串的类型。比如下面的"first_name"会被dynamic mapping成string 类型的。
PUT my_index/_doc/1 { "first_name": "John" }
ES使用_type
来描述doc字段的类型,原来我们直接往ES中存储数据,并没有指定字段的类型,原因是ES存在动态类型推断(ES支持的类型上文中我们也一起看过了,如果不记得阔以再去看一下哈)。默认的mapping中定义了每个field对应的数据类型以及如何进行分词。
null --> no field add true flase --> boolean 123 --> long 123.123 --> double 1999-11-11 --> date "hello world" --> string Object --> object
9.4、定制dynamic mapping 策略#
- ture: 语法陌生字段就进行dynamic mapping。
- false: 遇到陌生字段就忽略。
- strict: 遇到默认字段就报错。
示例
PUT /my_index/ { "mappings":{ "dynamic":"strict" } }
- 禁用ES的日期探测的Demo
# 创建mapping并制定:禁用ES的日期探测 PUT my_index { "mappings": { "_doc": { "date_detection": false } } } # 添加一条doc PUT my_index/_doc/1 { "create": "1985/12/22" } # 查看doc,结果如下 GET my_index/_doc/1 { "_index": "my_index", "_type": "_doc", "_id": "1", "_version": 1, "found": true, "_source": { "create": "1985/12/22" } } # 查看mapping GET my_index/_mapping # 结果如下: { "my_index": { "mappings": { "_doc": { "date_detection": false, "properties": { "create": { # 被任务是text类型 "type": "text", # ES会自动帮你创建的下面的field部分 # 即 create是text类型,create.ketword是keyword类型 # keyword类型不会分词,默认保留前256字符 "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } } } } }
- 定制日期发现规则
PUT my_index { "mappings": { "_doc": { "dynamic_date_formats": ["MM/dd/yyyy"] } } } PUT my_index/_doc/1 { "create_date": "09/25/2015" }
- 定制数字类型的探测规则
PUT my_index { "mappings": { "_doc": { "numeric_detection": true } } } PUT my_index/_doc/1 { "my_float": "1.0", "my_integer": "1" }
定制type field#
ES中type相当于MySQL的数据表嘛,ES中可以给现存的type添加field。但是不能修改,否则就会报错。
type在高版本的ES7中被废弃了,Index的概念依然保留着。
# 创建index:twitter PUT twitter { "mappings": { # user为type "user": { "properties": { "name": { # 会被全部检索 "type": "text" , # 指定当前field使用 english分词器 "analyzer":"english" }, "user_name": { "type": "keyword" }, "email": { "type": "keyword" } } }, "tweet": { "properties": { "content": { "type": "text" }, "user_name": { "type": "keyword" }, # "tweeted_at": { "type": "date" }, "tweeted_at": { "type": "date" # 通过index设置为当前field tweeted_at不能被分词 "index": "not_analyzeed" } } } } }
9.5、mapping复杂数据类型在底层的存储格式#
Object类型
# object类型的json { "address":{ "province":"shandong", "city":"qingdao" }, "name":"bairimeng", "age":"12" } # ES会将上面的json转换成如下的格式存储 { "name" : [bairimeng], "age" : [12], "address.province" : [shandong] "address.city" : [qingdao] }
Object数组类型
# Object数组类型 { "address":[ {"age":"12","name":"张三"}, {"age":"12","name":"张三"}, {"age":"12","name":"张三"} ] } # ES会将上面的json转换成如下的格式存储 { "address.age" : [12,12,12], "address.name" : [张三,张三,张三] }
9.6、ES7中废弃了type的概念#
在一开始我们将ElasticSearch的index比作MySQL中的database,将type比作table,其实这种类比是错误的。因为在MySQL中不同表之间的列在物理上是没有关系的,各自占有自己的空间。
但是在ES中不是这样,可能type=Student中的name列和type=Teacher中的name列会被lucene认为是同一个field。导致Lucene处理效率下降。
所以在ES7中直接就将type概念废弃了。
不过你也不用担心,大部分企业都倾向于使用低版本的ES,比好比你现在用的依然是java8 而不是JDK14。
9.7、认识一些mate-field(元数据字段)#
这里说的元数据字段指定的是,当你检索doc时,除了返回的doc本身的数据之外,其他的出现在检索结果中的数据,我们是需要了解这些字段都是什么含义的。如下:
_index , _type , _id , _source , _version
_id
它是document的唯一标识信息。上图中我手动指定了id等于1。如果不指定的话,ES会自动为我们生成一个长20个字符的id,ES会保证集群中的生成的doc id不会发生冲突。 有这种场景,比如你的数据是从MySQL这种数据库中倒入进ES的,那其实完全可以使用MySQL中的数据行的ID作为doc id。
_index
你可以简单粗暴的将es的index的地位理解成MYSQL中的数据库。这里的元数据_index被用来标识当前的doc存在于哪个index中。index的命名规范,名称小写,不能用下划线开头,不能包含逗号。
ES支持跨域index进行检索
详情见官网 https://www.elastic.co/guide/en/elasticsearch/reference/6.2/mapping-index-field.html
_type
这个字段用来标识doc的类型。但它其实是一个逻辑上的划分。
field中的value在顶层的lucene建立索引的时候,全部使用的opaque bytes类型,不区分类型的lucene是没有type概念的。
为了方便我们区分出不通doc的类型,于是在document中加了一个_type
属性。
ES会通过_type
进行type的过滤和筛选,一个index中是存放的多个type实际上是存放在一起的,因此一个index下,不可能存在多个重名的type。
_version `_version`是doc的版本号,可以用来做并发控制,当一个doc被创建时它的`_version`是1,之后对它的每一次修改,都会使这个版本号+1,哪怕是你将这个doc删除了,这个doc的版本号也会增加1。
_source
通过这个字段可以定制我们想要返回字段。比如说一个type = user类型的doc中存在100个字段,但是可能前端并不是真的需要这100个字段,于是我们使用_source去除一些字段,注意和filter是不一样的,因为filter不会影响相关性得分。
你可用像下面这样禁用_source
PUT tweets { "mappings": { "_doc": { "_source": { "enabled": false } } } }
_all
首先它也是一个元数据,当我们往ES中插入一条document时。ES会自动的将这个doc中的多个field的值串联成一个字符串,然后用这个作为_all
字段的值并建立索引。当用户发起检索却没有指定从哪个字段查询时,默认就会在这个_all
中进行匹配。
_field_names
举个例子说明这个属性怎么用:
首先往index=my_index的索引下灌两条数据
# Example documents PUT my_index/_doc/1 { "title": "This is a document" } PUT my_index/_doc/2?refresh=true { "title": "This is another document", "body": "This document has a body" }
然后像下面这样使用_field_names
检索,并且指定了字段=“title”。此时ES会将所有包含title字段,且title字段值不为空的doc检索出来。
GET my_index/_search { "query": { "terms": { "_field_names": [ "title" ] } } }
禁用_field_names
:
PUT tweets { "mappings": { "_doc": { "_field_names": { "enabled": false } } } }
_routing
下面路由导航中细说。
_uid
在ES6.0中被弃用。
9.8、copy_to#
在上一篇文章中跟大家介绍过可以像下面这样跨越多个字段搜索
# dis_max GET /your_index/your_type/_search { # 基于 tie_breaker 优化dis_max # tie_breaker可以使dis_max考虑其它field的得分影响 "query": { # 直接取下面多个query中得分最高的query当成最终得分 # 这也是best field策略 "dis_max": { "queries":[ {"match":{"name":"关注"}}, {"match":{"content":"白日梦"}} ], "tie_breaker":0.4 } } } # best_field # 使用multi_match query简化写法如下: GET /your_index/your_type/_search { "query": { "multi_match":{ "query":"关注 白日梦", # 指定检索的策略 best_fields(因为dis_max就是best field策略) "type":"best_fields", # content^2 表示增加权重,相当于:boost2 "fields":["name","content^2"], "tie_breaker":0.4, "minimum_should_match":3 } } } # most_field GET /your_index/your_type/_search { # most_fields策略、优先返回命中更多关键词的doc # 如下从title、name、content中搜索包含“赐我白日梦”的doc "query": { "multi_match":{ "query":"赐我白日梦", # 指定检索的策略most_fields "type":"most_fields", "fields":["title","name","content"] } } }
针对跨越多个字段的检索除了上面的most_field和best_field之外,还可以使用copy_to预处理。
这个copy_to实际上是在允许我们自定义一个_all字段, ES会将多个字段的值复制到一个_all中,然后再次检索时目标字段就使用我们通过copy_to创建出来的_all新字段中。
示例:
PUT my_index { "mappings": { "_doc": { "properties": { "first_name": { "type": "text", # 把当前的first_name copy进full_name字段中 "copy_to": "full_name" }, "last_name": { "type": "text", # 把当前的last_name copy进full_name字段中 "copy_to": "full_name" }, "full_name": { "type": "text" } } } } } PUT my_index/_doc/1 { "first_name": "John", "last_name": "Smith" } GET my_index/_search { "query": { "match": { "full_name": { "query": "John Smith", "operator": "and" } } } }
9.9、Arrays 和 Multi-field#
更多内容参见官网 https://www.elastic.co/guide/en/elasticsearch/reference/6.2/mapping-types.html