<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官:高并發(fā)下,如何保證分布式唯一全局 ID 生成?

          共 4116字,需瀏覽 9分鐘

           ·

          2022-03-15 15:23

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享?
          關(guān)注


          閱讀本文大概需要 8.5 分鐘。
          來自:blog.csdn.net/LookForDream_/article/details/109355335

          前言

          系統(tǒng)唯一ID是我們在設(shè)計(jì)一個(gè)系統(tǒng)的時(shí)候常常會(huì)遇見的問題,也常常為這個(gè)問題而糾結(jié)。
          這篇文章就是給各位看官提供一個(gè)生成分布式唯一全局id生成方案的思路,希望能幫助到大家。
          不足之處,請多多指教?。?/section>

          問題

          為什么需要分布式全局唯一ID以及分布式ID的業(yè)務(wù)需求

          在復(fù)雜分布式系統(tǒng)中,往往需要對大量的數(shù)據(jù)和消息進(jìn)行唯一標(biāo)識,如在美團(tuán)點(diǎn)評的金融、支付、餐飲、酒店
          貓眼電影等產(chǎn)品的系統(tǒng)中數(shù)據(jù)逐漸增長,對數(shù)據(jù)庫分庫分表后需要有一個(gè)唯一ID來標(biāo)識一條數(shù)據(jù)或信息;
          特別Ian的訂單、騎手、優(yōu)惠券都需要有唯一ID做標(biāo)識
          此時(shí)一個(gè)能夠生成全局唯一ID的系統(tǒng)是非常必要的

          ID生成規(guī)則部分硬性要求

          • 全局唯一
          • 趨勢遞增
            • 在MySQL的InnoDB引擎中使用的是聚集索引,由于多數(shù)RDBMS使用Btree的數(shù)據(jù)結(jié)構(gòu)來存儲索引,在主鍵的選擇上面我們應(yīng)該盡量使用有序的主鍵保證寫入性能
          • 單調(diào)遞增
            • 保證下一個(gè)ID一定大于上一個(gè)ID,例如事務(wù)版本號、IM增量消息、排序等特殊需求
          • 信息安全
            • 如果ID是連續(xù),惡意用戶的爬取工作就非常容易做了,直接按照順序下載指定URL即可,如果是訂單號就危險(xiǎn)了,競爭對手可以直接知道我們一天的單量,所以在一些應(yīng)用場景下,需要ID無規(guī)則不規(guī)則,讓競爭對手不好猜
          • 含時(shí)間戳
            • 一樣能夠快速在開發(fā)中了解這個(gè)分布式ID什么時(shí)候生成的

          ID號生成系統(tǒng)的可用性要求

          • 高可用
            • 發(fā)布一個(gè)獲取分布式ID請求,服務(wù)器就要保證99.999%的情況下給我創(chuàng)建一個(gè)唯一分布式ID
          • 低延遲
            • 發(fā)一個(gè)獲取分布式ID的請求,服務(wù)器就要快,極速
          • 高QPS
            • 例如并發(fā)一口氣10萬個(gè)創(chuàng)建分布式ID請求同時(shí)殺過來,服務(wù)器要頂?shù)米∏乙幌伦映晒?chuàng)建10萬個(gè)分布式ID

          一般通用解決方案

          UUID

          UUID.randomUUID(), UUID的標(biāo)準(zhǔn)型包含32個(gè)16進(jìn)制數(shù)字,以連字號分為五段,形式為 8-4-4-4-12的36個(gè)字符,性能非常高,本地生成,沒有網(wǎng)絡(luò)消耗。

          存在問題

          入數(shù)據(jù)庫性能差,因?yàn)閁UID是無序的
          無序,無法預(yù)測他的生成順序,不能生成遞增有序的數(shù)字
          首先分布式id一般都會(huì)作為逐漸,但是按照mysql官方推薦主鍵盡量越短越好,UUID每一個(gè)都很長,所以不是很推薦。
          主鍵,ID作為主鍵時(shí),在特定的環(huán)境下會(huì)存在一些問題
          比如做DB主鍵的場景下,UUID就非常不適用MySQL官方有明確的說明
          索引,B+樹索引的分裂
          既然分布式ID是主鍵,然后主鍵是包含索引的,而mysql的索引是通過B+樹來實(shí)現(xiàn)的,每一次新的UUID數(shù)據(jù)的插入,為了查詢的優(yōu)化,都會(huì)對索引底層的B+樹進(jìn)行修改,因?yàn)閁UID數(shù)據(jù)是無序的,所以每一次UUID數(shù)據(jù)的插入都會(huì)對主鍵的B+樹進(jìn)行很大的修改,這一點(diǎn)很不好,插入完全無序,不但會(huì)導(dǎo)致一些中間節(jié)點(diǎn)產(chǎn)生分裂,也會(huì)白白創(chuàng)造出很多不飽和的節(jié)點(diǎn),這樣大大降低了數(shù)據(jù)庫插入的性能。
          UUID只能保證全局唯一性,不滿足后面的趨勢遞增,單調(diào)遞增

          數(shù)據(jù)庫自增主鍵

          單機(jī)
          在分布式里面,數(shù)據(jù)庫的自增ID機(jī)制的主要原理是:數(shù)據(jù)庫自增ID和mysql數(shù)據(jù)庫的replace into實(shí)現(xiàn)的,這里的replace into跟insert功能 類似,不同點(diǎn)在于:replace into首先嘗試插入數(shù)據(jù)列表中,如果發(fā)現(xiàn)表中已經(jīng)有此行數(shù)據(jù)(根據(jù)主鍵或唯一索引判斷)則先刪除,在插入,否則直接插入新數(shù)據(jù)。
          REPLACE INTO的含義是插入一條記錄,如果表中唯一索引的值遇到?jīng)_突,則替換老數(shù)據(jù)
          REPLACE?into?t_test(stub)?values('b');
          select?LAST_INSERT_ID();
          我們每次插入的時(shí)候,發(fā)現(xiàn)都會(huì)把原來的數(shù)據(jù)給替換,并且ID也會(huì)增加
          這就滿足了
          • 遞增性
          • 單調(diào)性
          • 唯一性
          在分布式情況下,并且并發(fā)量不多的情況,可以使用這種方案來解決,獲得一個(gè)全局的唯一ID
          集群分布式集群
          那數(shù)據(jù)庫自增ID機(jī)制適合做分布式ID嗎?答案是不太適合
          系統(tǒng)水平擴(kuò)展比較困難,比如定義好步長和機(jī)器臺數(shù)之后,如果要添加機(jī)器該怎么辦,假設(shè)現(xiàn)在有一臺機(jī)器發(fā)號是:1,2,3,4,5,(步長是1),這個(gè)時(shí)候需要擴(kuò)容機(jī)器一臺,可以這樣做:把第二胎機(jī)器的初始值設(shè)置得比第一臺超過很多,貌似還好,但是假設(shè)線上如果有100臺機(jī)器,這個(gè)時(shí)候擴(kuò)容要怎么做,簡直是噩夢,所以系統(tǒng)水平擴(kuò)展方案復(fù)雜難以實(shí)現(xiàn)。
          數(shù)據(jù)庫壓力還是很大,每次獲取ID都得讀寫一次數(shù)據(jù)庫,非常影響性能,不符合分布式ID里面的延遲低和高QPS的規(guī)則(在高并發(fā)下,如果都去數(shù)據(jù)庫里面獲取ID,那是非常影響性能的)

          基于Redis生成全局ID策略

          單機(jī)版
          因?yàn)镽edis是單線程,天生保證原子性,可以使用原子操作INCR和INCRBY來實(shí)現(xiàn)
          INCRBY:設(shè)置增長步長
          集群分布式
          注意:在Redis集群情況下,同樣和MySQL一樣需要設(shè)置不同的增長步長,同時(shí)key一定要設(shè)置有效期,可以使用Redis集群來獲取更高的吞吐量。
          假設(shè)一個(gè)集群中有5臺Redis,可以初始化每臺Redis的值分別是 1,2,3,4,5 , 然后設(shè)置步長都是5
          各個(gè)Redis生成的ID為:
          A:1 6 11 16 21
          B:2 7 12 17 22
          C:3 8 13 18 23
          D:4 9 14 19 24
          E:5 10 15 20 25
          但是存在的問題是,就是Redis集群的維護(hù)和保養(yǎng)比較麻煩,配置麻煩。因?yàn)橐O(shè)置單點(diǎn)故障,哨兵值守
          但是主要是的問題就是,為了一個(gè)ID,卻需要引入整個(gè)Redis集群,有種殺雞焉用牛刀的感覺

          雪花算法

          是什么

          Twitter的分布式自增ID算法,Snowflake
          最初Twitter把存儲系統(tǒng)從MySQL遷移到Cassandra(由Facebook開發(fā)一套開源分布式NoSQL數(shù)據(jù)庫系統(tǒng))因?yàn)镃assandra沒有順序ID生成機(jī)制,所有開發(fā)了這樣一套全局唯一ID生成服務(wù)。
          Twitter的分布式雪花算法SnowFlake,經(jīng)測試SnowFlake每秒可以產(chǎn)生26萬個(gè)自增可排序的ID
          • twitter的SnowFlake生成ID能夠按照時(shí)間有序生成
          • SnowFlake算法生成ID的結(jié)果是一個(gè)64Bit大小的整數(shù),為一個(gè)Long型(轉(zhuǎn)換成字符串后長度最多19)
          • 分布式系統(tǒng)內(nèi)不會(huì)產(chǎn)生ID碰撞(由datacenter 和 workerID做區(qū)分)并且效率較高
          分布式系統(tǒng)中,有一些需要全局唯一ID的場景,生成ID的基本要求
          • 在分布式環(huán)境下,必須全局唯一性
          • 一般都需要單調(diào)遞增,因?yàn)橐话阄ㄒ籌D都會(huì)存在數(shù)據(jù)庫,而InnoDB的特性就是將內(nèi)容存儲在主鍵索引上的葉子節(jié)點(diǎn),而且是從左往右遞增的,所有考慮到數(shù)據(jù)庫性能,一般生成ID也最好是單調(diào)遞增的。為了防止ID沖突可以使用36位UUID,但是UUID有一些缺點(diǎn),首先是它相對比較長,并且另外UUID一般是無序的
          • 可能還會(huì)需要無規(guī)則,因?yàn)槿绻褂梦ㄒ籌D作為訂單號這種,為了不讓別人知道一天的訂單量多少,就需要這種規(guī)則

          結(jié)構(gòu)

          雪花算法的幾個(gè)核心組成部分
          在Java中64bit的證書是long類型,所以在SnowFlake算法生成的ID就是long類存儲的
          第一部分
          二進(jìn)制中最高位是符號位,1表示負(fù)數(shù),0表示正數(shù)。生成的ID一般都是用整數(shù),所以最高位固定為0。
          第二部分
          第二部分是41bit時(shí)間戳位,用來記錄時(shí)間戳,毫秒級
          41位可以表示 2^41 -1 個(gè)數(shù)字
          如果只用來表示正整數(shù),可以表示的范圍是:0 - 2^41 -1,減1是因?yàn)榭梢员硎镜臄?shù)值范圍是從0開始計(jì)算的,而不是從1。
          也就是說41位可以表示 2^41 - 1 毫秒的值,轉(zhuǎn)換成單位年則是 69.73年
          第三部分
          第三部分為工作機(jī)器ID,10Bit用來記錄工作機(jī)器ID
          可以部署在2^10 = 1024個(gè)節(jié)點(diǎn),包括5位 datacenterId(數(shù)據(jù)中心,機(jī)房) 和 5位 workerID(機(jī)器碼)
          5位可以表示的最大正整數(shù)是 2 ^ 5 = 31個(gè)數(shù)字,來表示不同的數(shù)據(jù)中心 和 機(jī)器碼
          第四部分
          12位bit可以用來表示的正整數(shù)是 2^12 = 4095,即可以用0 1 2 … 4094 來表示同一個(gè)機(jī)器同一個(gè)時(shí)間戳內(nèi)產(chǎn)生的4095個(gè)ID序號。

          SnowFlake可以保證

          所有生成的ID按時(shí)間趨勢遞增
          整個(gè)分布式系統(tǒng)內(nèi)不會(huì)產(chǎn)生重復(fù)ID,因?yàn)橛衐atacenterId 和 workerId來做區(qū)分

          實(shí)現(xiàn)

          雪花算法是由scala算法編寫的,有人使用java實(shí)現(xiàn),github地址
          https://github.com/beyondfengyu/SnowFlake/blob/master/SnowFlake.java
          /**
          ?*?twitter的snowflake算法?--?java實(shí)現(xiàn)
          ?*?
          ?*?@author?beyond
          ?*/

          public?class?SnowFlake?{

          ????/**
          ?????*?起始的時(shí)間戳
          ?????*/

          ????private?final?static?long?START_STMP?=?1480166465631L;

          ????/**
          ?????*?每一部分占用的位數(shù)
          ?????*/

          ????private?final?static?long?SEQUENCE_BIT?=?12;?//序列號占用的位數(shù)
          ????private?final?static?long?MACHINE_BIT?=?5;???//機(jī)器標(biāo)識占用的位數(shù)
          ????private?final?static?long?DATACENTER_BIT?=?5;//數(shù)據(jù)中心占用的位數(shù)

          ????/**
          ?????*?每一部分的最大值
          ?????*/

          ????private?final?static?long?MAX_DATACENTER_NUM?=?-1L?^?(-1L?<????private?final?static?long?MAX_MACHINE_NUM?=?-1L?^?(-1L?<????private?final?static?long?MAX_SEQUENCE?=?-1L?^?(-1L?<
          ????/**
          ?????*?每一部分向左的位移
          ?????*/

          ????private?final?static?long?MACHINE_LEFT?=?SEQUENCE_BIT;
          ????private?final?static?long?DATACENTER_LEFT?=?SEQUENCE_BIT?+?MACHINE_BIT;
          ????private?final?static?long?TIMESTMP_LEFT?=?DATACENTER_LEFT?+?DATACENTER_BIT;

          ????private?long?datacenterId;??//數(shù)據(jù)中心
          ????private?long?machineId;?????//機(jī)器標(biāo)識
          ????private?long?sequence?=?0L;?//序列號
          ????private?long?lastStmp?=?-1L;//上一次時(shí)間戳

          ????public?SnowFlake(long?datacenterId,?long?machineId)?{
          ????????if?(datacenterId?>?MAX_DATACENTER_NUM?||?datacenterId?0)?{
          ????????????throw?new?IllegalArgumentException("datacenterId?can't?be?greater?than?MAX_DATACENTER_NUM?or?less?than?0");
          ????????}
          ????????if?(machineId?>?MAX_MACHINE_NUM?||?machineId?0)?{
          ????????????throw?new?IllegalArgumentException("machineId?can't?be?greater?than?MAX_MACHINE_NUM?or?less?than?0");
          ????????}
          ????????this.datacenterId?=?datacenterId;
          ????????this.machineId?=?machineId;
          ????}

          ????/**
          ?????*?產(chǎn)生下一個(gè)ID
          ?????*
          ?????*?@return
          ?????*/

          ????public?synchronized?long?nextId()?{
          ????????long?currStmp?=?getNewstmp();
          ????????if?(currStmp?????????????throw?new?RuntimeException("Clock?moved?backwards.??Refusing?to?generate?id");
          ????????}

          ????????if?(currStmp?==?lastStmp)?{
          ????????????//相同毫秒內(nèi),序列號自增
          ????????????sequence?=?(sequence?+?1)?&?MAX_SEQUENCE;
          ????????????//同一毫秒的序列數(shù)已經(jīng)達(dá)到最大
          ????????????if?(sequence?==?0L)?{
          ????????????????currStmp?=?getNextMill();
          ????????????}
          ????????}?else?{
          ????????????//不同毫秒內(nèi),序列號置為0
          ????????????sequence?=?0L;
          ????????}

          ????????lastStmp?=?currStmp;

          ????????return?(currStmp?-?START_STMP)?<//時(shí)間戳部分
          ????????????????|?datacenterId?<//數(shù)據(jù)中心部分
          ????????????????|?machineId?<//機(jī)器標(biāo)識部分
          ????????????????|?sequence;?????????????????????????????//序列號部分
          ????}

          ????private?long?getNextMill()?{
          ????????long?mill?=?getNewstmp();
          ????????while?(mill?<=?lastStmp)?{
          ????????????mill?=?getNewstmp();
          ????????}
          ????????return?mill;
          ????}

          ????private?long?getNewstmp()?{
          ????????return?System.currentTimeMillis();
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????SnowFlake?snowFlake?=?new?SnowFlake(2,?3);

          ????????for?(int?i?=?0;?i?1?<12);?i++)?{
          ????????????System.out.println(snowFlake.nextId());
          ????????}

          ????}
          }

          工程落地經(jīng)驗(yàn)

          hutools工具包
          地址:https://github.com/looly/hutool
          SpringBoot整合雪花算法
          引入hutool工具類
          <dependency>
          ????<groupId>cn.hutoolgroupId>
          ????<artifactId>hutool-allartifactId>
          ????<version>5.3.1version>
          dependency>
          整合
          /**
          ?*?雪花算法
          ?*
          ?*?@author:?陌溪
          ?*/

          public?class?SnowFlakeDemo?{
          ????private?long?workerId?=?0;
          ????private?long?datacenterId?=?1;
          ????private?Snowflake?snowFlake?=?IdUtil.createSnowflake(workerId,?datacenterId);

          ????@PostConstruct
          ????public?void?init()?{
          ????????try?{
          ????????????//?將網(wǎng)絡(luò)ip轉(zhuǎn)換成long
          ????????????workerId?=?NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
          ????????}?catch?(Exception?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}

          ????/**
          ?????*?獲取雪花ID
          ?????*?@return
          ?????*/

          ????public?synchronized?long?snowflakeId()?{
          ????????return?this.snowFlake.nextId();
          ????}

          ????public?synchronized?long?snowflakeId(long?workerId,?long?datacenterId)?{
          ????????Snowflake?snowflake?=?IdUtil.createSnowflake(workerId,?datacenterId);
          ????????return?snowflake.nextId();
          ????}

          ????public?static?void?main(String[]?args)?{
          ????????SnowFlakeDemo?snowFlakeDemo?=?new?SnowFlakeDemo();
          ????????for?(int?i?=?0;?i?20;?i++)?{
          ????????????new?Thread(()?->?{
          ????????????????System.out.println(snowFlakeDemo.snowflakeId());
          ????????????},?String.valueOf(i)).start();
          ????????}
          ????}
          }
          得到結(jié)果
          1251350711346790400
          1251350711346790402
          1251350711346790401
          1251350711346790403
          1251350711346790405
          1251350711346790404
          1251350711346790406
          1251350711346790407
          1251350711350984704
          1251350711350984706
          1251350711350984705
          1251350711350984707
          1251350711350984708
          1251350711350984709
          1251350711350984710
          1251350711350984711
          1251350711350984712
          1251350711355179008
          1251350711355179009
          1251350711355179010

          優(yōu)缺點(diǎn)

          優(yōu)點(diǎn)
          • 毫秒數(shù)在高維,自增序列在低位,整個(gè)ID都是趨勢遞增的
          • 不依賴數(shù)據(jù)庫等第三方系統(tǒng),以服務(wù)的方式部署,穩(wěn)定性更高,生成ID的性能也是非常高的
          • 可以根據(jù)自身業(yè)務(wù)特性分配bit位,非常靈活
          缺點(diǎn)
          • 依賴機(jī)器時(shí)鐘,如果機(jī)器時(shí)鐘回?fù)埽瑫?huì)導(dǎo)致重復(fù)ID生成
          • 在單機(jī)上是遞增的,但由于涉及到分布式環(huán)境,每臺機(jī)器上的時(shí)鐘不可能完全同步,有時(shí)候會(huì)出現(xiàn)不是全局遞增的情況,此缺點(diǎn)可以認(rèn)為無所謂,一般分布式ID只要求趨勢遞增,并不會(huì)嚴(yán)格要求遞增,90%的需求只要求趨勢遞增。
          其它補(bǔ)充
          • 為了解決時(shí)鐘回?fù)軉栴},導(dǎo)致ID重復(fù),后面有人專門提出了解決的方案
            • 百度開源的分布式唯一ID生成器 UidGenerator
            • Leaf - 美團(tuán)點(diǎn)評分布式ID生成系統(tǒng)

          推薦閱讀:

          稅前110萬!
          關(guān)于Java 獲取時(shí)間戳的方法,我和同事爭論了半天~
          互聯(lián)網(wǎng)初中高級大廠面試題(9個(gè)G)

          內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper、數(shù)據(jù)結(jié)構(gòu)、限流熔斷降級......等技術(shù)棧!

          ?戳閱讀原文領(lǐng)取!? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??朕已閱?

          瀏覽 57
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草在线免费观看视频 | 在线免费av观看 在线免费精品福利 | 人人操人人摸人人搞 | 欧美人与动Zozo禽交大全 | 老熟女乱伦视频 |