漫画:什么是SnowFlake算法

转载自 漫画:什么是SnowFlake算法







方法一:UUID


UUID是通用唯一识别码 (Universally Unique Identifier),在其他语言中也叫GUID,可以生成一个长度32位的全局唯一识别码。


String uuid = UUID.randomUUID().toString()


结果示例:


046b6c7f-0b8a-43b9-b35d-6489e6daee91






为什么无序的UUID会导致入库性能变差呢?


这就涉及到 B+树索引的分裂





众所周知,关系型数据库的索引大都是B+树的结构,拿ID字段来举例,索引树的每一个节点都存储着若干个ID。


如果我们的ID按递增的顺序来插入,比如陆续插入8,9,10,新的ID都只会插入到最后一个节点当中。当最后一个节点满了,会裂变出新的节点。这样的插入是性能比较高的插入,因为这样节点的分裂次数最少,而且充分利用了每一个节点的空间。





但是,如果我们的插入完全无序,不但会导致一些中间节点产生分裂,也会白白创造出很多不饱和的节点,这样大大降低了数据库插入的性能。







方法二:数据库自增主键


假设名为table的表有如下结构:


id        feild

35        a


每一次生成ID的时候,访问数据库,执行下面的语句:


begin;

REPLACE INTO table ( feild )  VALUES ( 'a' );

SELECT LAST_INSERT_ID();

commit;


REPLACE INTO 的含义是插入一条记录,如果表中唯一索引的值遇到冲突,则替换老数据。


这样一来,每次都可以得到一个递增的ID。


为了提高性能,在分布式系统中可以用DB proxy请求不同的分库,每个分库设置不同的初始值,步长和分库数量相等:





这样一来,DB1生成的ID是1,4,7,10,13....,DB2生成的ID是2,5,8,11,14.....









————————————














初识SnowFlake


snowflake算法所生成的ID结构是什么样子呢?我们来看看下图:





SnowFlake所生成的ID一共分成四部分:


1.第一位

占用1bit,其值始终是0,没有实际作用。


2.时间戳

占用41bit,精确到毫秒,总共可以容纳约69 年的时间。


3.工作机器id

占用10bit,其中高位5bit是数据中心ID(datacenterId),低位5bit是工作节点ID(workerId),做多可以容纳1024个节点。


4.序列号

占用12bit,这个值在同一毫秒同一节点上从0开始不断累加,最多可以累加到4095。


SnowFlake算法在同一毫秒内最多可以生成多少个全局唯一ID呢?只需要做一个简单的乘法:


同一毫秒的ID数量 = 1024 X 4096 =  4194304


这个数字在绝大多数并发场景下都是够用的。




SnowFlake的代码实现





//初始时间截 (2017-01-01)
private static final long INITIAL_TIME_STAMP = 1483200000000L;//机器id所占的位数
private static final long WORKER_ID_BITS = 5L;//数据标识id所占的位数
private static final long DATACENTER_ID_BITS = 5L;//支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);//支持的最大数据标识id,结果是31
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);//序列在id中占的位数
private final long SEQUENCE_BITS = 12L;//机器ID的偏移量(12)
private final long WORKERID_OFFSET = SEQUENCE_BITS;//数据中心ID的偏移量(12+5)
private final long DATACENTERID_OFFSET = SEQUENCE_BITS + SEQUENCE_BITS;//时间截的偏移量(5+5+12)
private final long TIMESTAMP_OFFSET = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;//生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
private final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);//工作节点ID(0~31)
private long workerId;//数据中心ID(0~31)
private long datacenterId;//毫秒内序列(0~4095)
private long sequence = 0L;//上次生成ID的时间截
private long lastTimestamp = -1L;/*** 构造函数** @param workerId     工作ID (0~31)* @param datacenterId 数据中心ID (0~31)*/
public SnowFlakeIdGenerator(long workerId, long datacenterId) {if (workerId > MAX_WORKER_ID || workerId < 0) {throw new IllegalArgumentException(String.format("WorkerID 不能大于 %d 或小于 0", MAX_WORKER_ID));}if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {throw new IllegalArgumentException(String.format("DataCenterID 不能大于 %d 或小于 0", MAX_DATACENTER_ID));}this.workerId = workerId;this.datacenterId = datacenterId;
}/*** 获得下一个ID (用同步锁保证线程安全)** @return SnowflakeId*/
public synchronized long nextId() {long timestamp =  System.currentTimeMillis();//如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常if (timestamp < lastTimestamp) {throw new RuntimeException("当前时间小于上一次记录的时间戳!");}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & SEQUENCE_MASK;//sequence等于0说明毫秒内序列已经增长到最大值if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - INITIAL_TIME_STAMP) << TIMESTAMP_OFFSET)| (datacenterId << DATACENTERID_OFFSET)| (workerId << WORKERID_OFFSET)| sequence;
}/*** 阻塞到下一个毫秒,直到获得新的时间戳** @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/
protected long tilNextMillis(long lastTimestamp) {long timestamp = System.currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = System.currentTimeMillis();}return timestamp;
}public static void main(String[] args) {final SnowFlakeIdGenerator idGenerator = new SnowFlakeIdGenerator(1, 1);//线程池并行执行10000次ID生成ExecutorService executorService = Executors.newCachedThreadPool();;for (int i = 0; i < 10000; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {long id = idGenerator.nextId();System.out.println(id);}});}executorService.shutdown();
}

这段代码改写自网上的SnowFlake算法实现,有几点需要解释一下:


1.获得单一机器的下一个序列号,使用Synchronized控制并发,而非CAS的方式,是因为CAS不适合并发量非常高的场景。


2.如果当前毫秒在一台机器的序列号已经增长到最大值4095,则使用while循环等待直到下一毫秒。


3.如果当前时间小于记录的上一个毫秒值,则说明这台机器的时间回拨了,抛出异常。但如果这台机器的系统时间在启动之前回拨过,那么有可能出现ID重复的危险。



SnowFlake的优势和劣势







SnowFlake算法的优点:


1.生成ID时不依赖于DB,完全在内存生成,高性能高可用。


2.ID呈趋势递增,后续插入索引树的时候性能较好。



SnowFlake算法的缺点:


依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序。






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

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

相关文章

FeignClient调用服务及上传文件的注意点及问题

目录代码示例文件服务接口调用方注意点&#xff1a;2021.3.16更新&#xff1a;发新的坑坑点&#xff1a;原因分析部分异常及解决方案异常一&#xff1a;[Method has too many Body parameters](https://blog.csdn.net/haishiyizhenfeng/article/details/80607003)异常二&#x…

redis主从复制部署策略+jedis设置主从

【README】 redis 有3种集群模式&#xff0c;包括 主从&#xff0c; 哨兵&#xff0c; cluster&#xff1b; 本文主要po出 主从&#xff1b; master 192.168.163.201 6382 slave 192.168.163.202:6382 【1】从机 202:6382 的 redis.conf 配置 只需要编写 slaveof 192.…

python绘图时的分解问题的步骤-零基础学python-15.2 分解函数

这一章节我们来说说函数的一些主要概念 我们以一小段代码为例&#xff1a; >>> def test(): pass >>> 1. def是可执行的代码 >>> test() >>> 我们调用test方法&#xff0c;尽管什么都没有&#xff0c;但是test已经执行了 2.def创建了一个对…

千万条数据,Stack Overflow是如何实现快速分页的

转载自 千万条数据&#xff0c;Stack Overflow是如何实现快速分页的 Stack Overflow 在分页机制中使用页码代替偏移量&#xff0c;页码指向基于 LIMIT 和 OFFSET 的查询。假设要对 1000 万条记录进行分页&#xff0c;跳到最后一页会非常慢&#xff0c;但 Stack Overflow 还是想…

ubuntu系统下安装docker并部署Springboot+mysql+redis

目录安装DockerDocker常用命令构建mysql容器构建Redis容器构建Springboot应用镜像及容器&#xff08;1&#xff09;springboot使用maven将程序打成jar包&#xff0c;接着编写Dokerfile文件&#xff0c;[Dockerfile详细介绍](http://www.dockerinfo.net/dockerfile%e4%bb%8b%e7%…

redis连接池

【README】 本文旨在po出 redis连接池的测试用例 <dependencies><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.5.1</version></dependency></dependencies> 【1】 代…

qmc0转换mp3工具_GoldenRecords for Mac(唱片录音转换软件)

想要将你最喜欢的唱片音乐转换成为方便易于携带的数字音频么&#xff1f;GoldenRecords Mac版是一款Macos上的唱片录音转换软件&#xff0c;用户可以将自己收藏的旧唱片快速转换成为自己喜欢的数字音频音乐&#xff0c;随时随地想听就听&#xff01;GoldenRecords mac版&#x…

java提高篇之抽象类与接口

转载自 java提高篇之抽象类与接口 接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法。 抽象类与接口是java语言中对抽象概念进行定义的两种机制&#xff0c;正是由于他们的存在才赋予java强大的面向对象的能力。他们两者之间对抽象概念的支持有很大的相似&…

Java中synchronized同步块的执行流程

必要知识 Java 对象的数据结构 在 HotSpot 虚拟机中&#xff0c;Java 对象在内存中存储的布局可以分为 3 块区域&#xff1a;对象头&#xff08;Header&#xff09;、实例数据&#xff08;Instance Data&#xff09;和对齐填充&#xff08;Padding&#xff09;对象头中的 Mar…

深入理解Java的接口和抽象类

转载自 深入理解Java的接口和抽象类 对于面向对象编程来说&#xff0c;抽象是它的一大特征之一。在Java中&#xff0c;可以通过两种形式来体现OOP的抽象&#xff1a;接口和抽象类。这两者有太多相似的地方&#xff0c;又有太多不同的地方。很多人在初学的时候会以为它们可以随意…

转: 记录centos7 安装erlang22.3和rabbitMQ

转&#xff1a; https://blog.csdn.net/weixin_44436611/article/details/109492936 记录centos7 安装erlang22.3和rabbitMQ 记录centos7 安装erlang22.3和rabbitMQTOC 官网地址&#xff1a;https://www.rabbitmq.com/install-rpm.html#install-erlang 部分rpm包&#xff1a;…

hashmap应用场景_工作中常用到的Java集合有哪些?应用场景是什么?

秋招Java面试大纲&#xff1a;Java并发spring数据库RedisJVMNetty等疫情期间“闭关修炼”&#xff0c;吃透这本Java核心知识&#xff0c;跳槽面试不心慌Spring全家桶笔记&#xff1a;SpringSpring BootSpring CloudSpring MVC前言Java集合是我认为在Java基础中最最重要的知识点…

Zookeeper入门总结

什么是Zookeeper 官方定义&#xff1a; zookeeper是一个分布式服务框架&#xff0c;是Apache Hadoop 的一个子项目&#xff0c;它主要是用来解决分布式应用中经常遇到的一些数据管理问题&#xff0c;如&#xff1a;统一命名服务、状态同步服务、集群管理、分布式应用配置项的管…

rabbitmq启动失败-报Failed to load advanced configuration file解决方法

【README】 1、本文基于 centos8 搭建 rabbitmq&#xff1b; 版本信息如下&#xff1a; rabbitmq rabbitmq-server-3.8.12-1.el8.noarch.rpm erlang erlang 23.2.5 (erts-11.1.8) 上述两者版本一定要强对应&#xff0c;否则mq启动失败&#xff0c;参见官网 https://rabb…

Java抽象类与接口的区别

转载自 Java抽象类与接口的区别 抽象类 抽象类是用来捕捉子类的通用特性的 。它不能被实例化&#xff0c;只能被用作子类的超类。抽象类是被用来创建继承层级里子类的模板。以JDK中的GenericServlet为例&#xff1a; public abstract class GenericServlet implements Servlet…

gateway坑点:gateway有Controller时会直接处理对应的路径并返回

前言 使用gateway过程中&#xff0c;因为在gateway中加入了一个标注RestController的类&#xff0c;导致请求直接由gateway中的RestController的类处理并返回。花费了几小时才发现问题原因&#xff0c;特此记录 问题 以gateway作为网关转发请求得到的响应&#xff0c;所有字…

电脑任务栏跑到右边去了_电脑没有声音怎么解决 电脑没有声音解决方法【详解】...

电脑没有声音怎么办? 这个话题在我耳边已经听到过了无数次了&#xff0c;处理故障也是由以前的摸索解决到现在可以快速的找到原因。但对于新手朋友来说可能不知道从哪里入手&#xff0c;对于电脑没有声音怎么办&#xff0c;也给不出一个对策&#xff0c;今天围绕这个问题&…

rabbitmq-java生产者消费者

【README】 本文使用java 连接rabbitmq&#xff0c;模拟生产者&#xff0c;消费者场景 【1】项目搭建 1&#xff09;maven项目&#xff0c;依赖 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance…

Java 8新特性——default方法(defender方法)介绍

转载自 Java 8新特性——default方法&#xff08;defender方法&#xff09;介绍 我们都知道在Java语言的接口中只能定义方法名&#xff0c;而不能包含方法的具体实现代码。接口中定义的方法必须在接口的非抽象子类中实现。下面就是关于接口的一个例子&#xff1a; 1 2 3 4 5 6 …

对于高并发的理解及实践方案

目录01 如何理解高并发&#xff1f;02 高并发系统设计的目标是什么&#xff1f;2.1标题宏观目标高并发绝不意味着只追求高性能&#xff0c;这是很多人片面的理解。2.2 微观目标2.2.1 性能指标2.2.2 可用性指标2.2.3 可扩展性指标03 高并发的实践方案有哪些&#xff1f;3.1 通用…