首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

遊戲伺服器全局唯一 ID 生成方案對比:UUID、Redis、Flicker、Snowflake 及自定義改進版

     編輯:Java 綜合教學

分散式遊戲伺服器全局唯一 ID 生成技術選型指南

在高併發遊戲伺服器中,異步持久化、分庫分表、跨服合服等場景,要求在寫入資料庫前生成全局唯一 ID。本文對比四大主流方案並提供生產級改進版 Snowflake 實現。

方案對比總覽

方案優勢劣勢適用場景
Java UUID本地生成、無依賴、水平擴展極強128bit 佔用大、無序、索引效能極差、無時間語義非關鍵路徑、日誌追蹤 ID、臨時標識
Redis INCR原子遞增、部署簡單、支援步長、叢集高可用高併發單點瓶頸、網絡開銷、依賴外部服務中小型遊戲、單服併發 < 5萬 QPS
Flicker 方案資料庫自增、強一致單點寫入、架構複雜、擴展困難、不推薦極少見,歷史參考
Snowflake高效能、低延遲、趨勢遞增、無中心依賴需獨立部署/維護、時鐘回撥風險、機器 ID 分配管理大型分散式遊戲、高併發核心業務

Snowflake 算法原理

64-bit Long 結構劃分:

| 1bit 符號位 | 41bit 時間戳(毫秒) | 10bit 機器ID | 12bit 序列號 |
|    0       |   41 bits        |  10 bits    |  12 bits    |
  • 時間戳:41bit 支援 69 年 (2^41 ms),相對起始紀元
  • 機器 ID:10bit 支援 1024 個節點
  • 序列號:12bit 單毫秒 4096 個 ID,單機理論 400 萬 QPS

生產級改進版:支援區域/機房、時鐘回撥保護、序列號隨機化

public class GameIdGenerator {
    // ========== 位分配配置 ==========
    private static final long EPOCH = 1609459200000L; // 2021-01-01 00:00:00 GMT
    
    private static final long REGION_ID_BITS = 3L;    // 區域/機房:8 個
    private static final long WORKER_ID_BITS = 10L;   // 機器 ID:1024 個
    private static final long SEQUENCE_BITS = 10L;    // 序列號:1024/毫秒
    
    private static final long MAX_REGION_ID = ~(-1L << REGION_ID_BITS);
    private static final long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    private static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
    
    private static final long WORKER_ID_SHIFT = SEQUENCE_BITS;
    private static final long REGION_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + REGION_ID_BITS;
    
    // ========== 運行狀態 ==========
    private long lastTimestamp = -1L;
    private long sequence = 0L;
    private final long workerId;
    private final long regionId;
    private final SecureRandom random = new SecureRandom();

    // ========== 建構子 ==========
    public GameIdGenerator(long workerId, long regionId) {
        if (workerId < 0 || workerId > MAX_WORKER_ID) {
            throw new IllegalArgumentException("workerId 超出範圍 [0, " + MAX_WORKER_ID + "]");
        }
        if (regionId < 0 || regionId > MAX_REGION_ID) {
            throw new IllegalArgumentException("regionId 超出範圍 [0, " + MAX_REGION_ID + "]");
        }
        this.workerId = workerId;
        this.regionId = regionId;
    }

    // 僅機器 ID,區域預設 0
    public GameIdGenerator(long workerId) {
        this(workerId, 0);
    }

    // ========== 核心生成邏輯 ==========
    public synchronized long nextId() {
        long timestamp = currentTimeMillis();
        
        // 時鐘回撥檢測
        if (timestamp < lastTimestamp) {
            long offset = lastTimestamp - timestamp;
            if (offset > 5) { // 超過 5ms 視為嚴重回撥,拋異常或等待
                throw new IllegalStateException("時鐘回撥 " + offset + "ms,拒絕生成 ID");
            }
            // 輕微回撥:自旋等待
            timestamp = waitNextMillis(lastTimestamp);
        }
        
        // 同毫秒內序列號遞增
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) { // 序列號耗盡
                timestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            // 新毫秒:序列號重置為隨機值(0-9),改善尾數分布均勻性
            sequence = random.nextInt(10);
        }
        
        lastTimestamp = timestamp;
        
        // 組裝 ID
        return ((timestamp - EPOCH) << TIMESTAMP_SHIFT)
                | (regionId << REGION_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }

    // ========== 輔助方法 ==========
    private long waitNextMillis(long lastTimestamp) {
        long timestamp = currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = currentTimeMillis();
        }
        return timestamp;
    }

    protected long currentTimeMillis() {
        return System.currentTimeMillis();
    }

    // ========== 解析方法(除錯/監控用) ==========
    public static IdComponents parse(long id) {
        long sequence = id & SEQUENCE_MASK;
        long workerId = (id >> WORKER_ID_SHIFT) & MAX_WORKER_ID;
        long regionId = (id >> REGION_ID_SHIFT) & MAX_REGION_ID;
        long timestamp = (id >> TIMESTAMP_SHIFT) + EPOCH;
        return new IdComponents(timestamp, regionId, workerId, sequence);
    }

    public record IdComponents(long timestamp, long regionId, long workerId, long sequence) {}
}

關鍵改進點解析

部署與運維要點

  1. 機器 ID 分配:啟動腳本從 Zookeeper/Etcd/Consul 獲取,或 Kubernetes Downward API 注入 Pod 序號
  2. 時間同步:所有節點強制 NTP 同步,ntpd -g -x 禁止大幅回撥,監控時間偏移 < 10ms
  3. 單調性監控:埋點監控 ID 生成速率、重複率、回撥事件
  4. 擴展性:10bit Worker 支援 1024 節點,3bit Region 支援 8 機房,足夠大多數遊戲架構

技術選型決策樹

開始
  │
  ├─→ 業務量 < 1萬 QPS、團隊無運維能力? → Redis INCR (簡單可靠)
  │
  ├─→ 需要趨勢遞增、分庫分表、跨機房? → Snowflake 改進版
  │
  ├─→ 極高併發(>100萬 QPS)、多語言異構? → 獨立 ID 服務 + gRPC/HTTP
  │
  └─→ 臨時/非關鍵/離線生成? → UUID v1 (時間有序) 或 UUID v4

總結:手機遊戲/中小項目首選 Redis INCR(簡單、穩定、夠用);大型世界服/分散式架構必選 Snowflake 改進版(高效能、有序、無中心依賴)。核心在於根據業務規模、團隊能力、架構複雜度做權衡,而非盲目追求「高大上」。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
改進點問題解決方案
區域/機房位跨機房合服、多區域部署 ID 隔離增加 3bit regionId,支援 8 個機房
時鐘回撥保護NTP 同步導致時間倒退,產生重複 ID檢測回撥 > 5ms 拋異常,<=5ms 自旋等待
序列號隨機化每毫秒序列號歸 0,導致 ID 尾數分布不均(取模分表不均)跨毫秒重置為 0-9 隨機數,改善分布
SecureRandom預設 Random 線程安全但效能低ThreadLocalRandom 或 SecureRandom 實例化一次