Java 生成订单号或唯一id(高并发)方案

- 日理万妓 2024-03-31 09:05 204阅读 0赞

1、直接使用uuid

  1. public static String getUUID() {
  2. String replaceUUID = UUID.randomUUID().toString().replace("-", "");
  3. return replaceUUID;
  4. }

但由于生成的数据没有规律性,并且太长;

测试:循环1000w次

测试代码:

  1. public static void main(String[] args) {
  2. long startTime = System.currentTimeMillis();
  3. Set set=new HashSet<>();
  4. for(int i=0;i<10000000;i++){
  5. String uuid = getUUID();
  6. System.out.println("uuid---"+i+"======="+uuid);
  7. set.add(uuid);
  8. }
  9. long endTime = System.currentTimeMillis();
  10. System.out.println("set.size():"+set.size());
  11. System.out.println("endTime-startTime:"+(endTime-startTime));
  12. }

控制台提示:

9670c7c8616944e692c296a3c3738b8e.jpeg

2、用时间(精确到毫秒)+随机数

  1. //时间(精确到毫秒)
  2. DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
  3. String localDate = LocalDateTime.now().format(ofPattern);
  4. //随机数
  5. String randomNumeric = RandomStringUtils.randomNumeric(8);

for循环1000w次,发现重复数据太多。因此光靠随机数并不可靠。

3、使用 时间(精确到毫秒)+随机数+用户id(业务id)

注意:如果是类似用户id,项目当中集成了权限框架,使用工具类获取即可,就不用传参了

  1. /**
  2. * 生成订单号(25位):时间(精确到毫秒)+3位随机数+5位用户id
  3. */
  4. public static synchronized String getOrderNum(Long userId) {
  5. //时间(精确到毫秒)
  6. DateTimeFormatter ofPattern = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
  7. String localDate = LocalDateTime.now().format(ofPattern);
  8. //3位随机数
  9. String randomNumeric = RandomStringUtils.randomNumeric(3);
  10. //5位用户id
  11. int subStrLength = 5;
  12. String sUserId = userId.toString();
  13. int length = sUserId.length();
  14. String str;
  15. if (length >= subStrLength) {
  16. str = sUserId.substring(length - subStrLength, length);
  17. } else {
  18. str = String.format("%0" + subStrLength + "d", userId);
  19. }
  20. String orderNum = localDate + randomNumeric + str;
  21. log.info("订单号:{}", orderNum);
  22. return orderNum;
  23. }

在2的基础上改造,加入用户的id等其他的业务id。

4.Java实现Snowflake算法的方案(高并发下,推荐使用这个)

  1. package com.lucifer.order.util.idgenerate;
  2. /**
  3. * Twitter_Snowflake<br>
  4. * SnowFlake的结构如下(每部分用-分开):<br>
  5. * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
  6. * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
  7. * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
  8. * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
  9. * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
  10. * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
  11. * 加起来刚好64位,为一个Long型。<br>
  12. * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。
  13. *
  14. * @author Lucifer
  15. */
  16. public class SnowFlake {
  17. // ==============================Fields===========================================
  18. /**
  19. * 开始时间截 (2018-07-03)
  20. */
  21. private final long twepoch = 1530607760000L;
  22. /**
  23. * 机器id所占的位数
  24. */
  25. private final long workerIdBits = 5L;
  26. /**
  27. * 数据标识id所占的位数
  28. */
  29. private final long datacenterIdBits = 5L;
  30. /**
  31. * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
  32. */
  33. private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
  34. /**
  35. * 支持的最大数据标识id,结果是31
  36. */
  37. private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
  38. /**
  39. * 序列在id中占的位数
  40. */
  41. private final long sequenceBits = 12L;
  42. /**
  43. * 机器ID向左移12位
  44. */
  45. private final long workerIdShift = sequenceBits;
  46. /**
  47. * 数据标识id向左移17位(12+5)
  48. */
  49. private final long datacenterIdShift = sequenceBits + workerIdBits;
  50. /**
  51. * 时间截向左移22位(5+5+12)
  52. */
  53. private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
  54. /**
  55. * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
  56. */
  57. private final long sequenceMask = -1L ^ (-1L << sequenceBits);
  58. /**
  59. * 工作机器ID(0~31)
  60. */
  61. private long workerId;
  62. /**
  63. * 数据中心ID(0~31)
  64. */
  65. private long datacenterId;
  66. /**
  67. * 毫秒内序列(0~4095)
  68. */
  69. private long sequence = 0L;
  70. /**
  71. * 上次生成ID的时间截
  72. */
  73. private long lastTimestamp = -1L;
  74. //==============================Constructors=====================================
  75. /**
  76. * 构造函数
  77. *
  78. * @param workerId 工作ID (0~31)
  79. * @param datacenterId 数据中心ID (0~31)
  80. */
  81. public SnowFlake(long workerId, long datacenterId) {
  82. if (workerId > maxWorkerId || workerId < 0) {
  83. throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
  84. }
  85. if (datacenterId > maxDatacenterId || datacenterId < 0) {
  86. throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
  87. }
  88. this.workerId = workerId;
  89. this.datacenterId = datacenterId;
  90. }
  91. // ==============================Methods==========================================
  92. /**
  93. * 获得下一个ID (该方法是线程安全的)
  94. *
  95. * @return SnowflakeId
  96. */
  97. public synchronized long nextId() {
  98. long timestamp = timeGen();
  99. //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
  100. if (timestamp < lastTimestamp) {
  101. throw new RuntimeException(
  102. String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
  103. }
  104. //如果是同一时间生成的,则进行毫秒内序列
  105. if (lastTimestamp == timestamp) {
  106. sequence = (sequence + 1) & sequenceMask;
  107. //毫秒内序列溢出
  108. if (sequence == 0) {
  109. //阻塞到下一个毫秒,获得新的时间戳
  110. timestamp = tilNextMillis(lastTimestamp);
  111. }
  112. }
  113. //时间戳改变,毫秒内序列重置
  114. else {
  115. sequence = 0L;
  116. }
  117. //上次生成ID的时间截
  118. lastTimestamp = timestamp;
  119. //移位并通过或运算拼到一起组成64位的ID
  120. return (((timestamp - twepoch) << timestampLeftShift)
  121. | (datacenterId << datacenterIdShift)
  122. | (workerId << workerIdShift)
  123. | sequence);
  124. }
  125. /**
  126. * 阻塞到下一个毫秒,直到获得新的时间戳
  127. *
  128. * @param lastTimestamp 上次生成ID的时间截
  129. * @return 当前时间戳
  130. */
  131. protected long tilNextMillis(long lastTimestamp) {
  132. long timestamp = timeGen();
  133. while (timestamp <= lastTimestamp) {
  134. timestamp = timeGen();
  135. }
  136. return timestamp;
  137. }
  138. /**
  139. * 返回以毫秒为单位的当前时间
  140. *
  141. * @return 当前时间(毫秒)
  142. */
  143. protected long timeGen() {
  144. return System.currentTimeMillis();
  145. }
  146. //==============================Test=============================================
  147. /**
  148. * 测试
  149. */
  150. public static void main(String[] args) {
  151. long startTime = System.currentTimeMillis();
  152. SnowFlake idWorker = new SnowFlake(0, 0);
  153. Set set = new HashSet();
  154. for (int i = 0; i < 10000000; i++) {
  155. long id = idWorker.nextId();
  156. set.add(id);
  157. System.out.println("id----"+i+":"+id);
  158. }
  159. long endTime = System.currentTimeMillis();
  160. System.out.println("set.size():" + set.size());
  161. System.out.println("endTime-startTime:" + (endTime - startTime));
  162. }
  163. }

也可以在雪花算法生成的id的基础上拼接日期,不过性能有所损耗。

  1. public static String timestampConversionDate(String param) {
  2. Instant timestamp = Instant.ofEpochMilli(new Long(param));
  3. System.out.println("timestamp:"+param);
  4. LocalDateTime localDateTime = LocalDateTime.ofInstant(timestamp, ZoneId.systemDefault());
  5. String format = localDateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
  6. return format;
  7. }

测试1:

循环1000w次,发现并无重复

47e4bc46db194c7d972aba0feebc8146.jpeg

测试2:100个线程,每个线程负责生成10w个id

  1. //多线程测试
  2. public static void main(String[] args) throws InterruptedException {
  3. long startTime = System.currentTimeMillis();
  4. CountDownLatch countDownLatch=new CountDownLatch(10000000);
  5. final SnowFlake idWorker = new SnowFlake(0, 0);
  6. Set set = Collections.synchronizedSet(new HashSet());
  7. for (int i = 0; i < 100; i++) {
  8. Thread thread = new Thread(() -> {
  9. for (int i1 = 0; i1 < 100000; i1++) {
  10. long id = idWorker.nextId();
  11. System.out.println("id:"+id);
  12. set.add(id);
  13. countDownLatch.countDown();
  14. }
  15. });
  16. thread.start();
  17. }
  18. countDownLatch.await();
  19. long endTime = System.currentTimeMillis();
  20. System.out.println("set.size():" + set.size());
  21. System.out.println("endTime-startTime:" + (endTime - startTime));
  22. }

94e54a0acbbc408fbd9dc85340587a9e.jpeg

发表评论

表情:
评论列表 (有 0 条评论,204人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java 有序生成订单流水号

    前言 >   最近用到了一些编号的生成规则记录一下,有序的生成订单号或者流水号 概述   第一种方式,适用于循环生成订单编号,例如有 100 条数据,需要循环生成