java⽣成⾃增编号_分布式id⽣成策略,我和⾯试官扯了⼀个半
⼩时
前⾔
⾯试官:⼩伙⼦,你还记得我吗?我是上次⾯试你的那个⾯试官。
我⼼想:我去,怎么会不记得,我⼜不是青年痴呆,上次害我画了那么多图,还使劲敲了⼀个多钟的电脑,满脑⼦都是你的阴影。
我:记得记得,您好,很⾼兴能通过⼆⾯,能够继续和您交流技术问题。
我违背良⼼说这话真的好吗,姑且就那么⼀次吧,⾯个试都那么难?
⾯试官⼜快速的扫了⼀下的简历,可能上次看过⼀次,都快过了⼀个多星期了,估计他都都忘了我的简历了吧。
⾯试官:我看你简历上⾯写着深⼊了解分布式,并且也做过分布式项⽬,挺好的,那你知道分布式项⽬中⽣成分布式ID的⽅法有哪些吗?我:这个我知道,⽣成分布式Id的⽅法主要有以下⼏种:
1. 数据库⾃增ID。
2. 数据库⽔平拆分,设置初始值和相同的⾃增步长。
3. 批量申请⾃增ID。
4. UUID⽣成。
5. Redis的⽅式。
6. 雪花算法。
7. 百度UidGenerator算法
8. 美团Leaf算法
⾯试官:哦,不错能说出那么多,你能说⼀说对于上⾯的每⼀种⽅式的分析和理解吗?
我⼼想:我去,这下可糗⼤了,那么多,我只是⼤概知道主要的,怎么可能每⼀种都去了解和深⼊,⼀下⼦说了那么多不是给⾃⼰挖坑吗?哎,没办法出来混,总是要还的,只能说⾃⼰知道的吧?不知道的⼤概粗糙的略过。
数据库⾃增ID
我:嗯嗯,好的。数据库的⾃增,很容易理解,开发过的⼈员都知道,在创建表的时候,指定主键auto_increment(⾃增)便可以实现。
我:但是使⽤数据库的⾃增ID,虽然简单,会带来ID重复的问题,并且单机版的ID⾃增,并且每次⽣成⼀个ID都会访问数据库⼀次,DB的压⼒也很⼤,并没有什么并发性能可⾔。
⾯试官:恩额。
我看看⾯试官正听着有味,时不时摸摸他稀少的发量额头,深邃的⽬光透露出他的沉稳,这可能就是⼀个成熟架构师的魅⼒吧,让多少码渣苦读《Java编程思想》《Java核⼼技术》《Effectice java》《Java并发编程实战》《代码整洁之道》《重构: 改善既有代码的设计》......,都⽆法达到的境界,我乘热打铁,接着下⾯的回答。
数据库⽔平拆分,设置初始值和相同的⾃增步长
「数据库⽔平拆分,设置初始值和我:针对上⾯的数据库⾃增ID出现的问题:ID重复、性能不好。就出现了集版的⽣成分布式ID⽅案。「数据库⽔平拆分,设置初始值和「批量申请⾃增ID」。
相同的⾃增步长」
相同的⾃增步长」和「批量申请⾃增ID」
「不同「数据库⽔平拆分,设置初始值和相同的⾃增步长」是指在DB集的环境下,将数据库进⾏⽔平划分,然后每个数据库设置「不同我:「数据库⽔平拆分,设置初始值和相同的⾃增步长」
「相同的步长」,这样就能避免ID重复的情况。
的初始值」
的初始值」和「相同的步长」
⾯试官:⼩伙⼦,不好意思打断⼀下,你可以画个图吗,这个我有点没明⽩你讲的意思?
我能有什么办法阿,完全没办法,只能从裤兜⾥拿出笔和纸,快速的画了⼀张图。
我:我这⾥假设有三个数据库,为每⼀个数据库设置初始值,设置初始值可以通过下⾯的sql进⾏设置:
set @@auto_increment_offset = 1; // 设置初始值
set @@auto_increment_increment = 2; // 设置步长
复制代码
我:三个数据的初始值分别设置为1、2、3,⼀般步长设置为数据库的数据,这⾥数据库数量为3,所以步长也设置为3。
⾯试官:若是⾯对再次扩容的情况呢?
我:恩额,扩容的情况是这种⽅法的⼀个缺点,上⾯我说的步长⼀般设置为数据库的数量,这是在确保后期不会扩容的情况下,若是确定后
「预留⼀些初始值给后续扩容使⽤」。
期会有扩容情况,在前期设计的的时候可以将步长设置长⼀点,「预留⼀些初始值给后续扩容使⽤」
「后期可能会⾯对⽆ID初始值可分的窘境,数据库总归是数据我:总之,这种⽅案还是优缺点的,但是也有⾃⼰的优点,缺点就是:「后期可能会⾯对⽆ID初始值可分的窘境,数据库总归是数据库,抗⾼并发也是有限的」。
库,抗⾼并发也是有限的」
「DB单点的问题」。
我:它的优点就是算是解决了「DB单点的问题」
⾯试官:恩额。
批量申请⾃增ID
我:「批量申请⾃增ID」
「批量申请⾃增ID」的解决⽅案可以解决⽆ID可分的问题,它的原理就是⼀次性给对应的数据库上分配⼀批的id值进⾏消费,使⽤完了,再回来申请。
这次我很⾃觉的从裤兜⾥拿出笔和纸,画出了下⾯的这张图,历史总是那么惊⼈的相似。
我:在设计的初始阶段可以设计⼀个有初始值字段,并有步长字段的表,当每次要申请批量ID的时候,就可以去该表中申请,每次申请「初始值=上⼀次的初始值+步长」。
后「初始值=上⼀次的初始值+步长」
我:这样就能保持初始值是每⼀个申请的ID的最⼤值,避免了ID的重复,并且每次都会有ID使⽤,⼀次就会⽣成⼀批的id来使⽤,这样访问数据库的次数⼤⼤减少。
我:但是这⼀种⽅案依旧有⾃⼰的缺点,依然不能抗真正意义上的⾼并发。
UUID⽣成
「机器的⽹卡、当地时间、⼀个随机数」来⽣成我:第四种⽅式是使⽤「UUID⽣成」
「UUID⽣成」的⽅式⽣成分布式ID,UUID的核⼼思想是使⽤「机器的⽹卡、当地时间、⼀个随机数」
UUID。
我:使⽤UUID的⽅式只需要调⽤UUID.randomUUID().toString()就可以⽣成,这种⽅式⽅便简单,本地⽣成,不会消耗⽹络。
我:当时简单的东西,出现的问题就会越多,不利于存储,16字节128位,通常是以36位长度的字符串表⽰,很多的场景都不适合。
我:并且UUID⽣成的⽆序的字符串,查询效率低下,没有实际的业务含义,不具备⾃增特性,所以都不会使⽤UUID作为分布式ID来使⽤。⾯试官:恩额,那你知道⽣成UUID的⽅式有⼏种吗?不知道没关系,这个只是作为⼀个扩展。
「当前的时间戳及机器mac地址」来⽣成,可以确保⽣成的UUID全球唯⼀,其它的没有了解过。
我:这个我只知道可以通过「当前的时间戳及机器mac地址」
⾯试官:嗯嗯,没关系的。
Redis的⽅式
我:为了解决上⾯纯关系型数据库⽣成分布式ID⽆法抗⾼并发的问题,可以使⽤Redis的⽅式来⽣成分布式ID。
我:Redis本⾝有incr和increby 这样⾃增的命令,保证原⼦性,⽣成的ID也是有序的。
我:Redis基于内存操作,性能⾼效,不依赖于数据库,数据天然有序,利于分页和排序。
我:但是这个⽅案也会有⾃⼰的缺点,因为增加了中间件,需要⾃⼰编码实现⼯作量增⼤,增加复杂度。
「RDB是以快照的形式进⾏持久化,会丢失上⼀次快我:使⽤Redis的⽅式还要考虑持久化,Redis的持久化有两种「RDB和AOF」
「RDB和AOF」,「RDB是以快照的形式进⾏持久化,会丢失上⼀次快照⾄此时间的数据」。
照⾄此时间的数据」
「AOF可以设置⼀秒持久化⼀次,丢失的数据是秒内的」,也会存在可能上⼀次⾃增后的秒内的ID没有持久化的问题。
我:「AOF可以设置⼀秒持久化⼀次,丢失的数据是秒内的」
我:但是这种⽅法相对于上⾯的关系型数据库⽣成分布式ID的⽅法⽽⾔,已经优越了许多。
我:若是数据量⽐较⼤的话,重启Redis的时间也会⽐较长,可以采⽤Redis的集⽅式。
⾯试官:你能⼿写⼀下Redis的⽣成分布式ID的⼯具类代码吗?
我奔溃了,我最怕⼿写了,因为⼯具类这种东西,基本就是项⽬开始的时候写⼀次,后⾯对后市重复使⽤,记不住,还要⼿写,这也太难为我怕虎了吧。
我:⼿写应该不⾏,因为有些API记不住,⼯具类基本就是项⽬开始的时候写⼀些,后续都没有去看过了,没有专门去记它。
我:我可以使⽤您的电脑吗?使⽤电脑应该可以敲出这些⼯具类。
⾯试官:可以的,这边电脑给你,你在这个测试项⽬下吧。
我:好的,谢谢。
时间流逝中........
⼤概敲了⼏分钟,废了九⽜⼆虎之⼒,终于敲出来了,有好多API记不住,只能慢慢的了,写了主要两种⽅式来⽣成分布式ID。
第⼀种是使⽤RedisAtomicLong 原⼦类使⽤CAS操作来⽣成ID。
@Service
public class RedisSequenceFactory {
@Autowired
RedisTemplate<String, String> redisTemplate;
public void setSeq(String key, int value, Date expireTime) {
RedisAtomicLong counter = new RedisAtomicLong(key, ConnectionFactory()); counter.set(value);
}
public void setSeq(String key, int value, long timeout, TimeUnit unit) {
RedisAtomicLong counter = new RedisAtomicLong(key, ConnectionFactory()); counter.set(value);
}
public long generate(String key) {
RedisAtomicLong counter = new RedisAtomicLong(key, ConnectionFactory()); return counter.incrementAndGet();
}
public long incr(String key, Date expireTime) {
RedisAtomicLong counter = new RedisAtomicLong(key, ConnectionFactory()); pireAt(expireTime);
return counter.incrementAndGet();
}
public long incr(String key, int increment) {
RedisAtomicLong counter = new RedisAtomicLong(key, ConnectionFactory()); return counter.addAndGet(increment);
}
public long incr(String key, int increment, Date expireTime) {
RedisAtomicLong counter = new RedisAtomicLong(key, ConnectionFactory()); pireAt(expireTime);
return counter.addAndGet(increment);
}
}
复制代码
第⼆种是使⽤redisTemplate.opsForHash()和结合UUID的⽅式来⽣成⽣成ID。
public Long getSeq(String key,String hashKey,Long delta) throws BusinessException{
try {
if (null == delta) {
delta=1L;
}
return redisTemplate.opsForHash().increment(key, hashKey, delta);
} catch (Exception e) { // 若是redis宕机就采⽤uuid的⽅式
int first = new Random(10).nextInt(8) + 1;
int randNo=UUID.randomUUID().toString().hashCode();
if (randNo < 0) {
randNo=-randNo;
}
return Long.valueOf(first + String.format("%16d", randNo));
}
}
复制代码
我把电脑移回给⾯试官,他很快的扫了⼀下我的代码,说了⼀句。
⾯试官:⼩伙⼦,不写注释哦,这个习惯不好哦。
我:哦哦,谢谢提醒,不好意思,下次我会注意的。
雪花算法
「雪花算法」,也是现在市⾯上⽐较流⾏的⽣成分布式ID的⽅法。
我:第六种⽅式是「雪花算法」
说着说着,我知道画图⼜是必不可少的了,于是在桌⼦上有画了起来,⾯试官好奇的看看我,知道了我在⼲啥,⼜耐⼼的等了等。
我:他是采⽤64bit作为id⽣成类型,并且将64bit划分为,如下图的⼏段。
我顺⼿把我画的图递给他看了看,接着对着这个图进⾏解释。
我:第⼀位作为标识位,因为Java中long类型的时代符号的,因为ID位正数,所以第⼀位位0。
「当前时间-开始时间」)。我:接着的41bit是时间戳,毫秒级位单位,注意这⾥的时间戳并不是指当前时间的时间戳,⽽是值之间差(「当前时间-开始时间」
我:这⾥的开始时间⼀般是指ID⽣成器的开始时间,是由我们程序⾃⼰指定的。
「数据中⼼标识ID(datacenterId)和5位的机器标识ID(workerId)」,可以最多标识1024个我:接着后⾯的10bit:包括5位的「数据中⼼标识ID(datacenterId)和5位的机器标识ID(workerId)」
节点(1<<10=1024)。
我:最的12位是序列号,12位的计数顺序⽀持每个节点每毫秒差⽣4096序列号(1<<12=4096)。
我:雪花算法使⽤数据中⼼ID和机器ID作为标识,不会产⽣ID的重复,并且是在本地⽣成,不会消耗⽹络,效率⾼,有数据显⽰,每秒能⽣成26万个ID。
我:但是雪花算法也是⼜⾃⼰的缺点,因为雪花算法的计算依赖于时间,若是系统时间回拨,就会产⽣重复ID的情况。
⾯试官:那对于时间回拨产⽣重复ID的情况,你有什么⽐较好的解决⽅案吗?
我:在雪花算法的实现中,若是其前置的时间等于当前的时间,就抛出异常,也可以关闭掉时间回拨。
我:对于回拨时间⽐较短的,可以等待回拨时间过后再⽣成ID。
⾯试官:你可以帮我敲⼀个雪花算法吗?我这键盘给你。
我:。。。
我:好的。
时间流逝中......
过了⼏分钟时间,也总算是把雪花算法给敲出来了,真正要⽼命,⾯个试怎么就那么难呢?
/**
为了给⾯试官留下个好印象,这下也写上了注解,免得他⼜说我,敲完我⼜把电脑移回给他,他快速的看了看,点了点头,嘴⾓露出思思的笑意。
⾯试官:嗯,你的底⼦还算⽐价扎实,⾯试之前早有准备吧,看了很多的⾯试资料。
我⼼想怎么是⾯试之前准备呢?我是⼀直再准备,从⼯作到现在都在总结⾃⼰的知识点,形成⾃⼰的知识体系,为了迎合他,也只能说是。我:嗯嗯,是的,准备了很久,算是⽐较充分。
>java生成随机数的方法
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论