Elasticsearch这几个很常见却很容易答错的问题,你都能答对吗?

前言

elasticsearch发展至今,已经发展到7.X版本了,而其中的小版本更是不胜枚举。而每个版本的发布就会带来新的特性的加入以及旧的特性的修改,这就造成了elasticsearch的配置和使用方法不能一概而论。

在这个背景下,很多过时的使用方法以及配置在新版本就不再适用了;另外很多大家在其他NOSQL上习以为常的操作或者想法,也被平行挪到了elasticsearch上,这就造成了很多的谣言以及留言,本文就针对几个比较常见的问题进行下澄清,帮助大家去伪存真,更好的理解和使用elasticsearch。

另外对于某些知识点,大家平时正常情况下不会遇到,可能也不会去考虑,这里也拿出来几个一说讨论下,希望在特定情况下遇到类似的问题也会有自己的想法。

正文

下面就来讨论几个比较常见的问题以及说法,大家一起去伪存真,从底层了解elasticsearch的工作机制。另外,本文是基于elasticsearch 6.X及以上版本作为讨论的基础,5.X及以下版本可能不适用,请大家按需使用。

elasticsearch到底是先写index buffer还是先写translog

这个问题对于有nosql经验的小伙伴来说,可能会理所当然的认为为了保证数据的安全性,肯定是先写translog,然后再写index buffer。但elasticsearch真的是这么做的吗?

笔者曾经也是这么认为的,也有些人认为是两者同时写入。但是遗憾的是,elasticsearch一反常规,采用了先写入index buffer再写入translog后再返回客户端成功。惊掉了眼镜和下巴的小伙伴肯定觉得难以置信,话不多说,用源码来证明所有问题:

    public IndexResult index(Index index) throws IOException {
       .......
                final IndexResult indexResult;
                if (plan.earlyResultOnPreFlightError.isPresent()) {
                    indexResult = plan.earlyResultOnPreFlightError.get();
                    assert indexResult.getResultType() == Result.Type.FAILURE : indexResult.getResultType();
                } else if (plan.indexIntoLucene || plan.addStaleOpToLucene) {
                 // 将数据写入lucene,最终会调用lucene的文档写入接口
                    indexResult = indexIntoLucene(index, plan);
                } else {
                    indexResult = new IndexResult(
                        plan.versionForIndexing, getPrimaryTerm(), plan.seqNoForIndexing, plan.currentNotFoundOrDeleted);
                }
                if (index.origin().isFromTranslog() == false) {
                    final Translog.Location location;
                    if (indexResult.getResultType() == Result.Type.SUCCESS) {
                        location = translog.add(new Translog.Index(index, indexResult)); 
                    ......
                    // 将数据写入lucene后才开始写translog
                    indexResult.setTranslogLocation(location);
                }
              .......
        }

那elasticsearch为什么会选择这么操作呢?作者也曾经百思不得其解,后来经过网上查询资料以及部分源码的解读,发现大概的原因还是出在Lucene上。

数据在写入Lucene的segment时,Lucene会再对数据进行一些校验,有可能出现写入Lucene失败的情况。如果先写translog,那么就要处理写入translog成功但是写入Lucene一直失败的问题,容错成本比较高。所以ES采用了先写Lucene的方式后写translog的方式。

数据在写入index buffer之后在refresh之前真的无法查询吗

这个在大家看来应该是一个真命题了,原理index buffer的数据只有在refresh到os cache中,并生成segment file后才会被搜索到。好像没啥问题啊,但事实真的是这样吗?

其实这种说法不准确,因为上面的说法是数据不会被搜索到,翻译成英语是search,而get操作是可以拿到这条还没有refresh的数据的。如果查询包含get操作,那么上面的说法显然是不对的。那为什么get操作能拿到,search拿不到呢?大家考虑下?

这个之前其实在其他的文章中说过,这里再说一下,想想两者的差别,也就是查询流程的差别就能明白是怎么回事了。详细文章参见:

get操作:

search操作:

大家对比两张图看出差别了吗?

是的,差别就在于get操作首先读取的是translog数据,新数据在写入index buffer后也写入到了os cache的translog,所以在refresh之前就可以被查到;而search首先查询的是heap中的fst数据,这个数据是segment的前缀树索引,用以加速segment的查询,所以必须在flush以后才会被查询到。

单机elasticsearch服务crash之后会丢数据吗

这个问题其实就是在考察elasticsearch的failover机制。大家肯定知道elasticsearch的translog用以在服务crash后恢复数据,但是新数据写入时写入到的translog在os cache中,这样会不会丢数据呢?

答案是不会也会。为什么是这个答案呢?等于没说啊,下面就来分开说明下这两种场景。

首先说不会是因为在elasticsearch的默认配置下是不会丢数据的。这是为何?看下面几个配置项:

  • index.translog.durability:可选值(request(默认值),async)

  • index.translog.sync_interval:默认值5s

其实首先写入translog到os cache中其实是为了增加elasticsearch的写入效率,增大elasticsearch的写入吞吐量而做的设计。不管是为了提高内存使用效率还是最终保证数据安全,translog最终肯定会写入到disk的,即translog的flush操作。所以translog的flush周期就决定了elasticsearch的服务crash后会不会丢数据,以及丢多少数据。

回到那两个配置项:index.translog.durability这个配置项默认是request,就代表着elasticsearch默认会针对每个tranlog的写入都直接flush到硬盘,然后才返回客户端写入成功。在这种情况下,elasticsearch就不会丢数据了。

但是这种默认的配置虽然数据安全性得到了保障,但是一眼就能看到问题,每个request的操作都flush的话,效率明显会大受影响,那tranlog首先写入os cache增加效率和吞吐量的优势将荡然无存。所以除非数据安全性要求特别高的场景下会这么选择外,大部分场景下都会使用async配置项来增加elasticsearch的写入效率和吞吐量。

使用了async配置项后,上面的第二个配置项index.translog.sync_interval就会起作用,其意义就是os cache中的tranlog会异步flush到disk上,每5s执行一次。大家可以根据自己的场景来调整这个参数。在这种配置下,单机版的elasticsearch服务crash掉后就会丢数据了,而丢数据的量取决于index.translog.sync_interval这个参数配置的值,默认会丢5s的数据。

elasticsearch到底是强一致性数据库还是最终一致性数据库

这个问题其实就是在考察elasticsearch的shard多副本的同步机制了。这个就是一个参数控制的wait_for_active_shards。这个值的取值是在1到replica数量+1(即所有副本数量)。这里如果选择all(所有副本)的话,那么elasticsearch就会成为强一致性的数据库,能满足CAP属性中的CP,在保证了一致性的时候牺牲了可用性,即一个replica不可用后写入就会失败;而配置成其他值的时候elasticsearch就是最终一致性的数据库,能满足CAP属性中的AP,在保证了高可用性的时候牺牲了一致性(达到了弱一致性或最终一致性)。

至于把elasticsearch当强一致性数据库还是最终一致性数据库使用则完全看使用场景了。大多数情况下,elasticsearch还是作为最终一致性数据库使用。wait_for_active_shards这个值则取决于能容忍几个replica挂掉而进行相应的配置。

elasticsearch某个replica写入失败后,会导致脏读吗

众所周知,elasticsearch多有的replica都会提供读数据的服务。而到底使用哪个replica进行数据读取则使用round robin(轮询)算法进行选择。

那这样就会有问题了,如果某些replica写入失败未存储最新数据,会不会被client读到而造成脏读呢?

答案肯定是不会了,那么elasticsearch是怎么处理来避免这种情况的发生呢?

首先先来看看Replica写入失败后elasticsearch会怎么处理:

Replica写入失败,Primary会执行一些重试逻辑,尽可能保障Replica中写入成功。如果一个Replica最终写入失败,Primary会将Replica节点报告给Master,然后Master更新Meta中Index的InSyncAllocations配置,将Replica从中移除,移除后它就不再承担读请求。

在Meta更新到各个Node之前,用户可能还会读到这个Replica的数据,但是更新了Meta之后就不会了。所以这个方案并不是非常的严格,考虑到ES本身就是一个近实时系统,数据写入后需要refresh才可见,而且elasticsearch大多数场景下也是一个最终一致性的数据库,所以一般情况下,在短期内读到旧数据应该也是可接受的。

最后看一下elasticsearch的cluster state中的in_sync_allocations配置,查询数据时round robin(轮询)算法轮询的replica列表就是从这里拿到的:

这里面的id对应的就是cluster state中每个replica对应的id。

总结

上面的这几个场景在大家日常的工作中可能接触的不多,甚至不会考虑和接触到。但是在某些极端和特殊情况下很有可能就要考虑这些问题,所以对大家理解和掌握elasticsearch还是很有帮助的。而且弄清楚这些问题也会帮助大家了解elasticsearch内部的一些底层实现机制,进而进阶和精通elasticsearch。

退一万步讲,当你和别人讨论elasticsearch或者面试的时候,能了解这些特殊情况下elasticsearch的机制与使用,也会让对方对你刮目相看。

最后路漫漫其修远兮,大数据之路还很漫长。如果想一起大数据的小伙伴,欢迎点赞转发加关注,下次学习不迷路,我们在大数据的路上共同前进!

挂个公众号二维码,公众号的文章是最新的,CSDN的会有些滞后,想追更的朋友欢迎大家关注公众号,谢谢大家支持。 

公众号地址:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值