Elasticsearch在创建索引定义类型时指出如下参数:
analyzer
指定分词器。elasticsearch是一款支持全文检索的分布式存储系统,对于text类型的字段,首先会使用分词器进行分词,然后将分词后的词根一个一个存储在倒排索引中,后续查询主要是针对词根的搜索。
analyzer该参数可以在每个查询、每个字段、每个索引中使用,其优先级如下(越靠前越优先):
1、字段上定义的分词器
2、索引配置中定义的分词器
3、默认分词器(standard)
在查询上下文,分词器的查找优先为:
1、full-text query中定义的分词器
2、定义类型映射时,字段中search_analyzer 定义的分词器。
3、定义字段映射时analyzer定义的分词器
4、索引中default_search中定义的分词器。
5、索引中默认定义的分词器
6、标准分词器(standard)。
normalizer
规划化,主要针对keyword类型,在索引该字段或查询字段之前,可以先对原始数据进行一些简单的处理,然后再将处理后的结果当成一个词根存入倒排索引中,举例如下:
PUT index
{
"settings": {
"analysis": {
"normalizer": {
"my_normalizer": { // @1
"type": "custom",
"char_filter": [],
"filter": ["lowercase", "asciifolding"] // @2
}
}
}
},
"mappings": {
"_doc": {
"properties": {
"foo": {
"type": "keyword",
"normalizer": "my_normalizer" // @3
}
}
}
}
}
代码@1:首先在settings中的analysis属性中定义normalizer。
代码@2:设置标准化过滤器,示例中的处理器为小写、asciifolding。
代码@3:在定义映射时,如果字段类型为keyword,可以使用normalizer引用定义好的normalizer。
boost
权重值,可以提升在查询时的权重,对查询相关性有直接的影响,其默认值为1.0。其影响范围为词根查询(team query),对前缀、范围查询、全文索引(match query)不生效。
注意:不建议在创建索引映射时使用boost属性,而是在查询时通过boost参数指定。其主要原因如下:
1、无法动态修改字段中定义的boost值,除非使用reindex命令重建索引。
2、相反,如果在查询时指定boost值,每一个查询都可以使用不同的boost值,灵活。
3、在索引中指定boost值,boost存储在记录中,从而会降低分数计算的质量。
coerce
是否进行类型“隐式转换”。es最终存储文档的格式是字符串。
例如存在如下字段类型:
"number_one": {
"type": "integer"
}
声明number_one字段的类型为数字类型,那是否允许接收“6”字符串形式的数据呢?因为在JSON中,“6”用来赋给int类型的字段,也是能接受的,默认coerce为true,表示允许这种赋值,但如果coerce设置为false,此时es只能接受不带双引号的数字,如果在coerce=false时,将“6”赋值给number_one时会抛出类型不匹配异常。
可以在创建索引时指定默认的coerce值,示例如下:
PUT my_index
{
"settings": {
"index.mapping.coerce": false
},
"mappings": {
// 省略字段映射定义
}
}
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"first_name": {
"type": "text",
"copy_to": "full_name"
},
"last_name": {
"type": "text",
"copy_to": "full_name"
},
"full_name": {
"type": "text"
}
}
}
}
}
表示字段full_name的值来自 first_name + last_name。
关于copy_to重点说明:
1、字段的复制是原始值,而不是分词后的词根。
2、复制字段不会包含在_souce字段中,但可以使用复制字段进行查询。
3、同一个字段可以复制到多个字段,写法如下:“copy_to”: [ “field_1”, “field_2” ]
doc_values
当需要对一个字段进行排序时,es需要提取匹配结果集中的排序字段值集合,然后进行排序。倒排索引的数据结构对检索来说相当高效,但对排序就不那么擅长了。
业界对排序、聚合非常高效的数据存储格式首推列式存储,在elasticsearch中,doc_values就是一种列式存储结构,默认情况下绝大多数数据类型都是开启的,即在索引时会将字段的值(或分词后的词根序列)加入到倒排索引中,同时也会该字段的值加入doc_values中,所有该类型的索引下该字段的值用一列存储。
doc_values的使用示例:
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"status_code": {
"type": "keyword" // 默认情况下,“doc_values”:true
},
"session_id": {
"type": "keyword",
"doc_values": false
}
}
}
}
}
dynamic设置为false,也是可以通过put mapping api进行字段的新增,同样put mapping api可以对dynamic值进行更新。
举例说明:
PUT my_index/_doc/1
{
"username": "johnsmith",
"name": {
"first": "John",
"last": "Smith"
}
}
PUT my_index/_doc/2 // @1
{
"username": "marywhite",
"email": "[email protected]",
"name": {
"first": "Mary",
"middle": "Alice",
"last": "White"
}
}
GET my_index/_mapping // @2
代码@1在原有的映射下,增加了username,name.middle两个字段,通过代码@2获取映射API可以得知,es已经为原本不存在的字段自动添加了类型映射定义。
注意:dynamic只对当前层级具有约束力,例如:
PUT my_index
{
"mappings": {
"_doc": {
"dynamic": false, // @1
"properties": {
"user": { // @2
"properties": {
"name": {
"type": "text"
},
"social_networks": { // @3
"dynamic": true,
"properties": {}
}
}
}
}
}
}
}
代码@1:_doc类型的顶层不能不支持动态隐式添加字段映射。
代码@2:但_doc的嵌套对象user对象,是支持动态隐式添加字段映射。
代码@3:同样对于嵌套对象social_networks,也支持动态隐式添加字段映射。
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"user_id": {
"type": "keyword"
},
"last_updated": {
"type": "date"
},
"session_data": {
"enabled": false
}
}
}
}
}
PUT my_index/_doc/session_1
{
"user_id": "kimchy",
"session_data": {
"arbitrary_object": {
"some_array": [ "foo", "bar", { "baz": 2 } ]
}
},
"last_updated": "2015-12-06T18:20:22"
}
上述示例,es会存储session_data对象的数据,但无法通过查询API根据session_data中的属性进行查询。
同样,可以通过put mapping api更新enabled属性。
PUT my_index/_mapping/_doc
{
"properties": {
"tags": {
"type": "keyword",
"eager_global_ordinals": true
}
}
}
fielddata
为了解决排序与聚合,elasticsearch提供了doc_values属性来支持列式存储,但doc_values不支持text字段类型。因为text字段是需要先分析(分词),会影响doc_values列式存储的性能。es为了支持text字段高效排序与聚合,引入了一种新的数据结构(fielddata),使用内存进行存储。默认构建时机为第一次聚合查询、排序操作时构建,主要存储倒排索引中的词根与文档的映射关系,聚合,排序操作在内存中执行。因此fielddata需要消耗大量的JVM堆内存。一旦fielddata加载到内存后,它将永久存在。通常情况下,加载fielddata是一个昂贵的操作,故默认情况下,text字段的字段默认是不开启fielddata机制。在使用fielddata之前请慎重考虑为什么要开启fielddata。通常text字段用来进行全文搜索,对于聚合、排序字段,建议使用doc_values机制。
为了节省内存的使用,es提供了另一项机制(fielddata_frequency_filter),允许只加载那些词根频率在指定范围(最大,小值)直接的词根与文档的映射关系,最大最小值可以指定为绝对值,例如数字,也可以基于百分比(百分比的计算是基于整个分段(segment),其频率分母不是分段(segment)中所有的文档,而是segment中该字段有值的文档)。可以通过min_segment_size参数来指定分段中必须包含的最小文档数量来排除小段,也就是说可以控制fielddata_frequency_filter的作用范围是包含大于min_segment_size的文档数量的段。fielddata_frequency_filter的使用示例如下:
format
在JSON文档中,日期表示为字符串。Elasticsearch使用一组预先配置的格式来识别和解析这些字符串,并将其解析为long类型的数值(毫秒)。
日期格式主要包括如下3种方式:
1)自定义格式
2)date mesh(已在DSL查询API中详解)
3)内置格式
1、自定义格式
首先可以使用java定义时间的格式,例如:
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"date": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
}
}
}
}
}
2、date mesh
某些API支持,已在DSL查询API中详细介绍过,这里不再重复。
3、内置格式
elasticsearch为我们内置了大量的格式,如下:
epoch_millis
时间戳,单位,毫秒。
epoch_second
时间戳,单位,秒。
basic_date
其格式表达式为 :yyyyMMdd
basic_date_time
其格式表达式为:yyyyMMdd’T’HHmmss.SSSZ
basic_date_time_no_millis
其格式表达式为:yyyyMMdd’T’HHmmssZ
basic_ordinal_date
4位数的年 + 3位(day of year),其格式字符串为yyyyDDD
basic_ordinal_date_time
其格式字符串为yyyyDDD’T’HHmmss.SSSZ
basic_ordinal_date_time_no_millis
其格式字符串为yyyyDDD’T’HHmmssZ
basic_time
其格式字符串为HHmmss.SSSZ
basic_time_no_millis
其格式字符串为HHmmssZ
basic_t_time
其格式字符串为’T’HHmmss.SSSZ
basic_t_time_no_millis
其格式字符串为’T’HHmmssZ
basic_week_date
其格式字符串为xxxx’W’wwe,4为年 ,然后用’W’, 2位week of year(所在年里周序号) 1位 day of week。
basic_week_date_time
其格式字符串为xxxx’W’wwe’T’HH:mm:ss.SSSZ.
basic_week_date_time_no_millis
其格式字符串为xxxx’W’wwe’T’HH:mm:ssZ.
date
其格式字符串为yyyy-MM-dd
date_hour
其格式字符串为yyyy-MM-dd’T’HH
date_hour_minute
其格式字符串为yyyy-MM-dd’T’HH:mm
date_hour_minute_second
其格式字符串为yyyy-MM-dd’T’HH:mm:ss
date_hour_minute_second_fraction
其格式字符串为yyyy-MM-dd’T’HH:mm:ss.SSS
date_hour_minute_second_millis
其格式字符串为yyyy-MM-dd’T’HH:mm:ss.SSS
date_time
其格式字符串为yyyy-MM-dd’T’HH:mm:ss.SSS
date_time_no_millis
其格式字符串为yyyy-MM-dd’T’HH:mm:ss
hour
其格式字符串为HH
hour_minute
其格式字符串为HH:mm
hour_minute_second
其格式字符串为HH:mm:ss
hour_minute_second_fraction
其格式字符串为HH:mm:ss.SSS
hour_minute_second_millis
其格式字符串为HH:mm:ss.SSS
ordinal_date
其格式字符串为yyyy-DDD,其中DDD为 day of year。
ordinal_date_time
其格式字符串为yyyy-DDD‘T’HH:mm:ss.SSSZZ,其中DDD为 day of year。
ordinal_date_time_no_millis
其格式字符串为yyyy-DDD‘T’HH:mm:ssZZ
time
其格式字符串为HH:mm:ss.SSSZZ
time_no_millis
其格式字符串为HH:mm:ssZZ
t_time
其格式字符串为’T’HH:mm:ss.SSSZZ
t_time_no_millis
其格式字符串为’T’HH:mm:ssZZ
week_date
其格式字符串为xxxx-'W’ww-e,4位年份,ww表示week of year,e表示day of week。
week_date_time
其格式字符串为xxxx-'W’ww-e’T’HH:mm:ss.SSSZZ
week_date_time_no_millis
其格式字符串为xxxx-'W’ww-e’T’HH:mm:ssZZ
weekyear
其格式字符串为xxxx
weekyear_week
其格式字符串为xxxx-'W’ww,其中ww为week of year。
weekyear_week_day
其格式字符串为xxxx-'W’ww-e,其中ww为week of year,e为day of week。
year
其格式字符串为yyyy
year_month
其格式字符串为yyyy-MM
year_month_day
其格式字符串为yyyy-MM-dd
温馨提示,日期格式时,es建议在上述格式之前加上strict_前缀。
public static void create_mapping_ignore_above() { // 创建映射
RestHighLevelClient client = EsClient.getClient();
try {
CreateIndexRequest request = new CreateIndexRequest("mapping_test_ignore_above2");
XContentBuilder mapping = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject("lies")
.field("type", "keyword") // 创建关键字段
.field("ignore_above", 10) // 设置长度不能超过10
.endObject()
.endObject()
.endObject();
// request.mapping("user", mapping_user);
request.mapping("_doc", mapping);
System.out.println(client.indices().create(request, RequestOptions.DEFAULT));
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
public static void index_mapping_ignore_above() { // 索引数据
RestHighLevelClient client = EsClient.getClient();
try {
IndexRequest request = new IndexRequest("mapping_test_ignore_above2", "_doc");
Map data = new HashMap<>();
data.put("lies", new String[] {"dingabcdwei","huangsw","wuyanfengamdule"});
request.source(data);
System.out.println(client.index(request, RequestOptions.DEFAULT));
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
public static void search_ignore_above() { // 查询数据
RestHighLevelClient client = EsClient.getClient();
try {
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("mapping_test_ignore_above2");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(
// QueryBuilders.matchAllQuery() // @1
// QueryBuilders.termQuery("lies", "dingabcdwei") // @2
// QueryBuilders.termQuery("lies", "huangsw") // @3
);
searchRequest.source(sourceBuilder);
SearchResponse result = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(result);
} catch (Throwable e) {
e.printStackTrace();
} finally {
EsClient.close(client);
}
}
代码@1:首先查询所有数据,其_souce字段的值为:"_source":{“lies”:[“dingabcdwei”,“huangsw”,“wuyanfengamdule”]},表名不管字符串的值是否大于ignore_above指定的值,都会存储。
代码@2:用超过ignore_above长度的值尝试去搜索,发现无法匹配到记录,表明并未加入到倒排索引中。
代码@3:用未超过ignore_above长度的值尝试去搜索,发现能匹配到记录。
注意:在es中,ignore_above的长度是字符的长度,而es其底层实现lucene是使用字节进行计算的,故,如果要反馈到lucnce,请注意关系。
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"city": {
"type": "text", // @1
"fields": { // @2
"raw": {
"type": "keyword" // @3
}
}
}
}
}
}
}
@1:上述映射为city字段,定义类型为text,使用全文索引。
@2:为city定义多字段,city.raw,其类型用keyword。
主要就可以用user进行全文匹配,也可以用user.raw进行聚合、排序等操作。另外一种比较常用的场合是对该字段使用不同的分词器。
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"status_code": {
"type": "keyword",
"null_value": "NULL" // @1
}
}
}
}
}
PUT my_index/_doc/1
{
"status_code": null // @2
}
PUT my_index/_doc/2
{
"status_code": [] // @3
}
GET my_index/_search
{
"query": {
"term": {
"status_code": "NULL" // @4
}
}
}
代码@1:为status_code字段定义“NULL”为空值null;
代码@2:该处,存储在status_code为 null_value中定义的值,即"NULL"
代码@3:空数组不包含显式null,因此不会被null_value替换。
代码@4:该处查询,会查询出文档1。其查询结果如下:
{
"took":4,
"timed_out":false,
"_shards":{
"total":5,
"successful":5,
"skipped":0,
"failed":0
},
"hits":{
"total":1,
"max_score":0.2876821,
"hits":[
{
"_index":"mapping_test_null_value",
"_type":"_doc",
"_id":"RyjGEmcB-TTORxhqI2Zn",
"_score":0.2876821,
"_source":{
"status_code":null
}
}
]
}
}
null_value具有如下两个特点:
null_value需要与字段的数据类型相同。例如,一个long类型的字段不能有字符串null_value。
null_value只会索引中的值(倒排索引),无法改变_souce字段的值。
PUT my_index
{
"mappings": {
"_doc": {
"properties": {
"names": {
"type": "text",
"position_increment_gap": 0 // @1
// "position_increment_gap": 10 // @2
}
}
}
}
}
PUT my_index/_doc/1
{
"names": [ "John Abraham", "Lincoln Smith"]
}
names字段是个数组,也即ES中说的多值字段
当position_increment_gap=0时,es的默认使用标准分词器,分成的词根为:
position 0 : john
position 1 : abraham
position 2 : lincoln
position 3 : smith
当position_increment_gap = 10时,es使用默认分词器,分成的词根为:
position 0 : john
position 1 : abraham
position 11 : lincoln 这是第二个词,等于上一个词的position + position_increment_gap。
position 12 : smith
查询条件:
针对如下下查询:
GET my_index/_search
{
"query": {
"match_phrase": {
"names": "Abraham Lincoln"
}
}
}
针对position_increment_gap=0时,能匹配上文档,如果position_increment_gap=10,则无法匹配到文档,因为abraham与lincoln的位置相差10,如果要能匹配到该文档,需要在查询时设置slop=10,该参数在前面的DSL查询部分已详细介绍过。
本文详细介绍了Elasticsearch在创建类型映射时支持的参数。
欢迎加笔者微信号(dingwpmz),加群探讨,笔者优质专栏目录:
1、源码分析RocketMQ专栏(40篇+)
2、源码分析Sentinel专栏(12篇+)
3、源码分析Dubbo专栏(28篇+)
4、源码分析Mybatis专栏
5、源码分析Netty专栏(18篇+)
6、源码分析JUC专栏
7、源码分析Elasticjob专栏
8、Elasticsearch专栏(20篇+)
9、源码分析MyCat专栏