MiniMall:分布式id生成器,雪花算法了解一下

分布式系統(tǒng)中畦攘,有一些需要使用全局唯一ID的場(chǎng)景霸妹,這種時(shí)候?yàn)榱朔乐笽D沖突可以使用36位的UUID,但是UUID有一些缺點(diǎn)知押,首先他相對(duì)比較長(zhǎng)叹螟,另外UUID一般是無序的。

有些時(shí)候我們希望能使用一種簡(jiǎn)單一些的ID台盯,并且希望ID能夠按照時(shí)間有序生成罢绽。而Twitter的SnowFlake算法解決了這種需求,并且該算法生成id的效率是極高的爷恳。

1. SnowFlake

SnowFlake算法產(chǎn)生的ID是一個(gè)64位的整型有缆,結(jié)構(gòu)如下(每一部分用“-”符號(hào)分隔):

0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000

1位標(biāo)識(shí)部分,在java中由于long的最高位是符號(hào)位温亲,正數(shù)是0棚壁,負(fù)數(shù)是1,一般生成的ID為正數(shù)栈虚,所以為0袖外;

41位時(shí)間戳部分,這個(gè)是毫秒級(jí)的時(shí)間魂务,一般實(shí)現(xiàn)上不會(huì)存儲(chǔ)當(dāng)前的時(shí)間戳曼验,而是時(shí)間戳的差值(當(dāng)前時(shí)間-固定的開始時(shí)間),這樣可以使產(chǎn)生的ID從更小值開始粘姜;41位的時(shí)間戳可以使用69年鬓照,(1L << 41) / (1000L 60 60 24 365) = 69年;

10位節(jié)點(diǎn)部分孤紧,Twitter實(shí)現(xiàn)中使用前5位作為數(shù)據(jù)中心標(biāo)識(shí)豺裆,后5位作為機(jī)器標(biāo)識(shí),可以部署1024個(gè)節(jié)點(diǎn)号显;

12位序列號(hào)部分臭猜,支持同一毫秒內(nèi)同一個(gè)節(jié)點(diǎn)可以生成4096個(gè)ID;

SnowFlake算法生成的ID大致上是按照時(shí)間遞增的押蚤,用在分布式系統(tǒng)中時(shí)蔑歌,需要注意數(shù)據(jù)中心標(biāo)識(shí)和機(jī)器標(biāo)識(shí)必須唯一,這樣就能保證每個(gè)節(jié)點(diǎn)生成的ID都是唯一的揽碘。

2. 算法實(shí)現(xiàn)

public class IdWorker {
    // 時(shí)間起始標(biāo)記點(diǎn)次屠,作為基準(zhǔn)园匹,一般取系統(tǒng)的最近時(shí)間(一旦確定不能變動(dòng))
    private final static long twepoch = 1288834974657L;
    // 機(jī)器標(biāo)識(shí)位數(shù)
    private final static long workerIdBits = 5L;
    // 數(shù)據(jù)中心標(biāo)識(shí)位數(shù)
    private final static long datacenterIdBits = 5L;
    // 機(jī)器ID最大值
    private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
    // 數(shù)據(jù)中心ID最大值
    private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    // 毫秒內(nèi)自增位
    private final static long sequenceBits = 12L;
    // 機(jī)器ID偏左移12位
    private final static long workerIdShift = sequenceBits;
    // 數(shù)據(jù)中心ID左移17位
    private final static long datacenterIdShift = sequenceBits + workerIdBits;
    // 時(shí)間毫秒左移22位
    private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
    /* 上次生產(chǎn)id時(shí)間戳 */
    private static long lastTimestamp = -1L;
    // 0,并發(fā)控制
    private long sequence = 0L;

    private final long workerId;
    // 數(shù)據(jù)標(biāo)識(shí)id部分
    private final long datacenterId;

    public IdWorker() {
        this.datacenterId = getDatacenterId(maxDatacenterId);
        this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
    }

    /**
     * @param workerId     工作機(jī)器ID
     * @param datacenterId 序列號(hào)
     */
    public IdWorker(long workerId, long datacenterId) {
        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;
    }

    /**
     * 獲取下一個(gè)ID
     *
     * @return
     */
    public synchronized String nextId() {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate uuid for %d milliseconds", lastTimestamp - timestamp));
        }

        if (lastTimestamp == timestamp) {
            // 當(dāng)前毫秒內(nèi)帅矗,則+1
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                // 當(dāng)前毫秒內(nèi)計(jì)數(shù)滿了偎肃,則等待下一秒
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        // ID偏移組合生成最終的ID,并返回ID
        long nextId = ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift) | sequence;

        return String.valueOf(nextId);
    }

    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = this.timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = this.timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * <p>
     * 獲取 maxWorkerId
     * </p>
     */
    protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
        StringBuffer mpid = new StringBuffer();
        mpid.append(datacenterId);
        String name = ManagementFactory.getRuntimeMXBean().getName();
        if (!name.isEmpty()) {
            /*
             * GET jvmPid
             */
            mpid.append(name.split("@")[0]);
        }
        /*
         * MAC + PID 的 hashcode 獲取16個(gè)低位
         */
        return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
    }

    /**
     * <p>
     * 數(shù)據(jù)標(biāo)識(shí)id部分
     * </p>
     */
    protected static long getDatacenterId(long maxDatacenterId) {
        long id = 0L;
        try {
            InetAddress ip = InetAddress.getLocalHost();
            NetworkInterface network = NetworkInterface.getByInetAddress(ip);
            if (network == null) {
                id = 1L;
            } else {
                byte[] mac = network.getHardwareAddress();
                id = ((0x000000FF & (long) mac[mac.length - 1])
                        | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                id = id % (maxDatacenterId + 1);
            }
        } catch (Exception e) {
            System.out.println(" getDatacenterId: " + e.getMessage());
        }
        return id;
    }
}

使用時(shí)浑此,只需要new IdWorker().next()即可累颂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凛俱,隨后出現(xiàn)的幾起案子紊馏,更是在濱河造成了極大的恐慌,老刑警劉巖蒲犬,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朱监,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡原叮,警方通過查閱死者的電腦和手機(jī)赫编,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奋隶,“玉大人擂送,你說我怎么就攤上這事∥ㄐ溃” “怎么了嘹吨?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)境氢。 經(jīng)常有香客問我蟀拷,道長(zhǎng),這世上最難降的妖魔是什么萍聊? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任问芬,我火速辦了婚禮,結(jié)果婚禮上寿桨,老公的妹妹穿的比我還像新娘愈诚。我一直安慰自己,他們只是感情好牛隅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酌泰,像睡著了一般媒佣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陵刹,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天默伍,我揣著相機(jī)與錄音,去河邊找鬼。 笑死也糊,一個(gè)胖子當(dāng)著我的面吹牛炼蹦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播狸剃,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼掐隐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了钞馁?” 一聲冷哼從身側(cè)響起虑省,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎僧凰,沒想到半個(gè)月后探颈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡训措,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年伪节,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绩鸣。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡怀大,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出全闷,到底是詐尸還是另有隱情叉寂,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布总珠,位于F島的核電站屏鳍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏局服。R本人自食惡果不足惜钓瞭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望淫奔。 院中可真熱鬧山涡,春花似錦、人聲如沸唆迁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽唐责。三九已至鳞溉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鼠哥,已是汗流浹背熟菲。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工看政, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抄罕。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓允蚣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親呆贿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嚷兔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355