静态内部类单例模式实现雪花算法

在生成表主键ID时,我们可以考虑主键自增 或者 UUID,但它们都有很明显的缺点

主键自增1、自增ID容易被爬虫遍历数据。2、分表分库会有ID冲突。

UUID: 1、太长,并且有索引碎片,索引多占用空间的问题 2、无序。

雪花算法就很适合在分布式场景下生成唯一ID,它既可以保证唯一又可以排序。为了提高生产雪花ID的效率,

在这里面数据的运算都采用的是位运算,如果对位运算不了解可以参考博客:【java提高】(17)---Java 位运算符

一、概念

1、原理

SnowFlake算法生成ID的结果是一个64bit大小的整数,它的结构如下图:

算法描述:

  • 1bit 因为二进制中最高位是符号位,1表示负数,0表示正数。生成的ID都是正整数,所以最高位固定为0。

  • 41bit-时间戳 精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。

  • 10bit-工作机器id 10位的机器标识,10位的长度最多支持部署1024个节点。

  • 12bit-序列号 序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号。

    12位(bit)可以表示的最大正整数是,即可以用0、1、2、3、....4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

说明 由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。

二、静态类部类单例模式生产雪花ID代码

下面生成雪花ID的代码可以用于线上分布式项目中来生成分布式主键ID,因为设计采用的静态内部类的单例模式,通过加synchronized锁来保证在

同一个服务器线程安全。至于不同服务器其实是不相关的,因为它们的机器码是不一致的,所以就算同一时刻两台服务器都产生了雪花ID,那也不会一样的。

1、代码

/**
* @author xub
* @Description: 雪花算法
* @date 2019/8/14 下午8:22
*/
@Slf4j
public class SnowIdUtils {
/**
* 私有的 静态内部类
*/
private static class SnowFlake { /**
* 内部类对象(单例模式)
*/
private static final SnowIdUtils.SnowFlake SNOW_FLAKE = new SnowIdUtils.SnowFlake();
/**
* 起始的时间戳
*/
private final long START_TIMESTAMP = 1557489395327L;
/**
* 序列号占用位数
*/
private final long SEQUENCE_BIT = 12;
/**
* 机器标识占用位数
*/
private final long MACHINE_BIT = 10;
/**
* 时间戳位移位数
*/
private final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT;
/**
* 最大序列号 (4095)
*/
private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);
/**
* 最大机器编号 (1023)
*/
private final long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT);
/**
* 生成id机器标识部分
*/
private long machineIdPart;
/**
* 序列号
*/
private long sequence = 0L;
/**
* 上一次时间戳
*/
private long lastStamp = -1L; /**
* 构造函数初始化机器编码
*/
private SnowFlake() {
//模拟这里获得本机机器编码
long localIp = 4321;
//localIp & MAX_MACHINE_ID最大不会超过1023,在左位移12位
machineIdPart = (localIp & MAX_MACHINE_ID) << SEQUENCE_BIT;
}
/**
* 获取雪花ID
*/
public synchronized long nextId() {
long currentStamp = timeGen();
//避免机器时钟回拨
while (currentStamp < lastStamp) {
// //服务器时钟被调整了,ID生成器停止服务.
throw new RuntimeException(String.format("时钟已经回拨. Refusing to generate id for %d milliseconds", lastStamp - currentStamp));
}
if (currentStamp == lastStamp) {
// 每次+1
sequence = (sequence + 1) & MAX_SEQUENCE;
// 毫秒内序列溢出
if (sequence == 0) {
// 阻塞到下一个毫秒,获得新的时间戳
currentStamp = getNextMill();
}
} else {
//不同毫秒内,序列号置0
sequence = 0L;
}
lastStamp = currentStamp;
//时间戳部分+机器标识部分+序列号部分
return (currentStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | machineIdPart | sequence;
}
/**
* 阻塞到下一个毫秒,直到获得新的时间戳
*/
private long getNextMill() {
long mill = timeGen();
//
while (mill <= lastStamp) {
mill = timeGen();
}
return mill;
}
/**
* 返回以毫秒为单位的当前时间
*/
protected long timeGen() {
return System.currentTimeMillis();
}
} /**
* 获取long类型雪花ID
*/
public static long uniqueLong() {
return SnowIdUtils.SnowFlake.SNOW_FLAKE.nextId();
}
/**
* 获取String类型雪花ID
*/
public static String uniqueLongHex() {
return String.format("%016x", uniqueLong());
} /**
* 测试
*/
public static void main(String[] args) throws InterruptedException {
//计时开始时间
long start = System.currentTimeMillis();
//让100个线程同时进行
final CountDownLatch latch = new CountDownLatch(100);
//判断生成的20万条记录是否有重复记录
final Map<Long, Integer> map = new ConcurrentHashMap();
for (int i = 0; i < 100; i++) {
//创建100个线程
new Thread(() -> {
for (int s = 0; s < 2000; s++) {
long snowID = SnowIdUtils.uniqueLong();
log.info("生成雪花ID={}",snowID);
Integer put = map.put(snowID, 1);
if (put != null) {
throw new RuntimeException("主键重复");
}
}
latch.countDown();
}).start();
}
//让上面100个线程执行结束后,在走下面输出信息
latch.await();
log.info("生成20万条雪花ID总用时={}", System.currentTimeMillis() - start);
}
}

2、测试结果

从图中我们可以得出

1、在100个线程并发下,生成20万条雪花ID的时间大概在1.6秒左右,所有所性能还是蛮ok的。

2、生成20万条雪花ID并没有一条相同的ID,因为有一条就会抛出异常了。

3、为什么说41位时间戳最长只能有69年

我们思考41的二进制,最大值也就41位都是1,也就是也就是说41位可以表示个毫秒的值,转化成单位年则是

我们可以通过代码泡一下就知道了。

    public static void main(String[] args) {
//41位二进制最小值
String minTimeStampStr = "00000000000000000000000000000000000000000";
//41位二进制最大值
String maxTimeStampStr = "11111111111111111111111111111111111111111";
//转10进制
long minTimeStamp = new BigInteger(minTimeStampStr, 2).longValue();
long maxTimeStamp = new BigInteger(maxTimeStampStr, 2).longValue();
//一年总共多少毫秒
long oneYearMills = 1L * 1000 * 60 * 60 * 24 * 365;
//算出最大可以多少年
System.out.println((maxTimeStamp - minTimeStamp) / oneYearMills);
}

运行结果

所以说雪花算法生成的ID,只能保证69年内不会重复,如果超过69年的话,那就考虑换个服务器部署吧,并且要保证该服务器的ID和之前都没有重复过。

 我相信,无论今后的道路多么坎坷,只要抓住今天,迟早会在奋斗中尝到人生的甘甜。抓住人生中的一分一秒,胜过虚度中的一月一年!(5)

java算法(4)---静态内部类实现雪花算法的更多相关文章

  1. Snowflake(雪花算法),什么情况下会冲突?

    文章首发在公众号(龙台的技术笔记),之后同步到博客园和个人网站:xiaomage.info 分布式系统中,有一些需要使用全局唯一 ID 的场景,这种时候为了防止 ID 冲突可以使用 36 位的 UUI ...

  2. Twitter雪花算法 SnowFlake算法 的java实现

    概述 SnowFlake算法是Twitter设计的一个可以在分布式系统中生成唯一的ID的算法,它可以满足Twitter每秒上万条消息ID分配的请求,这些消息ID是唯一的且有大致的递增顺序. 原理 Sn ...

  3. 【Java】分布式自增ID算法---雪花算法 (snowflake,Java版)

    一般情况,实现全局唯一ID,有三种方案,分别是通过中间件方式.UUID.雪花算法. 方案一,通过中间件方式,可以是把数据库或者redis缓存作为媒介,从中间件获取ID.这种呢,优点是可以体现全局的递增 ...

  4. 基于雪花算法生成分布式ID(Java版)

    SnowFlake算法原理介绍 在分布式系统中会将一个业务的系统部署到多台服务器上,用户随机访问其中一台,而之所以引入分布式系统就是为了让整个系统能够承载更大的访问量.诸如订单号这些我们需要它是全局唯 ...

  5. 雪花算法(SnowFlake)Java实现

    分布式id生成算法的有很多种,Twitter的SnowFlake就是其中经典的一种. 算法原理 SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图: 1bit,不用,因为二 ...

  6. java 雪花算法实现获取分布式id

    import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.NetworkI ...

  7. 雪花算法 Java 版

    雪花算法根据时间戳生成有序的 64 bit 的 Long 类型的唯一 ID 各 bit 含义: 1 bit: 符号位,0 是正数 1 是负数, ID 为正数,所以恒取 0 41 bit: 时间差,我们 ...

  8. 分布式系统-主键唯一id,订单编号生成-雪花算法-SnowFlake

    分布式系统下 我们每台设备(分布式系统-独立的应用空间-或者docker环境) * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作 ...

  9. mybatis plus 主键生成 Twitter雪花算法 id 及修改id为字符型

    mybatis plus配置主键生成策略为2,就是 使用Twitter雪花算法 生成id spring boot中配置为: GlobalConfiguration conf = new GlobalC ...

随机推荐

  1. 大数据 SQL Boy 脱坑指南

    不可否认的是 SQL 是一个伟大的发明,它让增删改查的操作更加地便捷化,而且 SQL 的学习成本相对其他编程语言来说较低,被逼到会写 SQL 的运营和产品我都见过不少... 大数据行业跟 SQL 更是 ...

  2. 剖析Unreal Engine超真实人类的渲染技术Part 3 - 毛发渲染及其它

    目录 四.毛发渲染 4.1 毛发的构造及渲染技术 4.1.1 毛发的构造 4.1.2 Marschner毛发渲染模型 4.1.3 毛发的间接光照 4.2 毛发的底层实现 4.3 毛发的材质解析 4.3 ...

  3. opatch卸载weblogic12.1.3.0补丁

    1.首先进入opatch目录 2.查看opatch命令(心急于卸载的朋友直接跳过这步) [weblogic@localhost OPatch]$ ./opatch -help Oracle 中间补丁程 ...

  4. (CVE-2017-10271)weblogic12.1.3.0漏洞测试与打补丁过程

    1.漏洞测试 搭建完成weblogic12.1.3.0后,开始用工具测试 点击connect,右下角显示connected,说明已连接→说明漏洞存在 CMD输入:ls   ,然后点击Execute执行 ...

  5. springboot项目问题记录one

    上面三个方法描述如下: 首先有个业务,我是需要调取第三方一个sdk,然后sdk里面封装的kafka,也就是说,需要用sdk内置的连接kafka去消费消息,然后又有一个类需要实现Message,此Mes ...

  6. python基础知识五 各类型数据方法补充,转换,分类,编码+坑中菜

    3.9各类型数据方法补充,转换,分类,编码,坑中菜 3.9.1数据类型方法补充 1.str:不可变 补充方法 s1.capitalize():首字母大写 s1 = "alex" s ...

  7. 个人永久性免费-Excel催化剂功能第44波-可见区域复制粘贴不覆盖隐藏内容

    Excel的复制粘贴操作,每天都在进行,若其中稍能提升一点效率,长久来说,实在是很可观的效率提升. Excel自带的复制粘贴功能,若复制的数据源或粘贴的目标位置中有隐藏的行列内容,简单一个复制粘贴充满 ...

  8. 小代学Spring Boot之自定义Starter

    想要获取更多文章可以访问我的博客 - 代码无止境. 上一篇小代同学在Spring Boot项目中配置了数据源,但是通常来讲我们访问数据库都会通过一个ORM框架,很少会直接使用JDBC来执行数据库操作的 ...

  9. RabbitMQ 实践之在处理异步任务中的流程

    一.背景: 我司的系统,用户可以创建任务,启动任务,但任务的运行需要很长的时间,所以采用消息队列的方式,后台异步处理. 这里所用到的是 RabbitMQ . 二.MQ 处理任务的流程 ① ② ③ ④ ...

  10. vue history模式下出现空白页情况

    问题描述:   vue搭建的项目,路由一直用的hash模式,所以url中都会带有一个“#”号.现在想要去掉“#”,于是使用history模式 { mode: 'history' },代码如下: imp ...