前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >这份​Elasticsearch 工作笔记,值得收藏

这份​Elasticsearch 工作笔记,值得收藏

原创
作者头像
bellen
修改2022-03-14 21:56:11
1.5K0
修改2022-03-14 21:56:11
举报

从事Elasticsearch云产品的研发已经四年多了,在服务公有云客户的过程中也遇到了各种各样的使用方式以及问题,本文就把过去几年记录的一些问题和解决办法进行归类和总结,常读常新。

目录:

  • 一、es内核bug类的问题记录
  • 二、使用方式类的问题记录
  • 三、优化类的问题记录
  • 四、原理咨询类的问题记录

一、es内核bug类的问题记录

1 . 集群升级到7.5版本后自定义的normalizer无法使用了

es内核的bug,7.0版本对自定义analyzer这部分的代码进行了重构,导致所有的自定义normalizer都无法正常使用。

相关issue: https://github.com/elastic/elasticsearch/issues/48650

相关PR:https://github.com/elastic/elasticsearch/pull/48866

2 . 使用_search/template API查询时返回结果总量不准

在_search/template API的处理逻辑中,虽然rest_total_hits_as_int设置为了true, trackTotalHitsUpTo值却没有被设置,因此只能获取到最多为10000的total hits。

相关issue: https://github.com/elastic/elasticsearch/issues/52801

相关PR:https://github.com/elastic/elasticsearch/pull/53155

3 . 处理字符串类型数据的ingest processor, 不支持传入的field字段值为数组

对Lowercase Processors、Uppercase Processors、Trim Processors等处理字符串类型数据的ingest processor, 都支持要处理的字段类型为数组类型:

相关issue: https://github.com/elastic/elasticsearch/issues/51087

相关PR:https://github.com/elastic/elasticsearch/pull/53343

4 . reindex api在max_docs参数小于slices时,会报错max_docs为0

调用reindex api,当max_docs参数<slices时,会报错max_docs为0,实际上是因为没有提前校验max_docs是否<slices,导致max_docs被设置为0。

相关issue: https://github.com/elastic/elasticsearch/issues/52786

相关PR:https://github.com/elastic/elasticsearch/pull/54901

5 . ingest pipeline simulate API 在传入的docs参数是空列表时,没有响应

在调用_ingest/pipeline/_simulate API时,如果传入的docs参数是空列表,则什么结果都不会返回。

Bug产生的原因是,在异步请求的ActionListener中没有对docs参数进行判空,导致始终没有响应给客户端。

相关issue: https://github.com/elastic/elasticsearch/issues/52833

相关PR:https://github.com/elastic/elasticsearch/pull/52937

6 . Rename inegst processor对于设置ignore_missing参数无效

当使用template snippets时,如果取到的字段不存在,此时如果设置了ignore_missing, 仍然会报错。

相关issue: https://github.com/elastic/elasticsearch/issues/74241

相关PR:https://github.com/elastic/elasticsearch/pull/74248

7 . ILM中的Shrink Action,如果设置的目标分片数不合适,也就是不是原索引分片数的因子时,Shrink Action会卡住

在Shrink Action中增加校验,如果设置的目标分片数不合适,就提前中断ILM的执行。

相关issue: https://github.com/elastic/elasticsearch/issues/72724

相关PR:https://github.com/elastic/elasticsearch/pull/74219

8 . Get Snapshot API如果指定了ignore_unavailable为true时,会把当前正在执行的所有快照都返回

相关issue: https://github.com/elastic/elasticsearch/issues/68090

相关PR:https://github.com/elastic/elasticsearch/pull/68091

9 . 在ILM中使用的AllocationDeciders,会忽略掉用户自定义的cluster level的routing allocation 配置,导致在某些场景下需要移动分片时把分片移动了到了错误的节点上。

相关issue: https://github.com/elastic/elasticsearch/issues/64529

相关PR:https://github.com/elastic/elasticsearch/pull/65037

10 . 在执行bulk写入时,如果body里指定了pipeline, 执行结果是错误的

在bulk写入时,如果有的请求带有ingest pipeline, 有的没有,那么执行结果就是完全乱序的,也就是文档内容和指定的docId对应不上。

相关issue: https://github.com/elastic/elasticsearch/issues/60437

相关PR:https://github.com/elastic/elasticsearch/pull/60818

二、使用方式类的问题记录

11 . 二进制字段如何设置mapping?

代码语言:txt
复制
	"mapping": {
	  "type": "binary",
	  "doc_values": "false",
	  "norms": "false",
	  "fielddata": "false",
	  "store": "false"
	}

12 . 对ip字段进行聚合,希望聚合结果返回每个ip的一条数据,该怎么实现?

先使用terms聚合,再使用top_hits子聚合能达到目的,使用 collapse 配合 inner_hits也可以实现

13 . 有一张消费明细表(一个人有多条消费记录),首先想计算出一个人的总消费金额,然后想得到总消费大于500美金的所有人数,query DSL该怎么写?

代码语言:txt
复制
	{
    "aggs":{
        "one":{
            "terms":{
                "field":"mobile_nbr"
            },
            "aggs":{
                "x":{
                    "sum":{
                        "field":"trans_amt"
                    }
                },
                "sum_bucket_filter":{
                    "bucket_selector":{
                        "buckets_path":{
                            "totalSum":"x"
                        },
                        "script":"params.totalSum > 500"
                    }
                }
            }
        },
        "stats_buckets":{
            "stats_bucket":{
                "buckets_path":"one.x"
            }
        }
    }
}

14 . 使用Logstash迁移ES的数据,简单的配置文件

代码语言:txt
复制
	input {
	    elasticsearch {
	        hosts => ["http://x.x.x.x:9200"]
	        index => "*"
	        docinfo => true
	    }
	}
	output {
	    elasticsearch {
	        hosts => ["http://x.x.x.x:9200"]
	        index => "%{[@metadata][_index]}"
	    }
	}

15 . 一个有用的脚本,用于追加netsted objects

代码语言:txt
复制
	{
	  "script": {
	    "lang": "painless",
	    "inline": " if (ctx._source.redu!=null) {ctx._source.redu.add(params.object);} else {Object[] temp= new Object[]{params.object};ctx._source.redu= temp;}",
	    "params": {
	      "object": {
	        "visit_time": "2020-03-15 22:00:00",
	        "visit_cnt": 1000,
	        "visit_scene": 2
	      }
	    }
	  }
	}

16 . mustache小胡子脚本,用于把一个数组类型的字段复制到另外一个字段,高版本7.x可以使用set processor的copy_from, 低版本不支持copy_from

代码语言:txt
复制
	{
  "pipeline": {
    "processors": [
      {
        "set": {
          "field": "a",
          "value": "{{#b}}{{.}},{{/b}}"
        }
      },
      {
        "split": {
          "field": "a",
          "separator": ","
        }
      },
      {
        "convert": {
          "field": "a",
          "type": "integer"
        }
      }
    ]
  },
  "docs": [
    {
      "_source": {
        "b": [
          1,
          2
        ]
      }
    }
  ]
}

17 . 查询时对结果进行排序,如果文档的分值相同,需要返回顺序是随机的,可以通过script来进行处理

代码语言:txt
复制
	{
      "_script": {
        "script": "Math.random()",
        "type": "number",
        "order": "asc"
      }
    }

18 . 取消reindex任务

代码语言:txt
复制
	列出运行中的任务
	_tasks
	_tasks?nodes=nodeId1,nodeId2
	取消任务
	_tasks/node_id:task_id/_cancel
	取消重建索引任务
	_tasks/_cancel?nodes=nodeId1,nodeId2&action=*reindex

19 . 查看阻塞在队列中的索引

代码语言:txt
复制
	GET  _tasks?pretty\&detailed  | grep description | awk -F 'index' '{print $2}' | sort | uniq -c | sort -n

20 . cancel掉所有存量的查询,释放内存

代码语言:txt
复制
	POST _tasks/_cancel?actions='indices:data/read/search*'

三、优化类的问题记录

21 . 在需要批量拉取聚合结果时,可以使用index sorting + composite 聚合来代替term 聚合,composite聚合可以根据排序优化聚合提前结束并且支持分页。

22 . 系统高阶内存不足导致的节点离线

线上某个写入量比较大的集群,不定时的会出现某个节点离线又加回集群的情况。经过定位发现是虚拟机高阶内存不足,导致网卡收发包异常。

es和Lucene 会大量使用堆外内存,在应用层面的内存分配都是申请的低阶内存(0阶、1阶),会将高阶内存(3阶及以上)逐步拆分用掉。而系统内核层面的网卡驱动会优先分配高阶内存,如果高阶内存不足会再尝试分配低阶内存,这个过程会有一定延时,可能导致节点短暂收发包异常,短暂脱离集群。当应用层面将高阶内存拆分申请完毕后,就会出现这一高阶内存不足的现象。系统会有内存整理的过程但是不会那么及时。

解决办法是通过设定系统参数预留系统内存:

代码语言:txt
复制
	echo %2的系统总内存(单位kb) > /proc/sys/vm/min_free_kbytes
	
	echo 1 > /proc/sys/vm/compact_memory

23 . es节点上TCP全连接队列参数设置,防止节点数大于100的这种的集群中,节点异常重启时全连接队列在启动瞬间打满,造成节点hung住,导致集群响应迟滞:

代码语言:txt
复制
	echo "net.ipv4.tcp_abort_on_overflow = 1" >>/etc/sysctl.conf
	echo "net.core.somaxconn = 2048" >>/etc/sysctl.conf

24 . 查询时需要返回文档原文中的几个字段,从行存改为从列存读取,高压力查询场景性能可以提升 50%。从行存读取涉及到解压的开销,列存则可直接取对应字段的部分block,性能会更高:

查询body 中的取source 部分:

代码语言:txt
复制
	"_source": {
	    "includes": [
	      "a",
	      "b",
	      "c"
	    ]
	  }	

调整为从列存读取字段:

代码语言:txt
复制
	  "docvalue_fields":  [
	      "a",
	      "b",
	      "c"
	    ],
	  "stored_fields": "_none_", // 关闭行存读取

25 . 部署es时磁盘挂载时的可选配置

代码语言:txt
复制
* noatime:禁止记录访问时间戳,提高文件系统读写性能
* data=writeback: 不记录data journal,提高文件系统写入性能
* barrier=0:barrier保证journal先于data刷到磁盘,上面关闭了journal,这里的barrier也就没必要开启了
* nobh:关闭buffer_head,防止内核打断大块数据的IO操作

四、原理咨询类的问题记录

26 . 为了满足查询时延,是不是索引的分片数设置的越少越好?

如果单次搜索的时延可以满足业务上的要求,可以设置索引为1分片多副本。 如果时延过高,可以增加shard数量,代价是每次搜索的并发两增大,带来的额外开销更大,因而集群能支撑的峰值QPS可能会降低。 原则上,在满足搜索时延的前提下,划分尽量少的分片数。

另外有一种场景划分更多的分片数是合理的,那就是集群大多数搜索都会用到某个字段做过滤,比如城市id。 这个时候,可以用该字段做为routing_key,将相关联的数据route到某个或某几个(如果用到routing partition)shard。 适当多划分一些分片,可以让单个分片上的数据集较小,搜素速度快,同时因为搜索不会hit所有的分片,规避了划分过多的分片带来的并发过高,以及需要汇总的数据过多引起的性能问题。

27 . filter缓存的策略是怎么样的?

es会基于使用频次自动缓存查询。如果一个非评分查询在最近的 256 次查询中被使用过(次数取决于查询类型),那么这个查询就会作为缓存的候选。但是,并不是所有的segment都能保证缓存 bitset 。只有那些文档数量超过 10000 (或超过总文档数量的 3% )的segment才会缓存 bitset 。因为小的片段可以很快的进行搜索和合并。一旦缓存了,非评分计算的 bitset 会一直驻留在缓存中直到它被剔除。剔除规则是基于 LRU 的,一旦缓存满了,最近最少使用的过滤器会被剔除。

28 . bool查询是如何计算得到文档的分值的?

以should子句为例,先运行should子句中的两个查询,然后把子句查询返回的分值相加,相加得到的分值乘以匹配的查询子句的数量,再除以总的查询子句的数量得到最终的分值。

29 . 查询时的tie_breaker参数的作用是什么?

tie_breaker参数会让dis_max查询的行为更像是dis_max和bool的一种折中。它会通过下面的方式改变分值计算过程:

代码语言:txt
复制
* 取得最佳匹配查询子句的score
* 将其它每个匹配的子句的分值乘以tie\_breaker
* 将以上得到的分值进行累加并规范化通过tie_breaker参数,所有匹配的子句都会起作用,只不过最佳匹配子句的作用更大。
	

30 . min_score参数设置后,多次查询结果可能会不一致,因为查询primary shard和replica shard的结果可能不一致。

31 . 在plainless脚本中使用doc'field'取值和使用'_source'取值有什么不同?

使用doc'field'取值会把字段field的所有term都加载到内存(并且会被缓存),执行效率高,但是比较消耗内存,另外这种取值方式只能去简单类型的字段,不能对Object类型的字段取值;使用_source取值因为不会有缓存,所以每次都要把_source内容加载到内存并且解析,因此效率很低。推荐使doc'field'取值。

32 . scroll api里的scroll参数的作用是保持search context, 但是只需要设置为处理一个批次所需的时间即可。scroll时会在merge操作时依然保留merge前的old segments, 会带来存储上的开销以及需要更多文件描述符;search.max_open_scroll_context参数可以设置node上最大的context数量,默认无限制。scroll请求不会用到cache,因为使用cache在查询请求执行过程中会修改search context,会破坏掉scroll的context。

33 . es 5.6以后在search api中加入了pre filter shards 逻辑,当要查询的shards数量超过128并且查询可能会被重写为MatchNoneQuery时,会进行pre filter, 过滤掉shards,提高查询效率。在search时返回结果中的_shards.skipped表示了过滤掉了多少shard。

34 . es默认使用的用于打分的bm2.5相似度算法中,计算idf的部分,log(docCount+1/docFreq+0.5), docCount的值是所有包含要查询的field的文档数量;docFreq是所有包含field value的文档数量。

35 . es统计的索引大小是整个索引所占空间空间的大小,整个索引包括很多文件,比如tim词典,tip词典索引,pos位置信息,fdt存储字段信息(_source实际存储的文件),等等。es中"codec": "best_compression" ,是对fdt这个文件进行压缩,其他的是不会进行压缩的。

36 . 横向增加节点扩容时,不能搬迁已经close的索引到新的节点上, 需要先手动处理这种索引才可以。

37 . fielddata是在堆内存的,docvalues是在堆外内存的;docvalues默认对所有not_analyzed字段开启(index时生成),如果要对analyzed字段进行聚合,就要使用fielddata了(使用时把所有的数据全都加载进内存)。如果不需要对analyzed字段进行聚合,就可以降低堆内存的使用。

38 . ES 写入异常流程总结:

  • 如果请求在协调节点的路由阶段失败,则会等待集群状态更新,拿到更新后,进行重试,如果再次失败,则仍旧等集群状态更新,直至1分钟超时为止,超时后则进行整体请求失败处理
  • 在主分片写入过程中,写入是阻塞的;只有写入成功,才会发起写副本请求;如果主分片写失败,则整个请求被认为处理失败;如果有部分副本分片写失败,则整个请求被认为是处理成功的,会在结果中返回多少个分片成功,多少个分片失败;
  • 无论主分片还是副本分片,当写一个doc失败时,集群不会重试,而是关闭本地shard,然后向master汇报。

39 . ES写入流程存在的一些问题:

  • 副本分片写入过程重新写入数据,不能单纯复制数据,浪费计算能力,影响写入速度
  • 磁盘管理能力较差,对坏盘检查和容忍性比HDFS差不少;在配置多次盘路径的情况下,有一块坏盘就无法启动节点。

40 . ES在分布式上表现的一些特性:

  • 数据可靠性:通过分片副本和事务日志保障数据安全
  • 服务可用性:在可用性和一致性的取舍方面,默认ES更倾向于可用性,只要主分片可用即可执行写入操作
  • 一致性:弱一致性?最终一致性?只要主分片写入成功,数据就能被读取
  • 原子性:数据读写是原子操作,不会出现中间状态,但是bulk不是原子操作,不能用来实现事务
  • 扩展性:主分片和副本分片都可以承担读请求,分担系统负载

41. 腾讯云Elasticsearch有自研的熔断器,默认情况下当jvm old 区使用率超过85% ,拒绝写入;当jvm old 区使用率超过90% ,拒绝查询;日志报错有"pressure too high"字样; 详细介绍见https://cloud.tencent.com/document/product/845/56272

42 . term聚合,在High Cardinality下,性能越来越差的原因是什么?

字段唯一值非常多,对该字段进行terms聚合时需要构建Global Ordinals(内部实现),对旧的索引只需构建一次也就是首次查询时构建一次,后续查询就可以直接使用缓存中的Global Ordinals; 而对持续写入的索引,每当底层segment发生变化时(有新数据写入导致产生新的Segment、Segment Merge), 就需要重新构建Global Ordinals,随着数据量的增大,字段的唯一值越来越多,构建Global Ordinals越来越慢,所以对持续写入的索引,聚合查询会越来越慢。

terms聚合查询使用的Global Ordinals是shard级别的,把字符串转为整型,目的是为了在聚合时降低内存的使用;最后再reduce阶段,也就是收集各个shard的聚合结果的时候并且汇总完毕后,再把整型转换为原始字符串。

当底层的Segment发生变化时,Global Ordinals就失效了,再次查询时就需要重新构建;默认的构建时机是search查询时,在6.x版本引入了eager_global_ordinals,把构建全局序数放在了index索引时,但是对index性能有一定影响。

43 . es使用了CMS垃圾回收器,默认情况下JVM堆内存young区和old区的大小是多少?

JVM堆内存参数中用于控制young区和old区大小的参数如下:

代码语言:txt
复制
	* 最高优先级:  -XX:NewSize=1024m和-XX:MaxNewSize=1024m 
	* 次高优先级:  -Xmn1024m  (默认等效效果是:-XX:NewSize==-XX:MaxNewSize==1024m) 
	* 最低优先级:-XX:NewRatio=2 es用的是CMS垃圾回收器,所以young区的大小是JVM根据系统的配置计算得到的,newRatio默认虽然为2但是不起作用;除非显式的配置-Xmn或者-XX:NewSize, young区大小的计算公式为:const size_t preferred_max_new_size_unaligned =
    MIN2(max_heap/(NewRatio+1), ScaleForWordSize(young_gen_per_worker * parallel_gc_threads));

其中ScaleForWordSize大约为64M (64位机器) (机器cpu核数) 13 / 10

44 . update操作不一定会触发refresh, 如果update的doc_id已经是可以被searcher检索到的,比如已经存在于某个segment里,就无需refresh。 但是如果update的doc_id存在于index writter buffer里,还未refresh,典型的就是同一个bulk操作里写入了多个重复的id, 实时GET就会触发refresh。

45 . 一般1 TB的磁盘数据,需要 2- 5GB 左右的 FST内存开销,这个只是FST的开销(常驻内存),一般FST占用50%左右的堆内内存。如果查询和写入压力稍微大一点,32GB Heap,内存很容易成为瓶颈。

46 . zen2相比zen的优势?

  • mininum_master_nodes被移除,es自己决定哪些节点作为candidate master nodes;而6.x版本的zen协议,可能会因为该参数配置错误导致集群无法选主,另外在扩缩容节点时也需要调整该参数
  • 典型的主节点选举可以在1s内完成,相比6.x, es通过延迟几秒钟的时间再进行选举防止各种各样的配置错误,意味着有几秒钟的时间集群不可用
  • 增长和缩小集群变得更安全,更容易,并且错误配置导致数据丢失的机会变少了。
  • 点增加更多的记录状态的日志,帮助诊断无法加入集群或无法选举出主节点的原因

47 . 什么是adaptive replica selection?

默认情况下,ES的协调节点选择在处理查询请求时,对于有多个副本的某个分片,选择哪个分片进行查询,依据的准则1是shard allocation awareness, 也就是和协调节点在同一个位置(location)的节点,比如协调节点在可用区1,那么如果可用区1有要查询的副本分片,则会优先选择可用区1的节点进行查询;依据的准则2是:

(1) 协调节点和候选节点之前查询的响应时间,响应时间越短,优先选择

(2) 之前的查询中,候选节点执行查询任务的处理时间(took time),处理时间越短,优先选择

(3) 候选节点的查询队列,队列中查询任务越少,优先选择

adaptive replica selection旨在降低查询延迟,可以通过动态修改cluster.routing.use_adaptive_replica_selection配置为false关闭该特性,关闭之后,查询时将会采用round robin策略,可能会使得查询延迟增加。

48 . es为什么不适用一致性hash做数据的sharding?

es使用简单的hash方式:hash(routing) % number_of_shards,实现起来较为简单,效率也更高。当需要扩展分片数量的时候,可以通过创建新索引+别名的方式解决。

为什么不用一致性hash?对es来说,底层的存储是lucene 的segment, 它的不可变特性造成了仅仅移动5%左右的文档到新的分片,代价就会很大(因为使用一致性hash需要考虑rehash,所以需要移动文档到新的分片)。所以通过创建新的分片数量更大的索引进行读写,实现要简单的多,不必考虑移动文档造成的系统资源开销。

49 . ES在CAP理论上的实践:

  • C是一致性:最终一致性,主分片写完后再写副本分片,可能存在主分片写完之后可读,副本分片还没有refresh读不到数据
  • A是可用性:通过副本和translog保证数据可靠性
  • P是分区容错性:master和data节点间互相ping,进行故障节点检测与恢复 在CAP三个特性上如何做折中?写数据时主分片写完之后,写副本分片时不要求所有的副本分片都能成功,在一致性与可用性上倾向于可用性。

50 . es的读写模型相比原生的PacificA算法的区别

  • Prepare阶段:PacificA中有Prepare阶段,保证数据在所有节点Prepare成功后才能Commit,保证Commit的数据不丢,ES中没有这个阶段,数据会直接写入。
  • 读一致性:ES中所有InSync的Replica都可读,提高了读能力,但是可能读到旧数据。另一方面是即使只能读Primary,ES也需要Lease机制等避免读到Old Primary。因为ES本身是近实时系统,所以读一致性要求可能并不严格。

总结来讲,就是原生的PacificA算法具有两阶段提交的过程,ES并没有,数据会直接写入副本分片;ES中副本分片也是可以承担读请求的,因为ES本身设计之初是为了满足搜索场景,也是一个近实时系统,所以在读一致性上可能要求并不严格。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、es内核bug类的问题记录
  • 二、使用方式类的问题记录
  • 三、优化类的问题记录
  • 四、原理咨询类的问题记录
相关产品与服务
Elasticsearch Service
腾讯云 Elasticsearch Service(ES)是云端全托管海量数据检索分析服务,拥有高性能自研内核,集成X-Pack。ES 支持通过自治索引、存算分离、集群巡检等特性轻松管理集群,也支持免运维、自动弹性、按需使用的 Serverless 模式。使用 ES 您可以高效构建信息检索、日志分析、运维监控等服务,它独特的向量检索还可助您构建基于语义、图像的AI深度应用。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档