浅析ES的_source、_all、store、index

前言

Elasticsearch中有大量关键概念容易混淆,对于初学者来说是噩梦:

  • _source字段里存储了什么?
  • index属性的作用是什么?
  • 何时应该开启_all字段?
  • store属性和_source字段有什么关系?
  • store属性和_all字段有什么关系?
  • 什么情况下不用保留_source字段?

本文通过问答及展开描述的方式,深入理解Elasticsearch中的_source_all字段和storeindex属性。

概念解释

Q:_source字段里存储了什么?
A:原始文档的内容。

 

es _source 用法 es _all _source_字段

图1 Elasticsearch中的_source、_all、store和index属性解析

如图1所示, 第二象限是一份原始文档,有titlecontent2个字段,字段取值分别为"我是中国人"和"热爱***",这一点没什么可解释的。我们把原始文档写入Elasticsearch,默认情况下,Elasticsearch里面有2份内容,一份是原始文档,也就是_source字段里的内容,我们在Elasticsearch中搜索文档,查看的文档内容就是_source中的内容,如图2,相信大家一定非常熟悉这个界面。

 

es _source 用法 es _all _source_es _source 用法_02

图2 _source字段举例

另一份是倒排索引,倒排索引中的数据结构是倒排记录表,记录了词项和文档之间的对应关系,比如关键词"中国人"包含在文档ID为1的文档中,倒排记录表中存储的就是这种对应关系,当然也包括词频等更多信息。Elasticsearch底层用的是Lucene的API,Elasticsearch之所以能完成全文搜索的功能就是因为存储有倒排索引。如果把倒排索引拿掉,Elasticsearch是不是和mongoDB很像?

Q:index属性的作用是什么?
A:控制field是否生成倒排索引以及生成索引时是否做分词。

那么文档索引到Elasticsearch的时候,默认情况下是对所有字段创建倒排索引的(动态mapping解析出来为数字类型、布尔类型的字段除外),某个字段是否生成倒排索引是由字段的index属性控制的,在Elasticsearch 5之前,index属性的取值有三个:

  • analyzed:字段被索引,会做分词,可搜索。反过来,如果需要根据某个字段进行搜索,index属性就应该设置为analyzed
  • not_analyzed:字段值不分词,会被原样写入索引。反过来,如果某些字段需要完全匹配,比如人名、地名,index属性设置为not_analyzed为佳。
  • no:字段不写入索引,当然也就不能搜索。反过来,有些业务要求某些字段不能被搜索,那么index属性设置为no即可。

Q:何时应该开启_all字段?
A:_all字段开启适用于不指定搜索某一个字段,根据关键词,搜索整个文档内容。

再说_all字段,顾名思义,_all字段是把所有其它字段中的值,以空格为分隔符组成一个大字符串,然后被分析和索引,但是不存储,也就是说它能被查询,但不能被取回显示,是一个超级字段。以图中的文档为例,如果开启_all字段,那么title和content的值会组成一个超级字段,当然也可以设置只存储某几个字段到_all属性里面或者排除某些字段。_all字段在查询时占用更多的CPU和占用更多的磁盘空间,如果确实不需要它可以完全的关闭它或者基于字段定制。

_all能让你在不知道要查找的内容是属于哪个具体字段的情况下进行搜索,例如:

PUT my_index/user/1 
{
  "first_name":    "John",
  "last_name":     "Smith",
  "date_of_birth": "1970-10-24"
}
GET my_index/_search
{
  "query": {
    "match": {
      "_all": "john smith 1970"
    }
  }
}

得到的结果是:

{
  "took": 20,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.84748024,
    "hits": [
      {
        "_index": "my_index",
        "_type": "user",
        "_id": "1",
        "_score": 0.84748024,
        "_source": {
          "first_name": "John",
          "last_name": "Smith",
          "date_of_birth": "1970-10-24"
        }
      }
    ]
  }
}

Q:store属性和_source字段有什么关系?
A:_source是字段,store是字段的属性之一,Elasticsearch默认会存储一份_source字段作为原始文档。store属性和_source字段配合进行设置可以决定字段的检索方式和是否高亮展示。

回到图一的第一象限,用户输入关键词"中国人",分词以后,Elasticsearch从倒排记录表中查找哪些文档包含词项"中国人 ",注意变化,分词之前" 中国人"是用户查询(query),分词之后在倒排索引中" 中国人"是词项(term)。Elasticsearch根据文档ID(通常是文档ID的集合)返回文档内容给用户,如图一第四象限所示。

通常情况下,对于用户查询的关键字要做高亮处理,如图3所示:

 

es _source 用法 es _all _source_es _source 用法_03

图3 搜索引擎中的关键字高亮

关键字高亮实质上是根据倒排记录中的词项偏移位置,找到关键词,加上前端的高亮代码。这里就要说到store属性,store属性用于指定是否将原始字段写入索引,默认取值为false。如果在Lucene中,高亮功能和store属性是否存储息息相关,因为需要根据偏移位置到原始文档中找到关键字才能加上高亮的片段。两者不同取值时对字段检索和高亮的影响如下表:

_source\store

true

false

enabled

store为true的字段从倒排索引里检索,浪费IO次数;

所有字段根据Client类解析存储的 _source JSON串进行检索,仅需一次IO;

disabled

store为true的字段从倒排索引里检索,其他字段能检索不能高亮展示;

所有字段只能检索,不能高亮展示;

在Elasticsearch,因为_source中已经存储了一份原始文档,可以根据_source中的原始文档实现高亮,在索引中再存储原始文档就多余了,所以Elasticsearch默认是把store属性设置为false

如果想要对某个字段实现高亮功能,_sourcestore至少保留一个。下面给出一个例子,设置test索引不保存_source,title字段索引但不分析,字段原始值写入索引,content字段为默认属性,代码如下:

  • 新建test index:
PUT test/test/_mapping
{
   "test": {
      "_source": {
         "enabled": false
      },
      "properties": {
         "title": {
            "type": "string",
            "index": "not_analyzed",
            "store": "true"
         },
         "content": {
            "type": "string"
         }
      }
   }
}
  • 对title字段进行搜索并高亮:
GET test/_search
{
    "query": {
        "match": {
           "title": "我是中国人"
        }
    },
   "highlight": {
      "fields": {
         "title": {}
      }
   }
}
  • 返回结果:
{
   "took": 6,
   "timed_out": false,
   "_shards": {
      "total": 5,
      "successful": 5,
      "failed": 0
   },
   "hits": {
      "total": 1,
      "max_score": 0.30685282,
      "hits": [
         {
            "_index": "test",
            "_type": "test",
            "_id": "1",
            "_score": 0.30685282,
            "highlight": {
               "title": [
                  "<em>我是中国人</em>"
               ]
            }
         }
      ]
   }
}

从返回结果中可以看到,虽然没有保存title字段到_source, 但是依然可以实现搜索高亮。

{"store": true}既然这么费力不讨好,但是仍然有两个应用场景:

  • 文档很长,检索所有文档或者存储所有文档、获取所有field的代价比较大;
  • 仅仅针对某几个字段进行re-index的时候;

Q:store属性和_all字段有什么关系?
A:_all字段拥有store属性,是从属关系。store属性决定了_all字段是否可以被高亮展示。

首先store_all字段的一个属性,_all字段默认不属于_source,并且store属性默认为false,所以不能被高亮展示。如果希望被高亮展示,则有两种方式:

  • 设置_all字段的store为true,代价是大量的冗余存储;
  • 对原始字段进行高亮;

Q:什么情况下不用保留_source字段?
A:需要存储海量文档,Elasticsearch在架构中只做索引,存储选型为类似HBase的NoSQL,不存储_source可以极大减少存储开销(Elasticsearch往往用的是SSD)。

_source字段默认是存储的, 什么情况下不用保留_source字段?如果某个字段内容非常多,业务只需要能对该字段进行搜索,最后返回文档id,查看文档内容会再次到HBase中取数据,把大字段的内容存在Elasticsearch中只会增大索引,这一点文档数量越大结果越明显,如果一条文档节省几KB,放大到亿万级的量结果也是非常可观的。 如果想要关闭_source字段,在mapping中的设置如下:

{
    "yourtype":{
        "_source":{
            "enabled":false
        },
        "properties": {
            ... 
        }
    }
}

如果只想存储某几个字段的原始值到Elasticsearch,可以通过incudes参数来设置,在mapping中的设置如下:

{
    "yourtype":{
        "_source":{
            "includes":["field1","field2"]
        },
        "properties": {
            ... 
        }
    }
}

同样,可以通过excludes参数排除某些字段:

{
    "yourtype":{
        "_source":{
            "excludes":["field1","field2"]
        },
        "properties": {
            ... 
        }
    }
}

小结

至此,文章开头提出的几个问题都给出了答案。特别参考了这篇文章:图解Elasticsearch中的_source、_all、store和index属性

追加:

1、_source

_source字段我在们进行检索时相当重要,

ES默认检索只会返回ID,如果在{"enabled":false}情况下,你需通过根据这个ID去去倒排索引中去取每个Field数据,效率不高。

而反之,在{"enabled":true}的情况下可以根据ID直接检索对应source JSON的字段,不用去倒排索引去按Field取数据。

 

尽管_source非常有用, 但它确实会占用索引的存储空间, 所以也可以禁用(enabled false)、启用状态的压缩策略(compress)。

压缩的策略有两类:

  • compress:是否进行压缩,建议一般情况下将其设为true 
  • "includes" : ["author", "name"], "excludes" : ["sex"] 

2、store

默认为no,

如果在{"store":yes}的情况下,ES会对该字段单独存储倒排索引,每次根据ID检索的时候,会多走一次IO来从倒排索引取数据。

而如果_source enabled 情况下,ES可以直接根据Client类来解析_source JSON,只需一次IO就将所有字段都检索出来了。

 

{"store":yes}既然这么费力不讨好,但是仍然有两个应用场景:

  • 文档很长,检索所有文档或者存储所有文档、获取所有field的代价比较大
  • 仅仅针对某几个字段进行re-index的时候

3、总结

_source\store 

yes

no

enabled

store为yes的字段从倒排索引里检索,

浪费IO次数

所有字段根据Client类解析实现存储的JSON串

仅需一次IO

disabled

store为yes的字段从倒排索引里检索,

其他字段能检索不能展示  

所有字段只能检索,不能展示

PS. 索引后-->可查询检索,存储后-->可展示

999、参考

ElasticSearch学习

elasticsearch文档-字段的mapping

elasticsearch的store属性跟_source字段