本文转载自微信公众号「小姐姐味道」,作者小姐姐养的狗 。转载本文请联系小姐姐味道公众号。
没错。前端,就是用来坑后端的。
我也只能在这里,发表这样无耻的言论。因为xjjdog的修为主要体现在后端上,所以爱屋及乌。这体现了斗争是人类的基本属性:程序员除了要干产品经理、项目经理,内部也并不是铁板一块。
不过这次要聊的问题,确实是很坑。它几乎断送了整个系统,让暴躁的老板脸上爆炸式的长满了痘痘。
它的影响不限于此。扩大到整个业界:
原来能发财的,破产了。
原来能结婚的,分手了。
原来能摸鱼的,加班了。
原来搞前端的,搞后端了。
原来能退休的,延期了。
原来能活着的,去世了。
原来能双休的,大小周了。
为什么牛气的js,会有这么大的威力?请听我细细道来。
1. 事出有因
就如标题所说,这个会和雪花算法有关。
我们有个系统,使用的是MySQL数据库,所以在数据库的主键选择上,使用的是自增ID。
- ID INT PRIMARY KEY AUTO_INCREMENT
这样的ID简单流畅,但有一系列的弊端,不过用在一般的系统上,够用了。
在临上线之前,项目组邀请公司里最牛x的架构师,对项目进行了一次集中体检。其中的一项重要举措,就是针对于ID生成器的。
“不知道现在的开发系统,都至少要使用Snowflake作为ID生成器么?” 架构师对自增ID的方案非常的不满意。
它指出,哪怕你使用UUID,在遇到系统扩容、分库分表、数据迁移等场景的时候,也比自增ID强。
大家伙一讨论,觉得非常合理。UUID太无序,美团Leaf这种又太复杂,还不如直接使用老掉牙的Snowflake,直接生成最简单的ID即可。
类似于这种。
- 527574217068392807
- 527574217068392808
为了让你有个直观的认识,我们看一下Java中Long的最大值。
- 9223372036854775807
再看一下Int的最大值。
- 2147483647
可以看到生成的Snowflake ID,是比Int大,比Long小的数值(和最大的比较),所以在数据库中使用bigint存储,再好不过了。
说干就干,批量脚本一改,主键就变大变长了~~~
2. 问题发生
别说,这样子的ID,看起来还比较顺眼。ID在URL里传递,在formdata里传递,一看就比较的专业!
- /edit.do?id=527574217068392810
系统按照建议改完之后,单元测试很流畅。黑盒测试草草的点了一下,就算通过了。
灵异事件是被客户发现的。
客户说,很多记录,无法编辑、无法删除。提示找不到记录。
很多公司的尿性你也是知道的,和客户交流的,通常不太懂技术。对着客户的屏幕用牛x的手机拍照,原图发过来就有十几MB。但灵异的是图片大,内容却模模糊糊。
后端程序员,眯着眼睛打开图片,把里面显示的ID给抠出来,放在系统里一查。
没有此记录。
肯定是眯眼的姿势有问题。后端程序员不得不再录一遍。可惜的是,依然没有这条记录。
没办法,只好把客户的数据库拷贝一份过来。页面上一点击,果然有问题!
浏览器response里返回的数据竟然和preview里的不一样
3. 问题验证
也就是说,一个好好的数字:527183991665594368,经过浏览器一翻译,变成了527183991665594400。
我们在浏览器的devtools里面调试一下。
为了进一步验证,我们从typescript到js,都试验一下。
- # cat test.ts
- let a = 527183991665594368;
- console.log(a);
- # tsc test.ts
- # cat test.js
- var a = 527183991665594368;
- console.log(a);
- # node test.js
- 527183991665594400
可以看到,在整个js的生态里,都存在这个问题,真是坑坏了后端。
4. Why?
这是因为。在JavaScript中,存在两种数字。Number和BigInt。最常用的,就是number。
最大的Number,叫做Number.MAX_SAFE_INTEGER,它的值为:
2^53-1 或者
+/- 9,007,199,254,740,991
众所周知,Java中的Long,是64位的。Js中的这个安全Integer,完全达不到Java中定义的长度。
这就是万恶的IEEE_754规范,它在Long长度大于17位时会出现精度丢失的问题。
在最新的TypeScript3.2中,可是直接使用BigInt这个类型进行编码,或者使用long.js这种封装后的苦,但还是太麻烦了,需要编码太多,而且还可能漏掉。
使用数字类型,传输数据,实在是不太靠谱,转来转去,就物是人非了。
最好的方式,就是使用string进行传递。哪怕以后后台ID的长度变成了128位的,也不惧怕这种转换。
在Java中,如果你用的是jackson,直接通过注解,就可以完成字符串更改,不需要再改动数据库。
- @JsonSerialize(using=ToStringSerializer.class)
- private Long id;
这问题,明显不是后端的锅。后端传递了正确的数据到前端,能不能处理、处理的正确不正确,根本和后端一点关系都没有。JS的这种按照规范的不规范处理,已经让很多人踩坑。不管是萌新,还是老鸟,依然前赴后继的掉到坑里,不得不说这个特性是非常反人类的。
不过,我们还是在后端解决了。谁让咱走的是全栈路线呢?必要时,连产品的活儿都能做!
作者简介:小姐姐味道 (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。