當前位置: 妍妍網 > 碼農

在 SpringBoot 中設計一個訂單號生成系統

2010-11-04碼農

在Spring Boot中設計一個訂單號生成系統,主要考慮到生成的訂單號需要滿足的幾個要求:唯一性、可延伸性、以及可能的業務相關性。以下是幾種常見的解決方案及相應的範例程式碼:

1. UUID

最簡單的方法是使用UUID生成唯一的訂單號。UUID(Universally Unique Identifier)是一種廣泛使用的識別元,由128位元組成,通常以32個十六進制數位表示,分為五組,形式為8-4-4-4-12的字串,例如 123e4567-e89b-12d3-a456-426614174000

UUID全球唯一,實作簡單,但缺點是UUID較長,不易記憶和儲存。

例項程式碼

Java中生成UUID的範例程式碼如下:

import java.util.UUID;
public classUUIDGenerator{
publicstatic String generateUUID(){
// 生成一個UUID
UUID uuid = UUID.randomUUID();
// 將UUID轉換為字串
String uuidAsString = uuid.toString();
// 返回UUID字串
return uuidAsString;
}
publicstaticvoidmain(String[] args){
String uuid = generateUUID();
System.out.println("Generated UUID: " + uuid);
}
}

2. 資料庫序列或自增ID

利用資料庫的序列(如PostgreSQL的 SEQUENCE )或自增ID(如MySQL的 AUTO_INCREMENT )生成唯一的訂單號。資料庫序列或自增ID是一種常見的生成唯一識別元的方法,特別是在單體套用或非分布式系統中。

這種方法依賴於資料庫的內建機制來保證每次插入新記錄時自動產生一個唯一的識別元,缺點是難以在分布式環境中維護唯一性。

// 假設使用JPA
@Entity
public classOrder{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// 其他內容
}

資料庫序列(如PostgreSQL的SEQUENCE)

CREATESEQUENCE order_id_seq STARTWITH1INCREMENTBY1;
CREATETABLE orders (
order_id bigintNOTNULLDEFAULTnextval('order_id_seq'),
order_data text
);

自增ID(如MySQL的AUTO_INCREMENT)

CREATETABLE orders (
order_id INT AUTO_INCREMENT,
order_data TEXT,
PRIMARY KEY (order_id)
);

3. 時間戳+隨機數/序列

結合時間戳和隨機數(或自訂序列)生成訂單號,以保證唯一性和可讀性。可以透過添加業務相關的字首來增強業務相關性。

例項程式碼

以下是一個簡單的Java範例,展示了如何結合時間戳、隨機數和業務字首生成訂單號:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;
public classOrderNumberGenerator{
privatestaticfinal SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
privatestaticfinalint RANDOM_NUM_BOUND = 10000// 定義隨機數範圍
publicstatic String generateOrderNumber(String prefix){
// 生成時間戳部份
String timestamp = dateFormat.format(new Date());
// 生成隨機數部份
int randomNumber = ThreadLocalRandom.current().nextInt(RANDOM_NUM_BOUND);
// 組合成訂單號
return prefix + timestamp + String.format("d", randomNumber);
}
publicstaticvoidmain(String[] args){
// 範例:生成訂單號,假設業務字首為"ORD"
String orderNumber = generateOrderNumber("ORD");
System.out.println("Generated Order Number: " + orderNumber);
}
}




4. 分布式唯一ID生成方案

在分布式系統中,可以使用像Twitter的Snowflake演算法生成唯一的ID。Snowflake演算法可以生成一個64位元的長整數,其中包含時間戳、數據中心ID、機器ID和序列號,以確保生成的ID既唯一又有序。

Snowflake ID結構

Snowflake生成的64位元ID可以分為以下幾個部份:

  • 1位符號位: 由於整數的最高位是符號位,且64位元整數中的最高位為符號位,通常這一位為0,保證ID為正數。

  • 41位時間戳位: 記錄時間戳的差值(相對於某個固定的時間點),單位到毫秒。41位時間戳可以使用69年。

  • 10位數據中心ID和機器ID: 通常分為5位數據中心ID和5位機器ID,最多支持32個數據中心,每個數據中心最多支持32台機器。

  • 12位元序列號: 用來記錄同一毫秒內生成的不同ID,12位元序列號支持每個節點每毫秒產生4096個ID序號。

  • 以下是一個簡化的Snowflake演算法實作範例:

    public classSnowflakeIdGenerator{
    privatelong datacenterId; // 數據中心ID
    privatelong machineId; // 機器ID
    privatelong sequence = 0L// 序列號
    privatelong lastTimestamp = -1L// 上一次時間戳
    privatefinallong twepoch = 1288834974657L;
    privatefinallong datacenterIdBits = 5L;
    privatefinallong machineIdBits = 5L;
    privatefinallong maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    privatefinallong maxMachineId = -1L ^ (-1L << machineIdBits);
    privatefinallong sequenceBits = 12L;
    privatefinallong machineIdShift = sequenceBits;
    privatefinallong datacenterIdShift = sequenceBits + machineIdBits;
    privatefinallong timestampLeftShift = sequenceBits + machineIdBits + datacenterIdBits;
    privatefinallong sequenceMask = -1L ^ (-1L << sequenceBits);
    publicSnowflakeIdGenerator(long datacenterId, long machineId){
    if (datacenterId > maxDatacenterId || datacenterId < 0) {
    thrownew IllegalArgumentException("datacenterId can't be greater than %d or less than 0");
    }
    if (machineId > maxMachineId || machineId < 0) {
    thrownew IllegalArgumentException("machineId can't be greater than %d or less than 0");
    }
    this.datacenterId = datacenterId;
    this.machineId = machineId;
    }
    publicsynchronizedlongnextId(){
    long timestamp = System.currentTimeMillis();
    if (timestamp < lastTimestamp) {
    thrownew RuntimeException("Clock moved backwards. Refusing to generate id");
    }
    if (lastTimestamp == timestamp) {
    sequence = (sequence + 1) & sequenceMask;
    if (sequence == 0) {
    timestamp = tilNextMillis(lastTimestamp);
    }
    else {
    sequence = 0L;
    }
    lastTimestamp = timestamp;
    return ((timestamp - twepoch) << timestampLeftShift) |
    (datacenterId << datacenterIdShift) |
    (machineId << machineIdShift) |
    sequence;
    }
    privatelongtilNextMillis(long lastTimestamp){
    long timestamp = System.currentTimeMillis();
    while (timestamp <= lastTimestamp) {
    timestamp = System.currentTimeMillis();
    }
    return timestamp;
    }
    }








    下面是對這段程式碼的逐行解釋:

    類別定義和變量初始化

  • private long datacenterId ; 定義數據中心ID。

  • private long machineId ; 定義機器ID。

  • private long sequence = 0L ; 序列號,用於同一毫秒內生成多個ID時區分這些ID。

  • private long lastTimestamp = -1L ; 上一次生成ID的時間戳。

  • 以下是Snowflake演算法的一些關鍵參數:

  • private final long twepoch = 1288834974657L ; 系統的起始時間戳,這裏是Snowflake演算法的作者選擇的一個固定的時間點(2010-11-04 09:42:54.657 GMT)。

  • private final long datacenterIdBits = 5L ; 數據中心ID所占的位數。

  • private final long machineIdBits = 5L ; 機器ID所占的位數。

  • private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits) ; 數據中心ID的最大值,這裏透過位運算計算得出。

  • private final long maxMachineId = -1L ^ (-1L << machineIdBits) ; 機器ID的最大值,同樣透過位運算得出。

  • private final long sequenceBits = 12L ; 序列號占用的位數。

  • 以下是一些用於位運算的參數,用於計算最終的ID:

  • private final long machineIdShift = sequenceBits ; 機器ID的偏移位數。

  • private final long datacenterIdShift = sequenceBits + machineIdBits ; 數據中心ID的偏移位數。

  • private final long timestampLeftShift = sequenceBits + machineIdBits + datacenterIdBits ; 時間戳的偏移位數。

  • private final long sequenceMask = -1L ^ (-1L << sequenceBits) ; 用於保證序列號在指定範圍內迴圈。

  • 建構函式

    建構函式 SnowflakeIdGenerator(long datacenterId, long machineId) 接收數據中心ID和機器ID作為參數,並對這些參數進行校驗,確保它們在合法範圍內。

    ID生成方法

    public synchronized long nextId() 是生成ID的核心方法,使用synchronized保證執行緒安全。

  • 首先獲取當前時間戳。

  • 如果當前時間戳小於上一次生成ID的時間戳,丟擲異常,因為時鐘回撥會導致ID重復。

  • 如果當前時間戳等於上一次的時間戳(即同一毫秒內),透過增加序列號生成不同的ID;如果序列號溢位(超過最大值),則等待到下一個毫秒。

  • 如果當前時間戳大於上一次的時間戳,重設序列號為0。

  • 最後,將時間戳、數據中心ID、機器ID和序列號按照各自的偏移量左移,然後進行位或運算,組合成一個64位元的ID。

  • 輔助方法

    private long tilNextMillis(long lastTimestamp) 是一個輔助方法,用於在序列號溢位時等待直到下一個毫秒。

    來源:juejin.cn/post/7332708703399559209

    >>

    END

    精品資料,超贊福利,免費領

    微信掃碼/長按辨識 添加【技術交流群

    群內每天分享精品學習資料

    最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

    👇👇

    👇點選"閱讀原文",獲取更多資料(持續更新中