亚马逊AWS官方博客
在Amazon DocumentDB里处理Decimal128类型数据的解决方案
一道简单的数学题
在开始今天的内容之前,我们先计算一道简单的数学题。0.1 X 0.2 =?我相信很多人都笑了,0.02,这是一个孩童都可以回答得出的答案。我们用这道数学题问一下计算机,看看结果又是怎样。
欢迎第一位选手Java入场:
计算机给出了答案:
0.020000000000000004
怎么样,是不是手心开始出汗了!我们再欢迎第二位选手Node.Js入场:
还是0.020000000000000004。难道是乘法不行?那我们换加减法!
有请最后一位选手Golang入场:
这是Java亦或是Golang的问题吗?当我们继续在Python,Ruby等主流语言上得到相同的结果时,是否会让你感觉世界观遭到了颠覆?
不用怀疑自己,错的是计算机。为什么这么简单的数学题,强大如Intel/AMD/Graviton 的CPU却不能给出正确答案呢?
我们来看下真正的原因。其实是因为在十进制的数学体系中,二进制浮点类型并不适合用来表现或者描述数据本身。譬如0.1这个数字,如果使用二进制浮点类型来描述它时,它会被表现为0.0001100110011001101,这导致了很多数值在计算中会产生精度丢失或者结果偏差。
当然,这在我们的日常生活中,并不会带来太大的问题。譬如天气预报中的温度与湿度指标,数值仅用作体感的参考,35.79999992摄氏度并不会让你感觉比36摄氏度更凉爽或者比35.5摄氏度更酷热;您在超市购物时,收银员也不会非要让你支付12.133333元相比12元多出来的0.133333元,但是在一些高精度计算的场景中,数值精度的丢失,会对最终的结果产生严重甚至完全相反的结果。那我们应该如何在保留数值精度的前提下,对数值进行计算呢?
Decimal数据格式
与我们常见的Float,Double等近似保存的数据类型不同,Decimal保存了精确的原始数值。可以说Decimal专门为十进制数学体系设计,弥补了二进制转述小数部分的缺憾,我们通过一张示意图来理解decimal的原理。
MongoDB中的Decimal
作为广泛使用的文档型数据库,MongoDB也受到数值精度问题的困扰。为了能够实现高精度数值的存储与还原,decimal128应运而生,可以在特别微小数值的保存场景上,提供技术层面的支持。
亚马逊云科技推出了托管的兼容MongoDB的云原生文档数据库Amazon DocumentDB,依托计算与存储分离的架构,在很多不同的场景下,帮助客户实现了集群快速扩容,自动流式备份,计算层扩缩容,存储层自动扩容等诸多云原生数据库的功能,简化了数据库运维工作与提高了工作效率。不过截至到2022年7月,DocumentDB暂不支持Decimal128格式的数据,该如何解决这个问题呢?
通过现象看本质,大家都是”String”
数字 与小数,本身也属于字符的一种,所以Decimal本身也是基于字符格式的一种延展。Decimal128(14.999999)与Decimal(’14.999999’)存在什么本质上的不同,留给各位技术小伙伴们思考了。下面我们通过一个解决方案来解决DocumentDB与Decimal128的兼容问题。大家一起来吧!
本方案描述了如何短暂停机,将Decimal128数据格式转换为String的步骤,这解决了存量数据的格式转换问题,并通过Amazon Data Migration Service实现了MongoDB向DocumentDB的离线迁移。
MongoDB向DocumentDB迁移
除了可以使用MongoDB原生的mongodump/mongorestore进行数据的迁移,我们还可以使用Amazon Data Migration Service(DMS)以MongoDB为数据源,以Amazon DocumentDB为数据目标,进行数据迁移,本例采用后者
1.通过控制台找到DMS服务,并点选进入DMS控制台
2.点击左侧菜单栏的【子网组】,然后点击右上角的【创建子网组】
3.创建一个自定义子网组。如果您的环境是MongoDB与DocumentDB之间,存在有专线或者VPN构建的私有网络环境,您可以如图所示创建一个位于私有子网的自定义子网组,否则,请创建一个位于公有子网的自定义子网组。
4.点击【创建子网组】,完成子网创建
5.创建复制实例
6. 如果您的环境是MongoDB与DocumentDB之间,存在有专线或者VPN构建的私有网络环境,您可以如图所示反选【公开访问】功能,否则,请勾选【公开访问】功能。
7.创建终端节点
7.1 创建以MongoDB为引擎的源终端节点
7.2 按照您的实际情况替换红框内容
7.3 创建以Amazon DocumentDB为目标的目标终端节点
7.4 使用Secret Manager来管理DocumentDB的账号信息(可选)
详情可以阅读另一篇专题blog,请点击这里
8. 创建迁移任务
8.1 使用我们之前创建的复制节点,源终端节点,目标终端节点创建一个迁移任务
8.2 在表映像部分,我们创建一个选择规则,对poc 数据库下的newtable数据表做选中,然后点选创建任务。
8.3 等待迁移任务加载完成,进度到达100%
至此存量数据已经通过本方案结合DMS全部迁移至DocumentDB下,并且完成了Decimal128向string数据格式的转换。我们来做一个验证。
将Decimal128转换为Java BigDecimal
通过之前的解决方案,我们已经成功的把Decimal128转换成为String存储在数据库中,实现了精度的保留,但是string格式保存的数值无法参与计算,我们应该如何解决这个难题?
在Java语言中,Decimal128并不能被直接使用,需要专为BigDecimal之后,再进行各类处理与运算。我们知道Decimal128是基于String的一种延展,那String能否按照这个思路进行处理呢?
答案是可以的,我们可以借助Java的一个公共类 BigDecimal实现我们的需求。以下为Java的示例代码,展示我们如何利用这个公共类,进行格式的双向转换,可供参考。
将输入字符串“12.3456“转换得到数字12.3456,可用于从数据库中读取字符串格式数据后转换为Java的BigDecimal格式。
将BigDecimal格式65.4321转换得到字符串“65.4321“,可将结果以字符串格式存回数据库。
总结
用本方案使用String替代了Decimal128,完成了存量数据的迁移,对于新增数据,在保证效率的前提下,通过Java的BigDecimal公共类实现String与BigDecimal的双向转换,解决了DocumentDB中需要使用Decimal128格式的需求。DocumentDB新功能持续发布中,敬请关注。
参考链接:
1.快速理解Decimal
https://www.splashlearn.com/math-vocabulary/decimals/decimal
2.使用Secret Manager来管理DMS Endpoints
3.Java Public Class BigDecimal from Oracle
https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html