Java实现雪花算法的原理和实战教程

目录

SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的,后面的代码中有详细的注解。

这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。

给大家举个例子吧,比如下面那个 64 bit 的 long 型数字:

  1. 第一个部分,是 1 个 bit:0,这个是无意义的。
  2. 第二个部分是 41 个 bit:表示的是时间戳。
  3. 第三个部分是 5 个 bit:表示的是机房 id,10001。
  4. 第四个部分是 5 个 bit:表示的是机器 id,1 1001。
  5. 第五个部分是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 00000000。

①1 bit:是不用的,为啥呢?

因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。

②41 bit:表示的是时间戳,单位是毫秒。

41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间。

③10 bit:记录工作机器 id,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。

但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 id。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可以代表 2 ^ 5 个机器(32 台机器),也可以根据自己公司的实际情况确定。

④12 bit:这个是用来记录同一个毫秒内产生的不同 id。

12 bit 可以代表的最大正整数是 2 ^ 12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。

简单来说,你的某个服务假设要生成一个全局唯一 id,那么就可以发送一个请求给部署了 SnowFlake 算法的系统,由这个 SnowFlake 算法系统来生成唯一 id。

这个 SnowFlake 算法系统首先肯定是知道自己所在的机房和机器的,比如机房 id = 17,机器 id = 12。

接着 SnowFlake 算法系统接收到这个请求之后,首先就会用二进制位运算的方式生成一个 64 bit 的 long 型 id,64 个 bit 中的第一个 bit 是无意义的。

接着 41 个 bit,就可以用当前时间戳(单位到毫秒),然后接着 5 个 bit 设置上这个机房 id,还有 5 个 bit 设置上机器 id。

最后再判断一下,当前这台机房的这台机器上这一毫秒内,这是第几个请求,给这次生成 id 的请求累加一个序号,作为最后的 12 个 bit。

最终一个 64 个 bit 的 id 就出来了,类似于:

这个算法可以保证说,一个机房的一台机器上,在同一毫秒内,生成了一个唯一的 id。可能一个毫秒内会生成多个 id,但是有最后 12 个 bit 的序号来区分开来。

下面我们简单看看这个 SnowFlake 算法的一个代码实现,这就是个示例,大家如果理解了这个意思之后,以后可以自己尝试改造这个算法。

总之就是用一个 64 bit 的数字中各个 bit 位来设置不同的标志位,区分每一个 id。

SnowFlake 算法的实现代码如下:


public class IdWorker {

	//因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 id 都是正数,所以第一个 bit 统一都是 0。

	//机器ID  2进制5位  32位减掉1位 31个
	private long workerId;
	//机房ID 2进制5位  32位减掉1位 31个
	private long datacenterId;
	//代表一毫秒内生成的多个id的最新序号  12位 4096 -1 = 4095 个
	private long sequence;
	//设置一个时间初始值    2^41 - 1   差不多可以用69年
	private long twepoch = 1585644268888L;
	//5位的机器id
	private long workerIdBits = 5L;
	//5位的机房id
	private long datacenterIdBits = 5L;
	//每毫秒内产生的id数 2 的 12次方
	private long sequenceBits = 12L;
	// 这个是二进制运算,就是5 bit最多只能有31个数字,也就是说机器id最多只能是32以内
	private long maxWorkerId = -1L ^ (-1L << workerIdBits);
	// 这个是一个意思,就是5 bit最多只能有31个数字,机房id最多只能是32以内
	private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

	private long workerIdShift = sequenceBits;
	private long datacenterIdShift = sequenceBits + workerIdBits;
	private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
	private long sequenceMask = -1L ^ (-1L << sequenceBits);
	//记录产生时间毫秒数,判断是否是同1毫秒
	private long lastTimestamp = -1L;
	public long getWorkerId(){
		return workerId;
	}
	public long getDatacenterId() {
		return datacenterId;
	}
	public long getTimestamp() {
		return System.currentTimeMillis();
	}

	public IdWorker(long workerId, long datacenterId, long sequence) {

		// 检查机房id和机器id是否超过31 不能小于0
		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;
		this.sequence = sequence;
	}

	// 这个是核心方法,通过调用nextId()方法,让当前这台机器上的snowflake算法程序生成一个全局唯一的id
	public synchronized long nextId() {
		// 这儿就是获取当前时间戳,单位是毫秒
		long timestamp = timeGen();
		if (timestamp < lastTimestamp) {

			System.err.printf(
					"clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
			throw new RuntimeException(
					String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
							lastTimestamp - timestamp));
		}

		// 下面是说假设在同一个毫秒内,又发送了一个请求生成一个id
		// 这个时候就得把seqence序号给递增1,最多就是4096
		if (lastTimestamp == timestamp) {

			// 这个意思是说一个毫秒内最多只能有4096个数字,无论你传递多少进来,
			//这个位运算保证始终就是在4096这个范围内,避免你自己传递个sequence超过了4096这个范围
			sequence = (sequence + 1) & sequenceMask;
			//当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
			if (sequence == 0) {
				timestamp = tilNextMillis(lastTimestamp);
			}

		} else {
			sequence = 0;
		}
		// 这儿记录一下最近一次生成id的时间戳,单位是毫秒
		lastTimestamp = timestamp;
		// 这儿就是最核心的二进制位运算操作,生成一个64bit的id
		// 先将当前时间戳左移,放到41 bit那儿;将机房id左移放到5 bit那儿;将机器id左移放到5 bit那儿;将序号放最后12 bit
		// 最后拼接起来成一个64 bit的二进制数字,转换成10进制就是个long型
		return ((timestamp - twepoch) << timestampLeftShift) |
				(datacenterId << datacenterIdShift) |
				(workerId << workerIdShift) | sequence;
	}

	/**
	 * 当某一毫秒的时间,产生的id数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生ID
	 * @param lastTimestamp
	 * @return
	 */
	private long tilNextMillis(long lastTimestamp) {

		long timestamp = timeGen();

		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}
	//获取当前时间戳
	private long timeGen(){
		return System.currentTimeMillis();
	}

	/**
	 *  main 测试类
	 * @param args
	 */
	public static void main(String[] args) {
		System.out.println(1&4596);
		System.out.println(2&4596);
		System.out.println(6&4596);
		System.out.println(6&4596);
		System.out.println(6&4596);
		System.out.println(6&4596);
//		IdWorker worker = new IdWorker(1,1,1);
//		for (int i = 0; i < 22; i++) {
//			System.out.println(worker.nextId());
//		}
	}
}

SnowFlake算法的优点:

(1)高性能高可用:生成时不依赖于数据库,完全在内存中生成。

(2)容量大:每秒中能生成数百万的自增ID。

(3)ID自增:存入数据库中,索引效率高。

SnowFlake算法的缺点:

依赖与系统时间的一致性,如果系统时间被回调,或者改变,可能会造成id冲突或者重复。

实际中我们的机房并没有那么多,我们可以改进改算法,将10bit的机器id优化,成业务表或者和我们系统相关的业务。

到此这篇关于Java实现雪花算法的原理和实战教程的文章就介绍到这了,更多相关Java实现雪花算法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 总结一下Java回调机制的相关知识

    一.回调 回调分为同步回调和异步回调, 假如以买彩票的场景来模拟, 我买彩票, 调用彩票网,给我返回的结果确定是否中奖,同步回调就是,我买了彩票之后, 需要等待彩票网给我返回的结果, 这个时候我不能做其他事情, 我必须等待这个结果, 这就叫同步回调, 同步, 就意味着等待, 我不能去做其他事情, 必须等待, 异步回调就是, 我买了彩票之后, 可以去做其他事情, 然后当彩票网有了结果和消息, 再给我返回消息, 其中最明显的方式就是在得到彩票结果的函数之中, 添加一个其他的方法, 如果我的其他方法可

  • 浅谈java如何实现Redis的LRU缓存机制

    目录 LRU概述 使用LinkedHashMap实现 使用LinkedHashMap简单方法实现 双链表+hashmap LRU概述 最近使用的放在前面,最近没用的放在后面,如果来了一个新的数,此时内存满了,就需要把旧的数淘汰,那为了方便移动数据,肯定就得使用链表类似的数据结构,再加上要判断这条数据是不是最新的或者最旧的那么应该也要使用hashmap等key-value形式的数据结构. 使用LinkedHashMap实现 package thread; import java.util.Link

  • 带你入门java雪花算法原理

    雪花算法(SnowFlake) 雪花算法是Twitter开源的分布式ID生成算法. 主要是由64bit的long型生成的全局ID,引入了时间戳和ID保持自增的属性. 64bit分为四个部分: 第一个部分是1bit, 这不 使用,没有意义; 第二个部分是41bit, 组成时间戳; 第三个部分是10bit, 工作机器ID,里面分为两个部分,5个bit是的是机房号,代表最多有25即32个机房,5个bit是指机器的ID,代表最多有25个机器,即32个机器 . 第四部分是12bit, 代表是同一个毫秒类产

  • Java异步非阻塞编程的几种方式总结

    1 服务端执行,最简单的同步调用方式: 缺陷: 服务端响应之前,IO会阻塞在: java.net.SocketInputStream#socketRead0 的native方法上: 2 JDK NIO & Future java 1.5之后 优点:主线程可以不用等待IO响应,可以去做点其他的,比如说再发送一个IO请求,可以等到一起返回; 缺点:主线程在等待结果返回过程中依然需要等待,没有根本解决此问题; 3 使用Callback回调方式 优点:主线程完成发送请求后,再也不用关心这个逻辑,去执行其

  • Java编程技巧:if-else优化实践总结归纳

    目录 一.使用策略枚举来优化if-else 二.使用三目运算符来优化if-else 1.根据if-else条件来判断赋值的,如: 2.利用if-else条件来判断调用方法,如: 三.使用Stream优化if中判断条件过多情况 四.使用Map优化if-else 五.使用枚举优化if-else 六.使用Optional类优化if-else 总结 一.使用策略枚举来优化if-else 看到网上蛮多人推荐使用策略模式来优化if-else,但我总觉得,搞一堆策略类来优化大批量if-else,虽然想法很好,

  • Java实现雪花算法的原理和实战教程

    目录 SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法.其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id.在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的,后面的代码中有详细的注解. 这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号. 给大家举个例子吧,比如下面那个 64 bit 的 long 型数字: 第一

  • Java实现雪花算法的原理

    SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法.其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id.在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的,后面的代码中有详细的注解. 这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号. 给大家举个例子吧,比如下面那个 64 bit 的 long 型数字: 第一个部分

  • Java 基于雪花算法生成分布式id

    SnowFlake算法原理介绍 在分布式系统中会将一个业务的系统部署到多台服务器上,用户随机访问其中一台,而之所以引入分布式系统就是为了让整个系统能够承载更大的访问量.诸如订单号这些我们需要它是全局唯一的,同时我们基本上都会将它作为查询条件:出于系统安全考虑不应当让其它人轻易的就猜出我们的订单号,同时也要防止公司的竞争对手直接通过订单号猜测出公司业务体量:为了保证系统的快速响应那么生成算法不能太耗时.而雪花算法正好解决了这些问题. SnowFlake 算法(雪花算法), 是Twitter开源的分

  • Java实现雪花算法(snowflake)

    本文主要介绍了Java实现雪花算法(snowflake),分享给大家,具体如下: 简单描述 最高位是符号位,始终为0,不可用. 41位的时间序列,精确到毫秒级,41位的长度可以使用69年.时间位还有一个很重要的作用是可以根据时间进行排序.注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) 后得到的值,这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序SnowFlake类的START_STMP属性).41位的时间截

  • Java实现雪花算法的示例代码

    一.介绍 SnowFlow算法是Twitter推出的分布式id生成算法,主要核心思想就是利用64bit的long类型的数字作为全局的id.在分布式系统中经常应用到,并且,在id中加入了时间戳的概念,基本上保持不重复,并且持续一种向上增加的方式. 在这64bit中,其中``第一个bit是不用的,然后用其中的41个bit作为毫秒数,用10bit作为工作机器id,12bit`作为序列号.具体如下图所示: 第一个部分:0,这个是个符号位,因为在二进制中第一个bit如果是1的话,那么都是负数,但是我们生成

  • Java中Prime算法的原理与实现详解

    目录 Prim算法介绍 1.点睛 2.算法介绍 3. 算法步骤 4.图解 Prime 算法实现 1.构建后的图 2.代码 3.测试 Prim算法介绍 1.点睛 在生成树的过程中,把已经在生成树中的节点看作一个集合,把剩下的节点看作另外一个集合,从连接两个集合的边中选择一条权值最小的边即可. 2.算法介绍 首先任选一个节点,例如节点1,把它放在集合 U 中,U={1},那么剩下的节点为 V-U={2,3,4,5,6,7},集合 V 是图的所有节点集合. 现在只需要看看连接两个集合(U 和 V-U)

  • 图解Java中插入排序算法的原理与实现

    目录 一.基本思想 二.算法分析 1.算法描述 2.过程分析 三.算法实现 一.基本思想 插入排序(Insertion-Sort)的算法描述是一种简单直观的排序算法.它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入. 二.算法分析 1.算法描述 一般来说,插入排序都采用in-place在数组上实现.具体算法描述如下: 从第一个元素开始,该元素可以认为已经被排序: 取出下一个元素,在已经排序的元素序列中从后向前扫描: 如果该元素(已排序)大于新元素,将

  • 图解Java中归并排序算法的原理与实现

    目录 一.基本思想 二.算法分析 1.算法描述 2.过程分析 3.动图演示 三.算法实现 一.基本思想 归并排序是建立在归并操作上的一种有效的排序算法.该算法是采用分治法(Divide and Conquer)的一个非常典型的应用.将已有序的子序列合并,得到完全有序的序列:即先使每个子序列有序,再使子序列段间有序.若将两个有序表合并成一个有序表,称为2-路归并. 二.算法分析 1.算法描述 把长度为n的输入序列分成两个长度为n/2的子序列:对这两个子序列分别采用归并排序:将两个排序好的子序列合并

  • mybatis-plus雪花算法自动生成机器id原理及源码

    1.雪花算法原理 雪花算法使用一个 64 bit 的 long 型的数字作为全局唯一 id.这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数.生成的id一般都是用整数,所以最高位固定为0. 41bit-时间戳,用来记录时间戳,毫秒级. 10bit-工作机器id,用来记录工作机器id. 12bit-序列号,序列号,用来

  • Java实现插入排序算法可视化的示例代码

    参考文章 图解Java中插入排序算法的原理与实现 实现效果 示例代码 import java.awt.*; public class AlgoVisualizer { private static int DELAY = 40; private InsertionSortData data; private AlgoFrame frame; public AlgoVisualizer(int sceneWidth, int sceneHeight, int N){ // 初始化数据 data =

随机推荐