ElasticSearch 总结
ES 思维导图
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"
unassigned_shards 太高,就会导致 kibana 里集群状态为黄色。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17{
"cluster_name" : "elasticsearch-test",
"status" : "yellow",
"timed_out" : false,
"number_of_nodes" : 1,
"number_of_data_nodes" : 1,
"active_primary_shards" : 88,
"active_shards" : 88,
"relocating_shards" : 0,
"initializing_shards" : 0,
"unassigned_shards" : 70,
"delayed_unassigned_shards" : 0,
"number_of_pending_tasks" : 0,
"number_of_in_flight_fetch" : 0,
"task_max_waiting_in_queue_millis" : 0,
"active_shards_percent_as_number" : 55.69620253164557
}
具体可以使用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 |
|