ElasticSearch - 底层原理

 

 

测试数据

"index": false 不生成倒排索引

"doc_values": false 不生成正排索引


正排索引(doc values )VS 倒排索引:

  1. 概念:从广义来说,doc values 本质上是一个序列化的 列式存储 。列式存储 适用于聚合、排序、脚本等操作,所有的数字、地理坐标、日期、IP 和不分析( not_analyzed )字符类型都会默认开启。

  2. 特点:倒排索引的优势 在于查找包含某个项的文档,相反,如果用它确定哪些项是否存在单个文档里。

  3. 优化es官方是建议,es大量是基于os cache来进行缓存和提升性能的,不建议用jvm内存来进行缓存,那样会导致一定的gc开销和oom问题,给jvm更少的内存,给os cache更大的内存。比如64g服务器,给jvm最多4~16g(1/16~1/4),os cache可以提升doc value和倒排索引的缓存和查询效率。

正排索引: doc_values VS fielddata

这两个概念源于ES , 除了强大的搜索功能外 , 还可以支持排序,聚合之类的操作

搜索需要用到倒排索引,而排序和聚合则需要使用 “正排索引”。说白了就是一句话,倒排索引的优势在于查找包含某个项的文档,而反过来确定哪些项在单个文档里并不高效。

doc_values和fielddata就是用来给文档建立正排索引的。他俩一个很显著的区别是,前者的工作地盘主要在磁盘,而后者的工作地盘在内存。

我整理了一个表格,从不同维度比较这哥俩。

维度

doc_values

fielddata

创建时间

index时创建

使用时动态创建

创建位置

磁盘

内存(jvm heap)

优点

不占用内存空间

不占用磁盘空间

缺点

索引速度稍低

文档很多时,动态创建开销比较大,而且占内存

索引速度稍低这个是相对于fielddata方案的,其实仔细想想也可以理解。那排序举例,相对于一个在磁盘排序,一个在内存排序。谁的速度快自然不用多说。

但是fielddata用的是jvm的内存, 所以默认是关闭的, 不推荐使用, 一般用doc_values就足够了

在ES 1.x版本的官方说法是,

Doc values are now only about 10–25% slower than in-memory fielddata

虽然速度稍慢,doc_values的优势还是非常明显的。一个很显著的点就是他不会随着文档的增多引起OOM问题。正如前面说的,doc_values在磁盘创建排序和聚合所需的正排索引。这样我们就避免了在生产环境给ES设置一个很大的HEAP_SIZE,也使得JVM的GC更加高效,这个又为其它的操作带来了间接的好处。

而且,随着ES版本的升级,对于doc_values的优化越来越好,索引的速度已经很接近fielddata了,而且我们知道硬盘的访问速度也是越来越快(比如SSD)。所以 doc_values 现在可以满足大部分场景,也是ES官方重点维护的对象。

所以我想说的是,doc values相比field data还是有很多优势的。所以 ES2.x 之后,支持聚合的字段属性默认都使用doc_values,而不是fielddata。

 


基于mget批量查询以及基于bulk的批量增删改

mget:批量查询

index:     GET /_mget

GET /_mget
{
  "docs" : [
    {
      "_index": "product"
      ,"_id": 1
    },{
      "_index": "product"
      ,"_id": 2
    }
  ]
}

同一个index:   GET /<index>/_mget

GET /product/_mget
{
  "ids" : [1, 2]
}

返回的结果都是

{
  "docs" : [
    {
      "_index" : "product",
      "_type" : "_doc",
      "_id" : "1",              // id为1
      "_version" : 1,
      "_seq_no" : 0,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "name" : "xiaomi phone",
        "desc" : "shouji zhong de zhandouji",
        "price" : 3999,
        "tags" : [
          "xingjiabi",
          "fashao",
          "buka"
        ]
      }
    },
    {
      "_index" : "product",
      "_type" : "_doc",
      "_id" : "2",             // id为2
      "_version" : 1,
      "_seq_no" : 1,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "name" : "xiaomi nfc phone",
        "desc" : "zhichi quangongneng nfc,shouji zhong de jianjiji",
        "price" : 4999,
        "tags" : [
          "xingjiabi",
          "fashao",
          "gongjiaoka"
        ]
      }
    }
  ]
}

 

include包含哪些字段  exclude排除哪些字段

GET /product/_mget
{
  {
    "docs":[
        {
            "_id":2,
            "_source":false
        },
        {
            "_id":3,
            "_source":[
                "name",
                "price"
            ]
        },
        {
            "_id":4,
            "_source":{
                "include":[
                    "name"
                ],
                "exclude":[
                    "price"
                ]
            }
        }
    ]
}

 

 

bulk:批量增删改  no-query

语法格式:

POST /_bulk

POST /<index>/_bulk

{"action": {"metadata"}}

{"data"}

POST /_bulk
{ "delete": { "_index": "product2",  "_id": "1" }}
{ "create": { "_index": "product2",  "_id": "2" }}
{ "name":    "_bulk create 2" }
{ "create": { "_index": "product2",  "_id": "12" }}
{ "name":    "_bulk create 12" }
{ "index":  { "_index": "product2",  "_id": "3" }}
{ "name":    "index product2 " }
{ "index":  { "_index": "product2",  "_id": "13" }}
{ "name":    "index product2" }
{ "update": { "_index": "product2",  "_id": "4","retry_on_conflict" : "3"} }
{ "doc" : {"test_field2" : "bulk test1"} }

#加?filter_path=items.*.error  只显示失败的

All REST APIs accept a filter_path parameter that can be used to reduce the response returned by Elasticsearch. This parameter takes a comma separated list of filters expressed with the dot notation:

GET /_search?q=kimchy&filter_path=took,hits.hits._id,hits.hits._score

Responds:

{
    "took":3,
    "hits":{
        "hits":[
            {
                "_id":"0",
                "_score":1.6375021
            }
        ]
    }
}

 

Operate

  1. create:PUT /index/_create/id/,强制创建(如果已经存在, 会报错)(最好指定id, 否则生成的id是字符串形式)

  2. delete:删除(lazy delete原理)

  3. index:可以是创建,也可以是全量替换

  4. update:执行partial update(全量替换,部分替换)

ES并发冲突问题(悲观锁和乐观锁)

  1. 悲观锁:各种情况,都加锁,读写锁、行级锁、表级锁。使用简单,但是并发能力很低

  2. 乐观锁:并发能力高,操作麻烦,每次no-query操作都需要比对version

ES现在用的是乐观锁, 根据version字段控制

 

 


 

ES写入原理

 

1. 每次ES写入请求, 都会插入到内存Buffer

2. Refresh操作

Buffer如果被写满了, 或者到了1秒 ( refresh_interval ) 的间隔期限, 会Buffer里的缓存的文档, 写入Segment, 并把Segment同步到OS Cache里

如果出现断电等异常, 那么这部分数据就丢失了. 解决方案是translog文件

同步到OS cache之后, 立即打开Segment, 使原先处于CLOSE状态的Segment, 变成OPEN状态, 客户端的请求就可以打到Segment上了

所以, 数据从写入到可以被搜索, 花了大概1秒多的时间, 所以ES可以被称为NTR近实时搜索.

3. Flush操作

OS Cache的数据量不断的往上涨, 当达到一定阈值, 或者一定时间(默认30分钟), 就会触发flush

将缓存中的Segment全部写入磁盘并确保写入成功, 同时创建一个commit point, 整个过程就是一个完整的commit过程

flush操作步骤:

1. 把Buffer中缓存的文档flush到OS Cache

2. 把OS Cache中缓存的Segment全部fsync到OS Disk

3. 清空translog

fsync到OS Disk, 数据才算落地, 之前的过程中都会有断电丢失数据的可能

图中黄色部分是内存操作, 速度是非常快的

如何解决机器断电的情况下 , OS Cache里的数据丢失的问题?

每进行一次写入Buffer的操作, 都会同时在tranlog文件里记一条日志

当OS Cache同步到OS Disk完成后, 才把对应的tranlog记录清空

 

每隔1秒, 都会生成一个Segment, 会不会无休止的生成Segment? 解决方案: 合并Segment

1. 选择相似Segment合并

2. flush操作

3. 创建新的commit point标记新的segment, 删除旧的标记

4. 将新的segment搜索状态打开

5. 删除旧的segment文件

 

commit point

包含了当前可用的Segment

 

.del文件

被删除的doc不会立马删除, 而是记在.del文件里, 标记删除

更新操作, 也是把原先的记录在.del文件里标记为删除, 然后插入一条新的记录

 

设置refresh间隔时间

PUT /index
{
  "settings": {
    "refresh_interval": "2s"
  }
}

手动执行translog ( 慎用 )

POST /index/_flush