ElastricSearch 第二弹之分片原理
最近好多小伙伴后台留言想看 ES 的后续内容,今天它来了,废话不多说,直接上干货。
首先让我们来了解一下 ES 中的分片概念:ES 支持 PB 级全文搜索,当索引上的数据量太大的时候,ES 通过水平拆分的方式将一个索引上的数据拆分出来分配到不同的数据块上,分布在多台服务器上存储,拆分出来的数据库块称之为一个分片。分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。这类似于 MySQL 的分库分表,在一个多分片的索引中写入数据时,通过路由来确定具体写入哪一个分片中,所以在创建索引的时候需要指定主分片的数量,并且主分片的数量一旦确定就不能修改(原因在下文中介绍),副本分片可以动态修改。
那么什么是主分片?什么是副本分片呢?
shard(主分片):我们上边所说的分片其实就指的是主分片,主分片是数据的容器,文档保存在主分片内,主分片又被分配到集群内的各个节点里。每个 shard 都是一个 lucene index。
replica(副本分片):副本就是对分片的 Copy ,同步存储主分片的数据内容。为了达到高可用,Master 节点会避免将主分片和副本分片放在同一个节点上,所以副本分片数的最大值是 N-1(其中 N 为节点数)。
分片的数量和副本数量都是可以通过创建索引时的 Settings 来配置,ES 默认为一个索引创建 5 个主分片, 并分别为每个分片创建一个副本分片。
为什么要分片?为什么要区分主副分片呢?
ES 通过分片的功能使得索引在规模上和性能上都得到提升,有了 shard 就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。
读操作:搜索和返回数据可以同时被主分片或副本分片所处理,所以当你拥有越多的副本分片时,也将拥有越高的吞吐量;
区分主副分片的原因:任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,如果为每个 shard 创建 replica 副本,当 shard 所在的服务器不可用时,replica 可以提供备用服务,这样就保证了 ES 在失去某个节点的情况下不丢失任何数据。
举例说明
假设这时节点 Node1 服务宕机了或者网络不可用了,那么主节点上主分片 S0 也就不可用了。幸运的是还存在另外两个节点能正常工作,这时 ES 会重新选举新的主节点,而且这两个节点上存在我们所需要的 S0 的所有数据。我们会将 S0 的副本分片提升为主分片,这个提升主分片的过程是瞬间发生的。此时集群的状态将会为 Yellow。为什么我们集群状态是 Yellow 而不是 Green 呢?虽然我们拥有所有的 2 个主分片,但是同时设置了每个主分片需要对应两份副本分片,而此时只存在一份副本分片。所以集群不能为 Green 的状态。如果我们同样关闭了 Node2 ,我们的程序依然可以保持在不丢失任何数据的情况下运行,因为 Node3 为每一个分片都保留着一份副本。如果我们重新启动 Node1 ,集群可以将缺失的副本分片再次进行分配,那么集群的状态又将恢复到原来的正常状态。如果 Node1 依然拥有着之前的分片,它将尝试去重用它们,只不过这时 Node1 节点上的分片不再是主分片而是副本分片了,如果期间有更改的数据只需要从主分片上复制修改的数据文件即可。
知识点补充:
集群状态通过 绿,黄,红 来标识:
绿色:集群健康完好,一切功能齐全正常,所有分片和副本都可以正常工作。
黄色:预警状态,所有主分片功能正常,但至少有一个副本是不能正常工作的。此时集群是可以正常工作的,但是高可用性在某种程度上会受影响。
红色:集群不可正常使用。某个或某些分片及其副本异常不可用,这时集群的查询操作还能执行,但是返回的结果会不准确。对于分配到这个分片的写入请求将会报错,最终会导致数据的丢失。
写索引原理:主分片和副本分片的同步原理
写索引是只能写在主分片上,然后同步到副本分片。写操作:对文档的新建、索引和删除请求,必须在主分片上面完成之后才能被复制到相关的副本分片。
如图所示,上图中有 3 个节点,4 个主分片,8 个副本分片,一条数据 ES 是根据什么规则写到特定分片上的呢?首先这肯定不会是随机的,否则将来要获取文档的时候我们就不知道从何处寻找了。实际上,这个过程是根据下面这个公式决定的:
shard = hash(routing) % number_of_primary_shards
Routing 是一个可变值,默认是文档的_id
,也可以设置成一个自定义的值。Routing 通过 Hash 函数生成一个数字,然后这个数字再除以 number_of_primary_shards (主分片的数量)后得到余数。这个在 0 到 number_of_primary_shards-1 之间的余数,就是我们所寻求的文档所在分片的位置。这就解释了为什么我们要在创建索引的时候就确定好主分片的数量并且永远不会改变这个数量。因为如果数量变化了,那么所有之前路由的值都会无效,文档也再也找不到了。由于在 ES 集群中每个节点通过上面的计算公式都知道集群中的文档的存放位置,所以每个节点都有处理读写请求的能力。在一个写请求被发送到某个节点后,该节点即为前面说过的协调节点,协调节点会根据路由公式计算出需要写到哪个分片上,再将请求转发到该分片的主分片节点上。
ES 为了提高写入的能力这个过程是并发写的,同时为了解决并发写的过程中数据冲突的问题,ES 通过乐观锁的方式控制,每个文档都有一个 _version
(版本)号,当文档被修改时版本号递增。一旦所有的副本分片都报告写成功才会向协调节点报告成功,协调节点向客户端报告成功。
举例说明
假如此时数据通过路由计算公式取余后得到的值是 shard=hash(routing)%4=0。则具体流程如下:
客户端向 ES1 节点(协调节点)发送写请求,通过路由计算公式得到值为 0,则当前数据应被写到主分片 S0 上。
ES1 节点将请求转发到 S0 主分片所在的节点 ES3,ES3 接受请求并写入到磁盘。
并发将数据复制到两个副本分片 R0 上,其中通过乐观并发控制数据的冲突。一旦所有的副本分片都报告成功,则节点 ES3 将向协调节点报告成功,协调节点(ES1)向客户端报告成功。
副本分片是越多越好吗?
答案当然是 no ,原因有以下两点:
(1)多个 replica 可以提升搜索操作的吞吐量和性能,但是如果只是在相同节点数目的集群上增加更多的副本分片并不能提高性能,因为每个分片从节点上获得的资源会变少,这个时候你就需要增加更多的硬件资源来提升吞吐量。
(2)更多的副本分片数提高了数据冗余量,保证了数据的完整性,但是根据上边主副分片之间的交互原理可知,分片间的数据同步会占用一定的网络带宽,影响效率,所以索引的分片数和副本数也不是越多越好。
总结:
将数据分片是为了提高可处理数据的容量和易于进行水平扩展,为分片做副本是为了提高集群的稳定性和提高并发量。
副本是乘法,越多消耗越大,但也越保险。分片是除法,分片越多,单分片数据就越少也越分散。
副本越多,集群的可用性就越高,但是由于每个分片都相当于一个 Lucene 的索引文件,会占用一定的文件句柄、内存及 CPU。
阿 Q 将持续更新 java 实战方面的文章,如果你有不同的意见或者更好的 idea,欢迎联系阿 Q。
【阿 Q 说代码】,值得关注的公众号
文章风格多变,配图通俗易懂,故事生动有趣,来聊聊技术呀!
版权声明: 本文为 InfoQ 作者【阿Q说代码】的原创文章。
原文链接:【http://xie.infoq.cn/article/edc881ef278baf740595ddb60】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论