重学ElasticSearch (ES) 系列(一):核心概念及安装
重学ElasticSearch (ES) 系列(二):搜索外相关操作(集群、索引、文档)
重学ElasticSearch (ES) 系列(三):深入数据搜索
重学ElasticSearch (ES) 系列(四):ELK搭建SpringBoot日志实时分析系统
目录
- 一、基础概念
- 二、底层算法相关
- 三、使用语法相关
- 四、集群架构相关
- 4.1、集群、主分片、副本分片的关系
- 4.2、ES是如何实现master选举的?
- 4.3、如何解决ES集群的脑裂问题
- 4.4、集群健康度yellow、red是什么原因
- 4.5、为什么主分片数不能修改
- 4.6、写数据底层原理
- 4.7、ES集群写入和搜索数据过程
- 4.8、跨集群搜索的问题
- 五、ELK相关
- 六、优化手段
一、基础概念
1.1、什么是Elasticsearch
Elasticsearch 是基于 Lucene 的 Restful 的分布式实时全文搜索引擎,可以快速存储、搜索、分析海量的数据。
1.2、为什么要用Elasticsearch
1、传统数据库模糊查询不走索引,当数据量很大时,查询效率非常低下
2、Es搜索可以对搜索词进行自动分词等处理,更容易查询到相关的内容
二、底层算法相关
2.1、lucene与ES
简单来说,lucene 就是一个 jar 包,里面包含了封装好的各种建立倒排索引的算法代码。
ES是基于Lucene的,提供了更高层次的封装以及分布式等方面的增强与扩展,并且提供了基于JSON的REST API 来更方便地使用Lucene的功能
ELasticsearch中的每个分片都是一个分离的Lucene实例;
2.2、ES算分的算法是什么?
ES 5之前默认的算分采用TF-IDF,ES 5之后采用BM 25
BM 25是在TF-DF基础上做了一个收敛,避免了TF无限增长时得分无限增长的问题
2.3、倒排索引
倒排索引是一种数据结构
Elasticsearch是通过Lucene的倒排索引技术实现比关系型数据库更快的过滤。
-
创建索引
(1)把每个文档拆分为独立的词
(2)创建一个包含所有且不重复的词条列表
(3)记录每个词条出现在哪些文档中
-
搜索时
(1)搜索时,直接根据词,返回对应的文章id
2.4、正排索引
2.4.1、为什么要用正排索引?
正排索引主要用于实现根据指定字段进行排序和聚合的功能。
假如我们需要对数据做一些聚合操作,比如排序时,lucene会将所有匹配到的文档读入到内存,再进行排序,如果排序数据量巨大的话,非常容易就造成内存溢出。
而正排索引类似于数据库key-value-value,可以只读取id和排序字段到内存进行排序。
2.4.2、ES中正排索引有哪几种?
es中的正排索引有两种:FieldData和DocValues
FieldData是存在内存中的,DocValues存在磁盘中,DocValues不支持text类型字段的排序
2.4.3、ES是如何利用正排和倒排索引的?
【生成文档】
ES在生成文档时,会分别生成倒排索引和正排索引
【搜索】
Es先用倒排索引找到文档,然后用正排索引聚合排序等操作
2.4.4、如何自定义排序?
默认是根据算分排序,自定义排序需要用到正排索引
DocValues是默认开启的,所以可以直接使用,但是不支持对text类型字段的排序。
FieldData是默认关闭的,需要设置mapping开启
2.4.5、关闭正排索引
DocValues会占用更多磁盘空间,也会影响搜索速度,确定该字段不需要聚合、排序以及脚本操作可以通过mapping设置doc_values:false,改回true需要重建索引
2.5、高并发下如何保证读写一致?
-
更新操作
可以通过版本号使用乐观并发控制
每个文档都有一个
_version
版本号,对其更新会默认携带version=_version+1
,如果_version+1
小于更新时的_version
,说明已经被更新了,会更新失败 -
写操作
支持3种一致性级别,默认是:只有大多数分片可用时才允许写操作。
如果大多数可用,进行了写操作,写入失败的副本分片会在一个不同的节点上重建。
-
读操作
默认是主分片和副本分片上更新/写的操作都完成之后的文档才会被查到
可以修改参数,只要主分片更新/写的操作完成的文档即可被查到
三、使用语法相关
3.1、新增文档create和index的区别
【index】
文档不存在:新增
文档存在:删除旧的、新增
【create】
文档不存在:新增
文档存在:报错
3.2、更新文档update与index的区别
【index】
文档不存在:新增
文档存在:删除旧的、新增
【update】
文档不存在:报错
文档存在:直接更新
3.3、text 和 keyword类型的区别
keyword类型的字段不会分词,只能精确匹配,区分大小写的
text类型会被分词并且转为小写
3.4、query 和 filter 的区别?
query查询会计算相关度分数,进行排序
filter查询不进行分数计算,并且查询结果会被缓存,性能较高
四、集群架构相关
4.1、集群、主分片、副本分片的关系
【集群高可用架构】
es提供了分布式集群(Cluster)的方式,保证高可用
es将索引细分为分片(主分片、副本分片),一个索引被拆分为多个主分片,分布在不同节点上,用于解决存储空间水平扩容问题。副本分片是对主分片的拷贝,用于解决数据高可用问题(保证一个节点挂了,剩余节点主分片+副本分片能组成完整的数据,保证服务可用)。
【master节点】
master节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;
主节点(Master Node):也叫作主节点,主节点负责创建索引、删除索引、分配分片、追踪集群中的节点状态等工作。 Elasticsearch 中的主节点的工作量相对较轻。 用户的请求可以发往任何一个节点,并由该节点负责分发请求、收集结果等操作,而并不需要经过主节点转发。
【分片特性】
主分片在创建索引时指定,后续不允许修改
副本分片数可以动态调整。
4.2、ES是如何实现master选举的?
Elasticsearch 的选主是 ZenDiscovery 模块负责的,主要包含Ping(节点之间通过这个RPC来发现彼此)和 Unicast(单播模块包含一个主机列表以控制哪些节点需要ping通)这两部分;
1、【在elasticsearch.yml 配置文件中确定当master节点最少票数阈值】确认候选主节点的最少投票通过数量,elasticsearch.yml 设置的值
discovery.zen.minimum_master_nodes
;2、【每个节点给能ping通的NodeId最小的节点投票】对所有候选 master 的节点(
node.master: true
)根据 nodeId 字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。3、【选举投票达到阈值且自己选举了自己的节点为master节点】如果对某个节点的投票数达到阈值,并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。
4.3、如何解决ES集群的脑裂问题
【脑裂问题】
当网络出现问题,Node1是master节点和其他Node无法连接
Node1还是作为Master节点组成一个集群,其他节点重新选举出了Master节点,这样就形成了两个master节点,当网络回复,集群无法恢复
【解决脑裂】
7.0之前
设置quorum(仲裁)参数,只有集群中可被选举为master的节点数大于该参数,才能选举master
7.0开始
es做了优化,不会发生脑裂
4.4、集群健康度yellow、red是什么原因
为了保证集群的高可用,分片是要被分配到不同节点上的
Yellow或red原因是分片没有被合理分配,可以通过_cluster/allocation/explain查看具体原因
- 红:至少有一个主分片没有分配(节点挂了、存储空间不足等)
- 黄:至少有一个副本没有分配(节点不够)
- 绿:主副本分片全部正常分配
4.5、为什么主分片数不能修改
因为es在存储文档时,会根据主分片数和文档id,通过hash算法,计算出文档应该分布在哪个分片上,确保了文档可以均匀的分布在各个分片中。
如果修改了主分片数,那计算规则就变了,会导致文档不能均匀的分布在各个分片中。
Ps:保存文档时,可以传_routing参数,自定义存储到指定的分片上
4.6、写数据底层原理
由于倒排索引一旦生成,不可改变,所以每次新建文档(到某个分片),就会生成一个新的Segment,用来存储该分片上新的倒排索引。
- 【Refresh】
1、新建文档时,数据会分别写入Index Buffer和Transaction Log
2、Index Buffer每1s/存满(默认是jvm的10%),数据会刷新到Segment
- 【Transaction Log】
3、每次写入请求Transaction Log都会自动落盘
- 【Flush】
5、ES会自动做flush操作(每30分钟/Transaction log满(默认512MB)),会将Index Buffer刷新到Segment,并将Segment写入磁盘,清空Transaction log
- 【Merge】
6、Segment会越来越多,ES会自动进行Merge操作,减少Segment数量,也可以手动merge(POST my_index/_forcemerge)。
7、删除的文档数据会存到.log文件中,merge会真正删除.log文件。
4.6.1、ES为什么是近实时的?
【近实时】
ES默认新建文档1s后被搜到
【原因】
创建文档时,会先存储到Index Buffer中,每1s/Index Buffer存满(默认是jvm的10%)刷新存入Segment,查询是查的Segment,所以存入Segment之后才会被搜索到
4.6.2、ES如何保证断电数据不丢失?
答:创建文档时,存储到Index Buffer的同时,会存储到Transaction log中,Transaction log默认落盘,当断电恢复后,会根据Transaction log做数据恢复。
补充:每30分钟/Transaction log满(默认512MB),会进行一次Flush,将Index Buffer刷新到Segment,并将Segment写入磁盘,清空Transaction log。
4.6.3、为什么删除文档,并不会立即释放空间?
被删除的文档,会存到.log文件中,ES在自动进行merge时,会清理.log文件,此时才会释放空间
4.7、ES集群写入和搜索数据过程
4.7.1、写入数据
1、客户端向Node1节点发送新建/删除索引的请求
2、Node1节点根据文档id,通过Hash算法确定属于分片0,会将请求转发到Node3
3、Node3上主分片执行请求(1、对需要分词的字段进行分词、转成小写;2、存储为正排索引和倒排索引),如果成功了,将请求转发到Node1、Node2的副本分片上。一旦所有副本都报告成功,Node3向Node1节点报告成功,Node1节点向客户端报告成功
4.7.2、搜索数据
搜索执行阶段过程分俩个部分,我们称之为 Query Then Fetch。
-
Query查询阶段
1、客户端向Node3发送了一个search请求,Node3会创建一个大小为from+size的空队列
2、Node3将查询请求转发到索引的每个主分片/副本分片中。每个分片在本地执行查询(1、对需要分词的字段的搜索时,先分词、再转为小写,根据倒排索引匹配;2、对不需要分词的字段搜索时,直接精确匹配、自定义排序和聚合时使用正排索引)并添加结果到大小为 from + size 的本地有序优先队列中
3、每个分片返回各自优先队列中所有文档的 ID 和排序值给 Node 3 ,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
-
fetch - 读取阶段 / 取回阶段
1、协调节点根据ID,辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。
2、每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。
3、一旦所有的文档都被取回了,协调节点返回结果给客户端。
4.8、跨集群搜索的问题
4.8.1、深度分页
比如取100-110的数据,每个节点上的100-110并不是真正的100-110,所以需要把每个节点的前110都拿过来放一起排序,那么:
每个集群需要查询文档数 = from + size
协调节点需要处理文档数 = 分片数 * (from + size)
如果深度分页,from是1万,那协调点处理的数据量就会非常大,ES默认限定最多为1万个文档。
【解决】
1、使用滚动搜索,记录上一次搜索id,往下翻页从id之后开始取,但是只支持往后翻,不支持指定页数。会创建一个有时间限制的快照,新增数据快照不会更新。
2、业务上处理:每次少量翻页,让用户没有翻到很大页数的兴趣
4.8.2、相关性算分
【算分不准】
默认算分都是根据自己分片上的数据进行的算分,会导致各个分片上算分标准不一
【解决】
1、数据量少,主分片设置为1;数据量足够大时,保证文档均匀分布到各分片上
2、搜索时指定参数“_search?search_type=dfs_query_then_fetch”,会收集各个分片上的TF和IDF,合并算分。会耗费跟很多的CPU和内存
五、ELK相关
5.1、什么是ELK
ELK是一套日志采集、监控的解决方案
Elasticsearch:负责日志检索和储存
Logstash:负责日志的收集、处理
Kibana:负责日志的可视化
5.2、配置注意
对于日志类型的,可以基于时间序列的设置索引,通过通配符的方式匹配查找
对于旧的数据处理:直接删除文档是不会立即释放资源的,但是直接删除索引会立即释放索引,时间序列的索引也方便做冷热分离
六、优化手段
6.1、冷热分离架构
ES集群的索引写入及查询速度比较依赖于磁盘的IO速度,冷热数据分离的关键点为使用固态磁盘存储数据。
若全部使用固态,成本过高,且存放冷数据较为浪费,
因此我们可以将实时数据(5天内)存储到热节点(固态磁盘)中,历史数据(5天前)的存储到冷节点(普通机械磁盘)中,并且可以利用ES自身的特性,比如日志类的,可以按天建立索引,这样能更方便的根据时间将热节点的数据迁移到冷节点中,达到冷热分离,提升热点数据的查询速度
6.2、如何设计分片数、节点数、节点的设备要求
6.3.1、服务器节点要求
-
硬件设施
选择合理的硬件:数据节点尽量使用SSD
- 搜索类等性能要求高的场景,建议SSD
- 按照1:10的比例配置内存和硬盘
- 日志类和查询并发低的场景,可以考虑使用机械硬盘存储
- 按照1:50的比例配置内存和硬盘
- 单节点数据建议控制在2TB以内,最大不建议超过5TB
- JVM配置机器内存的一般,JVM内存配置不建议超过32G
- 搜索类等性能要求高的场景,建议SSD
-
内存
最大内存设置,不要超过50%,单节点最大内存不要超过32G
-
内存大小要根据Node需要存储的数据进行估算
- 搜索类:1:16
- 日之类:1:48-1:96
-
举例:加入需要存储1T的数据,设置一个副本,需要2T的空间
如果是搜索类项目:最大32G内存那么,一个节点最多存储32*16=512G,所以至少需要4个节点存储数据。
-
6.3.2、分片
-
主分片
7.0之前默认是5个主分片,7.0之后默认是1个主分片
【主分片少的问题】
单个主分片可以解决多个主分片算分不准等问题,但是单个主分片即使新增节点,也无法水平扩展
【主分片多的问题】
主分片过多,每个分片是一个lucene的索引,会占用更多的资源,并且每次搜索需要从每个分片上获取数据,会增加master节点管理负担,建议控制分片在10w以内。
数据量较少时,主分片过多也会导致相关性算分不准
【如何设计主分片】
根据数据量大小判定
- 日志类应用,单个分片不要大于50G
- 搜索类应用,单个分片不要大于20G
-
副本分片
【副本分片优点】
1、有效保证集群高可用
2、减缓主分片查询压力,可以提升整体查询的QPS
【缺点】
1、副本分片是主分片的一个拷贝,需要占与主分片一样的资源
2、创建索引及文档时,有几个副本就要消耗几倍的cpu
【如何设计副本分片】
考虑节点资源,如果资源充足、需要提升查询速度,可以设置多的副本分片
6.3、提升集群写性能
1、多线程
使用线程池,线程数设置为cpu核心数+1,避免过多的上下文切换,来不及处理的放入队列,队列不能太大,否则占用的内存会成为GC的负担
2、使用_bulk批量写入
单次建议在5-15M,使用负载均衡策略尽量将数据打到不同节点
3、分片设定:
(1)适当减少副本数
(2)合理设置主分片数,确保均匀分配到所有数据节点上(eg:5个数据节点的集群,5个主分片5个副本分片,需要限定每个节点上可分配分片数为(5+5)/5=2个,生产中还要适当调大,保证节点下线时,分片无法正常迁移)
4、关闭不需要的功能(mapping中设置)
(1)不需要聚合搜索的字段,index参数设置为fasle(不建立正排索引)
(2)不需要算分的字段,norms设置为fasle
(3)对于不需要分词的字符串使用keyword(不分词)
5、Index Buffer刷新到segment的时间默认是1s,可以设置为-1,即禁止自动refresh(Index Buffer存满再刷新),避免频繁refresh生成过多的segment文件;与此同时增大Index Buffer大小,可以进一步的减少生成segment文件(会导致搜索实时性降低)
6、每次写入数据,都会存入Transaction Log并落盘,把Transaction Log的落盘改为异步、把落盘时间改为60s(每分钟落盘一次)、增大Transaction Log大小(默认512M)(会降低数据的容灾性)
6.4、提升集群读性能
1、使用SSD盘和冷热分离架构,将旧的数据存到普通盘的节点上,把新的常查询的数据分布在SSD盘的节点上(日志类型的按时间序列建立索引)
2、尽量使用Filter Context,利用缓存机制,减少不必要的算分
3、禁止使用*开头通配符查询
4、避免查询时对数据做计算(字符串长度筛选等),可以在存储时计算存入新的字段
可以使用profile,explain api分析慢查询问题
5、【分片优化】
(1)适当减少主分片数,因为主分片数为5,一个查询需要访问5个分片
(2)控制单个分片尺寸,搜索类20G以内、日志类50G以内
6、使用基于时间序列的索引,将旧的只读的索引进行手动merge,减少segment的数量(日志类型)
6.5、提升搜索准确度
1、选用合适的分词器
分词器选用ik分词器,能够较好的对中文分词
创建索引时使用ik_max_word分词,搜索时使用ik_smart分词
即:索引时最大化的将文章内容分词,搜索时更精确的搜索到想要的结果。
更多,待补充。。。