From a28a4a33364e419776ff018eec204fb4e831ab51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E9=91=AB=E9=91=AB?= <1368084801@qq.com> Date: Tue, 23 Apr 2024 10:44:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20=E7=94=9F=E6=88=90Snowflak?= =?UTF-8?q?e=20ID=E4=B8=BA=E8=B4=9F=E6=95=B0=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新 IdUtil.SnowflakeId() 修复 生成Snowflake ID为负数的问题 添加测试方法 --- EasyTool.Core/ToolCategory/IdUtil.cs | 382 +++++++++--------- .../ToolCategory/IDUtilTests.cs | 41 +- 2 files changed, 212 insertions(+), 211 deletions(-) diff --git a/EasyTool.Core/ToolCategory/IdUtil.cs b/EasyTool.Core/ToolCategory/IdUtil.cs index 64edf2a..1905509 100644 --- a/EasyTool.Core/ToolCategory/IdUtil.cs +++ b/EasyTool.Core/ToolCategory/IdUtil.cs @@ -1,199 +1,197 @@ using System; -using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Text; using System.Threading; namespace EasyTool { - /// - /// uuid生成风格 - /// - public enum UUIDStyle - { - /// - /// guid - /// - GUID, - /// - /// mssql顺序guid - /// - SequentialGUID, - /// - /// 顺序 - /// - Sequence, - } - /// - /// 唯一ID工具 - /// - public class IdUtil - { - private static readonly DateTime epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - private static int objectIdCounter = 0; - - private static long _counter = DateTime.Now.Ticks; - /// - /// 生成UUID - /// - /// 生成的UUID - public static Guid UUID(UUIDStyle style = UUIDStyle.GUID) - { - switch (style) - { - case UUIDStyle.GUID: return Guid.NewGuid(); - case UUIDStyle.SequentialGUID: - { - Span guidBytes = stackalloc byte[16]; - var succeeded = Guid.NewGuid().TryWriteBytes(guidBytes); - var incrementedCounter = Interlocked.Increment(ref _counter); - Span counterBytes = stackalloc byte[sizeof(long)]; - MemoryMarshal.Write(counterBytes, ref incrementedCounter); - - if (!BitConverter.IsLittleEndian) - { - counterBytes.Reverse(); - } - - guidBytes[08] = counterBytes[1]; - guidBytes[09] = counterBytes[0]; - guidBytes[10] = counterBytes[7]; - guidBytes[11] = counterBytes[6]; - guidBytes[12] = counterBytes[5]; - guidBytes[13] = counterBytes[4]; - guidBytes[14] = counterBytes[3]; - guidBytes[15] = counterBytes[2]; - - return new Guid(guidBytes); - } - case UUIDStyle.Sequence: - { - Span guidBytes = stackalloc byte[16]; - var succeeded = Guid.NewGuid().TryWriteBytes(guidBytes); - var incrementedCounter = Interlocked.Increment(ref _counter); - Span counterBytes = stackalloc byte[sizeof(long)]; - MemoryMarshal.Write(counterBytes, ref incrementedCounter); - - if (!BitConverter.IsLittleEndian) - { - counterBytes.Reverse(); - } - - guidBytes[0] = counterBytes[4]; - guidBytes[1] = counterBytes[5]; - guidBytes[2] = counterBytes[6]; - guidBytes[3] = counterBytes[7]; - guidBytes[4] = counterBytes[2]; - guidBytes[5] = counterBytes[3]; - guidBytes[6] = counterBytes[0]; - guidBytes[7] = counterBytes[1]; - - return new Guid(guidBytes); - } - default: - throw new ArgumentException("不支持的UUIDStyle"); - } - } - /// - /// 生成UUID - /// - /// 生成的UUID - public static string UUIDString(UUIDStyle style = UUIDStyle.GUID) - => UUID(style).ToString(); - - /// - /// 生成MongoDB ObjectId - /// - /// 生成的MongoDB ObjectId - public static string ObjectId() - { - byte[] timestamp = BitConverter.GetBytes((int)(DateTime.UtcNow - epoch).TotalSeconds); - byte[] machineIdentifier = BitConverter.GetBytes(Environment.MachineName.GetHashCode()); - byte[] processIdentifier = BitConverter.GetBytes(System.Diagnostics.Process.GetCurrentProcess().Id); - byte[] increment = BitConverter.GetBytes(Interlocked.Increment(ref objectIdCounter)); - - if (BitConverter.IsLittleEndian) - { - Array.Reverse(timestamp); - Array.Reverse(machineIdentifier); - Array.Reverse(processIdentifier); - Array.Reverse(increment); - } - - byte[] objectId = new byte[12]; - Buffer.BlockCopy(timestamp, 0, objectId, 0, 4); - Buffer.BlockCopy(machineIdentifier, 2, objectId, 4, 2); - Buffer.BlockCopy(processIdentifier, 0, objectId, 6, 2); - Buffer.BlockCopy(increment, 1, objectId, 8, 3); - - return Convert.ToBase64String(objectId); - } - - private static readonly long epochTicks = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; - private static readonly long workerIdBits = 5L; - private static readonly long datacenterIdBits = 5L; - private static readonly long sequenceBits = 12L; - private static readonly long maxWorkerId = -1L ^ (-1L << (int)workerIdBits); - private static readonly long maxDatacenterId = -1L ^ (-1L << (int)datacenterIdBits); - private static readonly long sequenceMask = -1L ^ (-1L << (int)sequenceBits); - - private static long lastTimestamp = -1L; - private static long sequence = 0L; - private static readonly object lockObj = new object(); - - private static readonly Random random = new Random(); - - private static readonly long workerId = random.Next((int)maxWorkerId); - private static readonly long datacenterId = random.Next((int)maxDatacenterId); - - /// - /// 生成Snowflake ID - /// - /// 生成的Snowflake ID - public static long SnowflakeId() - { - lock (lockObj) - { - long timestamp = DateTime.UtcNow.Ticks - epochTicks; - - if (timestamp < lastTimestamp) - { - throw new Exception("Clock moved backwards, refusing to generate Snowflake ID"); - } - - if (timestamp == lastTimestamp) - { - sequence = (sequence + 1) & sequenceMask; - - if (sequence == 0) - { - timestamp = NextMillis(lastTimestamp); - } - } - else - { - sequence = 0L; - } - - lastTimestamp = timestamp; - - return (timestamp << (int)(workerIdBits + sequenceBits)) | - (datacenterId << (int)sequenceBits) | - (workerId << (int)sequenceBits) | - sequence; - } - } - - private static long NextMillis(long lastTimestamp) - { - long timestamp = DateTime.UtcNow.Ticks - epochTicks; - - while (timestamp <= lastTimestamp) - { - timestamp = DateTime.UtcNow.Ticks - epochTicks; - } - - return timestamp; - } - } + /// + /// uuid生成风格 + /// + public enum UUIDStyle + { + /// + /// guid + /// + GUID, + /// + /// mssql顺序guid + /// + SequentialGUID, + /// + /// 顺序 + /// + Sequence, + } + /// + /// 唯一ID工具 + /// + public class IdUtil + { + private static readonly DateTime epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static int objectIdCounter = 0; + + private static long _counter = DateTime.Now.Ticks; + /// + /// 生成UUID + /// + /// 生成的UUID + public static Guid UUID(UUIDStyle style = UUIDStyle.GUID) + { + switch (style) + { + case UUIDStyle.GUID: return Guid.NewGuid(); + case UUIDStyle.SequentialGUID: + { + Span guidBytes = stackalloc byte[16]; + var succeeded = Guid.NewGuid().TryWriteBytes(guidBytes); + var incrementedCounter = Interlocked.Increment(ref _counter); + Span counterBytes = stackalloc byte[sizeof(long)]; + MemoryMarshal.Write(counterBytes, ref incrementedCounter); + + if (!BitConverter.IsLittleEndian) + { + counterBytes.Reverse(); + } + + guidBytes[08] = counterBytes[1]; + guidBytes[09] = counterBytes[0]; + guidBytes[10] = counterBytes[7]; + guidBytes[11] = counterBytes[6]; + guidBytes[12] = counterBytes[5]; + guidBytes[13] = counterBytes[4]; + guidBytes[14] = counterBytes[3]; + guidBytes[15] = counterBytes[2]; + + return new Guid(guidBytes); + } + case UUIDStyle.Sequence: + { + Span guidBytes = stackalloc byte[16]; + var succeeded = Guid.NewGuid().TryWriteBytes(guidBytes); + var incrementedCounter = Interlocked.Increment(ref _counter); + Span counterBytes = stackalloc byte[sizeof(long)]; + MemoryMarshal.Write(counterBytes, ref incrementedCounter); + + if (!BitConverter.IsLittleEndian) + { + counterBytes.Reverse(); + } + + guidBytes[0] = counterBytes[4]; + guidBytes[1] = counterBytes[5]; + guidBytes[2] = counterBytes[6]; + guidBytes[3] = counterBytes[7]; + guidBytes[4] = counterBytes[2]; + guidBytes[5] = counterBytes[3]; + guidBytes[6] = counterBytes[0]; + guidBytes[7] = counterBytes[1]; + + return new Guid(guidBytes); + } + default: + throw new ArgumentException("不支持的UUIDStyle"); + } + } + /// + /// 生成UUID + /// + /// 生成的UUID + public static string UUIDString(UUIDStyle style = UUIDStyle.GUID) + => UUID(style).ToString(); + + /// + /// 生成MongoDB ObjectId + /// + /// 生成的MongoDB ObjectId + public static string ObjectId() + { + byte[] timestamp = BitConverter.GetBytes((int)(DateTime.UtcNow - epoch).TotalSeconds); + byte[] machineIdentifier = BitConverter.GetBytes(Environment.MachineName.GetHashCode()); + byte[] processIdentifier = BitConverter.GetBytes(System.Diagnostics.Process.GetCurrentProcess().Id); + byte[] increment = BitConverter.GetBytes(Interlocked.Increment(ref objectIdCounter)); + + if (BitConverter.IsLittleEndian) + { + Array.Reverse(timestamp); + Array.Reverse(machineIdentifier); + Array.Reverse(processIdentifier); + Array.Reverse(increment); + } + + byte[] objectId = new byte[12]; + Buffer.BlockCopy(timestamp, 0, objectId, 0, 4); + Buffer.BlockCopy(machineIdentifier, 2, objectId, 4, 2); + Buffer.BlockCopy(processIdentifier, 0, objectId, 6, 2); + Buffer.BlockCopy(increment, 1, objectId, 8, 3); + + return Convert.ToBase64String(objectId); + } + + private static readonly long epochTicks = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks; + private static readonly long workerIdBits = 5L; + private static readonly long datacenterIdBits = 5L; + private static readonly long sequenceBits = 12L; + private static readonly long maxWorkerId = -1L ^ (-1L << (int)workerIdBits); + private static readonly long maxDatacenterId = -1L ^ (-1L << (int)datacenterIdBits); + private static readonly long sequenceMask = -1L ^ (-1L << (int)sequenceBits); + + private static long lastTimestamp = -1L; + private static long sequence = 0L; + private static readonly object lockObj = new object(); + + private static readonly Random random = new Random(); + + private static readonly long workerId = random.Next((int)maxWorkerId); + private static readonly long datacenterId = random.Next((int)maxDatacenterId); + + /// + /// 生成Snowflake ID + /// + /// 生成的Snowflake ID + public static long SnowflakeId() + { + lock (lockObj) + { + long timestamp = DateTime.UtcNow.Ticks - epochTicks; + + if (timestamp < lastTimestamp) + { + throw new Exception("Clock moved backwards, refusing to generate Snowflake ID"); + } + + if (timestamp == lastTimestamp) + { + sequence = (sequence + 1) & sequenceMask; + + if (sequence == 0) + { + timestamp = NextMillis(lastTimestamp); + } + } + else + { + sequence = 0L; + } + + lastTimestamp = timestamp; + + return ((timestamp << (int)(workerIdBits + sequenceBits)) | + (datacenterId << (int)sequenceBits) | + (workerId << (int)sequenceBits) | + sequence) & long.MaxValue; + } + } + + private static long NextMillis(long lastTimestamp) + { + long timestamp = DateTime.UtcNow.Ticks - epochTicks; + + while (timestamp <= lastTimestamp) + { + timestamp = DateTime.UtcNow.Ticks - epochTicks; + } + + return timestamp; + } + } } diff --git a/EasyTool.CoreTests/ToolCategory/IDUtilTests.cs b/EasyTool.CoreTests/ToolCategory/IDUtilTests.cs index 7e796cf..b75def7 100644 --- a/EasyTool.CoreTests/ToolCategory/IDUtilTests.cs +++ b/EasyTool.CoreTests/ToolCategory/IDUtilTests.cs @@ -1,25 +1,28 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using EasyTool; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Diagnostics; namespace EasyTool.Tests { - [TestClass] - public class IdUtilTests - { - [TestMethod] - public void NextSequenceUUID_AreGreaterThan() - { - var uuid1 = IdUtil.UUID(UUIDStyle.Sequence); - Thread.Sleep(10); - var uuid2 = IdUtil.UUID(UUIDStyle.Sequence); + [TestClass] + public class IdUtilTests + { + [TestMethod] + public void NextSequenceUUID_AreGreaterThan() + { + var uuid1 = IdUtil.UUID(UUIDStyle.Sequence); + Thread.Sleep(10); + var uuid2 = IdUtil.UUID(UUIDStyle.Sequence); - Assert.IsTrue(uuid2.ToString().CompareTo(uuid1.ToString()) > 0); - } - } + Assert.IsTrue(uuid2.ToString().CompareTo(uuid1.ToString()) > 0); + } + + [TestMethod] + public void NextSnowflakeId_AreGreaterThan() + { + var snowflakeId1 = IdUtil.SnowflakeId(); + Thread.Sleep(10); + var snowflakeId2 = IdUtil.SnowflakeId(); + + Assert.IsTrue(snowflakeId1 > 0 && snowflakeId1 < snowflakeId2); + } + } } \ No newline at end of file