本文已参与「新人创作礼」活动,一起开启掘金创作之路。
需求来源
在ElasticSearch的生产应用中,尤其在数据量比较大的场景下,性能优化就显得十分重要。可以说在ElasticSearch的使用场景下,大神和小白能玩出完全不同的ElasticSearch。
对于性能优化,除了使用方式以及服务配置相关的修改以及优化外,前期对于整个ElasticSearch规模和资源的评估,进而提前规划相关的资源需求以及参数优化同样十分的重要。前期的良好的设计和评估除了在中后期能提供良好的性能外,也会避免很多麻烦。
这种前置性的需求也是整个ElasticSearch使用门槛较高的重要原因,毕竟index级别的动态扩展对于ElasticSearch来说并不能随随便便成功。为了让大家少走弯路,未雨绸缪,本文将在作者认知范围内提供相关的说明和方法帮助大家理解相关的内容和知识。
在此之前,先简单的回顾下shard以及segement的概念以及关系。需要的小伙伴们可以看一下,不需要的可以直接跳到后面的章节看结论。
关系梳理
逻辑结构
下面这张图就是ElasticSearch在逻辑层面上的架构关系:
从上面的图上可以得到如下结论:
- ElasticSearch的逻辑上的层级关系是cluster-node-shard-segment
- ElasticSearch的shard底层就是一个Lucene索引。而ElasticSearch的replica是作用在shard层面,也就是说从shard开始进行复制,图中的P就代表中主分片,R就代表副本分片
- segement文件并不是一个文件,而是一堆文件的集合。包含的各种各样的文件,这些文件是根据index的mapping动态生成的。详细内容可以参考作者之前的文章:添加链接
物理结构
下面再来看看ElasticSearch的数据在硬盘上的结构:
从上面的图上可以得到如下结论:
- 数据文件夹中包含indices、node.lock以及_state三个文件或者文件夹
- indices文件夹中用来存储真正的业务数据。下面的每个字符串都代表了存储在该节点上的index的id
- node.lock是用来对整个数据目录加锁,保证该目录的数据只能被一个ElasticSearch的实例操作,避免数据污染
- _state存储的是该节点的clusterState数据,即整个集群的集群状态数据。当集装状态发生变化,这个数据需要在节点之间进行同步,保证整个集群元数据的一致性。这个数据要注意一下,后面的文章中会经常提到这个概念。另外上面说的index的id也是在这里配置的对应关系,通过id在这里就能找到index的真是数据
接下来随便找一个index文件夹进入来看看进一步的结构,也就是shard的结构。这里选择的是ZV4开头的index:
从图上可以看出:
- 整个内容分两部分,数字文件夹和_state文件
- 数字文件夹代表着是该index的每一个shard。由于该ElasticSearch集群是单节点,所以该index的所有shard都在这里,也就是说该index有16个shard。如果该集群有4个node的话,那此处正常来说应该就会有4个文件夹,即shard数量%node数量获得的值
- _state文件里面存储了shard相关的部分元数据
最后终于到了segement相关的结构了:
- 首先可以看出该文件夹中的数据都是成组的存在。比如_3ux,_4sg等,这其实就是每个segment的前缀标识,而每一组文件就代表着一个segment
- segment存储的文件数量以及文件种类不同,这个是由该segment所存储数据的mapping和其他配置所控制的。详情可以参考作者之前的文章添加连接
- 同样segement也有相关的state文件,用来标识和管理segement文件,由于文件过多,图片中未能展示出来
shard和segment对集群的影响
- 一个shard底层为一个lucene索引,会消耗一定文件句柄,内存,cpu等。shard过多,对操作系统的资源占用就越大。
- 每个shard生成的segment cache的内存是不会被gc释放掉的!!!虽然ElasticSearch 7.X将segment cache从堆内挪到了堆外,但是仍然会导致大量的内存被浪费直到cache被操作系统回收。所以过大过多的segnment会导致系统运行内存不足。
- 由于ElasticSearch的数据存储以及GET查询使通过数据的docId取模然后分配到不同的shard中,进而确定数据在磁盘上的存储位置。所以shard的数量在创建索引之后就不能改变了,否则之前存储的数据将无法通过GET方式取到。想改变shard数量只能通过代价巨大的reindex和使用场景受限的split操作了。
- 在Elasticsearch中,每个查询都是在单个分片上以单线程方式执行的。然而,可以同时对多个分片进行处理。这意味着,最低查询延时(假设没有缓存)将取决于数据、查询类型,以及分片大小。尽管查询很多个小分片会加快单个分片的处理速度,但是由于有很多任务需要进入队列并按顺序加以处理,所以与查询较少的大分片相比,这种方法并不一定会加快查询速度。如果有多个并发查询,拥有很多(成百上千)小分片还会降低查询吞吐量。
- shard过大,导致集群故障后恢复缓慢;容易造成写热点,导致bulk queue打满,拒绝率上升
- shard过小,导致segement过小,无法充分利用多节点资源,机器资源不均衡,影响segment合并效率和浪费资源。shard过小同样会造成存储相关数据量时的shard数量会过多的现象。
- shard过多,会导致lucene查询时会遍历的segment数量变多,IO资源浪费严重。而过多的segment会导致查询速度下降,没有内存甚至导致超时或者拒绝访问等问题。另外shard过多同样会导致cluster state数据过大,在cluster state数据发生变更需要在节点同步时,会影响同步的速度以及成功率,进而影响到集群的性能以及稳定性。
具体方案
从上面的描述可以看出,shard以及segment大了小了不行,多了少了也不行,必须在合适的范围内才行,而且最好在创建索引的时候就能规划好。
这听起来就很困难,做起来更困难,只能尽量按照某些规范进行预规划,下面就来直接说几个场景和原则供大家参考:
数据分布均匀
对于数据量较小(100GB以下)的index,往往写入压力查询压力相对较低,一般设置3~5个shard,numberofreplicas设置为1即可(也就是一主一从,共两副本) 。
对于数据量较大(100GB以上)的index:
一般把单个shard的数据量控制在(20GB~50GB)
让index压力分摊至多个节点:可通过index.routing.allocation.totalshardsper_node参数,强制限定一个节点上该index的shard数量,让shard尽量分配到不同节点上
综合考虑整个index的shard数量,如果shard数量(不包括副本)超过50个,就很可能引发拒绝率上升的问题,此时可考虑把该index拆分为多个独立的index,分摊数据量,同时配合routing使用,降低每个查询需要访问的shard数量。
数据分布不均匀
数据分布不均匀可能会造成某些shard的数据量过大,某些shard的数据量过小,在此种情况下与上面的差别就是需要考虑shrink相关的操作。
由于shrink操作新索引的分片数必须是源索引分片数的质数,所以新建index时,索引的个数最好是类似于4,8,12,24之类的数字,否则shrink操作后新索引的分片只能是1,剩下的逻辑按照数据分布均匀时的原则进行即可
通用原则
如果是周期性的数据,可以考虑按照周期进行index的拆分,如时序数据可以按照天或者其他时间周期创建index,配合template使用往往会有很好的效果。
另外如果数据量不稳定或者波动较大,针对于固定时间或者周期创建索引可能就不是很合适了,在这种情况下固定时间窗口下通过计算index的shard大小来判断是否需要创建新索引可能是个更好的解决方案。
定期清除和管理旧数据。对只读索引进行force merge操作,释放系统资源。对不需要的数据进行delete或者是close index操作。在大数据量的场景下建议使用elasticsearch的ilm机制添加连接来管理数据。
总结
上述的规则以及原则的目的就是能更好的规划shard以及segment的数量以及大小,使得ElasticSearch集群的性能以及稳定性有所保障,在此基础上尽量减少集群运维以及故障填坑的风险和工作量,还是值得大家好好学习和体会下的。
另外这也是在长时间的填坑排雷以及血泪教训中得来的经验,希望大家将来能在此基础上少走弯路,直接走上ElasticSearch使用的康庄大道,那这篇文章就功德圆满了。
文章到这里就结束了,最后路漫漫其修远兮,大数据之路还很漫长。如果想一起大数据的小伙伴,欢迎点赞转发加关注,下次学习不迷路,我们在大数据的路上共同前进!