目录
1 基本介绍
1.1 什么是Elasticsearch
1.2 发展历程
1.2.1 美好的事物总有一个浪漫的开始
1.2.2 分布式为其注入了新的活力
1.2.3 开源力量助其腾飞
1.2.4 快速成长
1.3 搜索引擎对比
1.3.1 和Lucene的区别
1.3.2 和solr对比
1.3.3 search Engine排名
1.4 使用场景
1.5 有谁在使用
1.5.1 国内外使用情况
1.5.2 我团的使用
2 基础概念
2.1 准实时NRT(Near Realtime)
2.2 集群(Cluster)
2.2.1 集群的状态
2.3 节点(Node)
2.3.1 节点分类
2.4 索引(Index)
2.5 类型(Type)
2.6 文档(Document)
2.7 分片与副本(Shards & Replicas)
2.8 和数据库对比来看
3 ES基本操作
3.1 创建索引
3.1.1 水平扩容
3.2 新建、删除文档
3.2.1 什么是文档
3.2.2 文档元数据结构
3.2.3 新建&删除文档
2.3 修改文档
2.3.1 单文档修改
2.3.2 使用bulk修改多个文档
2.3.3 更新并发控制
2.4 查询
2.4.1 获取指定文件
2.4.2 使用mget取回多个文档
2.4.3 执行分布式检索查询(复杂查询)
Elasticsearch 是一个分布式的开源搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;Elastic Stack 是适用于数据采集、充实、存储、分析和可视化的一组开源工具。人们通常将 Elastic Stack 称为 ELK Stack(代指 Elasticsearch、Logstash 和 Kibana),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据。
其优势总结起来:
分布式实时文件存储
实时分析的分布式搜索引擎
可以扩展到上百台服务器
Elasticsearch的支持插件机制,如与mongoDB、couchDB同步的River插件、中文分词插件、Hadoop插件、脚本插件等。
许多年前,一个叫Shay Banon的年轻人想为正在学习厨艺的新婚妻子编写一款菜谱搜索软件。在开发过程中,他发现搜索引擎库Lucene不仅使用门槛高,还有会有许多重复性工作。因此他决定在lucene基础之上封装一个简单易用的搜索应用库,并命名为Compress。Elasticsearch的前身就在这样浪漫的机缘下诞生了
之后shay找到了一份工作,工作内容涉及到大量的高并发分布式场景,于是他决定重写Compress,引入了分布式架构,并更名为Elasticsearch。Elasticsearch的第一个版本发布于2010年5月,发布后公众反响强烈
Elasticsearch在github上发布后,使用量骤增,并很快有了自己的社区。很快,社区中的 Steven Schuurman、Uri Boness 和 Simon Willnauer 与Shay Banon 一起成立了一家搜索公司Elasticsearch Inc.。
在Elasticsearch Inc.公司成立前后,另外两个开源项目也正在快速发展。一个是Jordan Sissel的开源可插拔数据采集工具Logstash, 另一个是Rashid Khan的开源数据可视化UI Kibana。由于作者间对彼此产品比较熟悉,因此决定合作发展,最终形成了Elastic Stack的经典技术栈ELK: Elasticsearch, Logstash, Kibana。
之后Elasticsearch迅速发展,增加了许多新功能和特性
版本 |
发布日志 |
重要特性 |
---|---|---|
0.7.0 |
2010.5.14 |
github上第一个版本 |
1.0.0 |
2014.2.14 |
备份恢复,聚合,熔断器,docvalues等 |
2.0.0 |
2015.10.28 |
组件版本统一,推出Elastic Cloud等 |
5.0.0 |
2016.10.26 |
商业组件整合为x-pack;使用Lucene6.0引入BKD树,稀疏数据优化等; beat引入module概念; 增加machine learning功能; shrink API; ingest node; painless 脚本等 |
6.0.0 |
2017.8.31 |
稀疏性docvalues支持,index sorting, sequence num, 滚动升级等 |
7.0.0 |
2019.4.10 |
引入新的集群协调层zen2; real内存熔断器等 |
2018年美东时间10月5日上午 9:30 整,纽约证券交易所的铃声响起,Elastic 成功上市。
Lucene是当今最先进,最高效的全功能开源搜索引擎框架。但是Lucene只是一个框架,要充分利用它的功能,需要使用JAVA,并且在程序中集成Lucene。需要很多的学习了解,才能明白它是如何运行的,非常的复杂。
Elasticsearch使用Lucene作为内部引擎,但是在使用它做全文搜索时,只需要使用统一开发好的API即可,而不需要了解其背后复杂的Lucene的运行原理。
总结起来Elasticsearch是一个实时的分布式搜索和分析引擎,它可以帮助你用前所未有的速度去处理大规模数据。可以用于全文搜索,结构化搜索以及分析,当然你也可以将这三者进行组合。
Elasticsearch和solr底层都是建立Lucene基础上,且两者都是目前比较主流的企业级搜索引擎,对于两者的特性做一个简单对比,详细对比见(http://solr-vs-elasticsearch.com/)
ES更易使用(不依赖Zookeeper,只有一个进程)
ES能更好的处理查询分析,支持复杂的搜索时间聚合,在开源日志管理中占主导地位
ES监控和指标做得更好
Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供
Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch
对比维度 |
ES |
Solr |
---|---|---|
功能 |
分布式搜索、分析搜索、分组和聚合、多租户 |
分面搜索、实时索引、动态群集、数据库集成、NoSQL功能和丰富的文档处理(例如Word和PDF文件) |
分布式管理 |
内置Zen Discovery模块,自身带有分布式协调管理功能 |
需要借助第三方实现分布式管理( Zookeeper ) |
查询DSL |
仅支持json、sql 格式 |
支持更多格式的数据(xml,json,csv) |
分片放置 |
动态,可以根据群集状态按需移动分片 |
本质上是静态,需要手动工作来迁移分片,从Solr 7开始 - Autoscaling API允许一些动态操作 |
高速缓存 |
每段,更适合动态更改数据 |
全局,每个段更改无效 |
分析引擎性能 |
结果的准确性取决于数据放置 |
非常适合精确计算的静态数据 |
全文搜索功能 |
基于Lucene的语言分析,单一建议API实现,高亮显示重新计算 |
基于Lucene的语言分析,多建议,拼写检查,丰富的高亮显示支持 |
DevOps支持 |
非常好的API |
尚未完全支持 |
机器学习 |
商业功能,专注于异常和异常值以及时间序列数据 |
内置 - 在流聚合之上,专注于逻辑回归和学习排名贡献模块 |
社区和开发者 |
单一商业实体及其员工 |
Apache 软件基金和社区支持 |
Elasticsearch作为一款优秀的搜索引擎,在公司内外一致使用名列前茅
https://db-engines.com/en/ranking/search+engine
ES的使用场景非常广泛,不限于搜索,总结如下:
使用场景 |
说明 |
---|---|
存储 |
实际上ES完全可以作为一个分布式存储系统,类似于mongo,以松散的结构存储数据(实测1000W级别的数据读取速度在ms级别),比如存储移动端命令字数据 |
搜索 |
高可用的分布式搜索架构 |
监控 |
基础设施指标和容器监测、应用程序性能检测 |
聚合统计 |
对海量数据进行统计分析,比如日志管理与分析 |
可视化查询 |
ES提供了一系列可视化查询 |
预测 |
ES的Percolator功能可以计算行为趋势,进而进行预测 |
地理位置 |
ES内置了地理位置搜索功能(目前公司的很多业务部门都在使用ES作为位置搜索框架) |
国外:ebay、IEEE、Blizzard、Microsoft、Facebook、Netflix等
国内:阿里巴巴、腾讯、滴滴、京东、今日头条、饿了么、360安全、小米、vivo 等
具体使用案例(https://www.elastic.co/cn/customers/)
ElasticSearch是一个准实时的搜索平台。准实时的意思是说它的延迟非常小,从为一个文档建索引到这个文档可以搜索出来,只需要1秒的时间
一个ES集群可以由一个或者多个节点(nodes or servers)组成。所有这些节点用来存储所有的数据以及提供联合索引,为我们提供跨节点查询的能力。一个ES集群的名称是唯一的,默认情况下为“elasticsearch”。这个名称非常重要,因为一个节点(node)会通过这个名称来判断是否加入已有的集群
必须保证在不同环境下使用不同的集群名称,否则节点可能会加入错误的集群。比如我们可以为开发环境(development)、测试环境(staging)、产品环境(production)分别给出不同的集群名称:logging-dev、logging-staging、logging-prod。
需要注意的是我们也可以使用一个只有一个节点的集群,或者我们也可以有不同的集群,每个集群都有自己唯一的名称。
集群的状态有 Green、Yellow 和 Red 三种,如下所述:
Green:绿色,健康。所有的主分片和副本分片都可正常工作,集群 100% 健康。
Yellow:黄色,预警。所有的主分片都可以正常工作,但至少有一个副本分片是不能正常工作的。此时集群可以正常工作,但是集群的高可用性在某种程度上被弱化。
Red:红色,集群不可正常使用。集群中至少有一个分片的主分片及它的全部副本分片都不可正常工作。
这时虽然集群的查询操作还可以进行,但是也只能返回部分数据(其他正常分片的数据可以返回),而分配到这个分片上的写入请求将会报错,最终会导致数据的丢失。
一个节点是一个集群中的一台服务器,它用来存储数据,参与集群的索引以及提供搜索能力。如ES集群,一个节点也是由它的唯一名称来标识,默认状态下,ES会为在启动时随机为一个节点给定一个以漫威Marvel人物的名字为之命名,当然我们也可以为节点指定任何我们想指定的名称。这个名称对于管理ES集群非常重要,我们用它来定位网络或集群中的某一节点。
一个节点可以通过指定集群名称让它加入某个集群,默认情况下,每个节点都会加入到一个名为“elasticsearch”的集群中,也就是说,当我们在某一网络下启动一定数量的ES节点时,我们认为他们可以相互发现同一网络下的其他节点。
在单集群下,我们可以有任意数量的节点,如果当前网络下没有任何ES节点,那么在启动节点后,当前节点会默认形成一个单节点集群,名称为“elasticsearch”。
主节点(Master Node):也叫作主节点,主节点负责创建索引、删除索引、分配分片、追踪集群中的节点状态等工作。Elasticsearch 中的主节点的工作量相对较轻。
用户的请求可以发往任何一个节点,并由该节点负责分发请求、收集结果等操作,而并不需要经过主节点转发。
通过在配置文件中设置 node.master=true 来设置该节点成为候选主节点(但该节点不一定是主节点,主节点是集群在候选节点中选举出来的),在 Elasticsearch 集群中只有候选节点才有选举权和被选举权。其他节点是不参与选举工作的。
数据节点(Data Node):数据节点,负责数据的存储和相关具体操作,比如索引数据的创建、修改、删除、搜索、聚合。
所以,数据节点对机器配置要求比较高,首先需要有足够的磁盘空间来存储数据,其次数据操作对系统 CPU、Memory 和 I/O 的性能消耗都很大。
通常随着集群的扩大,需要增加更多的数据节点来提高可用性。通过在配置文件中设置 node.data=true 来设置该节点成为数据节点。
客户端节点(Client Node):就是既不做候选主节点也不做数据节点的节点,只负责请求的分发、汇总等,也就是下面要说到的协调节点的角色。
其实任何一个节点都可以完成这样的工作,单独增加这样的节点更多地是为了提高并发性。
可在配置文件中设置该节点成为数据节点:
node.master=false
node.data=false
部落节点(Tribe Node):部落节点可以跨越多个集群,它可以接收每个集群的状态,然后合并成一个全局集群的状态。
它可以读写所有集群节点上的数据,在配置文件中通过如下设置使节点成为部落节点:
tribe: one: cluster.name: cluster_one two: cluster.name: cluster_two
因为 Tribe Node 要在 Elasticsearch 7.0 以后移除,所以不建议使用。
协调节点(Coordinating Node):协调节点,是一种角色,而不是真实的 Elasticsearch 的节点,我们没有办法通过配置项来配置哪个节点为协调节点。集群中的任何节点都可以充当协调节点的角色。
当一个节点 A 收到用户的查询请求后,会把查询语句分发到其他的节点,然后合并各个节点返回的查询结果,最好返回一个完整的数据集给用户。
在这个过程中,节点 A 扮演的就是协调节点的角色。由此可见,协调节点会对 CPU、Memory 和 I/O 要求比较高。
一个索引是一组具有相似特性的文档的集合。例如,可以为客户数据(customer data)建立索引一个索引,也可以为产品目录(product catalog)建立另一个索引,还可以为订单数据(order data)建立另一个索引。一个索引由它的名称唯一标识(必须所有字母为小写字母),这个名称会在进行索引(indexing)、搜索(search)、修改(update)和删除(delete)操作的时候使用。
在一个单集群下,我们可以定义任意多的索引。
在一个索引下,我们可以定义一个或多个类型(types)。一个类型是一个索引逻辑分类或分区(category/partition),而分类或分区的划分方法由我们自己决定。通常情况下,我们会为具有相类似的字段的一组文档定义类型。比如,如果我们运行一个博客平台,所有的数据都使用同一索引,我们为用户数据定义一种类型,为博客数据定义另一种类型,同时为评论数据定义另一种类型。
一个文档是一个可以被索引的基本信息单元。比如,一个用户可以是一个文档,一个产品可以是一个文档,一个订单同样也可以成为一个文档。这个文档以JSON格式表示。
一个索引可能会存储大量数据从而超过单个节点硬件的限制。例如,单个索引可能会有上亿的文档占用1TB的磁盘空间,这对于单个节点来说太大,同时使用单个节点也会是搜索变慢。
为了解决这个问题,ES提供了一种分片(shard)能力,让我们将一个索引切分成片。当我们创建一个索引时,我们可以为它指定分片的数量。每个分片自己都能独立工作,并且存在与集群的任一节点中。
分片的重要性主要体现在以下两个原因:
可以水平分割或扩展内容体量。
可以分布式和并行的方式在多个分片上进行操作(多个节点)从而提高性能和吞吐量。
一个分片是如何散发的,如何将它的文档聚合并返回个查询是对用户透明的,这个过程完全由ES来管理。
在网络或云的环境下,错误可以在任何时候发生,当一个分片或节点因为某种原因下线或消失时,一个错误恢复机制就非常重要。为了解决这个问题,ES让我们可以为一个索引分片创建一个或多个拷贝,这个拷贝称作副本分片,简称为副本。
副本的重要性主要体现在以下两个原因:
当一个分片或者节点出错时,集群任然可用。正因如此,我们会发现一个分片副本从来不会在它的原始分片或主分片所在的节点出现。
横向扩展搜索体量和吞吐量,因为搜索可以在所有副本上并行执行。
总之,每个索引都可以分为多个分片,一个索引也可以被复制到零个或多个副本。一旦发生复制,每个索引都会有主分片(primary shards)和多个副本分片(replica shards)。分片数和副本数可以在一个索引创建时指定。当索引创建以后,可以动态的改变副本数,但是不能改变分片数。
默认情况下,每个ES索引都有5个主分片(primary shards)和1个副本(replica),也就是说当我们的集群有两个节点时,我们的索引会有5个主分片和另外5个副本分片,也就是说每个索引有总共10个分片。
每个Elasticsearch分片都是一个Lucene索引。对于单个Lucene索引,文档的最大数有一个限制,2,147,483,519。即(= Integer.MAX_VALUE - 128),即20亿,但是实际最大值还需要参考你的使用场景:包括你使用的硬件, 文档的大小和复杂程度,索引和查询文档的方式以及你期望的响应时长。
DB |
ES |
---|---|
数据库(database) |
索引(index):一组具有相似特性的文档的集合 |
表(table) |
类型(type):一个索引中只存放一类数据 |
行(row) |
文档(document):一个可以被索引的基本信息单元 |
列(column) |
字段(field) |
表结构(schema) |
映射(mapping) |
索引 |
倒排索引 |
SQL |
查询DSL |
SELECT |
GET |
UPDATE |
PUT |
DELETE |
DELETE |
//创建一个名为blogs的Index,并设定它有3个主shard每个主shard总共要有一个副本shard在其他机器。
PUT /blogs {
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
如果我们只有一个节点,那么创建索引后结果如图
此时集群的状态为黄色,因为没有副本分配到其他机器,此时ES能够正常工作,但是如果机器出问题则不能正常提供服务。
当我们加入节点时,会自动shard副本,并且之后集群状态为绿色了:
分片扩容
拥有三个节点的集群——为了分散负载而对分片进行重新分配
Node 1 和 Node 2 上各有一个分片被迁移到了新的 Node 3 节点,现在每个节点上都拥有2个分片,而不是之前的3个。 这表示每个节点的硬件资源(CPU, RAM, I/O)将被更少的分片所共享,每个分片的性能将会得到提升。
分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。 我们这个拥有6个分片(3个主分片和3个副本分片)的索引可以最大扩容到6个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源
副本扩容
主分片的数目在索引创建时就已经确定了下来。实际上,这个数目定义了这个索引能够 存储 的最大数据量。(实际大小取决于你的数据、硬件和使用场景。) 但是,读操作——搜索和返回数据——可以同时被主分片 或 副本分片所处理,所以当你拥有越多的副本分片时,也将拥有越高的吞吐量
我们可以发送请求到集群中的任一节点。 每个节点都有能力处理任意请求。 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。
在下面的例子中,将所有的请求发送到 Node 1 ,我们将其称为 协调节点(coordinating node) 。
通常情况下,我们使用的术语 对象 和 文档 是可以互相替换的。不过,有一个区别: 一个对象仅仅是类似于 hash 、 hashmap 、字典或者关联数组的 JSON 对象,对象中也可以嵌套其他的对象。 对象可能包含了另外一些对象。在 Elasticsearch 中,术语 文档 有着特定的含义。它是指最顶层或者根对象, 这个根对象被序列化成 JSON 并存储到 Elasticsearch 中,指定了唯一 ID
一个文档不仅仅包含它的数据 ,也包含 元数据 —— 有关 文档的信息。 三个必须的元数据元素如下:
_index:
文档在哪存放。一个 索引 应该是因共同的特性被分组到一起的文档集合。
_type:
文档表示的对象类别。 types (类型)的特性,它允许您在索引中对数据进行逻辑分区。不同 types 的文档可能有不同的字段,但最好能够非常相似
_id:
文档唯一标识。ID 是一个字符串,当它和 _index 以及 _type 组合就可以唯一确定 Elasticsearch 中的一个文档。 当你创建一个新的文档,要么提供自己的 _id ,要么让 Elasticsearch 帮你生成
{
"_index": "refund_info_201912",
"_type": "refund_info",
"_id": "ff70c5d6389f45d3a644c2b026c31a7f",
"_score": 1,
"_source": {
"acceptedTime": 1575129852000,
"actualFee": 9900,
"bizType": 103,
"channelRefundId": "",
"ctime": 1575129850949,
"id": "ff70c5d6389f45d3a644c2b026c31a7f",
"money": 9900,
"operator": "pos:50001_193GCA8M0671",
"operatorType": 6,
"orderCtime": 1575129754064,
"originFee": 9900,
"payOrderId": "105l82chqbnt1duygraeg",
"payType": 0,
"payWay": 1,
"poiId": 159758105,
"poiName": "大嘴猫老成都串串火锅(龙洞店)",
"reason": "商家pos退款",
"refundBatchNo": "",
"refundTime": 1575129851917,
"refundTraceNo": "",
"sn": "193GCA8M0671",
"status": 3,
"terminalId": "335373",
"type": 1,
"utime": 1575129850949
}
}
当需要新建、删除文档时,ES首先会经过下面的公式计算决定该doc存储到哪个shard:shard=hash(routing) % number_of_primary_shards
routing为可以指定的任意字段,也可以使用默认的_id。
客户端向 Node 1 发送新建、索引或者删除请求。
节点使用文档的 _id 确定文档属于分片 0 。请求会被转发到 Node 3,因为分片 0 的主分片目前被分配在 Node 3 上。
Node 3 在主分片上面执行请求。如果成功了,它将请求并行转发到 Node 1 和 Node 2 的副本分片上。一旦所有的副本分片都报告成功, Node 3 将向协调节点报告成功,协调节点向客户端报告成功。
在客户端收到成功响应时,文档变更已经在主分片和所有副本分片执行完成,变更是安全的。
有一些可选的请求参数允许您影响这个过程,可能以数据安全为代价提升性能。
consistency(一致性参数): 在默认设置下,即使仅仅是在试图执行一个_写_操作之前,主分片都会要求必须要有规定数量(quorum)(或者换种说法,也即必须要有大多数)的分片副本处于活跃可用状态,才会去执行写操作(其中分片副本可以是主分片或者副本分片)。这是为了避免在发生网络分区故障(network partition)的时候进行写操作,进而导致数据不一致。规定数量即:
int( (primary + number_of_replicas) / 2 ) + 1
onsistency 参数的值可以设为 :
One:只要主分片可用,就可以进行写操作。
All:只有当主分片和所有副本都可用时,才允许写操作。
Quorum(k-wu-wo/reng,法定人数):是 Elasticsearch 的默认选项。当有大部分的分片可用时才允许写操作。其中,对 “大部分” 的计算公式为 int ((primary+number_of_replicas)/2)+1。
timeout: 如果没有足够的副本分片会发生什么? Elasticsearch会等待,希望更多的分片出现。默认情况下,它最多等待1分钟。
客户端向 Node 1 发送更新请求。
它将请求转发到主分片所在的 Node 3 。
Node 3 从主分片检索文档,修改 _source 字段中的 JSON ,并且尝试重新索引主分片的文档。 如果文档已经被另一个进程修改,它会重试步骤 3 ,超过 retry_on_conflict 次后放弃。
如果 Node 3 成功地更新文档,它将新版本的文档并行转发到 Node 1 和 Node 2 上的副本分片,重新建立索引。 一旦所有副本分片都返回成功, Node 3 向协调节点也返回成功,协调节点向客户端返回成功。
bulk API 按如下步骤顺序执行:
客户端向 Node 1 发送 bulk 请求。
Node 1 为每个节点创建一个批量请求,并将这些请求并行转发到每个包含主分片的节点主机。
主分片一个接一个按顺序执行每个操作。当每个操作成功时,主分片并行转发新文档(或删除)到副本分片,然后执行下一个操作。 一旦所有的副本分片报告所有操作成功,该节点将向协调节点报告成功,协调节点将这些响应收集整理并返回给客户端。
bulk API格式:
POST _bulk
{ "index" : { "_index" : "test", "_type" : "_doc", "_id" : "1" } }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_type" : "_doc", "_id" : "2" } }
{ "create" : { "_index" : "test", "_type" : "_doc", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_type" : "_doc", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
思考:为什么 bulk API 需要有换行符的格式,而不是发送包装在 JSON 数组中的请求?
点击展开内容
为了回答这一点,我们需要解释一点背景:在批量请求中引用的每个文档可能属于不同的主分片, 每个文档可能被分配给集群中的任何节点。这意味着批量请求 bulk 中的每个操作都需要被转发到正确节点上的正确分片。
如果单个请求被包装在 JSON 数组中,那就意味着我们需要执行以下操作:
将 JSON 解析为数组(包括文档数据,可以非常大)
查看每个请求以确定应该去哪个分片
为每个分片创建一个请求数组
将这些数组序列化为内部传输格式
将请求发送到每个分片
这是可行的,但需要大量的 RAM 来存储原本相同的数据的副本,并将创建更多的数据结构,Java虚拟机(JVM)将不得不花费时间进行垃圾回收。
相反,Elasticsearch可以直接读取被网络缓冲区接收的原始数据。 它使用换行符字符来识别和解析小的 action/metadata 行来决定哪个分片应该处理每个请求。
这些原始请求会被直接转发到正确的分片。没有冗余的数据复制,没有浪费的数据结构。整个请求尽可能在最小的内存中处理。
试想我们使用 Elasticsearch 存储我们网上商城商品库存的数量, 每次我们卖一个商品的时候,我们在 Elasticsearch 中将库存数量减少。
假设有两个 web 程序并行运行,每一个都同时处理所有商品的销售,如图 所示:
web_1 对 stock_count 所做的更改已经丢失,因为 web_2 不知道它的 stock_count 的拷贝已经过期。 结果我们会认为有超过商品的实际数量的库存,因为卖给顾客的库存商品并不存在,我们将让他们非常失望。
变更越频繁,读数据和更新数据的间隙越长,也就越可能丢失变更
在数据库领域中,有两种方法通常被用来确保并发更新时变更不会丢失:
悲观并发控制
这种方法被关系型数据库广泛使用,它假定有变更冲突可能发生,因此阻塞访问资源以防止冲突。 一个典型的例子是读取一行数据之前先将其锁住,确保只有放置锁的线程能够对这行数据进行修改
乐观并发控制
Elasticsearch 中使用的这种方法假定冲突是不可能发生的,并且不会阻塞正在尝试的操作。 然而,如果源数据在读写当中被修改,更新将会失败。应用程序接下来将决定该如何解决冲突。 例如,可以重试更新、使用新的数据、或者将相关情况报告给用户
而Elasticsearch采用的是 乐观并发控制
Elasticsearch 是分布式的。当文档创建、更新或删除时, 新版本的文档必须复制到集群中的其他节点。Elasticsearch 也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许 顺序是乱的 。 Elasticsearch 需要一种方法确保文档的旧版本不会覆盖新的版本。Elasticsearch 中每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。 Elasticsearch 使用这个 _version 号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。_version (版本)值也可有由外部传入,也叫做外部版本号
1、客户端向 Node 1 发送获取请求。
2、节点使用文档的 _id 来确定文档属于分片 0 。分片 0 的副本分片存在于所有的三个节点上。 在这种情况下,它将请求转发到 Node 2 。
3、Node 2 将文档返回给 Node 1 ,然后将文档返回给客户端。
在处理读取请求时,协调结点在每次请求的时候都会通过轮询所有的副本分片来达到负载均衡。
在文档被检索时,已经被索引的文档可能已经存在于主分片上但是还没有复制到副本分片。 在这种情况下,副本分片可能会报告文档不存在,但是主分片可能成功返回文档。 一旦索引请求成功返回给用户,文档在主分片和副本分片都是可用的。
客户端向 Node 1 发送 mget 请求。
Node 1 为每个分片构建多文档获取请求,然后并行转发这些请求到托管在每个所需的主分片或者副本分片的节点上。一旦收到所有答复, Node 1 构建响应并将其返回给客户端。
可以对 docs 数组中每个文档设置 routing 参数。
前面介绍的一个 CRUD 操作只对单个文档进行处理,文档的唯一性由 _index, _type, 和路由值(通常默认是该文档的 _id )的组合来确定。 这表示我们确切的知道集群中哪个分片含有此文档。
搜索需要一种更加复杂的执行模型,因为我们不知道查询会命中哪些文档: 这些文档有可能在集群的任何分片上。 一个搜索请求必须询问我们关注的索引(index or indices)的所有分片的某个副本来确定它们是否含有任何匹配的文档。
但是找到所有的匹配文档仅仅完成事情的一半。 在 search 接口返回一个 page 结果之前,多分片中的结果必须组合成单个排序列表。 为此,搜索被执行成一个两阶段过程,我们称之为 query then fetch 。
2.4.3.1 查询阶段
查询阶段包含以下三个步骤:
客户端发送一个 search 请求到 Node 3 (这个结点就变成协调结点), Node 3 会创建一个大小为 from + size 的空优先队列。
Node 3 将查询请求转发到索引的每个主分片或副本分片中。每个分片在本地执行查询并添加结果到大小为 from + size 的本地有序优先队列中。
每个分片返回各自优先队列中所有文档的 ID 和排序值给协调节点,也就是 Node 3 ,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
2.4.3.2 取回阶段
数据取回阶段由以下步骤构成:
协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。
每个分片加载并 丰富 文档,如果有需要的话,接着返回文档给协调节点。
一旦所有的文档都被取回了,协调节点返回结果给客户端。
协调节点首先决定哪些文档 确实 需要被取回。例如,如果我们的查询指定了 { "from": 90, "size": 10 } ,最初的90个结果会被丢弃,只有从第91个开始的10个结果需要被取回。这些文档可能来自和最初搜索请求有关的一个、多个甚至全部分片
参考文件:
https://www.cnblogs.com/richaaaard/p/5213569.html
https://www.elastic.co/guide/cn/elasticsearch/guide/current/data-in-data-out.html
https://learnku.com/articles/40400