当前位置: 首页 > 后端技术 > Java

存储拆分后,主键唯一性问题如何解决?

时间:2023-04-01 21:57:25 Java

在单库单表的情况下,业务ID可以依靠数据库的自增主键来实现。现在我们将存储拆分到多个地方。如果仍然使用数据库的自增主键,则会复制主键。所以我们不得不面对的选择之一就是ID生成器,它使用唯一的字符串来标识一条完整的记录。这个时候不能用md5或者sha1来消化整条记录,因为后面我们要改这条记录。也不可以使用单机计数器,因为计数器容易重启清零,在多台机器上会出现重复值,违背了无状态服务构建的目标。UUID虽然UUID在大多数语言中都有相关的类库,但除非万不得已,否则我们一般不会使用它。UUID虽然不会重复,但是很长,让人望而生畏。标准的UUID由5部分组成:8-4-4-4-12,共32个十六进制字符。因此,一共有128位。当使用UUID作为数据库的索引时,由于其无序性,会造成索引的随机分布,并且由于数据量巨大,会降低查询性能。而乱序会导致每一次插入UUID数据都会极大的修改主键的b+树,从而产生离散IO,造成性能瓶颈。此外,UUID不可读,因此将其打印在纸质订单上不是一个好主意。UUID在信息安全方面也存在隐患。它的数据计算包括MAC地址的参与。更为著名的是它被用来寻找梅丽莎病毒创造者的位置。MySQL8之后,MySQL8.0引入了函数UUID_TO_BIN,可以把UUID字符串:通过参数把时间的高位放在最前面,解决了UUID插入乱序的问题;去掉无用的字符串“-”,简化存储空间;把字符字符串转换为二进制值存储,空间最终从之前的36字节缩短为16字节。同时还提供了BIN_TO_UUID,支持将二进制值反转为UUID字符串。您不必担心UUID的性能和存储占用的空间。相关插入性能测试结果如下表所示:由于UUID_TO_BIN转换后的结果为16字符段,仅比自增ID多8个字节,最终存储占用空间仅比自身多3G-增量。并且因为UUID可以保证全局唯一性,所以使用UUID的好处远大于自增ID。在海量并发的互联网业务场景中,推荐使用UUID等全局唯一值作为主键。但请记住:在分布式数据库架构中,仅使用UUID作为主键仍然不够。数据库自增ID当数据量巨大时,数据库分库分表后,数据库自增id不能满足标识数据的唯一id;因为每个表按照自己的节奏自增,会造成id冲突,不能满足改造的需要够了。但是在分布式环境下,timestamp也不是一个好的选择。即使你在机器上安装了ntpd时间同步,由于网络和机器的不同,电脑的时钟总会不一样,你的时间戳总会重复。为了解决这个问题,需要添加一些其他的标识,比如机器的ID,或者更细分的信息,以减少时间碰撞。这种自定义ID生成器只适用于特定业务,你会发现它本质上是雪花算法的变种。全局ID生成服务可以设计一个全局ID生成服务,每次查找服务都要询问主键,这样虽然可以做到业务间全局唯一,但是完全依赖于全局ID生成服务,高度依赖。一旦服务宕机,将影响所有相关的依赖服务。比如使用Redis的计数器,原子自增的优点是使用内存,并发性能好,但是存在数据丢失;自增数据泄露问题Snowflake算法TwitterSnowflake算法生成一个64bit的long值,默认字符字符串的长度为19位,分为4部分,基本保持自增,包括四个组成部分未使用:1bit,最高位为符号位,0表示正,1表示负,固定为0时间戳:41bit,毫秒级时间戳(41位长度可使用69年)标识位:5bit数据centerID,5bitworkingmachineID,两个标识位组合最多可以支持部署1024个节点(2^10=1024个节点)如果是分布式应用部署,需要保证标识位id的每个工作进程都是不同的序号:12bit递增序号,表示节点在毫秒内产生重复,序号表示唯一性,12bit通过序号每毫秒可以产生4096个ID,1毫秒可以产生4096个唯一ID,那么1秒可以生成4096*1000=409w个ID默认雪花算法是64位,具体长度可以自己配置。如果想运行更长时间,增加时间戳的位数;如果需要支持更多的节点部署,增加标识位的长度;如果并发高,增加序列号的位数针对场景定制SnowFlake算法的优点:高性能、高可用:生成时不依赖数据库,完全在内存中生成。高吞吐量:每秒可产生数百万个自增ID。SnowFlake算法的缺点:依赖与系统时间的一致性。如果回调或更改系统时间,可能会导致ID冲突或重复的应用场景。因为雪花算法是有序自增的,所以保证了MySQL中B+Tree索引结构的高插入。性能因此,在日常业务使用中,雪花算法多应用于数据库的主键ID和业务关联的主键存在的问题。当机器的标识位一致且标识位重复时,雪花ID也可能重复。例如:服务同样经过集群Deployment,部分机器出现相同的时钟回调问题。为什么会出现时钟回调问题?有人篡改了主机的系统时间。在集群中,可以进行全局时钟同步,从而修改机器的本地时间时钟回调。雪花算法的影响如果本地时间被篡改,存在ID重复的风险,趋势无法满足。方案一:想办法检测时钟回调,然后制定相应的策略方案二:探索一种不完全依赖时间戳的ID生成方法来保证雪花算法,或者直接用其他策略代替时间戳JS吧值得注意的是雪花算法在JavaScript中有一个坑。后端返回ID时需要使用String类型,不能使用Long类型,否则会出现意想不到的错误。这是因为。在JavaScript中,有两种数字。数字和BigInt。最常用的是数字。最大的数字称为Number.MAX_SAFE_INTEGER,其值为:2^53-1或+/-9,007,199,254,740,991。众所周知,Java中的Long是64位的。Js中这个安全的Integer根本没有达到Java中定义的长度。这就是邪恶的IEEE_754规范,当Long的长度大于17位时,它会出现精度丢失的问题。常见实现方案百度(uid-generator)uid-generator由百度技术部开发,项目地址:uid-generatoruid-generator是基于Snowflake算法的,和原来的snowflake算法不同的是uid-generator支持自定义时间戳、工作机ID和序列号等,uid-generator采用用户自定义的workId生成策略。uid-generator需要和数据库配合使用,需要增加一张WORKER_NODE表。当应用程序启动时,它会向数据库表中插入一条数据。插入成功后,返回的自增ID为本机的workId。数据由主机和端口组成。美团(Leaf)github地址:Leaf美团的Leaf也是一个分布式ID生成框架。很全面,就是支持号段模式和雪花模式。数段方式:与数据库有关,但不同于数据库主键的自增方式。假设100是一个100、200、300、100个ID的数字范围,每次都可以得到100个ID,性能有明显提升。