使用PHP涩惑、NodeJS、Swift 實現(xiàn)雪花算法

現(xiàn)有的MySql 數(shù)據(jù)庫在實現(xiàn)大數(shù)據(jù)的分庫分表時桑驱,會碰上分表時主鍵自增ID重復問題竭恬。雖然在分表時可以利用規(guī)分ID值區(qū)間規(guī)則的方式規(guī)避問題。但很明顯不可能會有程序員這樣玩熬的。


自增ID不可避免以面對多表ID重復問題

過去一些案例有使用 UUID 來作為主鍵痊硕,不過UUID 生成的是一個無序的字符串,對于 MySQL 推薦使用增長的數(shù)值類型值作為主鍵來說不適合押框,同時以字符串方式存入到MySql 的聚簇索引中岔绸,既浪費空間也不利于查詢性能優(yōu)化。

也有在插入數(shù)據(jù)表前橡伞,利用 Redis 的自增原子性來建立唯一 ID再存入的做法盒揉,當然也是考慮到性能問題,在業(yè)內(nèi)是非常少用兑徘。

比起MongoDB刚盈,天生帶有處理這種分庫分布能力的ObjectID特性。MySql在這方面確實是挺頹勢挂脑。還好外面還是有不少優(yōu)秀的解決方法藕漱,其中由Twitter 的SnowFlake(雪花) 算法就是能夠?qū)崿F(xiàn)這樣的一種分布式 id 的生成算法欲侮。其核心思想就是:使用一個 64 位 的 long 型的數(shù)字作為全局唯一 id。通過對64位中每個區(qū)間位置的引入特定特性的做法谴分,有效利用一個Long型來滿足自增并且唯一的能力锈麸。
簡單來介紹就是如圖:


雪花算法對Long型ID值的結(jié)構(gòu)說明

1bit-不用: 第一位為0,沒有意義牺蹄。因為MySql的Long類型屬于無符號正整數(shù)忘伞,如果是 1 就成負數(shù)了。

41bit-時間戳:表示的是時間戳沙兰,2^41/(1000606024365)=69氓奈,大概可以使用 69 年。

5bit-工作機器id + 5bit-服務碼id:這兩個數(shù)合計可以表達服務器+分區(qū)名鼎天,正常情況下同一個表允許分出 1024 個區(qū)舀奶。

12bit-序列號:表示1毫秒內(nèi)產(chǎn)生的不同 id,12 bit 可以代表的最大正整數(shù)是 2 ^ 12 - 1 = 4096斋射,也就是說可以用這個 12 bit 代表的數(shù)字來區(qū)分同一個毫秒內(nèi)的 4096 個不同的 id育勺。

PHP的實現(xiàn)算法如下:

function snowFlake_1($mach_id,$server_id,$seq){
    
    //由于41位容量只夠玩69年,所以時間戳應該由系統(tǒng)發(fā)布的時間開始進行減掉罗岖。69年后這個時間就得變更了涧至。
    $sys_public_time=1676731103000;//2023-02-18 22:38:23
    $time=intval(microtime(true) * 1000);
    $offset_time=$time-$sys_public_time;
    $time_22=($offset_time) << 22;
    $mach_id_17=$mach_id << 17;
    $server_id_12=$server_id << 12;
    $combine=$time_22|$mach_id_17|$server_id_12 | $seq;
    
    var_dump("當前時間:".$time);
    var_dump("扣減后的時間差:".$offset_time);

    var_dump("把時間差轉(zhuǎn)為二進制的值 :".decbin($offset_time));
    var_dump("時間差左移22位后的二進制的值:".decbin($time_22));
    var_dump("機器碼二進制的值:".decbin($mach_id_17));
    var_dump("服務碼二進制的值:".decbin($server_id_12));
    var_dump("序列碼:".decbin($seq));
    var_dump("合并后的二進制:".decbin($combine));
    var_dump("應該要返回給數(shù)據(jù)庫的雪花值:".$combine);

    return $combine;
}

#執(zhí)行
$seq=mt_rand(0, 4095);
snowFlake_1(1,3,$seq);

輸出結(jié)果:

string(26) "當前時間:1676731793789"
string(28) "扣減后的時間差:690789"
string(55) "把時間差轉(zhuǎn)為二進制的值 :10101000101001100101"
string(84) "時間差左移22位后的二進制的值:101010001010011001010000000000000000000000"
string(43) "機器碼二進制的值:100000000000000000"
string(39) "服務碼二進制的值:11000000000000"
string(22) "序列碼:110000010011"
string(66) "合并后的二進制:101010001010011001010000100011110000010011"
string(55) "應該要返回給數(shù)據(jù)庫的雪花值:2897379212307"

換成NodeJS來實現(xiàn),會出現(xiàn)一些小小的麻煩桑包,兩個類型不一樣的變量發(fā)生位運算等操作時南蓬,只會轉(zhuǎn)為32位。所以在發(fā)生計算時哑了,每一個環(huán)節(jié)的變量都必須是BigInt類型:

const snowFlake = (mach_id,server_id,seq)=>{
     //由于41位容量只夠玩69年赘方,所以時間戳應該由系統(tǒng)發(fā)布的時間開始進行減掉。69年后這個時間就得變更了弱左。
    const sys_public_time=BigInt(1676731103000);//2023-02-18 22:38:23

    const timestamp=BigInt(new Date().getTime());
    const offset_time=timestamp-sys_public_time;

    let timestamp_22=offset_time <<BigInt(22);
    let mach_id_17=BigInt(mach_id <<17 );
    let server_id_12=BigInt(server_id <<12);    

    let combine=timestamp_22|mach_id_17|server_id_12 | BigInt(seq);

    console.log("當前時間:"+timestamp);
    console.log("扣減后的時間差:"+offset_time);

    console.log("把時間差轉(zhuǎn)為二進制的值 :"+offset_time.toString(2));
    console.log("時間差左移22位后的二進制的值:"+timestamp_22.toString(2));
    console.log("機器碼二進制的值:"+mach_id_17.toString(2));
    console.log("服務碼二進制的值:"+server_id_12.toString(2));
    console.log("序列碼:"+seq.toString(2));
    console.log("合并后的二進制:"+combine.toString(2));
    console.log("應該要返回給數(shù)據(jù)庫的雪花值:"+combine);

    return combine;
}

//執(zhí)行
snowFlake(31,31,4095);

輸出結(jié)果:

當前時間:1676734225158
扣減后的時間差:3122158
把時間差轉(zhuǎn)為二進制的值 :1011111010001111101110
時間差左移22位后的二進制的值:10111110100011111011100000000000000000000000
機器碼二進制的值:1111100000000000000000
服務碼二進制的值:11111000000000000
序列碼:111111111111
合并后的二進制:10111110100011111011101111111111111111111111
應該要返回給數(shù)據(jù)庫的雪花值:13095283982335

Swift的代碼:

func snowFlake(mach_id: Int32, server_id: Int32, seq:Int32) -> Int64{
    
    let sys_public_time: Int = 1676731103000;//2023-02-18 22:38:23
    let time: Int = Int(Date().timeIntervalSince1970 * 1000);
    
    let offset_time: Int = time - sys_public_time;
    
    let time_22: Int64 = Int64(offset_time) << 22;
    let mach_id_17: Int64 = Int64(mach_id) << 17;
    let server_id_12: Int64 = Int64(server_id) << 12;
    let combine:Int64 = time_22 | mach_id_17 | server_id_12 | Int64(seq);

    
    print(sys_public_time)
    print("當前時間:\(time)");
    print("扣減后的時間差:\(offset_time)");

    print("把時間差轉(zhuǎn)為二進制的值 :\(String(offset_time,radix:2))");
    print("時間差左移22位后的二進制的值:\(String(time_22,radix:2))");
    print("機器碼二進制的值:\(String(mach_id_17,radix:2))");
    print("服務碼二進制的值:\(String(server_id_12,radix:2))");
    print("序列碼:\(String(seq,radix:2))");
    print("合并后的二進制:\(String(combine,radix:2))");
    print("應該要返回給數(shù)據(jù)庫的雪花值:\(combine)");

    
    return combine;
}
//執(zhí)行:
snowFlake(mach_id: 31,server_id: 31,seq:4095);

輸出結(jié)果:

1676731103000
當前時間:1679300780126
扣減后的時間差:2569677126
把時間差轉(zhuǎn)為二進制的值 :10011001001010100010100101000110
時間差左移22位后的二進制的值:100110010010101000101001010001100000000000000000000000
機器碼二進制的值:1111100000000000000000
服務碼二進制的值:11111000000000000
序列碼:111111111111
合并后的二進制:100110010010101000101001010001101111111111111111111111
應該要返回給數(shù)據(jù)庫的雪花值:10778007052484607
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窄陡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拆火,更是在濱河造成了極大的恐慌泳梆,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件榜掌,死亡現(xiàn)場離奇詭異优妙,居然都是意外死亡,警方通過查閱死者的電腦和手機憎账,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門套硼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胞皱,你說我怎么就攤上這事邪意【怕瑁” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵雾鬼,是天一觀的道長萌朱。 經(jīng)常有香客問我,道長策菜,這世上最難降的妖魔是什么晶疼? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮又憨,結(jié)果婚禮上翠霍,老公的妹妹穿的比我還像新娘。我一直安慰自己蠢莺,他們只是感情好寒匙,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著躏将,像睡著了一般锄弱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上祸憋,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天会宪,我揣著相機與錄音,去河邊找鬼夺衍。 笑死,一個胖子當著我的面吹牛喜命,可吹牛的內(nèi)容都是我干的沟沙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼壁榕,長吁一口氣:“原來是場噩夢啊……” “哼矛紫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牌里,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤颊咬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后牡辽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喳篇,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年态辛,在試婚紗的時候發(fā)現(xiàn)自己被綠了麸澜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡奏黑,死狀恐怖炊邦,靈堂內(nèi)的尸體忽然破棺而出编矾,到底是詐尸還是另有隱情,我是刑警寧澤馁害,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布窄俏,位于F島的核電站,受9級特大地震影響碘菜,放射性物質(zhì)發(fā)生泄漏凹蜈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一炉媒、第九天 我趴在偏房一處隱蔽的房頂上張望踪区。 院中可真熱鬧,春花似錦吊骤、人聲如沸缎岗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽传泊。三九已至,卻和暖如春鸭巴,著一層夾襖步出監(jiān)牢的瞬間眷细,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工鹃祖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留溪椎,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓恬口,卻偏偏與公主長得像校读,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子祖能,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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