elastic search(一)概述

一、分段存储

elastic search以下统称es底层使用Lucene,lucene使用基于倒排索引和分段(segment)存储的方式完成数据索引。
将一个索引文件分成了许多子文件,每个子文件就叫做段(索引中最小存储单元),段具有不变性,一旦索引数据被写入到硬盘就不可再修改
1.1数据操作过程:

  • 新增:新增索引数据时,由于段的不变性,所以会新建一个段存储新数据。
  • 删除:删除索引数据时,由于段的不变性,lucene在索引文件下新增了一个.del文件,存储被删除的数据id,查询时会根据del文件过滤掉标记为删除的数据,只有在进行段合并时删除的数据才会被真正的移除,**段的合并策略:**段的数量增多时会影响效率,所以隔一段时间lucene会把一些小的段合并成大段,一些本身比较大的段不会再进行合并
    类似于hbase的compact机制。
  • 更新:结合新增和删除,del记录旧数据,新建一个段存储新数据。

1.2段不变性的优点:
不需要锁,不需要考虑多线程读写不一致的情况
由于不变性,可以常驻内存,缓存友好
增量创建成本低,所以可以频繁更新数据使系统接近实时更新
1.3段不变性缺点:
删除和更新数据会有空间浪费,过滤删除数据增加了查询负担
1.4写数据流程:
为了提升写性能,lucene并没有每新增一条数据就创建一个新的段,而是采用延时写策略,数据写入时先写入到内存,达到一定量或者一定时间(默认一秒)批量写到文件(其实是文件系统缓存),单纯使用这种方式虽然可以提高写性能,但是如果在数据还没持久化的时候服务器断电缓存中的数据会丢失,为此es添加了事务日志,transLog,类似于hbase的WAL(write ahead log)和mysql的binLog,es采用多级缓存,数据先写入到transLog和jvm内存,然后达到一定量jvm内存数据写到本机文件系统缓存,文件系统缓存达到512M,持久化到磁盘,此时清空transLog。因为持久化是一个费时的操作所以采用这种方式提高性能。

二、es简介

2.1核心概念

  • Cluster:集群,由一个或多个es节点组成
  • Node:节点,组成es集群的服务单元
  • Shard:分片,一个索引数据量太大时,通常会把索引上的数据水平拆分,拆分出来的每个数据块叫做分片,每个分片必须由一个主分片和零到多个副本分片。
  • Replicas:备份,主分片的本分分片。
  • Index:索引,相当于数据库
  • Type:类别,指索引内部的逻辑分区,通过type的名字在索引内进行唯一标识。在查询时如果没有指定type,则在整个索引中查询。
  • Document:文档,相当于一条数据
  • Settings:对集群中索引的定义,比如一个索引默认的分片数,副本数等。
  • Mapping:类似于关系型数据库的表结构信息,用于定义索引中字段的类型、分词方式、是否存储等信息。es的mapping是可以动态识别的。非常不建议使用ES的动态识别和创建的机制,因为很多情况下这并非你所需要。推荐的做法是在写数据之前仔细的创建mapping 一个索引的mapping一旦创建,若已经存储了数据就不可以修改了。

2.2节点

  • 主节点:负责创建删除索引、分配分片、追踪集群中的节点状态等。用户请求可以发往任意一个节点并不需要主节点转发,主节点负载一般比较低,对磁盘要求也不高。
  • 数据节点:负责存储数据一级响应客户端的查询等操作

三、ES的选主机制

es不依赖zookeeper,采用单播的方式通信
主要根据以下三个方面来进行ES的选举:

  • 对有资格成为Master的节点进行NodeId排序,每一次选举都将自己识别的节点进行排序,然后选择第一位的节点,暂且认为它是主节点(注意:暂定)

  • 如果某一个几点的投票数达到了 N/2+1,并且此节点自己也投给了自己一票,那么就选举这个节点为主节点。否则,重新选举。

  • 对于brain split问题,需要把候选master节点最小值设置为可以成为master节点数n/2+1(quorum )
    脑裂
    如果一个节点没有收到主节点的状态就会发起重新选举,之前的主节点其实是好的这时候就会选出两个master产生脑裂,就是一个集群分裂成两个集群,出现了两个Master。这里如果要避免脑裂要配置discovery.zen.minimum_master_nodes=N/2+1。这个配置的意思是说在选举Master的过程中,需要多少个节点通信,说白点就是票数。如果达不到N/2+1,就是超过半数,就会选举失败,重新选举。

四、常见问题

4.1 es不可靠
ES不是mysql数据库,它有丢数据的风险。所以业务要自己做好容错处理

4.2 es不实时
由1.4内容可知,写入的数据只是先写入到缓存会定期刷到磁盘,调用refresh可以立即刷新但是会影响性能慎用,es本身就不是用作实时查询的,只能算作近实时

4.5 bulk批量写入有时会丢一部分数据
如果对数据可靠性要求高的话,批量写入后需要遍历bulkrespose的每一个结果,判断每一条数据的写入结果

4.6. es一般可以存多少数据
专门做优化后PB级别的数据比如百度,一般情况TB级别数据没压力

es的index分成若干shard(分片),每个shard最大能存20亿条数据,这是硬限制
IllegalArgumentException: number of documents in the index cannot exceed 2147483519
超过上头的限制报的异常.一个分片最多能存 2147483519 个文档
4.8 如何确定分片数

  • ES是静态分片,一旦分片数在创建索引时确定那么后继不能修改
  • 数据量在亿级别,8或者16分片就行当然也可以自己指定,分片数最好是2的n次方。
  • 如果后继数据量的增长超过创建索引的预期,那么需要创建新索引并重灌数据
  • 创建mapping时请自行指定分片数,否则创建的索引的分片数是ES的默认值。
  • 副本数:一般设置为1,副本太多可以提高可靠性,同时也会影响性能,看自己业务需求。

4.9 es client版本
使用对应的版本访问ES server,包括api参数等,请查看自己服务器对应的版本号的文档。

4.10 Date数据如何存,为什么差8小时
时区问题,写入date类型的数据到ES:.field(“date”, new Date(), ISODateTimeFormat.dateTime().withZone(DateTimeZone.forID(“Asia/Shanghai”)))

4.11 写性能优化

  • 当有大量写任务时,比如数据导入,使用批量写
  • 条件允许的话使用固态硬盘、es服务器可以挂在多块磁盘,通过es配置文件指定多个数据地址可以同时使用多磁盘、不要使用远程存储设备
  • 减少refresh次数,默认1s会把jvm缓存刷到文件系统缓存,可以通过配置适当延长此时间,缺点是牺牲了部分实时性
  • 减少flush次数,translog数据默认达到512M或者半小时会触发一次flush,把transLog对应的文件系统缓存刷到磁盘并清空transLog,可以调大改参数。
  • https://www.elastic.co/blog/performance-considerations-elasticsearch-indexing
    4.12 读性能优化
  • 避免大结果集和深翻页,这里顺便说一下scroll和scrool scan的区别,scroll是第一次在每个分片查询1w条数据,汇总到协调节点(即处理请求的节点)并放入缓存,以后都从缓存拿数据,数据的更新也不会影响缓存数据。scroll-scan和scroll原理差不多,只不过去掉了数据相似度计算、聚合、排序等耗时操作,默认按照数据在索引中的顺序返回
  • 使用routing来查询,默认路由是数据的默认唯一标识“-id”,自己指定主键时要防止数据分布不均的情况。
  • 定期整理删除.del文件,查询时最后会过滤del文件的数据,合并有数据删除的段
  • 设置合理的堆大小,默认是1G,但是es是一个很费费存的服务,一般不超过物理内存的50%,一般不超过32G(指针压缩问题)
  • 服务器关闭swap虚拟内存
  • https://www.elastic.co/blog/found-optimizing-elasticsearch-searches

4.15 search_type导致的错误查询用例
search type不要用DFS_QUERY_THEN_FETCH,用QUERY_THEN_FETCH
QUERY_THEN_FETCH:分两步,查询所有分片返回文档id和相似度分,然后通过_id获取最终返回的数据
QUERY_AND_FETCH:和前者区别,返回的不是文档id而是数具体数据
DFS_QUERY_THEN_FETCH:和第一个的区别增加了一步全局词频统计获取更精确的打分。
DFS_QUERY_AND_FETCH:和第二个区别增加了一步全局词频统计获取更精确的打分。

尽可能的用filter
不要用explain:true,这个是打印debug相关的信息的
不要用fields, 在client端去解码

4.16 ES java client
ES java client是线程安全的,全局构建一个即可满足读写需求,不要每次都创建ES client否则会出现java.lang.OutOfMemoryError

4.17 NoNodeAvailableException
查看链接的ip地址和端口是否正确。经常有人把TCP默认9300的端口错配为HTTP的端口9200
查看cluset name是否正确(cluster name错了也抛这个异常)
使用客户端 与ES server版本匹配的ES Client 版本

4.18 TransportSerializationException
Caused by: org.elasticsearch.transport.TransportSerializationException: Failed to deserialize exception response from stream
检查ES client的版本与server的版本是否一致

4.19 IllegalStateException
java.lang.IllegalStateException: Message not fully read (request) for requestId
检查是否有低版本的ES client访问高版本的ES server
4.20 InvalidIndexNameException
索引的名字有如下规范,如果违反如下规范,则抛出上述异常
索引名字不能包含非法的文件名称字符(\,/,*,?,",<,>,|,空格,逗号 )
索引名字不能包含#
索引名字不能以下划线_开头
索引名称如果包含字母,则必须为小写

4.21 TypeMissingException
ES的index是逻辑数据库,type是逻辑表
这个异常的意思是逻辑表不存在
逻辑表如果是通过ES系统申请创建的
新版本es已经去掉了type,这个后面专门讲一下

4.22 EsRejectedExecutionException
ES处理读写也是使用了常规的线程池。有一定数目的线程处理读写;超过线程的请求被放到等待队列中,如果等待队列满了,就会抛出EsRejectedExecutionException。

ES作为存储系统,有自己的处理能力上限,业务方也需要利用消息队列等系统“削峰”保护ES

4.23 正确导出数据
scroll/scan,相当于数据库的游标方式。开启了scroll/scan后,只需要指定步长即可。
官方demo:https://www.elastic.co/guide/en/elasticsearch/client/java-api/2.1/java-search-scrolling.html

4.24 如何处理bigint/bigdecimal类型
ES官方正在考虑支持
要么*100转成int/long,要么就string

4.25 Fielddata的危害和doc value
如果要对查询的结果按照某个特定字段排序或者使用了ES的聚合,那么用到了Fielddata
为了快速的获取排序字段的值以及为了快速的聚合某些字段的值,ES默认情况下会全量的加载该字段的全部值到内存。如果数据量大,那么ES数据节点的内存就非常吃紧,甚至OOM也时刻相伴

为了解决该问题,ES提供了doc_value属性,如果对某字段设置了doc_value,那么该字段被用来排序或者聚合的情况下,并不会加载到内存而是仍然从硬盘读取

Doc_value的属性对应的是ES存储引擎里的列式存储数据结构,索引读取非常快。根据ES的官方benchmark, 使用doc_value的情况比使用fielddata仅仅慢7%左右

很多人要提说根据他们的测试,doc_value慢的不止7%;在次并不对%多少做讨论,但是要明白,很多情况下,用doc_value并不是时间换空间,而是用时间换生命。否则OOM了,再快有毛用~~~

4.26 不要用post filter
Post filter的提供是为了满足特定场景,99%的情况下都用不到
Post filter的内部实现相当于数据库的扫表,因此非常非常慢。90%的性能问题都是由其引起

4.27 Index属性:Analyzed VS not_analyzed VS No
Analyzed是使用分词,一段描述性的文字:“中华人民共和国”就会被分词为“中华”、“人民”、“共和国”,这样用户在查询“人民”时就能找到这段话;如果不分词就找不到

Not_analyzed是不使用分词,一个电话号码:“010-62256225”不分词,那么用“62256225”查询不到,只能用“010-62256225”查询

No是不索引,这样的内容不能被查询到
最后,要知道的一点:不管上述analyzer还是not_analyzer, 经过这样的处理之后,这玩意就开始有个学名了:token
4.28 MaxBytesLengthExceededException
接上述,token的最大长度是有限制的,经常会出现将一个html当做not_analyzed的内存形成一个token,然后这样的token超过长度就抛出这个异常了。
如果抛出这个异常,要么这个字段不需要被索引(index:no),要么需要被分词(index: analyzed)
4.29 Type VS New Index
ES里提供了type,很多人以为type是物理表,一个type的数据是独立存储的;但是在ES内部并不是这样,type在ES内部仅仅是一个字段。所以在很多数据能分为独立index的情况下,不要放到一个index里用type去分。新版es已经去掉了多type

4.30 ._source的意义
_source是ES提供的默认字段,会将一个document的json全部存储

4.31 VersionConflictEngineException
并发update同一条document,ES内部采用了乐观并发的处理。并发修改的操作直到最后要提交是才加锁检查版本号,如果发现修改之前获取的版本号已经改变(即已经被人修改),那么会抛出这个异常。然后由用户觉得如何处理该异常

4.32 不要使用 TTL
ES2.X对TTL已经是Deprecated, 在最新版本5.X里已经是remove了.如果使用ttl,将来不能升级。
TTL替换方案
如果有根据时间过期的需求,可以构建一个时间的range query过滤掉失效的数据
或者定期运行数据删除的逻辑

4.33 ES的TPS
ES的单机写入能力是几w,然后可以认为是无限水平扩展的,也就是有N台机器,那么集群的写入能力就是N * ?w

4.34 在index时对字段用ik分词,查询时需要指定分词器吗?
指定方式:mapping中加这一段:“analyzer”:“ik” ,
ES2.1.X参照:https://github.com/medcl/elasticsearch-analysis-ik/tree/v1.7.0
ES1.7.X参照:https://github.com/medcl/elasticsearch-analysis-ik/tree/v1.4.1
查询时如果还想用ik,那么就不需要了。
查询中存在这样的语句 {“term”:{“name”:null}} ,以前的ES 1.7.6 支持这样,但是 ES 2.1.1 版不支持term字段的值为null。可以改为通过 exists 或者 missing 查询。

4.35 NumberFormatException Caused by: java.lang.NumberFormatException: For input string: "null"
如果field定义为 “type”: “double”,但是索引文档时,传递的是"null",就会抛NumberFormatException。

4.36 IllegalArgumentException Caused by: java.lang.IllegalArgumentException: Invalid format: “”
传递空字符串 “”,不满足格式要求

4.37 SearchParseException
SearchParseException Caused by Caused by: org.elasticsearch.search.SearchParseException:Parse Failure [No mapping found for [create] in order to sort on]

没有“ create ”这个field,但是使用它进行查询

4.38 InvalidIndexNameException
InvalidIndexNameException Exceptionfailed to create:[test*] InvalidIndexNameException[Invalid index name [xxx*], must not contain the following characters [, /, *, ?, ", <, >, |, , ,]]
查询的时候索引带有, /, *, ?, ", <, >, |, , ,] 等特殊符号

4.39 获取文档数
如果想要获取满足某一条件(获取全部可以用match_all)的文档数,不需要使用scroll,分页等。
HTTP API:
“hits” 中有一个"total" 字段,表示的就是符合查询条件的所有文档数。
JAVA API:
searchResponse.getHits().getTotalHits()获取的就是符合查询条件的所有文档数。

4.44 VerifyError
Exception in thread “main” java.lang.VerifyError: (class: org/jboss/netty/channel/socket/nio/NioWorkerPool, method: newWorker signature: (Ljava/util/concurrent/Executor;)Lorg/jboss/netty/channel/socket/nio/AbstractNioWorker;) Wrong return type in function

netty包冲突, 需要屏蔽低版本或者多余的jar包。ES 2.1.2版本java客户端需要的netty版本是 3.10.5

4.45 docker 线程数太多,或者 非docker线程多。
第一:docker环境,Settings中设置transport.netty.worker_count = docker核数*2 ,默认是物理核的2倍
第二:保障自己客户端是单例
第三:保障Settings中设置 client.transport.sniff 属性为false

4.46 ES transport client的connection
构建一个ES transport client,一般来说,这个client会向这个集群的每一个节点构建13个connection

4.48 throttling indexing: numMergesInFlight=10, maxNumMerges=9
es限制写入文档的速率。可能是写入太快了,es后端段合并赶不上你写入速度,请控制下写入速度。在ES 2.0.0之前,Elasticsearch以固定速率(默认为20 MB /秒)控制段合并的速率,以免段合并过快影响查询和写入。ES 2.0.0版本以后,Elasticsearch使用自适应合并IO限制,允许的IO速率会自动调节,所以您不需要触摸任何限制设置:只需使用默认设置.
可以通过indices.store.throttle.type: none来关闭段合并速率限制。但是即使这样设置不限制段进行合并的速率,断合并仍可能落后与你写文档的速度,导致throttling indexing。
4.49 SearchContextMissingException
查询上下文过期了。看是否是设置的query的timeout太短了,或者scroll查询传递了旧的scrollId。少量异常不影响使用。
注意:
1). context Id 每次使用最新的,不要用老的,可能能查到数据,但每个context Id 保留固定的时间,所以每次取数用最新的
2). scroll 的超时时间设置足够长,够处理一次响应的时间的1.5~2倍即可,当然不要过长 。注意,是scroll的超时,不是查询的超时,是2个概念,2个可以同时设置。
3). 应用里记得要try…catch 这样,搜不到结果也能看日常日志的原因

五、type

5.1、index、type的初衷

之前es将index、type类比于关系型数据库(例如mysql)中database、table,这么考虑的目的是“方便管理数据之间的关系”。

5.2、为什么现在要移除type?

5.2.1 在关系型数据库中table是独立的(独立存储),但es中同一个index中不同type是存储在同一个索引中的(lucene的索引文件),因此不同type中相同名字的字段的定义(mapping)必须一致。

5.2.2 不同类型的“记录”存储在同一个index中,会影响lucene的压缩性能。

5.3、替换策略

5.3.1 一个index只存储一种类型的“记录”

这种方案的优点:

a)lucene索引中数据比较整齐(相对于稀疏),利于lucene进行压缩。

b)文本相关性打分更加精确(tf、idf,考虑idf中命中文档总数)

5.3.2 用一个字段来存储type

如果有很多规模比较小的数据表需要建立索引,可以考虑放到同一个index中,每条记录添加一个type字段进行区分。

这种方案的优点:

a)es集群对分片数量有限制,这种方案可以减少index的数量。

5.4、迁移方案

之前一个index上有多个type,如何迁移到3.1、3.2方案?

5.4.1 先针对实际情况创建新的index,[3.1方案]有多少个type就需要创建多少个新的index,[3.2方案]只需要创建一个新的index。

5.4.2 调用_reindex将之前index上的数据同步到新的索引上。

5.5、参考
Removal of mapping types

发布了52 篇原创文章 · 获赞 7 · 访问量 3796

猜你喜欢

转载自blog.csdn.net/maomaoqiukqq/article/details/103414070
今日推荐