本文集主要是总结自己在项目中使用ES 的经验教训,包括各种实战和调优。
创建mapping的时候,_all设置为false。主要是从性能方面考虑。
包括mapping创建方式、mapping相关知识等。官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html#mapping-type
新增字段
curl -XPOST "http://127.0.0.1:9200/productindex/product/_mapping?pretty" -d '{ "product": { "properties": { "onSale":{ "type":"keyword" } } } }'
创建索引的同时创建setting和mapping:
curl -XPUT "192.168.1.101:9200/index_test" -d ' # 注意这里的'号
{
"settings": {
"index": {
"number_of_replicas": "1", # 设置复制数
"number_of_shards": "5" # 设置主分片数
}
},
"mappings": { # 创建mapping
"test_type": { # 在index中创建一个新的type(相当于table)
"properties": {
"name": { # 创建一个字段(string类型数据,使用普通索引)
"type": "string",
"index": "not_analyzed"
},
"age": {
"type": "integer"
}
}
}
}
}'
ES还可以使用json的方式来创建mapping:
- 创建一个扩展名为test_type.json的文件名,其中type_test就是mapping所对应的type名。
- 在test_type.json中输入mapping信息。假设你的mapping如下:
{
"test_type": { # 注意,这里的test_type与json文件名必须一致
"properties": {
"name": {
"type": "string",
"index": "not_analyzed"
},
"age": {
"type": "integer"
}
}
}
}
- 在$ES_HOME/config/路径下创建mappings/index_test子目录,这里的index_test目录名必须与我们要建立的索引名一致。将test_type.json文件拷贝到index_tes目录下。
- 步骤4 创建index_test索引。操作如下:
curl -XPUT "192.168.1.101:9200/index_test" # 注意,这里的索引名必须与mappings下新建的index_test目录名一致
这样我们就创建了一个新的索引,并且使用了test_type.json所定义的mapping作为索引的mapping。
参考链接:具体方法参考http://blog.csdn.net/xialei199023/article/details/48085125
mapping中一些常用的属性:
"activity_type": {
"type": "string",//string在新版本已经废弃
"index": "not_analyzed"
},
"address": {
"type": "string",
"analyzer": "ik_smart"
},
"last_update_time": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
}
- Fields with the same name in different mapping types in the same index must have the same mapping.(同一索引下,不同类型的相同字段必须定义成相同的mapping),但是每个字段的以下属性可以用不同的设置,如copy to、dynamic、enabled、ignore_above、include_in_all、properties。
具体可参考https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html#_field_datatypes
Field支持的数据类型:
- 基本类型:text、keyword、date、long、double、boolean、ip
- range datatypes:integer_range, float_range, long_range, double_range, date_range (可以直接存区间数据,目前项目中尚未应用,主要应用场景尚不明确,合同管理的日期似乎可以应用)
- 支持json分层属性,复杂的数据类型,如array、object、nested
- 特殊类型:地理点、地理形状、completion
防止mapping激增的一些常用设置:
- index.mapping.total_fields.limit The maximum number of fields in an index. The default value is 1000.
- index.mapping.depth.limit 一个字段的最大深度,默认是20
- index.mapping.nested_fields.limit 嵌套字段的最大数目,默认50
动态mapping
- 不允许更新已经设置好的mapping
- multi-fields是可以使用多种分词器,如french analyzer,目前我们使用的是standard analyzer、english analyzer、id_smart analyzer。
自动映射:
- 动态映射:可以设置为true(默认值。动态添加字段)、false(忽略新字段)、strict(如果碰到陌生字段,抛出异常)
- 默认映射:通常,一个索引中的所有类型具有共享的字段和设置。用 default 映射来指定公用设置会更加方便,而不是每次创建新的类型时重复操作。_default 映射像新类型的模板。所有在 default 映射 之后 的类型将包含所有的默认设置,除非在自己的类型映射中明确覆盖这些配置。
例如,我们可以使用 default 映射对所有类型禁用 _all 字段,而只在 blog 字段上开启它:
PUT /my_index
{
"mappings": {
"_default_": {
"_all": { "enabled": false }
},
"blog": {
"_all": { "enabled": true }
}
}
}
default 映射也是定义索引级别的动态模板的好地方。
- 动态模板:通过dynamic_templates,你可以拥有对新字段的动态映射规则拥有完全的控制。你设置可以根据字段名称或者类型来使用一个不同的映射规则。
每个模板都有一个名字,可以用来描述这个模板做了什么。同时它有一个mapping用来指定具体的映射信息,和至少一个参数(比如match)用来规定对于什么字段需要使用该模板。
模板的匹配是有顺序的 - 第一个匹配的模板会被使用。比如我们可以为string字段指定两个模板:
- es:以_es结尾的字段名应该使用spanish解析器
- en:其它所有字段使用english解析器
一个动态模板的示例:
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic_templates": [
{ "es": {
"match": "*_es",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "spanish"
}
}},
{ "en": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "english"
}
}}
]
}}}
另外一个典型的mapping对象:
{
"mappings": {
"my_type": {
//true:表示自动识别新字段并创建索引,false:不自动索引新字段,strict:遇到未知字段,抛异常,不能存入
"dynamic": "strict",
//动态模板
"dynamic_templates": [
{ "stash_template": {
"path_match": "stash.*",
"mapping": {
"type": "string",
"index": "not_analyzed"
}
}}
],
//属性列表
"properties": {
//一个string类型的字段
"title": { "type": "string"},
"stash": {
"type": "object",
"dynamic": true //这里设置为true主要针对的是动态模板,即新增的field要是动态模板匹配不上,就会抛异常,否则就是按照模板来创建。
}
}
}
}
}
注意点:Elasticsearch映射虽然有idnex和type两层关系,但是实际索引时是以index为基础的。 如果同一个index下不同type的字段出现mapping不一致的情况 虽然数据依然可以成功写入并生成并生成各自的mapping, 但实际上fielddata中的索引结果却依然是以index内第一个mapping类型来生成的。
一次定义一个index下的两个type mapping:
糖果库mapping设置:
curl -XPUT 'http://localhost:9200/moparticle3' -d '
{
"settings": {
"number_of_shards": 6,
"number_of_replicas": 1
},
"mappings": {
"article": {
"_all" : {
"enabled" : false
},
"_routing" : {
"required" : true
},
"properties" : {
"docid" : {
"type" : "keyword"
},
"boardid" : {
"type" : "keyword"
},
"postid" : {
"type" : "keyword"
},
"topicid" : {
"type" : "keyword"
},
"del" : {
"type" : "integer"
},
"title" : {
"type" : "text",
"analyzer" : "ik_smart",
"search_analyzer": "ik_smart",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
},
"en" : {
"type": "text",
"analyzer": "english"
}
}
},
"digest" : {
"type" : "text",
"analyzer" : "ik_smart",
"search_analyzer": "ik_smart",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
},
"en" : {
"type": "text",
"analyzer": "english"
}
}
},
"source" : {
"type" : "keyword"
},
"ptime" : {
"type" : "date",
"format": "yyyy-MM-dd HH:mm:ss"
}
}
}
}
}
'
article表mapping设置:
curl -XPOST http://localhost:9200/subscribe/article/_mapping -d '{
"article" : {
"_all" : {
"enabled" : false
},
"_routing" : {
"required" : true
},
"properties" : {
"id" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"original" : {
"type" : "long"
},
"postState" : {
"type" : "long"
},
"publishTime" : {
"type" : "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"source" : {
"type" : "long"
},
"title" : {
"type" : "text",
"analyzer" : "ik_smart",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
},
"en" : {
"type": "text",
"analyzer": "english"
}
}
},
"tname" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"topState" : {
"type" : "long"
},
"type" : {
"type" : "long"
},
"updateTime" : {
"type" : "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"userClassify" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"wemediaId" : {
"type" : "keyword"
}
}
}
}'
一些不常用的datatype 整理
Array datatype:四种定义方式
- an array of strings: [ "one", "two" ]
- an array of integers: [ 1, 2 ]
- an array of arrays: [ 1, [ 2, 3 ]] which is the equivalent of [ 1, 2, 3 ]
- an array of objects: [ { "name": "Mary", "age": 12 }, { "name": "John", "age": 10 }]
采用如下方式自动创建mapping:
PUT my_index/my_type/2
{
"tags": [ "哈哈哈","中国社会主义人民" ]
}
这时的tags是按照text的形式来定义的,但其实是个数组。会对我们数组中的每个元素进行分词,我们在搜索的时候,相当于对整个数组的数据进行分词匹配。同时还可以使用must方法来匹配一个数组的多个。比如匹配数组中既有1又有2的数据。
Binary datatype:
Binary类型的field是不能被搜索的。同时会以列的形式存到硬盘上,后续可以用来排序、聚合等,但是默认不会被store,即不会从_source字段中剥离出来,需要自己手动配置。
Range datatypes:
官网链接:https://www.elastic.co/guide/en/elasticsearch/reference/current/range.html
具体可以参考文档,暂不做总结,觉得可能实用不是很大。
项目在实际使用中,为了提升性能,降低带宽,在es查询时,只返回部分字段。具体实现方式如下:
官网链接:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-store.html
官网说明:If you only want to retrieve the value of a single field or of a few fields, instead of the whole_source, then this can be achieved with source filtering.
主要作用:不需要返回_source数据,减少返回数据的大小
es在定义mapping时可以指定某一个字段是都设置store参数,
PUT my_index
{
"mappings": {
"my_type": {
"properties": {
"title": {
"type": "text",
"store": true
},
"date": {
"type": "date",
"store": true
},
"content": {
"type": "text"
}
}
}
}
}
注:Stored fields returned as arrays
For consistency, stored fields are always returned as an array because there is no way of knowing if the original field value was a single value, multiple values, or an empty array.
Another situation where it can make sense to make a field stored is for those that don’t appear in the _source field (such as copy_to fields).
Java API的使用:
首先需要mapping定义store属性。
在SearchRequestBuilder后面可以添加storedFields(String...)或者addStoredField(String)方法。添加后,response.getHits().getHits()的hit对象的source就为空了,只能获取我们storedFields方法添加的字段。
方法如下:
hit.field("title").getValue()
hit.getFields().get("title").getValue()
hit.getFields().get("title").getValues()
同时还可以通过getValues方法或者一个List