ES
2020-07-12
ES(elasticsearch)
- https://www.elastic.co/cn/
- 基于Lucene实现分布式文件系统
集群(Cluster)
节点(Node)
- master: node.master: true, 有资格参与master竞选的节点,主节点通常管理集群状态,节点发现,元数据信息管理等。真正的主节点只有一个,其他master node只有选举权,但不起主节点的作用。
- data:node.data: true, 存储数据节点。数据节点用来存储索引的数据到磁盘上,并不参与主节点选取,集群状态维护等工作。
- tribe:>6.x被移除,5.x中用来执行夸集群搜索/写入路由功能
- ingest: node.ingest: true, 用来对请求进行过滤转换操作,支持Pipline配置,通常用在ETL环节中。
- coordinate: 节点比较特殊,十分重要,成为协调节点,每个节点都潜在成为协调节点,当一个节点不属于以上任意一种,则为协调者。
- 最佳实践:?
索引(Index)
分片(Shard)
段(Segment)
索引缓冲区(IndexBuffer)
- 当我们写入一条数据时,数据并不会直接写入底层文件中,而是会先放入某一个实例下的index buffer内,当index buffer触发策略同步索引时才会真正发起一次写入操作,底层Lucene则是会发起一次 segment commit, 产生一个新的段,此时数据可以被检索到。这个过程我们可以称之为 refresh;
- Index Buffer同步策略有2个:1. 当达到 index.refresh_interval的时长,执行一次refresh 2. 当IndexBuffer写满时,触发一次 refresh。策略1默认时间为1秒,策略2 IndexBuffer默认大小为当前es进程内存的10%。因此我们说es是准实时的搜索引擎,数据写入到能被查询到至少有1秒的延迟。由此我们还可以得出结论:需要实时准确查询得到结果的业务(例如对账)不可以依赖es。
事务日志(TransLog)
- 用过Mysql、Redis和Hbase等分布式系统的同学对事务日志一定不陌生,这类日志通常也被称作:WAL(Write Ahead Log),即每次写入数据同时都会将写数据行为记录到一个日志中,这个日志包括了数据本身和操作(插入更新删除),作用是用来在节点发生宕机时对数据进行修复。es的事务日志叫做translog,每当一次写请求到来时es将记录这一次操作,当节点宕机后,es会根据commit point对数据进行恢复。同时translog也是有大小限制的,每当translog达到一定大小或一定时间时会执行一次flush操作,强制将文件系统缓存中的数据同步到磁盘中(即产生一次磁盘IO),并清空日志。
健康状态(Health)
- 集群有三种健康状态分别由三种颜色表示,green,yellow,red。看上去很简单但是总是有同学不能准确理解这三种状态的含义,所以这里我来挨个解释下
-
- green: 这个很简单,就是集群健康运转时候的状态
-
- yellow: 当有副本丢失的时候处于该状态。
-
- red: 当有主分片丢失的时候处于该状态
- 之前有同学总是问我,集群yellow了,有宕机你赶紧看看。其实不是宕机,而是副本数设置不合理导致分片分配失败,当然大部分情况集群yellow都是节点宕机导致的。
一次搜索
- 首先client的请求经过es的 协调节点(coordinate node),协调节点会解析请求中的ppreference参数决定是走默认的路由策略还是定制的策略。
- 假设是默认的路由策略,协调节点将会从本地路由表中找到目标索引有哪些主分片或副本以及分别在哪些节点上。随机选择主或副将请求发往每个shard上,例如你有0,1,2三个主分片一个副本,会发往这三个分片号所在的主分片或副本的节点。
- 每个到达分片对应机器的请求,执行查询语法解析分词等一系列操作后,执行查询操作,默认取回10条数据(请求可以设置)的docId作为结果,这一步叫做
query
- 每个shard执行完均返回10条数据到发起请求的协调节点,协调节点聚合这N个节点返回的结果到一条优先队列,取top 10(或搜索时指定条数)个docId,再根据这些docId所在shard,去指定的机器获取其他属性值并返回,这一步叫做
fetch
。
- 协调节点将数据返回client。
- 注意:1. 搜索要去整个分片查,不然可能会漏数据 2. 副本也能用来检索
一次写入
- 请client求还是来到协调节点,协调节点首先根据文档的id计算一个hash值,并通过hash值定位到应该去哪个主分片;如果用户没有设置id,则会使用 TimeBasedUUID算法并做base64最终生成一个20位长度的字母大小写和数字混合的字符串,保证唯一性,并根据这个id计算hash找到应该去哪个主分片
- 请求路由到对应的主分片,立马写入IndexBuffer,并同时记录下当前操作到Translog,当IndexBuffer达到触发条件时,refresh数据到文件系统缓存中,此时Lucene产生一个可被检索的segment。当然写入时还会经历数据分词,包括类型判断,解析,归一化等等,数字还会有压缩算法等等。
- 当Translog达到触发条件(文件超过大小或一定时间)则会触发一次操作系统的缓存刷写(fsync),产生一次磁盘io,并清空日志,此时数据被真正落地到磁盘。当然操作系统缓存也有自己的同步策略,当脏页达到一定大小会被同步到磁盘中并清空。
- 在步骤2执行完后,当前节点会将这个请求同步到其他副本分片上,副本还会重复3这个步骤。根据集群的数据一致性配置,写入请求会决定是立即告诉协调节点写入完成,还是等待半数副本同步完成,还是全部副本同步完成时再告诉协调节点写入完成。
- 协调节点接收到写入完成的响应,并响应client写入完成。
- 注意:1. 写数据永远先写主分片 2. 副本同步不像MySQL使用binlog的形式,而是在写入过程中直接散发请求。
ES分页查询问题
- ES主要支持以下三种形式的分页:from+size、scroll、search after
from + size
- 问题:from不断增大,性能急剧下降,es节点OOM问题;
- 假如一共有10个shard,分页大小为10的情况下,用户需要查询第1001页,也就是from=10000,size=10,此时,协调节点(第一个收到查询请求的节点)会向10个shard发起查询,每个shard会返回10010条数据,协调节点需要对1001010条数据进行排序,然后筛选出10条数据,返回给客户端,很明显该算法的空间复杂度是O((from+size)numOfShard),numOfShard为shard的个数,所以随着from的增加,需要的空间也会急剧变大,因此很可能会引起ES实例OOM。除此之外,每次查询都需要重新计算前n-1页的数据,造成数据节点cpu等资源的浪费。
- 最佳实践:
- 限制翻页深度,建议from+size不要超过1000;
- 通过其他手段提供更精准的查询,例如联想词、热词推荐;不要使用_id、_uid 字段进行排序;这两个字段由于没有开启doc value 因此在查询时,ES会将该字段的所有值都加载进内存,很容易导致OOM,服务端可以通过以下配置来避免OOM:默认值是jvm堆的60%;indices.breaker.fielddata.limit:10%
- 服务端可以通过以下参数限制from+size的最大值,避免OOM的发生:默认值1W, index.max_result_window: 1000
- Scroll查询使用类似于数据库游标的方式,可以跳过之前已读取的数据,保证数据节点可以只返回size条数据,从而避免了from+size方式在处理深分页时导致OOM的风险。
- TODO 持续学习
Search After
最佳实践
索引mapping设计?
字段设计
- text和keyword的用途必须分清:分词和关键词(确定字段是否需要分词)
- text 类型作用:分词,用于搜索。
适用于:email 内容、某产品的描述等需要分词全文检索的字段;
不适用:排序或聚合(Significant Terms 聚合例外)
- keyword 类型:无需分词、整段完整精确匹配。
适用于:email 地址、住址、状态码、分类 tags。
禁止
- 大聚合
- 模糊搜索
- 深度分页
- https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-id-field.html
其他问题
- 一个索引的shard数一旦确定不能改变
- ES不支持事务ACID特性。
- reindex:reindex可以实现索引的shard变更,但代价非常大:速度慢、对性能有影响,所以好的设计和规划更重要
- 拒绝TOP>100查询
- 拒绝正则匹配查询
- 拒绝多层嵌套,不要超过2层