[分布式] ------ 全局唯一id生成之雪花算法(Twitter_Snowflake)

雪花算法(Twitter_Snowflake)

我们知道,分布式全局唯一id的生成,一般是以下几种:

  1. 基于雪花算法生成
  2. 基于数据库
  3. 基于redis
  4. 基于zookeeper

本文说下雪花算法,后面附源码以及测试代码。

如下图:
在这里插入图片描述

如上图:雪花算法生成的id,总共64位
第一位作为保留位,默认0
中间41位用来存放时间戳,是当前时间与初始时间的差值,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
后10位是机器id,可满足同时(1L << 10) = 1024个机器同时生成id
最后12位作为随机序列,每个机器,每毫秒可生成(1L << 12) = 4096个不同的值

改进:

其实雪花算法就是把id按位打散,然后再分成上面这几块,用位来表示状态,这其实就是一种思想。
所以咱们实际在用的时候,也不必非得按照上面这种分割,只需保证总位数在64位即可

  1. 如果你的业务不需要69年这么长,或者需要更长时间
    用42位存储时间戳,(1L << 42) / (1000L * 60 * 60 * 24 * 365) = 139年
    用41位存储时间戳,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
    用40位存储时间戳,(1L << 40) / (1000L * 60 * 60 * 24 * 365) = 34年
    用39位存储时间戳,(1L << 39) / (1000L * 60 * 60 * 24 * 365) = 17年
    用38位存储时间戳,(1L << 38) / (1000L * 60 * 60 * 24 * 365) = 8年
    用37位存储时间戳,(1L << 37) / (1000L * 60 * 60 * 24 * 365) = 4年

  2. 如果你的机器没有那么1024个这么多,或者比1024还多
    用7位存储机器id,(1L << 7) = 128
    用8位存储机器id,(1L << 8) = 256
    用9位存储机器id,(1L << 9) = 512
    用10位存储机器id,(1L << 10) = 1024
    用11位存储机器id,(1L << 11) = 2048
    用12位存储机器id,(1L << 12) = 4096
    用13位存储机器id,(1L << 13) = 8192

  3. 如果你的业务,每个机器,每毫秒最多也不会4096个id要生成,或者比这个还多
    用8位存储随机序列,(1L << 8) = 256
    用9位存储随机序列,(1L << 9) = 512
    用10位存储随机序列,(1L << 10) = 1024
    用11位存储随机序列,(1L << 11) = 2048
    用12位存储随机序列,(1L << 12) = 4096
    用13位存储随机序列,(1L << 13) = 8192
    用14位存储随机序列,(1L << 14) = 16384
    用15位存储随机序列,(1L << 15) = 32768
    注意,随机序列建议不要太大,一般业务,每毫秒要是能产生这么多id,建议在机器id上增加位

  4. 如果你的业务量很小,比如一般情况下每毫秒生成不到1个id,此时可以将随机序列设置成随机开始自增
    比如从0到48随机开始自增,算是一种优化建议

  5. 如果你有多个业务,也可以拿出来几位来表示业务,比如用最后4位,支持16种业务的区分

  6. 如果你的业务特别复杂,可以考虑128位存储,不过这样的话,也可以考虑使用uuid了,但uuid无序,这个有序

  7. 如果你的业务很简单,甚至可以考虑32位存储,时间戳改成秒为单位…

总结:

  1. 合理的根据自己的实际情况去设计各个唯一条件的组合,雪花算法只是提供了一种相对合理的方式。
  2. 雪花算法这种用位来表示状态的,我们还可以用在其他方面,比如数据库存储,可以用更小的空间去表示不同的状态位
    包括各种底层的比如序列化,也是有用到拆解位,充分利用存储

源码:

package com.zs.common;/*** Twitter_Snowflake<br>* SnowFlake的结构如下(每部分用-分开):<br>* 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>* 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>* 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)* 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>* 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>* 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>* 加起来刚好64位,为一个Long型。<br>* SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。*/
public class SnowflakeIdWorker {// ==============================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 SnowflakeIdWorker(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;}// ==============================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位的IDreturn ((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) {long start = System.currentTimeMillis();SnowflakeIdWorker idWorker = new SnowflakeIdWorker(1, 3);for (int i = 0; i < 50; i++) {long id = idWorker.nextId();System.out.println(Long.toBinaryString(id));System.out.println(id);}long end = System.currentTimeMillis();System.out.println(end - start);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

非对称加解密交互故事

1.鲍勃有两把钥匙&#xff0c;一把是公钥&#xff0c;另一把是私钥。 2.鲍勃把公钥送给他的朋友们—-帕蒂、道格、苏珊—-每人一把。 3.苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密&#xff0c;就可以达到保密的效果 4.鲍勃收信后&#xff0c;用私钥解密&#xff0…

2019年规划

2019年规划&#xff0c;到2019-12-31检查&#xff1a; 1.至少读5本与工作无关的书&#xff08;平均两个月1本&#xff09;&#xff0c;每本都产出读后感 1.1 《重构 改善代码既有设计》&#xff0c;2月19日~3月5日&#xff0c;刚好两周读完&#xff0c;产出两篇总结&#xff0…

RSA私钥文件(PEM-PKCS#8)解析

***此文仅针对没有执行加密的PKCS#8私钥文件***一、实例解析PKCS#8格式使用的是ASN.1结构&#xff0c;首先我们对一个没有执行加密的PKCS#8格式私钥文件进行一下解析&#xff0c;方便我们下面分析其结构。命令&#xff1a;openssl asn1parse -i -in privatekey.pem结果如下&…

分布式和集群的区别

分布式和集群的区别 分布式是多个不同功能的机器共同完成一件事情 集群是多个相同功能的机器完成的是相同的事情 分布式是为了分担压力 集群是为了稳定性和高可用 举个例子&#xff1a; 一个厨师开了一个饭店&#xff0c;他要负责做饭和卖饭&#xff0c;他的压力就比较大&am…

Sqlite3中replace语句用法详解

在本例中使用如下数据库表&#xff1a; &#xff08;图 1&#xff09; 该表的表名为student&#xff0c; 存储学生信息。 所有字段的数据类型都是TEXT 。 其中id和name作为复合主键。 email字段加上了唯一约束。建表语句如下&#xff1a; CREATE TABLE IF NOT EXISTS student …

[分布式一致性协议] ------ raft协议的解释与理解

前言 在分布式系统中&#xff0c;为了保证容错性&#xff0c;一般会维护多个副本集群&#xff0c;提高系统的高可用&#xff0c;但与之带来的问题就是多个副本的一致性&#xff08;consensus&#xff09;问题。 我们认为&#xff0c;对于一个具有一致性的的集群中&#xff0c;…

iOS应用图片命名规则

一、界面图片命名规则&#xff1a;MyImage.png 一般图片命名MyImage2x.png 高清图片命名MyImage~iphone.png iPhone 和 iPod touch版一般图片命名MyImage2x~iphone.png iPhone 和 iPod touch版高清图片命名MyImage~ipad.png …

用户自定义排序的几种实现思路

场景 每个用户&#xff0c;有多个分组 每个分组在页面展示&#xff0c;而且是有顺序的&#xff0c;这个顺序是由用户决定 以下是关于多种情况下的库表设计思路&#xff1a; 情景一&#xff1a;如果每改一次&#xff0c;就要实时修改库&#xff0c;而且用户可以任意修改顺序&…

重构,体现一个工程师的基本素养和底蕴

重构小记&#xff08;重构&#xff0c;改善既有代码的设计读后总结&#xff09; 我们要时时刻刻保持一颗项目要重构的心。 在非技术出身的领导看来&#xff0c;能用的代码就是好代码&#xff0c;只关注功能。 在工程师看来&#xff0c;代码不仅要好用&#xff0c;更要好看&…

应用内购买(IAP)各类型在服务端的验证规则

一、非消耗品(比如单本杂志购买,苹果服务器支持恢复) 1.先验证服务器有没有购买记录&#xff0c;如果有&#xff0c;则不处理&#xff0c;此次操作成功 &#xff1b;2.如果服务器没有购买记录&#xff0c;则到苹果服务器验证(1)首先到正式验证地址验证收据&#xff0c;如果返回…

@Transactional事务生效条件与样例

Transactional事务生效条件 Transactional注释的方法&#xff0c;不能是private修饰 Transactional注释的方法&#xff0c;必须是有接口的方法实现&#xff08;通用的Spring面向接口编程的套路&#xff09; Transactional注释的方法&#xff0c;必须要通过接口的方式调用&…

利用.dSYM和.app文件准确定位Crash位置

当发布到iPhone上的应用程序Crash之后&#xff0c;iPhone会自动生成一个Crash Log&#xff08;*.crash&#xff09;&#xff0c;这个文件包含了一些有用的调试信息&#xff0c;但对于堆栈&#xff0c;它只记录的函数地址&#xff0c;而无法显示函数名。函数名保存在一个叫dSYM的…

使用maven的profile区分本地环境和线上环境

使用maven的profile区分本地环境和线上环境 多环境开发&#xff0c;使用maven-profile&#xff0c;就可以在打包的时候通过参数的调整&#xff0c;最终打的包也不同。 以区分本地数据库和线上数据库为例 比如测试环境&#xff0c;用的是本地测试数据库&#xff1b;生产环境用…

查看函数库.a函数符号信息

一、概述 nm命令可以列出一个函数库文件中的符号表。它对于静态的函数库和共享的函数库都起作用。对于一个给定的函数库&#xff0c;nm命令可以列出函数库中定义的所有符号&#xff0c;包括每个符号的值和类型。还可以给出在原程序中这个函数&#xff08;符号&#xff09;是在多…

重构,体现一个工程师的基本素养和底蕴(细节篇)

重构小记&#xff08;重构&#xff0c;改善既有代码的设计读后总结&#xff09; 方法级别 提炼函数&#xff1a; 将一个大方法&#xff0c;拆成多个小方法&#xff0c;难点在于小方法的命名。 假如有早上上学的一个大方法&#xff0c; 那么就应该在里面有起床&#xff0c;穿衣…

MVPVM模式介绍

一、概述MVPVM即&#xff1a;Model-View-Presenter-ViewModel。此模式是MVVM和MVP模式的结合体。但是交互模式发生了比较大的变化。MVVM参考本博客文章&#xff1a;iOS-MVVM-模式介绍MVP参考本博客文章&#xff1a;MVP模式介绍 二、原理&#xff1a;Presenter同时持有View、Mod…

[线程池] ------ 形象的描述线程池,用一个特好记的例子来记忆

线程池 为了减少线程频繁的创建和销毁过程&#xff0c;引入池的概念。 将一些线程先创建好放在线程池中&#xff0c;每次来任务就用池中的线程执行&#xff0c;空闲时池中线程就等待&#xff0c;但不销毁。 原始线程池的创建&#xff1a; ThreadPoolExecutor executor1 new …

分组密码的工作模式

一、理论基础1.概述密码学中&#xff0c;块密码的工作模式允许使用同一个块密码密钥对多于一块的数据进行加密&#xff0c;并保证其安全性。块密码自身只能加密长度等于密码块长度的单块数据&#xff0c;若要加密变长数据&#xff0c;则数据必须先被划分为一些单独的密码块。通…

数据仓库基本认知

数据仓库概念&#xff1a; 数据仓库&#xff0c;英文名称Data Warehouse&#xff0c;简写为DW。 是一种面向分析的存储系统。 他是一个很大的数据存储集合&#xff0c;出于企业的分析性报告和决策支持目的而创建&#xff0c;对多样的业务数据进行筛选与整合。 它为企业提供一…

PBOC3.0中使用的国密SM2算法

一、知识准备 PBOC3.0规范就是《中国金融集成电路&#xff08;IC&#xff09;卡规范》3.0版本。SM2是国密局推出的一种他们自己说具有自主知识产权的非对称商用密码算法。本身是基于ECC椭圆曲线算法的&#xff0c;所以要讲SM2, 先要弄懂ECC。 完全理解ECC算法需要一定的数学功底…