1、什么是ElasticSearch:
Elasticsearch 是基于 Lucene 的 Restful 的分布式实时全文搜索引擎,每个字段都被索引并可被搜 索,可以快速存储、搜索、分析海量的数据。
集群:是一个或多个服务器的集合,共同保存数据并提供所有节点的联合索引和搜索功能。集群有唯一 标志,为"ElasticSearch"。
节点:节点是属于集群一部分的单体服务器,储存有数据并参与集群索引和搜索功能。如果节点设置为 按名称加入集群,则该节点只能是集群的一部分。
索引:类似关系型数据库中的数据库,有一个定义多重类型的映射。索引是逻辑名称空间,可映射到一 个或多个主分片,并且可以有不限个数的副本分片。
文档:文档类似关系型数据库中的数据行,不同的是处在索引中的文档可以有不同的结构或字段,但是 通用字段应该具有相同的数据类型。
2、ElasticSearch 的基本概念:
(1)index 索引:索引类似于mysql 中的数据库,Elasticesearch 中的索引是存在数据的地方,包含了 一堆有相似结构的文档数据。
(2)type 类型:类型是用来定义数据结构,可以认为是 mysql 中的一张表,type 是 index 中的一个 逻辑数据分类 。
(3)document 文档:类似于 MySQL 中的一行,不同之处在于 ES 中的每个文档可以有不同的字段, 但是对于通用字段应该具有相同的数据类型,文档是es中的最小数据单元,可以认为一个文档就是一条 记录。
(4)Field 字段:Field是Elasticsearch的最小单位,一个document里面有多个field
(5)shard 分片:单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在 多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务 器上去执行,提升吞吐量和性能。
(6)replica 副本:任何服务器随时可能故障或宕机,此时 shard 可能会丢失,通过创建 replica 副 本,可以在 shard 故障时提供备用服务,保证数据不丢失,另外 replica 还可以提升搜索操作的吞吐量。
节点是指ElasticSearch的实例。
当启动Elasticsearch的实例,就会启动至少一个节点。 相同集群名的多个节点的连接就组成了一个集群,在默认情况下,集群中的每个节点都可以处理http请 求和集群节点间的数据传输,集群中所有的节点都知道集群中其他所有的节点,可以将客户端请求转发 到适当的节点。
节点有以下类型:
主(master)节点:在一个节点上当node.master设置为True(默认)的时候,它有资格被选作为主节 点,控制整个集群。
数据(data)节点:在一个节点上node.data设置为True(默认)的时候。该节点保存数据和执行数据相 关的操作,如增删改查,搜索,和聚合。
客户端节点:当一个节点的node.master和node.data都设置为false的时候,它既不能保持数据也不能 成为主节点,该节点可以作为客户端节点,可以响应用户的情况,并把相关操作发送到其他节点。
主节点配置为:node.master: true node.data: false
从节点配置为:node.master: false node.data: true
3、ElasticSearch的分片:
Shard即数据分片,是ES的数据载体。在ES中数据分为primary shard(主分片)和replica shard(副本分片),每一个primary承载单个索引的一部分数据,分布于各个节点,replica为某个primary的副本,即备份。分片分配的原则是尽量均匀的分配在集群中的各个节点,以最大程度降低部分shard在出现意外时对整个集群乃至服务造成的影响。每个分片就是一个Lucene的实例,具有完整的功能。
4、ElasticSearch的分片分配策略:
ES使用数据分片(shard)来提高服务的可用性,将数据分散保存在不同的节点上以降低当单个节点发生故障时对数据完整性的影响,同时使用副本(repiica)来保证数据的完整性。关于分片的默认分配策略,在7.x之前,默认5个primary shard,每个primary shard默认分配一个replica,即5主1副,而7.x之后,默认1主1副。
ES在分配单个索引的分片时会将每个分片尽可能分配到更多的节点上。但是,实际情况取决于集群拥有的分片和索引的数量以及它们的大小,不一定总是能均匀地分布。Paimary只能在索引创建时配置数量,而replica可以在任何时间分配,并且primary支持读和写操作,而replica只支持客户端的读取操作,数据由es自动管理,从primary同步。
ES不允许Primary和它的Replica放在同一个节点中,并且同一个节点不接受完全相同的两个Replica同一个节点允许多个索引的分片同时存在。
5、text 和 keyword类型的区别:
两个类型的区别主要是分词:
keyword 类型是不会分词的,直接根据字符串内容建立倒排索引,所以 keyword类型的字段只能通过精确值搜索到;
Text 类型在存入 Elasticsearch 的时候,会先分词,然后 根据分词后的内容建立倒排索引
6、query 和 filter 的区别:
(1) query:查询操作不仅仅会进行查询,还会计算分值,用于确定相关度;
(2) filter:查询操作仅判断是否满足查询条件,不会计算任何分值,也不会关心返回的排序问题,同 时,filter 查询的结果可以被缓存,提高性能。
7、倒排索引过程:
倒排索引是区别于正排索引的概念: 正排索引:是以文档对象的唯一 ID 作为索引,以文档内容作为记录。 倒排索引:Inverted index,指的是将文档内容中的单词作为索引,将包含该词的文档 ID 作为记录。
倒排索引的生成过程
下面通过一个例子来说明下倒排索引的生成过程。
假设目前有以下两个文档内容: 苏州街维亚大厦 桔子酒店苏州街店 其处理步骤如下:
- 正排索引给每个文档进行编号,作为其唯一的标识。
- 生成倒排索引。
首先要对字段的内容进行分词,分词就是将一段连续的文本按照语义拆分为多个单词,这里两个文 档包含的关键词有:苏州街、维亚大厦… 然后按照单词来作为索引,对应的文档 id 建立一个链表,就能构成上述的倒排索引结构。
有了倒排索引,能快速、灵活地实现各类搜索需求。整个搜索过程中我们不需要做任何文本的模糊匹配。
倒排索引的结构根据倒排索引的概念,我们可以用一个 Map来简单描述这个结构。这个 Map 的 Key 的即是分词后的 单词,这里的单词称为 Term,这一系列的 Term 组成了倒排索引的第一个部分 —— Term Dictionary (索引表,可简称为 Dictionary)。 倒排索引的另一部分为 Postings List(记录表),也对应上述 Map 结构的 Value 部分集合。 记录表由所有的 Term 对应的数据(Postings) 组成,它不仅仅为文档 id 信息,可能包含以下信息:
文档 id(DocId, Document Id),包含单词的所有文档唯一 id,用于去正排索引中查询原始数 据。
词频(TF,Term Frequency),记录 Term 在每篇文档中出现的次数,用于后续相关性算分。
位置(Position),记录 Term 在每篇文档中的分词位置(多个),用于做词语搜索(Phrase Query)。
偏移(Offset),记录 Term 在每篇文档的开始和结束位置,用于高亮显示等。
8、Elasticsearch是如何实现master选举的?
Elasticsearch 的选主是 ZenDiscovery 模块负责的,主要包含 Ping(节点之间通过这个 RPC 来发 现彼此)和 Unicast(单播模块包含一个主机列表以控制哪些节点需要 ping 通)这两部分 对所有可以成为 master 的节点(node.master: true)根据 nodeId 字典排序,每次选举每个节 点都把自己所知道节点排一次序,然后选出第一个(第 0 位)节点,暂且认为它是 master 节 点。 如果对某个节点的投票数达到一定的值(可以成为 master 节点数 n/2+1)并且该节点自己也选 举自己,那这个节点就是 master。否则重新选举一直到满足上述条件。 master 节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;data 节点可以 关闭 http功能。
9、详细描述一下Elasticsearch索引文档的过程。
首先客户端向集群发出索引文档的请求,它会选择任何一个节点,这个节点当接收到请求后会根据路由算法找到应该放的那个主分片的位置,从而索引数据。
这个节点当接收到请求后会根据路由算法找到应该放的那个主分片的位置,从而索引数据,
之后为了保证数据的完整性,它会将它的副本数据进行同步,同步完成后客户端就可以进行访问了。 细节方面:
用户的索引请求发过来之后,首先协调结点默认使用文档ID参与哈希计算(也支持通过routing),
shard = hash(document_id) % (num_of_primary_shards)
分片位置索引 = 将文档ID或路由ID进行哈希计算后的值 % 所有分片总数
随后会在内存(memory)中建立一个索引(Index),这个Index会在内存中形成一个分段对象 (Segment), 为了防止数据出现问题,会同时在索引数据之后写入到日志(Translog)当中, 在此过程中,每隔1秒钟,会向Segment会将数据刷新到系统文件缓存区(OS Cache),以方便接收用户的查询, 因为如果让用户查询直接访问内存或磁盘,会使速度变慢。 当过了30分钟或者Translog中的数据超过了512M,Os Cache中的Segment会将数据刷写(flush)到 磁盘当中,刷写后内存中的缓冲将被清除。 此时一旦刷写的数据比较多了的话(磁盘中有多个Segment),磁盘就会将这些分段进行合并。
当分片所在的节点接收到来自协调节点的请求后,会将请求写入到Memory Buffer,然后定时(默 认是每隔1秒)写入到 Filesystem Cache,这个从MomeryBuffer到Filesystem Cache的过程就叫做 refresh;
当然在某些情况下,存在Momery Buffer和Filesystem Cache的数据可能会丢失,ES是通过 translog的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到translog中,当 Filesystem cache中的数据写入到磁盘中时,才会清除掉,这个过程叫做flush;
在 flush 过程中,内存中的缓冲将被清除,内容被写入一个新段,段的fsync将创建一个新的提交 点,并将内容刷新到磁盘,旧的translog将被删除并开始一个新的translog。
flush触发的时机是定时触发(默认30分钟)或者translog变得太大(默认为512M)时。
10、详细描述一下Elasticsearch写入数据主要流程?
- Es 客户端选择集群中的一个节点发起写请求;
- Es 集群将节点标记为协调节点;
- 协调节点对写入的 document 进行路由,选择 primary shard 写入数据(图中只是表示简单路由到一个节点,实际会把数据路由到多个节点);
- 该 primary shard 上的数据写入完毕后,将数据同步到副分片中;
- 协调节点告诉客户端 primary shard 和 replica shard 已经写入数据完毕;
写入请求将数据同时发送到 Es 的 Buffer 缓存和 translog 日志,此时数据在 Es 进程的 Buffer 缓存中,无法查询到;
默认每 1 秒,Es Buffer 中的数据会被 Refres 到 segment file os cache 中,并清空 ES 进程的 Buffer 缓存,此时数据就可以被查到了,这个过程就是refresh。
index.refresh_interval:1s
新segment会被先写入到 os cache –这一步代价会比较低,稍后再被刷新到磁盘–这一步代价比较高。
每隔1秒钟,es将buffer中的数据写入一个新的segment file,这个segment file中就存储最近1秒内 buffer中写入的数据
操作系统里面,磁盘文件其实都有一个东西,叫做 os cache,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入os cache,先进入操作系统级别的一个内存缓存中去。只要buffer中的数据被refresh操作输入os cache中,这个数据就可以被搜索到了。
重复上面的步骤,新的数据就不断进入buffer和translog,不断将buffer数据写入一个又一个新的segment file中去,每次refresh完buffer清空,translog保留
随着这个过程的推进,translog会变得越来越大。当translog达到一定的大小的时候,或者到达设置的默认时长(30min)后, 就会触发commit操作,这种操作也叫 flush(可以通过 API 触发)
flush操作 = refresh + 将translog中的记录刷到磁盘上 + 更新commit point信息 + 清空translog文件.
1)、先强行将 Es Buffer 中的数据写入 segment file os cache 中,然后清空 Es Buffer;
2)、向磁盘写入一个 commit point 文件,该文件标识着 commit point 对应的所有 segment file;
3)、强行将 segment file os cache 中的数据都 fsync 到磁盘文件中去;
4)、清空translog中的数据 (6.x版本为了实现sequenceIDs,不删除translog)
translog 文件:
在执行 commit(flush)操作前,所有数据基本都是在 os cache 中的,一旦机器宕机,或者断电,内存中的数据就会全部丢失。所以将数据先写入一个专门的日志文件,当机器出现故障并重启后,可以从日志文件中将数据重新读取出来,恢复到 Es Buffer 和 os cahce 中,保证数据完整性。
但是 translog 也是先写入到 translog os cache默认每经过 5 秒才会同步到本地磁盘,也就是说断电后,translog os cache 中的数也会丢失,但也是只丢失 5 秒的数据,但是性能要好一些;
也可以设置为不写入 translog os cache 中,直接 fsync 到本地磁盘,但是这样性能会差一些;
commit point 文件:
这个文件中记录中所有可用的 segment file,并且每个 commit point 都会维护一个 .del 文件
当es做删改操作时首先会在.del文件中声明某个document已经被删除,文件内记录了在某个segment内某个文档已经被删除,当查询请求过来时在segment中被删除的文件是能够查出来的,但是当返回结果时会根据commit point维护的那个.del文件把已经删除的文档过滤掉;
说明: es第一是准实时的,默认数据写入1秒后就可以搜索到。可能会丢失数据的,默认有5秒的数据,停留在buffer、translog os cache 、segment file os cache中,而不在磁盘上,
说明: es第一是准实时的,默认数据写入1秒后就可以搜索到。可能会丢失数据的,默认有5秒的数据,停留在buffer、translog os cache 、segment file os cache中,而不在磁盘上,
总结如下:
ES的任意节点都可以作为协调节点(coordinating node)接受请求
当协调节点接受到请求后进行一系列处理,然后通过_routing字段找到对应的primary shard,并将请求转发给primary shard,primary shard完成写入后,将写入并发发送给各replica, raplica执行写入操作后返回给primary shard, primary shard再将请求返回给协调节点。数据写入ES buffer,然后每隔1s,将数据refresh到os cache,到了os cache数据就能被搜索到。默认每隔5s,将 translog os cache 数据写入到translog文件, 当translog达到一定量或者默认每隔30min,会触发commit操作,将缓存区的数据flush到segment file磁盘文件中。数据写入到segment file之后,同时就建立好了倒排索引
10、Elasticsearch的查询原理
包括单个ID查询文档和多个ID查询文档的流程。在搜索查询方面,通过两阶段查询,首先在各个分片拷贝中搜索匹配的文档标识符,然后在协调节点合并结果并获取完整文档
单个ID查询文档
例如:当 ES客户端 将 单文档id Get请求发送到节点 2 时,节点 2 将作为协调节点来处理查询请求。由于哈希取模运算确定了文档所在的分片为 shard3,因此查询流程如下:
协调节点处理:节点 2(协调节点)接收到查询请求后,它将确定文档所在的分片为 shard3。
分片路由:节点 2 根据分片路由 知道 shard3 的主分片在节点 3 上,因此它将查询请求转发到节点 3。
文档检索:一旦查询请求到达节点 3,它将在 shard3 的主分片上执行文档检索操作并将检索到的文档结果返回给协调节点(节点 2)
最终返回:最后协调节点(节点 2)将收到来自数据节点(节点 3)的文档结果,并将其返回给客户端。
多个ID查询文档
对于 mget 请求,Elasticsearch 客户端会一次性发送多个文档ID,并且在协调节点(这里是节点 2)进行路由和汇总查询结果。
多文档ID查询(mget 请求)的流程:
协调节点处理:节点 2(协调节点)接收到 mget 请求后,会根据每个文档ID的哈希值确定对应的分片。
分片路由:协调节点会根据每个文档ID的哈希值确定其所在的分片,并将查询请求路由到负责相应分片的节点上。
并行查询:每个数据节点接收到查询请求后,并行地搜索相应的文档。每个节点将同时处理其负责的文档ID的查询请求。
结果汇总:每个数据节点将查询结果返回给协调节点。
结果返回:协调节点收到来自数据节点的查询结果后,会将它们汇总并返回给 Elasticsearch 客户端。
11、Elasticsearch 集群脑裂问题?有哪些解决方法?
“脑裂”问题可能的成因:(有两个master)
网络问题:集群间的网络延迟导致一些节点访问不到 master,认为 master 挂掉了从而选举出新 的master,并对 master 上的分片和副本标红,分配新的主分片 。
节点负载:主节点的角色既为 master 又为 data,访问量较大时可能会导致 ES 停止响应造成大面 积延迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点。
内存回收:data 节点上的 ES 进程占用的内存较大,引发 JVM 的大规模内存回收,造成 ES 进程 失去响应。
脑裂问题解决方案
减少误判:discovery.zen.ping_timeout 节点状态的响应时间(超过这个时间就会重新选举 master),默认为 3s,可以适当调大,如果 master在该响应时间的范围内没有做出响应应答, 判断该节点已经挂掉了。
调大参数(如 6s,discovery.zen.ping_timeout:6),可适当减少误判。 选举触发:discovery.zen.minimum_master_nodes:1 该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值, 且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(n/2)+1,n 为主节点个数 (即有资格成为主节点的节点个数)
该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值, 且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(n/2)+1,n 为主节点个数 (即有资格成为主节点的节点个数)
角色分离:即 master 节点与 data 节点分离,限制角色 主节点配置为:node.master: true node.data: false 从节点配置为:node.master: false node.data: true
12、Elasticsearch了解多少,聊聊你们公司ES的集群架 构,索引数据大小,分片有多少,以及一些调优手段 。
比如:ES集群架构13个节点,索引根据通道不同共20+索引,根据日期,每日递增20+ 索引:10分片,每日递增1亿+数据,每个通道每天索引大小控制:150GB之内。
索引层面调优手段:
1、设计阶段调优
1)根据业务增量需求,采取基于日期模板创建索引,通过roll over API滚动索引;
2)使用别名进行索引管理;
3)每天凌晨定时对索引做force_merge操作,以释放空间;
4)采取冷热分离机制,热数据存储到SSD,提高检索效率;冷数据定期进行shrink操作,以缩减存储;
5)采取curator进行索引的生命周期管理;
6)仅针对需要分词的字段,合理的设置分词器;
7)Mapping阶段充分结合各个字段的属性,是否需要检索、是否需要存储等。
2、写入调优
1)写入前副本数设置为0;
2)写入前关闭refresh_interval设置为-1,禁用刷新机制;
3)写入过程中:采取bulk批量写入;
4)写入后恢复副本数和刷新间隔;
5)尽量使用自动生成的id。
3、查询调优
1、filesystem cache越大越好 为了使得搜索速度更快, es严重依赖filesystem cache 一般来说,需要至少一半的 可用内存作为filesystem cache,这样es可以在物理内存中 保有 索引的热 点区域(hot regions of the index)
2、用更好的硬件 搜索一般是I/O bound的,此时,你需要 1. 为filesystem cache分配更多的内存 2. 使用SSD硬盘 3. 使用local storage(不要使用NFS、SMB 等remote filesystem) 4. 亚马逊的 弹性块存储(Elastic Block Storage)也是极好的,当然,和local storage比起来,它还 是要慢点 如果你的搜索是 CPU-bound,买好的CPU吧
3、文档模型(document modeling) 文档需要使用合适的类型,从而使得 search-time operations 消耗更少的资源。咋作呢? 答:避免 join操作。具体是指 a.nested 会使得查询慢 好几倍 b.parent-child关系 更是使得查询慢几百 倍 如果 无需join 能解决问题,则查询速度会快很多
4、预索引 数据 根据“搜索数据最常用的方式”来最优化索引数据的方式 举个例子:所有文档都有price字段,大部分query 在 fixed ranges 上运行 range aggregation。你可以 把给定范围的数据 预先索引下。然后,使用 terms aggregation
5、Mappings(能用 keyword 最好了) 数字类型的数据,并不意味着一定非得使用numeric类型的字 段。 一般来说,存储标识符的 字段(书号ISBN、或来自数据库的 标识一条记录的 数字),使用keyword更 好(integer,long 不好哦)
6、避免运行脚本 一般来说,脚本应该避免。如果他们是绝对需要的,你应该使用painless和 expressions引擎。
7、搜索rounded 日期 日期字段上使用now,一般来说不会被缓存。但,rounded date则可以利用上 query cache rounded到分钟等
8、强制merge只读的index 只读的index可以从“merge成 一个单独的 大segment”中收益
9、预热 全局序数(global ordinals)。全局序数用于在keyword字段上运行terms aggregations。es 不知道哪些fields将用于/不用于 term aggregation 因此全局序数在需要时才加载进内存,但可以在mapping type上,定义 eager_global_ordinals==true。这样,refresh时就会加载 全局序数
10、预热 filesystem cache 机器重启时,filesystem cache就被清空。 OS将index的热点区域(hot regions of the index)加载进filesystem cache是需要花费一段时间的。 设置 index.store.preload 可以告知OS 这些文件需要提早加载进入内存 page:13/37 of 尼恩Java硬核架构班:狠卷3高架构,卷透底层技术,走向技术自由!
11、使用索引排序来加速连接 索引排序对于以较慢的索引为代价来加快连接速度非常有用。在索引分 类文档中阅读更多关于它的信息。
12、使用preference来优化高速缓存利用率 有多个缓存可以帮助提高搜索性能,例如文件系统缓存, 请求缓存或查询缓存。 然而,所有这些缓存都维护在节点级别,这意味着如果连续运行两次相同的请求,则有一个或多个副 本,并使用循环(默认路由算法),那么这两个请求将转到不同的分片副本,阻止节点级别的缓存帮 助。 由于搜索应用程序的用户一个接一个地运行类似的请求是常见的,例如为了分析索引的较窄的子集,使 用标识当前用户或会话的优选值可以帮助优化高速缓存的使用。
13、副本可能有助于吞吐量,但不会一直存在 除了提高弹性外,副本可以帮助提高吞吐量。例如,如 果您有单个分片索引和三个节点,则需要将副本数设置为2,以便共有3个分片副本,以便使用所有节 点。 现在假设你有一个2-shards索引和两个节点。 在一种情况下,副本的数量是0,这意味着每个节点拥有一个分片。在第二种情况下,副本的数量是1, 这意味着每个节点都有两个碎片。 哪个设置在搜索性能方面表现最好?通常情况下,每个节点的碎片数少的设置将会更好。 原因在于它将可用文件系统缓存的份额提高到了每个碎片,而文件系统缓存可能是Elasticsearch的1号 性能因子。 同时,要注意,没有副本的设置在发生单个节点故障的情况下会出现故障,因此在吞吐量和可用性之间 进行权衡。 那么复制品的数量是多少?如果您有一个具有num_nodes节点的群集,那么num_primaries总共是主 分片,如果您希望能够一次处理max_failures节点故障,那么正确的副本数是max(max_failures, ceil(num_nodes / num_primaries) - 1)。
14、打开自适应副本选择 当存在多个数据副本时,elasticsearch可以使用一组称为自适应副本选择的 标准,根据包含分片的每个副本的节点的响应时间,服务时间和队列大小来选择数据的最佳副本。这可 以提高查询吞吐量并减少搜索量大的应用程序的延迟。
13、详细描述一下Elasticsearch更新和删除文档的过程
1、删除和更新也都是写操作,但是Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展 示其变更。
2、磁盘上的每个段都有一个相应的del文件。当删除请求发送后,文档并没有真的被删除,而是在del 文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在del文件中 被标记为删除的文档将不会被写入新段。
3、在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在 del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在 结果中被过滤掉。
14、Elasticsearch在部署时,对Linux的设置有哪些优化方法?
1、64GB内存的机器是非常理想的, 但是32GB和16GB机器也是很常见的。少于8GB会适得其反。
2、如果你要在更快的CPUs和更多的核心之间选择,选择更多的核心更好。多个内核提供的额外 并发远 胜过稍微快一点点的时钟频率。
3、如果你负担得起SSD,它将远远超出任何旋转介质。 基于SSD的节点,查询和索引性能都有提升。 如果你负担得起,SSD 是一个好的选择。
4、即使数据中心们近在咫尺,也要避免集群跨越多个数据中心。绝对要避免集群跨越大的地理距离。
5、请确保运行你应用程序的 JVM和服务器的JVM是完全一样的。 在Elasticsearch的几个地方,使用Java 的本地序列化。
6、通过设置 gateway.recover_after_nodes、gateway.expected_nodes、 gateway.recover_after_time 可以在集群重启的时候避免过多的分片交换,这可能会让数据恢复从数个 小时缩短为几秒钟。
7、Elasticsearch 默认被配置为使用单播发现,以防止节点无意中加入集群。只有在同一台机器上运行 的节点才会自动组成集群。最好使用单播代替组播。
8、不要随意修改垃圾回收器(CMS)和各个线程池的大小。
9、把你的内存的(少于)一半给Lucene(但不要超过32GB!),通过ES_HEAP_SIZE 环境变量设置。
10、内存交换到磁盘对服务器性能来说是致命的。如果内存交换到磁盘上,一个100微秒的操作可能变 成10毫秒。 再想想那么多10微秒的操作时延累加起来。 不难看出swapping对于性能是多么可怕。
11、Lucene使用了大量的文件。同时,Elasticsearch在节点和HTTP客户端之间进行通信也使用了大量 的套接字。 所有这一切都需要足够的文件描述符。你应该增加你的文件描述符,设置一个很大的值,如 64,000。
补充:索引阶段性能提升方法
1、使用批量请求并调整其大小:每次批量数据5–15 MB大是个不错的起始点。
2、存储:使用 SSD 。
3、段和合并:Elasticsearch 默认值是20MB/s,对机械磁盘应该是个不错的设置。如果你用的是SSD, 可以考虑提高到 100–200 MB/s。 如果你在做批量导入,完全不在意搜索,你可以彻底关掉合并限流。另外还可以增加 index.translog.flush_threshold_size设置,从默认的512MB到更大一些的值,比如1GB,这可以在一 次清空触发的时候在事务日志里积累出更大的段。
4、如果你的搜索结果不需要近实时的准确度,考虑把每个索引的index.refresh_interval 改到30s。
5、如果你在做大批量导入,考虑通过设置 index.number_of_replicas: 0 关闭副本。
15、在并发情况下,Elasticsearch如果保证读写一致?
1、可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲 突;
2、另外对于写操作,一致性级别支持quorum/one/all,默认为quorum,即只有当大多数分片可用时 才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为 故障,分片将会在一个不同的节点上重建。
3、对于读操作,可以设置replication为sync(默认),这使得操作在主分片和副本分片都完成后才会返 回;如果设置replication 为async时,也可以通过设置搜索请求参数_preference 为 primary来查询主 分片,确保文档是最新版本。
可以通过版本号使用乐观锁并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲 突;
对于写操作:一致性级别支持 quorum/one/all,默认为 quorum。
quorum:即只有当大多数(一半以上)分片可用时才允许写操作。但即使大多数可用,也 可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同 的节点上重建。
one:即只要主分片数据保存成功,那么客户端就可以进行查询操作了。
all:是最高的一致性级别,要求所有分片的数据要全部保存成功,才可以继续进行。 对于读操作:可以设置 replication 为 sync(默认为同步),这使得操作在主分片和副本分片都完成 后才会返回;设置 replication 为 async(异步)时,也可以通过设置搜索请求参数_preference 为 primary 来查询主分片,确保文档是最新版本。
16、ES的参数优化
优化 fsync
为了保证不丢失数据,就要保护 translog 文件的安全:
该方式提高数据安全性的同时, 降低了一点性能.
==> 频繁地执行 fsync
操作, 可能会产生阻塞导致部分操作耗时较久. 如果允许部分数据丢失, 可设置异步刷新 translog 来提高效率,还有降低 flush 的阀值,优化如下:
"index.translog.durability": "async",
"index.translog.flush_threshold_size":"1024mb",
"index.translog.sync_interval": "120s"
优化 refresh
写入 Lucene 的数据,并不是实时可搜索的,ES 必须通过 refresh 的过程把内存中的数据转换成 Lucene 的完整 segment 后,才可以被搜索。
默认 1秒后,写入的数据可以很快被查询到,但势必会产生大量的 segment,检索性能会受到影响。所以,加大时长可以降低系统开销。对于日志搜索来说,实时性要求不是那么高,设置为 5 秒或者 10s;对于 SkyWalking,实时性要求更低一些,我们可以设置为 30s。
设置如下:
"index.refresh_interval":"5s"
优化 merge
index.merge.scheduler.max_thread_count 控制并发的 merge 线程数,如果存储是并发性能较好的 SSD,可以用系统默认的 max(1, min(4, availableProcessors / 2)),当节点配置的 cpu 核数较高时,merge 占用的资源可能会偏高,影响集群的性能,普通磁盘的话设为1,发生磁盘 IO 堵塞。设置max_thread_count 后,会有 max_thread_count + 2 个线程同时进行磁盘操作,也就是设置为 1 允许3个线程。
设置如下:
"index.merge.scheduler.max_thread_count":"1"
优化线程池配置
前文已经提到过,write 线程池满负荷,导致拒绝任务,而有的数据无法写入。
而经过上面的优化后,拒绝的情况少了很多,但是还是有拒绝任务的情况。
所以我们还需要优化 write 线程池。
为了更直观看到 ES 线程池的运行情况,我们安装了 elasticsearch_exporter 收集 ES 的指标数据到 prometheus,再通过 grafana 进行查看。
经过上面的各种优化,拒绝的数据量少了很多,但是还是存在拒绝的情况,如下图:
write 线程池采用 fixed 类型的线程池,也就是核心线程数与最大线程数值相同。线程数默认等于 cpu 核数,可设置的最大值只能是 cpu 核数加 1,也就是 16 核 CPU,能设置的线程数最大值为 17。
优化的方案:
-
线程数改为 17,也就是 cpu 总核数加 1
-
队列容量加大。队列在此时的作用是消峰。不过队列容量加大本身不会提升处理速度,只是起到缓冲作用。此外,队列容量也不能太大,否则积压很多任务时会占用过多堆内存。
config/elasticsearch.yml文件增加配置
# 线程数设置
thread_pool:
write:
# 线程数默认等于cpu核数,即16
size: 17
# 因为任务多时存在任务拒绝的情况,所以加大队列大小,可以在间歇性任务量陡增的情况下,缓存任务在队列,等高峰过去逐步消费完。
queue_size: 10000
锁定内存,不让 JVM 使用 Swap
Swap 交换分区:
当系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序,**这些被释放的空间被临时保存到 Swap 中,等到那些程序要运行时,再从 Swap 中恢复保存的数据到内存中。**这样,系统总是在物理内存不够时,才进行 Swap 交换。
Swap 交换分区对性能和节点稳定性非常不利,一定要禁用。它会导致垃圾回收持续几分钟而不是几毫秒,并会导致节点响应缓慢,甚至与集群断开连接。
减少分片数、副本数
分片
索引的大小取决于分片与段的大小,分片过小,可能导致段过小,进而导致开销增加;分片过大可能导致分片频繁 Merge,产生大量 IO 操作,影响写入性能。
因为我们每个索引的大小在 15G 以下,而默认是 5 个分片,没有必要这么多,所以调整为 3 个。
"index.number_of_shards": "3"
分片的设置我们也可以配置在索引模板。
副本数
减少集群副本分片数,过多副本会导致 ES 内部写扩大。副本数默认为 1,如果某索引所在的 1 个节点宕机,拥有副本的另一台机器拥有索引备份数据,可以让索引数据正常使用。但是数据写入副本会影响写入性能。对于日志数据,有 1 个副本即可。对于大数据量的索引,可以设置副本数为0,减少对性能的影响。
"index.number_of_replicas": "1"
17、解析es的返回json文档信息。·
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
// 假设responseString是从Elasticsearch获取的JSON响应字符串
String responseString = "{\"took\":1,\"timed_out\":false,\"_shards\":{\"total\":5,\"successful\":5,\"skipped\":0,\"failed\":0},\"hits\":{\"total\":1,\"max_score\":1.0,\"hits\":[{\"_index\":\"index_name\",\"_type\":\"doc\",\"_id\":\"1\",\"_score\":1.0,\"_source\":{\"field1\":\"value1\",\"field2\":\"value2\"}}]}}";
ObjectMapper mapper = new ObjectMapper(); // 创建ObjectMapper实例
JsonNode rootNode = mapper.readTree(responseString); // 将JSON字符串解析为JsonNode树
// 获取"hits"节点
JsonNode hitsNode = rootNode.path("hits").path("hits");
for (int i = 0; i < hitsNode.size(); i++) {
JsonNode hit = hitsNode.path(i);
String index = hit.path("_index").asText();
String type = hit.path("_type").asText();
String id = hit.path("_id").asText();
float score = (float) hit.path("_score").asDouble();
JsonNode source = hit.path("_source");
// 处理source中的字段
String field1 = source.path("field1").asText();
String field2 = source.path("field2").asText();
// 打印获取的数据
System.out.println("Index: " + index + ", Type: " + type + ", Id: " + id + ", Score: " + score + ", Field1: " + field1 + ", Field2: " + field2);
}