【分布式】分布式ID

目录

  • 前言
  • 一、雪花算法snowflake
    • 1. 组成
    • 2. 优缺点
    • 3. 时钟回拨怎么解决
      • a. 时钟回拨
      • b. 解决方案
    • 4. 项目中如何使用
  • 二、基于Redis
  • 三、基于Zookeeper
  • 四、号段模式
  • 五、指定步长的自增ID
  • 六、UUID
    • 参考
  • 六、扩展
  • 总结

前言

分布式场景下,一张表可能分散到多个数据结点上。因此需要一些分布式ID的解决方案。

分布式ID需要有几个特点:

  • 全局唯一(必要) :在多个库的主键放在一起也不会重复
  • 有序(必要) :避免频繁触发索引重建
  • 信息安全:ID连续,可以根据订单编号计算一天的单量,造成信息泄露
  • 包含时间戳:能够快速根据ID得知生成时间

下面几种方案按推荐顺序排序,越推荐使用越靠前。

一、雪花算法snowflake

64 位的 long 类型的唯一 id
在这里插入图片描述

1. 组成

1)1位不用

带符号整数第1位是符号位,正数是0,ID一般为正数,此位不用。

2)41位毫秒级时间戳

41位存储当前时间截 – 开始时间截得到的差值,可以表示 2 41 2^{41} 241个毫秒的值,转化成单位年则是: 2 41 1000 ∗ 60 ∗ 60 ∗ 24 ∗ 365 = 69 年 \frac{2^{41}}{1000∗60∗60∗24∗365}=69年 1000606024365241=69

注:开始时间截由程序指定,一般是id生成器开始使用的时间,设置好后避免更改。依赖服务器时间,服务器时钟回拨时可能会生成重复 id。

3)10位机器ID

生成ID的服务可以部署在1024台机器上

4)12位序列号
能够表示4096个序列号。

因此,某一毫秒,同一台机器,最多能生成4096个序号。理论上单机QPS最大为4096*1000=409.6w/s

2. 优缺点

优点:

  • ID不重复:用时间戳+机器+序号生成不重复ID
  • 性能高:在内存中生成
  • 有序

缺点:

依赖服务器时间,存在时钟回拨的问题。

3. 时钟回拨怎么解决

时钟回拨可能产生重复ID进而影响关联系统。

a. 时钟回拨

什么是时钟回拨: 服务器上的时间倒退回之前的时间

哪些情况造成时钟回拨:

  • 人为修改服务器时间
  • 时钟同步后,由于机器之间时间不同,可能产生时钟回拨

b. 解决方案

算法中会记录当前服务上次生成ID的最后时间,只需要保证我下次生成ID的时间大于上次最后时间即可。根据回拨后时间距离上次生成最后时间大小,可以有不同的解决方案。

  • 相差0~100ms :等待直至当前时间超过上次最后生活时间
  • 相差100ms~1s:采用等待方式可能导致接口超时。可以记录已生成ID的最大ID,在这个基础上++。(预留扩展位,在扩展位上增加。回拨后又回拨可能有问题)
  • 相差1s~5s:采用最大ID增加的方式,时间过长可能导致范围溢出。可以生成ID服务响应异常,由调用方例如基于Ribbon调用其他生成ID服务。
  • 相差超过5s:采用Ribbon循环调用的方式,下次访问到时钟回拨的服务可能还没达到上次生成最后时间,浪费时间。可以让超过5s的服务主动下线,并通知运维,人工介入,等待时钟正常后再重启。

4. 项目中如何使用

在这里插入图片描述

时钟回拨的处理逻辑在nextId()里的if (timestamp < lastTimestamp) 逻辑下。这里直接抛出异常。

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位的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) {SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);for (int i = 0; i < 100; i++) {long id = idWorker.nextId();System.out.println(id);}}
}

二、基于Redis

三、基于Zookeeper

四、号段模式

数据库中保存号段表,每次从数据库获取一个号段范围,由服务在内存中自增生成ID,直到达到号段范围再去获取。

id业务类型最大可用ID号段长度版本号
1xxx200010000
updateset 最大可用ID = 3000, version = version + 1 where version = 0 and biz_type = xxx

采用乐观锁的方式避免长时间锁表。

优点:

  • ID有序递增
  • 对数据库压力比较小

缺点:

  • 存在安全问题,用ID可以判断数据量
  • 存在单点问题,集群实现困难

五、指定步长的自增ID

多主集群模式下,表主键设置自增ID,多节点之间会有重复ID。需要采用指定初始值的自增步长
举例:两台数据库A,B。A初始值和步长为(1,2),B初始值和步长为(2,2)。两张表生成的主键分别为
A:1,3,5,7…
B:2,4,6,8…

设置方法:

set @@auto_increment_offset = 1;     -- 起始值
set @@auto_increment_increment = 2;  -- 步长

优点:

  • 简单、解决单点问题

缺点:

  • 扩容困难

六、UUID

128位长的字符标识串,由32个16进制数字组成,用-连接,共36个字符。如:03425604-5462-11ee-80ad-80fa5b8732b1

生成算法与重复性:

  • 基于随机数:不重复
  • 基于MAC地址:不重复
  • 基于时间戳:可能重复

作为分布式ID的优缺点:

  • 优点:本地生成,性能高,无网络损耗
  • 缺点:
    • 无序:造成索引重建,入库性能差
    • 字符串长:需要36字符

参考

  • UUID会重复吗?
  • 雪花算法视频
  • 9种分布式ID生成方式

六、扩展

  • 百度:UidGenerator
  • 美团:Leaf

总结

优点缺点
uuid实现简单连续性差,作为主键每次新增数据都会触发索引重建。
分布式环境中可能重复
雪花算法性能好,有序依赖服务器时间,时钟回拨可能生成重复ID
号段模式
redis/zookeeperRedis基于INCR 命令生成 分布式全局唯一id
zookeeper一种通过节点,一种通过节点的版本号

基因算法

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

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

相关文章

【JavaEE】多线程案例-单例模式

文章目录 1. 前言2. 什么是单例模式3. 如何实现单例模式3.1 饿汉模式3.2 懒汉模式4. 解决单例模式中遇到的线程安全问题4.1 加锁4.2 加上一个判断解决频繁加锁问题4.2 解决因指令重排序造成的线程不安全问题 1. 前言 单例模式是我们面试中最常考到的设计模式。什么是设计模式呢…

【Redis】深入探索 Redis 主从结构的创建、配置及其底层原理

文章目录 前言一、对 Redis 主从结构的认识1.1 什么是主从结构1.2 主从结构解决的问题 二、主从结构创建2.1 配置并建立从节点2.2.1 从节点配置文件2.2.2 启动并连接 Redis 主从节点2.2.3 SLAVEOF 命令2.2.4 断开主从关系 2.2 查看主从节点的信息2.2.1 INFO REPLICATION 命令2.…

《DevOps实践指南》- 读书笔记(六)

DevOps实践指南 Part 4 第二步 &#xff1a;反馈的技术实践17. 将假设驱动的开发和A/B测试融入日常工作17.1 A/B 测试简史17.2 在功能测试中集成 A/B 测试17.3 在发布中集成 A/B 测试17.4 在功能规划中集成 A/B 测试17.5 小结 18. 建立评审和协作流程以提升当前工作的质量18.1 …

04条件构造器和常用接口

条件构造器和常用接口 wapper介绍 条件构造器的两个条件之间默认就是AND并列关系,如果需要或者的关系则需要调用构造器的or()方法 条件构造器类型作用Wrapper条件构造抽象类,最顶端父类AbstractWrapper生成SQL的where条件QueryWrapper封装查询或删除的条件UpdateWrapper封装修…

小程序自定义tabbar

前言 使用小程序默认的tabbar可以满足常规开发&#xff0c;但是满足不了个性化需求&#xff0c;如果想个性化开发就需要用到自定义tabbar,以下图为例子 一、在app.json配置 先按照以往默认的形式配置&#xff0c;如果中间的样式特殊则不需要配置 "tabBar": {&qu…

社区分享|MeterSphere变身“啄木鸟”,助力云帐房落地接口自动化测试

云帐房网络科技有限公司&#xff08;以下简称为“云帐房”&#xff09;成立于2015年3月&#xff0c;以“成为最值得信赖的税务智能公司”为愿景&#xff0c;运用人工智能、大数据等互联网技术&#xff0c;结合深厚的财税行业服务经验&#xff0c;为代账公司和中大型企业提供智能…

避雷器雷击计数器检验

试验目的 由于密封不良&#xff0c; 放电计数器在运行中可能进入潮气或水分&#xff0c; 使内部元件锈蚀&#xff0c;导致计数器不能正确动作&#xff0c; 因此需定期试验以判断计数器是否状态良好、 能否正常动作&#xff0c; 以便总结运行经验并有助于事故分析。 带有泄漏电…

小程序隐私弹窗的实现

小程序的开发者对于微信官方来说是有爱有恨&#xff0c;三天二头整事是鹅厂的一贯风格。 隐私弹窗的几个要点 回归正题&#xff0c;小程序隐私弹窗的几个要点&#xff1a; 1、何时弹出用户隐私协议的弹窗&#xff1f; 2、是每次进小程序都弹出来吗&#xff1f; 这两个想明…

什么是HTTP状态码?常见的HTTP状态码有哪些?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是HTTP状态码&#xff1f;⭐ 1xx - 信息性状态码⭐ 2xx - 成功状态码⭐ 3xx - 重定向状态码⭐ 4xx - 客户端错误状态码⭐ 5xx - 服务器错误状态码⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前…

1979-2021年地级市空气流通系数数据

1979-2021年地级市空气流通系数数据 1、时间&#xff1a;1979-2021年 2、来源&#xff1a;整理自era-interim 3、范围&#xff1a;367个地级市 4、指标&#xff1a;10米风速、边界层高度、空气流通系数 5、指标解释&#xff1a; 空气流动系数是空气污染的常用工具变量&am…

小米手机安装面具教程(Xiaomi手机获取root权限)

文章目录 1.Magisk中文网&#xff1a;2.某呼&#xff1a;3.最后一步打开cmd命令行输入的时候:4.Flash Boot 通包-Magisk&#xff08;Flash Boot通刷包&#xff09;5.小米Rom下载&#xff08;官方刷机包&#xff09;6.Magisk最新版本国内源下载 1.Magisk中文网&#xff1a; htt…

SpringBoot对Filter过滤器中的异常进行全局处理

前言 今天处理拦截器中的异常时&#xff0c;遇到这样一个问题&#xff0c;我们希望在过滤器中对用户的请求进行判断&#xff0c;如果不符合要求直接抛出异常并在前端展示。但是如果我们直接在过滤器中throw一个异常时&#xff0c;尽管我们使用ControllerAdvice和 ExceptionHan…

再见纸质档案!电子会计档案数字化建设,为企业持续创造价值

传统的会计档案工作需要依靠纸张存储&#xff0c;对存储的空间大小、温度和湿度都有要求&#xff0c;财务人员要人工完成所有归档工作&#xff0c;档案在采集、调阅、借阅的过程中也易发生损坏、丢失等情况&#xff0c;企业需要付出高额的管理成本&#xff0c;不仅给财务人员带…

【IoT】生产制造:锅仔片上机做 SMT 加工吗?

目录 简介 锅仔片 简介 由于最近做产品用到了锅仔按键&#xff0c;由于单品用量过多&#xff0c;但是成品锅仔按键价格又太高&#xff0c;不适合量产。 这个时候就想到了锅仔片&#xff0c;问题又来了&#xff0c;锅仔片是否可以上机呢&#xff1f; 答案是肯定的。 锅仔片…

Spring实例化源码解析(二)

ConfigurationClassPostProcessor源码 解析 书接上回&#xff0c;在第一次调用invokeBeanDefinitionRegistryPostProcessors方法的时候参数currentRegistryProcessors为ConfigurationClassPostProcessor&#xff0c;本章主要深入这个类的postProcessBeanDefinitionRegistry方法…

B树的定义和特点

1.多叉查找树的效率 策略1:m叉查找树中&#xff0c;规定除了根节点外&#xff0c;任何结点至少有[m/2]个分叉&#xff0c;即至少含有[m/2]-1个关键字。策略2:m叉查找树中&#xff0c;规定对于任何一个结点&#xff0c;其所有子树的高度都要相同。 而满足以上两种策略的树被称…

halcon算子2、gray_histo

gray_histo 计算直方图 原形&#xff1a;gray_histo(Regions, Image : : : AbsoluteHisto, RelativeHisto) 功能&#xff1a;计算直方图 参数&#xff1a;Regions&#xff1a;区域&#xff0c;要计算的区域&#xff08;在image上的区域&#xff09; Image &#xff1a;要计算的…

【算法】迷宫问题

文章目录 前言1.迷宫问题求解分步骤求解代码 2.迷宫最短路径求解代码 前言 迷宫问题本质就是一个图的遍历问题&#xff0c;从起点开始不断四个方向探索&#xff0c;直到走到出口&#xff0c;走的过程中我们借助栈记录走过路径的坐标。 栈记录坐标有两方面的作用&#xff0c;一…

Git配置SSH

前言&#xff1a; Git是分布式的代码管理工具&#xff0c;远程的代码管理是基于SSH的&#xff0c;所以要使用远程的Git则需要SSH的配置 温馨提示&#xff1a; 1.查看是否已经有了ssh公钥&#xff1a;cd ~/.ssh 如果没有则不会有此文件夹&#xff0c;有则删除 一、git 配置 &a…

【HarmonyOS】【DevEco Studio】盘点DevEco Studio日志获取途径

【关键词】 DevEco Studio、日志获取 【问题背景】 在收到IDE工单的时候&#xff0c;很多时候开发者出现的问题都需要提供一些日志&#xff0c;然后根据日志分析&#xff0c;那么你知道IDE各种日志的获取方式么&#xff1f;往下看 【获取方法】 一、idea.log获取 IDE界面H…