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

七种分布式全局ID生成策略,你更喜欢哪一种?

时间:2023-04-02 09:16:56 Java

@[toc]使用微服务后,很多原本简单的问题现在都变复杂了,比如全局ID!宋兄在最近的工作中恰好用到了这个内容,于是调研了市面上常见的几种全局ID生成策略,做了一点比较,供大家参考。数据库分库分表后,原有的主键自增不便继续使用,需要寻找新的合适的解决方案。宋歌的要求,就是在这样的情况下提出来的。接下来,我们一起来看看吧。1、两种思路一般来说,这个问题有两种不同的思路:让数据库自己处理Java代码中的主键,然后直接插入到数据库中。这两种思路对应不同的解决方案,我们一一来看。2.数据库是自己固定的。也就是说,我在插入数据的时候,还是没有考虑主键的问题。希望继续使用数据库的主键自增,但是很明显,原来默认的主键自增已经不能用了。我们必须有新的解决方案。2.1修改数据库配置分库分库分表后的结构如下(假设数据库中间件使用MyCat):此时如果原来的db1、db2、db3继续增加主键,则对于MyCat,主键不是自增的。如果增加,主键会重复,用户从MyCat中查询的数据的主键就会出现问题。找到问题的原因,剩下的就很容易解决了。我们可以直接修改MySQL数据库主键自增的起始值和步长。首先,我们可以通过如下SQL查看与此相关的两个变量的值:SHOWVARIABLESLIKE'auto_increment%'可以看到主键自增的初始值和步长都是1。初始值容易改变,在定义表格的时候就可以设置。我们可以通过修改这个配置来修改步长:set@@auto_increment_increment=9;修改后,查看对应的变量值,发现发生了变化:这时候我们再插入数据,主键自增不是每次都是1,而是每次都是9。至于自增起始值,其实很容易设置,在建表的时候就可以设置。创建表test01(idintegerPRIMARYKEYauto_increment,usernamevarchar(255))auto_increment=8;由于MySQL可以修改自增的初始值和每次增加的步长,现在假设我有db1、db2和db3,我将三个库中的表的自增起始值设置为分别为1、2、3,则自增步长均为3,即可实现自增。但是很明显这个方法不够优雅,处理起来比较麻烦,而且以后扩展起来也不方便,所以不推荐。2.2MySQL+MyCat+ZooKeeper如果你正好使用MyCat作为你的分库分表工具,那么结合Zookeeper也可以实现主键的全局自增。MyCat作为一个分布式数据库,屏蔽了数据库集群的运行。让我们像操作单机数据库一样操作数据库集群。主键自增有自己的解决方案:通过本地文件通过数据库通过本地时间戳通过分布式ZKID生成器实现通过ZK增量方式实现这里我们主要看方案4,配置步骤如下:首先修改主键自增方式为4,4表示使用zookeeper实现主键自增。server.xml配置表自增,设置主键。schema.xml设置主键自增,主键设置为id。在myid.properties中配置zookeeper信息配置zookeeper信息:配置表sequence_conf.properties自增,注意这里表名要大写。TABLE.MINID线程当前范围内的最小值TABLE.MAXID线程当前范围内的最大值TABLE.CURID线程当前范围内的当前值有效文件中只有这三个属性配置有效对于第一个进程的第一个线程,其他线程和进程会动态读取ZKrestartMyCattest最后重启MyCat,删除之前创建的表,然后创建一个新表用于测试即可。这种方法麻烦少,可扩展性更好。如果选择MyCat作为分库分表工具,那么这是最好的解决方案。前面说了,这两个方法是在数据库或者数据库中间件层面来处理主键自增的,我们的Java代码不需要额外的工作。接下来我们看一下Java代码中需要处理的几个场景。3、Java代码处理3.1UUID最容易想到的就是UUID(UniversallyUniqueIdentifier)。UUID的标准格式包含32个十六进制数,用连字符分成五段,形式为8-4-4-4-12到36个字符,这是Java内置的,使用方便。最大的优点是在本地生成,没有网络消耗,但是在公司开发的都知道,这个东西在公司项目中用的不多。原因如下:字符串太长,不利于MySQL建立索引。UUID的随机性对I/O密集型应用很不友好!它会使聚簇索引的插入完全随机,使数据不具有任何聚簇特性。信息不安全:基于MAC地址生成UUID的算法可能会导致MAC地址泄露。这个漏洞被用来找到Melissa病毒的创建者。因此,UUID不是最好的解决方案。3.2SNOWFLAKE算法是Twitter公布的一种分布式主键生成算法,可以保证不同进程主键的不重复和同一进程主键的有序性。在同一个进程中,它首先通过时间位来保证不重复,如果时间相同,则通过顺序位来保证。同时,由于时间位是单调递增的,如果各个服务器的时间大致同步,那么生成的主键在分布式环境下一般可以认为是有序的,保证了插入索引字段的效率。比如MySQL的Innodb存储引擎的主键。使用雪花算法生成的主键,二进制表示包含4个部分,从高到低:1bit符号位,41bit时间戳位,10bit工作过程位,12bit序号位。符号位(1bit)保留的符号位始终为零。时间戳位(41bit)41位的时间戳可以容纳的毫秒数是2的41次方,一年使用的毫秒数是:3652460601000。通过计算可知:Math.pow(2,41)/(365*24*60*60*1000L);结果大约等于69.73年。ShardingSphere雪花算法的时间纪元从2016年11月1日午夜开始,可以使用到2086年,相信可以满足大部分系统的要求。Workerprocessbit(10bit)这个标志在Java进程中是唯一的。如果是分布式应用部署,需要保证每个worker进程的id是不同的。该值默认为0,可以通过属性进行设置。序列号位(12bit)该序列用于在同一毫秒内生成不同的ID。如果这个毫秒内产生的数量超过4096(2的12次方),生成器会等到下一毫秒继续生成。注意:该算法存在时钟回调问题。服务器时钟回调将导致重复序列。因此,默认的分布式主键生成器提供了最大容忍时钟回调毫秒数。如果时钟回调时间超过最大容忍毫秒阈值,程序会报错;如果在可容忍的范围内,默认的分布式主键生成器会等待时钟同步到上次生成主键的时间再继续工作。以毫秒为单位的最大容忍时钟返回默认值为0,可以通过属性设置。下面松哥给出了雪花算法的工具类,可以参考:publicclassIdWorker{//时间起点,作为基准,一般取系统最新时间(一经确认,不可更改)private最终静态长twepoch=1288834974657L;//机器ID号privatefinalstaticlongworkerIdBits=5L;//数据中心ID号privatefinalstaticlongdatacenterIdBits=5L;//机器ID最大值privatefinalstaticlongmaxWorkerId=-1L^(-1L<maxWorkerId||workerId<0){thrownewIllegalArgumentException(String.format("workerId不能大于%d或小于0",maxWorkerId));}if(datacenterId>maxDatacenterId||datacenterId<0){thrownewIllegalArgumentException(String.format("datacenterIdcan'tbegreaterthan%dorlessthan0",maxDatacenterId));}this.workerId=workerId;this.datacenterId=datacenterId;}/***获取下一个ID**@return*/publicsynchronizedlongnextId(){longtimestamp=timeGen();if(timestamp*获取maxWorkerId*

*/protectedstaticlonggetMaxWorkerId(longdatacenterId,longmaxWorkerId){StringBuffermpid=newStringBuffer();mpid.append(datacenterId);字符串名称=ManagementFactory.getRuntimeMXBean().getName();if(!name.isEmpty()){/**GETjvmPid*/mpid.append(name.split("@")[0]);}/**MAC+PID的hashcode获取16个低位*/return(mpid.toString().hashCode()&0xffff)%(maxWorkerId+1);}/***

*数据标准id部分*

*/protectedstaticlonggetDatacenterId(longmaxDatacenterId){longid=0L;尝试{InetAddressip=InetAddress.getLocalHost();NetworkInterface网络=NetworkInterface.getByInetAddress(ip);如果(网络==null){id=1L;}else{byte[]mac=network.getHardwareAddress();id=((0x000000FF&(long)mac[mac.length-1])|(0x0000FF00&(((long)mac[mac.length-2])<<8)))>>6;id=id%(maxDatacenterId+1);}}catch(Exceptione){System.out.println("getDatacenterId:"+e.getMessage());}返回编号;}}用法如下:IdWorkeridWorker=newIdWorker(0,0);for(inti=0;i<1000;i++){System.out.println(idWorker.nextId());}3.3LEAFLeaf是美团开源的分布式ID生成系统。最早的需求是各业务线的订单ID生成需求在美团早期,有的业务直接通过DB自增生成ID,有的业务通过Redis缓存生成ID,有的业务直接通过UUID生成ID。以上几种方式各有各的问题,所以美团决定自己实现一套分布式ID生成服务来满足需求。目前,Leaf涵盖了美团点评内部金融、餐饮、外卖、酒店旅游、猫眼电影等多条业务线。基于4C8GVM,通过公司RPC方式调用,QPS压测结果接近5w/s,TP9991ms类,TP50、TP90、TP99等指标常用于系统性能监控场景,指高于50%、90%、99%等的情况)。目前LEAF有两种不同的使用方式,数段模式和SNOWFLAKE模式。您可以同时启用两种模式,也可以指定启用某种模式(默认模式为关闭)。我们从GitHub克隆LEAF后,它的配置文件在leaf-server/src/main/resources/leaf.properties。各配置含义如下:。可见,如果使用数段方式,需要数据库支持;如果使用SNOWFLAKE模式,则需要Zookeeper支持。3.3.1号段模式号段模式仍然是基于数据库的,但是思路变了,如下:使用代理服务器从数据库中批量获取ID,获取一个段的值(步骤决定其尺寸)每次。去数据库中获取新的号码段可以大大减轻数据库的压力。biz_tag字段用于区分每个业务不同的发号需求,每个biz-tag的ID获取相互隔离,互不影响。如果有新业务需要扩展区域ID,只需要添加表记录即可。如果使用segment模式,我们首先需要创建一个数据表,脚本如下:CREATEDATABASEleafCREATETABLE`leaf_alloc`(`biz_tag`varchar(128)NOTNULLDEFAULT'',`max_id`bigint(20)NOTNULLDEFAULT'1',`step`int(11)NOTNULL,`description`varchar(256)DEFAULTNULL,`update_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMP,PRIMARYKEY(`biz_tag`))ENGINE=InnoDB;insertintoleaf_alloc(biz_tag,max_id,step,description)values('leaf-segment-test',1,2000,'TestleafSegmentModeGetId')本表各字段含义如下:biz_tag:业务标签(不同的业务可以有不同的号段序列)max_id:当前号段下的最大idstep:每个号段的步长description:描述信息update_time:更新时间配置完成后,启动项目,访问http://localhost:8080/api/segment/get/leaf-segment-test路径(路径最后的leaf-segment-test是业务标记),即可获取ID。您可以通过以下地址http://localhost:8080/cache访问号码段模式的监控页面。号段模式的优缺点:优点Leaf服务可以轻松线性扩展,性能可以全面支持大部分业务场景。ID号为8字节64位数字,呈递增趋势,满足上述数据库存储的主键要求。高容灾:Leaf服务内部有段缓存。即使DB宕机了,Leaf仍然可以在短时间内对外提供服务。可以自定义max_id的大小,非常方便业务从原来的id方式迁移。缺点:身份证号不够随机,可能会泄露发号数信息,不太安全。数据库宕机会导致整个系统不可用。3.3.2SNOWFLAKE模式SNOWFLAKE模式需要配合Zookeeper,但是SNOWFLAKE对Zookeeper的依赖性较弱。启动Zookeeper后,我们可以在SNOWFLAKE中配置Zookeeper信息,如下:leaf.snowflake.enable=trueleaf.snowflake.zk。address=192.168.91.130leaf.snowflake.port=2183然后重启项目。启动成功后,可以通过以下地址访问ID:http://localhost:8080/api/snowflake/get/test3.4Redis生成这个main是使用Redis的incrby实现的。我觉得这没什么好说的。3.5Zookeeper也可以处理zookeeper,但是比较麻烦,不推荐。4.总结综上所述,如果项目中刚好用到MyCat,那么可以用MyCat+Zookeeper,否则建议用LEAF,两种模式都可以。

最新推荐
猜你喜欢