Elasticsearch之深入理解

@

目录

ES应用场景

ES应用场景举例
  1. 维基百科,类似百度百科,全文检索,高亮,搜索推荐
  2. The Guardian(国外新闻网站),用户行为日志(点击,浏览,收藏,评论)+社交网络数据,数据分析
  3. Stack Overflow(国外的程序异常讨论论坛)
  4. GitHub(开源代码管理)
  5. 电商网站,检索商品
  6. 日志数据分析,logstash采集日志,ES进行复杂的数据分析(ELK技术,elasticsearch+logstash+kibana)
  7. 商品价格监控网站
  8. BI系统,商业智能,Business Intelligence。
  9. 国内:站内搜索(电商,招聘,门户,等等),IT系统搜索(OA,CRM,ERP,等等),数据分析(ES热门的一个使用场景)
应用场景思考

ES的优势在倒排索引的设计,以及单节点PB级数据存储;所以有搜索全文或大数据需求,可以考虑使用ES。

ES和其他数据库对比

在这里插入图片描述
在这里插入图片描述
https://blog.csdn.net/weixin_39576751/article/details/112272464

ES架构

在这里插入图片描述

Gateway

存储索引文件的一个文件系统且它支持很多类型,主要职责是对数据的持久化以及整个集群重启后可以通过gateway重新恢复数据

支持的格式有:本地Local FileSystem、分布式Shared FileSystem(做snapshot的时候会用到)、Hadoop的文件系统HDFS、Amazon(亚马逊)的S3。

Lucene

ES是基于Lucene的,一个分片就是一个lucene。 Lucene是做检索的,但是他是一个单机的搜索引擎,ES底层使用Lucene需要在每个节点上都运行Lucene进行响应的索引,查询及更新所以需要支持业务层的分布式

数据处理
  • Index Module:是索引模块,就是对数据建立索引也就是通常所说的建立一些倒排索引等
  • Search Module:是搜索模块,就是对数据进行查询搜索
  • Mapping模块:是数据映射与解析模块,数据的每个字段根据建立的表结构通过mapping进行映射解析,如果没有建表结构,会根据数据类型推测数据结构自动生成一个mapping
  • River模块:是运行在ES内部的一个插件,主要用来从外部获取异构数据,然后在ES里创建索引

River常见的插件有RabbitMQ River、Twitter River,例如可以通过一些自定义的脚本将传统的数据库(mysql)等数据源通过格式化转换后直接同步到es集群里,这个river大部分是自己写的,写出来的东西质量参差不齐,将这些东西集成到es中会引发很多内部bug,严重影响了es的正常应用,所以在es2.0之后考虑将其去掉。

发现机制与脚本

Elasticsearch是基于P2P的系统,它首先通过广播的机制寻找存在的节点,然后再通过多播协议来进行节点间的通信,同时也支持点对点的交互

  • Directory 是ES自动发现节点的机制,Master选举,默认使用的是 Zen,也可是使用EC2
  • Scripting 是脚本执行功能,有这个功能能很方便对查询出来的数据进行加工处理
  • 3rd Plugins 表示ES支持安装很多第三方的插件,例如elasticsearch-ik分词插件、elasticsearch-sql sql插件。
Transport

ES支持多种通信接口,Thrift、Memcached以及Http,默认的是http,JMX就是java的一个远程监控管理框架

REST full API

ES暴露给我们的访问接口,官方推荐的方案就是Restful接口,直接发送http请求,方便后续使用nginx做代理、分发包括可能后续会做权限的管理,通过http很容易做这方面的管理。如果使用java客户端它是直接调用api,在做负载均衡以及权限管理还是不太好做

ES中集群、节点、索引、分片、段等概念

集群

查看集群统计信息(对大集群非常有用),可以查看jvm、os等信息,进行调优或监控很有帮助,具体参数解释查看:https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-stats.html

GET _cluster/stats
{
   "_nodes" : {
      "total" : 1,
      "successful" : 1,
      "failed" : 0
   },
   "cluster_uuid": "YjAvIhsCQ9CbjWZb2qJw3Q",
   "cluster_name": "elasticsearch",
   "timestamp": 1459427693515,
   "status": "green",
   "indices": {
      "count": 1,
      "shards": {
         "total": 5,
         "primaries": 5,
         "replication": 0,
         "index": {
            "shards": {
               "min": 5,
               "max": 5,
               "avg": 5
            },
            "primaries": {
               "min": 5,
               "max": 5,
               "avg": 5
            },
            "replication": {
               "min": 0,
               "max": 0,
               "avg": 0
            }
         }
      },
      "docs": {
         "count": 10,
         "deleted": 0
      },
      "store": {
         "size": "16.2kb",
         "size_in_bytes": 16684,
         "total_data_set_size": "16.2kb",
         "total_data_set_size_in_bytes": 16684,
         "reserved": "0b",
         "reserved_in_bytes": 0
      },
      "fielddata": {
         "memory_size": "0b",
         "memory_size_in_bytes": 0,
         "evictions": 0
      },
      "query_cache": {
         "memory_size": "0b",
         "memory_size_in_bytes": 0,
         "total_count": 0,
         "hit_count": 0,
         "miss_count": 0,
         "cache_size": 0,
         "cache_count": 0,
         "evictions": 0
      },
      "completion": {
         "size": "0b",
         "size_in_bytes": 0
      },
      "segments": {
         "count": 4,
         "memory": "8.6kb",
         "memory_in_bytes": 8898,
         "terms_memory": "6.3kb",
         "terms_memory_in_bytes": 6522,
         "stored_fields_memory": "1.2kb",
         "stored_fields_memory_in_bytes": 1248,
         "term_vectors_memory": "0b",
         "term_vectors_memory_in_bytes": 0,
         "norms_memory": "384b",
         "norms_memory_in_bytes": 384,
         "points_memory" : "0b",
         "points_memory_in_bytes" : 0,
         "doc_values_memory": "744b",
         "doc_values_memory_in_bytes": 744,
         "index_writer_memory": "0b",
         "index_writer_memory_in_bytes": 0,
         "version_map_memory": "0b",
         "version_map_memory_in_bytes": 0,
         "fixed_bit_set": "0b",
         "fixed_bit_set_memory_in_bytes": 0,
         "max_unsafe_auto_id_timestamp" : -9223372036854775808,
         "file_sizes": {}
      },
      "mappings": {
        "field_types": [],
        "runtime_field_types": []
      },
      "analysis": {
        "char_filter_types": [],
        "tokenizer_types": [],
        "filter_types": [],
        "analyzer_types": [],
        "built_in_char_filters": [],
        "built_in_tokenizers": [],
        "built_in_filters": [],
        "built_in_analyzers": []
      },
      "versions": [
        {
          "version": "8.0.0",
          "index_count": 1,
          "primary_shard_count": 1,
          "total_primary_size": "7.4kb",
          "total_primary_bytes": 7632
        }
      ]
   },
   "nodes": {
      "count": {
         "total": 1,
         "data": 1,
         "coordinating_only": 0,
         "master": 1,
         "ingest": 1,
         "voting_only": 0
      },
      "versions": [
         "7.14.0"
      ],
      "os": {
         "available_processors": 8,
         "allocated_processors": 8,
         "names": [
            {
               "name": "Mac OS X",
               "count": 1
            }
         ],
         "pretty_names": [
            {
               "pretty_name": "Mac OS X",
               "count": 1
            }
         ],
         "architectures": [
            {
               "arch": "x86_64",
               "count": 1
            }
         ],
         "mem" : {
            "total" : "16gb",
            "total_in_bytes" : 17179869184,
            "free" : "78.1mb",
            "free_in_bytes" : 81960960,
            "used" : "15.9gb",
            "used_in_bytes" : 17097908224,
            "free_percent" : 0,
            "used_percent" : 100
         }
      },
      "process": {
         "cpu": {
            "percent": 9
         },
         "open_file_descriptors": {
            "min": 268,
            "max": 268,
            "avg": 268
         }
      },
      "jvm": {
         "max_uptime": "13.7s",
         "max_uptime_in_millis": 13737,
         "versions": [
            {
               "version": "12",
               "vm_name": "OpenJDK 64-Bit Server VM",
               "vm_version": "12+33",
               "vm_vendor": "Oracle Corporation",
               "bundled_jdk": true,
               "using_bundled_jdk": true,
               "count": 1
            }
         ],
         "mem": {
            "heap_used": "57.5mb",
            "heap_used_in_bytes": 60312664,
            "heap_max": "989.8mb",
            "heap_max_in_bytes": 1037959168
         },
         "threads": 90
      },
      "fs": {
         "total": "200.6gb",
         "total_in_bytes": 215429193728,
         "free": "32.6gb",
         "free_in_bytes": 35064553472,
         "available": "32.4gb",
         "available_in_bytes": 34802409472
      },
      "plugins": [
        {
          "name": "analysis-icu",
          "version": "7.14.0",
          "description": "The ICU Analysis plugin integrates Lucene ICU module into elasticsearch, adding ICU relates analysis components.",
          "classname": "org.elasticsearch.plugin.analysis.icu.AnalysisICUPlugin",
          "has_native_controller": false
        },
        ...
      ],
      "ingest": {
        "number_of_pipelines" : 1,
        "processor_stats": {
          ...
        }
      },
      "network_types": {
        ...
      },
      "discovery_types": {
        ...
      },
      "packaging_types": [
        {
          ...
        }
      ]
   }
}

查看集群状态,可以更细到索引层面,查看具体哪个索引导致集群状态Red等。

GET _cluster/health?level=indices
GET _cluster/health?level=shards

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_cluster_health.html

节点

es集群最大节点建议200。过多节点会产生什么问题?

对于每个 Elasticsearch 索引,映射和状态的相关信息都存储在集群状态中。这些信息存储在内存中,以便快速访问。因此,如果集群中的索引和分片数量过多,这会导致集群状态过大,如果映射较大的话,尤为如此。这会导致更新变慢,因为所有更新都需要通过单线程完成,从而在将变更分发到整个集群之前确保一致性

集群状态会加载到每个节点(包括主节点)上的堆内存中,而且堆内存大小与索引数量以及单个索引和分片中的字段数成正比关系,所以还需要同时监测主节点上的堆内存使用量并确保其大小适宜,这一点很重要。

GET /_nodes/stats # 同上集群
索引

索引相当于数据库中Database。每个索引又由一个或多个分片组成。每个分片都是一个 Lucene 索引实例,您可以将其视作一个独立的搜索引擎,它能够对 Elasticsearch 集群中的数据子集进行索引并处理相关查询。

Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields

分片

一个索引默认1000分片(参数:cluster.max_shards_per_node)。

人们通常将 50GB 作为分片上限,而且这一限值在各种用例中都已得到验证

这里有一个很好的经验法则:确保对于节点上已配置的每个 GB,将分片数量保持在 20 以下。如果某个节点拥有 30GB 的堆内存,那其最多可有 600 个分片,但是在此限值范围内,您设置的分片数量越少,效果就越好。一般而言,这可以帮助集群保持良好的运行状态。

GET /_cat/shards

https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster

段(segment)
GET /_cat/segments?v
index shard prirep ip        segment generation docs.count docs.deleted size size.memory committed searchable version compound
test  0     p      127.0.0.1 _0               0          1            0  3kb        2042 false     true       8.0.0   true
test1 0     p      127.0.0.1 _0               0          1            0  3kb        2042 false     true       8.0.0   true

GET _segments  # 可查看每个段信息

已提交索引段是指那些已经执行过提交命令的段,意味着在磁盘已经持久化,且是只读的

文档(document)

一个文档是一个可被索引的基础信息单元。存储的数据是文档,一条数据对应一篇文档,类似mysql数据库中的一行数据

字段(field)

一个文档中对应的多个列,类似mysql数据库中一行有多列。字段包括词项和词项对应的value值

词项(term)

词项是索引的最小单位,一般可以把词项当作词

词条(token)(_analyze查询)

查询token:https://www.elastic.co/guide/en/elasticsearch/reference/1.6/docs-termvectors.html#_term_information

GET /_analyze?
{
  "analyzer": "standard",
  "text": "orJ2t4r8Rlgz-988Y947mMas5zuU"
}
返回:
{
    "tokens": [
        {
            "token": "orj2t4r8rlgz",
            "start_offset": 0,
            "end_offset": 12,
            "type": "<ALPHANUM>",
            "position": 0
        },
        {
            "token": "988y947mmas5zuu",
            "start_offset": 13,
            "end_offset": 28,
            "type": "<ALPHANUM>",
            "position": 1
        }
    ]
}
Mapping

可以理解为mysql或者solr中对应的schema,只不过这里的mapping增加了动态识别功能

在这里插入图片描述
在这里插入图片描述

分片解决什么问题?

允许集群系统存储数据总量超过单机容量

分片数该怎么设置?

默认5分片是标准用法。
如果有一个有限且明确的数据集——可以只使用一个分片。
如果没有,最理想分片数应该依赖于节点的数量:

节点最大数 = 分片数 *(副本数 + 1)

节点最大数该如何确定?

副本解决什么问题?

解决访问压力过大时,单机无法处理所有请求,应对增长的并发查询。

缺点:各分片副本占用额外的存储空间,主分片及副本之间数据拷贝也存在时间开销。

集群的初期规划

单集群最多节点200

单节点最多600分片,es默认每个节点最多1000分片

确保对于节点上已配置的每个 GB,将分片数量保持在 20 以下。如果某个节点拥有 30GB 的堆内存,那其最多可有 600 个分片,但是在此限值范围内,您设置的分片数量越少,效果就越好

单分片大小控制在50GB

最后根据自己要存储的数据量,进行压测。再根据压测数据,对索引设置分片大小。

创建一定量数据后,可:

GET _cluster/stats  # 查看集群状态(os、jvm、indices、nodes等信息)
GET /_nodes/stats # 同上集群
GET /_cat/shards # 查看索引分片
GET /_cat/segments # 查看segments

ES底层数据结构

Elasticsearch Server(Elasticsearch caches)
https://elastic.blog.csdn.net/article/details/108970774

un-inverted data(Doc Values 正排索引)

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_deep_dive_on_doc_values.html

场景

Elasticsearch 中的 Doc Values 常被应用到以下场景:

  • 对一个字段进行排序
  • 对一个字段进行聚合
  • 某些过滤,比如地理位置过滤
  • 某些与字段相关的脚本计算
生成

Doc Values 是在索引时与 倒排索引 同时生成。也就是说 Doc Values 和 倒排索引 一样,基于 Segement 生成并且是不可变的。同时 Doc Values 和 倒排索引 一样序列化到磁盘,这样对性能和扩展性有很大帮助。

因为文档值被序列化到磁盘,我们可以依靠操作系统的帮助来快速访问。

当 工作集(working set) 远小于节点的可用内存,系统会自动将所有的文档值保存在内存中,使得其读写十分高速;

当其远大于可用内存,操作系统会自动把 Doc Values 加载到系统的页缓存中,从而避免了 jvm 堆内存溢出异常

禁用Doc Values

Doc Values 默认对所有字段启用,除了 analyzed strings。也就是说所有的数字、地理坐标、日期、IP 和不分析( not_analyzed )字符类型都会默认开启。

analyzed strings 暂时还不能使用 Doc Values。文本经过分析流程生成很多 Token,使得 Doc Values 不能高效运行

PUT my_index
{
  "mappings": {
    "my_type": {
      "properties": {
        "session_id": {
          "type":       "string",
          "index":      "not_analyzed",
          "doc_values": false 
        }
      }
    }
  }
}
列式存储

在这里插入图片描述

列存储优势:

  1. 自动索引

因为基于列存储,所以每一列本身就相当于索引。所以在做一些需要索引的操作时,就不需要额外的数据结构来为此列创建合适的索引。

  1. 利于数据压缩

利于压缩有两个原因。一来你会发现大部分列数据基数其实是重复的,二来相同的列数据类型一致,这样利于数据结构填充的优化和压缩,而且对于数字列这种数据类型可以采取更多有利的算法去压缩存储。

https://blog.csdn.net/dc_726/article/details/41143175

inverted data(倒排索引)

倒排索引特点
  • 在索引时创建
  • 序列化到磁盘
  • 全文搜索非常快
  • 不适合做排序
  • 默认开启
倒排索引适用场景
  • 查询
  • 全文检索
倒排索引数据结构

文本字段存储在倒排索引中,数字字段和地理字段存储在BKD树中。

数据类型 数据结构
text/keyword 倒排索引
数字/地理位置 BKD树

ES内存

es 内存由哪些东西占用

https://nereuschen.github.io/2015/09/16/ElasticSearch内存使用分析/

https://blog.csdn.net/jiao_fuyou/article/details/50509941

在这里插入图片描述

在这里插入图片描述

由于集群状态会加载到每个节点(包括主节点)上的堆内存中,而且堆内存大小与索引数量以及单个索引和分片中的字段数成正比关系,所以还需要同时监测主节点上的堆内存使用量并确保其大小适宜,这一点很重要。

每个分片都有一部分数据需要保存在内存中,这部分数据也会占用堆内存空间。这包括存储分片级别以及段级别信息的数据结构,因为只有这样才能确定数据在磁盘上的存储位置。这些数据结构的大小并不固定,不同用例之间会有很大的差别

为了能够在单个节点上存储尽可能多的数据,下面两点至关重要:管理堆内存使用量;尽可能减少开销。节点的堆内存空间越多,其能处理的数据和分片就越多。
(https://www.elastic.co/cn/blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster)

https://www.elastic.co/cn/blog/significantly-decrease-your-elasticsearch-heap-memory-usage
https://blog.csdn.net/qq_42046105/article/details/91488572?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162900587816780264061739%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=162900587816780264061739&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_v2~rank_v29-3-91488572.pc_v2_rank_blog_default&utm_term=elasticsearch&spm=1018.2226.3001.4450

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_finding_exact_values.html#_internal_filter_operation
https://www.elastic.co/guide/cn/elasticsearch/guide/current/filter-caching.html
https://blog.csdn.net/jiao_fuyou/article/details/50509941

https://segmentfault.com/a/1190000040238635?utm_source=sf-similar-article

query_cache

Query Cache也称为Filter Cache,顾名思义它的作用就是对一个查询中包含的过滤器执行结果进行缓存。
比如我们常用的term,terms,range过滤器都会在满足某种条件后被缓存,注意,这里的bool过滤器是不会被缓存的,但bool过滤器包含的子query clause会被缓存

(5.1.1版本term被取消了)

query_cache总结
  1. Query Cache是节点级别的,每个节点上的所有分片共享一份缓存
  2. Query Cache采用LRU缓存失效策略
  3. Query Cache只能在Filter Context中被使用
  4. Query Cache可以通过 indices.queries.cache.size来设置缓存占用大小,默认10%,可手动设置比如512mb
  5. Query Cache实际缓存的是Bitset(位图),一个Query clause对应一个Bitset
  6. 缓存要生效,必须满足两个条件
    a)查询对应的segments所持有的文档数必须大于10000
    b)查询对应的segments所持有的文档数必须大于整个索引size的3%
  7. 当新索引文档时,Query Cache不会重新计算,而是判断索引的文档是否符合缓存对应的Query clause,满足则加入BitSet中

https://blog.csdn.net/chennanymy/article/details/52403594
https://segmentfault.com/a/1190000040238635?utm_source=sf-similar-article

过滤器内部原理

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_finding_exact_values.html#_internal_filter_operation

  1. 查找匹配文档.

term 查询在倒排索引中查找 XHDK-A-1293-#fJ3 然后获取包含该 term 的所有文档。本例中,只有文档 1 满足我们要求。

  1. 创建 bitset.

过滤器会创建一个 bitset (一个包含 0 和 1 的数组),它描述了哪个文档会包含该 term 。匹配文档的标志位是 1 。本例中,bitset 的值为 [1,0,0,0] 。在内部,它表示成一个 "roaring bitmap",可以同时对稀疏或密集的集合进行高效编码。

  1. 迭代 bitset(s)

一旦为每个查询生成了 bitsets ,Elasticsearch 就会循环迭代 bitsets 从而找到满足所有过滤条件的匹配文档的集合。执行顺序是启发式的,但一般来说先迭代稀疏的 bitset (因为它可以排除掉大量的文档)。

  1. 增量使用计数.

Elasticsearch 能够缓存非评分查询从而获取更快的访问,但是它也会不太聪明地缓存一些使用极少的东西。非评分计算因为倒排索引已经足够快了,所以我们只想缓存那些我们 知道 在将来会被再次使用的查询,以避免资源的浪费。

为了实现以上设想,Elasticsearch 会为每个索引跟踪保留查询使用的历史状态。如果查询在最近的 256 次查询中会被用到,那么它就会被缓存到内存中。当 bitset 被缓存后,缓存会在那些低于 10,000 个文档(或少于 3% 的总索引数)的段(segment)中被忽略。这些小的段即将会消失,所以为它们分配缓存是一种浪费。

https://www.elastic.co/guide/cn/elasticsearch/guide/current/filter-caching.html

request_cache

全称是 Shard Request Cache,即分片级请求缓存。当对一个或多个索引发送搜索请求时,搜索请求首先会发送到ES集群中的某个节点,称之为协调节点;协调节点会把该搜索请求分发给其他节点并在相应分片上执行搜索操作,我们把分片上的执行结果称为“本地结果集”,之后,分片再将执行结果返回给协调节点;协调节点获得所有分片的本地结果集之后,合并成最终的结果并返回给客户端。

Request Cache 在每个分片上缓存了本地结果集,这使得频繁使用的搜索请求几乎立即返回结果。默认情况下只会缓存查询中参数 size=0 的搜索请求的结果,因此将不会缓存hits,但会缓存 hits.total,aggregations(聚合) 和 suggestions。所以,request cache 分片请求缓存非常适合日志用例场景,在这种情况下,数据不会在旧索引上更新,并且可以将常规聚合保留在高速缓存中以供重用。

https://blog.csdn.net/a745233700/article/details/118055871

fielddata

从历史上看,fielddata 是 所有 字段的默认设置。但是 Elasticsearch 已迁移到 doc values 以减少 OOM 的几率(ES2.0)。分析的字符串是仍然使用 fielddata 的最后一块阵地。 最终目标是建立一个序列化的数据结构类似于 doc values ,可以处理高维度的分析字符串,逐步淘汰 fielddata。

fielddata 需要与 request 断路器共享堆内存、索引缓冲内存和过滤器缓存

可配置:indices.fielddata.cache.size

GET /_nodes/stats/indices/fielddata
fielddata 特点
  • 适用于文档之类的操作,但仅适用于 text 文本字段类型
  • 在查询时创建
  • 内存中数据结构
  • 没有序列化到磁盘
  • 默认情况下被禁用(构建它们很昂贵,并且在堆中预置)
fielddata 适用场景
  • 全文统计词频
  • 全文生成词云
  • text类型:聚合、排序、脚本计算
fielddata 使用注意事项

在启用字段数据之前,请考虑为什么将文本字段用于聚合、排序或在脚本中使用。启用 fielddata 通常没有任何意义,因为它非常耗费内存资源。仅仅是做全文搜索的应用,就不需要启用fielddata。

https://blog.csdn.net/laoyang360/article/details/108970774
https://www.elastic.co/guide/cn/elasticsearch/guide/current/aggregations-and-analysis.html
Elasticsearch Server(Elasticsearch caches)

参考:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/_talking_to_elasticsearch.html
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-create-index.html

Indexing Buffer

Indexing Buffer是用来缓存新数据,当其满了或者refresh/flush interval到了,就会以segment file的形式写入到磁盘。 这个参数的默认值是10% heap size。根据经验,这个默认值也能够很好的工作,应对很大的索引吞吐量。 但有些用户认为这个buffer越大吞吐量越高,因此见过有用户将其设置为40%的。到了极端的情况,写入速度很高的时候,40%都被占用,导致OOM。

Cluster State Buffer

ES被设计成每个node都可以响应用户的api请求,因此每个node的内存里都包含有一份集群状态的拷贝。这个cluster state包含诸如集群有多少个node,多少个index,每个index的mapping是什么?有少shard,每个shard的分配情况等等 (ES有各类stats api获取这类数据)。 在一个规模很大的集群,这个状态信息可能会非常大的,耗用的内存空间就不可忽视了。并且在ES2.0之前的版本,state的更新是由master node做完以后全量散播到其他结点的。 频繁的状态更新都有可能给heap带来压力。 在超大规模集群的情况下,可以考虑分集群并通过tribe node连接做到对用户api的透明,这样可以保证每个集群里的state信息不会膨胀得过大。

超大搜索聚合结果集的fetch

ES是分布式搜索引擎,搜索和聚合计算除了在各个data node并行计算以外,还需要将结果返回给汇总节点进行汇总和排序后再返回。无论是搜索,还是聚合,如果返回结果的size设置过大,都会给heap造成很大的压力,特别是数据汇聚节点。超大的size多数情况下都是用户用例不对,比如本来是想计算cardinality,却用了terms aggregation + size:0这样的方式; 对大结果集做深度分页;一次性拉取全量数据等等。

为什么内存设置不要超过32G

Java 使用一个叫作 内存指针压缩(compressed oops)的技术来解决这个问题。 它的指针不再表示对象在内存中的精确位置,而是表示 偏移量 。这意味着 32 位的指针可以引用 40 亿个 对象 , 而不是 40 亿个字节。最终, 也就是说堆内存增长到 32 GB 的物理内存,也可以用 32 位的指针表示。

一旦你越过那个神奇的 ~32 GB 的边界,指针就会切回普通对象的指针。 每个对象的指针都变长了,就会使用更多的 CPU 内存带宽,也就是说你实际上失去了更多的内存。事实上,当内存到达 40–50 GB 的时候,有效内存才相当于使用内存对象指针压缩技术时候的 32 GB 内存。

这段描述的意思就是说:即便你有足够的内存,也尽量不要 超过 32 GB。因为它浪费了内存,降低了 CPU 的性能,还要让 GC 应对大内存。

( 大内存的机器、及swapping)

https://www.elastic.co/guide/cn/elasticsearch/guide/current/heap-sizing.html

ES语句执行过程

https://www.elastic.co/guide/cn/elasticsearch/guide/current/distrib-write.html

es 写数据过程

在这里插入图片描述
倒排索引写过程
在这里插入图片描述
https://www.elastic.co/guide/cn/elasticsearch/guide/current/translog.html

es 读数据过程

在这里插入图片描述

es 查询数据过程

  • 客户端发送请求到一个 coordinate node 。
  • 协调节点将搜索请求转发到所有的 shard 对应的 primary shard 或 replica shard ,都可以。
  • query phase:每个 shard 将自己的搜索结果(其实就是一些 doc id )返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
  • fetch phase:接着由协调节点根据 doc id 去各个节点上拉取实际的 document 数据,最终返回给客户端。

所有的查询都或多或少的会进行相关度计算,但不是所有的查询都会有分析阶段,文本查询可以分为两个部分:
  1. 基于词项的查询,如 term 或 fuzzy 这样的查询是没有分析阶段的。他们对单个词项进行操作。
  2. 基于全文的查询,比如match, 它们会先了解字段映射的信息,判断字段是否被分词,是否是日期还是数字等, 再根据映射信息,构建要查询的词项列表,根据列表进行查询。

https://www.jianshu.com/p/6d55b24fb9ab

段合并

https://www.elastic.co/guide/cn/elasticsearch/guide/current/dynamic-indices.html

ES分布式架构

分布式架构特性

透明隐藏
  • 分片机制:我们不用关心数据是按照什么机制分片的、最后放入到哪个分片中
  • 分片副本:实现数据的高可用与负载均衡
  • 集群发现机制(cluster discovery):比如当前我们启动了一个es进程,当启动了第二个es进程时,这个进程作为一个node自动就发现了集群,并且加入了进去
  • shard负载均衡:比如现在有10shard,集群中有3个节点,es会进行均衡的进行分配,以保持每个节点均衡的负载请求
  • 请求路由
  • 集群扩容:垂直扩容-替换低配置机器;水平扩容-直接增加新的机器
  • Shard重分配
rebalance

增加或者减少机器时候会自动均衡

master节点

主节点的主要职责是和集群操作相关的内容,如创建或删除索引,跟踪哪些节点是群集的一部分,并决定哪些分片分配给相关的节点。稳定的主节点对集群的健康是非常重要的

节点对等
  • 每个节点都能接收请求
  • 每个节点接收到请求后都能把该请求路由到有相关数据的其它节点上
    接收原始请求的节点负责采集数据并返回给客户端

集群节点类型

  • master:可竞选主节点
  • data:数据节点负责数据的存储和相关具体操作,比如CRUD、搜索、聚合
  • ingest:提取节点。Ingest node可以通过ingest pipeline对文档执行预处理操作,以便在索引文档之前对文档进行转换或者增强。
  • ml(Machine learning node):机器学习
  • remote_cluster_client
  • transform

https://www.elastic.co/guide/en/elasticsearch/reference/7.9/modules-node.html

Zen发现

Zen是Elasticsearch默认发现机制。Zen发现默认使用多播来发现其他节点。有时多播由于各种原因而失效,或在一个大型集群多播发现产生大量不必要流量Zen也可使用单播方式。

最小节点数

discovery.zen.minimum_master_nodes = 候选主节点(集群节点数)/ 2 + 1

Zen发现错误检测
  • 第一个流程,由主节点向其他节点发送ping请求检测是否正常
  • 第二个流程,由每个节点向主节点发送请求来验证主节点是否在运行并履行其职责。

Master选举

https://zhuanlan.zhihu.com/p/34858035

master选举谁发起,什么时候发起?

master选举当然是由master-eligible节点发起,当一个master-eligible节点发现满足以下条件时发起选举:

  • 该master-eligible节点的当前状态不是master。
  • 该master-eligible节点通过ZenDiscovery模块的ping操作询问其已知的集群其他节点,没有任何节点连接到master。
  • 包括本节点在内,当前已有超过minimum_master_nodes个节点没有连接到master。

总结一句话,即当一个节点发现包括自己在内的多数派的master-eligible节点认为集群没有master时,就可以发起master选举。

选举过程

假如集群中有3个master-eligible node,分别为Node_A、 Node_B、 Node_C, 选举优先级也分别为Node_A、Node_B、Node_C。三个node都认为当前没有master,于是都各自发起选举,选举结果都为Node_A(因为选举时按照优先级排序,如上文所述)。于是Node_A开始等join(选票),Node_B、Node_C都向Node_A发送join,当Node_A接收到一次join时,加上它自己的一票,就获得了两票了(超过半数),于是Node_A成为Master。此时cluster_state(集群状态)中包含两个节点,当Node_A再收到另一个节点的join时,cluster_state包含全部三个节点。

https://zhuanlan.zhihu.com/p/334348919

在这里插入图片描述

脑裂

脑裂问题可能有以下几个原因造成:

  1. 网络问题:集群间的网络延迟导致一些节点访问不到Master,认为Master 挂掉了,而master其实并没有宕机,而选举出了新的Master,并对Master上的分片和副本标红,分配新的主分片。
  2. 节点负载:主节点的角色既为Master又为Data,访问量较大时可能会导致 ES 停止响应(假死状态)造成大面积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。
  3. 内存回收:主节点的角色既为Master又为Data,当Data节点上的ES进程占用的内存较大,引发JVM的大规模内存回收,造成ES进程失去响应。

如何避免脑裂:我们可以基于上述原因,做出优化措施:

  1. 适当调大响应超时时间,减少误判。通过参数 discovery.zen.ping_timeout 设置节点ping超时时间,默认为 3s,可以适当调大。
  2. 选举触发,我们需要在候选节点的配置文件中设置参数 discovery.zen.munimum_master_nodes 的值。这个参数表示在选举主节点时需要参与选举的候选主节点的节点数,默认值是 1,官方建议取值(master_eligibel_nodes/2)+1,其中 master_eligibel_nodes 为候选主节点的个数。这样做既能防止脑裂现象的发生,也能最大限度地提升集群的高可用性,因为只要不少于 discovery.zen.munimum_master_nodes 个候选节点存活,选举工作就能正常进行。当小于这个值的时候,无法触发选举行为,集群无法使用,不会造成分片混乱的情况。
  3. 角色分离,即是上面我们提到的候选主节点和数据节点进行角色分离,这样可以减轻主节点的负担,防止主节点的假死状态发生,减少对主节点宕机的误判。

Allocation&Rebalance

https://www.jianshu.com/p/a65c9c62db4a
https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-cluster.html

shard allocation发生时机
  1. 创建/删除一个Index
  2. 加入/离开一个Node
  3. 手动执行reroute命令
  4. 修改replication数量
  5. 集群重启
  • 当把allocation关闭的时候,新建的index的shard不会被创建!
  • 手动reroute不受allocation关闭影响。
  • 节点重启时恢复上面的primary shard不受allocation的配置是否开启的影响,但是replica shared不会被重新allocation上来。
  • allocation关闭以后,可以使用reroute指令手动显示进行allocation,不受影响。
allocation 过程
  1. allocator:尝试寻找最优的节点来分配分片
  • 对于新建的索引。allocator按照分片数量升序排序节点列表,注意,是按照分片数量,而不是分片的大小 。然后decider依次遍历节点列表,根据分配规则判断是否要把分片分配到该节点。
  • 对于已有的索引。当节点重启时,触发对于已有分片的分配。对于主分片,allocator只允许把主分片分配到拥有分片完整数据的节点上,即只有完整数据的副分片才能升为主节点;对于副分片,allocator优先分配到拥有分片数据(即使数据不是最新的)的节点上。
  1. decider:负责判断并决定是否要进行要分配
    allocation与rebalance的决策权由下列有控制的decider决定,只有全返回true,才可allocation/rebalance(如果没控制,默认返回true。例如SameShardAllocationDecider不控制rebalance,则返回true)

在这里插入图片描述

rebalance

通过阈值判定,将在不同的node上的shard转移来平衡集群中每台node的shard数。发生在节点增减、修改replica的配置的时候。
关闭allocation以后,rebalance过程也会被禁止。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

数据扩容

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_scale_horizontally.html

数据迁移

在这里插入图片描述

https://cloud.tencent.com/developer/article/1825511?from=article.detail.1529651
https://help.aliyun.com/document_detail/170095.html

数据恢复

nested

nested 的可能的性能问题不容小觑。

nested本质:每个嵌套对象都被索引为一个单独的Lucene文档。如果我们为包含100个用户对象的单个文档建立索引,则将创建101个Lucene文档。

nested 较 父子文档不同之处:

  • 如果子文档频繁更新,建议使用父子文档。
  • 如果子文档不频繁更新,查询频繁建议 nested类型

评分

查询和过滤

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_queries_and_filters.html

从 Elasticsearch 2.0 开始,过滤(filters)已经从技术上被排除了,同时所有的查询(queries)拥有变成不评分查询的能力。

然而,为了明确和简单,我们用 "filter" 这个词表示不评分、只过滤情况下的查询。你可以把 "filter" 、 "filtering query" 和 "non-scoring query" 这几个词视为相同的。

相似的,如果单独地不加任何修饰词地使用 "query" 这个词,我们指的是 "scoring query" 。

过滤查询(Filtering queries)只是简单的检查包含或者排除,这就使得计算起来非常快。考虑到至少有一个过滤查询(filtering query)的结果是 “稀少的”(很少匹配的文档),并且经常使用不评分查询(non-scoring queries),结果会被缓存到内存中以便快速读取,所以有各种各样的手段来优化查询结果。

相反,评分查询(scoring queries)不仅仅要找出匹配的文档,还要计算每个匹配文档的相关性,计算相关性使得它们比不评分查询费力的多。同时,查询结果并不缓存。

相关性

https://www.elastic.co/guide/cn/elasticsearch/guide/current/relevance-intro.html
https://www.elastic.co/guide/cn/elasticsearch/guide/current/controlling-relevance.html
https://www.elastic.co/guide/cn/elasticsearch/guide/current/scoring-theory.html

检索词频率:检索词在该字段出现的频率?出现频率越高,相关性也越高。 字段中出现过 5 次要比只出现过 1 次的相关性高。
反向文档频率:每个检索词在索引中出现的频率?频率越高,相关性越低。检索词出现在多数文档中会比出现在少数文档中的权重更低。
字段长度准则:字段的长度是多少?长度越长,相关性越低。 检索词出现在一个短的 title 要比同样的词出现在一个长的 content 字段权重更大

https://en.wikipedia.org/wiki/Okapi_BM25
https://en.wikipedia.org/wiki/Tf–idf
https://www.elastic.co/guide/en/elasticsearch/reference/current/similarity.html
https://www.jianshu.com/p/70d1c3045c11

调优

硬件环境选择

如果有条件,尽可能使用SSD硬盘, 不错的CPU。ES的厉害之处在于ES本身的分布式架构以及lucene的特性。IO的提升,会极大改进ES的速度和性能。

初期规划

初期规划,设置合适的分片数,集群节点数。

使用alias:生产提供服务的索引,切记使用别名提供服务,而不是直接暴露索引名称,避免后续因为业务变更或者索引数据需要reindex等情况造成业务中断。

冷热数据隔离(如日志数据)

避免宽表
在索引中定义太多字段是一种可能导致映射爆炸的情况,这可能导致内存不足错误和难以恢复的情况,这个问题可能比预期更常见,index.mapping.total_fields.limit ,默认值是1000

避免稀疏索引
因为索引稀疏之后,对应的相邻文档id的delta值会很大,lucene基于文档id做delta编码压缩导致压缩率降低,从而导致索引文件增大,同时,es的keyword,数组类型采用doc_values结构,每个文档都会占用一定的空间,即使字段是空值,所以稀疏索引会造成磁盘size增大,导致查询和写入效率降低。

作者:架构文摘
链接:https://juejin.cn/post/6868081322779230216
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

系统层面的调优

系统层面的调优主要是内存的设定与避免交换内存。

内存调优
  1. 垃圾回收器选择
  2. 新生代老年代比例设置,有无内存泄漏
参数调优
  • 配置项cluster.routing.allocation.node_concurrent_recoveries 决定了单个节点执行副分片recovery时最大的并发数,默认为2,可以适当提高。
  • 配置项indices.recovery.max_bytes_per_sec决定节点间复制数据时的限速,默认为40MB,可以适当提高。
  • 配置项cluster.routing.allocation.node_initial_primaries_recoveries决定了单个节点执行主分片recovery时的最大并发数,默认为4。可以适当提高。
  • 在重启集群前,停止写入并执行sync flush,恢复时有机会跳过phase1。
    适当多保留些translog,配置项index.translog.retention.size默认最大保留512MB,index.translog.retention.age默认12小时,调整这两个参数可以有机会跳过phase1
  • 合并Lucene分段,对于冷索引执行_forcemerge,较少的分段可以提升恢复效率。
写入速度优化

综合来说,可以考虑以下几个方面来提升写索引的性能:

  • 加大 Translog Flush ,目的是降低 Iops、Writeblock。
  • 增加 Index Refresh 间隔,目的是减少 Segment Merge 的次数。
  • 调整 Bulk 线程池和队列。
  • 优化节点间的任务分布。
  • 优化 Lucene 层的索引建立,目的是降低 CPU 及 IO。

https://cloud.tencent.com/developer/article/1436787

  1. translog flush间隔调整
    在默认设置下,translog的持久化策略为每个请求都flush,这个是影响es写入速度的最大因素,如果可以接受一定概率的数据丢失,可以将刷盘设置为周期性或者到一定大小再刷盘。

  2. 索引刷新间隔refresh_interval
    默认索引的refresh_interval为1秒,这意味着数据写入1秒后就可以搜索到,每次索引的refresh会产生一个新的Lucene段,这会导致频繁的segment merge行为,如果不需要这么高的搜索实时性,应该降低索引refresh周期。

  3. 使用bulk请求
    批量写比一个索引请求只写单个文档的效率高得多。

  4. 自动生成doc ID
    写入文档时如果外部指定了id,则es会先尝试读取原来的doc版本号,以判断是否要更新,这会涉及一次读磁盘的操作,通过自动生成doc id可以避免。

  5. 减少副本的数量
    ES 为了保证集群的可用性,提供了 Replicas(副本)支持,然而每个副本也会执行分析、索引及可能的合并过程,所以 Replicas 的数量会严重影响写索引的效率。

当写索引时,需要把写入的数据都同步到副本节点,副本节点越多,写索引的效率就越慢。

如果我们需要大批量进行写入操作,可以先禁止 Replica 复制,设置 index.number_of_replicas: 0 关闭副本。在写入完成后,Replica 修改回正常的状态。

  1. 合理使用合并
    Lucene 以段的形式存储数据。当有新的数据写入索引时,Lucene 就会自动创建一个新的段。

随着数据量的变化,段的数量会越来越多,消耗的多文件句柄数及 CPU 就越多,查询效率就会下降。

由于 Lucene 段合并的计算量庞大,会消耗大量的 I/O,所以 ES 默认采用较保守的策略,让后台定期进行段合并,如下所述:

索引写入效率下降:当段合并的速度落后于索引写入的速度时,ES 会把索引的线程数量减少到 1。
这样可以避免出现堆积的段数量爆发,同时在日志中打印出“now throttling indexing”INFO 级别的“警告”信息。
提升段合并速度:ES 默认对段合并的速度是 20m/s,如果使用了 SSD,我们可以通过以下的命令将这个合并的速度增加到 100m/s。

PUT /_cluster/settings
{
    "persistent" : {
        "indices.store.throttle.max_bytes_per_sec" : "100mb"
    }
}
读取速度优化
  1. 不需要评分时,使用过滤查询,减少评分过程

  2. 文档模型
    文档应该合理建模,特别是应该避免join操作,嵌套(nested)会使查询慢几倍,父子关系可能使查询慢数百倍。

  3. 优化日期搜索
    在使用日期范围检索时,使用now的查询通常不能缓存,因为匹配到的范围一直在变化,我们可以将日期四舍五入到分钟,甚至可以将时间分为一个大的可缓存部分和一个小的不可缓存部分。

  4. 避免大结果集和深翻

  5. 定期删除
    由于在 Lucene 中段具有不变性,每次进行删除操作后不会立即从硬盘中进行实际的删除,而是产生一个 .del 文件记录删除动作。

随着删除操作的增长,.del 文件会越来也多。当我们进行查询操作的时候,被删除的数据还会参与检索中,然后根据 .del 文件进行过滤。.del 文件越多,查询过滤过程越长,进而影响查询的效率。

当机器空闲时,我们可以通过如下命令删除文件,来提升查询的效率

https://cloud.tencent.com/developer/article/1436787
https://cloud.tencent.com/developer/article/1436787
https://www.jianshu.com/p/472963f7a3f4
https://www.jianshu.com/p/883325b7bbda?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
https://zhuanlan.zhihu.com/p/43437056

posted @ 2021-08-23 18:09  曹自标  阅读(811)  评论(0编辑  收藏  举报