Java實現數值型ID生成器

yueking 8年前發布 | 17K 次閱讀 Java Java開發

基于推ter ID 生成策略

  1. 每秒能生成幾十萬條 ID

    ID 生成要以一種非協作的(uncoordinated)的方式進行,例如不能利用一個全局的原子變量。

  2. ID 大致有序,就是說生成時間相近的兩個ID,它們的值也應當相近

    按 ID 排序后滿足 k-sorted 條件。如果序列 A 要滿足 k-sorted,當且僅當對于任意的 p, q,如果 1 <= p <= q - k (1 <= p <= q <= n),則有 A[p] <= A[q]。換句話說,如果元素 p 排在 q 前面,且相差至少 k 個位置,那么 p 必然小于或等于 q。如果ID序列滿足這個條件,要獲取第 r 條ID之后的記錄,只要從第 r - k 條開始查找即可。

  3. 解決方案

    推ter 解決這兩個問題的方案非常簡單高效:每一個 ID 都是 64 位數字,由時間戳、節點號和序列編號組成。其中序列編號是每個節點本地生成的序號,而節點號則由 ZooKeeper 維護。

  4. 實現代碼

/**

  • @author zhujuan
  • From: https://github.com/推ter/snowflake
  • An object that generates IDs.
  • This is broken into a separate class in case
  • we ever want to support multiple worker threads
  • per process */ public class IdWorker {

    protected static final Logger LOG = LoggerFactory.getLogger(IdWorker.class);

    private long workerId; private long datacenterId; private long sequence = 0L;

    private long workerIdBits = 5L; private long datacenterIdBits = 5L; private long maxWorkerId = -1L ^ (-1L << workerIdBits); private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); private long sequenceBits = 12L;

    private long workerIdShift = sequenceBits; private long datacenterIdShift = sequenceBits + workerIdBits; private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long lastTimestamp = -1L;

    public IdWorker(long workerId, long datacenterId) {

     // sanity check for workerId
     if (workerId > maxWorkerId || workerId < 0) {
         throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
     }
     if (datacenterId > maxDatacenterId || datacenterId < 0) {
         throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
     }
     this.workerId = workerId;
     this.datacenterId = datacenterId;
     LOG.info(String.format("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d", timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId));
    

    }

    public synchronized long nextId() {

     long timestamp = timeGen();
    
     if (timestamp < lastTimestamp) {
         LOG.error(String.format("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp));
         throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
     }
    
     if (lastTimestamp == timestamp) {
         sequence = (sequence + 1) & sequenceMask;
         if (sequence == 0) {
             timestamp = tilNextMillis(lastTimestamp);
         }
     } else {
         sequence = 0L;
     }
    
     lastTimestamp = timestamp;
    
     return (timestamp << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
    

    }

}</code></pre>

基于Instagram 的ID生成策略

  1. 生成的ID可以按時間排序
    與推ter需求大致相同

  2. ID最好是64bit的
    為了索引更小且方便存儲在像Redis這樣的系統中

  3. 按照某種用戶標識進行邏輯分片

  4. 解決方案

    • 41bits 存儲毫秒格式的時間

    • 10bits 表示邏輯分片ID
      原方案是13bits(最多8192個邏輯分片),這里為了與基于推ter的策略保持大致一致,改成了10bits

    • 12bits 存儲自增序列值
      原方案是10bits(最多1024個序列),這里為了與基于推ter的策略保持大致一致,改成了12bits(最多4096個序列)

  5. 代碼實現

/**

  • @author Mr_Shang
  • @version 1.0 / public class InstaIdGenerator {

    protected static final Logger LOG = LoggerFactory.getLogger(IdWorker.class);

    /**

    • 時間戳的位數,實際占41位,最高位保持為0,保證long值為正數 */ private int timestampBitCount = 42;

    /**

    • 邏輯分片位數 */ private int regionBitCount = 10;

    /**

    • 邏輯分片的最大數量 */ private int regionModelVal = 1 << regionBitCount;

    /**

    • 序列位數 */ private int sequenceBitCount = 12;

    /**

    • 總的位數 */ private int totalBitCount = timestampBitCount + regionBitCount + sequenceBitCount;

    /**

    • 當前序列值 */ private long sequence = 0;

    /**

    • 最后一次請求時間戳 */ private long lastTimestamp = -1L;

    /**

    • 序列的位板 */ private long sequenceMask = -1L ^ (-1L << sequenceBitCount);

    /**

    • 最后一次請求用戶標識 */ private long lastTag=1L;

    public InstaIdGenerator() {

    }

    public InstaIdGenerator(long seq) { if (seq < 0) { seq = 0; } this.sequence = seq; }

    public synchronized long nextId(long tag) { long timestamp = timeGen();

    if(tag<0){ tag=-tag; }

    if (timestamp < lastTimestamp) { LOG.error(String.format("clock is moving backwards. Rejecting requests until %d.", lastTimestamp)); throw new RuntimeException(String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); }

    if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; }

    if(tag==lastTag){ sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } lastTag=tag;

    lastTimestamp = timestamp;

    return (timestamp << (totalBitCount - timestampBitCount)) | ((tag % regionModelVal) << (totalBitCount - timestampBitCount - regionBitCount)) | sequence; } }</code></pre>

    參考內容

     

    來自:http://www.jianshu.com/p/d76e86fdf045

     

 本文由用戶 yueking 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!