使用PHP、NodeJS、Swift 实现雪花算法

现有的MySql 数据库在实现大数据的分库分表时,会碰上分表时主键自增ID重复问题。虽然在分表时可以利用规分ID值区间规则的方式规避问题。但很明显不可能会有程序员这样玩。


自增ID不可避免以面对多表ID重复问题

过去一些案例有使用 UUID 来作为主键,不过UUID 生成的是一个无序的字符串,对于 MySQL 推荐使用增长的数值类型值作为主键来说不适合,同时以字符串方式存入到MySql 的聚簇索引中,既浪费空间也不利于查询性能优化。

也有在插入数据表前,利用 Redis 的自增原子性来建立唯一 ID再存入的做法,当然也是考虑到性能问题,在业内是非常少用。

比起MongoDB,天生带有处理这种分库分布能力的ObjectID特性。MySql在这方面确实是挺颓势。还好外面还是有不少优秀的解决方法,其中由Twitter 的SnowFlake(雪花) 算法就是能够实现这样的一种分布式 id 的生成算法。其核心思想就是:使用一个 64 位 的 long 型的数字作为全局唯一 id。通过对64位中每个区间位置的引入特定特性的做法,有效利用一个Long型来满足自增并且唯一的能力。
简单来介绍就是如图:


雪花算法对Long型ID值的结构说明

1bit-不用: 第一位为0,没有意义。因为MySql的Long类型属于无符号正整数,如果是 1 就成负数了。

41bit-时间戳:表示的是时间戳,2^41/(1000606024365)=69,大概可以使用 69 年。

5bit-工作机器id + 5bit-服务码id:这两个数合计可以表达服务器+分区名,正常情况下同一个表允许分出 1024 个区。

12bit-序列号:表示1毫秒内产生的不同 id,12 bit 可以代表的最大正整数是 2 ^ 12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 id。

PHP的实现算法如下:

function snowFlake_1($mach_id,$server_id,$seq){
    
    //由于41位容量只够玩69年,所以时间戳应该由系统发布的时间开始进行减掉。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("把时间差转为二进制的值 :".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("应该要返回给数据库的雪花值:".$combine);

    return $combine;
}

#执行
$seq=mt_rand(0, 4095);
snowFlake_1(1,3,$seq);

输出结果:

string(26) "当前时间:1676731793789"
string(28) "扣减后的时间差:690789"
string(55) "把时间差转为二进制的值 :10101000101001100101"
string(84) "时间差左移22位后的二进制的值:101010001010011001010000000000000000000000"
string(43) "机器码二进制的值:100000000000000000"
string(39) "服务码二进制的值:11000000000000"
string(22) "序列码:110000010011"
string(66) "合并后的二进制:101010001010011001010000100011110000010011"
string(55) "应该要返回给数据库的雪花值:2897379212307"

换成NodeJS来实现,会出现一些小小的麻烦,两个类型不一样的变量发生位运算等操作时,只会转为32位。所以在发生计算时,每一个环节的变量都必须是BigInt类型:

const snowFlake = (mach_id,server_id,seq)=>{
     //由于41位容量只够玩69年,所以时间戳应该由系统发布的时间开始进行减掉。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("把时间差转为二进制的值 :"+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("应该要返回给数据库的雪花值:"+combine);

    return combine;
}

//执行
snowFlake(31,31,4095);

输出结果:

当前时间:1676734225158
扣减后的时间差:3122158
把时间差转为二进制的值 :1011111010001111101110
时间差左移22位后的二进制的值:10111110100011111011100000000000000000000000
机器码二进制的值:1111100000000000000000
服务码二进制的值:11111000000000000
序列码:111111111111
合并后的二进制:10111110100011111011101111111111111111111111
应该要返回给数据库的雪花值: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("把时间差转为二进制的值 :\(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("应该要返回给数据库的雪花值:\(combine)");

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

输出结果:

1676731103000
当前时间:1679300780126
扣减后的时间差:2569677126
把时间差转为二进制的值 :10011001001010100010100101000110
时间差左移22位后的二进制的值:100110010010101000101001010001100000000000000000000000
机器码二进制的值:1111100000000000000000
服务码二进制的值:11111000000000000
序列码:111111111111
合并后的二进制:100110010010101000101001010001101111111111111111111111
应该要返回给数据库的雪花值:10778007052484607
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355

推荐阅读更多精彩内容