全栈杂谈第四期 什么是雪花算法

news2024/10/13 21:55:11

引言

ID就像身份证号,每个人都有对应的且唯一身份证号码,在日常工作生活中,我们也时常需要给物品或某一事件一个唯一的标识来区分它们之间谁是谁。尤其是在当今的分布式系统中,生成唯一ID是一项至关重要的任务。无论是数据库中的主键,还是分布式环境中的任务ID,都需要确保ID的唯一性、时序性和高效性。传统的自增ID虽然简单,但在分布式环境下却无法保证全局唯一性。因此,诸如Twitter提出的雪花算法(Snowflake)成为了解决这一问题的重要方案。

本文将介绍雪花算法的原理和优势,探讨为什么在生成唯一ID时,雪花算法或基于它的变种算法被广泛应用。

基本概念

什么是雪花算法

雪花算法由Twitter在2010年提出,旨在为其分布式系统生成全球唯一的ID。雪花算法生成的ID是一个64位的长整数(Long类型),其组成如下:

  1. 符号位(1位):始终为0,因为生成的ID都是正数。
  2. 时间戳(41位):表示从某个自定义的起始时间(Epoch)到当前时间的毫秒数。41位能够支持长达约69年的时间范围。
  3. 数据中心标识(5位):用于标识数据中心,可以支持最多32个数据中心。
  4. 机器标识(5位):用于标识同一个数据中心内的机器,最多支持32台机器。
  5. 序列号(12位):在同一毫秒内生成的序列号,最多可以生成4096个不同的ID。

这样,通过组合这些不同的部分,雪花算法能够在分布式环境中生成唯一且有序的ID。

特点

雪花算法的设计具有以下几个重要特点:

分布式高效生成:由于每个节点都可以根据自身的时间戳和序列号生成ID,避免了分布式锁的开销。

在大规模分布式系统中,ID的生成速度直接影响系统的性能。自增ID由于需要集中管理,很难在多节点环境中高效生成。而雪花算法依赖于本地时钟和序列号生成ID,无需与中心节点通信,因此在每台机器上都可以独立生成ID,效率极高。

全局唯一性:通过不同的数据中心标识和机器标识,确保在不同节点生成的ID不冲突。

分布式系统最重要的需求之一就是全局唯一性。传统的ID生成方法,如UUID(通用唯一标识符),虽然可以保证唯一性,但生成的ID往往很长且无序。雪花算法通过时间戳、数据中心和机器标识的组合,不仅能够保证唯一性,还能生成较短的、有序的ID,便于数据库的存储和检索。

趋势递增:由于ID的主要部分是时间戳,生成的ID大致呈递增趋势,有助于数据库索引优化。

雪花算法生成的ID具有趋势递增的特点,这对于数据库索引的优化具有极大优势。传统的UUID由于是随机生成的,无法确保有序性,容易导致数据库索引碎片化,降低查询效率。而雪花算法的ID因基于时间戳生成,通常是递增的,有助于数据库索引的性能优化。

可扩展性:雪花算法允许通过增加标识位数或调整部分结构,扩展到更多的场景和需求。

雪花算法的灵活性使其在应对不同的业务场景时可以轻松调整。通过增加或调整标识位,可以支持更多的数据中心或机器节点。此外,在一些实现中,还可以通过缩短时间戳部分来延长序列号的位数,以支持每毫秒更多的ID生成需求。

应用场景

雪花算法由于其高效性、全局唯一性和趋势递增的特点,广泛应用于各类分布式系统中。以下是几个常见的应用场景:

分布式数据库

在分布式数据库中,数据分布在多个节点上,因此生成唯一的主键ID至关重要。雪花算法能够在各个节点独立生成唯一ID,避免了集中生成ID的瓶颈和复杂性。

消息队列系统

消息队列系统中的每条消息都需要有唯一的ID来进行追踪和处理。雪花算法可以确保在高并发环境下,生成的消息ID是唯一且有序的,便于后续的消息处理。

微服务架构

在微服务架构中,多个服务之间经常需要共享或传递ID来标识请求和事务。雪花算法的ID生成方式能够确保不同服务生成的ID不会冲突,便于跨服务的协同工作。

订单系统

订单系统需要为每个订单生成唯一的订单号,尤其在电商平台上,订单量巨大且并发高。雪花算法的高效性和全局唯一性,能够很好地满足订单号生成的需求。

雪花算法的变种

随着业务需求的不断发展,雪花算法的基本结构也被许多公司进行了改进和优化,衍生出一些变种算法。

百度的UidGenerator

百度的UidGenerator是基于雪花算法的优化版本。其主要改进点在于:

  • 扩大了机器ID的范围,以支持更多的节点。
  • 对时间戳进行了预处理,防止时钟回拨带来的ID冲突问题。

美团的Leaf

美团的Leaf是一种支持分布式系统生成唯一ID的解决方案,其提供了两种ID生成方式:基于数据库自增的号段模式和基于雪花算法的号段模式。Leaf在高并发场景下表现出色,且通过数据库模式解决了时间回拨等问题。

后面有机会我们将详细向大家介绍这两种雪花算法变种的优势在哪

为什么一定要使用雪花算法

分布式环境的必然选择

在分布式系统中,唯一ID的生成涉及多个节点和服务的协调,传统的集中式ID生成方法往往无法满足大规模系统的需求。雪花算法无需中心化的ID生成服务,极大提升了系统的扩展性和容错性。

高效和可靠性并存

雪花算法不仅可以高效生成ID,还能确保生成的ID具有全局唯一性、时序性和分布式环境下的稳定性。而这些特点正是分布式系统中不可或缺的。

优秀的兼容性

雪花算法的简单设计使得它可以轻松与各类技术栈和业务场景集成。无论是消息队列、订单系统,还是数据库主键生成,雪花算法都能发挥出色的作用。

代码示例

接下来我们来看看手搓一个简单的雪花算法要怎么写出关键代码

JAVA版本:

public class SnowflakeIdGenerator {

    // 起始的时间戳(这里定义为2023-01-01)
    private final long twepoch = 1672531200000L;
    
    // 机器ID所占的位数(5位)
    private final long workerIdBits = 5L;
    // 数据中心ID所占的位数(5位)
    private final long datacenterIdBits = 5L;
    // 支持的最大机器ID
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 支持的最大数据中心ID
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    // 序列在ID中占的位数(12位)
    private final long sequenceBits = 12L;
    
    // 机器ID左移12位
    private final long workerIdShift = sequenceBits;
    // 数据中心ID左移17位(12 + 5)
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    // 时间戳左移22位(12 + 5 + 5)
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    
    // 序列号最大值(4095)
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    
    // 机器ID
    private long workerId;
    // 数据中心ID
    private long datacenterId;
    // 序列号
    private long sequence = 0L;
    // 上次生成ID的时间戳
    private long lastTimestamp = -1L;
    
    // 构造函数,传入机器ID和数据中心ID
    public SnowflakeIdGenerator(long workerId, long datacenterId) {
        // 校验workerId是否在合理范围
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("机器ID不能大于最大ID%d和小于0", maxWorkerId));
        }
        // 校验datacenterId是否在合理范围
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("数据中心ID不能大于最大ID%d和小于0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }
    
    // 生成唯一ID的核心方法
    public synchronized long nextId() {
        // 获取当前时间戳(毫秒)
        long timestamp = System.currentTimeMillis();
    
        // 如果当前时间小于上一次生成ID的时间,抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时间回拨 拒绝生成ID ,需等待 " + (lastTimestamp - timestamp) + " 毫秒");
        }
    
        // 如果在同一毫秒内生成ID,则增加序列号
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 如果序列号达到最大值(4096),等待下一毫秒
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 如果是新的毫秒,序列号重置为0
            sequence = 0L;
        }
    
        // 更新上次生成ID的时间戳
        lastTimestamp = timestamp;
    
        // 生成ID并返回,ID由时间戳、数据中心ID、机器ID和序列号组成
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }
    
    // 等待直到下一毫秒
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
    
    public static void main(String[] args) {
        // 创建一个雪花算法生成器,传入机器ID和数据中心ID
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
        // 生成10个ID并输出
        for (int i = 0; i < 10; i++) {
            // 输出生成的ID
            System.out.println("生成的唯一ID: " + idGenerator.nextId());
        }
    }

}

Python版本:

import time

class SnowflakeIdGenerator:
    def __init__(self, worker_id, datacenter_id, sequence=0):
        # 起始的时间戳(2023-01-01)
        self.twepoch = 1672531200000

        # 机器ID和数据中心ID的位数
        self.worker_id_bits = 5
        self.datacenter_id_bits = 5
        self.sequence_bits = 12
    
        # 计算最大值(机器ID和数据中心ID)
        self.max_worker_id = -1 ^ (-1 << self.worker_id_bits)
        self.max_datacenter_id = -1 ^ (-1 << self.datacenter_id_bits)
    
        # 位移偏移量,用于生成ID
        self.worker_id_shift = self.sequence_bits
        self.datacenter_id_shift = self.sequence_bits + self.worker_id_bits
        self.timestamp_left_shift = self.sequence_bits + self.worker_id_bits + self.datacenter_id_bits
    
        # 序列号掩码(12位:0b111111111111)
        self.sequence_mask = -1 ^ (-1 << self.sequence_bits)
    
        # 校验worker_id和datacenter_id是否在合理范围内
        if worker_id > self.max_worker_id or worker_id < 0:
            raise ValueError(f"worker_id 不能大于 {self.max_worker_id} 或小于 0")
        if datacenter_id > self.max_datacenter_id or datacenter_id < 0:
            raise ValueError(f"datacenter_id 不能大于 {self.max_datacenter_id} 或小于 0")
    
        self.worker_id = worker_id
        self.datacenter_id = datacenter_id
        self.sequence = sequence
        self.last_timestamp = -1
    
    # 获取当前时间戳(以毫秒为单位)
    def _current_timestamp(self):
        return int(time.time() * 1000)
    
    # 等待下一毫秒
    def _til_next_millis(self, last_timestamp):
        timestamp = self._current_timestamp()
        while timestamp <= last_timestamp:
            timestamp = self._current_timestamp()
        return timestamp
    
    # 生成唯一ID的核心方法
    def next_id(self):
        timestamp = self._current_timestamp()
    
        # 如果当前时间戳小于上一次生成ID的时间,抛出异常
        if timestamp < self.last_timestamp:
            raise Exception(f"系统时钟回拨,拒绝生成ID,需等待 {self.last_timestamp - timestamp} 毫秒")
    
        # 在同一毫秒内,增加序列号
        if self.last_timestamp == timestamp:
            self.sequence = (self.sequence + 1) & self.sequence_mask
            # 如果序列号用完,则等待下一毫秒
            if self.sequence == 0:
                timestamp = self._til_next_millis(self.last_timestamp)
        else:
            # 新的一毫秒,序列号重置为0
            self.sequence = 0
    
        # 更新上一次生成ID的时间戳
        self.last_timestamp = timestamp
    
        # 返回生成的唯一ID,基于时间戳、数据中心ID、机器ID和序列号
        return ((timestamp - self.twepoch) << self.timestamp_left_shift) | (self.datacenter_id << self.datacenter_id_shift) | (self.worker_id << self.worker_id_shift) | self.sequence

if __name__ == "__main__":
    # 创建一个雪花算法生成器,传入机器ID和数据中心ID
    id_generator = SnowflakeIdGenerator(1, 1)
    # 生成10个ID并输出
    for _ in range(10):
        # 输出生成的唯一ID
        print("生成的唯一ID:", id_generator.next_id())
# 创建一个雪花算法生成器,传入机器ID和数据中心ID

​ id_generator = SnowflakeIdGenerator(1, 1)
​ # 生成10个ID并输出
​ for _ in range(10):
​ # 输出生成的唯一ID
​ print(“生成的唯一ID:”, id_generator.next_id())

总结

雪花算法通过时间戳、数据中心标识、机器标识和序列号的组合,能够在分布式环境中高效生成全局唯一且有序的ID。它的高效性、趋势递增性和可扩展性,使其成为分布式系统中生成唯一ID的首选方案。随着业务需求的不断变化,雪花算法及其衍生版本在各种应用场景中得到了广泛应用,并不断优化改进。

欢迎关注公众:“全栈开发指南针”
这里是技术潮流的风向标,也是你代码旅程的导航仪!🚀
Let’s code and have fun! 🎉

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

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

相关文章

C0013.Clion中利用C++调用opencv打开摄像头

下载opencv https://opencv.org/get-started/ 直接官网下载opencv-4.9.0-windows.exe 安装opencv opencv配置环境变量 如上安装配置完成。

SpringBoot框架下的健康信息管理解决方案

第1章 绪论 1.1背景及意义 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们生活水平的不断提高&#xff0c;日常生活中人们对医院管理方面的要求也在不断提高&#xff0c;由于老龄化人数更是不断增加&#xff0c;使得师生健康信息管理系统的开发成为必需而且紧…

第三批安全可靠评测名单公布,几家欢喜几家忧

9月30号&#xff0c;赶在国庆长假之前&#xff0c;中国信息安全评测中心发布了《安全可靠评测结果公告(2024年第2号)》&#xff0c;测试结果自发布之日起有效期三年。 本期测试分为集中式数据库、分布式数据库和中央处理器三个大类&#xff0c;结果共有14家公司的16个产品入围&…

AI绘画实现数字人2D形象生成及3D数字人视频生成

概述 随着人工智能技术的不断进步&#xff0c;AI绘画已经成为数字艺术创作领域的重要工具。本章将详细介绍如何利用AI绘画技术生成数字人的2D形象&#xff0c;并进一步将其转化为3D数字人视频。通过一系列实践步骤和Python代码示例&#xff0c;您将能够掌握从平台使用到系统部…

计算机毕业设计之:音乐媒体播放及周边产品运营平台(源码+文档+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

看Threejs好玩示例,学习创新与技术(Noise)

给图像加一点噪声效果&#xff0c;可以起到朦胧背景的效果&#xff0c;比如下面这幅画。 除了普通的图片外&#xff0c;我们可以把这个效果应用到地图或其他方面&#xff0c;比如超过范围不允许用户了解更详细的内容。当然&#xff0c;也可以采用雾Fog效果&#xff0c;但后处理…

鸿蒙ArkUI实战开发-主打自研语言及框架

ArkUI 是 HarmonyOS 的声明式 UI 开发框架&#xff0c;而 ArkUI-X 是基于 ArkUI 框架扩展而来的跨平台开发框架。ArkUI-X 支持 HarmonyOS、OpenHarmony、Android 和 iOS 平台&#xff0c;允许开发者使用一套代码构建支持多平台的应用程序。 一、ArkUI-X 的实战开发步骤 在实战开…

(c++)在堆区创建一个数组并且访问与释放

在堆区创建一个数组&#xff0c;然后利用一个指针指向这个数组的首地址&#xff0c;通过这个指针来访问这个数组。 代码展示了三种赋值的方式&#xff1a; 1.直接利用数组访问赋值 2.利用循环结构&#xff08;和1原理一样&#xff09; 3.循环结构键盘输入赋值 然后输出这个…

Ray_Tracing_In_One_Weekend上

目标&#xff1a; 使用vscodeIDE编写代码&#xff0c;这是我的配置 学习这个教程&#xff0c;完成一个简易的光线追踪器开发 1输出PPM图像 在不使用 opengl &#xff08;渲染图像&#xff09;/ std_image.h&#xff08;加载图像&#xff09;等库的情况下&#xff0c;怎样通…

某信服, 一点底线都没有, 一点Face都不要

某些软件厂商, 仗着自己有点背景, 做出来的东西真的是流氓 !!! 铁子们, 这玩意儿怎么卸载呢?

CertiK《Hack3d:2024年第三季度安全报告》(附报告全文链接)

CertiK《Hack3d&#xff1a;2024年第三季度Web3.0安全报告》现已发布&#xff0c;本次报告深入分析了2024年7月至9月的链上安全状况&#xff0c;本季度总损失金额为7.53亿美元&#xff0c;网络钓鱼和私钥泄露是本季度造成资产损失的主要原因。 ​ 关键数据 2024年第三季度&a…

数电基础(脉冲波形的变化和发生+multisim)

1.脉冲波形的变化和发生 1.1单稳态电路 1.1.1逻辑门组成的单稳态电路 基本概念 &#xff08;1&#xff09;单稳态电路&#xff08;monostable multivibrator又称one-shot&#xff09;常用于脉冲的变换&#xff0c;延时和定时 电路的输出有稳态和暂稳态两个不同的工作状态 …

java常用框架结构

1. Spring框架 特色&#xff1a;Spring框架就像是一个万能工具箱&#xff0c;提供了丰富的功能来满足开发者的各种需求。它支持面向切面编程&#xff08;AOP&#xff09;、依赖注入&#xff08;DI&#xff09;等特性&#xff0c;使得代码更加模块化和可维护。Spring还提供了对数…

【web安全】——XXE漏洞

1.XML基础 1.1.XML简介 XML被称为可扩展标记语言&#xff0c;与HTML类似&#xff0c;但是HTML中的标签都是预定义(预先定义好每个标签的作用)的&#xff0c;而XML语言中的标签都是自定义(可以自己定义标签的名称、属性、值、作用)的;HTML中的标签可以是单标签&#xff0c;而X…

洛谷 P11045 [蓝桥杯 2024 省 Java B] 最优分组

[Problem Discription] \color{blue}{\texttt{[Problem Discription]}} [Problem Discription] [Analysis] \color{blue}{\texttt{[Analysis]}} [Analysis] 首先得注意这么一点&#xff1a; k k k 必须得是 n n n 的因数&#xff08;这里的 n , k n,k n,k 对应于题目的 N ,…

【若依】postman调试出现认证失败,无法访问系统资源

如果前后端都已经连接通了&#xff0c;但是调试出现错误代码&#xff0c;可能是因为没有授权的问题&#xff0c;需要获得授权。 授权内容在cookie中 把cookie中的token内容粘贴到postman里面 这个时候再在postman里测试接口&#xff0c;发现可以拿到数据了

【C++】“list”的介绍和常用接口的模拟实现

【C】“list”的介绍和常用接口的模拟实现 一. list的介绍1. list常见的重要接口2. list的迭代器失效 二. list常用接口的模拟实现&#xff08;含注释&#xff09;三. list与vector的对比 一. list的介绍 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xf…

操作符详解与表达式求值

目录 操作符分类 1.算数操作符 2.移位操作符&#xff08;只适用于整数范围&#xff09; &#xff08;1&#xff09;引入 &#xff08;2&#xff09;左移操作符<< &#xff08;2&#xff09;右移操作符>> 3.位操作符 4.赋值操作符 复合赋值符 5.单目操作符 5…

SQL:函数以及约束

目录 介绍 函数 字符串函数 数值函数 日期函数 流程函数 约束 总结 介绍 说到函数我们都不陌生,在C,C,java等语言中都有库函数,我们在平时也是经常使用,函数就是一段代码,我们既可以自定义实现,又可以使用库里内置的函数;从来更加简洁方便的完成业务;同样的在SQL中也有…

vscode qt 最新开发环境配置, 基于最新插件 Qt All Extensions Pack

qt 之前发布了vscode qt offical ,但是最新更新中将其升级改为了几个不同的插件&#xff0c;功能更强大 1. 前置条件 qt 已安装 2. 插件安装 打开vscode 插件安装&#xff0c;搜索qt 会看到很多qt插件&#xff0c;直接选择Qt All Extensions Pack 安装 会安装qt环境所需的…