Whiz Tools

Генератор на Snowflake ID

Генератор на Snowflake ID

Optional: Unix timestamp in milliseconds (defaults to current time)

Генератор на Snowflake ID

Въведение

Snowflake ID е уникален идентификатор, използван в разпределени системи, първоначално разработен от Twitter. Този инструмент ви позволява да генерирате и анализирате Snowflake ID, които са 64-битови цели числа, съставени от времеви печат, идентификатор на машина и номер на последователност.

Как работят Snowflake ID

Snowflake ID са 64-битови цели числа, структурирани по следния начин:

  • 41 бит: Времеви печат (милисекунди от собствена епоха)
  • 10 бита: Идентификатор на машина (5 бита за идентификатор на дата център, 5 бита за идентификатор на работник)
  • 12 бита: Номер на последователност

Тази структура позволява генерирането на приблизително 4,096 уникални ID на милисекунда на машина.

Използване на генератора на Snowflake ID

  1. (По избор) Задайте собствена епоха (по подразбиране е епохата на Twitter: 2010-11-04T01:42:54.657Z)
  2. Въведете идентификатор на машина (0-31) и идентификатор на дата център (0-31)
  3. Щракнете върху "Генерирай", за да създадете нов Snowflake ID
  4. Генерираният ID и неговите компоненти ще бъдат показани

За да анализирате съществуващ Snowflake ID, въведете го в полето "Анализиране на ID" и щракнете върху "Анализиране".

Формула

Snowflake ID се конструира с помощта на битови операции:

ID = (timestamp << 22) | (datacenterId << 17) | (workerId << 12) | sequence

Където:

  • timestamp е броят на милисекундите от епохата
  • datacenterId е 5-битов цяло число (0-31)
  • workerId е 5-битов цяло число (0-31)
  • sequence е 12-битов цяло число (0-4095)

Изчисление

Генераторът на Snowflake ID извършва следните стъпки:

  1. Вземете текущия времеви печат в милисекунди
  2. Уверете се, че времевият печат е по-голям от последния използван времеви печат (за уникалност)
  3. Ако времевият печат е същият като последния, увеличете номера на последователността
  4. Ако номерът на последователността прелее (достигне 4096), изчакайте следващата милисекунда
  5. Комбинирайте компонентите с помощта на битови операции, за да създадете окончателния ID

Случаи на употреба

Snowflake ID са особено полезни в:

  1. Разпределени системи: Генерирайте уникални ID на множество машини без координация
  2. Високобройни данни: Създайте сортиращи ID за големи набори от данни
  3. Микросервизи: Осигурете уникални идентификатори между различни услуги
  4. Разделяне на бази данни: Използвайте времевия печат или компонента на идентификатора на машината за ефективно разделяне

Алтернативи

Докато Snowflake ID са мощни, други системи за генериране на ID включват:

  1. UUID (Универсален уникален идентификатор): Полезен, когато е необходимо разпределено генериране без сортиране
  2. Автоинкрементни идентификатори на бази данни: Прости, но ограничени до единични инстанции на бази данни
  3. ULID (Универсален уникален лексикографски сортиран идентификатор): Подобен на Snowflake, но с различна структура

Гранични случаи и ограничения

  1. Синхронизация на часовника: Snowflake ID разчитат на системното време. Ако часовникът се върне назад поради корекции на NTP или промени в лятното часово време, това може да предизвика проблеми с генерирането на ID.

  2. Проблемът с годината 2038: 41-битовият времеви печат ще прелее през 2079 (при условие, че епохата на Twitter). Системите, използващи Snowflake ID, трябва да планират за това.

  3. Колизии на идентификаторите на машините: В големи разпределени системи, осигуряването на уникални идентификатори на машините може да бъде предизвикателство и може да изисква допълнителна координация.

  4. Преливане на последователността: В изключително високообемни сценарии е възможно да се изчерпат 4096 последователности на милисекунда, което потенциално да предизвика забавяния.

  5. Ненадеждност на последователността между машините: Докато ID са монотонно нарастващи на една машина, те може да не са строго монотонни между множество машини.

История

Snowflake ID бяха въведени от Twitter през 2010 г. с цел да се задоволи нуждата от разпределени, времево-сортирани уникални идентификатори. Те оттогава са приети и адаптирани от много други компании и проекти.

Примери

Ето реализации на генератори на Snowflake ID на различни езици:

class SnowflakeGenerator {
  constructor(epoch = 1288834974657, datacenterIdBits = 5, workerIdBits = 5, sequenceBits = 12) {
    this.epoch = BigInt(epoch);
    this.datacenterIdBits = datacenterIdBits;
    this.workerIdBits = workerIdBits;
    this.sequenceBits = sequenceBits;
    this.maxDatacenterId = -1n ^ (-1n << BigInt(datacenterIdBits));
    this.maxWorkerId = -1n ^ (-1n << BigInt(workerIdBits));
    this.sequenceMask = -1n ^ (-1n << BigInt(sequenceBits));
    this.workerIdShift = BigInt(sequenceBits);
    this.datacenterIdShift = BigInt(sequenceBits + workerIdBits);
    this.timestampLeftShift = BigInt(sequenceBits + workerIdBits + datacenterIdBits);
    this.sequence = 0n;
    this.lastTimestamp = -1n;
  }

  nextId(datacenterId, workerId) {
    let timestamp = this.currentTimestamp();

    if (timestamp < this.lastTimestamp) {
      throw new Error('Часовникът се е преместил назад. Отказвам да генерирам ID');
    }

    if (timestamp === this.lastTimestamp) {
      this.sequence = (this.sequence + 1n) & this.sequenceMask;
      if (this.sequence === 0n) {
        timestamp = this.tilNextMillis(this.lastTimestamp);
      }
    } else {
      this.sequence = 0n;
    }

    this.lastTimestamp = timestamp;

    return ((timestamp - this.epoch) << this.timestampLeftShift) |
           (BigInt(datacenterId) << this.datacenterIdShift) |
           (BigInt(workerId) << this.workerIdShift) |
           this.sequence;
  }

  tilNextMillis(lastTimestamp) {
    let timestamp = this.currentTimestamp();
    while (timestamp <= lastTimestamp) {
      timestamp = this.currentTimestamp();
    }
    return timestamp;
  }

  currentTimestamp() {
    return BigInt(Date.now());
  }
}

// Използване
const generator = new SnowflakeGenerator();
const id = generator.nextId(1, 1);
console.log(`Генериран Snowflake ID: ${id}`);
import time
import threading

class SnowflakeGenerator:
    def __init__(self, datacenter_id, worker_id, sequence=0):
        self.datacenter_id = datacenter_id
        self.worker_id = worker_id
        self.sequence = sequence

        self.last_timestamp = -1
        self.epoch = 1288834974657

        self.datacenter_id_bits = 5
        self.worker_id_bits = 5
        self.sequence_bits = 12

        self.max_datacenter_id = -1 ^ (-1 << self.datacenter_id_bits)
        self.max_worker_id = -1 ^ (-1 << self.worker_id_bits)

        self.worker_id_shift = self.sequence_bits
        self.datacenter_id_shift = self.sequence_bits + self.worker_id_bits
        self.timestamp_left_shift = self.sequence_bits + self.worker_id_bits + self.datacenter_id_bits
        self.sequence_mask = -1 ^ (-1 << self.sequence_bits)

        self._lock = threading.Lock()

    def _til_next_millis(self, last_timestamp):
        timestamp = self._get_timestamp()
        while timestamp <= last_timestamp:
            timestamp = self._get_timestamp()
        return timestamp

    def _get_timestamp(self):
        return int(time.time() * 1000)

    def next_id(self):
        with self._lock:
            timestamp = self._get_timestamp()

            if timestamp < self.last_timestamp:
                raise ValueError("Часовникът се е преместил назад. Отказвам да генерирам ID")

            if timestamp == self.last_timestamp:
                self.sequence = (self.sequence + 1) & self.sequence_mask
                if self.sequence == 0:
                    timestamp = self._til_next_millis(self.last_timestamp)
            else:
                self.sequence = 0

            self.last_timestamp = timestamp

            return ((timestamp - self.epoch) << self.timestamp_left_shift) | \
                   (self.datacenter_id << self.datacenter_id_shift) | \
                   (self.worker_id << self.worker_id_shift) | \
                   self.sequence

## Използване
generator = SnowflakeGenerator(datacenter_id=1, worker_id=1)
snowflake_id = generator.next_id()
print(f"Генериран Snowflake ID: {snowflake_id}")
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SnowflakeGenerator {
    private final long epoch;
    private final long datacenterIdBits;
    private final long workerIdBits;
    private final long sequenceBits;
    private final long maxDatacenterId;
    private final long maxWorkerId;
    private final long workerIdShift;
    private final long datacenterIdShift;
    private final long timestampLeftShift;
    private final long sequenceMask;

    private long datacenterId;
    private long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    private final Lock lock = new ReentrantLock();

    public SnowflakeGenerator(long datacenterId, long workerId) {
        this.epoch = 1288834974657L;
        this.datacenterIdBits = 5L;
        this.workerIdBits = 5L;
        this.sequenceBits = 12L;

        this.maxDatacenterId = ~(-1L << datacenterIdBits);
        this.maxWorkerId = ~(-1L << workerIdBits);

        this.workerIdShift = sequenceBits;
        this.datacenterIdShift = sequenceBits + workerIdBits;
        this.timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        this.sequenceMask = ~(-1L << sequenceBits);

        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than maxDatacenterId or less than 0");
        }
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("workerId can't be greater than maxWorkerId or less than 0");
        }
        this.datacenterId = datacenterId;
        this.workerId = workerId;
    }

    public long nextId() {
        lock.lock();
        try {
            long timestamp = timeGen();
            if (timestamp < lastTimestamp) {
                throw new RuntimeException("Часовникът се е преместил назад. Отказвам да генерирам ID");
            }

            if (lastTimestamp == timestamp) {
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }

            lastTimestamp = timestamp;

            return ((timestamp - epoch) << timestampLeftShift) |
                   (datacenterId << datacenterIdShift) |
                   (workerId << workerIdShift) |
                   sequence;
        } finally {
            lock.unlock();
        }
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        SnowflakeGenerator generator = new SnowflakeGenerator(1, 1);
        long id = generator.nextId();
        System.out.println("Генериран Snowflake ID: " + id);
    }
}
require 'time'

class SnowflakeGenerator
  def initialize(datacenter_id, worker_id, sequence = 0)
    @datacenter_id = datacenter_id
    @worker_id = worker_id
    @sequence = sequence
    @last_timestamp = -1
    @epoch = 1288834974657

    @datacenter_id_bits = 5
    @worker_id_bits = 5
    @sequence_bits = 12

    @max_datacenter_id = -1 ^ (-1 << @datacenter_id_bits)
    @max_worker_id = -1 ^ (-1 << @worker_id_bits)

    @worker_id_shift = @sequence_bits
    @datacenter_id_shift = @sequence_bits + @worker_id_bits
    @timestamp_left_shift = @sequence_bits + @worker_id_bits + @datacenter_id_bits
    @sequence_mask = -1 ^ (-1 << @sequence_bits)
  end

  def next_id
    timestamp = (Time.now.to_f * 1000).to_i

    raise 'Часовникът се е преместил назад' if timestamp < @last_timestamp

    if timestamp == @last_timestamp
      @sequence = (@sequence + 1) & @sequence_mask
      timestamp = til_next_millis(@last_timestamp) if @sequence == 0
    else
      @sequence = 0
    end

    @last_timestamp = timestamp

    ((timestamp - @epoch) << @timestamp_left_shift) |
      (@datacenter_id << @datacenter_id_shift) |
      (@worker_id << @worker_id_shift) |
      @sequence
  end

  private

  def til_next_millis(last_timestamp)
    timestamp = (Time.now.to_f * 1000).to_i
    timestamp = (Time.now.to_f * 1000).to_i while timestamp <= last_timestamp
    timestamp
  end
end

## Използване
generator = SnowflakeGenerator.new(1, 1)
snowflake_id = generator.next_id
puts "Генериран Snowflake ID: #{snowflake_id}"
<?php

class SnowflakeGenerator {
    private $epoch;
    private $datacenterIdBits;
    private $workerIdBits;
    private $sequenceBits;
    private $maxDatacenterId;
    private $maxWorkerId;
    private $workerIdShift;
    private $datacenterIdShift;
    private $timestampLeftShift;
    private $sequenceMask;

    private $datacenterId;
    private $workerId;
    private $sequence = 0;
    private $lastTimestamp = -1;

    public function __construct($datacenterId, $workerId) {
        $this->epoch = 1288834974657;
        $this->datacenterIdBits = 5;
        $this->workerIdBits = 5;
        $this->sequenceBits = 12;

        $this->maxDatacenterId = -1 ^ (-1 << $this->datacenterIdBits);
        $this->maxWorkerId = -1 ^ (-1 << $this->workerIdBits);

        $this->workerIdShift = $this->sequenceBits;
        $this->datacenterIdShift = $this->sequenceBits + $this->workerIdBits;
        $this->timestampLeftShift = $this->sequenceBits + $this->workerIdBits + $this->datacenterIdBits;
        $this->sequenceMask = -1 ^ (-1 << $this->sequenceBits);

        if ($datacenterId > $this->maxDatacenterId || $datacenterId < 0) {
            throw new Exception("datacenterId не може да бъде по-голям от maxDatacenterId или по-малък от 0");
        }
        if ($workerId > $this->maxWorkerId || $workerId < 0) {
            throw new Exception("workerId не може да бъде по-голям от maxWorkerId или по-малък от 0");
        }
        $this->datacenterId = $datacenterId;
        $this->workerId = $workerId;
    }

    public function nextId() {
        $timestamp = $this->timeGen();

        if ($timestamp < $this->lastTimestamp) {
            throw new Exception("Часовникът се е преместил назад. Отказвам да генерирам ID");
        }

        if ($this->lastTimestamp == $timestamp) {
            $this->sequence = ($this->sequence + 1) & $this->sequenceMask;
            if ($this->sequence == 0) {
                $timestamp = $this->tilNextMillis($this->lastTimestamp);
            }
        } else {
            $this->sequence = 0;
        }

        $this->lastTimestamp = $timestamp;

        return (($timestamp - $this->epoch) << $this->timestampLeftShift) |
               ($this->datacenterId << $this->datacenterIdShift) |
               ($this->workerId << $this->workerIdShift) |
               $this->sequence;
    }

    private function tilNextMillis($lastTimestamp) {
        $timestamp = $this->timeGen();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->timeGen();
        }
        return $timestamp;
    }

    private function timeGen() {
        return floor(microtime(true) * 1000);
    }
}

// Използване
$generator = new SnowflakeGenerator(1, 1);
$id = $generator->nextId();
echo "Генериран Snowflake ID: " . $id . "\n";
using System;
using System.Threading;

public class SnowflakeGenerator
{
    private readonly long _epoch;
    private readonly int _datacenterIdBits;
    private readonly int _workerIdBits;
    private readonly int _sequenceBits;
    private readonly long _maxDatacenterId;
    private readonly long _maxWorkerId;
    private long _workerId;
    private long _datacenterId;
    private long _sequence = 0L;
    private long _lastTimestamp = -1L;

    private readonly object _lock = new object();

    public SnowflakeGenerator(long datacenterId, long workerId)
    {
        _epoch = 1288834974657L;
        _datacenterIdBits = 5;
        _workerIdBits = 5;
        _sequenceBits = 12;

        _maxDatacenterId = -1L ^ (-1L << _datacenterIdBits);
        _maxWorkerId = -1L ^ (-1L << _workerIdBits);

        _workerIdShift = _sequenceBits;
        _datacenterIdShift = _sequenceBits + _workerIdBits;
        _timestampLeftShift = _sequenceBits + _workerIdBits + _datacenterIdBits;
        _sequenceMask = -1L ^ (-1L << _sequenceBits);

        if (datacenterId > _maxDatacenterId || datacenterId < 0)
        {
            throw new ArgumentException($"datacenterId не може да бъде по-голям от {_maxDatacenterId} или по-малък от 0");
        }
        if (workerId > _maxWorkerId || workerId < 0)
        {
            throw new ArgumentException($"workerId не може да бъде по-голям от {_maxWorkerId} или по-малък от 0");
        }
        _datacenterId = datacenterId;
        _workerId = workerId;
    }

    public long NextId()
    {
        lock (_lock)
        {
            var timestamp = TimeGen();

            if (timestamp < _lastTimestamp)
            {
                throw new Exception("Часовникът се е преместил назад. Отказвам да генерирам ID");
            }

            if (_lastTimestamp == timestamp)
            {
                _sequence = (_sequence + 1) & _sequenceMask;
                if (_sequence == 0)
                {
                    timestamp = TilNextMillis(_lastTimestamp);
                }
            }
            else
            {
                _sequence = 0L;
            }

            _lastTimestamp = timestamp;

            return ((timestamp - _epoch) << _timestampLeftShift) |
                   (_datacenterId << _datacenterIdShift) |
                   (_workerId << _workerIdShift) |
                   _sequence;
        }
    }

    private long TilNextMillis(long lastTimestamp)
    {
        var timestamp = TimeGen();
        while (timestamp <= lastTimestamp)
        {
            timestamp = TimeGen();
        }
        return timestamp;
    }

    private long TimeGen()
    {
        return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
    }
}

// Използване
class Program
{
    static void Main(string[] args)
    {
        var generator = new SnowflakeGenerator(1, 1);
        var id = generator.NextId();
        Console.WriteLine($"Генериран Snowflake ID: {id}");
    }
}
package main

import (
	"fmt"
	"sync"
	"time"
)

type SnowflakeGenerator struct {
	epoch             int64
	datacenterIdBits  uint
	workerIdBits      uint
	sequenceBits      uint
	maxDatacenterId   int64
	maxWorkerId       int64
	workerIdShift     uint
	datacenterIdShift uint
	timestampLeftShift uint
	sequenceMask      int64

	datacenterId int64
	workerId     int64
	sequence     int64
	lastTimestamp int64

	lock sync.Mutex
}

func NewSnowflakeGenerator(datacenterId, workerId int64) (*SnowflakeGenerator, error) {
	g := &SnowflakeGenerator{
		epoch:             1288834974657,
		datacenterIdBits:  5,
		workerIdBits:      5,
		sequenceBits:      12,
		lastTimestamp:     -1,
	}

	g.maxDatacenterId = -1 ^ (-1 << g.datacenterIdBits)
	g.maxWorkerId = -1 ^ (-1 << g.workerIdBits)

	g.workerIdShift = g.sequenceBits
	g.datacenterIdShift = g.sequenceBits + g.workerIdBits
	g.timestampLeftShift = g.sequenceBits + g.workerIdBits + g.datacenterIdBits
	g.sequenceMask = -1 ^ (-1 << g.sequenceBits)

	if datacenterId > g.maxDatacenterId || datacenterId < 0 {
		return nil, fmt.Errorf("datacenterId не може да бъде по-голям от %d или по-малък от 0", g.maxDatacenterId)
	}
	if workerId > g.maxWorkerId || workerId < 0 {
		return nil, fmt.Errorf("workerId не може да бъде по-голям от %d или по-малък от 0", g.maxWorkerId)
	}
	g.datacenterId = datacenterId
	g.workerId = workerId

	return g, nil
}

func (g *SnowflakeGenerator) NextId() (int64, error) {
	g.lock.Lock()
	defer g.lock.Unlock()

	timestamp := g.timeGen()

	if timestamp < g.lastTimestamp {
		return 0, fmt.Errorf("часовникът се е преместил назад, отказвам да генерирам ID")
	}

	if g.lastTimestamp == timestamp {
		g.sequence = (g.sequence + 1) & g.sequenceMask
		if g.sequence == 0 {
			timestamp = g.tilNextMillis(g.lastTimestamp)
		}
	} else {
		g.sequence = 0
	}

	g.lastTimestamp = timestamp

	return ((timestamp - g.epoch) << g.timestampLeftShift) |
		(g.datacenterId << g.datacenterIdShift) |
		(g.workerId << g.workerIdShift) |
		g.sequence, nil
}

func (g *SnowflakeGenerator) tilNextMillis(lastTimestamp int64) int64 {
	timestamp := g.timeGen()
	for timestamp <= lastTimestamp {
		timestamp = g.timeGen()
	}
	return timestamp
}

func (g *SnowflakeGenerator) timeGen() int64 {
	return time.Now().UnixNano() / int64(time.Millisecond)
}

func main() {
	generator, err := NewSnowflakeGenerator(1, 1)
	if err != nil {
		fmt.Printf("Грешка при създаване на генератора: %v\n", err)
		return
	}

	id, err := generator.NextId()
	if err != nil {
		fmt.Printf("Грешка при генериране на ID: %v\n", err)
		return
	}

	fmt.Printf("Генериран Snowflake ID: %d\n", id)
}

Диаграма

Ето визуално представяне на структурата на Snowflake ID:

Времеви печат (41 бита) Идентификатор на машина (10 бита) Последователност (12 бита)

64-битова структура на Snowflake ID

Референции

  1. "Обявяване на Snowflake." Блог на Twitter Engineering, https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake
  2. "Snowflake ID." Уикипедия, https://en.wikipedia.org/wiki/Snowflake_ID
  3. "Разпределено генериране на ID в микросервизи." Medium, https://medium.com/swlh/distributed-id-generation-in-microservices-b6ce9a8dd93f
Обратна връзка