怎样生成全局唯一流水号?UUID、自增主键,你已经Out啦,快来学习定制化雪花算法。

article/2025/11/8 16:24:08

前言

流水号是每个系统永远都绕不开的一个话题,如订单系统中的订单号,物流系统的运单号、银行系统的业务单号等等,不难发现这些单号虽然叫法不一样,但都有着一些相同的共性,那就是全局唯一性。除此之外,一个设计良好的流水号生成规则还应该包含如下特性:

  • 全局唯一性:在整个系统中唯一,可以通过单号直接定位到具体数据
  • 可读性:能够直接从单号上获取一些基本信息
  • 可扩展性:支持海量id,当应用扩展时可以做到平滑升级
  • 递增趋势:需要根据业务或时间呈递增趋势
  • 不可推测性:使用户无法猜测下一个或上一个订单号是什么
  • 高性能:流水号生成速度要快,不能成为业务瓶颈
  • 高可用:流水号生成必须要稳定,不能影响业务发展

背景

因为最近要做新的支付系统,而旧系统的订单号生成规则略有缺陷,所以老大让我设计一个新的订单号生成规则,于是我基于“内事不决问百度,外事不决问谷歌”的原则了解到目前常见的流水号生成方案有如下几种:数据库自增流水号、UUID流水号、雪花算法流水号。

数据库自增流水号、uuid流水号

数据库自增流水号、uuid流水号应该是最简单的两种实现方案了,根据之前提到的特性来简单分析一下这两方案的优缺点。

数据库自增流水号示例:1000001、1000002、1000003.

uuid流水号示例:59ced782-6000-4ca5-969d-ad99d11ccc54、fba05087-e6a0-4cb9-8bdc-7fa36ecadd92

全局唯一性可读性可扩展性递增趋势不可推测性高性能高可用
数据库自增流水号XXX
UUIDXX

数据库自增流水号解读:

数据库自增流水号由数据库生成,使用起来非常简单,递增趋势,性能及可用性都依赖于底层数据库,弊端也很明显:无可读性、扩展性差(指分库分表),而且最大的问题是可推测的,用户完全可以通过一个订单号推测出上一个订单号。(不推荐)

UUID流水号解读:

UUID也是一种算法,且具有很多个版本。在Java中通过UUID.randomUUID()就可以生成一个全局唯一的流水号,由于不需要依赖第三方类库,因此扩展性、性能、可用性都还可以,但是它也存在着致命的缺陷:如果在mysql中用UUID作为主键,由于它是无序的,所以在写入时可能会产生大量的随机IO及页分裂等问题,影响数据库性能。其次uuid是字符串类型的数据,也占用更大的储存空间 (不推荐)

优化建议:如果采用uuid建议删除中间的“-”减少字符长度,同时还可以将uuid转为hex进行使用

雪花算法流水号(SnowFlake )

SnowFlake 算法,是Twitter 开源的分布式id生成算法。其核心思想是:使用一个64bit的long 型数字作为全局唯一id,它将64bit的long类型划分为5个部分,每个部分表示不同的意义,最终合并成一个long类型全局唯一id,雪花算法划分规则如下

  • 因为计算机中规定了二进制数的第一位是符号位,0表示正数,1表示负数。很明显我们不需要负的流水号,因此64bit中的第一位表示为0
  • 第二个部分是由41bit的时间戳组成。(这也是雪花算法递增的原因)
  • 第三部分是一个5bit的机房标识,可以标识出这个id是由那个机房产生的
  • 第四部分是一个5bit的机器标识,可以标识出这个id是由那个机器产生的
  • 最后一部分是由12bit组成的序号,当一台机器上统一毫秒产生了多个id时,通过这个序号进行累加

雪花算法原本是Twitter用Scala写的,开源后网上也出现了很多Java的船新版本,具体实现随便去百度翻一翻就有了。

雪花算法流水号示例:1402972067925639168、1402972117498118144

全局唯一性可读性可扩展性递增趋势不可推测性高性能高可用
雪花算法X

雪花算法流水号解读:

雪花算法生成的是一个19位long类型流水号,除了可读性以外的其他特性基本都是可以满足的,我刚开始也是采用的雪花算法 (可以使用)

注意点1:单机环境服务器时钟发生倒退时,会存在流水号重复的风险

注意点2:集群环境时使用雪花算法需要为每一台机器设置不同的机器号,否则会存在单号重复的风险

定制化雪花算法

系统开发完成在测试环境跑了两天后,我觉得雪花算法生成的订单号还是不太理想,原因主要有两方面

  • 1、雪花算法生成的订单号不可读,单从订单号看不出任何有用的信息
  • 2、如果后期需要将应用扩展为集群环境,怎样在不更改代码的前提下为每一个应用设置不同的机器标识也是一个棘手的问题。

我拿出手机观察了一下早上买包子时的两个支付宝订单号:2021060222001434331413012752、2021060322001434331413013303,很明显前面的20210602是订单日期,我顿时心生一计:能不能仿照雪花算法的思想实现一套类似于支付宝的这种订单号呢?于是诞生了如下代码

import java.math.BigInteger;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ThreadLocalRandom;/*** 基于雪花算法的思想定制化的一个id规则生成器** 方案一:* 17位时间戳 + 3位序号 + 2位随机数 + 2位机器号  String类型  mysql存储用varchar     支持1000000QPS** 方案二:* 14位时间戳 + 2位序号 + 2位随机数 + 1位机器号  Long类型  采用bigint类型存取      支持100QPS** @author hcq* @date 2021/6/10 20:50*/
public class IdGenerator {private final long workerId;private final int seqBit;private final int workBit;private final int randomBit;private long lastTimestamp = 0L;private long sequence = 0L;private static final BigInteger UNIT = new BigInteger("10");private final static DateTimeFormatter FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");/*** @param workerId   机器序列号* @param workBit 机器序列号位数* @param seqBit 序列号位数* @param randomBit 随机数位数*/public IdGenerator(long workerId, int seqBit, int workBit, int randomBit) {this.workerId = workerId;this.seqBit = seqBit;this.workBit = workBit;this.randomBit = randomBit;int maxValue = (UNIT.pow(workBit).intValue() - 1);if (workerId > maxValue || workerId < 0) {throw new IllegalArgumentException(String.format("workerId不能大于[%d],且不能小于0", maxValue));}}/*** 获取下一个ID*/public synchronized String nextId() {long timestamp = now();if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("时钟倒退[%s]ms", lastTimestamp - timestamp));}if (lastTimestamp == timestamp) {sequence = sequence + 1;// 当前毫秒内计数满了,则等待下一毫秒if (sequence >  (UNIT.pow(seqBit).intValue()-1)) {timestamp = nextMillis(lastTimestamp);}} else {sequence = 0L;}lastTimestamp = timestamp;int random = ThreadLocalRandom.current().nextInt(0, UNIT.pow(randomBit).intValue());return String.format("%017d", timestamp) +String.format("%0"+seqBit+"d", sequence) +String.format("%0"+randomBit+"d", random) +String.format("%0"+workBit+"d", workerId) ;}private long nextMillis(final long lastTimestamp) {long timestamp = this.now();while (timestamp <= lastTimestamp) {timestamp = this.now();}return timestamp;}private long now() {LocalDateTime dateTime = LocalDateTime.now();return Long.parseLong(dateTime.format(FORMAT));}public static void main(String[] args) {// 方案一IdGenerator wo = new IdGenerator(1, 3, 2,2);System.out.println(wo.nextId());}
}

方案一:生成的Id流水号示例:202106102155286780008401、202106102158223300001801、202106102158281530004801

方案二:生成的Id流水号示例:2021061022021200331、2021061022021800971、2021061022022400441

定制化雪花算法解读:

虽然我这个定制化的雪花算法优化了雪花算法不可读的弊端,但是方案一生成的雪花ID有24位,这也就意味着在Java中只能用String或者BigInteger来存储,在Mysql中则需要用25个字节长度的varchar类型来存储,而mysql中bigint类型才只占用8个字节而已,因此才衍生出19位长度的第二种方案、如果系统业务量不是很大的情况下还是建议优先使用方案二。(推荐使用)

优化建议一:日期20210610可以优化为210610,节省的两个位置,随机数让出一位,然后将日期精确到毫秒

优化建议二:日期让出两位补给序号位

方案对比

全局唯一性可读性可扩展性递增趋势不可推测性
数据库自增流水号XXX
UUIDXX
雪花算法X
定制化雪花算法

自动注册机器号

回到刚才定制化雪花算法的第二个问题:如果后期需要将应用扩展为集群环境,怎样在不更改代码的前提下为每一个应用设置不同的机器标识?

我初步的想法是通过第三方的存储介质(mysql、redis、zk等等)来实现应用自动注册并获取机器号的方式,例如在应用启动的时候,向mysql中写入一条数据记录ip地址,同时借助mysql的自增id作为机器号来初始化雪花算法组件。

结尾

太晚了,明天还要搬砖,今天就先写到这里吧,后面准备写一个通用的SpringBootStart,这个功能后续应该也会集成进去,到时候再来填坑(下次一定)

img


http://chatgpt.dhexx.cn/article/RYmfIBwr.shtml

相关文章

Java自动生成订单编号+流水号

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…

JAVA如何利用Lock实现多线程并发生成唯一的流水号

本文记录在开发过程&#xff0c;JAVA如何利用ReentrantLock实现多线程并发生成唯一的流水号。 在实际的开发中&#xff0c;我们会经常碰到需要生成唯一流水号的业务场景。有些情况可以采用数据库自定义序列号自增生成流水号&#xff0c;亦或是自己编写数据库触发器生成流水号。…

mysql 生成流水号 存储过程 订单编号

用存储过程生成流水号是很常用的&#xff0c;这里以生成订单编号的流水号作为示例。&#xff08;新的一天的流水号从1开始&#xff0c;如&#xff1a;今天的订单编号是CD2013010900014&#xff0c;下一个订单编号将是CD2013010900015&#xff1b;明天的订单编号将从CD201301100…

流水号生成规则

流水号生成规则 从“0001”号起始&#xff0c;依序不跳跃不间断地编号&#xff0c;形成流水编码&#xff0c;依次为0001、0002、0003、0004、0005、0006…等。当编至“9999”号&#xff0c;仍需继续编号时&#xff0c;从“A000”号&#xff08;A000代表10000&#xff09;起始重…

一种生成流水号的方法

1.介绍 今天做了一个功能&#xff0c;生成订单流水号&#xff0c;当然这其实这并不是一个很难的功能&#xff0c;最直接的方式就是日期主机Id随机字符串来拼接一个流水号。但是今天有个我认为比较优雅方式来实现。我要介绍是日期 long&#xff08;商家Id订单类型主机IDAtomicIn…

简单介绍订单号或者流水号的生成方法

一般订单号或者流水号等可能在一些平台会用到&#xff0c;然后我就简单的介绍一个我自己生成订单号和流水号的一个方法吧&#xff0c;如果程序有问题或者你有更好的生成办法&#xff0c;欢迎留言&#xff0c;留下你的文章链接&#xff0c;我们一起学习和进步哈。 方法简介&…

如何使用redis生成流水号

概述 本文讲述如何使用redis生成流水号。本文是在Springboot中实现的。知道原理之后其他框架也可以轻松实现。 原理介绍 本文主要是使用redis的incr方法进行自增补零。然后结合时间、随机数、前缀组成唯一的流水号。 下面是流水号的结构。 在文章的最后还是简单介绍一下redis的…

谈谈订单号和流水号的关系

订单号和流水号是不同的。 首先订单号是订单唯一的编号&#xff0c;而且电商平台的各种子系统也是根据订单来统计业务完成的情况&#xff0c;订单编号经常用来被查询&#xff0c;所以数据类型必须是数字&#xff0c;而且是全局唯一&#xff0c;那肯定就得主键字段了。 然后流水…

低代码学习教程:生成固定格式流水号

方法1&#xff1a;RECNO()方法2&#xff1a;MAPX() 表单设计中经常涉及流水号的制作问题&#xff0c;下面就分别介绍下两种编号的实现方法&#xff0c;大家可以根据需要自行选择。 注意&#xff1a; 百数已支持【流水号】控件&#xff0c;如有特殊要求可参考文档&#xff1a;…

【26天高效学习Java编程】Day19:Java 多线程

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;由浅入深讲解Java的基本使用&#xff0c;希望大家都能够从中有所收获&#xff0c;也请大家多多支持。 专栏地址:26天高效学习Java编程 相关软件地址:软件地址 所有代码地址:代码地址 如果文章知识点有错误的地方&#x…

怎么入门学习Java编程

因为目前java非常火,应用非常的广泛,是目前最火的行业之一,竞争很大,工资很高,未来发展也极好。 如条件还可以,负担不是那么大,可以选择培训,培训一定会比你自学的好,如果培训都学不好,自学肯定更难。目前java的培训费用都是2W+,这还只是培训费而已,加上一些其他的…

Java学习

集合 什么是集合&#xff1f; 集合&#xff1a;集合是java中提供的一种容器&#xff0c;可以用来存储多个数据。 集合和数组的区别 数组的长度是固定的。集合的长度是可变的。 数组中存储的是同一类型的元素&#xff0c;可以存储任意类型数据。集合存储的都是引用数据类型。如…

想学习Java编程,看书还是看视频更合适?

首先&#xff1a;自己本身就是初级或者零基础的&#xff0c;自己对软件了解的都不足够&#xff0c;跟着视频学&#xff0c;老师操作操作一步你就能看着他操作&#xff0c;这样心里更有谱。 第二&#xff1a;跟着视频学能学的更好&#xff0c;知识体系更全&#xff0c;一般视频…

自学过来人告诉你,初学者应该怎么快速的学习Java编程?

我说说我个人的案例吧&#xff0c;我电子信息专业&#xff0c;后来选择做了Java开发&#xff0c;在11年的时候开始学习的Java&#xff0c;可以说那时候的企业要求低于现在&#xff0c;我当时学习由于没有钱&#xff0c;我是自学的&#xff0c;我大学学过C语言 我晚上下班的时候…

通过项目驱动的学习方法快速掌握Java编程

摘要 Java作为一种广泛应用于软件开发领域的编程语言&#xff0c;对于零基础的学习者来说&#xff0c;学习Java编程可能存在一定的难度。本文将介绍如何通过项目驱动的学习方法&#xff0c;帮助零起点的学习者快速掌握Java编程。通过以项目为核心的学习路径、结合实践和理论的…

Java学习之编程入门

0 编程入门 0.1 概述0.2 计算机硬件介绍0.2.1 中央处理器0.2.2 存储设备0.2.3 内存0.2.4 输入和输出设备0.2.5 通信设备 0.3 计算机发展史上的鼻祖0.4 操作系统0.5 万维网0.6 职业发展与提升0.7 学习经验探讨 0.1 概述 计算机包括硬件(hardware)和软件(software)两部分。 硬件…

学习Java编程知识 必知要点

Java 是全球最受欢迎的编程语言之一&#xff0c;在世界编程语言排行榜 TIOBE 中&#xff0c;Java 一直霸占着前三名&#xff0c;有好多年甚至都是第一名。那么如此强大的Java你真的了解他的知识体系吗&#xff1f;他的学习路线你知道吗&#xff1f; 1. Java虚拟机——JVM JVM&a…

java三大平台介绍,选择哪个平台学习java编程?

&#x1f482; 个人主页: IT学习日记&#x1f91f; 版权: 本文由【IT学习日记】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦&#x1f485; 想寻找共同成长的小伙伴&#xff0c;请点击【技术圈子】 眼见…

学习java

Java 深度历险(作者成富&#xff0c;是IBM 中国软件开发中心的高级工程师) 2 目录 序 .................................................................................................................................. 1 目录 ................................…

Java怎么学习

入门的时候一定要搞清楚面向对象相关的概念 对象&#xff0c;类&#xff0c;实例&#xff0c;这三者的含义&#xff0c;还有三者之间有什么关系。 类之间的关系有那些<继承&#xff0c;聚合 组合&#xff0c;普通关联 自返关联>&#xff0c;关联的多重性&#xff0c;都…