剖析 Elasticsearch 源码,解读 ES 在 index、create 时的原理

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: “源码面前,了无秘密”

作者介绍

魏彬,普翔科技 CTO,开源软件爱好者,中国第一位 Elastic 认证工程师,《Elastic日报》和 《ElasticTalk》社区项目发起人,被 elastic 中国公司授予 2019 年度合作伙伴架构师特别贡献奖。对 Elasticsearch、Kibana、Beats、Logstash、Grafana 等开源软件有丰富的实践经验,为零售、金融、保险、证券、科技等众多行业的客户提供过咨询和培训服务,帮助客户在实际业务中找准开源软件的定位,实现从 0 到 1 的落地、从 1 到 N 的拓展,产生实际的业务价值。

社区里面有人问了如下一个问题:

执行 bulk 索引文档的时候,用 index 或者 create 类型并且自定义 doc id 的情况下,是否会像 update 一样每次都要去 get 一遍原始文档? 比如下面的这条命令:POST _bulk

{ "index" : { "_index" : "test", "_type" : "type1", "_id" : "1" } }
{ "field1" : "value1" }
{ "create" : { "_index" : "test", "_type" : "type1", "_id" : "3" } }
{ "field1" : "value3" }

问题出现的原因是他们在 bulk 测试的时候遇到了写性能的问题,而正巧社区里面前几天有这么一个类似的帖子,说的是 es 5.x 版本里面做 update 操作的性能问题。虽然和这个问题不完全一致,但都涉及到 ES 索引数据部分。

侯捷老师说:“源码面前,了无秘密”,那我们就来简单看下 ES 这部分的相关代码,以便回答开篇提出的问题。

准备工作

我是用 IntelliJ IDEA 来阅读 Elasticsearch 源码的,操作也简单,步骤如下:

1、下载 es 源码,由于 es 的 commit 信息比较多,可以增加 --depth=1 只下载最近的 commit,减少下载时间。

git clone https://github.com/elastic/elasticsearch.git --depth=1

2、安装 gradle,确保版本在 3.3 及以上,然后在源码目录下执行以下命令准备导入 IntelliJ IDEA需要的文件

gradle idea

3、下载安装 IntelliJ IDEA,确保版本为 2017.2 及以上版本。安装完成后,将 elasticsearch 以 gradle 形式导入即可。

大家可以参考 elasticsearch 文档说明 和 Elasticsearch源码分析—环境准备 这两篇文章,细节我这里就不赘述了。

另外我是分析的 5.5.0 分支,大家记得 checkout,防止行数对应不起来。另外由于 es 代码结构有些复杂,先不在这篇文章里面梳理整个流程了,直接说核心代码。

Index/Create 源码分析

es index 和 create 最终都会调用 org/elasticsearch/index/engine/InternalEngine.java 中下面的方法:

457 public IndexResult index(Index index) throws IOException

注意这里的 index 中包含有要写入的 doc, 简单画下该方法的执行流程图,代码这里就不贴了,赶兴趣的自己去看。

image.png

index 流程图

请结合上面的流程图来看相应的代码,整个逻辑应该还是很清晰的,接下来我们看 planIndexingAsPrimary 的逻辑。

558 private IndexingStrategy planIndexingAsPrimary(Index index) throws IOException {

这个方法最终返回一个 IndexingStrategy,即一个索引的策略,总共有如下几个策略:

• optimizedAppendOnly
• skipDueToVersionConflict
• processNormally
• overrideExistingAsIfNotThere
• skipAsStale

不同的策略对应了不同的处理逻辑,前面3个是常用的,我们来看下流程图。

image.png

planIndexingAsPrimary 流程图

这里的第一步判断 是否是自定义 doc id?这一步就是 es 对于日志类非自定义 doc id的优化,感兴趣的可以自己去看下代码,简单讲就是在非自定义 id 的情况下,直接将文档 add ,否则需要 update,而 update 比 add 成本高很多。

而第二个判断 检查版本号是否冲突? 涉及到是如何根据文档版本号来确认文档可写入,代码都在index.versionType().isVersionConflictForWrites方法里,逻辑也比较简单,不展开讲了,感兴趣的自己去看吧。

上面的流程图也比较清晰地列出了策略选择的逻辑,除去 optimizedAppendOnly 策略,其他都需要根据待写入文档的版本号来做出决策。接下来我们就看下获取文档版本号的方法。

389 private VersionValue resolveDocVersion(final Operation op) throws IOException {

该方法逻辑比较简单,主要分为2步:

1、尝试从 versionMap 中读取待写入文档的 version,也即从内存中读取。versionMap 会暂存还没有 commit 到磁盘的文档版本信息。

2、如果第 1 步中没有读到,则从 index 中读取,也即从文件中读取。

看到这里,开篇问题便有了答案。ES 在 index 或者 create 的时候并不会 get 整个文档,而是只会获取文档的版本号做对比,而这个开销不会很大。

Update 源码分析

ES update 的核心代码在 org/elasticsearch/action/update/UpdateHelper.java 中,具体方法如下:

public Result prepare(UpdateRequest request, IndexShard indexShard, LongSupplier nowInMillis) {
        final GetResult getResult = indexShard.getService().get(request.type(), request.id(),
                new String[]{RoutingFieldMapper.NAME, ParentFieldMapper.NAME, TTLFieldMapper.NAME, TimestampFieldMapper.NAME},
                true, request.version(), request.versionType(), FetchSourceContext.FETCH_SOURCE);
        return prepare(indexShard.shardId(), request, getResult, nowInMillis);
    }

代码逻辑很清晰,分两步走:

1、获取待更新文档的数据
2、执行更新文档的操作

第 1 步最终会调用 InternalEngine 中的 get 方法,如下:

350     public GetResult get(Get get, Function<String, Searcher> searcherFactory, LongConsumer onRefresh) throws EngineException {

这里就接上开篇提到的社区问题中的源码分析了。代码就不展开讲了,感兴趣的自己去看吧。

update 操作需要先获取原始文档的原因也很简单,因为这里是允许用户做部分更新的,而 es 底层每次更新时要求必须是完整的文档(因为 lucene 的更新实际是删除老文档,新增新文档),如果不拿到原始数据的话,就不能组装出更新后的完整文档了。

因此,比较看重效率的业务,最好还是不要用 update 这种操作,直接用上面的 index 会更好一些。

总结

本文通过源码分析的方式解决了开篇提到的问题,答案简单总结在下面。

es 在 index 和 create 操作的时候,如果没有自定义 doc id,那么会使用 append 优化模式,否则会获取待写入文档的版本号,进行版本检查后再决定是否写入lucene。所以这里不会去做一个 get 操作,即获取完整的文档信息。

最后,记住侯捷老师的话:

源码面前,了无秘密!

声明:本文由原文《elasticsearch index、create和update的源码分析》作者“魏彬”授权转载,对未经许可擅自使用者,保留追究其法律责任的权利。


image.png

阿里云Elastic Stack】100%兼容开源ES,独有9大能力,提供免费X-pack服务(单节点价值$6000)

相关活动


更多折扣活动,请访问阿里云 Elasticsearch 官网

阿里云 Elasticsearch 商业通用版,1核2G ,SSD 20G首月免费
阿里云 Logstash 2核4G首月免费


image.png

image.png

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
相关文章
|
1月前
|
存储 搜索推荐 数据挖掘
ElasticSearch架构介绍及原理解析
ElasticSearch架构介绍及原理解析
84 0
|
3月前
|
自然语言处理 API 索引
Elasticsearch Analyzer原理分析并实现中文分词
Elasticsearch Analyzer原理分析并实现中文分词
72 0
|
2月前
|
API 索引
Elasticsearch Index Shard Allocation 索引分片分配策略
Elasticsearch Index Shard Allocation 索引分片分配策略
76 1
|
2月前
|
前端开发 Java iOS开发
elasticsearch8.1源码编译笔记
elasticsearch8.1源码编译笔记
32 0
|
2月前
|
Java iOS开发 MacOS
Elasticsearch7.4源码编译记录
Elasticsearch7.4源码编译记录
15 0
|
2月前
|
Java iOS开发 MacOS
Elasticsearch 6.5源码编译最新版
Elasticsearch 6.5源码编译最新版
15 0
Elasticsearch 6.5源码编译最新版
|
2月前
|
数据安全/隐私保护
spring-boot-starter-data-elasticsearch es带x-pack后台配置
spring-boot-starter-data-elasticsearch es带x-pack后台配置
24 0
|
3月前
|
索引
ES(elasticsearch)删除指定索引
ES(elasticsearch)删除指定索引
160 0
|
4月前
|
NoSQL Java API
SpringBoot【ElasticSearch集成 02】Java HTTP Rest client for ElasticSearch Jest 客户端集成(依赖+配置+增删改查测试源码)推荐使用
SpringBoot【ElasticSearch集成 02】Java HTTP Rest client for ElasticSearch Jest 客户端集成(依赖+配置+增删改查测试源码)推荐使用
56 0
|
4月前
|
Java API
SpringBoot【集成ElasticSearch 01】2种方式的高级客户端 RestHighLevelClient 使用(依赖+配置+客户端API测试源码)
SpringBoot【集成ElasticSearch 01】2种方式的高级客户端 RestHighLevelClient 使用(依赖+配置+客户端API测试源码)
75 0

相关产品

  • 检索分析服务 Elasticsearch版