1. 引言

1.在海量数据中执行搜索功能时,如果使用MySQL, 效率太低。

2.如果关键字输入的不准确,一样可以搜索到想要的数据。

3.将搜索关键字,以红色的字体展示。

2. 介绍

es是一个使用java语言并且基于Lucene编写的搜索引擎框架,提供了分布式的全文搜索功能,提供了一个统一的基于restful风格的web接口。

3.基本概念

3.1. 索引

ElasticSearch 将它的数据存储在一个或多个索引( index )中。用 SQL 领域的术语来类比,案引就像数据库,可以向索引写人文档或者从索引中读取文档,并通过在 ElasticSearch 内部使用 Lucene 将数据写人索引或从索引中检索数据。需要注意的是, ElasticSearch 中的索引可能由一个或多个 Lucene 索引构成,具体细节由 ElasticSearch 的索引分(shard )、复制(replica)机制及其配置决定。

3.2. 文档

文档( docummnt )是 ElasticSearch 世界中的主要实体(对 Lucene 来说也是如此)。对所有使用 ElasticSearch 的案例来说,它们最终都可以归结为对文档的搜索。文档由字段构成,每个字段有它的字段名以及一个或多个字段值(在这种情况下,该字段被称为是多值的,即文档中有多个同名字段)。文档之间可能有各自不同的字段集合,且文档并没有固定的模式或强制的结构。另外,这些规则也适用于 Lucene 文档。事实上, ElasticSearch 的文档最后都存储为 Lucene 文档了。从客户端的角度来看,文档是一个 JSON 对象(想了解更多关于JSON 格式细节,请参考http://en.wikipedia.org/wiki/JSON

3.3. 映射

所有文档在写人索引前都需要先进行分析。用户可以设置一些参数,来决定如何将输人文本分割为词条,哪些词条应该被过滤掉,或哪些附加处理是有必要被调用的(如移除 HTML 标签)。此外, ElasticSearch 也提供了各种特性,如排序时所需的字段内容信息。这就是映射( mapping )扮演的角色,存储所有这种元信息。虽然 ElasticSearch 能根据字段值自动检测字段的类型,但有时候(事实上,几乎是所有时候)用户还是想自行配置映射,以便处理一些特殊要求。

3.4. 类型

ElasticSearch 中每个文档都有与之对应的类型( type )定义。这允许用户在一个索引中存储多种文档类型,并为不同文档类型提供不同的映射。

大类

类型

说明

公共类型

binary

使用Base64编码的二进制字符串

boolean

true或false值

Keywords

keyword族,keyword、constant_keyword及wildcard

Number

数值类型,如long、double等

Dates

日期类型,包括date及date_nanos

alias

针对已存在的字段映射一个alias

对象及关系类型

object

表示JSON对象

flattened

整个JSON对象作为单个字段值

nested

JSON对象保留与其内部字段关系

join

同一个索引中定义父子文档

结构化数据类型

Range

范围类型,如long_range,double_range,date_range或ip_range

ip

IPv4及IPv6地址

version

软件版本

murmur3

计算并存储哈希值

聚合数据类型

aggregate_metric_double

聚合metric值

histogram

聚合数值并用直方图展示

文本搜索类型

text fields

文本族,包括text及match_omly_text,解析非结构化文本

*annotated-text

包含特殊标记的文本,用于识别命名实体

completion

用于suggestion的自动补全

*search_as_you_type

键入自动补全的类text类型

token_count

计算文本中token的数量

文档排序类型

*dense_vector

记录浮点值的密集向量

*sparse_vector

记录浮点值的稀疏向量

*rank_feature

记录数值特征以在查询时提高命中率

*rank_features

记录数值特征以在查询时提高命中率

空间数据类型

geo_point

经纬度点坐标

geo_shape

复杂形状,如多边形

*point

任意笛卡尔点

*shape

任意笛卡尔几何

其他类型

percolator

索引用QueryDSL编写的查询

3.5. 节点

单个的 ElasticSearch 服务实例称为节点( node )。很多时候部署一个 ElasticSearch 节点就足以应付大多数简单的应用,但是考虑到容错性或在数据膨胀到单机无法应付这些状况时,你也许会更倾向于使用多竹点的 ElasticSearch 集群。

3.6. 集群

当数据量或查询压力超过单机负载时,需要多个节点来协问处理,所有这些节点组成的系统称为集群( cluster )。集群同时也是无间断提供服务的一种解决方案,即使节点因为宕机或执行管理任务(如升级)不可用时。 ElasticSearch 几乎无缝集成了集群功能,在我看来,这是它胜过竞争对手的最主要的优点之。而且,在 ElasticSearch 中配置一个集群是再容易不过的事了。

3.7. 分片

正如我们之前提到的那样,集群允许系统存储的数据总量超过单机容量。为了满足这个需求, ElasticSearch 将数据散布到多个物理 Lucene 索引上。这些 Lucene 索引称为分片 Shard ,而散布这些分片的过程叫作分片处理( sharding )。 ElasticSearch 会自动完成分片处理,开且让这些分片呈现出一个大索引的样子。请记住,除了 ElasticSearch 本身自动进行分片处理外,用户为具体的应用进行参数调优也是至关重要的,因为分片的数量在索引创建时就已经配置好,而且之后无法改变。

3.8. 副本

分片处理允许用户向 ElasticSearch 集群推送超过单机容量的数据。副本( replica )则解决了访问压力过大时单机无法处理所有请求的问题。思路很简单,即为每个分片创建冗余的副本,处理查询时可以把这些副本用作最初的主分片( primary shard )。请记住,我们并未付出额外的代价。即使某个分片所在的节点宕机, ElasticSearch 也可以使用其副本,从而不会造成数据丢失,而且支持在任意时间点加或移除副本,所以一旦有需要可随时调整副本的数量。

3.9. 网关

在 ElasticSearch 的工作过程中,关于集群状态,索引设置的各种信息都会被收集起来,并在网关( gateway )中被持久化。

4. 流程图

4.1. 写入&删除流程图

es 字段里面保存JSON 数据 有多个 es存储数据_lucene

4.2. 读取流程图

客户端在进行查询操作的时候,分成了两个阶段:分散阶段,合并接口。

es 字段里面保存JSON 数据 有多个 es存储数据_lucene_02

5. 索引

倒排索引是ElasticSearch里面的一个核心功能,此处主要讲解倒排索引,并与传统数据库的正排索引进行对比。

数字例子:

文档ID

内容

doc_1

我爱家园

doc_2

宁波是我的家园

doc_3

宁波天气挺好

5.1. 正排索引

索引

内容

doc_1

我爱家园

doc_2

宁波是我的家园

doc_3

宁波天气挺好

5.2. 倒排索引

倒排之前会对内容进行分词,此处将内容拆分成:我,我的,爱,家园,宁波,是,天气,挺好,好,我的家园。

索引

文档ID

doc_1,doc_2

我的

doc_2

doc_1

宁波

doc_2,doc_3

doc_2

天气

doc_3

挺好

doc_3

doc_3

我的家园

doc_2

6. 优化

6.1. 硬件优化

一、简述

es的默认配置是一个非常合理的默认配置,绝大多数情况下是不需要修改的,如果不理解某项配置的含义,没有经过验证就贸然修改默认配置,可能造成严重的后果。比如max_result_window这个设置,默认值是1W,这个设置是分页数据每页最大返回的数据量,冒然修改为较大值会导致OOM。ES没有银弹,不可能通过修改某个配置从而大幅提升ES的性能,通常出厂配置里大部分设置已经是最优配置,只有少数和具体的业务相关的设置,事先无法给出最好的默认配置,这些可能是需要我们手动去设置的。关于配置文件,如果你做不到彻底明白配置的含义,不要随意修改。

jvm heap分配:7.x 版本默认1GB,这个值太小,很容易导致OOM。Jvm heap大小不要超过物理内存的50%,最大也不要超过32GB(compressed oop),它可用于其内部缓存的内存就越多,但可供操作系统用于文件系统缓存的内存就越少,heap过大会导致GC时间过长

二、节点

  • 相同角色的节点,避免使用差异较大的服务器配置。
  • 避免使用“超大杯”服务器(SS:Super Server),比如128核CPU,1 T的内存,2T的固态硬盘。这样可能会产生较大的资源浪费。
  • 等量的配置,使用较少的物理机好于使用较多的虚拟机。比如一个一个五台4核16G的物理机,好于10甚至11台2核8G的虚拟机,这里不仅仅是虚拟机本身可能也会消耗一部分性能的问题,也涉及数据安全的问题。
  • 避免在同一台服务器上部署多个节点,会增加集群管理的难度。

三、分片

1. 分片创建策略

分片产生的目的是为了实现分布式,而分布式的好处之一就是实现“高可用性”(还包括高性能如提高吞吐量等会在后面内容展开讲),分片的分配策略极大程度上都是围绕如何提高可用性而来的,如分片分配感知强制感知等。

互联网开发没有“银弹”,分片的数量分配也没有适用于所有场景的最佳值,创建分片策略的最佳方法是使用你在生产中看到的相同查询和索引负载在生产硬件上对生产数据进行基准测试。分片的分配策略主要从两个指标来衡量:即数量单个分片的大小

2. 分片分配策略

  • 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
  • 同一个节点允许多个索引的分片同时存在。

3. 分片的数量

避免分片过多:大多数搜索会命中多个分片。每个分片在单个 CPU 线程上运行搜索。虽然分片可以运行多个并发搜索,但跨大量分片的搜索会耗尽节点的搜索线程池。这会导致低吞吐量和缓慢的搜索速度。

分片越少越好:每个分片都使用内存和 CPU 资源。在大多数情况下,一小组大分片比许多小分片使用更少的资源。

4. 分片的大小决策

  • 分片的合理容量:10GB-50GB。虽然不是硬性限制,但 10GB 到 50GB 之间的分片往往效果很好。根据网络和用例,也许可以使用更大的分片。在索引的生命周期管理中,一般设置50GB为单个索引的最大阈值。
  • 堆内存容量和分片数量的关联:小于20分片/每GB堆内存,一个节点可以容纳的分片数量与节点的堆内存成正比。例如,一个拥有 30GB 堆内存的节点最多应该有 600 个分片。如果节点超过每 GB 20 个分片,考虑添加另一个节点。

四、内存

根据业务量不同,内存的需求也不同,一般生产建议不要少于16G。ES是比较依赖内存的,并且对内存的消耗也很大,内存对ES的重要性甚至是高于CPU的,所以即使是数据量不大的业务,为了保证服务的稳定性,在满足业务需求的前提下,我们仍需考虑留有不少于20%的冗余性能。一般来说,按照百万级、千万级、亿级数据的索引,我们为每个节点分配的内存为16G/32G/64G就足够了,太大的内存,性价比就不是那么高了。

五、磁盘

对于ES来说,磁盘可能是最重要的了,因为数据都是存储在磁盘上的,当然这里说的磁盘指的是磁盘的性能。磁盘性能往往是硬件性能的瓶颈,木桶效应中的最短板。ES应用可能要面临不间断的大量的数据读取和写入。生产环境可以考虑把节点冷热分离,“热节点”使用SSD做存储,可以大幅提高系统性能;冷数据存储在机械硬盘中,降低成本。另外,关于磁盘阵列,可以使用raid 0。

六、CPU

CPU对计算机而言可谓是最重要的硬件,但对于ES来说,可能不是他最依赖的配置,因为提升CPU配置可能不会像提升磁盘或者内存配置带来的性能收益更直接、显著。当然也不是说CPU的性能就不重要,只不过是说,在硬件成本预算一定的前提下,应该把更多的预算花在磁盘以及内存上面。通常来说单节点cpu 4核起步,不同角色的节点对CPU的要求也不同。服务器的CPU不需要太高的单核性能,更多的核心数和线程数意味着更高的并发处理能力。现在PC的配置8核都已经普及了,更不用说服务器了。

七、网络

ES是天生自带分布式属性的,并且ES的分布式系统是基于对等网络的,节点与节点之间的通信十分的频繁,延迟对于ES的用户体验是致命的,所以对于ES来说,低延迟的网络是非常有必要的。因此,使用扩地域的多个数据中心的方案是非常不可取的,ES可以容忍集群跨多个机房,可以有多个内网环境,支持跨AZ部署,但是不能接受多个机房跨地域构建集群,一旦发生了网络故障,集群可能直接GG,即使能够保证服务正常运行,维护这样(跨地域单个集群)的集群带来的额外成本可能远小于它带来的额外收益。

八、总结

  • 集群需要多少种配置(内存型/IO型/运算型),每种配置需要多少数量,通常需要和产品运营和运维测试商定,视业务量和服务器的承载能力而定,并留有一定的余量。
  • 一个合理的ES集群配置应不少于5台服务器,避免脑裂时无法选举出新的Master节点的情况,另外可能还需要一些其他的单独的节点,比如ELK系统中的Kibana、Logstash等。

6.2. 查询优化

在大数据量的情况下,如何优化查询效率

一、增加os cache(又称FileSystem cache)的内存

读取os cache是在内存中进行的,如果你的os cache足够大,使大量的查询都在内存中进行,这样的查询速度会明显比从磁盘中读取快。要让 es 性能要好,最佳的情况下,就是你的机器的内存,至少可以容纳你的总数据量的一半

如果一行数据的字段太多,es存的数据是搜索的少量字段,其他的数据就可以放在mysql或hbase,一般es+hbase架构。

hbase 的特点是适用于海量数据的在线存储,就是对 hbase 可以写入海量数据,但是不要做复杂的搜索,做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 doc id,然后根据 doc id 到 hbase 里去查询每个 doc id 对应的完整的数据,给查出来,再返回给前端。

总结:写入 es 的数据最好小于等于,或者是略微大于 es 的 os cache 的内存容量。然后你从 es 检索可能就花费 20ms,然后再根据 es 返回的 id 去 hbase 里查询,查 20 条数据,可能也就耗费个 30ms,每次查询就是 50ms。

二、数据预热

es 集群中每个机器写入的数据量还是超过了 os cache 一倍,比如说你写入一台机器 60G 数据,结果 os cache 就 30G,还是有 30G 数据留在了磁盘上,就可以做数据预热。

举个例子,拿微博来说,你可以把一些大V,平时看的人很多的数据,你自己提前后台搞个系统,每隔一会儿,自己的后台系统去搜索一下热数据,刷到 os cache 里去,后面用户实际上来看这个热数据的时候,他们就是直接从内存里搜索。

总结:对于那些你觉得比较热的、经常会有人访问的数据,最好做一个专门的缓存预热子系统,就是对热数据每隔一段时间,就提前访问一下,让数据进入 os cache 里面去。这样下次别人访问的时候,性能一定会好很多。

三、冷热数据分离

如果es中只有一个索引,将热点数据放到os cache中,此时会有数据量大但访问频率低的数据把热点数据给刷到硬盘上,此时就可以做数据的冷热分离。

就是说将数据量大但访问频率低的数据单独写一个索引,将访问很频繁的热数据单独写一个索引。这样可以确保热数据在被预热之后,尽量都让他们留在 os cache 里,不会让冷数据给冲刷掉。

总结:2 个索引,一个放冷数据,一个放热数据。大量的时间是在访问热数据 index,几乎全都保留在os cache 里面了,就可以确保热数据的访问性能是很高的。 对于冷数据而言,是在别的 index 里的,跟热数据 index 不在相同的机器上,大家互相之间都没什么联系了。

四、document模型设计

对于 MySQL,我们经常有一些复杂的关联查询。在 es 里该怎么玩儿,es 里面的复杂的关联查询尽量别用,一旦用了性能一般都不太好。

最好是先在 Java 系统里就完成关联,将关联好的数据直接写入 es 中。搜索的时候,就不需要利用 es 的搜索语法来完成 join 之类的关联搜索了。

总结:很多操作,不要在搜索的时候才想去执行各种复杂的操作,如果复杂操作无法避免,就在写入的时候完成。

五、分页性能优化

假如你要查第 10 页的 10 条数据,不可能说从 3个 shard,每个 shard 就查 4 条数据,最后到协调节点合并成 10 条数据吧?你必须得从每个 shard 都查 100 条数据过来,然后根据你的需求进行排序、筛选等等操作,最后再次分页,拿到里面第 10 页的数据。你翻页的时候,翻的越深,每个 shard 返回的数据就越多,而且协调节点处理的时间越长,非常坑爹。所以用 es 做分页的时候,你会发现越翻到后面,就越是慢。

解决方案

1.系统不允许深度分页,默认翻的越深,性能就越差。

2.可以用 scroll api 类似于新闻中,向下拉刷一页数据。初始化时必须指定 scroll 参数,告诉 es 要保存此次搜索的上下文多长时间。你需要确保用户不会持续不断翻页翻几个小时,否则可能因为超时而失败。

优点:scroll 会一次性给你生成所有数据的一个快照,然后每次滑动向后翻页就是通过游标 scroll_id 移动,获取下一页下一页这样子,性能会比上面说的那种分页性能要高很多很多,基本上都是毫秒级的。

缺点:下拉翻页的,不能随意跳到任何一页的场景。也就是说,你不能先进入第 10 页,然后去第 120 页,然后又回到第 58 页,不能随意乱跳页。

3.用 search_after 来做,search_after 的思想是使用前一页的结果来帮助检索下一页的数据,显然,这种方式也不允许你随意翻页,你只能一页页往后翻。初始化时,需要使用一个唯一值的字段作为 sort 字段。

6.3. 写入优化

一、基本原则

写性能调优是建立在对Elasticsearch的写入原理之上。ES 数据写入具有一定的延时性,这是为了减少频繁的索引文件产生。默认情况下 ES 每秒生成一个 segment 文件,当达到一定阈值的时候 会执行merge,merge 过程发生在 JVM中,频繁的生成 Segmen 文件可能会导致频繁的触发 FGC,导致 OOM。为了避免避免这种情况,通常采取的手段是降低 segment 文件的生成频率,手段有两个,一个是 增加时间阈值,另一个是 增大Buffer的空间阈值,因为缓冲区写满也会生成 Segment 文件。

生产经常面临的写入可以分为两种情况:

高频低量:高频的创建或更新索引或文档一般发生在 处理 C 端业务的场景下。

低频高量:一般情况为定期重建索引或批量更新文档数据。

二、 增加refresh_interval的参数值

目的是减少segment文件的创建,减少segment的merge次数,merge是发生在jvm中的,有可能导致full GC,增加refresh会降低搜索的实时性。

ES的 refresh 行为非常昂贵,并且在正在进行的索引活动时经常调用,会降低索引速度。

默认情况下,Elasticsearch 每秒定期刷新索引,但仅在最近 30 秒内收到一个或多个搜索请求的索引上。

如果没有搜索流量或搜索流量很少(例如每 5 分钟不到一个搜索请求)并且想要优化索引速度,这是最佳配置。此行为旨在在不执行搜索的默认情况下自动优化批量索引。建议显式配置此配置项,如 30秒。

三、增加Buffer大小

本质也是减小refresh的时间间隔,因为导致segment文件创建的原因不仅有时间阈值,还有buffer空间大小,写满了也会创建。 默认最小值 48MB< 默认值 JVM 空间的10% < 默认最大无限制

四、关闭副本

当需要单次写入大量数据的时候,建议关闭副本,暂停搜索服务,或选择在检索请求量谷值区间时间段来完成。

第一,减小读写之间的资源抢占,读写分离

第二,当检索请求数量很少的时候,可以减少甚至完全删除副本分片,关闭segment的自动创建以达到高效利用内存的目的,因为副本的存在会导致主从之间频繁的进行数据同步,大大增加服务器的资源占用。

具体可通过则设置index.number_of_replicas 为0以加快索引速度。没有副本意味着丢失单个节点可能会导致数据丢失,因此数据保存在其他地方很重要,以便在出现问题时可以重试初始加载。初始加载完成后,可以设置index.number_of_replicas改回其原始值。

五、禁用swap

大多数操作系统尝试将尽可能多的内存用于文件系统缓存,并急切地换掉未使用的应用程序内存。这可能导致部分 JVM 堆甚至其可执行页面被换出到磁盘。

交换对性能和节点稳定性非常不利,应该不惜一切代价避免。它可能导致垃圾收集持续几分钟而不是几毫秒,并且可能导致节点响应缓慢甚至与集群断开连接。在Elastic分布式系统中,让操作系统杀死节点更有效。

六、使用多个工作线程

发送批量请求的单个线程不太可能最大化 Elasticsearch 集群的索引容量。为了使用集群的所有资源,应该从多个线程或进程发送数据。除了更好地利用集群的资源外,还有助于降低每个 fsync 的成本。

确保注意TOO_MANY_REQUESTS (429)响应代码(EsRejectedExecutionException使用 Java 客户端),这是 Elasticsearch 告诉我们它无法跟上当前索引速度的方式。发生这种情况时,应该在重试之前暂停索引,最好使用随机指数退避。

与调整批量请求的大小类似,只有测试才能确定最佳工作线程数量是多少。这可以通过逐渐增加线程数量来测试,直到集群上的 I/O 或 CPU 饱和。

七、避免使用稀疏数据

稀疏数据是指,在数据集中绝大多数数值缺失或者为零的数据

八、max_result_window参数

max_result_window是分页返回的最大数值,默认值为10000。max_result_window本身是对JVM的一种保护机制,通过设定一个合理的阈值,避免初学者分页查询时由于单页数据过大而导致OOM。

在很多业务场景中经常需要查询10000条以后的数据,当遇到不能查询10000条以后的数据的问题之后,网上的很多答案会告诉你可以通过放开这个参数的限制,将其配置为100万,甚至1000万就行。但是如果仅仅放开这个参数就行,那么这个参数限制的意义有何在呢?如果你不知道这个参数的意义,很可能导致的后果就是频繁的发生OOM而且很难找到原因,设置一个合理的大小是需要通过你的各项指标参数来衡量确定的,比如你用户量、数据量、物理内存的大小、分片的数量等等。通过监控数据和分析各项指标从而确定一个最佳值,并非越大越好。