前言 {#前言}
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,并且能够避免一些潜在的问题。