需求

在业务系统中常常需要生成业务流水号,通常可根据流水号判断业务触发时间,同时又必须保证在业务服务多实例情况下流水号的唯一性。网上常见的方案是使用redis自增的功能或直接使用数据库表序列自增功能做流水号,这些方案必须依赖第三方服务使得业务的依赖性变高,可用性降低,基于此是否可开发出不依赖任何第三方服务的流水号功能了?

实现方案

再看mybatis-plus源码时,受到生成序列号的实现启发,自己实现了一个订单流水号生成器SequenceNoUtils,分布式订单流水号生成器SequenceNoUtils 基于开源项目:https://gitee.com/yu120/sequence优化改造,使用类雪花算法实现,可生成23个字符串流水号,前15位字符串为时间,具体可到毫秒,后8位为机器ID+序列号,可满足大部分业务场景需求。

订单流水号生成器,最多23个数字字符串,如:22070908371818823233395,15个数字字符时间戳+8个数字字符标识[26位bit(12位机器标识位数(最大4095)+14位序列号(最大16383)), 新的毫秒"sequence"等于1到10000的随机数,16383-10000=6383,故单机排除机器其他性能因数,理论上一秒钟可生成6376617(6383*999)个流水号,4095个业务服务部署,流水号只有在多实例部署下有极低重复,使用时需设置流水号字段做唯一索引,获取重复在获取一次即可。

流水号样例:22071210583016014764553

源码:

package com.likavn.falcon.common.utils;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;/*** 业务流水号生成器,固定为23个数字字符,如:22070908371818823233395* <p>* 15个数字字符时间戳+8个数字字符标识[26位bit(3位数据中心位(最大7个)+11位机器标识位数(最大2047)+12位序列号(最大4095))* </p>* 新的毫秒"sequence"等于1到1000的随机数,4095-1000=3095,* 故单机排除机器其他性能因数,理论上一秒钟可生成3091905(3095*999)个流水号* 同一个数据中心同一个业务服务不可超过最大2047个业务服务实例数.** <p>优化开源项目:https://gitee.com/yu120/sequence</p>** @author Lwei* @date 2021/12/23 9:50*/
public class SequenceNoUtils {private static final Logger logger = LoggerFactory.getLogger(SequenceNoUtils.class);/*** 时间搓格式化*/private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmssSSS");/*** 数字标识位数*/private final long numberBitsMast = 26L;/*** 数据中心识位数*/private final long dataCenterIdBits = 3L;/*** 机器标识位数*/private final long workerIdBits = 11L;/*** 毫秒内自增位*/private final long sequenceBits = numberBitsMast - dataCenterIdBits - workerIdBits;/*** 最大序列号*/private final long sequenceMask = ~(-1L << sequenceBits);private final long workerIdShift = sequenceBits;private final long dataCenterIdShift = workerIdShift + workerIdBits;private long sequence = 0L;/*** 8个数字字符标识段左补齐*/private final String numberFullLeft = "%08d";private long lastTimestamp = -1L;/*** 当前数据中心*/private final long dataCenterId;/*** 当前机器节点*/private final long workerId;private SequenceNoUtils() {// 最大数据中心数long maxDataCenterId = ~(-1L << dataCenterIdBits);this.dataCenterId = getDataCenterId(maxDataCenterId);// 最大机器节点数long maxWorkerId = ~(-1L << workerIdBits);this.workerId = getMaxWorkerId(dataCenterId, maxWorkerId);}/*** 获取流水号** @return code*/public static String generateNo() {return getSingleton().nextCode();}/*** 单例模式-双重校验锁** @return sq*/private static SequenceNoUtils getSingleton() {return SingletonHolder.SINGLETON;}private static final class SingletonHolder {static final SequenceNoUtils SINGLETON = new SequenceNoUtils();}/*** next code** @return code*/private synchronized String nextCode() {long timestamp = timeGen();//闰秒if (timestamp < lastTimestamp) {long offset = lastTimestamp - timestamp;if (offset <= 5) {try {wait(offset << 1);timestamp = timeGen();if (timestamp < lastTimestamp) {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));}} catch (Exception e) {throw new RuntimeException(e);}} else {throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", offset));}}if (lastTimestamp == timestamp) {// 相同毫秒内,序列号自增sequence = (sequence + 1) & sequenceMask;if (sequence == 0) {// 同一毫秒的序列数已经达到最大timestamp = tilNextMillis(lastTimestamp);}} else {// 新增的毫秒,序列号置为 1 - 1000 随机数sequence = ThreadLocalRandom.current().nextLong(1, 1000);}lastTimestamp = timestamp;// 时间戳格式化 + (数据中心标识 | 机器标识部分 | 序列号部分)return TIME_FORMATTER.format(LocalDateTime.ofInstant(new Date(timestamp).toInstant(), ZoneId.systemDefault()))// 左补齐+ String.format(numberFullLeft, ((dataCenterId << dataCenterIdShift)| (workerId << workerIdShift)| sequence));}/*** 数据标识id部分*/private static long getDataCenterId(long maxDataCenterId) {long id = 0L;try {InetAddress ip = InetAddress.getLocalHost();NetworkInterface network = NetworkInterface.getByInetAddress(ip);if (network == null) {id = 1L;} else {byte[] mac = network.getHardwareAddress();if (null != mac) {id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6;id = id % (maxDataCenterId + 1);}}} catch (Exception e) {logger.warn(" getDataCenterId: " + e.getMessage());}return id;}/*** 获取 maxWorkerId*/private static long getMaxWorkerId(long maxDataCenterId, long maxWorkerId) {StringBuilder mpId = new StringBuilder();mpId.append(maxDataCenterId);String name = ManagementFactory.getRuntimeMXBean().getName();if (null != name && name.trim().length() > 0) {/** GET jvmPid*/mpId.append(name.split("@")[0]);}/** MAC + PID 的 hashcode 获取16个低位*/return (mpId.toString().hashCode() & 0xffff) % (maxWorkerId + 1);}/*** tilNextMillis** @param lastTimestamp stamp* @return l*/private long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 获取毫秒** @return l*/private long timeGen() {return SystemClock.now();}public static void main(String[] args) {long t = System.currentTimeMillis();for (int i = 0; i < 100000; i++) {System.out.println(generateNo());}System.out.println(System.currentTimeMillis() - t);}
}

使用:

public static void main(String[] args) {SequenceNoUtils.generateNo()
}

源码地址:SequenceNoUtils

分布式订单流水号生成器SequenceNoUtils相关推荐

  1. 分布式数据库id生成器的技术选型

    分布式数据库id生成器的技术选型 一. 数据库自增主键 二.UUID 三.数据库或者Redis生成ID 五.雪花算法 六.LEAF 七.案例 注:本文部分内容为转载,加入自己的理解及资料 作者:晚歌y ...

  2. 百度开源的分布式唯一ID生成器UidGenerator,解决了时钟回拨问题

    转载自   百度开源的分布式唯一ID生成器UidGenerator,解决了时钟回拨问题 UidGenerator是百度开源的Java语言实现,基于Snowflake算法的唯一ID生成器.而且,它非常适 ...

  3. php id 生产器,Laravel 分布式唯一 ID 生成器运用_PHP开发框架教程

    在运用程序中,常常须要全局唯一的ID作为数据库主键.怎样生成全局唯一ID? 起首,须要肯定全局唯一ID是整型照样字符串?假如是字符串,那末现有的UUID就完整满足需求,不须要分外的事情.瑕玷是字符串作 ...

  4. 商城系统中常用的订单流水号生成工具OrderIdUtil工具类(亲测好用)

    在我们写商城项目时,由于系统业务数据量巨大,所以唯一订单号的生成成为了必须,下面提供了易使用的订单流水号生成工具类 OrderIdUtil工具类代码如下 package com.zyw.seckill ...

  5. D-ITG(分布式互联网流量生成器)简介和基本操作

    D-ITG简介 D-ITG(分布式互联网流量生成器)是一个能够在数据包级别产生流量的平台,能够准确地复制IDT(出发间时间)和PS(数据包大小)随机变量(指数,均匀,柯西,正态,帕累托等)的适当随机过 ...

  6. 分布式全局ID生成器

    需求背景 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识.如在美团点评的金融.支付.餐饮.酒店.猫眼电影等产品的系统中,数据日渐增长,对数据分库分表后需要有一个唯一ID来标识一条数据或消息 ...

  7. 分布式全局ID生成器设计

    项目是分布式的架构,需要设计一款分布式全局ID,参照了多种方案,博主最后基于snowflake的算法设计了一款自用ID生成器.具有以下优势: 保证分布式场景下生成的ID是全局唯一的 生成的全局ID整体 ...

  8. 高并发下唯一订单号生成器【16位数字订单号】

    参考:常用的分布式ID方案 快速查询设计 带业务含义的编号,可以用来进行快速查询设计 高并发下唯一订单号生成思考? 订单号3个性质:1.唯一性 2.不可推测性 3.效率性 可选方案一  本方案使用的是 ...

  9. oracle生成工单号,订单流水号(唯一编号)的生成

    (1)通过存储过程方式生成(以Oracle为例) Create Or Replace Procedure MyProcedure { CV_1 OUT NVARCHAR2 } AS BEGIN DEC ...

  10. 淘宝订单截图生成器网页版制作

    你是否曾经为手动制作淘宝订单截图而烦恼?现在,有了淘宝订单生成器,这一切都变得轻松起来. 作为一款专为淘宝购物爱好者打造的神器,淘宝订单生成器可以轻松帮你生成美观的订单截图,让你的朋友们羡慕不已.不再 ...

最新文章

  1. 统计学习导论 Chapter2--What Is Statistical Learning?
  2. 刘若英《爱情限量版》摘录
  3. SAP Spartacus服务器端渲染模式下的调试方法
  4. Ansible 五(inventory文件 主机清单)
  5. 小凡模拟器:DynamipsGUI使用问题解决方法
  6. Linux命令之文件相关
  7. 新零售时代招商的新鲜玩法——用全网联动 促销活动来招商
  8. android 网页取词,有道词典屏幕取词怎么用?,你知道吗?在浏览网页
  9. maven安装教程(Idea)
  10. 经典查找算法 --- R树
  11. imdisk命令行使用及配置
  12. QT制作简易串口助手
  13. python中路径什么意思_python路径引用r的含义
  14. 美食app的UI设计
  15. Windows服务器搭建Node-Media-Server视频服务器
  16. helm安装cert-manager自动化Https(1.8)
  17. C# webBrowser 通过代理访问网页
  18. ModuleNotFoundError: No module named 'lightgbm'
  19. 大型网站系统架构的演化【转自飘扬的红领巾】
  20. UWA学堂|Unreal课程合集

热门文章

  1. Springboot 整合百度地图 API
  2. 储备池计算(Reservoir Computing)综述
  3. 灵巧好用的手机便签软件
  4. 「读书笔记」第五项修炼-学习型组织的艺术与实践(一)
  5. 酷狗音乐QQ显示(VC源码)
  6. 基于最大子覆盖问题的文本摘要实验
  7. 【白帽子学习笔记】CTF实践
  8. 数据库附加失败解决方法
  9. 超好用的ps图表快速生成插件:PS拉框助手 for mac
  10. linux r画图如何输出图片大小,R画图,设画布大小