現(xiàn)有的MySql 數(shù)據(jù)庫在實現(xiàn)大數(shù)據(jù)的分庫分表時桑驱,會碰上分表時主鍵自增ID重復問題竭恬。雖然在分表時可以利用規(guī)分ID值區(qū)間規(guī)則的方式規(guī)避問題。但很明顯不可能會有程序員這樣玩熬的。
過去一些案例有使用 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型來滿足自增并且唯一的能力锈麸。
簡單來介紹就是如圖:
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