Last active
April 4, 2023 12:39
-
-
Save jessejjohnson/68c11b0951230ae46c2ad9df6c246631 to your computer and use it in GitHub Desktop.
Generate Twitter Snowflake ID's
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SnowflakeIdGenerator | |
{ | |
public const byte DefaultMachineIdBits = 6; | |
public const byte DefaultSequenceBits = 12; | |
private readonly long _machineId = 0; | |
private readonly byte _machineIdBits = 0; | |
private readonly byte _sequenceBits = 0; | |
private readonly long _maxSequence = 0; | |
private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); | |
private readonly object _lockObject = new object(); | |
private long _sequence = 0; | |
private long _lastTimestamp = 0; | |
private static SnowflakeIdGenerator _instance; | |
public static void Init(ushort machineId, byte machineIdBits = DefaultMachineIdBits, byte sequenceBits = DefaultSequenceBits) | |
{ | |
if (_instance != null) | |
throw new InvalidOperationException($"Instance has already been initialized."); | |
_instance = new SnowflakeIdGenerator(machineId, machineIdBits, sequenceBits); | |
} | |
public static long NewId() | |
{ | |
if (_instance == null) | |
throw new Exception("Instance has not been initialized."); | |
return _instance.NewSequenceId(); | |
} | |
private static long GetMaxOfBits(byte bits) | |
{ | |
return (1L << bits) - 1; | |
} | |
private static int GetBitsLength(long number) | |
{ | |
return (int)Math.Log(number, 2) + 1; | |
} | |
// 637134336000000000 = new DateTime(2020, 1, 1, 0, 0, 0, DateTimeKind.Utc).Ticks | |
private static readonly long _offsetTicks = DateTime.UtcNow.Ticks - 637134336000000000; | |
public SnowflakeIdGenerator(ushort machineId) : this(machineId, DefaultMachineIdBits, DefaultSequenceBits) | |
{ | |
} | |
public SnowflakeIdGenerator(ushort machineId, byte machineIdBits, byte sequenceBits) | |
{ | |
if (sequenceBits > 20) | |
throw new ArgumentOutOfRangeException(nameof(sequenceBits), "sequenceBits > 20"); | |
if (machineIdBits > 10) | |
throw new ArgumentOutOfRangeException(nameof(machineIdBits), "machineIdBits > 10"); | |
_machineIdBits = machineIdBits; | |
_sequenceBits = sequenceBits; | |
_maxSequence = GetMaxOfBits(_sequenceBits); | |
var maxMachineId = GetMaxOfBits(machineIdBits); | |
if (machineId > maxMachineId) | |
throw new ArgumentOutOfRangeException(nameof(machineId), $"machineId > {maxMachineId}"); | |
_machineId = machineId; | |
} | |
private long GetTimestampNow() | |
{ | |
// 10000000 = TimeSpan.FromSeconds(1).Ticks | |
return (_offsetTicks + _stopwatch.Elapsed.Ticks) / 10000000L; | |
} | |
private long GetNextTimestamp() | |
{ | |
long timestamp = GetTimestampNow(); | |
if (timestamp < _lastTimestamp) | |
throw new Exception(""); | |
while (timestamp == _lastTimestamp) | |
{ | |
if (_sequence < _maxSequence) | |
{ | |
_sequence++; | |
return timestamp; | |
} | |
Thread.Sleep(0); | |
timestamp = GetTimestampNow(); | |
} | |
_sequence = 0; | |
return timestamp; | |
} | |
public long NewSequenceId() | |
{ | |
lock (_lockObject) | |
{ | |
_lastTimestamp = GetNextTimestamp(); | |
//int bitsLength = GetBitsLength(_lastTimestamp); | |
//Console.WriteLine($"Timestamp bits: {bitsLength}"); | |
int timestampShift = _machineIdBits + _sequenceBits; | |
int machineIdShift = _sequenceBits; | |
return (_lastTimestamp << timestampShift) | (_machineId << machineIdShift) | _sequence; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment