資料庫主鍵不能依賴自增 ID (分庫分表衝突、洩露業務量、單點寫入瓶頸)。常見方案:UUID (無序、長)、Snowflake (需機器號、時鐘回撥風險)。本文實作一個**單機高併發、有序、純數字**的 ID 生成器:毫秒時間戳 (13 位) + 4 位原子計數器 = 19 位 Long,支援單毫秒 10,000 次並發。
import java.util.concurrent.atomic.AtomicInteger;
public class IdGenerator {
/** 計數器:單毫秒內遞增,達 10000 歸零 */
private static final AtomicInteger COUNTER = new AtomicInteger(0);
private static final int MAX_COUNTER = 10000; // 4 位數上限
/** 生成唯一 ID
* @return 19 位 Long:時間戳(13位) + 計數器(4位補零)
*/
public static long nextId() {
long timestamp = System.currentTimeMillis(); // 13 位
int count = COUNTER.getAndIncrement();
if (count >= MAX_COUNTER) {
// 簡單自旋等待下一毫秒 (高併發下可改用 while 循環 CAS)
while (System.currentTimeMillis() == timestamp) {
Thread.yield();
}
COUNTER.set(0);
timestamp = System.currentTimeMillis();
count = COUNTER.getAndIncrement();
}
// 組合:timestamp * 10000 + count
return timestamp * 10000L + count;
}
// --- 測試主程式 ---
public static void main(String[] args) throws InterruptedException {
// 單執行緒測試
for (int i = 0; i < 5; i++) System.out.println(nextId());
// 多執行緒壓測:10 萬並發
int threads = 10;
int perThread = 10000;
Thread[] ts = new Thread[threads];
for (int i = 0; i < threads; i++) {
ts[i] = new Thread(() -> {
for (int j = 0; j < perThread; j++) nextId();
});
ts[i].start();
}
for (Thread t : ts) t.join();
System.out.println("10 萬並發生成完成,最後 ID:" + nextId());
}
}
LongAdder 或分段鎖)。timestamp * 10000 + count 比字串拼接快數數量級,且保持數值型態。| 方案 | 長度 | 有序性 | 單機 QPS | 依賴 | 適用場景 |
|---|---|---|---|---|---|
| UUID | 36 字元 | 無序 | 極高 | 無 | 非關鍵主鍵、離線生成 |
| Snowflake | 19 位 | 趨勢遞增 | 409 萬/毫秒 | 機器號、時鐘 | 分布式系統標準選擇 |
| 本文方案 | 19 位 | 嚴格遞增 | 1 萬/毫秒 | 無 | 單機/主從、簡易分庫分表 |
| 資料庫自增 | 不定 | 遞增 | 受限單點 | DB | 單體應用 |
timestamp < lastTimestamp 拋異常或等待。@Component,注入 Service 層使用。counter 當前值、生成耗時、等待次數到 Prometheus。此方案零依賴、程式碼極簡、效能優異,是中小型專案從自增 ID 遷移的最佳踏腳石。