51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

解决 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,并且能够避免一些潜在的问题。

赞(5)
未经允许不得转载:工具盒子 » 解决 Elasticsearch 8.x Java API 中 Update 写入 null 值无效的问题