Trong các hệ thống phân tán, có những trường hợp yêu cầu một ID duy nhất trên toàn cầu. Để ngăn chặn xung đột ID, UUID 36-bit có thể được sử dụng, nhưng nó có một số nhược điểm. Đầu tiên, nó tương đối dài và các UUID thường không theo thứ tự. Đôi khi chúng tôi muốn có thể sử dụng một ID đơn giản hơn và chúng tôi muốn các ID được tạo theo thứ tự thời gian. MySQL đến Cassandra, vốn không có cơ chế tạo ID tuần tự.
Snowflake ID sử dụng số nguyên 64 bit làm ID, tương ứng với long in .Net, bigint trong cơ sở dữ liệu và scala là phiên bản gốc của thuật toán Snowflake, được sử dụng để tạo ID phân tán (số thuần túy, chuỗi thời gian), số thứ tự, Vân vân.
Snowflake ID chủ yếu dựa vào thời gian hiện tại của hệ thống. Khi thời gian hệ thống được điều chỉnh một cách giả tạo, thuật toán sẽ không theo thứ tự hoặc không thể xử lý callback của clock sau khi hệ thống ngừng hoạt động. Nó có thể bị trùng lặp.
Các ID được tạo bởi snowflake được sắp xếp theo thời gian tổng thể, không tạo ra xung đột ID trong toàn bộ hệ thống phân tán và rất hiệu quả. Người ta nói rằng snowflake có thể tạo ra 260.000 ID mỗi giây.
Sau đây là mã nguồn của Snowflake ID trên C#
public class SnowflakeId {
// Start time cutoff ((New datetime (2020, 1, 1, 0, 0, datetimekind. UTC) -
// jan1st1970). Totalmilliseconds)
private const long twepoch = 1577836800000L;
// Number of digits occupied by machine ID
private const int workerIdBits = 5;
// Number of digits occupied by data ID
private const int datacenterIdBits = 5;
// The maximum machine ID supported, the result is 31 (this shift algorithm
// can quickly calculate the maximum decimal number that several binary numbers
// can represent)
private const long maxWorkerId = -1L ^ (-1L << workerIdBits);
// Maximum data ID supported, result is 31
private const long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// Number of digits of sequence in ID
private const int sequenceBits = 12;
// Move data ID 17 bits to the left (12 + 5)
private const int datacenterIdShift = sequenceBits + workerIdBits;
// Machine ID moved 12 bits to the left
private const int workerIdShift = sequenceBits;
// Time cut 22 bits left (5 + 5 + 12)
private const int timestampLeftShift =
sequenceBits + workerIdBits + datacenterIdBits;
// Mask for generating sequence, here is 4095 (0b111111111111 = 0xfff = 4095)
private const long sequenceMask = -1L ^ (-1L << sequenceBits);
// Data center ID (0-31)
public long datacenterId {
get;
private set;
}
// Working machine ID (0-31)
public long workerId {
get;
private set;
}
// Sequence in milliseconds (0-4095)
public long sequence {
get;
private set;
}
// Last ID generated by
public long lastTimestamp {
get;
private set;
}
///
// / snowflake ID
///
/// Data center ID
/// Working machine ID
public SnowflakeId(long datacenterId, long workerId) {
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new Exception(string.Format(
"datacenter Id can't be greater than {0} or less than 0",
maxDatacenterId));
}
if (workerId > maxWorkerId || workerId < 0) {
throw new Exception(string.Format(
"worker Id can't be greater than {0} or less than 0", maxWorkerId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
this.sequence = 0L;
this.lastTimestamp = -1L;
}
///
/// Get next ID
///
///
public long NextId() {
lock(this) {
long timestamp = GetCurrentTimestamp();
If(timestamp > lasttimestamp) // the timestamp changes and the sequence
// resets in milliseconds
{
sequence = 0L;
}
Else if (timestamp = = lasttimestamp) // if generated at the same time,
// sequence in milliseconds
{
sequence = (sequence + 1) & sequenceMask;
If(sequence = = 0) // sequence overflow in MS
{
Timestamp =
getnexttimestamp(lasttimestamp); // block to the next millisecond
// to get a new timestamp
}
}
Else // the current time is less than the time stamp generated by the
// last ID, which proves that the system clock has been called back.
// At this time, call back processing is required
{
sequence = (sequence + 1) & sequenceMask;
if (sequence > 0) {
Timestamp =
lasttimestamp; // stay on the last timestamp, wait for the system
// time to catch up, and then completely pass the
// clock callback problem.
}
Else // sequence overflow in milliseconds
{
Timestamp =
lasttimestamp + 1; // carry directly to the next millisecond
}
// throw new Exception(string.Format("Clock moved backwards. Refusing
// to generate id for {0} milliseconds", lastTimestamp - timestamp));
}
Lasttimestamp = timestamp; // last time to generate ID
// Shift and combine by or operation to form a 64 bit ID
var id = ((timestamp - twepoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) | sequence;
return id;
}
}
///
/// Resolve snowflake ID
///
///
public static string AnalyzeId(long Id) {
StringBuilder sb = new StringBuilder();
var timestamp = (Id >> timestampLeftShift);
var time = Jan1st1970.AddMilliseconds(timestamp + twepoch);
sb.Append(time.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss:fff"));
var datacenterId =
(Id ^ (timestamp << timestampLeftShift)) >> datacenterIdShift;
sb.Append("_" + datacenterId);
var workerId = (Id ^ ((timestamp << timestampLeftShift) |
(datacenterId << datacenterIdShift))) >>
workerIdShift;
sb.Append("_" + workerId);
var sequence = Id & sequenceMask;
sb.Append("_" + sequence);
return sb.ToString();
}
///
/// Blocks to the next millisecond until a new timestamp is obtained
///
/// Last ID generated by
/// Current timestamp
private static long GetNextTimestamp(long lastTimestamp) {
long timestamp = GetCurrentTimestamp();
while (timestamp <= lastTimestamp) {
timestamp = GetCurrentTimestamp();
}
return timestamp;
}
///
/// Get current timestamp
///
///
private static long GetCurrentTimestamp() {
return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds;
}
private static readonly DateTime Jan1st1970 =
new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
}