ElasticSearch的面试题
ElasticSearch是开源的高扩展的分布式全文搜索引擎。
ElasticSearch基本操作
ES里的Index
可以看做一个库,而Types
想当于表,Documents
则相当于表的行,这里的Types
的概念已经被逐渐弱化,ElasticSearch6.X
中,一个index
下已经只能包含一个type,ElasticSearch7.X
中,Type
的概念已经被删除了。
索引操作
创建索引
PUT 请求 http://127.0.0.1:9200/shopping
查看索引
GET 请求 http://127.0.0.1:9200/shopping 查看对应索引
GET 请求 http://127.0.0.1:9200/_cat/indices?v
删除索引
DELETE 请求 http://127.0.0.1:9200/shopping
文档操作
索引已经创建好了,接下来就是创建文档,并添加数据。这里的文档可以类比为关系型数据库中的表数据,添加数据格式为json
创建文档
_id 是随机生成的
POST 请求 http://127.0.0.1:9200/shopping/_doc
{
"title":"小米手机",
"category":"小米",
"price":3999.00
}
_id指定ID
POST/PUT 请求 http://127.0.0.1:9200/shopping/_doc/1001
这样操作_id 就会变成 1001
查询文档
GET 请求 http://127.0.0.1:9200/shopping/_doc/1001
查询所有文档
GET 请求 http://127.0.0.1:9200/shopping/_search
更新文档
更新全量数据
http://127.0.0.1:9200/shopping/_doc/1001
{
"title":"小米手机",
"category":"小米",
"price":4999.00
}
局部更新数据 更新一个字段的数据
POST 请求 http://127.0.0.1:9200/shopping/_update/1001
{
"doc" : {
"title":"华为手机"
}
}
删除文档
DELETE 请求 http://127.0.0.1:9200/shopping/_doc/1001
条件查询
1、 GET 请求
http://127.0.0.1:9200/shopping/_search?q=category:小米
2、请求体查询
http://127.0.0.1:9200/shopping/_search
{
"query":{
"match" : {
"category":"小米"
}
}
}
3、分页查询
http://127.0.0.1:9200/shopping/_search
{
"query":{
"match_all" : {
}
},
# 分页查询
"from":0,
"size":2,
# 指定查找的结果
"_source":["title"],
# 排序
"sort":{
"price":{
"order":"desc"
}
}
}
4、多条件查询
and
{
"query" :{
"bool" :{
# 多个条件同时成立
"must":[
"match":{
"category":"小米"
},{
"match":{
"price":1999.00
}
]
}
}
}
or
{
"query" :{
"bool" :{
# 多个条件只要一个成立即可
"should":[
"match":{
"category":"小米"
},{
"match":{
"category":"华为"
}
]
}
}
}
范围
{
"query" :{
"bool" :{
# 多个条件只要一个成立即可
"should":[
"match":{
"category":"小米"
},{
"match":{
"category":"华为"
}
],
"filter" :{
"range":{
"price":{
"gt":5000
}
}
}
}
}
}
完全匹配
完全匹配到小米
{
"query":{
"match_phrase" : {
"category":"小米"
}
}
}
高亮匹配
{
"query":{
"match_phrase" : {
"category":"小米"
}
},
"highlight":{
"fields" :{
"category":{}
}
}
}
聚合操作
{
"aggs":{ //聚合操作
"price_group":{ // 名称,可自定义
"terms":{ // 分组
"field":"price" //分组字段
}
}
},
//不需要原始数据
"size":0
}
{
"aggs":{ //聚合操作
"price_avg":{ // 名称,可自定义
"avg":{ // 平均值
"field":"price" //分组字段
}
}
},
//不需要原始数据
"size":0
}
映射关系
PUT 请求 http://127.0.0.1:9200/user/_mapping
{
"properties":{
"name":{
//可以被分词查询
"type":"text",
"index":true
},
"sex":{
"type":"keyword",
"index":true
},
"tel":{
"type":"keyword",
"index":false
}
}
}
ElasticSearch集群
核心概念
集群Cluster
一个集群就是由一个或多个服务器节点组织在一起,共同持有整个的数据,并一起提供索引和搜索功能。一个ElasticSearch集群中有一个唯一的名字标识,这个名字默认就是"elasticsearch"。这个名字很重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。
节点Node
集群中包含很多服务器,一个节点就是其中的一个服务器。作为集群的一部分,它存储数据,参与集群的索引和搜索功能。一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。
配置集群
elsticsearch.yml
cluster.name --- 集群名称 必须统一
node.name --- 节点名称 不能重复
node.master: true
node.data: true
network.host --- 主机名称 localhost
http.port --- 端口号 1001
transport.tcp.port --- 监听端口 9301
//跨域配置
http.cors.enabled: true
http.cors.allow-origin: "*"
http.port: 1002
transport.tcp.port: 9302
discovery.seed_hosts: ["localhost:9301"]
discovery.zen.fd.ping_timeout: 1m
discovery.zen.fd.ping_retries: 5
分片
- 1、允许你水平分割/扩展你的内容容量
- 2、允许你在分片之上进行分布式的、并行的操作,进而提供性能/吞吐量
副本
- 1、在分片/节点失败的情况下,提供高可用性。因为这个原因,注意到复制分片从不与原/主要分片置于同一个节点上是非常重要的
- 2、扩展你的搜索量/吞吐量,因为搜索可以在所有的副本上并行运行
路由计算
hash(id) % 主分片数量 = [0,1,2]
分片控制
用户可以访问任何一个节点获取数据,这个节点称之为协调节点
写数据
- 1、客户端请求集群节点 – 协调节点
- 2、协调节点将请求转换到指定的节点
- 3、主分片需要将数据保存
- 4、主分片需要将数据发送给副本
- 5、副本保存后,进行反馈
- 6、主分片进行反馈
- 7、客户端获取反馈
读数据
- 1、客户端发送查询请求到协调节点
- 2、协调节点计算数据所在的分片以及全部的副本位置
- 3、为了能够负载均衡,可能轮询所有节点
- 4、将请求转发给具体的节点
- 5、节点返回查询结果,将结果反馈给客户端
ElasticSearch读写一致性
-
对于更新操作:可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,每个文档都有一个
_version
版本号,这个版本号在文档被改变时加一。Elasticsearch使用这个_version
保证所有修改都被正确排序。当一个旧版本出现在新版本之后,它会被简单的忽略。利用_version
的这一优点确保数据不会因为修改冲突而丢失。比如指定文档的version
来做更改。如果那个版本号不是现在的,我们的请求就失败了。 -
对于写操作,一致性级别支持
quorum/one/all
,默认为quorum
,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。one
: 要求我们这个写操作,只要有一个primary shard
是active
活跃可用的,就可以执行all
: 要求我们这个写操作,必须所有的primary shard
和replica shard
都是活跃的,才可以执行这个写操作quorum
: 默认的值,要求所有的shard
中,必须是大部分的shard
都是活跃的,可用的,才可以执行这个写操作
-
对于读操作,可以设置
replication
为sync
(默认),这使得操作在主分片和副本分片都完成后才会返回;如果设置replication
为async
时,也可以通过设置搜索请求参数_preference
为primary
来查询主分片,确保文档是最新版本。
ElasticSearch操作流程
ES的写入流程
- 客户端选择一个
node
发送请求过去,这个node
就是coordinating node
(协调节点) coordinating node
对document
进行路由,将请求转发给对应的node
(有primary shard
)- 实际的
node
上的primary shard
处理请求,然后将数据同步到replica node
coordinating node
等到primary node
和所有replica node
都执行成功之后,就返回响应结果给客户端。
ES写入底层原理
- 数据先写入
memory buffer
,然后定时(默认每隔1s)将memory buffer
中的数据写入一个新的segment
文件中,并进入Filesystem cache
(同时清空memory buffer
),这个过程就叫做refresh
;ES 的近实时性:数据存在memory buffer
时是搜索不到的,只有数据被refresh
到Filesystem cache
之后才能被搜索到,而refresh
是每秒一次, 所以称es
是近实时的,可以通过手动调用es
的api
触发一次refresh
操作,让数据马上可以被搜索到; - 由于
memory Buffer
和Filesystem Cache
都是基于内存,假设服务器宕机,那么数据就会丢失,所以ES
通过translog
日志文件来保证数据的可靠性,在数据写入memory buffer
的同时,将数据写入translog
日志文件中,在机器宕机重启时,es
会自动读取translog
日志文件中的数据,恢复到memory buffer
和Filesystem cache
中去。ES 数据丢失的问题:translog
也是先写入Filesystem cache
,然后默认每隔 5 秒刷一次到磁盘中,所以默认情况下,可能有 5 秒的数据会仅仅停留在memory buffer
或者translog
文件的Filesystem cache
中,而不在磁盘上,如果此时机器宕机,会丢失 5 秒钟的数据。也可以将translog
设置成每次写操作必须是直接fsync
到磁盘,但是性能会差很多。 flush
操作:不断重复上面的步骤,translog
会变得越来越大,当translog
文件默认每30分钟或者 阈值超过512M
时,就会触发commit
操作,即flush
操作。- 将
buffer
中的数据refresh
到Filesystem Cache
中去,清空buffer
; - 创建一个新的
commit point
(提交点),同时强行将Filesystem Cache
中目前所有的数据都fsync
到磁盘文件中; - 删除旧的
translog
日志文件并创建一个新的translog
日志文件,此时commit
操作完成
- 将
ES更新和删除流
删除和更新都是写操作,但是由于Elasticsearch
中的文档是不可变的,因此不能被删除或者改动以展示其变更;所以 ES
利用.del
文件 标记文档是否被删除,磁盘上的每个段都有一个相应的.del
文件
- 如果是删除操作,文档其实并没有真的被删除,而是在
.del
文件中被标记为deleted
状态。该文档依然能匹配查询,但是会在结果中被过滤掉。 - 如果是更新操作,就是将旧的
doc
标识为deleted
状态,然后创建一个新的doc
。
memory buffer
每 refresh
一次,就会产生一个 segment
文件 ,所以默认情况下是 1s 生成一个segment
文件,这样下来 segment
文件会越来越多,此时会定期执行 merge
。
每次merge
的时候,会将多个 segment
文件合并成一个,同时这里会将标识为 deleted
的 doc
给物理删除掉,不写入到新的 segment
中,然后将新的segment
文件写入磁盘,这里会写一个 commit point
,标识所有新的 segment
文件,然后打开 segment
文件供搜索使用,同时删除旧的 segment
文件。
ES的搜索流程
搜索被执行成一个两阶段过程,即 Query Then Fetch
:
Query阶段
客户端发送请求到 coordinate node
,协调节点将搜索请求广播到所有的 primary shard
或 replica shard
。每个分片在本地执行搜索并构建一个匹配文档的大小为from + size
的优先队列。每个分片返回各自优先队列中 所有文档的ID
和排序值 给协调节点,由协调节点及逆行数据的合并、排序、分页等操作,产出最终结果。
Fetch阶段
协调节点根据 doc id
去各个节点上查询实际的 document
数据,由协调节点返回结果给客户端。
coordinate node
对doc id
进行哈希路由,将请求转发到对应的node
,此时会使用round-robin
随机轮询算法,在primary shard
以及其所有replica
中随机选择一个,让读请求负载均衡。- 接收请求的
node
返回document
给coordinate node
。 coordinate node
返回document
给客户端。
Query Then Fetch
的搜索类型在文档相关性打分的时候参考的是本分片的数据,这样在文档数量较少的时候可能不够准确,DFS Query Then Fetch
增加了一个预查询的处理,询问 Term
和 Document frequency
,这个评分更准确,但是性能会变差。
ElasticSearch提高查询效率
ES在数据量很大的情况下(数十亿级别)如何提高查询效率
解题思路
需要从ES搜索优化作答
es
的性能优化,主要是围绕着fileSystem cache
,也可以叫做OS cache
来进行;es
写入数据实际上数据最终都会写入到磁盘中去,当我们搜索读取的时候,系统会将数据放入到os cache
中,而es严重依赖于这个os cache
,如果我们给机器的内存足够多,在es
里存的数据小于内存容量,那么搜索效率是非常高的。
filesystem cahce
你往es
里写的数据,实际上都写到磁盘文件里去了,查询的时候,操作系统会将磁盘文件里的数据自动缓存到filesystem cache
里面去,es
的搜索引擎严重依赖于底层的filesystem cache
,你如果给filesystem cache
更多的内存,尽量让内存可以容纳所有的idx segment file
索引数据文件,那么你搜索的时候就基本都是走内存的,性能会非常高
减少字段
es
减少数据量仅仅放要用于搜索的几个关键字段即可,尽量写入es
数据量跟es
机器的filesystem cache
是差不多的就可以了;其他不用来检索的数据放hbase
里或者mysql
数据预热
每隔一段时间,访问一下数据,然后数据进入到os cache
中。这样用户来访问 时候就访问到os cache
中的数据,就比较快。
冷热分离
es
可以做类似于mysql
的水平切分,就是将大量的访问很少、频率很低的数据,单独写一个索引,然后将访问很频繁的热数据单独写一个索引。这样可以确保热数据在被预热之后,尽量都让他们留在filesystem os cache
里,别被冷数据冲刷掉。
document模型设计
es
里面的复杂的关联查询尽量别用,比如join/ nested /parent-child
搜索都要尽量避免
不允许深度分页
可以使用scroll api
scroll
会一次性给你生成所有数据的一个快照,然后每次滑动向后翻页就是通过游标scroll_id
移动,获取下一页。除了使用scroll api
,也可以使用search_after
来做,search_after
的思想使用前一页的结果来帮助检索下一页的数据,这种方式也不允许你随意翻页,你只能一页页往后翻。
ElasticSearch优化
磁盘选择
- 使用SSD
- 使用RAID 0
- 使用多块硬盘,并允许
ElasticSearch
通过多个path.data
目录配置把数据条带化分配到他们上面 - 不要使用远程挂载的存储,比如
NFS
或者SMB
、CIFS
合理设置分片数
- 控制每个分片占用磁盘容量不超过ES的最大JVM的堆空间设置(一般设置不超过32G),因此如果索引的总容量在500G左右,那么分片大小在16个左右即可
- 考虑一下node的数量,一般一个节点有时候就是一台物理机,如果分片数过多,大大超过了节点数,很可能会导致一个节点上存在多个分片,一旦该节点故障,
- 即使保持了1个以上的副本,同样有可能会导致数据丢失,集群无法恢复。所以,一般都设置分片数不超过节点数的3倍。
- 主分片,副本和节点最大数之间数量,我们分配可以参考
节点数<=主分片数*(副本数+1)
推迟分片分配
- 对于节点瞬时中断的情况,默认的情况,集群会等待一分钟来查看节点是否重新加入,如果这个节点在此期间重新加入,重新加入的节点会保持其现有的分片数据,不会触发新的分片分配
- 通过修改参数
delayed_timeout
,可以延长再均衡的时间,可以全局设置也可以在索引级别进行修改。
路由选择
带路由查询
写入速度优化
- 加大
Translog Flush
,目的是降低Iops
、Writelock
- 增加
Index Refresh
间隔,目的是减少Segment Merge
的次数 - 调整
Bulk
线程池和队列 - 优化节点间的任务分布
- 优化
Lucene
层的索引建立,目的是降低CPU及IO
批量数据提交
- Bulk默认设置批量提交的数据量不能超过100M。数据条数一般是根据文档的大小和服务器性能而定的。但是单次批处理的数据大小应从5MB-15MB逐渐增加,
- 当性能没有提升,把这个数据量作为最大值。
优化存储设备
密集使用硬盘的应用
合理使用合并
ES默认采用较保守的策略,让后台定期进行段合并
减少Refresh的次数
Lucene
在新增数据时,采用了延迟写入的策略,默认情况下索引的refresh_interval
为1秒Lucene
将待写入的数据先写到内存中,超过1秒(默认)时就会触发一次refresh
,然后refresh会把内存中的数据刷新到操作系统的文件缓存系统中。- 如果我们对搜索的实效性要求不高,可以将
refresh
周期延长
加大flush设置
Flush
的主要目的是把文件缓存系统中的段持久化到硬盘,当Translog
的数据量达到512MB或者30分钟时,会触发一次Flush
index.translog.flush_threshold_size
参数默认值是512MB
减少副本数量
如果我们需要大批量进行写入操作,可以先禁止Replica
复制,设置index.number_of_replicas:0
关闭副本。再写入完成后,Replica
修改回正常的状态。
内存设置
jvm.options
不能超过物理内存的50%
堆内存的大小最好不要超过32GB
-Xms31g
-Xmx31g
ElasticSearch选举Master节点
Elasticsearch 的分布式原理
Elasticsearch
会对存储的数据进行切分,将数据划分到不同的分片上,同时每一个分片会保存多个副本,主要是为了保证分布式环境的高可用。在 Elasticsearch
中,节点是对等的,节点间会选取集群的 Master
,由 Master
会负责集群状态信息的改变,并同步给其他节点。
Elasticsearch
的性能会不会很低:只有建立索引和类型需要经过 Master
,数据的写入有一个简单的Routing
规则,可以路由到集群中的任意节点,所以数据写入压力是分散在整个集群的。
Elasticsearch 如何 选举 Master
Elasticsearch
的选主是ZenDiscovery
模块负责的,主要包含Ping
(节点之间通过这个RPC来发现彼此)和 Unicast
(单播模块包含一个主机列表以控制哪些节点需要ping通)这两部分;
- 确认候选主节点的最少投票通过数量,
elasticsearch.yml
设置的值discovery.zen.minimum_master_nodes
; - 对所有候选
master
的节点(node.master: true
)根据nodeId
字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master
节点。 - 如果对某个节点的投票数达到阈值,并且该节点自己也选举自己,那这个节点就是
master
。否则重新选举一直到满足上述条件。
补充:master
节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;data
节点可以关闭http
功能。
Elasticsearch是如何避免脑裂现象
(1)当集群中 master
候选节点数量不小于3个时(node.master: true
),可以通过设置最少投票通过数量(discovery.zen.minimum_master_nodes
),设置超过所有候选节点一半以上来解决脑裂问题,即设置为 (N/2)+1
;
(2)当集群master
候选节点 只有两个时,这种情况是不合理的,最好把另外一个node.master
改成false
。如果我们不改节点设置,还是套上面的(N/2)+1
公式,此时discovery.zen.minimum_master_nodes
应该设置为2。这就出现一个问题,两个master
备选节点,只要有一个挂,就选不出master
了