一个数据精度引发的血案

前言

最近在做X项目的时候用到了弹性搜索引擎ES(Elasticsearch),在检索遇到了一个诡异的问题,当存储(长)整型数据超过某个值(具体就是百万),就会出现数据精度丢失的情况,比如put下面一个数据

put数据
{
    "uid": 251221233,
    "location": {
        "lat": 22.5405,
        "lon": 113.935
    },
    ......
}

然后get出来,发现uid被转成科学计数,存在精度丢失问题,uid在系统表示用户的身份,出现了偏差导致非常严重的后果,而浮点型数据却没有影响。

{
    "uid": 2.51221e+08
    "location": {
        "lat": 22.5405,
        "lon": 113.935
    },
    ......
}

一、问题定位

项目中首次采用ES,之前对这个搜索引擎了解不多,因此最开始怀疑数据是在搜索引擎那里转坏了,先查资料,后求达人,都没有找到答案,由于ES提供Restful接口,走HTTP协议,通过抓包最后发现get时候数据并没有被修改,那肯定是逻辑代码问题喽。

服务框架采用SRF,存储在ES的数据格式为JSON,编解码使用的是SRF框架的TC库,这个库在后台多个项目中使用过,之前一直都没有遇到过问题,最开始也没有怀疑到它,走了一段弯路。经过定位发现是将json对象转发string的时候出现了数据的改变,如下面的红框代码,出问题就是这一行代码。(这里为了方便其它服务访问ES,封装了一个通用的增删改查的SRF接口进行RPC调用)

JsonValueObjPtr pObj = JsonValueObjPtr::dynamicCast(TC_Json::getValue(httpRsp.getContent()));
JsonInput::readJson(rsp.isExisted, pObj->value["found"], false);
if (rsp.isExisted)
{
    JsonInput::readJson(rsp.sId, pObj->value["_id"], false);
    JsonInput::readJson(rsp.sDataBase, pObj->value["_index"], false);
    JsonInput::readJson(rsp.sTableName, pObj->value["_type"], false);
    rsp.sSource = TC_Json::writeValue(JsonValueObjPtr::dynamicCast(pObj->value["_source"]));
}

走进SRF框架代码,发现TC_Json将所有number数据对象按照double去处理,这样其实也是合理的,但是在转换成string的时候却用了 ostringstream,用流算子做转换的时候会区分数据类型,当数据是整形的时候问题不大,如果是浮点型数据会出现数据被截断,流算子默认按float型数据去处理,这是数据被篡改的原因

问题是数据并不是浮点型,而是整形,而正常用Jce结构体的时候整形转换成json字符串并没有问题,这又是什么原因呢?分析发布正常使用Jce对象的时候都会指定数据类型格式,而TC_Json做解析的时候并没有这样子去做(如下源码),也就是说如果使用TC库去解析json,然后再回写成string,出现大整数或double数据则会出现精度丢失。

JsonValueNumPtr TC_Json::getNum(BufferJsonReader & reader,char head)
{
    bool bFloat=false;
    double dResult=0;

    // ..... 此处省略解析代码
    
    JsonValueNumPtr p = new JsonValueNum();
    p->value=dResult;
    return p;
}

二、解决办法

2.1 TC_Json优化

找到了问题原因,解决起来自然就很容易,TC_Json在进行数据解析的时候指定对数据类型进行指定,避免整形数据转成string当成double型,这样改完之后整形数据再也不会有问题。

    JsonValueNumPtr p = new JsonValueNum();
    p->value=dResult;
    p->isInt=!bFloat;
    return p;

2.2 Double精度问题

改完之后整形数据自然就没啥问题,但是我们知道在计算机系统中,C/C++的浮点数据F/D分别占用32/64位,是按照指数+尾数方式存储,精确范围分别为小数点后6位和15位,采用流算子对double数据进行json转换还是存在精度丢失的问题,虽说浮点型数据在逻辑服务开发工作中比较少用到,但是从框架的角度希望能有一个比较完美的解决方案。

之前miloyip老师讲rapidjson实现的时候,他重点介绍了浮点型数据格式化处理问题,rapidjson处理地非常完美,但代码实现略显复杂,在这里使用标准库提供gcvt函数处理,基本能满足我们的精度要求,代码实现也会显优雅很多。

结语

SRF/TAF框架提供了一些公共函数实现Number到String的转换,大量都采用流算子实现,大家在日常的业务代码开发中,用它处理浮点型的数据要十分注意数据精度丢失问题。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java编程技术

深入浅出一致性Hash原理

在解决分布式系统中负载均衡的问题时候可以使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载...

571
来自专栏進无尽的文章

聊聊程序设计思想之面向接口编程IOP

我们在一般实现一个系统的时候,通常是将定义与实现合为一体,不加分离的,但是有时候最为理想的系统设计规范应是所有的定义与实现分离,尽管这可能对系统中的某些情况有点...

842
来自专栏博客园迁移

日常理解

{ 空 } 1. 什么叫线程安全?servlet是线程安全吗? { 答:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次...

752
来自专栏java一日一条

Java 8 开发顶级技巧

我使用Java 8编码已经有些年头,既用于新的应用程序,也用来迁移现有的应用,感觉是时候写一些我发现的非常有用的“最佳实践”。我个人并不喜欢“最佳实践”这个说法...

381
来自专栏PPV课数据科学社区

如何使用Python对Instagram进行数据分析?

我写此文的目的在于展示以编程的方式使用Instagram的基本方法。我的方法可用于数据分析、计算机视觉以及任何你所能想到的酷炫项目中。 Instagram是最大...

2687
来自专栏大闲人柴毛毛

架构高性能网站秘笈(一)——了解衡量网站性能的指标

服务器如何发送数据? 服务器程序将需要发送的数据写入该程序的内存空间中; 服务器程序通过操作系统的接口向内核发出系统调用; 系统内核将用户态内存空间中的数据复制...

4979
来自专栏织云平台团队的专栏

Tcpdump,从入门到不放弃

tcpdump 是一款抓包分析利器,其灵活的过滤规则和对表达式的支持能够让我们在众多的数据报文中抓取到理想的关键信息。本文在介绍 tcpdump 的基本使用方式...

8062
来自专栏Java帮帮-微信公众号-技术文章全总结

MQ消息中间件(工作+面试)

MQ消息中间件(工作+面试) ? AMQP协议介绍 AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协...

5947
来自专栏风口上的猪的文章

.NET面试题系列[10] - IEnumerable的派生类

IEnumerable分为两个版本:泛型的和非泛型的。IEnumerable只有一个方法GetEnumerator。如果你只需要数据而不打算修改它,不打算为集合...

712
来自专栏为数不多的Android技巧

Android性能优化之虚拟机调优

介绍完 深入学习Android:虚拟机&运行时 之后,很多小伙伴问我,你描述的这些知识结构看起来艰深晦涩高大上,实际工作中能有多大用途呢?今天我就简单举个例子。

841

扫码关注云+社区