爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      面试官:讲讲雪花算法,越详细越好

      首页 知识中心 大数据 文章详情页

      面试官:讲讲雪花算法,越详细越好

      2024-04-26 08:03:54 阅读次数:42

      时间戳

      前面文章在谈论分布式唯一ID生成的时候,有提到雪花算法,这一次,我们详细点讲解,只讲它。

      SnowFlake算法

      据国家大气研究中心的查尔斯·奈特称,一般的雪花大约由10^19个水分子组成。在雪花形成过程中,会形成不同的结构分支,所以说大自然中不存在两片完全一样的雪花,每一片雪花都拥有自己漂亮独特的形状。雪花算法表示生成的id如雪花般独一无二。

      snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。

      核心思想:分布式,唯一。

      算法具体介绍

      雪花算法是 64 位 的二进制,一共包含了四部分:

      • 1位是符号位,也就是最高位,始终是0,没有任何意义,因为要是唯一计算机二进制补码中就是负数,0才是正数。
      • 41位是时间戳,具体到毫秒,41位的二进制可以使用69年,因为时间理论上永恒递增,所以根据这个排序是可以的。
      • 10位是机器标识,可以全部用作机器ID,也可以用来标识机房ID + 机器ID,10位最多可以表示1024台机器。
      • 12位是计数序列号,也就是同一台机器上同一时间,理论上还可以同时生成不同的ID,12位的序列号能够区分出4096个ID。

      面试官:讲讲雪花算法,越详细越好

      优化

      由于41位是时间戳,我们的时间计算是从1970年开始的,只能使用69年,为了不浪费,其实我们可以用时间的相对值,也就是以项目开始的时间为基准时间,往后可以使用69年。获取唯一ID的服务,对处理速度要求比较高,所以我们全部使用位运算以及位移操作,获取当前时间可以使用System.currentTimeMillis()。

      时间回拨问题

      在获取时间的时候,可能会出现时间回拨的问题,什么是时间回拨问题呢?就是服务器上的时间突然倒退到之前的时间。

      1. 人为原因,把系统环境的时间改了。
      2. 有时候不同的机器上需要同步时间,可能不同机器之间存在误差,那么可能会出现时间回拨问题。

      解决方案

      1. 回拨时间小的时候,不生成 ID,循环等待到时间点到达。
      2. 上面的方案只适合时钟回拨较小的,如果间隔过大,阻塞等待,肯定是不可取的,因此要么超过一定大小的回拨直接报错,拒绝服务,或者有一种方案是利用拓展位,回拨之后在拓展位上加1就可以了,这样ID依然可以保持唯一。但是这个要求我们提前预留出位数,要么从机器id中,要么从序列号中,腾出一定的位,在时间回拨的时候,这个位置 +1。

      由于时间回拨导致的生产重复的ID的问题,其实百度和美团都有自己的解决方案了,有兴趣可以去看看,下面不是它们官网文档的信息:

      • 百度UIDGenerator:https:///baidu/uid-generator/blob/master/README.zh_cn.md
        • UidGenerator是Java实现的, 基于Snowflake算法的唯一ID生成器。UidGenerator以组件形式工作在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。 在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。
      • 美团Leaf:https:///2019/03/07/open-source-project-leaf.html
        • leaf-segment 方案
          • 优化:双buffer + 预分配
          • 容灾:Mysql DB 一主两从,异地机房,半同步方式
          • 缺点:如果用segment号段式方案:id是递增,可计算的,不适用于订单ID生成场景,比如竞对在两天中午12点分别下单,通过订单id号相减就能大致计算出公司一天的订单量,这个是不能忍受的。
        • leaf-snowflake方案
          • 使用Zookeeper持久顺序节点的特性自动对snowflake节点配置workerID
            • 1.启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点)。
            • 2.如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务。
            • 3.如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务。
          • 缓存workerID,减少第三方组件的依赖
          • 由于强依赖时钟,对时间的要求比较敏感,在机器工作时NTP同步也会造成秒级别的回退,建议可以直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE,等时钟追上即可。或者做一层重试,然后上报报警系统,更或者是发现有时钟回拨之后自动摘除本身节点并报警

      代码展示

      public class SnowFlake {
      
          // 数据中心(机房) id
          private long datacenterId;
          // 机器ID
          private long workerId;
          // 同一时间的序列
          private long sequence;
      
          public SnowFlake(long workerId, long datacenterId) {
              this(workerId, datacenterId, 0);
          }
      
          public SnowFlake(long workerId, long datacenterId, long sequence) {
              // 合法判断
              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));
              }
              System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
                      timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);
      
              this.workerId = workerId;
              this.datacenterId = datacenterId;
              this.sequence = sequence;
          }
      
          // 开始时间戳(2021-10-16 22:03:32)
          private long twepoch = 1634393012000L;
      
          // 机房号,的ID所占的位数 5个bit 最大:11111(2进制)--> 31(10进制)
          private long datacenterIdBits = 5L;
      
          // 机器ID所占的位数 5个bit 最大:11111(2进制)--> 31(10进制)
          private long workerIdBits = 5L;
      
          // 5 bit最多只能有31个数字,就是说机器id最多只能是32以内
          private long maxWorkerId = -1L ^ (-1L << workerIdBits);
      
          // 5 bit最多只能有31个数字,机房id最多只能是32以内
          private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
      
          // 同一时间的序列所占的位数 12个bit 111111111111 = 4095  最多就是同一毫秒生成4096个
          private long sequenceBits = 12L;
      
          // workerId的偏移量
          private long workerIdShift = sequenceBits;
      
          // datacenterId的偏移量
          private long datacenterIdShift = sequenceBits + workerIdBits;
      
          // timestampLeft的偏移量
          private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
      
          // 序列号掩码 4095 (0b111111111111=0xfff=4095)
          // 用于序号的与运算,保证序号最大值在0-4095之间
          private long sequenceMask = -1L ^ (-1L << sequenceBits);
      
          // 最近一次时间戳
          private long lastTimestamp = -1L;
      
      
          // 获取机器ID
          public long getWorkerId() {
              return workerId;
          }
      
      
          // 获取机房ID
          public long getDatacenterId() {
              return datacenterId;
          }
      
      
          // 获取最新一次获取的时间戳
          public long getLastTimestamp() {
              return lastTimestamp;
          }
      
      
          // 获取下一个随机的ID
          public synchronized long nextId() {
              // 获取当前时间戳,单位毫秒
              long timestamp = timeGen();
      
              if (timestamp < lastTimestamp) {
                  System.err.printf("clock is moving backwards.  Rejecting requests until %d.", 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;
      
                  // sequence序列大于4095
                  if (sequence == 0) {
                      // 调用到下一个时间戳的方法
                      timestamp = tilNextMillis(lastTimestamp);
                  }
              } else {
                  // 如果是当前时间的第一次获取,那么就置为0
                  sequence = 0;
              }
      
              // 记录上一次的时间戳
              lastTimestamp = timestamp;
      
              // 偏移计算
              return ((timestamp - twepoch) << timestampLeftShift) |
                      (datacenterId << datacenterIdShift) |
                      (workerId << workerIdShift) |
                      sequence;
          }
      
          private long tilNextMillis(long lastTimestamp) {
              // 获取最新时间戳
              long timestamp = timeGen();
              // 如果发现最新的时间戳小于或者等于序列号已经超4095的那个时间戳
              while (timestamp <= lastTimestamp) {
                  // 不符合则继续
                  timestamp = timeGen();
              }
              return timestamp;
          }
      
          private long timeGen() {
              return System.currentTimeMillis();
          }
      
          public static void main(String[] args) {
              SnowFlake worker = new SnowFlake(1, 1);
              long timer = System.currentTimeMillis();
              for (int i = 0; i < 10000; i++) {
                  worker.nextId();
              }
              System.out.println(System.currentTimeMillis());
              System.out.println(System.currentTimeMillis() - timer);
          }
      
      }
      
      
      

      问题分析

      1. 第一位为什么不使用?

      在计算机的表示中,第一位是符号位,0表示整数,第一位如果是1则表示负数,我们用的ID默认就是正数,所以默认就是0,那么这一位默认就没有意义。

      2.机器位怎么用?

      机器位或者机房位,一共10 bit,如果全部表示机器,那么可以表示1024台机器,如果拆分,5 bit 表示机房,5bit表示机房里面的机器,那么可以有32个机房,每个机房可以用32台机器。

      3. twepoch表示什么?

      由于时间戳只能用69年,我们的计时又是从1970年开始的,所以这个twepoch表示从项目开始的时间,用生成ID的时间减去twepoch作为时间戳,可以使用更久。

      4. -1L ^ (-1L << x) 表示什么?

      表示 x 位二进制可以表示多少个数值,假设x为3:

      在计算机中,第一位是符号位,负数的反码是除了符号位,1变0,0变1, 而补码则是反码+1:

      -1L 原码:1000 0001
      -1L 反码:1111 1110
      -1L 补码:1111 1111
      

      从上面的结果可以知道,-1L其实在二进制里面其实就是全部为1,那么 -1L 左移动 3位,其实得到 1111 1000,也就是最后3位是0,再与-1L异或计算之后,其实得到的,就是后面3位全是1。-1L ^ (-1L << x)表示的其实就是x位全是1的值,也就是x位的二进制能表示的最大数值。

      5.时间戳比较

      在获取时间戳小于上一次获取的时间戳的时候,不能生成ID,而是继续循环,直到生成可用的ID,这里没有使用拓展位防止时钟回拨。

      6.前端直接使用发生精度丢失

      如果前端直接使用服务端生成的long 类型 id,会发生精度丢失的问题,因为 JS 中Number是16位的(指的是十进制的数字),而雪花算法计算出来最长的数字是19位的,这个时候需要用 String 作为中间转换,输出到前端即可。

      雪花算法其实是依赖于时间的一致性的,如果时间回拨,就可能有问题,一般使用拓展位解决。而只能使用69年这个时间限制,其实可以根据自己的需要,把时间戳的位数设置得更多一点,比如42位可以用139年,但是很多公司首先得活下来。当然雪花算法也不是银弹,它也有缺点,在单机上递增,而多台机器只是大致递增趋势,并不是严格递增的。

      没有最好的设计方案,只有合适和不合适的方案。

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://blog.51cto.com/u_13604316/4596709,作者:秦怀杂货店,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:Powershell数据类型介绍-02

      下一篇:YOLO格式数据集和VOC格式数据集互转(txt&xml)

      相关文章

      2025-04-07 10:20:39

      Python是如何表示时间的?2个模块、3种方式,1文搞定~

      Python是如何表示时间的?2个模块、3种方式,1文搞定~

      2025-04-07 10:20:39
      python , 时间戳 , 结构化
      2025-04-07 10:20:39

      ​Python是如何表示时间的?2个模块、3种方式,1文看懂~

      ​Python是如何表示时间的?2个模块、3种方式,1文看懂~

      2025-04-07 10:20:39
      python , 开发语言 , 时间戳 , 结构化
      2024-12-16 08:31:32

      java之序列化与反序列化的详细解析(全)

      序列化(Serialize):内存当中的java对象放到硬盘文件中,java对象存储到文件中,将java对象的状态保存下来的过程,需要使用ObjectOutputStream类

      2024-12-16 08:31:32
      java , 对象 , 序列化 , 接口
      2024-06-25 09:51:58

      JavaScript:Date将时间戳转换为时间对象

      JavaScript:Date将时间戳转换为时间对象

      2024-06-25 09:51:58
      JavaScript , 时间戳
      2024-03-22 08:13:30

      python3关于time时间的各种格式

      python3关于time时间的各种格式

      2024-03-22 08:13:30
      python , 时间戳
      2023-06-21 06:40:36

      Python 计算与伪造TCP序列号

      计算TCP序列号: 通过发送TCP SYN数据包来从依次收到的SYN/ACK包中计算TCP序列号之差,查看是否存在可被猜测的规律。 伪造TCP连接: 添加伪造主要过程为先对远程服务器进行SYN洪泛攻击、使之拒绝服务,然后猜测TCP序列号并

      2023-06-21 06:40:36
      Python , TCP
      2023-04-19 09:37:55

      时间戳转datetime格式

      时间戳转datetime格式

      2023-04-19 09:37:55
      datetime , 时间戳
      2023-03-10 10:15:15

      前端工作小结61-时间戳转换

      main.js

      2023-03-10 10:15:15
      时间戳 , 前端 , javascript
      2023-02-21 03:02:11

      Python编程:time时间模块

      时间的三种形式:时间戳(秒),元组形式,字符串形式 时间戳 timestamp1970年1月1日计时,unix诞生于1970年,”UNIX元年” 格林威治时间1970年01月01日00时00分00秒起至现在的 总秒数元组形式 struct

      2023-02-21 03:02:11
      时间戳 , 字符串 , 元组
      2023-02-15 10:02:05

      Python编程:time和datetime时间模块详解

      说明: 绿色线条:timestamp -> datetime对象路径 橙色线条:datetime对象 -> timestamp路径 灰色线条:time模块 与 datetime模块 分界过渡时间的四个存在方式时间戳,float元

      2023-02-15 10:02:05
      时间戳 , 字符串 , 元组
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5247530

      查看更多

      热门标签

      算法 leetcode python 数据 java 数组 节点 大数据 i++ 链表 golang c++ 排序 django 数据类型
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号