SQLServer中生成雪花ID(Snowflake?ID)的實(shí)現(xiàn)方法
前言
在我的印象中用到這個(gè)雪花ID比較少,可能是我接觸的大型項(xiàng)目或者開(kāi)源項(xiàng)目比較少,同時(shí)接觸到中大型分布式也比較少,基本都是自研系統(tǒng),用的是自增ID和GuidValue作為唯一編號(hào)。
最近項(xiàng)目上使用了一套第三方框架代碼,使用了雪花ID作為表的唯一主鍵,并且之前表沒(méi)有這個(gè)字段,需要進(jìn)行表遷移的同時(shí)初始化雪花ID字段值。
因此,趁這次機(jī)會(huì)簡(jiǎn)單總結(jié)下雪花ID以及在Sql Server上如何生成雪花ID。
認(rèn)識(shí)雪花ID
雪花ID是Twitter開(kāi)發(fā)的一種分布式唯一ID生成算法,主要用于在分布式系統(tǒng)中生成全局唯一的ID標(biāo)識(shí)符。它的名稱來(lái)源于"自然界中沒(méi)有兩片完全相同的雪花"這一概念,象征著每個(gè)生成的ID都是獨(dú)一無(wú)二的。
雪花ID的核心特點(diǎn)
- 全局唯一性:在分布式系統(tǒng)中生成的ID不會(huì)重復(fù)
- 時(shí)間有序性:ID按照時(shí)間順序遞增
- 高性能:本地生成,不依賴數(shù)據(jù)庫(kù)等外部系統(tǒng)
- 可解析:ID中包含的信息可以被解析出來(lái)
雪花ID的結(jié)構(gòu)(64位)
標(biāo)準(zhǔn)的雪花ID由以下三部分組成(共64位):
| 1位符號(hào)位 | 41位時(shí)間戳 | 10位工作節(jié)點(diǎn)ID | 12位序列號(hào) |
具體分解:
- 符號(hào)位(1位):始終為0,保證ID為正數(shù)
- 時(shí)間戳(41位):毫秒級(jí)的時(shí)間戳,可以使用約69年
- 通常從自定義紀(jì)元開(kāi)始計(jì)算(如Twitter使用2010-11-04 01:42:54 UTC)
- 工作節(jié)點(diǎn)ID(10位):
- 通常分為5位數(shù)據(jù)中心ID + 5位機(jī)器ID
- 最多支持32個(gè)數(shù)據(jù)中心,每個(gè)數(shù)據(jù)中心32臺(tái)機(jī)器
- 序列號(hào)(12位):同一毫秒內(nèi)的序列號(hào),支持每毫秒生成4096個(gè)ID
雪花ID的優(yōu)勢(shì)
- 分布式友好:不同節(jié)點(diǎn)可以獨(dú)立生成ID而不需要協(xié)調(diào)
- 時(shí)間有序:生成的ID按時(shí)間遞增,有利于數(shù)據(jù)庫(kù)索引
- 高性能:本地生成,不依賴網(wǎng)絡(luò)或數(shù)據(jù)庫(kù)
- 信息豐富:ID本身包含時(shí)間、節(jié)點(diǎn)等信息
雪花ID的局限性
- 時(shí)鐘依賴:嚴(yán)重依賴系統(tǒng)時(shí)鐘,時(shí)鐘回?fù)軙?huì)導(dǎo)致ID重復(fù)
- 節(jié)點(diǎn)ID配置:需要手動(dòng)或通過(guò)外部系統(tǒng)分配節(jié)點(diǎn)ID
- 時(shí)間耗盡:41位時(shí)間戳大約69年后會(huì)耗盡
雪花ID的應(yīng)用場(chǎng)景
- 分布式系統(tǒng)主鍵生成
- 訂單號(hào)、交易號(hào)等業(yè)務(wù)編號(hào)
- 日志跟蹤ID
- 任何需要全局唯一且有序ID的場(chǎng)景
示例ID解析
假設(shè)一個(gè)雪花ID:123456789012345678
轉(zhuǎn)換為二進(jìn)制后可以解析出:
- 時(shí)間戳部分:可以轉(zhuǎn)換為具體的生成時(shí)間
- 工作節(jié)點(diǎn)部分:知道是在哪個(gè)數(shù)據(jù)中心哪臺(tái)機(jī)器生成的
- 序列號(hào)部分:知道這是該毫秒內(nèi)生成的第幾個(gè)ID
雪花ID因其簡(jiǎn)單高效的特性,已經(jīng)成為分布式系統(tǒng)ID生成的經(jīng)典解決方案之一。
生成雪花ID
雪花ID是Twitter提出的一種分布式ID生成算法,它生成64位的唯一ID,通常包含時(shí)間戳、工作節(jié)點(diǎn)ID和序列號(hào)。
在SQL Server中可以通過(guò)以下幾種方式實(shí)現(xiàn)雪花ID的生成:
使用T-SQL函數(shù)實(shí)現(xiàn)
-- 創(chuàng)建配置表
CREATE TABLE SnowflakeConfig (
MachineId BIGINT NOT NULL,
DatacenterId BIGINT NOT NULL,
LastTimestamp BIGINT NOT NULL,
Sequence BIGINT NOT NULL,
CONSTRAINT PK_SnowflakeConfig PRIMARY KEY (MachineId, DatacenterId)
);
-- 初始化配置 (機(jī)器ID和數(shù)據(jù)中心ID需要在每個(gè)節(jié)點(diǎn)上配置不同) INSERT INTO SnowflakeConfig (MachineId, DatacenterId, LastTimestamp, Sequence) VALUES (1, 1, 0, 0);
-- 創(chuàng)建獲取當(dāng)前時(shí)間戳的函數(shù)
CREATE FUNCTION GetCurrentTimestamp()
RETURNS BIGINT
AS
BEGIN
DECLARE @epoch DATETIME2 = '1970-01-01 00:00:00';
DECLARE @now DATETIME2 = SYSUTCDATETIME();
RETURN CAST(DATEDIFF_BIG(MILLISECOND, @epoch, @now) AS BIGINT);
END;
-- 創(chuàng)建等待下一毫秒的函數(shù)
CREATE FUNCTION TilNextMillis(@lastTimestamp BIGINT)
RETURNS BIGINT
AS
BEGIN
DECLARE @timestamp BIGINT;
SET @timestamp = dbo.GetCurrentTimestamp();
WHILE @timestamp <= @lastTimestamp
BEGIN
SET @timestamp = dbo.GetCurrentTimestamp();
END
RETURN @timestamp;
END;
GO
-- 創(chuàng)建計(jì)算冪的函數(shù)(替代位移操作)
CREATE FUNCTION PowerOfTwo(@exponent BIGINT)
RETURNS BIGINT
AS
BEGIN
RETURN CAST(POWER(CAST(2 AS FLOAT), @exponent) AS BIGINT);
END;
GO
-- 創(chuàng)建生成雪花ID的存儲(chǔ)過(guò)程
CREATE PROCEDURE GenerateSnowflakeId
@MachineId BIGINT = 1,
@DatacenterId BIGINT = 1,
@SnowflakeId BIGINT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
-- 常量定義
DECLARE @Twepoch BIGINT = 1700058600000;
DECLARE @MachineIdBits BIGINT = 5;
DECLARE @DatacenterIdBits BIGINT = 5;
DECLARE @SequenceBits BIGINT = 12;
-- 使用POWER計(jì)算替代位移
DECLARE @MaxMachineId BIGINT = dbo.PowerOfTwo(@MachineIdBits) - 1;
DECLARE @MaxDatacenterId BIGINT = dbo.PowerOfTwo(@DatacenterIdBits) - 1;
DECLARE @SequenceMask BIGINT = dbo.PowerOfTwo(@SequenceBits) - 1;
DECLARE @MachineIdShift BIGINT = @SequenceBits;
DECLARE @DatacenterIdShift BIGINT = @SequenceBits + @MachineIdBits;
DECLARE @TimestampLeftShift BIGINT = @SequenceBits + @MachineIdBits + @DatacenterIdBits;
-- 驗(yàn)證參數(shù)
IF @MachineId > @MaxMachineId OR @MachineId < 0
BEGIN
THROW 50000, 'MachineId can''t be greater than maxMachineId or less than 0', 1;
RETURN;
END
IF @DatacenterId > @MaxDatacenterId OR @DatacenterId < 0
BEGIN
THROW 50000, 'DatacenterId can''t be greater than maxDatacenterId or less than 0', 1;
RETURN;
END
-- 使用事務(wù)確保原子性
BEGIN TRANSACTION;
BEGIN TRY
DECLARE @LastTimestamp BIGINT;
DECLARE @Sequence BIGINT;
DECLARE @Timestamp BIGINT;
-- 獲取當(dāng)前狀態(tài)
SELECT @LastTimestamp = LastTimestamp, @Sequence = Sequence
FROM SnowflakeConfig WITH (UPDLOCK)
WHERE MachineId = @MachineId AND DatacenterId = @DatacenterId;
-- 獲取當(dāng)前時(shí)間戳
SET @Timestamp = dbo.GetCurrentTimestamp();
-- 檢查時(shí)鐘回?fù)?
IF @Timestamp < @LastTimestamp
BEGIN
ROLLBACK TRANSACTION;
THROW 50000, 'Clock moved backwards. Refusing to generate id', 1;
RETURN;
END
-- 同一毫秒內(nèi)生成多個(gè)ID
IF @LastTimestamp = @Timestamp
BEGIN
SET @Sequence = (@Sequence + 1) & @SequenceMask;
IF @Sequence = 0
BEGIN
-- 序列耗盡,等待下一毫秒
SET @Timestamp = dbo.TilNextMillis(@LastTimestamp);
END
END
ELSE
BEGIN
SET @Sequence = 0;
END
-- 更新?tīng)顟B(tài)
UPDATE SnowflakeConfig
SET LastTimestamp = @Timestamp,
Sequence = @Sequence
WHERE MachineId = @MachineId AND DatacenterId = @DatacenterId;
-- 生成ID (使用乘法替代位移)
SET @SnowflakeId =
(@Timestamp - @Twepoch) * dbo.PowerOfTwo(@TimestampLeftShift) +
@DatacenterId * dbo.PowerOfTwo(@DatacenterIdShift) +
@MachineId * dbo.PowerOfTwo(@MachineIdShift) +
@Sequence;
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
THROW;
END CATCH
END;
GO
查看效果
-- 使用存儲(chǔ)過(guò)程版本 DECLARE @Id BIGINT; EXEC GenerateSnowflakeId @MachineId = 1, @DatacenterId = 1, @SnowflakeId = @Id OUTPUT; SELECT @Id AS SnowflakeId;

到此這篇關(guān)于SQLServer中生成雪花ID(Snowflake ID)的實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)sqlserver生成雪花id內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
啟動(dòng)sqlserver服務(wù)的bat腳本分享
這篇文章主要介紹了啟動(dòng)sqlserver服務(wù)的bat腳本分享,本文直接給出腳本代碼,需要的朋友可以參考下2015-02-02
SqlServer生成連續(xù)數(shù)字根據(jù)指定的數(shù)字操作
這篇文章主要介紹了SqlServer生成連續(xù)數(shù)字根據(jù)指定的數(shù)字操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
和表值函數(shù)連接引發(fā)的性能問(wèn)題分析
最近調(diào)優(yōu)過(guò)程中遇到一個(gè)問(wèn)題,就是表值函數(shù)作為連接中的一部分時(shí),可能會(huì)引起麻煩,本文會(huì)簡(jiǎn)單闡述表值函數(shù)是什么,以及為什么使用表值函數(shù)進(jìn)行連接時(shí)會(huì)引發(fā)性能問(wèn)題2015-02-02
通過(guò)SQL語(yǔ)句直接把表導(dǎo)出為XML格式
有時(shí)候我們需要把從數(shù)據(jù)庫(kù)里讀出的數(shù)據(jù)直接保存為XML的形式,這里我們通過(guò)SQL語(yǔ)句就可以達(dá)到這種效果。2010-09-09
SQLServer:探討EXEC與sp_executesql的區(qū)別詳解
本篇文章是對(duì)EXEC與sp_executesql的區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
SQL?Server數(shù)據(jù)庫(kù)備份和恢復(fù)數(shù)據(jù)庫(kù)的全過(guò)程
最近在功能調(diào)試前需要先將測(cè)試數(shù)據(jù)庫(kù)備份,然后功能調(diào)試之后再將測(cè)試數(shù)據(jù)庫(kù)還原,這樣就可以重復(fù)的進(jìn)行功能調(diào)試,這篇文章主要給大家介紹了關(guān)于SQL?Server數(shù)據(jù)庫(kù)備份和恢復(fù)數(shù)據(jù)庫(kù)的相關(guān)資料,需要的朋友可以參考下2022-06-06
SQL?Server數(shù)據(jù)庫(kù)用戶管理及權(quán)限管理詳解
在SQLServer數(shù)據(jù)庫(kù)中,為了確保數(shù)據(jù)的安全性和完整性,我們需要為用戶分配適當(dāng)?shù)臋?quán)限,這篇文章主要給大家介紹了關(guān)于SQL?Server數(shù)據(jù)庫(kù)用戶管理及權(quán)限管理的相關(guān)資料,需要的朋友可以參考下2024-07-07
必須會(huì)的SQL語(yǔ)句(八) 數(shù)據(jù)庫(kù)的完整性約束
這篇文章主要介紹了sqlserver中數(shù)據(jù)庫(kù)的完整性約束使用方法,需要的朋友可以參考下2015-01-01
sql server中批量插入與更新兩種解決方案分享(存儲(chǔ)過(guò)程)
對(duì)于sql 來(lái)說(shuō)操作集合類型(一行一行)是比較麻煩的一件事,而一般業(yè)務(wù)邏輯復(fù)雜的系統(tǒng)或項(xiàng)目都會(huì)涉及到集合遍歷的問(wèn)題,通常一些人就想到用游標(biāo),這里我列出了兩種方案,供大家參考2012-05-05

