ES 思维导图

ElasticSearch总结.xmind

ElasticSearch总结

ES 的定位

  1. ES 是 build on top of Lucene 建立的可以集群化部署的搜素引擎。
  2. ES 可以是 document store(此处可以理解为文档仓库或者文档存储),可以结构化解决数据仓库存储的问题。在 es 中一切皆对象,使用对象对数据建模可以很好地处理万事万物的关系。
  3. ES 是海量数据的分析工具能够支持:搜索、分析(这一条其实我们很少用到)和实时统计。

ES 的架构有优越的地方:

  1. 自己使用 pacifica 协议,写入完成就达成共识。
  2. 节点对内对外都可以使用 RESTful API(or json over http)来通信,易于调试。
  3. 因为它有很多很好的默认值,api 也是 convention over configuration的,所以开箱即用。
  4. 它天生就是分布式的,可以自己管理多节点-它的路由机制是一个方便的,需要优化也可以优化的机制。

ES 的架构

ES 是基于 Lucene 的,集群上的每个 node 都有一个 Lucene 的实例。而 Lucene 本身是没有 type 的,所以 ES 最终也去掉了 type。

ES 中每个节点自己都能充当其他节点的 proxy,每个节点都可以成为 primary。用户需要设计拓扑的时候只需要关注种子节点和 initial master 即可。

ES 中的搜索

全文搜索

按照《全文搜索》中的例子,使用 match 总会触发全文搜索。因为在Elasticsearch中,每一个字段的数据都是默认被索引的。也就是说,每个字段专门有一个反向索引用于快速检索。想不要做索引需要对任意的 properties 使用 "index": "not_analyzed"

精确(短语)搜索

精确查询的方法有:

短语查询

1
2
3
4
5
6
7
{
"query" : {
"match_phrase" : {
"about" : "rock climbing"
}
}
}

这种查询可以查出rock climbinga rock climbing

term 查询(这种查询是客户端查询里最常用的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"query": {
"bool": {
"must": [
{
"term": {
"about" : "rock climbing"
}
}
],
"must_not": [],
"should": [],
"filter": []
}
},
"from": 0,
"size": 10,
"sort": [],
"profile": false
}

这种查询可以查出rock climbing,不可以查出a rock climbing

另一种 phrase 查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"from": 0,
"size": 200,
"query": {
"bool": {
"must": {
"match": {
"about": {
"query": "rock climbing",
"type": "phrase"
}
}
}
}
}
}

精确和不精确查询有好几种方法,详见《elasticsearch 查询(match和term)》《finding_exact_values》

注意,es 的查询可以有很多的变种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 以下几个查询请求体在语义上等价

{
"query" : {
"match": {
"content": "我的宝马多少马力"
}
}
}

// 加入了嵌套的 content 和 query,content 就是 field name 的意思
{
"query": {
"match": {
"content" : {
"query" : "我的宝马多少马力"
}
}
}
}

// 在同层级里加入 filtered 和 filter
{
"query": {
"filtered": {
"filter": {
"range": {
"age": {
"gt": 30
}
}
},
"match": {
"content": "我的宝马多少马力"
}
}
}
}

// 使用 bool 子句进行查询
{
"query": {
"bool": {
"must": [
{
"match": {
"content": "我的宝马多少马力"
}
}
]
}
}
}

// 使用 term 来提供 query
{
"query": {
// 注意,即使一个 doc 的原始 content 是 我的宝马多少马力,它如果被 analyzed 过了,这个搜索可能还是搜不出来东西
"term": { "content":"我的宝马多少马力"}
}
}

//

{
"query": {
"terms": {
"content": [
"我的宝马多少马力"
]
}
}

elastic-search查询字句整理
elastic-search查询字句整理

search api 的搜索

参考《空查询》

倒排索引简析

倒排索引的 key 是 term,value 是文档的指针,可以参考这个《倒排索引》文档。

过滤机制

头机制

x-pack

version 机制

容量与分片

为何分片不宜过大?

  1. 增加索引读压力-不利于并行查询。
  2. 不利于集群扩缩容(分片迁移耗费较长时间)。
  3. 集群发生故障时,恢复时间较长。

类似的问题也会发生在 Redis 之类的架构方案里。

解决大分片的方法

  1. 索引按月、日进行分割。
  2. delete_by_query 方法删除索引对索引进行缩容。
  3. 模板增加主分片数量-存疑,通常只能增加副本数,而无法改变切片比例。
  4. 扩容数据节点。
  5. 减小单位分片大小。

一个可实操的方案:

  1. 修改写入逻辑和 mapping,让新写入的 doc 的 field 变少。
  2. 对老的索引进行 delete_by_query。

常用查询段

  • aggs
  • match
    • 等于分词以后的 or 查询,依赖于 analyzer。“hello world”如果被分词为 hello 和 world,则类似term = hello or term = wolrd
  • match_phrase
    • term 出现在一个句子中,句子包含 term,依赖于 analyzer。类似contains("hello world")
    • 这是 String.contains。
    • 不匹配 slop,还不如用 term。
  • page:
  • range:是大于等于小于的意思,不是 from 和 size
  • search_after
  • term
    • term 类似于 Collection.contains"[hello, world]".contains("hello")"[hello]".terms("hello") 都成立。
  • 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前缀和通配符查询等。内部结构如下:fst前缀树
  • 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
{"error":{"root_cause":[{"type":"cluster_block_exception","reason":"blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"}],"type":"cluster_block_exception","reason":"blocked by: [FORBIDDEN/12/index read-only / allow delete (api)];"},"status":403}

原始配置

1
2
3
4
5
6
7
8
9
10
# 配置文件
# 允许监控磁盘阈值
cluster.routing.allocation.disk.threshold_enabled: true

# 超过这两个阈值会触发 es 的写保护而且还需要触发分片迁移
cluster.routing.allocation.disk.watermark.high: "95%"
cluster.routing.allocation.disk.watermark.low: "93%"

# 设置这个文件才能备份某个索引
path.repo: ["/data/es/index_backup"]
1
2
# 原始启动命令
/usr/local/services/jdk8-1.0/bin/java -Xms3g -Xmx3g -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+AlwaysPreTouch -Xss1m -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djna.nosys=true -XX:-OmitStackTraceInFastThrow -Dio.netty.noUnsafe=true -Dio.netty.noKeySetOptimization=true -Dio.netty.recycler.maxCapacityPerThread=0 -Dlog4j.shutdownHookEnabled=false -Dlog4j2.disable.jmx=true -Djava.io.tmpdir=/tmp/elasticsearch.tJnDMs83 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=data -XX:ErrorFile=logs/hs_err_pid%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -Xloggc:logs/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=32 -XX:GCLogFileSize=64m -Des.path.home=/data/es/elasticsearch-6.5.3 -Des.path.conf=/data/es/elasticsearch-6.5.3/config -Des.distribution.flavor=default -Des.distribution.type=tar -cp /data/es/elasticsearch-6.5.3/lib/* org.elasticsearch.bootstrap.Elasticsearch -d

这个 -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
[2024-11-11T16:15:54,205][INFO ][o.e.c.r.a.AllocationService] [node-1] Cluster health status changed from [RED] to [YELLOW] (reason: [shards started [[leads][4]] ...]).

这时候集群是黄色的,意味着所有主分片都已分配,但有副本分片未被分配。数据可用,但容错性降低。

诊断命令

  • curl -X GET "http://9.147.246.82:9201/_cluster/health?pretty"
    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
    }
    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
2
3
4
5
6
7
8
9
10
# 将所有索引的只读删除状态解除
PUT /_all/_settings
{
"index.blocks.read_only_allow_delete": null
}

curl -X PUT "http://localhost:9200/_all/_settings" -H 'Content-Type: application/json' -d'
{
"index.blocks.read_only_allow_delete": null
}'

从 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 刷新 .kibana 索引
curl -X POST "http://localhost:9200/.kibana/_refresh"
# 打开 .kibana 索引
curl -X POST "http://localhost:9200/.kibana/_open"
# 获取 .kibana 索引的信息
curl -X GET "http://localhost:9200/_cat/indices/.kibana?v"
# 创建快照存储库
curl -X PUT "http://localhost:9200/_snapshot/backup_repository" -H 'Content-Type: application/json' -d'
{
"type": "fs",
"settings": {
"location": "/data/es/index_backup",
"compress": true
}
}'
# 创建 .kibana 索引的快照
curl -X PUT "http://localhost:9200/_snapshot/backup_repository/snapshot_1?wait_for_completion=true" -H 'Content-Type: application/json' -d'
{
"indices": ".kibana",
"ignore_unavailable": true,
"include_global_state": false
}'
# 删除 .kibana 索引,以后kibana会自己重建 .kibana_1 或者其他 alias
curl -X DELETE "http://localhost:9200/.kibana"

# 恢复备份的 .kibana 索引(如果有备份):
curl -X POST "http://localhost:9200/_snapshot/backup_repository/snapshot_1/_restore"
{
"indices": ".kibana",
"ignore_unavailable": false,
"include_global_state": false,
"rename_pattern": "(.+)",
"rename_replacement": "$1"
}

然后备份 kibana 的data 文件夹,在kibana的配置里加上日志:

1
logging.dest: /usr/local/services/kibana/kibana-6.5.3-linux-x86_64/log/kibana.log

然后执行kibana的命令:

1
2
# 注意看 node 的ps结果和日志的migration过程,不行就继续删除data、es索引并且 kill kibana 父子进程
bin/kibana