最近学习了一下Elasticsearch,学习过程简单记了一下笔记,其中有些内容是引用的Elasticsearch官方文档。
第1章 Elasticsearch 入门
1.1 Elasticsearch安装
1.1.1软件下载
下载地址:Download Elasticsearch | Elastic
elasticsearch-8.8.1-windows-x86_64.zip
1.1.2服务启动
注意:9300端口为Elasticsearch集群间组件的通讯端口,9200端口为浏览器访问的http协议RESTful端口。
启动的时候遇到的坑:
elasticsearch-8.8.1默认开启ssl,需要通过https访问,如需关闭ssl,修改elasticsearch.yml
#默认为true xpack.security.enabled: false #默认为true xpack.security.enrollment.enabled: false
修改后重新启动
1.2 基本操作
倒排索引(Inverted Index)是一种用于快速搜索的数据结构,常用于全文搜索引擎中。它将文档中的每个单词映射到包含该单词的文档列表,以便快速确定包含特定单词的文档。
倒排索引的主要思想是将文档集合中的每个单词(或术语)作为关键词,然后记录每个关键词出现的位置或出现频率,并将其与包含该关键词的文档相关联。倒排索引通常由两个主要部分组成:
-
词典(Dictionary):词典是存储所有唯一单词的数据结构,每个单词都有一个唯一的词项编号。词典通常使用树状结构(如B树或哈希表)进行组织,以便快速查找和插入。
-
倒排列表(Inverted List):对于每个单词,倒排列表记录了包含该单词的文档列表。每个文档条目包含文档的标识符和该单词在文档中的位置信息或其他相关信息。倒排列表可以使用数组、链表或其他数据结构来表示。
通过构建倒排索引,我们可以快速定位包含特定单词的文档。当执行搜索查询时,搜索引擎会查找查询词在倒排索引中对应的倒排列表,并从中获取相关的文档信息。倒排索引能够高效地支持关键词搜索、短语匹配、布尔逻辑查询等操作。
1.2.1 索引操作
1.2.1.1 创建索引
对比关系型数据库,创建索引就等同于创建数据库。
在Postman中,向ES服务器发送PUT请求:http://localhost:9200/shopping
响应:
{ "acknowledged": true, "shards_acknowledged": true, "index": "shopping" }
1.2.1.2 查询索引信息
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping
响应:
{ "shopping": { "aliases": {}, "mappings": { "properties": { "category": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "desc": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "images": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "price": { "type": "long" }, "title": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } } }, "settings": { "index": { "routing": { "allocation": { "include": { "_tier_preference": "data_content" } } }, "number_of_shards": "1", "provided_name": "shopping", "creation_date": "1687278417742", "number_of_replicas": "1", "uuid": "BJdkP3-iQUKuIfnks0yimg", "version": { "created": "8080199" } } } } }
1.2.1.3 查询所有索引
Postman发送GET请求请求地址 http://localhost:9200/_cat/indices?v
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size yellow open shopping2 76luTfE8SzSzQ124mwJ6_w 1 1 0 0 225b 225b yellow open shopping BJdkP3-iQUKuIfnks0yimg 1 1 5 0 31.2kb 31.2kb
1.2.1.4 删除索引
在Postman中,向ES服务器发送DELETE请求:http://localhost:9200/shopping
响应:
{ "acknowledged": true }
1.2.2 文档操作
文档可以类比为关系型数据库中的表数据,添加的数据格式为JSON格式。
1.2.2.1 创建文档
在Postman中,向ES服务器发送POST请求:http://localhost:9200/user/_doc
请求体:
{ "name": "倪卟懂", "sex": "男", "pass": "123456", "tel": "1111", "age": 26 }
响应体:
{ "_index": "user", "_id": "1008", "_version": 1, "result": "created", "_shards": { "total": 1, "successful": 1, "failed": 0 }, "_seq_no": 7, "_primary_term": 4 }
1.2.2.2 创建文档(指定ID)
在Postman中,向ES服务器发送POST请求:http://localhost:9200/user/_doc/1009
指定id为1006
请求体:
{ "name": "张无忌", "sex": "男", "pass": "123456", "tel": "1111", "age": 26 }
响应体:
{ "_index": "user", "_id": "1009", "_version": 1, "result": "created", "_shards": { "total": 1, "successful": 1, "failed": 0 }, "_seq_no": 9, "_primary_term": 4 }
1.2.2.3 查询文档
在Postman中,向ES服务器发送GET请求:http://localhost:9200/user/_doc/1009,即可查询到id为1009的文档
响应体:
{ "_index": "user", "_id": "1009", "_version": 1, "_seq_no": 9, "_primary_term": 4, "found": true, "_source": { "name": "张无忌", "sex": "男", "pass": "123456", "tel": "1111", "age": 26 } }
1.2.2.4 查询索引下的所有文档
在Postman中,向ES服务器发送GET请求:http://localhost:9200/user/_search,即可查询到shopping索引下的所有文档。
响应体:
{ "took": 473, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 2, "relation": "eq" }, "max_score": 1.0, "hits": [ { "_index": "user", "_id": "1008", "_score": 1.0, "_source": { "name": "倪卟懂", "sex": "男", "pass": "123456", "tel": "1111", "age": 26 } }, { "_index": "user", "_id": "1009", "_score": 1.0, "_source": { "name": "张无忌", "sex": "男", "pass": "123456", "tel": "1111", "age": 26 } } ] } }
1.2.2.5 查询索引下的所有文档(请求体)
在Postman中,向ES服务器发送GET请求:http://localhost:9200/user/_search,即可查询到shopping索引下的所有文档。
请求体:
{ "query": { "match_all": {} } }
响应体:
{ "took": 10, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 2, "relation": "eq" }, "max_score": 1.0, "hits": [ { "_index": "user", "_id": "1008", "_score": 1.0, "_source": { "name": "倪卟懂", "sex": "男", "pass": "123456", "tel": "1111", "age": 26 } }, { "_index": "user", "_id": "1009", "_score": 1.0, "_source": { "name": "张无忌", "sex": "男", "pass": "123456", "tel": "1111", "age": 26 } } ] } }
1.2.2.6 更新文档
1.2.2.6.1 全量更新
在Postman中,向ES服务器发送PUT请求:http://localhost:9200/user/_doc/1009。
请求内容为:
{ "name": "张无忌", "sex": "男", "pass": "123456", "tel": "1111", "age": 28 }
响应体:
{ "_index": "user", "_id": "1009", "_version": 2, "result": "updated", "_shards": { "total": 1, "successful": 1, "failed": 0 }, "_seq_no": 10, "_primary_term": 4 }
1.2.2.6.2 局部更新
在Postman中,向ES服务器发送POST请求:http://localhost:9200/user/_update/1009
请求内容为:
{ "doc": { "tel": "15328395263" } }
响应体:
{ "_index": "user", "_id": "1009", "_version": 3, "result": "updated", "_shards": { "total": 1, "successful": 1, "failed": 0 }, "_seq_no": 11, "_primary_term": 4 }
1.2.2.7 删除文档
在Postman中,向ES服务器发送DELETE请求:http://localhost:9200/user/_doc/1009
响应体:
{ "_index": "user", "_id": "1009", "_version": 4, "result": "deleted", "_shards": { "total": 1, "successful": 1, "failed": 0 }, "_seq_no": 12, "_primary_term": 4 }
1.2.2.8 条件查询(查询条件在请求URL上)
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search?q=category:thinkbook
响应数据:
{ "took": 212, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 0.13353139, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 0.13353139, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 0.13353139, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "1006", "_score": 0.13353139, "_source": { "title": "联想ThinkBook笔记本电脑", "category": "thinkbook", "images": "/images/test2.png", "price": 6000 } } ] } }
1.2.2.9 条件查询(请求体)
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
match是支持全文检索的,如果当前查询的字段类型支持分词,并且使用的是match,即可进行全文检索。
请求体:
{ "query": { "match": { "category": "thinkbook" } } }
响应体
{ "took": 6, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 0.13353139, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 0.13353139, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 0.13353139, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "1006", "_score": 0.13353139, "_source": { "title": "联想ThinkBook笔记本电脑", "category": "thinkbook", "images": "/images/test2.png", "price": 6000 } } ] } }
1.2.2.10 分页查询
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
请求体:
{ "query": { "match_all": {} }, "from": 0, "size": 2 }
响应体:
{ "took": 5, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 1.0, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } } ] } }
1.2.2.11 查询文档指定字段
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
请求体:
{ "query": { "match_all": {} }, "from": 0, "size": 2, "_source" : ["title", "category", "price"] }
响应体;
{ "took": 21, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 1.0, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkbook", "price": 6000 } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkbook", "price": 6000 } } ] } }
1.2.2.12 指定字段排序查询
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
请求体:
{ "query": { "match_all": {} }, "from": 0, "size": 2, "_source": [ "title", "category", "price" ], "sort": { "price": { "order": "desc" } } }
响应体:
{ "took": 64, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": null, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": null, "_source": { "title": "联想笔记本", "category": "thinkbook", "price": 6000 }, "sort": [ 6000 ] }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": null, "_source": { "title": "联想笔记本", "category": "thinkbook", "price": 6000 }, "sort": [ 6000 ] } ] } }
1.2.2.13 组合条件查询
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
must表示多个条件必须同时满足,相当于关系型数据库表查询中的and
should表示多个条件至少满足一个,相当于关系型数据库表查询中的or
请求体:
{ "query": { "bool": { "must": [ { "match": { "category": "thinkbook" } }, { "match": { "price": 6000 } } ] } } }
响应体:
{ "took": 10, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 1.1335313, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 1.1335313, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 1.1335313, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "1006", "_score": 1.1335313, "_source": { "title": "联想ThinkBook笔记本电脑", "category": "thinkbook", "images": "/images/test2.png", "price": 6000 } } ] } }
1.2.2.14 范围查询
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
请求体:
{ "query": { "bool": { "must": [ { "match": { "category": "thinkbook" } }, { "match": { "price": 6000 } } ], "filter": { "range": { "price": { "gt": 5000, "lt": 8000 } } } } } }
响应体:
{ "took": 5, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 1.5389965, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 1.5389965, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 1.5389965, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "1006", "_score": 1.5389965, "_source": { "title": "联想ThinkBook笔记本电脑", "category": "thinkbook", "images": "/images/test2.png", "price": 6000 } } ] } }
1.2.2.15 指定字段精准匹配查询
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
请求体:
{ "query": { "match_phrase": { "category": "thinkbook" } } }
响应体:
{ "took": 5, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 0.53899646, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 0.53899646, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 0.53899646, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "1006", "_score": 0.53899646, "_source": { "title": "联想ThinkBook笔记本电脑", "category": "thinkbook", "images": "/images/test2.png", "price": 6000 } } ] } }
1.2.2.16 高亮显示匹配上的字段
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
请求体:
{ "query": { "match_phrase": { "category": "thinkbook" } }, "highlight": { "fields": { "category": {} } } }
响应体:
{ "took": 84, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 3, "relation": "eq" }, "max_score": 0.53899646, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 0.53899646, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 }, "highlight": { "category": [ "<em>thinkbook</em>" ] } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 0.53899646, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 }, "highlight": { "category": [ "<em>thinkbook</em>" ] } }, { "_index": "shopping", "_id": "1006", "_score": 0.53899646, "_source": { "title": "联想ThinkBook笔记本电脑", "category": "thinkbook", "images": "/images/test2.png", "price": 6000 }, "highlight": { "category": [ "<em>thinkbook</em>" ] } } ] } }
1.2.2.17 聚合查询
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
-
aggs:聚合操作
-
test_group:分组名称,自定义
-
terms:分组
-
field:分组字段
这样查询机会查询出分组数据,也会查询出原始文档数据。如果只想查询分组统计数据,可以增加size字段,值为0即可。
{ "aggs": { "test_group": { "terms": { "field": "price" } } }, "size": 0 }
请求体:
{ "aggs": { "test_group": { "terms": { "field": "price" } } } }
响应体:
{ "took": 69, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 5, "relation": "eq" }, "max_score": 1.0, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "1006", "_score": 1.0, "_source": { "title": "联想ThinkBook笔记本电脑", "category": "thinkbook", "images": "/images/test2.png", "price": 6000 } }, { "_index": "shopping", "_id": "-0Ws3YgBPIsY3zd6-Tir", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkpad", "images": "/images/test666.png", "price": 9000 } }, { "_index": "shopping", "_id": "_EWt3YgBPIsY3zd6JDid", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkpad", "images": "/images/test555.png", "price": 8000 } } ] }, "aggregations": { "test_group": { "doc_count_error_upper_bound": 0, "sum_other_doc_count": 0, "buckets": [ { "key": 6000, "doc_count": 3 }, { "key": 8000, "doc_count": 1 }, { "key": 9000, "doc_count": 1 } ] } } }
1.2.2.18 统计平均值
在Postman中,向ES服务器发送GET请求:http://localhost:9200/shopping/_search
-
aggs:聚合操作
-
test_avg_group:分组名称,自定义
-
avg:分组
-
field:求平均值的字段
这样查询机会查询出分组数据,也会查询出原始文档数据。如果只想查询分组统计数据,可以增加size字段,值为0即可。
请求体:
{ "aggs": { "test_avg_group": { "avg": { "field": "price" } } } }
响应体:
{ "took": 21, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 5, "relation": "eq" }, "max_score": 1.0, "hits": [ { "_index": "shopping", "_id": "2lei2YgB5UEN_4BCJ-L9", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "W41O24gBi5-8oz1SUwWo", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkbook", "images": "/images/test.png", "price": 6000 } }, { "_index": "shopping", "_id": "1006", "_score": 1.0, "_source": { "title": "联想ThinkBook笔记本电脑", "category": "thinkbook", "images": "/images/test2.png", "price": 6000 } }, { "_index": "shopping", "_id": "-0Ws3YgBPIsY3zd6-Tir", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkpad", "images": "/images/test666.png", "price": 9000 } }, { "_index": "shopping", "_id": "_EWt3YgBPIsY3zd6JDid", "_score": 1.0, "_source": { "title": "联想笔记本", "category": "thinkpad", "images": "/images/test555.png", "price": 8000 } } ] }, "aggregations": { "test_avg_group": { "value": 7000.0 } } }
1.2.2.19 字段映射关系
创建索引的时候可以指定未来要存储在索引内的文档的字段类型,字段类型也决定了查询方式。
-
Text:用于存储长文本或字符串。默认会进行分词处理,适用于全文搜索。
-
Keyword:用于存储短文本或字符串。不进行分词处理,适用于精确匹配或聚合操作。
-
Numeric:包括整数和浮点数。可以进一步细分为:
-
Integer:存储整数。
-
Long:存储长整数。
-
Short:存储短整数。
-
Byte:存储字节。
-
Double:存储双精度浮点数。
-
Float:存储单精度浮点数。
-
Half_float:存储半精度浮点数。
-
Scaled_float:根据指定的缩放因子存储浮点数。
-
-
Date:存储日期和时间。可以使用不同的格式进行存储和解析。
-
Boolean:存储布尔值(true 或 false)。
-
Binary:存储二进制数据,如图像、音频、视频等。
-
Object:存储复杂的结构化数据,可以包含多个字段。
-
Nested:存储嵌套的对象,每个对象都作为一个独立的文档进行索引。
除了上述常见的数据类型,Elasticsearch 还提供了其他一些特殊的数据类型,如地理位置类型(Geo Point)、IP 地址类型(IP)和完成自定义的脚本类型(Script)。此外,还可以通过自定义插件或使用 Elasticsearch 的动态映射功能定义自定义的数据类型。
1.2.2.19.1 创建索引
在Postman中,向ES服务器发送PUT请求:http://localhost:9200/user
响应内容:
{ "acknowledged": true, "shards_acknowledged": true, "index": "user" }
1.2.2.19.2 创建索引属性
在Postman中,向ES服务器发送PUT请求:http://localhost:9200/user/_mapping
请求体:
{ "properties": { "name": { "type": "text", "index": true }, "sex": { "type": "keyword", "index": true }, "pass": { "type": "keyword", "index": false } } }
响应体:
{ "acknowledged": true }
1.2.2.19.3 创建文档
在Postman中,向ES服务器发送POST请求:http://localhost:9200/user/_doc
请求体:
{ "name": "倪卟懂", "sex": "男人", "pass": "123456" }
响应体:
{ "_index": "user", "_id": "_UXT3ogBPIsY3zd6lTh8", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 }, "_seq_no": 0, "_primary_term": 1 }
1.2.2.19.4 文档查询01
在Postman中,向ES服务器发送GET请求:http://localhost:9200/user/_search
说明:由于name字段类型为text,会对存储的内容进行分词,因此可以通过单个字查询到记录。
请求体:
{ "query":{ "match":{ "name" : "倪" } } }
响应体:
{ "took": 398, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 1, "relation": "eq" }, "max_score": 0.5754429, "hits": [ { "_index": "user", "_id": "_UXT3ogBPIsY3zd6lTh8", "_score": 0.5754429, "_source": { "name": "倪卟懂", "sex": "男人", "pass": "123456" } } ] } }
1.2.2.19.5 文档查询02
在Postman中,向ES服务器发送GET请求:http://localhost:9200/user/_search
说明:由于sex字段类型为keyword,不会对存储的内容进行分词,必须完全匹配才能查到数据。
请求体:
{ "query":{ "match":{ "sex" : "男" } } }
响应体:
{ "took": 3, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 0, "relation": "eq" }, "max_score": null, "hits": [] } }
1.2.2.19.6 文档查询03
在Postman中,向ES服务器发送GET请求:http://localhost:9200/user/_search
说明:由于pass字段的index设置的是false,不能被索引,因此无法通过此字段进行查询。
TODO:自己在测试过程中设置了不能被索引的字段依旧可以查询
请求体:
{ "query":{ "match":{ "pass" : "123456" } } }
响应体:
{ "took": 4, "timed_out": false, "_shards": { "total": 1, "successful": 1, "skipped": 0, "failed": 0 }, "hits": { "total": { "value": 1, "relation": "eq" }, "max_score": 1.0, "hits": [ { "_index": "user", "_id": "1008", "_score": 1.0, "_source": { "name": "倪卟懂", "sex": "男的", "pass": "123456", "tel": "1111" } } ] } }
1.3 API操作
1.3.1 添加依赖
添加的依赖版本需要由当前Elasticsearch版本决定,当前环境的Elasticsearch版本为8.8.1,以来如下。
<dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>8.8.1</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>8.8.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.20.0</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.20.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.3</version> </dependency>
1.3.2 索引操作
1.3.2 创建索引
-
number_of_shards:它指定了索引被分成的主分片数。分片是将索引的数据分散存储在集群中的不同节点上的方式之一。主分片的数量在索引创建后就无法更改。主分片数的选择对于索引的性能和可伸缩性非常重要。
-
较低的主分片数(例如1或2)可能导致索引数据存储在较少的节点上,限制了索引的并行性能。但是,较少的主分片数可以降低分布式索引的复杂性,特别是对于较小的数据集或较小的集群。
-
较高的主分片数(例如10或更多)可以提高索引的并行性能和吞吐量,因为数据分散在更多的节点上。然而,较高的主分片数可能会增加集群的负载和复杂性,并且需要更多的存储空间。
因此,在选择主分片数时,需要考虑索引的大小、数据量、查询负载、集群的规模和硬件资源等因素。
-
-
number_of_replicas:它指定了每个主分片的副本数。副本是主分片的复制,用于提供冗余和高可用性。副本的数量可以在索引创建后动态调整。
-
增加副本数可以提高读取操作的并发性能和可用性,因为数据可以从多个副本中获取。然而,每个副本都需要额外的存储空间和处理能力,因此会增加集群的负载。
-
减少副本数可以节省存储空间和减轻集群的负载,但会降低读取操作的冗余和可用性。
通常,建议将副本数设置为大于等于2,以确保有足够的冗余和可用性。但是,具体的设置取决于集群的规模、可用资源和对性能和可用性的需求。
-
import org.apache.http.HttpHost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import java.io.IOException; public class CreateIndex { public static void main(String[] args) throws IOException { RestClient client = getClient("localhost", 9200, "http"); createIndex(client, "my_index"); closeClient(client); } private static RestClient getClient(String host, Integer port, String scheme) { RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)); RestClient restClient = builder.build(); return restClient; } private static void createIndex(RestClient restClient, String indexName) throws IOException { System.out.println("准备创建索引:" +indexName); // 创建索引请求 Request request = new Request("PUT", "/" + indexName); request.setJsonEntity("{\"settings\":{\"number_of_shards\":1,\"number_of_replicas\":0}}"); // 发送请求 Response response = restClient.performRequest(request); // 处理响应 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("Response Status Code: " + statusCode); System.out.println("创建索引结束:" +indexName); } private static void closeClient(RestClient restClient) throws IOException { System.out.println("关闭请求客户端"); restClient.close(); } }
1.3.3 查询索引
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import org.apache.http.HttpHost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class QueryIndex { public static void main(String[] args) throws IOException { RestClient client = getClient("localhost", 9200, "http"); String myIndex = queryIndex(client, "my_index"); System.out.println(myIndex); closeClient(client); } private static RestClient getClient(String host, Integer port, String scheme) { RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)); RestClient restClient = builder.build(); return restClient; } private static String queryIndex(RestClient restClient, String indexName) throws IOException { // 创建索引请求 Request request = new Request("GET", "/" + indexName); // 发送请求 Response response = restClient.performRequest(request); // 处理响应 String result = convertInputStreamToString(response.getEntity().getContent()); result = JSON.toJSONString(JSONObject.parseObject(result), SerializerFeature.PrettyFormat); return result; } private static String convertInputStreamToString(InputStream inputStream) { StringBuilder stringBuilder = new StringBuilder(); BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { stringBuilder.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { // 关闭流 if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return stringBuilder.toString(); } private static void closeClient(RestClient restClient) throws IOException { System.out.println("关闭请求客户端"); restClient.close(); } }
1.3.4 删除索引
private static void deleteIndex(RestClient restClient, String indexName) throws IOException { // 创建索引请求 Request request = new Request("DELETE", "/" + indexName); // 发送请求 Response response = restClient.performRequest(request); }
1.3.3 文档操作
1.3.3.1 创建文档
import com.alibaba.fastjson.JSON; import com.nibudon.elasticsearch.vo.DocVo; import org.apache.http.HttpHost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import java.io.*; public class CreateDoc { public static void main(String[] args) throws IOException { RestClient client = getClient("localhost", 9200, "http"); DocVo vo = new DocVo("王木木", "女", "123456", "15328395632"); String result = createDoc(client, "my_index", vo); System.out.println(result); client.close(); } private static RestClient getClient(String host, Integer port, String scheme) { RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)); RestClient restClient = builder.build(); return restClient; } public static String createDoc(RestClient restClient, String indexName, DocVo vo) throws IOException { System.out.println("准备创建文档:" + indexName); // 创建索引请求 Request request = new Request("POST", "/" + indexName + "/_doc"); String doc = JSON.toJSONString(vo); System.out.println("创建的文档内容:" + doc); request.setJsonEntity(doc); // 发送请求 Response response = restClient.performRequest(request); // 处理响应 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("Response Status Code: " + statusCode); System.out.println("创建文档结束:" + indexName); return convertInputStreamToString(response.getEntity().getContent()); } private static String convertInputStreamToString(InputStream inputStream) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } }
1.3.3.2 查询文档
1.3.3.2.1 查询所有文档
import org.apache.http.HttpHost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.SortOrder; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class QueryAll { public static void main(String[] args) throws IOException { RestClient client = getClient("localhost", 9200, "http"); String result = queryAll(client, "user"); System.out.println(result); client.close(); } private static RestClient getClient(String host, Integer port, String scheme) { RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)); RestClient restClient = builder.build(); return restClient; } public static String queryAll(RestClient restClient, String indexName) throws IOException { // 创建索引请求 Request request = new Request("GET", "/" + indexName + "/_search"); String condition = getQueryCondition(); System.out.println("查询条件:" + condition); request.setJsonEntity(condition); // 发送请求 Response response = restClient.performRequest(request); // 处理响应 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("Response Status Code: " + statusCode); return convertInputStreamToString(response.getEntity().getContent()); } public static String getQueryCondition() throws IOException { // 创建 SearchSourceBuilder 对象 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //查询所有数据 MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); // 设置排序,降序 sourceBuilder.sort("age", SortOrder.DESC); // 设置分页,分页规则,pageIndex从1开始,from=(pageIndex-1)*pageSize size=pageSize sourceBuilder.from(0); sourceBuilder.size(3); //需要查询的字段 String sources[] = {"name", "sex", "tel", "age"}; //需要排除掉的字段 String exSources[] = {}; sourceBuilder.fetchSource(sources, exSources); sourceBuilder.query(matchAllQueryBuilder); String string = sourceBuilder.toString(); return string; } private static void test() { BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); boolQuery.must(QueryBuilders.matchQuery("field1", "value1")); boolQuery.must(QueryBuilders.matchQuery("field2", "value2")); boolQuery.must(QueryBuilders.matchQuery("field3", "value3")); boolQuery.must(QueryBuilders.rangeQuery("field4").gte(10).lte(100)); boolQuery.mustNot(QueryBuilders.termQuery("field5", "value5")); System.out.println(boolQuery.toString()); } private static String convertInputStreamToString(InputStream inputStream) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } }
1.3.3.2.2 组合条件查询(AND)
import com.nibudon.elasticsearch.vo.User; import org.apache.http.HttpHost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.SortOrder; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class QueryByConditions { public static void main(String[] args) throws IOException { RestClient client = getClient("localhost", 9200, "http"); User user = new User(); user.setName("张"); user.setSex("男"); String result = queryByConditions(client, "user", user); System.out.println(result); client.close(); } private static RestClient getClient(String host, Integer port, String scheme) { RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)); RestClient restClient = builder.build(); return restClient; } public static String queryByConditions(RestClient restClient, String indexName, User user) throws IOException { // 创建索引请求 Request request = new Request("GET", "/" + indexName + "/_search"); String condition = getQueryCondition(user); System.out.println("查询条件:" + condition); request.setJsonEntity(condition); // 发送请求 Response response = restClient.performRequest(request); // 处理响应 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("Response Status Code: " + statusCode); return convertInputStreamToString(response.getEntity().getContent()); } public static String getQueryCondition(User user) throws IOException { // 创建 SearchSourceBuilder 对象 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //设置查询条件 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); if (user.getName() != null) { boolQueryBuilder.must(QueryBuilders.matchQuery("name", user.getName())); } if (user.getSex() != null) { boolQueryBuilder.must(QueryBuilders.matchQuery("sex", user.getSex())); } // 设置排序,降序 sourceBuilder.sort("age", SortOrder.DESC); // 设置分页,分页规则,pageIndex从1开始,from=(pageIndex-1)*pageSize size=pageSize sourceBuilder.from(0); sourceBuilder.size(3); //需要查询的字段 String sources[] = {"name", "sex", "tel", "age"}; //需要排除掉的字段 String exSources[] = {}; sourceBuilder.fetchSource(sources, exSources); sourceBuilder.query(boolQueryBuilder); String string = sourceBuilder.toString(); return string; } private static void test() { BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); boolQuery.must(QueryBuilders.matchQuery("field1", "value1")); boolQuery.must(QueryBuilders.matchQuery("field2", "value2")); boolQuery.must(QueryBuilders.matchQuery("field3", "value3")); boolQuery.must(QueryBuilders.rangeQuery("field4").gte(10).lte(100)); boolQuery.mustNot(QueryBuilders.termQuery("field5", "value5")); System.out.println(boolQuery.toString()); } private static String convertInputStreamToString(InputStream inputStream) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } }
1.3.3.2.3 组合条件查询(OR)
import com.nibudon.elasticsearch.vo.User; import org.apache.http.HttpHost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.sort.SortOrder; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class QueryByOrConditions { public static void main(String[] args) throws IOException { RestClient client = getClient("localhost", 9200, "http"); User user = new User(); user.setName("张"); user.setSex("女"); String result = queryByConditions(client, "user", user); System.out.println(result); client.close(); } private static RestClient getClient(String host, Integer port, String scheme) { RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)); RestClient restClient = builder.build(); return restClient; } public static String queryByConditions(RestClient restClient, String indexName, User user) throws IOException { // 创建索引请求 Request request = new Request("GET", "/" + indexName + "/_search"); String condition = getQueryCondition(user); System.out.println("查询条件:" + condition); request.setJsonEntity(condition); // 发送请求 Response response = restClient.performRequest(request); // 处理响应 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("Response Status Code: " + statusCode); return convertInputStreamToString(response.getEntity().getContent()); } public static String getQueryCondition(User user) throws IOException { // 创建 SearchSourceBuilder 对象 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //设置查询条件 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); if (user.getName() != null) { boolQueryBuilder.should(QueryBuilders.matchQuery("name", user.getName())); } if (user.getSex() != null) { boolQueryBuilder.should(QueryBuilders.matchQuery("sex", user.getSex())); } // 设置排序,降序 sourceBuilder.sort("age", SortOrder.DESC); // 设置分页,分页规则,pageIndex从1开始,from=(pageIndex-1)*pageSize size=pageSize sourceBuilder.from(0); sourceBuilder.size(3); //需要查询的字段 String sources[] = {"name", "sex", "tel", "age"}; //需要排除掉的字段 String exSources[] = {}; sourceBuilder.fetchSource(sources, exSources); sourceBuilder.query(boolQueryBuilder); String string = sourceBuilder.toString(); return string; } private static void test() { BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); boolQuery.must(QueryBuilders.matchQuery("field1", "value1")); boolQuery.must(QueryBuilders.matchQuery("field2", "value2")); boolQuery.must(QueryBuilders.matchQuery("field3", "value3")); boolQuery.must(QueryBuilders.rangeQuery("field4").gte(10).lte(100)); boolQuery.mustNot(QueryBuilders.termQuery("field5", "value5")); System.out.println(boolQuery.toString()); } private static String convertInputStreamToString(InputStream inputStream) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } }
1.3.3.2.4 聚合查询
import org.apache.http.HttpHost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class QueryByGroup { public static void main(String[] args) throws IOException { RestClient client = getClient("localhost", 9200, "http"); String result = queryByGroup(client, "user"); System.out.println(result); client.close(); } private static RestClient getClient(String host, Integer port, String scheme) { RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)); RestClient restClient = builder.build(); return restClient; } public static String queryByGroup(RestClient restClient, String indexName) throws IOException { // 创建索引请求 Request request = new Request("GET", "/" + indexName + "/_search"); String condition = getQueryCondition(); System.out.println("查询条件:" + condition); request.setJsonEntity(condition); // 发送请求 Response response = restClient.performRequest(request); // 处理响应 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("Response Status Code: " + statusCode); return convertInputStreamToString(response.getEntity().getContent()); } public static String getQueryCondition() throws IOException { // 创建 SearchSourceBuilder 对象 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //查询所有数据 MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); sourceBuilder.query(matchAllQueryBuilder); //聚合查询 sourceBuilder.aggregation(AggregationBuilders.terms("sex_group").field("sex")); String string = sourceBuilder.toString(); return string; } private static void test() { BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); boolQuery.must(QueryBuilders.matchQuery("field1", "value1")); boolQuery.must(QueryBuilders.matchQuery("field2", "value2")); boolQuery.must(QueryBuilders.matchQuery("field3", "value3")); boolQuery.must(QueryBuilders.rangeQuery("field4").gte(10).lte(100)); boolQuery.mustNot(QueryBuilders.termQuery("field5", "value5")); System.out.println(boolQuery.toString()); } private static String convertInputStreamToString(InputStream inputStream) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } }
1.3.3.2.5 平均值查询
import org.apache.http.HttpHost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.MatchAllQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.builder.SearchSourceBuilder; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class QueryByAvg { public static void main(String[] args) throws IOException { RestClient client = getClient("localhost", 9200, "http"); String result = queryByAvg(client, "user"); System.out.println(result); client.close(); } private static RestClient getClient(String host, Integer port, String scheme) { RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)); RestClient restClient = builder.build(); return restClient; } public static String queryByAvg(RestClient restClient, String indexName) throws IOException { // 创建索引请求 Request request = new Request("GET", "/" + indexName + "/_search"); String condition = getQueryCondition(); System.out.println("查询条件:" + condition); request.setJsonEntity(condition); // 发送请求 Response response = restClient.performRequest(request); // 处理响应 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("Response Status Code: " + statusCode); return convertInputStreamToString(response.getEntity().getContent()); } public static String getQueryCondition() throws IOException { // 创建 SearchSourceBuilder 对象 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); //查询所有数据 MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery(); sourceBuilder.query(matchAllQueryBuilder); //聚合查询 sourceBuilder.aggregation(AggregationBuilders.avg("age_group").field("age")); String string = sourceBuilder.toString(); return string; } private static void test() { BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); boolQuery.must(QueryBuilders.matchQuery("field1", "value1")); boolQuery.must(QueryBuilders.matchQuery("field2", "value2")); boolQuery.must(QueryBuilders.matchQuery("field3", "value3")); boolQuery.must(QueryBuilders.rangeQuery("field4").gte(10).lte(100)); boolQuery.mustNot(QueryBuilders.termQuery("field5", "value5")); System.out.println(boolQuery.toString()); } private static String convertInputStreamToString(InputStream inputStream) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } }
1.3.3.3 更新文档
import com.alibaba.fastjson.JSON; import com.nibudon.elasticsearch.vo.User; import org.apache.http.HttpHost; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; public class UpdateDoc { public static void main(String[] args) throws IOException { RestClient client = getClient("localhost", 9200, "http"); User vo = new User(); vo.setTel("153283666666"); vo.setAge(48); String result = updateDoc(client, "user", vo, "BaHT44gBt1zaMUrGN9A1"); System.out.println(result); client.close(); } private static RestClient getClient(String host, Integer port, String scheme) { RestClientBuilder builder = RestClient.builder(new HttpHost(host, port, scheme)); RestClient restClient = builder.build(); return restClient; } public static String updateDoc(RestClient restClient, String indexName, User vo, String id) throws IOException { System.out.println("准备更新文档:" + indexName); // 创建索引请求 Request request = new Request("POST", "/" + indexName + "/_update/" + id); Map updateMap = new HashMap(); updateMap.put("doc", vo); String doc = JSON.toJSONString(updateMap); System.out.println("更新的文档内容:" + doc); request.setJsonEntity(doc); // 发送请求 Response response = restClient.performRequest(request); // 处理响应 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("Response Status Code: " + statusCode); System.out.println("更新文档结束:" + indexName); return convertInputStreamToString(response.getEntity().getContent()); } private static String convertInputStreamToString(InputStream inputStream) throws IOException { StringBuilder sb = new StringBuilder(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } }
1.4 集群环境
Elasticsearch集群是由多个Elasticsearch节点组成的分布式系统,它用于存储和处理大量数据。集群提供高可用性、可扩展性和容错性,以满足处理大规模数据的需求。
在Elasticsearch集群中,有两种类型的节点:
-
主节点(Master Node):主节点负责管理集群的整体状态和元数据,包括索引的创建、删除和分片分配等操作。每个集群只有一个主节点,当主节点不可用时,会自动选举一个新的主节点。
-
数据节点(Data Node):数据节点存储实际的数据,并执行搜索和分析操作。数据节点负责处理索引的读写请求,并负责将数据分片存储在自己的节点上。
在搭建Elasticsearch集群时,你需要进行以下步骤:
-
安装Elasticsearch:在每个节点上安装Elasticsearch软件。可以从Elasticsearch官网下载适合你操作系统的安装包,并按照官方文档的指导进行安装。
-
配置节点:对于每个节点,你需要编辑Elasticsearch的配置文件,指定节点的角色和集群相关的配置。主节点需要设置
node.master: true
,数据节点需要设置node.data: true
。另外,你需要为集群指定一个唯一的名称,通过cluster.name
参数进行配置。 -
配置网络通信:确保集群中的节点可以互相通信。节点之间的通信依赖于网络设置,确保节点能够相互发现和连接。
-
启动节点:在每个节点上启动Elasticsearch服务。启动后,节点会自动加入到集群中,并开始与其他节点进行通信。
-
验证集群状态:使用Elasticsearch提供的API或命令行工具,验证集群的状态是否正常。你可以检查主节点是否选举成功,数据节点是否加入到集群中,并确保数据分片在各个节点上均匀分布。
-
数据索引和搜索:一旦集群建立成功,你可以使用Elasticsearch的API或客户端库来进行数据的索引和搜索操作。根据你的需求,可以进行数据分片、备份、聚合等操作。
ElasticSearch 的主旨是随时可用和按需扩容。 而扩容可以通过购买性能更强大( 垂直扩容 ,或 纵向扩容 ) 或者数量更多的服务器( 水平扩容 ,或 横向扩容 )来实现。
虽然 Elasticsearch 可以获益于更强大的硬件设备,但是垂直扩容是有极限的。 真正的扩容能力是来自于水平扩容—为集群添加更多的节点,并且将负载压力和稳定性分散到这些节点中。
对于大多数的数据库而言,通常需要对应用程序进行非常大的改动,才能利用上横向扩容的新增资源。 与之相反的是,ElastiSearch天生就是 分布式的 ,它知道如何通过管理多节点来提高扩容性和可用性。 这也意味着你的应用无需关注这个问题。
1.4.1 空集群
如果我们启动了一个单独的节点,里面不包含任何的数据和索引,那我们的集群看起来就是一个包含空内容节点的集群。
一个运行中的 Elasticsearch 实例称为一个节点,而集群是由一个或者多个拥有相同
cluster.name
配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。当一个节点被选举成为 主 节点时, 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点。我们的示例集群就只有一个节点,所以它同时也成为了主节点。
作为用户,我们可以将请求发送到 集群中的任何节点 ,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。
1.4.2 集群健康状态查看
Elasticsearch 的集群监控信息中包含了许多的统计数据,其中最为重要的一项就是 集群健康 , 它在 status
字段中展示为 green
、 yellow
或者 red
。
GET /_cluster/health
{ "cluster_name": "elasticsearch", "status": "green", "timed_out": false, "number_of_nodes": 1, "number_of_data_nodes": 1, "active_primary_shards": 0, "active_shards": 0, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 0 }
status
字段指示着当前集群在总体上是否工作正常。它的三种颜色含义如下:
green
所有的主分片和副本分片都正常运行。
yellow
所有的主分片都正常运行,但不是所有的副本分片都正常运行。
red
有主分片没能正常运行。
1.4.3 集群下创建索引
我们往 Elasticsearch 添加数据时需要用到 索引 —— 保存相关数据的地方。 索引实际上是指向一个或者多个物理 分片 的 逻辑命名空间 。
一个 分片 是一个底层的 工作单元 ,它仅保存了全部数据中的一部分。 在分片内部机制中,我们将详细介绍分片是如何工作的,而现在我们只需知道一个分片是一个 Lucene 的实例,以及它本身就是一个完整的搜索引擎。 我们的文档被存储和索引到分片内,但是应用程序是直接与索引而不是与分片进行交互。
Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。 当你的集群规模扩大或者缩小时, Elasticsearch 会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里。
一个分片可以是 主 分片或者 副本 分片。 索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量。
技术上来说,一个主分片最大能够存储 Integer.MAX_VALUE - 128 个文档,但是实际最大值还需要参考你的使用场景:包括你使用的硬件, 文档的大小和复杂程度,索引和查询文档的方式以及你期望的响应时长。
一个副本分片只是一个主分片的拷贝。副本分片作为硬件故障时保护数据不丢失的冗余备份,并为搜索和返回文档等读操作提供服务。
在索引建立的时候就已经确定了主分片数,但是副本分片数可以随时修改。
让我们在包含一个空节点的集群内创建名为
blogs
的索引。 索引在默认情况下会被分配5个主分片, 但是为了演示目的,我们将分配3个主分片和一份副本(每个主分片拥有一个副本分片):PUT /blogs { "settings" : { "number_of_shards" : 3, "number_of_replicas" : 1 } }我们的集群现在是拥有一个索引的单节点集群”。所有3个主分片都被分配在
Node 1
。
此时查看集群健康状态:
{ "cluster_name": "elasticsearch", "status": "yellow", "timed_out": false, "number_of_nodes": 1, "number_of_data_nodes": 1, "active_primary_shards": 3, "active_shards": 3, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 3, "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": 50 }
集群
status
值为yellow
。没有被分配到任何节点的副本数。
集群的健康状况为
yellow
则表示全部 主 分片都正常运行(集群可以正常服务所有请求),但是 副本 分片没有全部处在正常状态。 实际上,所有3个副本分片都是unassigned
—— 它们都没有被分配到任何节点。 在同一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点上的所有副本数据。当前我们的集群是正常运行的,但是在硬件故障时有丢失数据的风险。
1.4.4 集群故障转移
当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。
如果启动了第二个节点,我们的集群将会变成拥有两个节点的集群——所有主分片和副本分片都已被分配。
当第二个节点加入到集群后,3个 副本分片 将会分配到这个节点上——每个主分片对应一个副本分片。 这意味着当集群内任何一个节点出现问题时,我们的数据都完好无损。
所有新近被索引的文档都将会保存在主分片上,然后被并行的复制到对应的副本分片上。这就保证了我们既可以从主分片又可以从副本分片上获得文档。
cluster-health
现在展示的状态为green
,这表示所有6个分片(包括3个主分片和3个副本分片)都在正常运行。{ "cluster_name": "elasticsearch", "status": "green", "timed_out": false, "number_of_nodes": 2, "number_of_data_nodes": 2, "active_primary_shards": 3, "active_shards": 6, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 0, "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": 100 }
集群
status
值为green
。我们的集群现在不仅仅是正常运行的,并且还处于 始终可用 的状态。
1.4.5 集群水平扩容
当启动了第三个节点,我们的集群将会变成拥有三个节点的集群——为了分散负载而对分片进行重新分配
Node 1
和Node 2
上各有一个分片被迁移到了新的Node 3
节点,现在每个节点上都拥有2个分片,而不是之前的3个。 这表示每个节点的硬件资源(CPU, RAM, I/O)将被更少的分片所共享,每个分片的性能将会得到提升。分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。 我们这个拥有6个分片(3个主分片和3个副本分片)的索引可以最大扩容到6个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源。
主分片的数目在索引创建时就已经确定了下来。实际上,这个数目定义了这个索引能够 存储 的最大数据量。(实际大小取决于你的数据、硬件和使用场景。) 但是,读操作——搜索和返回数据——可以同时被主分片 或 副本分片所处理,所以当你拥有越多的副本分片时,也将拥有越高的吞吐量。
在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把副本数从默认的
1
增加到2
:PUT /blogs/_settings { "number_of_replicas" : 2 }如果将将参数
number_of_replicas
调大到 2,blogs
索引现在拥有9个分片:3个主分片和6个副本分片。 这意味着我们可以将集群扩容到9个节点,每个节点上一个分片。相比原来3个节点时,集群搜索性能可以提升 3 倍。
当然,如果只是在相同节点数目的集群上增加更多的副本分片并不能提高性能,因为每个分片从节点上获得的资源会变少。 你需要增加更多的硬件资源来提升吞吐量。
但是更多的副本分片数提高了数据冗余量:按照上面的节点配置,我们可以在失去2个节点的情况下不丢失任何数据。
1.4.6 故障应对
如果我们关闭第一个节点,这时集群的状态为关闭了一个节点后的集群。
我们关闭的节点是一个主节点。而集群必须拥有一个主节点来保证正常工作,所以发生的第一件事情就是选举一个新的主节点:
Node 2
。在我们关闭
Node 1
的同时也失去了主分片1
和2
,并且在缺失主分片的时候索引也不能正常工作。 如果此时来检查集群的状况,我们看到的状态将会为red
:不是所有主分片都在正常工作。幸运的是,在其它节点上存在着这两个主分片的完整副本, 所以新的主节点立即将这些分片在
Node 2
和Node 3
上对应的副本分片提升为主分片, 此时集群的状态将会为yellow
。 这个提升主分片的过程是瞬间发生的,如同按下一个开关一般。为什么我们集群状态是
yellow
而不是green
呢? 虽然我们拥有所有的三个主分片,但是同时设置了每个主分片需要对应2份副本分片,而此时只存在一份副本分片。 所以集群不能为green
的状态,不过我们不必过于担心:如果我们同样关闭了Node 2
,我们的程序 依然 可以保持在不丢任何数据的情况下运行,因为Node 3
为每一个分片都保留着一份副本。如果我们重新启动
Node 1
,集群可以将缺失的副本分片再次进行分配,那么集群的状态也将如下图所示。
如果
Node 1
依然拥有着之前的分片,它将尝试去重用它们,同时仅从主分片复制发生了修改的数据文件。
1.4.7 集群环境配置
node-1节点配置(master)
#集群名称 cluster.name: my-application #节点名称 node.name: node-1 #启动时默认的master节点 cluster.initial_master_nodes: ["node-1"] #节点角色,master节点即充当master节点,可充当data节点 node.roles: [master, data] #主机 network.host: localhost #服务端口 http.port: 9201 #集群间的通讯端口 transport.port: 9301 #允许跨域访问 http.cors.enabled: true http.cors.allow-origin: "*"
node-2节点配置
#集群名称 cluster.name: my-application #节点名称 node.name: node-2 #节点角色,充当data节点 node.roles: [data] #要加入的集群节点列表 discovery.seed_hosts: ["localhost:9301"] #主机 network.host: localhost #端口 http.port: 9202 #集群间的通讯端口 transport.port: 9302 #允许跨域访问 http.cors.enabled: true http.cors.allow-origin: "*"
node-3节点配置
#集群名称 cluster.name: my-application #节点名称 node.name: node-3 #节点角色,充当data节点 node.roles: [data] #要加入的集群节点列表 discovery.seed_hosts: ["localhost:9301", "localhost:9302"] #主机 network.host: localhost #端口 http.port: 9203 #集群间的通讯端口 transport.port: 9303 #允许跨域访问 http.cors.enabled: true http.cors.allow-origin: "*"
配置修改完成后,分别启动三个节点,启动完成后,查看集群状态:
使用Postman发送GET请求,请求地址:http://localhost:9201/_cluster/health
响应体:
{ "cluster_name": "my-application", "status": "green", "timed_out": false, "number_of_nodes": 3, "number_of_data_nodes": 3, "active_primary_shards": 1, "active_shards": 2, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 0, "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": 100.0 }
使用Postman发送GET请求,请求地址:http://localhost:9201/_cat/nodes
响应体:
127.0.0.1 33 51 0 d - node-3 127.0.0.1 36 51 0 d - node-2 127.0.0.1 45 51 0 dm * node-1
第2章 进阶
2.1 核心概念
-
索引(Index)
索引是存储、搜索和分析数据的逻辑容器。它由一个或多个分片(Shard)组成,每个分片可以在集群的不同节点上进行分布式存储。
-
类型(Type)
在早期的版本中,Elasticsearch使用类型来表示索引中的不同数据集。从Elasticsearch 7.0版本开始,类型逐渐被弃用,一个索引只能包含一个类型。
-
文档(Document)
文档是Elasticsearch中的基本数据单元,它是以JSON格式表示的一条记录。每个文档都有一个唯一的标识(ID),并属于一个索引。文档可以通过索引、更新、删除和查询等操作进行管理。
-
映射(Mapping)
映射定义了索引中文档的结构和字段的数据类型。它类似于数据库中的表结构定义,用于指定字段名称、数据类型、分析器等信息。
-
分片(Shard)
索引可以分成多个分片,每个分片是一个独立的Lucene索引。分片允许数据在集群中进行分布式存储和并行处理,提高了性能和可扩展性。
-
副本(Replica)
副本是索引分片的复制,用于提高数据的冗余和可用性。每个分片可以有零个或多个副本,副本可以分布在不同的节点上,当主分片不可用时,副本可以接管服务。
-
查询(Query)
Elasticsearch支持丰富的查询语法和功能,可以进行全文搜索、过滤、聚合等操作。常见的查询类型包括匹配查询、范围查询、布尔查询、聚合查询等。
-
分析器(Analyzer)
分析器用于将文本字段分解为词项(Terms),并进行标准化和处理。它包括字符过滤器、分词器和词项过滤器等组件,用于处理文本数据的预处理和索引。
-
聚合(Aggregation)
聚合是一种对数据进行分组、过滤、计算和统计的操作,类似于关系型数据库中的聚合函数。它可以用于生成报告、生成统计数据、进行数据分析等。
-
集群(Cluster)
集群是多个节点的组合,共同协作完成数据的存储和处理。Elasticsearch的集群具有自动发现、节点间通信、故障转移和负载均衡等功能,可以通过集群配置实现高可用和横向扩展。
-
节点(Node)
节点是集群中的一个单独的实例,可以是物理机器或虚拟机。每个节点都有自己的唯一标识和名称,它可以承载一个或多个分片,并参与集群的协调和数据处理。
2.2 系统架构
Elasticsearch集群由一个或多个节点组成,共同承载数据的存储和处理任务。每个节点都有一个唯一的名称,并通过集群名称来识别彼此。集群具有高可用性和伸缩性,如果某个节点出现故障,集群中的其他节点可以接管其工作。
创建索引时,可以根据集群节点的数量配置分片和副本数量,合理的分片数可以提高es服务数据处理的吞吐量。一个索引的主分片和副本肯定不会在同一个节点上,因为如果副本和主分片在同一个节点,当此节点出现故障时,将导致服务不可用。
2.3 路由计算 & 分片控制
-
路由计算
在Elasticsearch中,路由计算是用于确定文档应该被分配到哪个分片的过程。每个文档都需要被路由到一个特定的分片,以便在分布式集群中进行存储和检索操作。
Elasticsearch的路由计算基于文档的路由值进行。路由值可以是文档中的某个字段的值,或者通过自定义的路由逻辑来计算得出。
路由计算的基本原理如下:
-
默认路由:如果索引没有设置自定义的路由逻辑,Elasticsearch会使用文档的ID来进行路由计算。它将文档的ID进行哈希运算,并将结果与分片总数取模,以确定文档应该被分配到哪个分片。
-
自定义路由逻辑:索引可以定义自己的路由逻辑,以根据文档的特定字段值进行路由计算。通过自定义路由逻辑,可以将具有相同路由值的文档路由到同一个分片中,从而实现数据的聚合和快速检索。
对于文档的插入、更新和删除操作,路由计算会在数据发送到集群之前进行。当执行搜索操作时,查询会被路由到涉及的分片上进行执行。
需要注意的是,路由计算是在索引级别进行的,而不是在文档级别。这意味着对于同一个索引中的所有文档,它们的路由计算都是基于相同的规则和配置进行的。
通过灵活的路由计算,Elasticsearch能够将数据均匀地分布在集群的各个分片上,实现负载均衡和数据的高可用性。同时,它也为数据的聚合和检索提供了便利,使得用户可以根据自定义的需求将文档路由到特定的分片中。
当索引一个文档的时候,文档会被存储到一个主分片中。 Elasticsearch 如何知道一个文档应该存放到哪个分片中呢?当我们创建文档时,它如何决定这个文档应当被存储在分片
1
还是分片2
中呢?首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的:
shard = hash(routing) % number_of_primary_shards
routing
是一个可变值,默认是文档的_id
,也可以设置成一个自定义的值。routing
通过 hash 函数生成一个数字,然后这个数字再除以number_of_primary_shards
(主分片的数量)后得到 余数 。这个分布在0
到number_of_primary_shards-1
之间的余数,就是我们所寻求的文档所在分片的位置。这就解释了为什么我们要在创建索引的时候就确定好主分片的数量 并且永远不会改变这个数量:因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。
你可能觉得由于 Elasticsearch 主分片数量是固定的会使索引难以进行扩容。实际上当你需要时有很多技巧可以轻松实现扩容。我们将会在扩容设计一章中提到更多有关水平扩展的内容。
所有的文档 API(
get
、index
、delete
、bulk
、update
以及mget
)都接受一个叫做routing
的路由参数 ,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被存储到同一个分片中。我们也会在扩容设计这一章中详细讨论为什么会有这样一种需求。
-
分片控制
在Elasticsearch中,分片控制是指管理索引分片的过程,包括创建、调整和管理分片的数量和分布。通过适当的分片控制,可以实现数据的均衡分布、提高查询性能和容错能力。
以下是一些与分片控制相关的概念和操作:
-
分片数量:在创建索引时,需要指定分片的数量。分片数量决定了索引数据的分布方式和扩展性。通常建议在创建索引之前考虑好分片数量,因为分片数量一旦确定后就不可更改。
-
分片分配策略:Elasticsearch采用分片分配策略来决定将新的索引分片分配到哪些节点上。默认情况下,Elasticsearch会尽量将分片均匀地分配到集群的各个节点上,以实现负载均衡和数据的高可用性。
-
分片重分配:当新增或删除节点、索引或分片时,Elasticsearch会自动进行分片重分配。分片重分配是为了保持数据的均衡分布和高可用性。它会根据集群的状态和配置信息,自动将分片从一个节点迁移到另一个节点,以达到数据的平衡。
-
分片迁移控制:可以通过手动指定分片的迁移操作来控制分片的移动。这在某些情况下可能是有用的,比如在进行节点维护或数据迁移时,可以手动控制分片的迁移过程,以避免对集群性能造成过大的影响。
-
分片失败处理:当分片发生故障或数据丢失时,Elasticsearch会自动尝试恢复分片或复制丢失的数据。它会根据复制因子(replication factor)来决定将数据复制到哪些副本分片上,以确保数据的冗余和容错性。
通过适当的分片控制和管理,可以优化索引的性能、容错能力和可扩展性。这包括合理设置分片数量、处理分片的迁移和故障,以及监控集群的状态和分片分布情况。
2.4 分片数据交互
为了说明目的, 我们 假设有一个集群由三个节点组成。 它包含一个叫
blogs
的索引,有两个主分片,每个主分片有两个副本分片。相同分片的副本不会放在同一节点,所以我们的集群就是有三个节点和一个索引的集群。
我们可以发送请求到集群中的任一节点。 每个节点都有能力处理任意请求。 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。 在下面的例子中,将所有的请求发送到
Node 1
,我们将其称为 协调节点(coordinating node) 。
2.5 新建、索引和删除文档
新建、索引和删除 请求都是 写 操作, 必须在主分片上面完成之后才能被复制到相关的副本分片,如下图所示。
以下是在主副分片和任何副本分片上面 成功新建,索引和删除文档所需要的步骤顺序:
客户端向
Node 1
发送新建、索引或者删除请求。节点使用文档的
_id
确定文档属于分片 0 。请求会被转发到Node 3
,因为分片 0 的主分片目前被分配在Node 3
上。
Node 3
在主分片上面执行请求。如果成功了,它将请求并行转发到Node 1
和Node 2
的副本分片上。一旦所有的副本分片都报告成功,Node 3
将向协调节点报告成功,协调节点向客户端报告成功。在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。
有一些可选的请求参数允许您影响这个过程,可能以数据安全为代价提升性能。这些选项很少使用,因为Elasticsearch已经很快,但是为了完整起见,在这里阐述如下:
consistency
consistency,即一致性。在默认设置下,即使仅仅是在试图执行一个写操作之前,主分片都会要求 必须要有 规定数量(quorum)(或者换种说法,也即必须要有大多数)的分片副本处于活跃可用状态,才会去执行写操作(其中分片副本可以是主分片或者副本分片)。这是为了避免在发生网络分区故障(network partition)的时候进行写操作,进而导致数据不一致。规定数量即:
int( (primary + number_of_replicas) / 2 ) + 1``consistency
参数的值可以设为one
(只要主分片状态 ok 就允许执行写操作),all
(必须要主分片和所有副本分片的状态没问题才允许执行写操作), 或quorum
。默认值为quorum
, 即大多数的分片副本状态没问题就允许执行写操作。注意,规定数量 的计算公式中number_of_replicas
指的是在索引设置中的设定副本分片数,而不是指当前处理活动状态的副本分片数。如果你的索引设置中指定了当前索引拥有三个副本分片,那规定数量的计算结果即:int( (primary + 3 replicas) / 2 ) + 1 = 3
如果此时你只启动两个节点,那么处于活跃状态的分片副本数量就达不到规定数量,也因此您将无法索引和删除任何文档。
timeout
如果没有足够的副本分片会发生什么? Elasticsearch会等待,希望更多的分片出现。默认情况下,它最多等待1分钟。 如果你需要,你可以使用
timeout
参数 使它更早终止:100
100毫秒,30s
是30秒。新索引默认有
1
个副本分片,这意味着为满足规定数量
应该 需要两个活动的分片副本。 但是,这些默认的设置会阻止我们在单一节点上做任何事情。为了避免这个问题,要求只有当number_of_replicas
大于1的时候,规定数量才会执行。
2.6 文档查询流程
可以从主分片或者从其它任意副本分片检索文档 ,如下图所示。
以下是从主分片或者副本分片检索文档的步骤顺序:
1、客户端向
Node 1
发送获取请求。2、节点使用文档的
_id
来确定文档属于分片0
。分片0
的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到Node 2
。3、
Node 2
将文档返回给Node 1
,然后将文档返回给客户端。在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。
在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。
2.7 局部更新文档
如下图所示,
update
API 结合了先前说明的读取和写入模式。
以下是部分更新一个文档的步骤:
客户端向
Node 1
发送更新请求。它将请求转发到主分片所在的
Node 3
。
Node 3
从主分片检索文档,修改_source
字段中的 JSON ,并且尝试重新索引主分片的文档。 如果文档已经被另一个进程修改,它会重试步骤 3 ,超过retry_on_conflict
次后放弃。如果
Node 3
成功地更新文档,它将新版本的文档并行转发到Node 1
和Node 2
上的副本分片,重新建立索引。 一旦所有副本分片都返回成功,Node 3
向协调节点也返回成功,协调节点向客户端返回成功。
update
API 还接受在 新建、索引和删除文档 章节中介绍的routing
、replication
、consistency
和timeout
参数。基于文档的复制
当主分片把更改转发到副本分片时, 它不会转发更新请求。 相反,它转发完整文档的新版本。请记住,这些更改将会异步转发到副本分片,并且不能保证它们以发送它们相同的顺序到达。 如果Elasticsearch仅转发更改请求,则可能以错误的顺序应用更改,导致得到损坏的文档。
2.8 多文档模式
mget
和bulk
API 的模式类似于单文档模式。区别在于协调节点知道每个文档存在于哪个分片中。 它将整个多文档请求分解成 每个分片 的多文档请求,并且将这些请求并行转发到每个参与节点。协调节点一旦收到来自每个节点的应答,就将每个节点的响应收集整理成单个响应,返回给客户端,如下图所示。
以下是使用单个
mget
请求取回多个文档所需的步骤顺序:
客户端向
Node 1
发送mget
请求。
Node 1
为每个分片构建多文档获取请求,然后并行转发这些请求到托管在每个所需的主分片或者副本分片的节点上。一旦收到所有答复,Node 1
构建响应并将其返回给客户端。可以对
docs
数组中每个文档设置routing
参数。bulk API, 如下图所示, 允许在单个批量请求中执行多个创建、索引、删除和更新请求。
bulk
API 按如下步骤顺序执行:
客户端向
Node 1
发送bulk
请求。
Node 1
为每个节点创建一个批量请求,并将这些请求并行转发到每个包含主分片的节点主机。主分片一个接一个按顺序执行每个操作。当每个操作成功时,主分片并行转发新文档(或删除)到副本分片,然后执行下一个操作。 一旦所有的副本分片报告所有操作成功,该节点将向协调节点报告成功,协调节点将这些响应收集整理并返回给客户端。
bulk
API 还可以在整个批量请求的最顶层使用consistency
参数,以及在每个请求中的元数据中使用routing
参数。