跳到主要内容

解决 Elasticsearch 8.x Java API 中 Update 写入 null 值无效的问题

前言

Elasticsearch 是一个开源的分布式搜索和分析引擎,它可以帮助用户在大规模数据集中快速、准确地搜索、分析和可视化数据。ES 可以处理各种类型的数据,包括结构化、半结构化和非结构化数据,使其非常适用于大数据应用场景。

本文将详细介绍 Elasticsearch Java API 中 Update 写入 null 值无效的问题,以及如何排查、解决此类问题,同时分享笔者的一些反思。

问题描述

在使用 Elasticsearch 8.x 的 Java API 时,进行新增数据有以下方法:

  • Create:如果文档不存在,那么就创建它;存在会报错,发生异常报错不会影响其他操作。
  • Index:创建一个新文档或者替换一个现有的文档。
  • Update:部分更新一个文档(设置 upsert 为 true)

但是大多数情况下,新增一条数据通常会使用 Update 作为一个操作,因为不希望做一个更新把整行给覆盖掉,在中间件同步当中通常会遇见多表汇聚的情况,这种情况就强依赖于 Update 更新某一些字段,而不是把整个文档给替换掉。

然而,当笔者在使用 BulkOperation 来构造多个 Update 操作时,将某个值为 null 的字段写入到 Elasticsearch 的 Index 中时,会发现该值无法被正确地写入到 Index 中。

问题排查

笔者首先在 Kibana 中利用控制台进行实验,发现更新一条带有 null 值文档是没有可以的,也就是说明肯定是我们的代码中有问题,下面我将代码拆出来进行了测试.

首先使用 PUT 命令将一条数据写入到 Elasticsearch 中。

PUT index_test/_doc/1
{
"age": 12,
"name": "John Doe"
}

然后使用 Java API 来对 _id 为 1 的数据进行修改。

RestClient restClient = RestClient.builder(new HttpHost("xxx", 9200)).build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
ElasticsearchClient client8 = new ElasticsearchClient(transport);

String indexName = "index_test";
String esIdVal = "1";

Map<String, Object> doc = new HashMap<>();
doc.put("age", null);
doc.put("name", null);

BulkOperation op = new BulkOperation.Builder().update(i -> i.action(new UpdateAction.Builder<>()
.doc(doc)
.docAsUpsert(true)
.build()).id(esIdVal)).build();

List<BulkOperation> list = Collections.singletonList(op);

BulkResponse response = client8.bulk(builder -> builder.index(indexName).operations(list));

logger.info(response.toString());

client.shutdown();

运行结果为:

{
...
"result":"noop"
...
}

返回结果中的 result 为 noop,说明 Elasticsearch 没有对文档进行实际的更新操作,但是请求的 doc 中携带了 null 值,这非常诡异,笔者还一度怀疑是 Elasticsearch 的 Java API 出了 Bug。

后来在代码 Review 时,发现在初始化 ElasticsearchTransport 时,传入了 new JacksonJsonpMapper()。翻看源码发现,它利用了 Jackson 作为 Json 的序列化器;JacksonJsonpMapper 的默认初始化中有这样一段代码:

this(new ObjectMapper()
.configure(SerializationFeature.INDENT_OUTPUT, false)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
);

笔者看到 setSerializationInclusion(JsonInclude.Include.NON_NULL) 时,恍然大悟,这里配置的序列化默认将 null 值排除了,也就是修改传入的 ObjectMapper 就可以解决这个问题。

ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper(mapper));

代码修改后,null 值能够成功地写入到 Elasticsearch 中了。

总结

本文介绍了在 Elasticsearch 8.x Java API 中 Update 写入 null 值无效的问题,并提供了一种解决方案。通过自定义 ObjectMapper 并将其传递给 JacksonJsonpMapper,我们可以成功地将 null 值写入到 Elasticsearch 中。同时,我们也需要在使用 Elasticsearch Java API 时注意序列化器的配置,以免出现类似的问题。

反思

在本文中,我分享了在使用 Elasticsearch 8.x Java API 进行 Update 操作时遇到的一个问题,即写入 null 值无效。通过代码排查,我发现是序列化器的默认配置导致的。解决方案是自定义 ObjectMapper 并将其传递给 JacksonJsonpMapper。这个问题的出现让我反思了在使用 Elasticsearch 时需要对其内部实现有足够的了解,需要更加谨慎地进行代码审核和测试,以及更加重视配置的影响。这些反思可以帮助我们更好地使用 Elasticsearch,并且能够避免一些潜在的问题。