背景

随着现在业务量的越来越大,数据库的划分也变的越来越细,分库分表的理念也渐渐的落地,自增主键或者序列之类的主键id生成方式已经不再满足需求,所以分布式ID的生成就应运而生,总的来说就是生成规则更加负责,减少重复的概率。

一、雪花算法

雪花算法的原始版本是scala版,用于生成分布式ID(纯数字,时间顺序),订单编号等。

自增ID:对于数据敏感场景不宜使用,且不适合于分布式场景。
GUID:采用无意义字符串,数据量增大时造成访问过慢,且不宜排序。

算法描述:

  • 最高位是符号位,始终为0,不可用。
  • 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
  • 10位的机器标识,10位的长度最多支持部署1024个节点。
  • 12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。

二、时间部分

时间部分的逻辑起始很简单,就是规定一个起始时间戳,然后用当前时间戳减去起始时间戳,这两个数的差就是我们要的结果,但是有一个问题如果系统的时间出错了怎么办,毕竟我们正常取的都是服务器的时间,所以需要校验

        //如果当前时间小于上次ID生成的时间,说明系统回退过抛出异常if (now < LAST_TIME_STAMP) {log.info("系统时间异常,请检查!");throw new RuntimeException("系统时间异常!");}

三、机器信息部分

机器信息,占10位。我们这里把机器信息分成两部分,一部分是数据中心id,占5位,一部分是机器id,占5位。这两个id可以在部署项目的时候根据不同的机器自定义不同的id,这样能人为的保障每个id都不同。jdk库中,有api可以获取本地机器的hostname和hostaddress,可以把hostname的信息作为数据中心id,把hostaddress的信息作为机器id,如何把两个字符串改为两个数字id呢?其实很简单。获取字符串的字节数组,然后把数组的每个数字相加,对节点数的最大值取余。因为都是5位,所以最大值是31,需要对32取余。那么雪花算法可以部署的机器总数就是32*32=1024个,这是机器信息的限制。

private static int getDataId() {try {return getHostId(Inet4Address.getLocalHost().getHostName(), DATA_MAX_NUM);} catch (UnknownHostException e) {return new Random().nextInt(DATA_RANDOM);}}private static int getWorkId() {try {return getHostId(Inet4Address.getLocalHost().getHostAddress(), WORK_MAX_NUM);} catch (UnknownHostException e) {return new Random().nextInt(WORK_RANDOM);}}private static int getHostId(String str, int max) {byte[] bytes = str.getBytes();int sums = 0;for (int b : bytes) {sums += b;}return sums % (max + 1);}

四、毫秒内序列

毫秒内的序列。什么意思呢?我们在生成时间部分获取时间戳的时候,使用 long now = System.currentTimeMillis(); 获取,是个毫秒级的时间戳,但是即使是这么短的时间,对于电脑来说也足够生成很多个id,所以很多id可能会在同一个毫秒内生成,也就是时间部分的数值一样。这个时候就要让同一个毫秒内生成的id加上数字序列标识,就是第三部分的序列。第三部分占的长度是12位,转成整数值就是4095,所以最后一部分的范围就是4095到0之间的数字。如果毫秒内访问的数量超过了这个限制怎么办?没法解决,只能强制等到下一毫秒再生产id。

五、实战

上面的讲述我们基本可以写出方法了,但是考虑到多线程,我们需要加锁保证。下面是完整代码。

@Slf4j
public class SnowflakeUtil {/*** 时间部分所占长度*/private static final int TIME_LEN = 41;/*** 数据中心id所占长度*/private static final int DATA_LEN = 5;/*** 机器id所占长度*/private static final int WORK_LEN = 5;/*** 毫秒内序列所占长度*/private static final int SEQ_LEN = 12;/*** 定义起始时间 2015-01-01 00:00:00*/private static final long START_TIME = 1420041600000L;/*** 上次生成id的时间戳*/private static long LAST_TIME_STAMP = -1L;/*** 时间部分向左移动的位数 22*/private static final int TIME_LEFT_BIT = 64 - 1 - TIME_LEN;/*** 自动获取数据中心id(可以手动定义0-31之间的任意数)*/private static final long DATA_ID = getDataId();/*** 自动获取机器id(可以手动定义0-31之间的任意数)*/private static final long WORK_ID = getWorkId();/*** 数据中心id最大值 31*/private static final int DATA_MAX_NUM = ~(-1 << DATA_LEN);/*** 机器id最大值 31*/private static final int WORK_MAX_NUM = ~(-1 << WORK_LEN);/*** 随机获取数据中心id的参数 32*/private static final int DATA_RANDOM = DATA_MAX_NUM + 1;/*** 随机获取机器id的参数 32*/private static final int WORK_RANDOM = WORK_MAX_NUM + 1;/*** 数据中心id左位移数 17*/private static final int DATA_LEFT_BIT = TIME_LEFT_BIT - DATA_LEN;/*** 机器id左位移数 12*/private static final int WORK_LEFT_BIT = DATA_LEFT_BIT - WORK_LEN;/*** 上一次毫秒内序列值*/private static long LAST_SEQ = 0L;/*** 毫秒内序列的最大值 4095*/private static final long SEQ_MAX_NUM = ~(-1 << SEQ_LEN);private final Object object = new Object();public synchronized static long getId() {long now = System.currentTimeMillis();//如果当前时间小于上次ID生成的时间,说明系统回退过抛出异常if (now < LAST_TIME_STAMP) {log.info("系统时间异常,请检查!");throw new RuntimeException("系统时间异常!");}if (now == LAST_TIME_STAMP) {LAST_SEQ = (LAST_SEQ + 1) & SEQ_MAX_NUM;if (LAST_SEQ == 0) {now = nextMillis(LAST_TIME_STAMP);}} else {LAST_SEQ = 0;}LAST_TIME_STAMP = now;return ((now - START_TIME) << TIME_LEFT_BIT) | (DATA_ID << DATA_LEFT_BIT) | (WORK_ID << WORK_LEFT_BIT) | LAST_SEQ;}private static long nextMillis(Long lastMillis) {long now = System.currentTimeMillis();while (now <= lastMillis) {now = System.currentTimeMillis();}return now;}private static int getDataId() {try {return getHostId(Inet4Address.getLocalHost().getHostName(), DATA_MAX_NUM);} catch (UnknownHostException e) {return new Random().nextInt(DATA_RANDOM);}}private static int getWorkId() {try {return getHostId(Inet4Address.getLocalHost().getHostAddress(), WORK_MAX_NUM);} catch (UnknownHostException e) {return new Random().nextInt(WORK_RANDOM);}}private static int getHostId(String str, int max) {byte[] bytes = str.getBytes();int sums = 0;for (int b : bytes) {sums += b;}return sums % (max + 1);}public static void main(String[] args) {
//        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//        try {
//            long time = dateFormat.parse("2015-01-01 00:00:00").getTime();
//            System.out.println(time);
//        } catch (ParseException e) {
//            e.printStackTrace();
//        }for (int i = 0; i <10 ; i++) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("-------------");System.out.println(getId());}}).start();}}
}

学习专题:雪花算法(07)雪花算法最终版

https://www.cnblogs.com/Hollson/p/9116218.html

分布式ID——雪花算法相关推荐

  1. 分布式ID雪花算法-解析

    SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图: 1位,不用.二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0 41位,用来记录时 ...

  2. 理解分布式id生成算法--雪花算法(SnowFlake)

    分布式ID生成算法的有很多种,Twitter的SnowFlake就是其中经典的一种. 注: 1B就是1个字节. Byte.KB.B.MB.GB之间的关系是: Bit--比特 : B --字节:KB-- ...

  3. python雪花算法生成id_理解分布式id生成算法SnowFlake

    分布式id生成算法的有很多种,Twitter的SnowFlake就是其中经典的一种. 概述 SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图: 1位,不用.二进制中最高位 ...

  4. 分布式ID生成算法——leaf算法

    leaf是美团在雪花算法的基础上提出的一种分布式ID生成算法,它具有全局唯一.高可用.高并发.低延迟.接入简单(http或公司内rpc)的优点. Twitter:世界上不存在两片一样的雪花. 美团:世 ...

  5. 美团技术分享:深度解密美团的分布式ID生成算法

    本文来自美团技术团队"照东"的分享,原题<Leaf--美团点评分布式ID生成系统>,收录时有勘误.修订并重新排版,感谢原作者的分享. 1.引言 鉴于IM系统中聊天消息I ...

  6. java怎样生成32位全是整形的主键_你肯定会需要的分布式Id生成算法雪花算法(Java)...

    最近公司正好在做数据库迁移从oracle到mysql,因为之前oracle主键是使用的 SYS_GUID() 这个oracle提供的函数来生成全球唯一的标识符(原始值)由16个字节组成. 不过由于my ...

  7. 存数据返回他的序列号id_雪花般的分布式唯一ID雪花算法

    点击上方 Java老铁,并选择 设为星标 优质文章和资料会及时送达 导读:唯一ID可以标识数据的唯一性,在分布式系统中生成唯一ID的方案有很多,常见的方式大概有以下三种 依赖数据库,使用如MySQL自 ...

  8. 【分布式ID】键高并发 分布式 全局唯一 ID 雪花算法 snowflake

    文章目录 1.概述 2.为什么 3.要求 3.1 软件要求 3.2 机器要求 4.区别 4.1 UUID 4.2 MySQL数据库自增ID 4.3 Redis数据库自增ID 5. 雪花算法 5.1 核 ...

  9. java不规则算法_分布式id生成算法 snowflake 详解

    背景 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.如在支付流水号.订单号等,随者业务数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息,数据库的自增ID显然不能满足需 ...

最新文章

  1. 批处理 探测在线计算机,批处理(bat)用来监测Windows网络状态脚本
  2. php txt,PHP规范TXT文件
  3. jvm在不同系统中的最大内存空间地址
  4. 「知识表示学习」专题论文推荐 | 每周论文清单
  5. 关于Qt、Qt/E、Qtopia、qvfb、framebuffer、qpe等概念的对比介绍
  6. java中instanceof使用详细介绍
  7. 【C#学习笔记】使用C#中的Dispatcher
  8. c语言只能最大值不能最小值,用c语言编写输入10个无序的整数,去掉一个最大值和最小值,然后求其平均值...
  9. leetcode-reverse words in a string
  10. 来自对象字段的Python字典
  11. 设计模式---命令模式(C++实现)
  12. [转]国外英语教学网页
  13. xp win7 linux 三系统下载,打造xp+linux+win7三系统教程.doc
  14. ug12对计算机配置要求,ug12.0对电脑配置要求
  15. 《给青年的十二封信》 朱光潜 (摘录)
  16. rails erb_您需要知道Rails中的erb以及如何掌握它
  17. 猫哥教你写爬虫 003--数据类型转换
  18. Redis源码阅读笔记 ----数据结构部分 sds
  19. 如何编写无法维护的代码
  20. 山寨凤凰新闻菜单效果

热门文章

  1. 本地文件包含与远程文件包含漏洞
  2. 杰里之解决开关机”POPO”声】【篇】
  3. LeetCode 每日一题 2021-6-15 (山峰数组的封顶索引)
  4. SYNOPSYS™非球面激光束整形器
  5. 2022-2027年中国大枣及深加工行业发展监测及投资战略研究报告
  6. 【docker启动容器错误】Error response from daemon: Mounts denied: EOF Error: failed to start containers:
  7. 解决Hash冲突的方法
  8. 三层网络PPS极限测试
  9. 。求推荐一个usb集线器的购买网址
  10. 邮箱群发邮件的小技巧