雪花算法及Java实现

news2025/1/18 6:28:04

一、全局唯一ID介绍

1、背景

现如今越来越多的公司都在用分布式、微服务,那么对应的就会针对不同的服务进行数据库拆分,然后当数据量上来的时候也会进行分表,那么随之而来的就是分表以后id的问题。
例如之前单体项目中一个表中的数据主键id都是自增的,mysql是利用autoincrement来实现自增,而oracle是利用序列来实现的,但是当单表数据量上来以后就要进行水平分表,阿里java开发建议是单表大于500w的时候就要分表,但是具体还是得看业务,如果索引用的号的话,单表千万的数据也是可以的。水平分表就是将一张表的数据分成多张表,那么问题就来了如果还是按照以前的自增来做主键id,那么就会出现id重复,就得考虑用什么方案来解决分布式id的问题了。

2、解决方案

2.1数据库表
可以在某个库中专门维护一张表,然后每次无论哪个表需要自增id的时候都去查这个表的记录,然后用for update锁表,然后取到的值加一,然后返回以后把再把值记录到表中,但是这个方法适合并发量比较小的项目,因此每次都得锁表。
2.2redis
因为redis是单线程的,可以在redis中维护一个键值对,然后哪个表需要直接去redis中取值然后加一,但是这个跟上面一样由于单线程都是对高并发的支持不高,只适合并发量小的项目。
2.3uuid
可以使用uuid作为不重复主键id,但是uuid有个问题就是其是无序的字符串,如果使用uuid当做主键,那么主键索引就会失效。
2.4雪花算法
雪花算法是解决分布式id的一个高效的方案,大部分互联网公司都在使用雪花算法,当然还有公司自己实现其他的方案。

雪花算法

1、原理

在这里插入图片描述
雪花算法就是使用64位long类型的数据存储id,最高位一位存储0或者1,0代表整数,1代表负数,一般都是0,所以最高位不变,41位存储毫秒级时间戳,10位存储机器码(包括5位datacenterId和5位workerId),12存储序列号。这样最大2的10次方的机器,也就是1024台机器,最多每毫秒每台机器产生2的12次方也就是4096个id。(下面有代码实现)
但是一般我们没有那么多台机器,所以我们也可以使用53位来存储id。为什么要用53位?
因为我们几乎都是跟web页面打交道,就需要跟js打交道,js支持最大的整型范围为53位,超过这个范围就会丢失精度,53之内可以直接由js读取,超过53位就需要转换成字符串才能保证js处理正确。53存储的话,32位存储秒级时间戳,5位存储机器码,16位存储序列化,这样每台机器每秒可以生产65536个不重复的id。

2、缺点

由于雪花算法严重依赖时间,所以当发生服务器时钟回拨的问题是会导致可能产生重复的id。当然几乎没有公司会修改服务器时间,修改以后会导致各种问题,公司宁愿新加一台服务器也不愿意修改服务器时间,但是不排除特殊情况。
如何解决时钟回拨的问题?可以对序列化的初始值设置步长,每次触发时钟回拨事件,则其初始步长就加1w,可以在下面代码的第85行来实现,将sequence的初始值设置为10000。

3、Java实现
package com.yl.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===========================================
    /** 开始时间截 (2020-01-01) */
    private final long twepoch = 1577808000000L;
 
    /** 机器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位的ID
        return ((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) {
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
        
        for (int i = 0; i < 100; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
    }
}

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

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

相关文章

10.抽象工厂模式

江湖上再也没人找林家的麻烦了。因为林平之一怒之下将辟邪剑谱公诸天下。一下子印出去几万份&#xff0c;江湖上人人都能轻而易举的得到这本无尚武学&#xff1b;然而江湖人士却陷入深深的矛盾之中&#xff1a; 不练&#xff0c;别人练了&#xff0c;分分钟秒杀你&#xff1b;练…

Qt QSlider滑动条控件

文章目录 1 属性和方法1.1 值1.2 方向1.3 步长1.4 信号和槽 2 实例2.1 布局2.2 代码实现 QSlider是滑动条控件&#xff0c;滑动条可以在一个范围内拖动&#xff0c;并将其位置转换为整数 最常见的应用就是视频播放器中的进度条 1 属性和方法 QSlider继承自QAbstractSlider&…

<软考高项备考>《论文专题 - 66 质量管理(5) 》

4 收尾 4.1 收尾-经验教训 经验&#xff1a; 1、制订科学合理的质量管理&#xff0c;管理质量&#xff0c;质量控制计划很重要。 2、严格执行质量管理&#xff0c;管理质量和质量控制计划很重要。 3、质量管理的工具和技术不宜采用过多&#xff0c;适用和够用就好&#xff0c…

Simulink|电力系统风储联合一次调频仿真模型

主要内容 风电高渗透率下&#xff0c;电力系统对风电场频率调节能力提出了技术要求。考虑风机惯性控制和变桨距控制的频率响应能力&#xff0c;提出将储能与风电自身调频手段相结合&#xff0c;参与系统频率调节。模型采用Simulink进行仿真&#xff0c;建立了风储联合调频下…

Spring Cloud整体架构解析

Spring Cloud整体架构 本文已收录至我的个人网站&#xff1a;程序员波特&#xff0c;主要记录Java相关技术系列教程&#xff0c;共享电子书、Java学习路线、视频教程、简历模板和面试题等学习资源&#xff0c;让想要学习的你&#xff0c;不再迷茫。 Spring Cloud的中文名我们就…

Tiktok/抖音旋转验证码识别

一、引言 在数字世界的飞速发展中&#xff0c;安全防护成为了一个不容忽视的课题。Tiktok/抖音&#xff0c;作为全球最大的短视频平台之一&#xff0c;每天都有数以亿计的用户活跃在其平台上。为了保护用户的账号安全&#xff0c;Tiktok/抖音引入了一种名为“旋转验证码”的安…

《Git学习笔记:IDEA整合Git》

在IDEA中集成Git去使用 通过Git命令可以完成Git相关操作&#xff0c;为了简化操作过程&#xff0c;我们可以在IDEA中配置Git&#xff0c;配置好后就可以在IDEA中通过图形化的方式来操作Git。 在IDEA开发工具中可以集成Git&#xff1a; 集成后在IDEA中可以看到Git相关图标&…

WeNet2.0:提高端到端ASR的生产力

摘要 最近&#xff0c;我们提供了 WeNet [1]&#xff0c;这是一个面向生产&#xff08;工业生产环境需求&#xff09;的端到端语音识别工具包&#xff0c;在单个模型中&#xff0c;它引入了统一的两次two-pass (U2) 框架和内置运行时&#xff08;built-in runtime&#xff09;…

WPF 布局

了解 WPF中所有布局如下&#xff0c;我们一一尝试实现&#xff0c;本文档主要以图形化的形式展示每个布局的功能。 布局&#xff1a; Border、 BulletDecorator、 Canvas、 DockPanel、 Expander、 Grid、 GridView、 GridSplitter、 GroupBox、 Panel、 ResizeGrip、 Separat…

怎样制作一本旅游电子相册呢?

​随着数码技术的发展&#xff0c;旅游电子相册已成为越来越多旅游爱好者的必备工具。它不仅能让您随时随地欣赏自己的旅行回忆&#xff0c;还能分享给亲朋好友&#xff0c;甚至上传到社交媒体上&#xff0c;让更多人了解您的旅行故事。那么&#xff0c;如何制作一本精美的旅游…

HarmonyOS4.0系列——05、状态管理之@Prop、@Link、@Provide、@Consume,以及@Watch装饰器

状态管理 看下面这张图 Components部分的装饰器为组件级别的状态管理&#xff0c;Application部分为应用的状态管理。开发者可以通过StorageLink/LocalStorageLink 实现应用和组件状态的双向同步&#xff0c;通过StorageProp/LocalStorageProp 实现应用和组件状态的单向同步。…

智能路由器中的 dns.he.net可使用自定义域名的免费 DDNS 服务配置方法

今天介绍的这个是可以使用自定义域名同时支持使用二级域名的免费DDNS服务 dns.he.net的动态DDNS服务的配置方法, 这个服务相对还是比较稳定的, 其配置也和其他的DDNS服务有些不太一样, 首先他的主机名: 这里需要设置为登录后分配的区域域名: ipv6.he.net 然后就是 DDNS 用户…

cad二次开发autolisp(一)

文章目录 一、概述1.1 简介1.2 打开编辑器1.3 调试页面 二、数据类型三、函数3.1 用户函数 四、语句4.1 常规语句4.2 流程控制语句 五、图元操作5.1 定义5.2 图元选择5.3 图元属性列表 一、概述 1.1 简介 简介&#xff1a;cad 二次开发语言&#xff0c;后缀名*.lsp适用于编写…

tda7294引脚功能和电压_三款tda7294应用电路

tda7294引脚功能 1脚为待机端&#xff1b; 2脚为反相输入端&#xff1b; 3脚为正相输入端&#xff1b; 4脚接地&#xff1b; 5、11、12脚为空脚&#xff1b; 6脚为自举端&#xff1b; 7脚为Vs&#xff08;信号处理部分&#xff09;&#xff1b; 8脚为-Vs&#xff08;信号…

分布式计算平台 Hadoop 简介

Hadoop简介 Hadoop是一种分析和处理大数据的软件平台&#xff0c;是一个用Java语言实现的Apache的开源软件框架&#xff0c;在大量计算机组成的集群中实现了对海量数据的分布式计算。其主要采用MapReduce分布式计算框架&#xff0c;包括根据GFS原理开发的分布式文件系统HDFS、…

解锁 JavaScript 数组的强大功能:常用方法和属性详解(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

使用Qt连接scrcpy-server控制手机

Qt连接scrcpy-server 测试环境如何启动scrcpy-server1. 连接设备2. 推送scrcpy-server到手机上3. 建立Adb隧道连接4. 启动服务5. 关闭服务 使用QTcpServer与scrcpy-server建立连接建立连接并视频推流完整流程1. 开启视频推流过程2. 关闭视频推流过程 视频流的解码1. 数据包协议…

C++进阶--AVL树

AVL树 一、AVL树的概念二、AVL树节点的定义三、AVL树的插入四、AVL树的旋转4.1 左单旋4.2 右单旋4.3 左右双旋4.4 右左双旋 五、AVL树的验证六、AVL树的删除七、AVL树的性能七、完整代码7.1 AVLTree.h 一、AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有…

命令行登录Mysql的详细讲解

目录 前言1. 本地登录2. 远程登录3. 拓展 前言 对于命令行登录Mysql一般都是用mysql -u root -p 但对于如何远程登陆&#xff0c;一直其他的参数还是有些盲区&#xff0c;对此总结科普 对于登录过程中出现的问题&#xff0c;可看我之前的文章&#xff1a; 服务器 出现ERROR …

CTF伪随机数爆破

要了解伪随机数的爆破首先你的先知道什么是PHP种子&#xff0c; 借用在rand()函数中&#xff0c;我们可以通过设置随机数种子来影响随机数的生成。例如&#xff0c;在rand()函数中加入了随机数种子编码后&#xff0c;每次运行程序将会生成同样的随机整数序列。这个就是伪随机数…