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

京东:高并发下,如何保证分布式全局唯一ID的生成?

时间:2023-04-02 09:10:53 Java

出自:blog.csdn.net/LookForDream_/article/details/109355335前言系统的唯一ID是我们在设计系统时经常遇到的问题,也经常为这个问题纠结。本文旨在为读者提供一种生成分布式全局唯一id生成方案的思路,希望对大家有所帮助。不足之处,请指教!!Question为什么需要分布式的全球唯一ID以及分布式ID的业务需求?在复杂的分布式系统中,往往需要对大量的数据和消息进行唯一标识,例如美团点评中的金融、支付、餐饮、酒店猫眼电影等。产品系统中的数据在逐渐增加。数据库分库分表后,需要一个唯一的ID来标识一条数据或信息;特别是,Ian的订单、骑手和优惠券都需要通过唯一ID来标识。这时候就可以生成一个全球唯一的ID。ID系统是非常必要的,ID生成规则部分要求全球唯一性趋势越来越大。聚集索引用于MySQL的InnoDB引擎。由于大多数RDBMS使用Btree数据结构来存储索引,所以在主键的选择上我们应该尽可能的使用它。主键有序保证写入性能单调递增保证下一个ID必须大于上一个ID,比如交易版本号、IM增量消息、排序等特殊要求信息安全如果ID是连续的,恶意用户很容易爬取完成,直接按顺序下载指定的网址即可。如果是订单号,那就危险了。竞争对手可以直接知道我们一天的订单数量,所以在一些应用场景中,ID需要不规则,不规则,这让竞争对手很吃亏。猜测时间戳可以快速了解开发中分布式ID是什么时候生成的。身份证号生成系统的可用性要求高可用发出请求获取分布式ID,服务器要保证99.999%的时间。为我创建一个唯一的分布式ID低延迟发送请求获取分布式ID,服务器必须是快速的,极快的和高QPS的例如,如果同时发送100,000个创建分布式ID的请求,服务器必须能承受一次成功创建100000个分布式ID的通用方案UUIDUUID.randomUUID(),标准类型的UUID包含32个16进制数,用连字符分成5段,36个字符,形式为8-4-4-4-12,非常高性能,本地生成,无网络消耗。有个问题就是入库性能差,因为UUID是无序无序的,无法预测它的生成顺序。它不能生成递增和有序的数字。首先,分布式id一般是逐步使用的,但是根据mysql官方的建议,主键越短越好,每个UUID都很长,所以不是很推荐。使用主键和ID作为主键时,在特定环境下会出现一些问题。比如做DB主键的场景,UUID就很不适用。MySQL官方对索引有明确的描述,B+树索引的拆分。既然分布式ID是主键,那么主键包含索引,mysql索引是通过B+树实现的。每插入一个新的UUID数据,为了优化查询,索引底部的B+树都会被修改,因为UUID数据是乱序的。因此,每插入一次UUID数据,都会对主键的B+树进行大幅度的修改。不是很好。完全乱序插入,不仅会导致一些中间节点分裂,还会白白创建很多不饱和节点。这大大降低了数据库插入的性能。UUID只能保证全局唯一,不满足后续递增趋势,单调递增数据库自增主键单机在分布式中,数据库自增ID机制的主要原理是:数据库自增ID并替换成mysql数据库,这里的replaceinto和insert功能类似,不同的是:replaceinto先尝试插入数据列表,如果发现表中已经存在这一行数据(根据主键或唯一索引判断)),先删除再插入,否则直接插入新数据。REPLACEINTO的意思是插入一条记录。如果表中唯一索引的值遇到冲突,将旧数据替换为REPLACEintot_test(stub)values('b');selectLAST_INSERT_ID();每次插入,我们发现替换原来的数据,ID也会增加,满足增量单调唯一性。在分布式的情况下,并发不多的情况下,可以用这个方案获取一个全局唯一的ID数据库自增ID机制适合分布式集群的分布式ID吗?答案是不适合系统的水平扩展。比如定义了步长和机器数量后,如果要添加机器怎么办?假设有一台机器,编号为:1,2,3,4,5,(步长为1)。这时需要扩容一台机器。你可以这样做:将第二台机器的初始值设置得比第一台高很多。如何进行扩容简直就是一场噩梦,系统水平扩容方案复杂且难以实现。数据库压力还是很大的。每拿到一个ID,都要读写一次数据库,非常影响性能。非常影响性能)基于Redis生成全局ID策略单机版由于Redis是单线程的,本质上保证了原子性,可以使用原子操作INCR和INCRBY来实现INCRBY:设置生长步长集群distributed注意:如果是Redis集群,比如MySQL,需要设置不同的增长步长。同时,密钥必须设置有效期限。您可以使用Redis集群来获得更高的吞吐量。假设一个集群中有5个Redis,可以将每个Redis的值分别初始化为1、2、3、4、5,然后设置步长为5。每个Redis生成的ID为:A:16111621B:27121722C:38131823D:49141924E:510152025但是问题是Redis集群维护维护麻烦,配置是麻烦。因为需要设置单点故障,哨兵值班,但是主要问题是对于一个ID,需要引入整个Redis集群。感觉就像用大锤杀死一只鸡。什么是雪花算法?Twitter的分布式自增ID算法Snowflake最初,Twitter将存储系统从MySQL迁移到Cassandra(Facebook开发的开源分布式NoSQL数据库系统)。因为Cassandra没有顺序的ID生成机制,所以开发了这样一套全球唯一的ID生成服务。Twitter的分布式雪花算法SnowFlake,经过测试,SnowFlake每秒可以生成26万个自增可排序的ID。Twitter的SnowFlake生成的ID可以按时间顺序生成。SnowFlake算法生成ID的结果是一个64Bit整数,是Long类型(转换成字符串后最多19个长度)在分布式系统中不会发生ID冲突(以datacenter和workerID区分),效率更高。在分布式系统中,有一些场景需要全局唯一的ID。要求在分布式环境下,全局唯一性一般必须单调递增,因为数据库中一般都会存在唯一ID,而InnoDB的特点是将叶子节点中的内容存储在主键索引上,从左到右。考虑到数据库的性能,一般最好生成单调递增的ID。为了防止ID冲突,可以使用36位的UUID,但是UUID有一些缺点。一是比较长,另外UUID一般是无序的,可能需要不规则,因为如果用唯一ID作为订单号,为了防止别人知道一天有多少订单,这个需要某种规则结构。Snowflake算法的几个核心组成部分。在Java中,64位的证书是long类型的,所以SnowFlake算法生成的ID是二进制存储的long类的第一部分。最高位是符号位,1表示负数,0表示正数。生成的ID一般都是整数,所以最高位固定为0。第二部分第二部分是41bit的时间戳位,用来记录时间戳。毫秒级别的41位可以表示2^41-1个数。如果只用来表示正整数,可以表示的范围是:0-2^41-1,负1因为可以表示的取值范围是从0开始计算的,而不是从1开始计算的。也就是说,41位可以表示2^41-1毫秒的值,换算成一个单位年就是69.73年。第三部分是工作机器ID。10Bit用于记录工作机器ID,可以部署在2^10=1024个节点,包括5位datacenterId(数据中心、机房)和5位workerID(机器码)。5位数字可以表示的最大正整数是2^5=31个数来表示不同的数据中心和机器码。第四部分12位可以表示的正整数为2^12=4095,即012...4094可以表示同一台机器同一时间戳产生的4095个ID序号.SnowFlake可以保证所有生成的ID都会按照时间趋势递增,整个分布式系统中不会产生重复的ID,因为有datacenterId和workerId来区分。雪花算法是由scala算法编写的。有人用java来实现。github地址为https://github.com/beyondfeng.../***twitter的雪花算法--java实现**@authorbeyond*/publicclassSnowFlake{/***初始时间戳*/privatefinalstatic长START_STMP=1480166465631L;/***各部分占用的位数*/privatefinalstaticlongSEQUENCE_BIT=12;//序列号占用的位数privatefinalstaticlongMACHINE_BIT=5;//机器标识占用的位数privatefinalstaticlongDATACENTER_BIT=5;//数据中心占用的位数/***各部分的最大值*/privatefinalstaticlongMAX_DATACENTER_NUM=-1L^(-1L<MAX_DATACENTER_NUM||datacenterId<0){thrownewIllegalArgumentException("datacenterIdcan'tbegreater大于MAX_DATACENTER_NUM或小于0");}if(machineId>MAX_MACHINE_NUM||machineId<0){thrownewIllegalArgumentException("machineId不能大于MAX_MACHINE_NUM或小于0");}this.datacenterId=datacenterId;this.machineId=machineId;}/***生成下一个ID**@return*/publicsynchronizedlongnextId(){longcurrStmp=getNewstmp();if(currStmpcn.hutoolhutool-all5.3.1integration/***Snowflake算法**@author:Moxi*/publicclassSnowFlakeDemo{privatelongworkerId=0;私有长数据中心ID=1;私人雪花snowFlake=IdUtil.createSnowflake(workerId,datacenterId);@PostConstructpublicvoidinit(){try{//将网络ip转换为longworkerId=NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());}catch(Exceptione){e.printStackTrace();}}/***获取SnowflakID*@return*/publicsynchronizedlongsnowflakeId(){returnthis.snowFlake.nextId();}publicsynchronizedlongsnowflakeId(longworkerId,longdatacenterId){Snowflakesnowflake=IdUtil.createSnowflake(workerId,datacenterId);returnsnowflake.nextId();}publicstaticvoidmain(String[]args){SnowFlakeDemosnowFlakeDemo=newSnowFlakeDemo();for(inti=0;i<20;i++){newThread(()->{System.out.println(snowFlakeDemo.snowflakeId());},String.valueOf(i)).start();}}}得到结果12513507113467904001251350711346790402125135071134679040112513507113467904031251350711346790405125135071134679040412513507113467904061251350711346790407125135071135098470412513507113509847061251350711350984705125135071135098470712513507113509847081251350711350984709125135071135098471012513507113509847111251350711350984712125135071135517900812513507113551790091251350711355179010优缺点优点毫秒数在高维,自增序列在低位,整个ID都是趋势递增的不依赖数据图书馆等第三方系统以服务的形式部署,稳定性更高,ID生成性能高。比特可以根据自己的业务特点进行分配,非常灵活。缺点是依赖于机器时钟。如果拨回机器时钟,就会造成重复。ID的生成是在单机自增,但是由于分布式环境,每台机器上的时钟不能完全同步,有时不是全局自增。这个缺点可以认为是微不足道的。一般分布式ID只要求有增加的趋势,并不严格要求增加。90%的需求只要求趋势增加。其他补充为了解决时钟回调问题,导致ID重复,后来有人提出了解决方案。美团点评分布式ID生成系统近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.不要用爆破爆满画面,试试装饰者模式,这才是优雅的方式!!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!

最新推荐
猜你喜欢