互联网大厂面试雪花算法(Snowflake)实现
雪花算法(Snowflake),是由Twitter公司开源的分布式ID生成算法,通过以划分名称空间的方式将64位的数据分割成多个部分,并且每个部分来代表不同的含义。而在Java中64位的数据只有是Long类型的数据,所以在Java中对于雪花算法的实现都是以Long类型的数据来存储的。64Bit分别代表什么意思?
如图所示1、第1位,占用1个bit,但是其值始终是不变的,所以可以看做是一个符号位。 2、第2位开始到第41位,是时间戳,41bit可以表示2的41次方个数据,这个数据代表是毫秒数, 那么从这里就可以知道雪花算法的使用年限大概是(2的41次方/1000*3600*24*365) ,大概的 计算结果是69年。 3、中间10位,表示机器数,也就是2的10次方台机器,但是在一般情况下,都用不了这么多的机 器。所以这十个位置可以根据需求自己拟定。 4、最后的12位,相当于一个自增排序,可以表示2的12次方的数据也就是4096个数据。
通过这样的划分之后,相当于在一个毫秒之内一个服务器上可以产生4096个不重复的ID值。其量级也是非常可观的。
从雪花算法的实现思想上来看,其ID呈现出一个递增的趋势,并且不依赖第三方的数据库,第三方的服务系统,我们可以将其以工具类,或者是以JavaBean的方式注入到容器中,使用非常方便,而且在生成ID方面也是比较高效的,同样也可以结合业务来对Bit位进行合理的分配从而生成出符合业务逻辑的全局唯一ID。
这里有读者会问了?
在中间的位置中我们引入了时间戳,那么我们如何避免时钟回拨的问题呢?也就是说如果时钟回拨了,就会导致以前恰巧生成的ID再次出现,也会出现重复ID的情况,那么我们如何去解决这个问题呢?下面是我来实现的一个雪花算法ID生成器/** * @Classname SnowflakeIdUtils * @Description TODO 雪花算法ID生成器 * @Date 2020/8/14 2:29 PM * @Created by nihui * @Version 1.0 */ @Component public class SnowflakeIdUtils { // ==============================Fields=========================================== /** 开始时间截 (2015-01-01) */ private final long twepoch = 1420041600000L; /** 机器id所占的位数 */ private final long workerIdBits = 5L; /** 数据标识id所占的位数 */ private final long datacenterIdBits = 5L; /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** 支持的最大数据标识id,结果是31 */ private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); /** 序列在id中占的位数 */ private final long sequenceBits = 12L; /** 机器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** 数据标识id向左移17位(12+5) */ private final long datacenterIdShift = sequenceBits + workerIdBits; /** 时间截向左移22位(5+5+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** 工作机器ID(0~31) */ private long workerId; /** 数据中心ID(0~31) */ private long datacenterId; /** 毫秒内序列(0~4095) */ private long sequence = 0L; /** 上次生成ID的时间截 */ private long lastTimestamp = -1L; //==============================Constructors===================================== /** * 构造函数 * @param workerId 工作ID (0~31) * @param datacenterId 数据中心ID (0~31) */ public SnowflakeIdUtils(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("worker Id can"t be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException(String.format("datacenter Id can"t be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } public SnowflakeIdUtils(){ this(1,1); } public synchronized String stringId(){ return String.valueOf(this.nextId()); } // ==============================Methods========================================== /** * 获得下一个ID (该方法是线程安全的) * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //如果是同一时间生成的,则进行毫秒内序列 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; //毫秒内序列溢出 if (sequence == 0) { //阻塞到下一个毫秒,获得新的时间戳 timestamp = tilNextMillis(lastTimestamp); } } //时间戳改变,毫秒内序列重置 else { sequence = 0L; } //上次生成ID的时间截 lastTimestamp = timestamp; //移位并通过或运算拼到一起组成64位的ID return ((timestamp - twepoch) << timestampLeftShift) // | (datacenterId << datacenterIdShift) // | (workerId << workerIdShift) // | sequence; } /** * 阻塞到下一个毫秒,直到获得新的时间戳 * @param lastTimestamp 上次生成ID的时间截 * @return 当前时间戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒为单位的当前时间 * @return 当前时间(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } // //==============================Test============================================= // /** 测试 */ // public static void main(String[] args) { // SnowflakeIdUtils idWorker = new SnowflakeIdUtils(3, 1); // System.out.println(idWorker.nextId()); // } }总结
由于官方也没有对时间回拨的情况给出明确的答案,这里笔者的处理结果就是将其以异常的方式进行抛出,后来也有很多的分布式ID算法都是基于雪花算法的思想进行升级,而且避免了雪花算法带来的各种缺陷。例如百度的UidGrnerator、美团的Leaf都是在雪花算法的基础上演变出来的。
选择牛股,原来这么简单?面对市场里的几千只个股,如何才能快速地选择到牛股那?一个大牛股是投资者最关心的问题,投资者在买入一只股票在一段时间该股快速上升,收益几倍或者几十倍的增长,赚得不亦乐乎,心里也乐开了
浅谈某宁事件,企业应该尊重消费者,放下傲慢前日,知名品牌李宁因发布的新品逐梦行中一款军绿色大衣加上绿色双耳扇帽引起争议。19号晚上,不堪压力的李宁在微博上发布了道歉声明很显然网友并不买账,评论区一如既往翻车,一片狼藉。傲慢
用一碗酱汁,简单几步教你做出红亮诱人,酱香浓郁好吃的鸡肉来秋日生活打卡季嗨大家好!我是小猫爱厨房的小猫,今天又和大家见面了,分享生活分享美食分享快乐!今天给大家带来的是一道酱汁鸡的做法,这是一道非常好吃的肉菜,酱香浓郁,色泽也特别好看的一
光阴不孤独,生活不寂寞,唯有懂得恬淡和简单,才是生活的味道我们要尽量去相信美好的东西相信真爱存在,相信生活很精彩,相信他人的善意,相信自己的能力,相信努力有意义,相信事情会变好,相信幸福会来敲门这些美好,你越相信,就越接近。时间会告诉你一
人生最简单的幸福是什么?头条创作挑战赛小时候看不懂大人说的吃得下饭睡得着觉就是幸福。心想瞎扯,这些我都会,我也没觉得多幸福啊。我想快点长大,看看外面的世界是什么样的。长大了,看到了外面的世界,遇到了形形色
想成功,很简单,也很辛苦其实谁也不知道自己能活多久,但是大部分人都认为自己能活八九十岁,或者一百岁,于是就不珍惜时间,总认为自己还有时间。实际上,谁也不知道明天和意外哪个先到。有时候我就想,如果自己明天就
世界上著名的6大禁区,不管是什么身份,都不得进入在人类之中,有人喜欢就有人不喜欢,对于那些喜欢冒险的人来说,能够接触一些未知的新事物比保护自己的安全更重要,因为在这个过程中,他们可以体验无数的乐趣,然而,在这个世界上,有一些著名
我家门口那条路丨打通山西太行一号旅游公路,让偏僻山村成网红打卡地山西省晋城市阳城县润城镇润城村西哄哄位于太行山深处,这里峰峦叠嶂,山路崎岖,从县城到村子里需要转十来条盘山公路,行驶近两个小时的车程。太行山绵延八百里,山势险峻,道路崎岖,自古以来
2010年自驾青藏尼泊尔川藏纪行(五)第五天。清晨的三江源宁静又祥和,阳光照在不远处的雪山上熠熠生辉,这就是我们心心念念的日照金山啊!整理好行装,我们朝着前方的唐古拉出发!我们朝着心中的唐古拉出发!唐古拉山口,海拔52
柳州的秋天有多美?这丰景便是答案!柳城县六塘镇,成片的稻谷进入成熟期颜篁摄来源南国今报(已获授权,未经允许不可转载)AUTUMN丰光好养眼树树皆秋色,山山唯落晖。柳州秋天有多美?你要写它的美就不能单纯只写它很美,要
济南南部山区摩旅首次家庭摩旅南部山区一日游丁子寨斗母泉朱凤山前序买摩托车已1年有余,一直有摩旅南部山区的想法,奈何疫情和琐事影响未能成行。10月13日,周三,无意刷到头条视频,号称济南南部山区近郊