ElasticSearch 总结
ES 思维导图
%% 完整的ES实战决策图 - 基于Leads系统真实案例
flowchart TD
%% 字段设计部分
Start([开始ES字段设计]) --> FieldType{需要索引该字段吗?}
FieldType -->|否| Skip[不索引该字段]
FieldType -->|是| SearchType{需要分词搜索吗?}
SearchType -->|是| TextType[text类型]
SearchType -->|否| KeywordType[keyword类型]
TextType --> TextConfig[配置text字段]
TextConfig --> TextAnalyzer{选择分词器}
TextAnalyzer -->|中文| IKAnalyzer[ik_max_word]
TextAnalyzer -->|英文| StandardAnalyzer[standard]
TextConfig --> NeedExact{需要精确匹配/排序/聚合?}
NeedExact -->|是| AddKeyword[添加keyword子字段]
NeedExact -->|否| TextOnly[仅text类型]
AddKeyword --> KeywordName{子字段命名}
KeywordName -->|通用| Raw[.raw]
KeywordName -->|业务| Keyword[.keyword]
KeywordType --> KeywordConfig[配置keyword字段]
AddKeyword --> LengthCheck1{字段可能超长吗?}
KeywordConfig --> LengthCheck2{字段可能超长吗?}
LengthCheck1 -->|是| SetLimit1[设置ignore_above]
LengthCheck1 -->|否| NoLimit1[不设置ignore_above]
LengthCheck2 -->|是| SetLimit2[设置ignore_above]
LengthCheck2 -->|否| NoLimit2[不设置ignore_above]
%% 查询构建部分
QueryStart([开始构建查询]) --> QueryType{查询类型?}
QueryType -->|精确匹配| TermQuery[term/terms]
QueryType -->|范围查询| RangeQuery[range]
QueryType -->|模糊搜索| MatchQuery[match/match_phrase]
QueryType -->|通配符| WildcardQuery[wildcard]
QueryType -->|嵌套对象| NestedQuery[nested]
%% 组合查询
ComplexQuery{需要组合条件?} -->|是| BoolQuery[bool查询]
ComplexQuery -->|否| SimpleQuery[单条件查询]
BoolQuery --> MustClause[must - 必须满足]
BoolQuery --> ShouldClause[should - 满足其一]
BoolQuery --> MustNotClause[must_not - 必须不满足]
%% 高级特性
Advanced{高级需求?} -->|大数据分页| SearchAfter[search_after分页]
Advanced -->|字段去重| Collapse[collapse去重]
Advanced -->|聚合统计| Aggregation[aggs聚合]
%% search_after详细流程
SearchAfter --> SearchFlow[search_after流程]
SearchFlow --> CheckCursor{是否有cursor?}
CheckCursor -->|是| GetLastDoc[获取最后一条记录]
CheckCursor -->|否| FirstPage[第一页查询]
GetLastDoc --> SetSearchAfter[设置search_after参数]
SetSearchAfter --> ExecuteQuery[执行查询]
FirstPage --> ExecuteQuery
ExecuteQuery --> ReturnResults[返回结果]
ReturnResults --> NextPage{需要下一页?}
NextPage -->|是| UpdateCursor[更新cursor]
UpdateCursor --> GetLastDoc
NextPage -->|否| EndQuery[结束查询]
%% 实际案例
Examples([实际案例]) --> Case1["用户姓名: text + keyword.raw"]
Examples --> Case2["电话号码: keyword(ignore_above:20)"]
Examples --> Case3["创意名称: text + keyword.keyword"]
Examples --> Case4["分页查询: search_after + sort"]
Examples --> Case5["去重查询: collapse + field"]
%% 连接关系
SetLimit1 --> Examples
SetLimit2 --> Examples
QueryStart --> Examples
%% 样式定义
classDef decisionBox fill:#e1f5fe,stroke:#01579b,stroke-width:2px
classDef configBox fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
classDef exampleBox fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px
classDef searchBox fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef queryBox fill:#fce4ec,stroke:#c2185b,stroke-width:2px
class FieldType,SearchType,NeedExact,LengthCheck1,LengthCheck2,KeywordName,QueryType,ComplexQuery,Advanced,CheckCursor,NextPage decisionBox
class TextConfig,KeywordConfig,SetLimit1,SetLimit2,SearchFlow,SearchAfter,Collapse,Aggregation configBox
class Examples,Case1,Case2,Case3,Case4,Case5 exampleBox
class TermQuery,RangeQuery,MatchQuery,WildcardQuery,NestedQuery,BoolQuery,MustClause,ShouldClause,MustNotClause queryBox
class GetLastDoc,SetSearchAfter,ExecuteQuery,ReturnResults,UpdateCursor,EndQuery searchBox
ES 的定位
- ES 是 build on top of Lucene 建立的可以集群化部署的搜素引擎。
- ES 可以是 document store(此处可以理解为文档仓库或者文档存储),可以结构化解决数据仓库存储的问题。在 es 中一切皆对象,使用对象对数据建模可以很好地处理万事万物的关系。
- ES 是海量数据的分析工具能够支持:搜索、分析(这一条其实我们很少用到)和实时统计。
ES 的架构有优越的地方:
- 自己使用 pacifica 协议,写入完成就达成共识。
- 节点对内对外都可以使用 RESTful API(or json over http)来通信,易于调试。
- 因为它有很多很好的默认值,api 也是 convention over configuration的,所以开箱即用。
- 它天生就是分布式的,可以自己管理多节点-它的路由机制是一个方便的,需要优化也可以优化的机制。
ES 的架构
ES 是基于 Lucene 的,集群上的每个 node 都有一个 Lucene 的实例。而 Lucene 本身是没有 type 的,所以 ES 最终也去掉了 type。
ES 中每个节点自己都能充当其他节点的 proxy,每个节点都可以成为 primary。用户需要设计拓扑的时候只需要关注种子节点和 initial master 即可。
ES 中的搜索
全文搜索
按照《全文搜索》中的例子,使用 match 总会触发全文搜索。因为在Elasticsearch中,每一个字段的数据都是默认被索引的。也就是说,每个字段专门有一个反向索引用于快速检索。想不要做索引需要对任意的 properties 使用 "index": "not_analyzed"
。
精确(短语)搜索
精确查询的方法有:
短语查询:
1 |
|
这种查询可以查出rock climbing
和a rock climbing
。
term 查询(这种查询是客户端查询里最常用的):
1 |
|
这种查询可以查出rock climbing
,不可以查出a rock climbing
。
另一种 phrase 查询:
1 |
|
精确和不精确查询有好几种方法,详见《elasticsearch 查询(match和term)》 和《finding_exact_values》。
注意,es 的查询可以有很多的变种:
1 |
|
search api 的搜索
参考《空查询》。
倒排索引简析
倒排索引的 key 是 term,value 是文档的指针,可以参考这个《倒排索引》文档。
过滤机制
头机制
x-pack
version 机制
容量与分片
为何分片不宜过大?
- 增加索引读压力-不利于并行查询。
- 不利于集群扩缩容(分片迁移耗费较长时间)。
- 集群发生故障时,恢复时间较长。
类似的问题也会发生在 Redis 之类的架构方案里。
解决大分片的方法
- 索引按月、日进行分割。
- delete_by_query 方法删除索引对索引进行缩容。
- 模板增加主分片数量-存疑,通常只能增加副本数,而无法改变切片比例。
- 扩容数据节点。
- 减小单位分片大小。
一个可实操的方案:
- 修改写入逻辑和 mapping,让新写入的 doc 的 field 变少。
- 对老的索引进行 delete_by_query。
常用查询段
- aggs
- match
- 等于分词以后的 or 查询,依赖于 analyzer。“hello world”如果被分词为 hello 和 world,则类似
term = hello or term = wolrd
。
- 等于分词以后的 or 查询,依赖于 analyzer。“hello world”如果被分词为 hello 和 world,则类似
- match_phrase
- term 出现在一个句子中,句子包含 term,依赖于 analyzer。类似
contains("hello world")
- 这是 String.contains。
- 不匹配 slop,还不如用 term。
- term 出现在一个句子中,句子包含 term,依赖于 analyzer。类似
- page:
- range:是大于等于小于的意思,不是 from 和 size
- search_after
- term
- term 类似于 Collection.contains
"[hello, world]".contains("hello")
和"[hello]".terms("hello")
都成立。
- term 类似于 Collection.contains
- terms:term 的数组形式,类似
in
,是多值或的意思。 - wildcard:
- 允许指定匹配的正则式。它使用标准的 shell 通配符查询:? 匹配任意字符,* 匹配 0 或多个字符。
写入流程
-
同步双写:对数据的实时性要求极高,通常在一个事务中完成数据的双写动作,保证数据层面的强一致性;
-
异步解耦:在完成数据库的写动作之后,基于MQ消息解耦索引的写入,流程存在轻微的延迟,如果消费失败会导致数据缺失;
-
定时任务:通过任务调度的方式,以指定的时间周期执行新增数据的同步机制,存在明显的时效问题;
-
组件同步:采用合适的同步组件,比如官方提供的组件或者一些第三方开源的组件,在原理上与任务同步类似;
数据同步的选型方案有多种,如何选择完全看具体的场景,在过往的使用过程中,对于核心业务会采用同步双写,对于内部的活动类业务会采用异步的方式,对于业务日志会采用任务调度,对于系统的监控或执行日志则多是依赖同步组件;
数据同步
设计难点
翻页
ES中常用From/Size进行分页查询,但是存在一个限制,在索引的设置中存在max_result_window分页深度的限制,6.8版本默认值是10000条,即10000之后的数据无法使用From/Size翻页;
先从实际应用场景来分析,大多数的翻页需求最多也就前10页左右,所以从这个角度考虑,ES的翻页限制在合理区间,在实践中也存在对部分索引调高的情况,暂未出现明显问题;
再从技术角度来思考一下,如果翻页的参数过大意味着更多的数据过滤,那计算资源的占用也会升高,ES引擎的强大在于搜索能力,检索出符合要求的数据即可;
索引设计
ES的底层是Lucene,可以说Lucene的查询性能就决定了ES的查询性能。Lucene内最核心的倒排索引,本质上就是Term到所有包含该Term的文档的DocId列表的映射。ES 默认会对写入的数据都建立索引,并且常驻内存,主要采用了以下几种数据结构:
- 倒排索引:保存了每个term对应的docId的列表,采用skipList的结构保存,用于快速跳跃。
- FST(Finite State Transducer):原理上可以理解为前缀树,用于保存term字典的二级索引,用于加速查询,可以在FST上实现单Term、Term范围、Term前缀和通配符查询等。内部结构如下:
- BKD-Tree:BKD-Tree是一种保存多维空间点的数据结构,主要用于数值类型(包括空间点)的快速查找。
字段存储
- 行存(stored fields , _source)
- 列存(doc_value)
聚合
指标聚合
桶聚合
性能分析
Quantitative Cluster Sizing
It depends.
ES 运行,是要解决 scale 和 speed 的矛盾。
有4种 basic computing resources:
- storage: 热数据放在贵硬件里。分成 warm tier 和 cold tier。
- memory:heap 使用 50% 内存就行了,堆不需要超过30gb,否则 gc 会是大问题:biz object、meta data(index、cluster)、segment。剩下的内存仍然有用,os cache、file cache 用来存储搜索和分析用的数据 in a cache format。es 会有很棒的缓存机制,既缓存最热的,也缓存长尾的(也要关注长尾是很多人意想不到的)。
- ML 节点很消耗内存。
- 如果有必要,使用 dedicated server 来 host master 节点。
- compute:
- 一定要知道我们的核心数、线程数和队列数的关系。
- network:
- 跨级群搜索的时候需要考虑网络问题。这时候我们要考虑“联合客户端”tribe node。
基本操作:
- index: store for future retrieval.
- delete
- update
- search
线上事故修复
磁盘满导致无法写入
症状
1 |
|
原始配置
1 |
|
1 |
|
这个 -d 好像可以用守护进程的方式执行服务,但实际上还是需要使用 nohup。
有时候 cp 会导致某些 jar not found,解法是准备一个 lib1,把 jar 拷贝过去,然后-cp /lib1/*:/lib/*
。有时候可以把 lib1 下的 jar 名字明确写出来,但如果有2个文件夹有同一个 jar,会导致Caused by: java.lang.IllegalStateException: jar hell! class: org.elasticsearch.cli.ExitCodes
独立的日志配置在 log4j2.properties 里。
1 |
|
这时候集群是黄色的,意味着所有主分片都已分配,但有副本分片未被分配。数据可用,但容错性降低。
。
诊断命令
curl -X GET "http://9.147.246.82:9201/_cluster/health?pretty"
1 |
|
unassigned_shards 太高,就会导致 kibana 里集群状态为黄色。
具体可以使用curl -X GET "http://9.147.246.82:9201/_cluster/allocation/explain"
来查看分配失败的分片,但这个命令不一定看得到全部的分片。同样可以用_cat/shards?v
和_cat/nodes?v
来查看不同颗粒度的结论。
curl -X GET "http://9.147.246.82:9201/_cat/indices?v&s=index&bytes=b"
,可以看到全部索引的状态。
修复命令
1 |
|
从 Elasticsearch 7.4 版本开始,当节点的磁盘使用率降到高水位线以下时,Elasticsearch 会自动移除 index.blocks.read_only_allow_delete 设置。这意味着一旦磁盘空间恢复到安全水平,索引将不再是只读的,写操作将再次被允许。
在较早的 Elasticsearch 版本(7.4 之前),当磁盘使用率过高导致索引被设置为只读后,即使磁盘空间释放,index.blocks.read_only_allow_delete 也不会自动恢复。需要手动将该设置修改为 false,以重新允许写入操作。
因此,如果使用的是 7.4 或更高版本的 Elasticsearch,并且启用了 cluster.routing.allocation.disk.threshold_enabled: true,那么当磁盘占用变少、低于高水位线后,Elasticsearch 会自动解除索引的只读限制,您无需手动干预。
总结:如果磁盘占用降低到安全水平,Elasticsearch 会自动更新 index.blocks.read_only_allow_delete 设置,不再禁止写入。
kibana 索引损毁
1 |
|
然后备份 kibana 的data 文件夹,在kibana的配置里加上日志:
1 |
|
然后执行kibana的命令:
1 |
|