Open Bpazy opened 1 year ago
库存的出入库明细搜索中的值与数据库不一致,数据库时正确的数值,ElasticSearch 中因为科学计数法的问题丢失了精度。
首先调查方向是 ES 中 subtotal 字段的类型不正确导致丢失精度,但是查看了下发现 subtotal 的类型是 scaled_float,完全可以存放该数字。
于是调查方向就改变为 ES 中新增记录时的入参。 出入库明细搜索的原理为:stock 工程在出入库时,会以流水级别发送 kafka 消息给 stock-search-index, index 收到消息后再将消息存入 ES 中。
于是断点在 index 收消息处,然后构造一个相同的数字的手工入库单:数量为 111111,成本为 1111.11。入库后,收到的消息如下:
可以看见收到的 JSON 已经是错误的值了。
于是查看发消息的代码,再次打断点:
可以看到 subtotal 已经变成科学计数法了,但是通过利用 BigDecimal 转出来的字符串还是能正常展示的。
java 对任何超过 9999999 的值都会用科学计数法表示,这一点可以从 Double.java 中的注释看到:
fastjson 也使用了相同的处理:
那怎么避免呢?有多个方向。
将代码中的类型改为 BigDecimal,影响范围可能会很大,需要判断代码是否能够支撑。实际判断下来影响范围很小。
在上面的问题原因中我们知道,利用 BigDecimal 可以规避这个问题,那只要我们找一下 fastjson 是否有针对这种情况的特殊处理即可,经过查询,找到以下方法:
public static void main(String[] args) { SerializeConfig config = new SerializeConfig(); config.put(Double.class, new DoubleSerializer("#.######")); String json = JSON.toJSONString(new Content(), config); System.out.println(json); } @Data private static class Content { private BigDecimal a = new BigDecimal(12345678.1234); }
利用 DoubleSerializer 处理即可,其原理类似于 BigDecimal,内部使用的是 DecimalFormat 来处理 double 的科学计数问题,但是这中方向会带来几个问题:
综上 方向1 是比较好的,代码简单,影响可控,易于理解。我们只要修改 stock 工程代码即可,这样就能获得正确的 json,即使 index 仍用 double 来接,也可以正确的写入 ES 中。
背景
库存的出入库明细搜索中的值与数据库不一致,数据库时正确的数值,ElasticSearch 中因为科学计数法的问题丢失了精度。
调查
首先调查方向是 ES 中 subtotal 字段的类型不正确导致丢失精度,但是查看了下发现 subtotal 的类型是 scaled_float,完全可以存放该数字。
于是调查方向就改变为 ES 中新增记录时的入参。 出入库明细搜索的原理为:stock 工程在出入库时,会以流水级别发送 kafka 消息给 stock-search-index, index 收到消息后再将消息存入 ES 中。
于是断点在 index 收消息处,然后构造一个相同的数字的手工入库单:数量为 111111,成本为 1111.11。入库后,收到的消息如下:
可以看见收到的 JSON 已经是错误的值了。
于是查看发消息的代码,再次打断点:
可以看到 subtotal 已经变成科学计数法了,但是通过利用 BigDecimal 转出来的字符串还是能正常展示的。
问题原因
java 对任何超过 9999999 的值都会用科学计数法表示,这一点可以从 Double.java 中的注释看到:
fastjson 也使用了相同的处理:
解决方案
那怎么避免呢?有多个方向。
方向1:从类型彻底解决问题
将代码中的类型改为 BigDecimal,影响范围可能会很大,需要判断代码是否能够支撑。实际判断下来影响范围很小。
方向2:从序列化着手
在上面的问题原因中我们知道,利用 BigDecimal 可以规避这个问题,那只要我们找一下 fastjson 是否有针对这种情况的特殊处理即可,经过查询,找到以下方法:
利用 DoubleSerializer 处理即可,其原理类似于 BigDecimal,内部使用的是 DecimalFormat 来处理 double 的科学计数问题,但是这中方向会带来几个问题:
结论
综上 方向1 是比较好的,代码简单,影响可控,易于理解。我们只要修改 stock 工程代码即可,这样就能获得正确的 json,即使 index 仍用 double 来接,也可以正确的写入 ES 中。