Elasticsearch聚合浮点类型丢失精度
ElasticsearchTemplate 对Double类型进行Sum 聚合操作丢失精度情景描述:公司数据库采用读写分离,利用canal将mysql数据同步至ES,在对ES某些数据使用聚合函数时统计时,发现精度丢失。先来复现一下这个场景:新建一个document类package com.hclc.document;import io.swagger.annotations.ApiModelPro
·
使用ElasticsearchTemplate 对Double类型进行Sum 聚合操作丢失精度
情景描述:为减轻mysql压力,采用了读写分离,统计的相关操作也移动到了ES上,但在对ES某些数据使用聚合函数时,发现精度丢失。
先来复现一下这个场景:
新建一个document类
package com.hclc.document;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
/**
* @ClassName NumberTestDocument
* @Author: yurj
* @Mail:1638234804@qq.com
* @Date: Create in 9:52 2020/6/15
* @version: 1.0
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Document(indexName = "t_number_test_document", type = "docs", shards = 5, replicas = 1)
public class NumberTestDocument {
/**
* 主键
*/
@Id
@ApiModelProperty(value = "主键")
@Builder.Default
@Field(type = FieldType.Long)
private Long id = 1L;
/**
* 数值
*/
@ApiModelProperty(value = "数值")
@Builder.Default
@Field(type = FieldType.Double)
private Double number = 0.62;
/**
* 字符
*/
@ApiModelProperty(value = "字符")
@Builder.Default
@Field(type = FieldType.Keyword)
private String character = "";
}
使用spring data ElasticsearchTemplate 插入50条模拟数据
package com.test;
import com.hclc.CustomerApplication;
import com.hclc.document.NumberTestDocument;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.test.context.junit4.SpringRunner;
import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = CustomerApplication.class)
@Slf4j
public class DemoApplicationTests {
static {
System.setProperty("es.set.netty.runtime.available.processors", "false");
}
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Test
public void contextLoads() {
if (!elasticsearchTemplate.indexExists(NumberTestDocument.class)) {
elasticsearchTemplate.createIndex(NumberTestDocument.class);
}
List<IndexQuery> queries = new ArrayList<>();
for (int i = 0; i < 50; i++) {
long mostSignificantBits = UUID.randomUUID().getMostSignificantBits();
BigDecimal b = new BigDecimal(Math.random() * 100);
double f1 = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
String uuid = String.valueOf(mostSignificantBits);
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(uuid);
indexQuery.setObject(new NumberTestDocument().setId(mostSignificantBits).setNumber(f1).setCharacter(uuid));
indexQuery.setIndexName(NumberTestDocument.class.getAnnotation(Document.class).indexName());
indexQuery.setType(NumberTestDocument.class.getAnnotation(Document.class).type());
queries.add(indexQuery);
elasticsearchTemplate.bulkIndex(queries);
}
}
}
数据插入效果图:
对应映射(注意number字段的type):
对number字段进行聚合操作:
@Test
public void contextLoads() {
//聚合条件
SumAggregationBuilder doubleSum = AggregationBuilders.sum(BeanUtils.convertToFieldName(NumberTestDocument::getNumber))
.field(BeanUtils.convertToFieldName(NumberTestDocument::getNumber));
// 创建查询对象
SearchQuery build = new NativeSearchQueryBuilder()
.withIndices(NumberTestDocument.class.getAnnotation(Document.class).indexName())
.withTypes(NumberTestDocument.class.getAnnotation(Document.class).type())
.addAggregation(doubleSum) // 添加聚合条件
.build();
Aggregations aggregations = elasticsearchTemplate.query(build, (s) -> s.getAggregations());
//拿到聚合对象
InternalSum internalSum = (InternalSum) aggregations.asMap().get(BeanUtils.convertToFieldName(NumberTestDocument::getNumber));
System.out.println(internalSum.getValue());
}
打印结果会丢失精度,如下图:
如何解决呢?答案是将float类型转换为double即可,可以借用动态模板来实现。
官方动态模板介绍传送门:戳我跳转
编写模板json:
{
"dynamic_templates":[
{
"all_to_double":{
"match_mapping_type":"double",
"mapping":{
"type":"double"
}
}
}]
}
删除创建的index:
重新创建,在索引中加入dynamic_templates:
@Test
public void contextLoads() {
if (!elasticsearchTemplate.indexExists(NumberTestDocument.class)) {
elasticsearchTemplate.createIndex(NumberTestDocument.class);
}
//加入动态模板json
String templateJson ="{\"dynamic_templates\":[{\"all_to_double\":{\"match_mapping_type\":\"double\",\"mapping\":{\"type\":\"double\"}}}]}";
//配置映射
elasticsearchTemplate.putMapping(NumberTestDocument.class.getAnnotation(Document.class).indexName(),NumberTestDocument.class.getAnnotation(Document.class).type(),templateJson);
List<IndexQuery> queries = new ArrayList<>();
for (int i = 0; i < 50; i++) {
long mostSignificantBits = UUID.randomUUID().getMostSignificantBits();
BigDecimal b = new BigDecimal(Math.random() * 100);
double f1 = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
String uuid = String.valueOf(mostSignificantBits);
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(uuid);
indexQuery.setObject(new NumberTestDocument().setId(mostSignificantBits).setNumber(f1).setCharacter(uuid));
indexQuery.setIndexName(NumberTestDocument.class.getAnnotation(Document.class).indexName());
indexQuery.setType(NumberTestDocument.class.getAnnotation(Document.class).type());
queries.add(indexQuery);
elasticsearchTemplate.bulkIndex(queries);
}
}
我们查看新的映射可以发现动态模板已经成功加入(这里同样注意number字段的type,已经更新为double):
试试聚合:
聚合结果正常,问题解决。
相同问题链接:戳我跳转
更多推荐
已为社区贡献1条内容
所有评论(0)