声明:本来是打算春节期间在官网学习,温故一下相关知识,。无意间发现铭毅天下 Elasticsearch文章很全,对快速了解一些知识点,很有帮助。尤其是知识星球里的内容,奈何收费。别人辛勤劳动成果,当然无可厚非。我就借鉴了他的知识图谱,确定自己的学习点,再结合官网文档和他的公众号。引用的地方已经标注,特此声明。
如果没钱没时间,收藏我这边篇笔记就好。如果你舍得花钱,有充足的时间,推荐去购买一下他的知识星球。
当字段类型为 text 的时候会进行分词,默认分词器是standard
两个地方会出现分词,一个是 indexing,一个是 search。文档索引的时候肯定会分词,search 时候针对 search 查询语句内容分析。默认的话是两者保持一致。某些场景下可以在search 中设置分词。
分词器分为三个部分:Tokenizers (分词)、Token filters(修改分词例如小写,删除分词,增加分词)、Character filters(用在分词前去除字符)
POST _analyze{"analyzer":"standard","text": "The quick brown fox. 1"}
GET kibana_sample_data_logs/_analyze{"field": "my_text","text": "Is this déjà vu?"}
PUT my-index-000001{"settings": {"analysis": {"analyzer": {"std_english": {"type": "standard","stopwords": "_english_"}}}},"mappings": {"properties": {"my_text": {"type": "text","analyzer": "standard","fields": {"english": {"type": "text","analyzer": "std_english"}}}}}}
测试
POST my-index-000001/_analyze{"field": "my_text","text": "The old brown cow"}// [ the, old, brown, cow ]POST my-index-000001/_analyze{"field": "my_text.english","text": "The old brown cow"}// [ old, brown, cow ]
为了避免 Mapping 爆炸,默认最多字段数index.mapping.total_fields.limit:1000
包含字段别名。也就意味着默认情况下,一个 index 最多 1000 个字段(Field and object)。
index.mapping.nested_fields.limit:50
nested_fields 默认 50。
Mapping 有Explicit mapping (显示)和Dynamic mapping (动态)。Mapping 可以定义 runtime field,runtime field 不会占用存储,增加获取数据的灵活能力,但是速度会慢(由 runtime script 决定性能影响)。
设置一个分片为 5,副本为 2,别名为 truman,timestamp 为时间类型,event 类型为 text 和 keyword,rawData 不支持检索。
PUT _template/truman_template{"index_patterns": ["truman-*"],"settings": {"number_of_shards": 5,"number_of_replicas": 2},"mappings": {"properties": {"timestamp":{"type": "date"},"event":{"type": "text","fields": {"keyword":{"type":"keyword","ignore_above" : 256}}},"rawData":{"type": "keyword","index": false}}},"aliases": {"truman": {}}}
默认为 true。针对 string 字段,会自动生成两个类型text
和keyword
Dynamic templates 允许通过一定的规则设置字段类型
PUT my-index-000001/{"mappings": {"dynamic_templates": [{"strings_as_ip": {"match_mapping_type": "string","match": "ip*","runtime": {"type": "ip"}}}]}}PUT my-index-000001{"mappings": {"dynamic_templates": [{"longs_as_strings": {"match_mapping_type": "string","match": "long_*","unmatch": "*_text","mapping": {"type": "long"}}}]}}
在 es 中可以定义字段为 join 类型,实现类似于关系型数据库的多表关联查询。但是 es 父子文档还是存储在一个 index 下的。
- 每个索引只允许一个 Join 类型 Mapping 定义;
- 父文档和子文档必须在同一个分片上编入索引;这意味着,当进行删除、更新、查找子文档时候需要提供相同的路由值。
- 一个文档可以有多个子文档,但只能有一个父文档。
- 可以为已经存在的 Join 类型添加新的关系。
- 当一个文档已经成为父文档后,可以为该文档添加子文档。
PUT knownledge{"mappings": {"properties": {"id":{"type": "keyword"},"my_join_field":{"type": "join","relations":{"question":["answer"]}}}}}
PUT knownledge/_doc/1?refresh{"id":"1","text":"What are the brands of computers?","my_join_field":{"name": "question"}}PUT knownledge/_doc/2?refresh{"id":"2","text":"What are the brands of mobile phones?","my_join_field":{"name": "question"}}PUT knownledge/_doc/3?routing=1&refresh{"id":"3","text":"dell","my_join_field":{"name": "answer","parent": "1"}}PUT knownledge/_doc/4?routing=2&refresh{"id":"4","text":"apple","my_join_field":{"name": "answer","parent": "2"}}PUT knownledge/_doc/5?routing=1&refresh{"id":"5","text":"神州电脑","my_join_field":{"name": "answer","parent": "1"}}
查询父 id 为 1 下的所有子文档。注意这里是父 id,我这边例子业务 id 于 index id 是一致的。其实可以不一致。
GET knownledge/_search{"query": {"parent_id":{"type": "answer","id": "1"}}}
查询符合父文档下的子文档
GET knownledge/_search{"query": {"has_parent": {"parent_type": "question","query": {"match": {"text": "computers"}}}}}
GET knownledge/_search{"query": {"has_child": {"type": "answer","query": {"match": {"text": "apple"}}}}}
Nested 为了解决数组对象被扁平化为一个简单的字段名称和值列表。
{"group" : "fans","user" : [{"first" : "John","last" : "Smith"},{"first" : "Alice","last" : "White"}]}
默认类型,上面数据会被 elasticsearch 解析为:
{"group" : "fans","user.first" : [ "alice", "john" ],"user.last" : [ "smith", "white" ]}
如果需要查询 user.first:John user.last:White 就会出错。
可以通过将该字段类型设置为 Nested 解决。
PUT nested-index-000001{"mappings": {"properties": {"user": {"type": "nested"}}}}
设置为 Nested 类型的查询必须在 Nested 下才能查到
GET nested-index-000001/_search{"query": {"nested": {"path": "user","query": {"bool": {"must": [{"match": {"user.first": "Alice"}},{"match": {"user.last": "White"}}]}}}}}
Nested 和 join 都可解决多表关联的场景,各有优缺点,想了解更多的,可以查看干货 | Elasticsearch 多表关联设计指南
如果想用别名写数据,同一个别名,只能有一个 index 是is_write_index:true
模板可以创建别名,当然也可以用过 api 创建
创建别名(前提是 index 存在)
PUT /truman-003/_alias/truman
创建别名(index 不存在)
PUT truman-003{"aliases": {"truman": {}}}
更新别名
POST /_aliases{"actions" : [{ "add" : { "index" : "truman-001", "alias" : "truman","is_write_index": false } },{ "add" : { "index" : "truman-002", "alias" : "truman","is_write_index": true } }]}
PUT /my-index-000001{"settings": {"number_of_shards": 1},"mappings": {"properties": {"field1": { "type": "text" }}},"aliases": {"my_index_aliases": {}}}// 只可更改动态参数 https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settingsPUT /my-index-000001/_settings{"index" : {"number_of_replicas" : 2}}GET /my-index-000001DELETE /my-index-000001
POST test-003/_doc{"name":"qqq","age":36}
GET test-003/_doc/gk5pz3cBlx8vUhtcU9IJ
PUT test-003/_doc/gk5pz3cBlx8vUhtcU9IJ{"name":"wwww","age":36}
DELETE test-003/_doc/gk5pz3cBlx8vUhtcU9IJ
第一步:客户端向集群某节点写入数据,发送请求。(如果没有指定路由/协调节点,请求的节点扮演协调节点的角色。)
第二步:协调节点接受到请求后,默认使用文档 ID 参与计算(也支持通过 routing),得到该文档属于哪个分片。随后请求会被转到另外的节点。
bash# 路由算法:根据文档id或路由计算目标的分片idshard = hash(document_id) % (num_of_primary_shards)
第三步:当分片所在的节点接收到来自协调节点的请求后,会将请求写入到 Memory Buffer,然后定时(默认是每隔 1 秒)写入到 Filesystem Cache,这个从 Memory Buffer 到 Filesystem Cache 的过程就叫做 refresh;
第四步:当然在某些情况下,存在 Memory Buffer 和 Filesystem Cache 的数据可能会丢失,ES 是通过 translog 的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到 translog 中,当 Filesystem cache 中的数据写入到磁盘中时,才会清除掉,这个过程叫做 flush;
第五步:在 flush 过程中,内存中的缓冲将被清除,内容被写入一个新段,段的 fsync 将创建一个新的提交点,并将内容刷新到磁盘,旧的 translog 将被删除并开始一个新的 translog。
第六步:flush 触发的时机是定时触发(默认 30 分钟)或者 translog 变得太大(默认为 512 M)时。
删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更。
磁盘上的每个段都有一个相应的 .del 文件。当删除请求发送后,文档并没有真的被删除,而是在 .del 文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在 .del 文件中被标记为删除的文档将不会被写入新段。
在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档在 .del 文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
在 bool 查询中,filter
和 must_not
属于 Filter Context,不会对算分结果产生影响;must
和 should
属于 Query Context,会对结果算分产生影响。
Filter Context 会使用缓存,因为速度会比 Query Context 快。如果不用考虑评分的话,优先考虑使用 Filter Context。
intervals 可以对匹配项的顺序和接近度进行细粒度控制
match
用于执行全文查询的标准查询,包括模糊匹配和短语或接近查询。
创建一个布尔查询,将与每个词匹配的词查询作为词查询,但最后一个词除外,后者作为前缀查询匹配。这里和 match_phrase_prefix 有点区别。前者不在意顺序,后者严格的词组顺序。
match_phrase/match_phrase_prefix
match_phrase 用于匹配精确短语或单词接近匹配。match_phrase_prefix 前缀短语匹配查询
multi_match
匹配查询的多字段版本。
GET /_search{"query": {"multi_match" : {"query": "Will Smith","fields": [ "title", "*_name" ]}}}
query_string 支持紧凑的 Lucene query string 语法,允许在单个查询字符串中指定 AND | OR | NOT 条件和多字段搜索。仅限于专业用户
simple_query_string 适用于直接向用户公开的 query_string 语法的更简单,更可靠的版本。
query_string 和 simple_query_string 区别在于,simple_query_string 提供语法容错。如果你的 search 很容易写错,避免告诉用户错误信息,推荐用这个。
和全文检索不同,Term-level 不会对查询语句分词。Term-leve 用作精确查找。
GET test/_search{"query": {"exists": {"field": "user"}}}
返回包含与搜索词相似的词的文档,以编辑距离测量
例如以下例子可以查询出user:truman
数据
GET test/_search{"query": {"fuzzy": {"user": {"value": "troman","fuzziness": "AUTO"}}}}
fuzziness: AUTO
表示 0-2 字符,必须完全匹配;3-5 字符,允许一个编辑距离;>5 字符允许两个编辑距离
可以批量根据_id 获取文档
GET test/_search{"query": {"ids" : {"values" : ["1", "xNT8w3cBZr0oc6pbkb3Q", "100"]}}}
避免 term 查询 text 类型的字段,对于 text 类型的字段,应该是 match 来查询。因为 text 类型会被分词器做一定处理,例如小写,删除某些字符。因此用 term 查询很可能查不出来。
terms 和 term 一样,不过允许查询多个值。
GET test/_search{"query": {"terms": {"user": [ "truman", "jim" ],"boost": 1.0}}}
terms_set 和 terms 类似,但是支持设置要求 minimum_should_match_field。
POST test/_doc{"user":"ddd","programming_languages": [ "c++", "php" ],"minimum_should_match":2}GET test/_search{"query": {"terms_set": {"programming_languages": {"terms": [ "c++", "java", "php" ],"minimum_should_match_field": "minimum_should_match"}}}}
通配符查找
GET /_search{"query": {"wildcard": {"user.id": {"value": "ki*y","boost": 1.0,"rewrite": "constant_score"}}}}
GET test/_search{"sort": [{"rawData": {"order": "desc"}}],"query": {"match": {"event": "has"}},"highlight": {"fields": {"event": {}}}}
from+size 深度分页性能较差
GET test/_search?from=0&size=5{"query": {"match_all": {}}}
scroll
GET test*/_search?scroll=2m&size=3{"query": {"match_all": {}}}
GET _search/scroll{"scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAcDFllrSTUzWlEzUUFtTnlGY3czWVJlRmcAAAAAAAAHAhZZa0k1M1pRM1FBbU55RmN3M1lSZUZnAAAAAAAABwEWWWtJNTNaUTNRQW1OeUZjdzNZUmVGZwAAAAAAAAcEFllrSTUzWlEzUUFtTnlGY3czWVJlRmcAAAAAAAAHBRZZa0k1M1pRM1FBbU55RmN3M1lSZUZn"}
query 关注的是文档是否包含,相关得分怎么样,得分越高的排名越靠前
filter 关注是查询是否包含在结果中,不涉及评分,有缓存,速度能更快。
query 在 search api 中 query 参数
filter 在 bool 查询中(filter,must_not),或者 filter 聚合中,constant_score 查询中
GET test/_search/template{"source":{"query": {"match": {"{{custom_field}}":"{{custom_value}}"}},"size":"{{custom_size}}"},"params" : {"custom_field" : "event","custom_value" : "has","custom_size" : 5}}
或者现将该模板储存,通过 id 查询
POST _scripts/my_search_template{"script": {"lang": "mustache","source":{"query": {"match": {"{{custom_field}}":"{{custom_value}}"}},"size":"{{custom_size}}"}}}GET test/_search/template{"id":"my_search_template","params" : {"custom_field" : "event","custom_value" : "has","custom_size" : 5}}
聚合分 3 大类:
这里涉及内容很多,推荐查看官网学习
标题是 pipeline,这里要说的是 Ingest 节点提供的能力,当数据量小的时候,可以通过 pipeline 实现修改字段名,修改字段值,新增字段等功能。它生效在文档能够被索引之前,这点要注意。
新建 pipeline
PUT _ingest/pipeline/add_timestap_pipeline{"description": "set timestap field ","processors": [{"set": {"field": "timestap","value": "{{_ingest.timestamp}}"}}]}
POST pipeline-index/_doc?pipeline=add_timestap_pipeline{"name":"truman","age":"19"}
PUT _ingest/pipeline/drop_document_pipeline{"description": "drop document ","processors": [{"drop": {"if": "ctx.name=='jim'"}}]}POST pipeline-index/_doc?pipeline=drop_document_pipeline{"name":"jim","age":"19"}
ILM 定义了四种解析阶段:
在不同的阶段可以做不行的动作:
不同动作作用:
推荐查看如下文章
或者原文Top 10 Elasticsearch Metrics to Monitor
查询集群健康情况
GET _cluster/health
{"cluster_name" : "docker-cluster","status" : "yellow","timed_out" : false,"number_of_nodes" : 1,"number_of_data_nodes" : 1,"active_primary_shards" : 41,"active_shards" : 41,"relocating_shards" : 0,"initializing_shards" : 0,"unassigned_shards" : 65,"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" : 38.67924528301887}
这里关注 status(集群的健康状态),relocating_shards(迁移分片),initializing_shards(初始分片),unassigned_shards(未分配分片)
索引级别的健康情况
GET /_cluster/health?level=indices&pretty
分片健康
GET /_cluster/health?level=shards&pretty
监控机器资源负载
GET /_cat/nodes?v&h=heap.percent,diskUsedPercent,cpu,load_1m,master,name
查看哪个索引是黄色或者红色
GET /_cat/indices?v&health=yellowGET /_cat/indices?v&health=red
查询集群状态不正常的原因
GET _cluster/allocation/explain
查询未分配原因
GET _cat/shards?v&h=index,shard,prirep,state,unassigned.reason
优雅下线节点
PUT /_cluster/settings{"transient": {"cluster.routing.allocation.exclude._ip": "192.168.0.110"}}
清除节点缓存
POST /_cache/clear
手动刷盘
POST /_flush
手动移动分片
POST /_cluster/reroute{"commands": [{"move": {"index": "test", "shard": 0,"from_node": "node1", "to_node": "node2"}},{"allocate_replica": {"index": "test", "shard": 1,"node": "node3"}}]}
index 迁移
POST _reindex{"source": {"index": "my-index-000001"},"dest": {"index": "my-new-index-000001"}}
部署层面:
1)最好是 64GB 内存的物理机器
3)尽量使用 SSD
4)避免集群跨越大的地理距离,一般一个集群的所有节点位于一个数据中心中。
5)设置堆内存:节点内存/2,不要超过 32GB。一般来说设置 export ES_HEAP_SIZE=32g 环境变量,比直接写-Xmx32g -Xms32g 更好一点。
6)关闭缓存 swap。
7)增加文件描述符,设置一个很大的值
8)不要随意修改垃圾回收器(CMS)和各个线程池的大小。
9)通过设置 gateway.recover_after_nodes、gateway.expected_nodes、gateway.recover_after_time 可以在集群重启的时候避免过多的分片交换,这可能会让数据恢复从数个小时缩短为几秒钟。
索引层面:
1)使用批量请求并调整其大小:每次批量数据 5–15 MB 大是个不错的起始点。
2)段合并:Elasticsearch 默认值是 20MB/s,对机械磁盘应该是个不错的设置。如果你用的是 SSD,可以考虑提高到 100-200MB/s。如果你在做批量导入,完全不在意搜索,你可以彻底关掉合并限流。另外还可以增加 index.translog.flush_threshold_size 设置,从默认的 512MB 到更大一些的值,比如 1GB,这可以在一次清空触发的时候在事务日志里积累出更大的段。
3)如果你的搜索结果不需要近实时的准确度,考虑把每个索引的 index.refresh_interval 改到 30s。
4)如果你在做大批量导入,考虑通过设置 index.number_of_replicas: 0 关闭副本。
5)需要大量拉取数据的场景,可以采用 scan & scroll api 来实现,而不是 from/size 一个大范围。
存储层面:
1)基于数据+时间滚动创建索引,每天递增数据。控制单个索引的量,一旦单个索引很大,存储等各种风险也随之而来,所以要提前考虑+及早避免。
2)冷热数据分离存储,热数据(比如最近 3 天或者一周的数据),其余为冷数据。对于冷数据不会再写入新数据,可以考虑定期 force_merge 加 shrink 压缩操作,节省存储空间和检索效率。
未搞明白,先占位。
详细信息参考图解 Elasticsearch 之一——索引创建过程
搜索分为两阶段过程,即 Query Then Fetch;
Query 阶段:
查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。每个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。PS:在搜索的时候是会查询 Filesystem Cache 的,但是有部分数据还在 Memory Buffer,所以搜索是近实时的。
每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
Fetch 阶段:
协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端。
详细信息参考Elasticsearch 存储深入详解