diff --git a/src/DotNetCore.CAP/Infrastructure/SnowflakeId.cs b/src/DotNetCore.CAP/Infrastructure/SnowflakeId.cs new file mode 100644 index 0000000..59e4db4 --- /dev/null +++ b/src/DotNetCore.CAP/Infrastructure/SnowflakeId.cs @@ -0,0 +1,93 @@ +// Copyright 2010-2012 Twitter, Inc. +// An object that generates IDs. This is broken into a separate class in case we ever want to support multiple worker threads per process + +using System; + +namespace DotNetCore.CAP.Infrastructure +{ + public class SnowflakeId + { + public const long Twepoch = 1288834974657L; + + private const int WorkerIdBits = 5; + private const int DatacenterIdBits = 5; + private const int SequenceBits = 12; + private const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits); + private const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits); + + private const int WorkerIdShift = SequenceBits; + private const int DatacenterIdShift = SequenceBits + WorkerIdBits; + public const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits; + private const long SequenceMask = -1L ^ (-1L << SequenceBits); + + private static SnowflakeId _snowflakeId; + + private readonly object _lock = new object(); + private long _lastTimestamp = -1L; + + private SnowflakeId(long workerId, long datacenterId, long sequence = 0L) + { + WorkerId = workerId; + DatacenterId = datacenterId; + Sequence = sequence; + + // sanity check for workerId + if (workerId > MaxWorkerId || workerId < 0) + throw new ArgumentException($"worker Id can't be greater than {MaxWorkerId} or less than 0"); + + if (datacenterId > MaxDatacenterId || datacenterId < 0) + throw new ArgumentException($"datacenter Id can't be greater than {MaxDatacenterId} or less than 0"); + } + + public long WorkerId { get; protected set; } + public long DatacenterId { get; protected set; } + + public long Sequence { get; internal set; } + + public static SnowflakeId Default(long datacenterId = 0) + { + return _snowflakeId ?? (_snowflakeId = new SnowflakeId(AppDomain.CurrentDomain.Id, datacenterId)); + } + + public virtual long NextId() + { + lock (_lock) + { + var timestamp = TimeGen(); + + if (timestamp < _lastTimestamp) + throw new Exception( + $"InvalidSystemClock: Clock moved backwards, Refusing to generate id for {_lastTimestamp - timestamp} milliseconds"); + + if (_lastTimestamp == timestamp) + { + Sequence = (Sequence + 1) & SequenceMask; + if (Sequence == 0) timestamp = TilNextMillis(_lastTimestamp); + } + else + { + Sequence = 0; + } + + _lastTimestamp = timestamp; + var id = ((timestamp - Twepoch) << TimestampLeftShift) | + (DatacenterId << DatacenterIdShift) | + (WorkerId << WorkerIdShift) | Sequence; + + return id; + } + } + + protected virtual long TilNextMillis(long lastTimestamp) + { + var timestamp = TimeGen(); + while (timestamp <= lastTimestamp) timestamp = TimeGen(); + return timestamp; + } + + protected virtual long TimeGen() + { + return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + } + } +} \ No newline at end of file