在 Amazon DocumentDB 里处理 Decimal128类型数据的解决方案

news2024/11/16 8:49:58

一道简单的数学题

在开始今天的内容之前,我们先计算一道简单的数学题。0.1 X 0.2 =?我相信很多人都笑了,0.02,这是一个孩童都可以回答得出的答案。我们用这道数学题问一下计算机,看看结果又是怎样。

亚马逊云科技开发者社区为开发者们提供全球的开发技术资源。这里有技术文档、开发案例、技术专栏、培训视频、活动与竞赛等。帮助中国开发者对接世界最前沿技术,观点,和项目,并将中国优秀开发者或技术推荐给全球云社区。如果你还没有关注/收藏,看到这里请一定不要匆匆划过,点这里让它成为你的技术宝库!

欢迎第一位选手 Java 入场

Java Code:
class Main {
 public static void main(String[] args) {
   System.out.println(0.1 * 0.2);
 }
}

计算机给出了答案:

0.020000000000000004

怎么样,是不是手心开始出汗了!我们再欢迎第二位选手 Node.Js 入场:

Node.Js Code:
> 0.1
0.1

> 0.2
0.2
> 0.1 * 0.2
0.020000000000000004

还是0.020000000000000004。难道是乘法不行?那我们换加减法!

有请最后一位选手 Golang 入场:

Golang Code:
a := 1024.1
b := a * 100
fmt.Println(b)

102409.99999999999

c := 2.6
fmt.Println(a - c)

1021.4999999999999

这是 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 的原理。

image.png

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 的离线迁移。

Code 部分:
##MongoShell Statement,于 MongoDB 执行
##切换至 poc 数据库
use poc;

##创建 origin 数据表并插入两条测试数据,value 字段为 Decimal128
db.origin.insertMany( [
{"_id": 1,  "item": "Byte", "value": Decimal128("1.333333") },

{ "_id": 2, "item": "Bit", "value": Decimal128("2.666666")  }
] )

##结果返回为插入成功
{ acknowledged: true, insertedIds: { '0': 1, '1': 2 } }

##验证一下数据是否存在
db.origin.find();
##返回结果确认数据创建成功;
[
  { _id: 1, item: 'Byte', value: Decimal128("1.333333") },
  { _id: 2, item: 'Bit', value: Decimal128("2.666666") }
]

##转换开始,将 value 字段的 Decimal128格式转换为字符串 String 并另存新字段/列,取名为 newvalue,并将聚合之后的新表输出保存为 poc 数据库下以 newtable 为名的新表

db.getSiblingDB("poc").origin.aggregate( [
{
$addFields: {
newvalue: { $toString: "$value" }
}
},
{ $out : "newtable" }
] )

##确认一下输出是否成功,在看到原始表 origin 之外,增加了一张新表 newtable
show tables;

##得到结果
newtable
origin

##查看一下转换之后新表 newtabl e里面的数据
db.newtable.find();

##结果返回可以看出除了原始表 origin 里的_id,item,value 三个字段之外,新增了一个字段 newvalue,其值与原始 decimal128格式的 value 字段,数值相等,且为字符串 String

[
  {
    _id: 1,
    item: 'Byte',
    value: Decimal128("1.333333"),
    newvalue: '1.333333'
  },
  {
    _id: 2,
    item: 'Bit',
    value: Decimal128("2.666666"),
    newvalue: '2.666666'
  }
]

#经过比对后,数据无误,我们删除原始 decimal128格式的 value 字段
db.newtable.updateMany(
{ "_id": { $gt: 0 } },

{ $unset: { value : "" } 
}
)

##并将 string 格式的 newvalue 重命名为 value
db.newtable.updateMany(
{ "_id": { $gt: 0 } },

{ $rename: { "newvalue": "value"}
 }
)

##返回两条数据修改完成,本段代码为控制台返回,无需执行
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 2,
  modifiedCount: 2,
  upsertedCount: 0
}

##我们确认一下数据
db.newtable.find();

##返回数据中 value 字段已经是 string 格式,本段代码为控制台返回,无需执行
[
  { _id: 1, item: 'Byte', value: '1.333333' },
  { _id: 2, item: 'Bit', value: '2.666666' }
]
##使用 Mongo Shell 原生客户端登录 Amazon DocumentDB
##Bash Statement,其中 YOUR_DOCUMENTDB_ENDPOINT 请使用您环境的##DocumentDB 终端节点地址替换,YOUR_USER_NAME 请使用您环境的 DocumentDB 用户##替换,本操作使用了 DocumentDB 自定义参数组并关闭了 TLS,在您生产环境,建议保留##TLS 处于启用状态
mongosh --host YOUR_DOCUMENTDB_ENDPOINT -u YOUR_USER_NAME -p

##输入用户密码登陆
Enter password: *************

##DocumentDB Statement
##切换至 poc 数据库
Use poc;

##查看数据表
show tables;

##返回为空,当前我们的数据库中没有数据表存在;

MongoDB 向 DocumentDB 迁移

除了可以使用 MongoDB 原生的 mongodump/mongorestore 进行数据的迁移,我们还可以使用 Amazon Data Migration Service(DMS)以 MongoDB 为数据源,以 Amazon DocumentDB 为数据目标,进行数据迁移,本例采用后者

1.通过控制台找到 DMS 服务,并点选进入 DMS 控制台

image.png

2.点击左侧菜单栏的【子网组】,然后点击右上角的【创建子网组】

image.png

3.创建一个自定义子网组。如果您的环境是 MongoDB 与 DocumentDB 之间,存在有专线或者 VPN 构建的私有网络环境,您可以如图所示创建一个位于私有子网的自定义子网组,否则,请创建一个位于公有子网的自定义子网组。

image.png

4.点击【创建子网组】,完成子网创建

5.创建复制实例

image.png

image.png

  1. 如果您的环境是 MongoDB 与 DocumentDB 之间,存在有专线或者 VPN 构建的私有网络环境,您可以如图所示反选【公开访问】功能,否则,请勾选【公开访问】功能。

image.png

7.创建终端节点

image.png

7.1 创建以 MongoDB 为引擎的源终端节点

image.png

7.2 按照您的实际情况替换红框内容

image.png

7.3 创建以 Amazon DocumentDB 为目标的目标终端节点

image.png

image.png

7.4 使用 Secret Manager 来管理 DocumentDB 的账号信息(可选)

详情可以阅读另一篇专题 blog,请点击这里

  1. 创建迁移任务

image.png

8.1 使用我们之前创建的复制节点,源终端节点,目标终端节点创建一个迁移任务

image.png

8.2 在表映像部分,我们创建一个选择规则,对 poc 数据库下的newtable 数据表做选中,然后点选创建任务。

image.png

8.3 等待迁移任务加载完成,进度到达100%

image.png

至此存量数据已经通过本方案结合DMS全部迁移至 DocumentDB 下,并且完成了 Decimal128向 string 数据格式的转换。我们来做一个验证。

##登陆到 DocumentDB
##DocumentDB Statement
mongosh --host YOUR_DOCUMENTDB_ENDPOINT -u YOUR_USER_NAME -p

##输入用户密码登陆
Enter password: *************

##切换至 poc 数据库
use poc;

##查看数据表
show tables;

##数据表已经由 DMS 同步到了 DocumentDB
newtable

##验证一下数据
db.newtable.find();

##结果返回符合我们预期
[
  { _id: 1, item: 'Byte', value: '1.333333' },
  { _id: 2, item: 'Bit', value: '2.666666' }
]

将 Decimal128转换为 Java BigDecimal

通过之前的解决方案,我们已经成功的把 Decimal128转换成为 String 存储在数据库中,实现了精度的保留,但是 string 格式保存的数值无法参与计算,我们应该如何解决这个难题?

在 Java 语言中,Decimal128并不能被直接使用,需要专为 BigDecimal 之后,再进行各类处理与运算。我们知道 Decimal128是基于 String 的一种延展,那 String 能否按照这个思路进行处理呢?

答案是可以的,我们可以借助 Java 的一个公共类 BigDecimal 实现我们的需求。以下为 Java 的示例代码,展示我们如何利用这个公共类,进行格式的双向转换,可供参考。

##Java Code
##Transfer String to Java BigDecimal
##引用 BigDecimal 公共类
Import java.math.BigDecimal;
##定义公共类 String2BD
Public class String2BD{
	public static void main(String[ ] args){
	String inputstring = “12.3456”;
	BigDecimal bd = new BigDecimal(inputstring);
	System.out.printIn(bd);
}
}

将输入字符串“12.3456“转换得到数字12.3456,可用于从数据库中读取字符串格式数据后转换为 Java 的 BigDecimal 格式。

##Java Code
##Transfer Java BigDecimal to String
##引用 BigDecimal 公共类
Import java.math.BigDecimal;
##定义公共类 BD2String
Public class BD2String{
	BigDecimal inputbd = new BigDecimal(65.4321)
	String outputstring = inputbd.toString();
	System.out.println(outputstring);
}

将 BigDecimal 格式65.4321转换得到字符串“65.4321“,可将结果以字符串格式存回数据库。

总结

用本方案使用 String 替代了 Decimal128,完成了存量数据的迁移,对于新增数据,在保证效率的前提下,通过 Java 的 BigDecimal 公共类实现 String 与 BigDecimal 的双向转换,解决了 DocumentDB 中需要使用 Decimal128格式的需求。DocumentDB 新功能持续发布中,敬请关注。

参考链接:

1.快速理解 Decimal

What is a Decimal? Definition, Properties, Types, Examples, Facts

2.使用 Secret Manager 来管理 DMS Endpoints

Manage your AWS DMS endpoint credentials with Amazon Secrets Manager | Amazon Database Blog

3.Java Public Class BigDecimal from Oracle

BigDecimal (Java Platform SE 8 )

本篇作者

image.png

付晓明

亚马逊云解决方案架构师,负责云计算解决方案的咨询与架构设计,同时致力于数据库,边缘计算方面的研究和推广。在加入亚马逊云科技之前曾在金融行业IT部门负责互联网券商架构的设计,对分布式,高并发,中间件等具有丰富经验。

文章来源:https://dev.amazoncloud.cn/column/article/6309d3e2d4155422a4610a4d?sc_medium=regulartraffic&sc_campaign=crossplatform&sc_channel=CSDN 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/818008.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于我对刚开始学Java的小白想分享的内容:

编程是很有魅力的,让很多人为之痴迷 如果你是初学者,俗称小白,不妨看看下述内容: 文章目录 1. Java 简介1.1 特性介绍1.简单性2. 面向对象3. 分布式4. 健壮性5. 安全性6. 体系结构中立7. 可移植性8. 解释型9. 高性能10. 多线程11…

【javascript】关于path-package

背景 一个老的vue项目,预览pdf文件的时候,电子签章不显示 解决方案 由于是老项目,升级版本存在风险,然后又找到一些解决方案,都是修改源码,修改源码就引出了今天的主题 path-package,我们需要…

maven项目、springboot项目复制文件进来后没反应、不编译解决方法

问题如下 把文件复制进springboot项目后,没反应,不编译。 解决 在maven工具框中选择compile工具,运行即可。

cicd实验

系列文章目录 文章目录 系列文章目录一、1.2. 二、安装并使用1.安装gitlab2.//Jenkins安装3. 总结 一、 1. 2. 二、安装并使用 需要三台服务器一台安装gitlab 192.168.169.10 第二台负责 安装jenkins 192.168.169.20 第三台是负责业务 192.168.169.30 1.安装gitlab yum in…

JVM总结笔记

JVM JVM是什么?JVM 的主要组成部分JVM工作流程JVM内存模型直接内存与堆内存的区别:堆栈的区别Java会存在内存泄漏吗?简述Java垃圾回收机制垃圾收集算法轻GC(Minor GC)和重GC(Full GC)新生代gc流程JVM优化与JVM调优 JVM是什么? JVM是Java Virtual Mach…

【自用记录】常见的第三方接口加密签名方式(ASCll码字典序、URL键值对、 SHA-256加密、MD5加密)

案例1: 案例2: 以上第三方接口都用类似的加密签名方式,两者有类似的部分: 方案1的: $kdata = array(parkId=>$parkId,ts => $ts,serviceCode=>getParkingPaymentList,reqId => $reqId,plateNo => $car_code,//车牌 可为空pageIndex => 1,//第几页page…

15. Spring AOP 的实现原理 代理模式

目录 1. 代理模式 2. 静态代理 3. 动态代理 3.1 JDK 动态代理 3.2 CGLIB 动态代理 4. JDK 动态代理和 CGLIB 动态代理对比 5. Spring代理选择 6. Spring AOP 实现原理 6.1 织入 7. JDK 动态代理实现 8. CGLIB 动态代理实现 9. 总结 1. 代理模式 代理模式&#xf…

<Git/Gerrit>版本控制Git以及代码评审Gerrit常见的开发操作

下载安装,环境变量配置直接百度; 1.代码拉取: 操作步骤:在正确配置完git的条件下:在本地文件夹下:右键–Git Bash -Here: 出现如下弹窗: 在黑窗口输入:代码拉取路径(一般都是把命令和路径在外面写好,直接粘贴(在窗口右键,Paste,回车)) 代码…

linux系统磁盘性能监视工具iostat

目录 一、iostat介绍 二、命令格式 三、命令参数 四、参考命令:iostat -c -x -k -d 1 (一)输出CPU 属性值 (二)CPU参数分析 (三)磁盘每一列的含义 (四)磁盘参数分…

AI生成式视频技术来临:Runway Gen-2文本生成视频

Runway Gen-2的官方网站提供了一种文本生成视频的工具。以下是对该工具的介绍: 文本生成视频:Runway Gen-2是一个创新的在线工具,可以将文本转化为视频。用户只需输入文本描述或句子,Runway Gen-2就能自动生成相应的视频内容。这…

uni-ajax网络请求库使用

uni-ajax网络请求库使用 uni-ajax是什么 uni-ajax是基于 Promise 的轻量级 uni-app 网络请求库,具有开箱即用、轻量高效、灵活开发 特点。 下面是安装和使用教程 安装该请求库到项目中 npm install uni-ajax编辑工具类request.js // ajax.js// 引入 uni-ajax 模块 import ajax…

【最短路算法】SPFA

引入 在计算机科学的世界里,算法就像是星空中的繁星,各自闪烁着智慧的光芒。它们沉默而坚定,像是一群不语的哲人,默默地解答着世界的问题。 算法的步骤,如同优美的诗行,让复杂的问题在流转的字符中得以释…

PHP8的常量-PHP8知识详解

常量和变量是构成PHP程序的基础,在PHP8中常量的这一节中,主要讲到了定义常量和预定义常量两大知识点。 一、定义常量 定义常量也叫声明常量。在PHP8中,常量就是一个标识符(名字),一旦定义(声明&…

ansible-playbook编写 lnmp 剧本

ansible-playbook编写 lnmp 剧本 vim /opt/lnmp/lnmp.yaml执行剧本 ansible-playbook lnmp.yaml

WebDAV之π-Disk派盘 + DEVONthink

DEVONthink是由一家来自德国的老牌软件开发商发布的「知识管理」软件,运行于 Mac/iOS 平台。官方自己定位为全方位(中文环境下略有遗憾)帮助你实现知识管理,可以称之为“模块级”应用了。 DEVONthink还支持各种云服务同步,文件管理您的终极文件管理应用、文件、图片与连接远…

Android Ble蓝牙App(一)扫描

Ble蓝牙App(一)扫描 前言正文一、基本配置二、扫描准备三、扫描页面① 增加UI布局② 点击监听③ 扫描处理④ 广播处理 四、权限处理五、扫描结果① 列表适配器② 扫描结果处理③ 接收结果 六、源码 前言 关于低功耗的蓝牙介绍我已经做过很多了&#xff0…

基于SHARC+®单核的ADSP-21567KBCZ6、ADSP-21566BBCZ4、ADSP-21566KBCZ4高性能DSP处理器产品

ADSP-2156x 处理器的速度高达 1 GHz,属于 SHARC 系列产品。ADSP-2156x 处理器基于 SHARC 单核。ADSP-2156x SHARC 处理器是 SIMD SHARC 系列数字信号处理器 (DSP) 中的一款产品,采用 ADI 的超级哈佛架构。这些 32 位/40 位/64 位浮点处理器已针对高性能音…

Practice3|922. 按奇偶排序数组 II、143. 重排链表

922. 按奇偶排序数组 II 1.题目: 给定一个非负整数数组 nums, nums 中一半整数是 奇数 ,一半整数是 偶数 。 对数组进行排序,以便当 nums[i] 为奇数时,i 也是 奇数 ;当 nums[i] 为偶数时, i…

将临时表的所有数据添加到另一张表的某个字段

临时表 目标表 SmsCatagory smsCatagory new SmsCatagory(); smsCatagory.setGasStationId(); smsCatagory.setType(); ...... sendTaskService.batchInsertByTemp(smsCatagory);xml&#xff1a; <insert id"batchInsertByTemp">INSERT INTO cs_sms_cata…

[每日习题]进制转换 参数解析——牛客习题

hello,大家好&#xff0c;这里是bang___bang_&#xff0c;本篇记录2道牛客习题&#xff0c;进制转换&#xff08;简单&#xff09;&#xff0c;参数解析&#xff08;中等&#xff09;&#xff0c;如有需要&#xff0c;希望能有所帮助&#xff01; 目录 1️⃣进制转换 2️⃣参…