Java實現(xiàn)數(shù)值型ID生成器


基于Twitter ID 生成策略

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

ID 生成要以一種非協(xié)作的(uncoordinated)的方式進(jìn)行饥臂,例如不能利用一個全局的原子變量漩符。

  1. ID 大致有序毁兆,就是說生成時間相近的兩個ID意蛀,它們的值也應(yīng)當(dāng)相近

    按 ID 排序后滿足 k-sorted 條件傍妒。如果序列 A 要滿足 k-sorted留瞳,當(dāng)且僅當(dāng)對于任意的 p, q悯嗓,如果 1 <= p <= q - k (1 <= p <= q <= n)件舵,則有 A[p] <= A[q]。換句話說脯厨,如果元素 p 排在 q 前面铅祸,且相差至少 k 個位置,那么 p 必然小于或等于 q合武。如果ID序列滿足這個條件临梗,要獲取第 r 條ID之后的記錄,只要從第 r - k 條開始查找即可稼跳。

  2. 解決方案
    Twitter 解決這兩個問題的方案非常簡單高效:每一個 ID 都是 64 位數(shù)字盟庞,由時間戳、節(jié)點號和序列編號組成汤善。其中序列編號是每個節(jié)點本地生成的序號什猖,而節(jié)點號則由 ZooKeeper 維護(hù)。

  3. 實現(xiàn)代碼

/**
 * @author zhujuan
 * From: https://github.com/twitter/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;
    }
 
}

基于Instagram 的ID生成策略

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

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

  3. 按照某種用戶標(biāo)識進(jìn)行邏輯分片

  4. 解決方案

  • 41bits 存儲毫秒格式的時間
  • 10bits 表示邏輯分片ID
    原方案是13bits(最多8192個邏輯分片),這里為了與基于Twitter的策略保持大致一致红淡,改成了10bits
  • 12bits 存儲自增序列值
    原方案是10bits(最多1024個序列),這里為了與基于Twitter的策略保持大致一致不狮,改成了12bits(最多4096個序列)
  1. 代碼實現(xiàn)
/**
 * 
 * @author Mr_Shang
 * 
 * @version 1.0
 *
 */
public class InstaIdGenerator {

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

 /**
  * 時間戳的位數(shù),實際占41位在旱,最高位保持為0摇零,保證long值為正數(shù)
  */
 private int timestampBitCount = 42;

 /**
  * 邏輯分片位數(shù)
  */
 private int regionBitCount = 10;

 /**
  * 邏輯分片的最大數(shù)量
  */
 private int regionModelVal = 1 << regionBitCount;

 /**
  * 序列位數(shù)
  */
 private int sequenceBitCount = 12;

 /**
  * 總的位數(shù)
  */
 private int totalBitCount = timestampBitCount + regionBitCount + sequenceBitCount;

 /**
  * 當(dāng)前序列值
  */
 private long sequence = 0;

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

 /**
  * 序列的位板
  */
 private long sequenceMask = -1L ^ (-1L << sequenceBitCount);
 
 /**
  * 最后一次請求用戶標(biāo)識
  */
 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;
 }
}

完整源碼地址

項目已經(jīng)托管到github,飛機在這里? 數(shù)值型id生成器

參考內(nèi)容

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市桶蝎,隨后出現(xiàn)的幾起案子遂黍,更是在濱河造成了極大的恐慌终佛,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雾家,死亡現(xiàn)場離奇詭異铃彰,居然都是意外死亡,警方通過查閱死者的電腦和手機芯咧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進(jìn)店門牙捉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人敬飒,你說我怎么就攤上這事邪铲。” “怎么了无拗?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵带到,是天一觀的道長。 經(jīng)常有香客問我英染,道長揽惹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任四康,我火速辦了婚禮搪搏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闪金。我一直安慰自己疯溺,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布哎垦。 她就那樣靜靜地躺著囱嫩,像睡著了一般。 火紅的嫁衣襯著肌膚如雪漏设。 梳的紋絲不亂的頭發(fā)上挠说,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天,我揣著相機與錄音愿题,去河邊找鬼损俭。 笑死,一個胖子當(dāng)著我的面吹牛潘酗,可吹牛的內(nèi)容都是我干的杆兵。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼仔夺,長吁一口氣:“原來是場噩夢啊……” “哼琐脏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤日裙,失蹤者是張志新(化名)和其女友劉穎吹艇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體昂拂,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡受神,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了格侯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鼻听。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖联四,靈堂內(nèi)的尸體忽然破棺而出撑碴,到底是詐尸還是另有隱情,我是刑警寧澤朝墩,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布醉拓,位于F島的核電站,受9級特大地震影響收苏,放射性物質(zhì)發(fā)生泄漏亿卤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一倒戏、第九天 我趴在偏房一處隱蔽的房頂上張望怠噪。 院中可真熱鬧恐似,春花似錦杜跷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至双藕,卻和暖如春淑趾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背忧陪。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工扣泊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人嘶摊。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓延蟹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叶堆。 傳聞我的和親對象是個殘疾皇子阱飘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容