Whiz Tools

Sniegpārsla ID ģenerators

Sniegpārsla ID ģenerators

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

Snoflake ID ģenerators

Ievads

Snoflake ID ir unikāls identifikators, ko izmanto izplatītajās sistēmās, sākotnēji izstrādājusi Twitter. Šis rīks ļauj ģenerēt un analizēt snoflake ID, kas ir 64 bitu veseli skaitļi, kas sastāv no laika zīmoga, mašīnas ID un secības numura.

Kā darbojas snoflake ID

Snoflake ID ir 64 bitu veseli skaitļi, kuru struktūra ir šāda:

  • 41 bits: Laika zīmogs (milisekundes kopš pielāgota laikmeta)
  • 10 bits: Mašīnas ID (5 bits datu centra ID, 5 bits darba ID)
  • 12 bits: Secības numurs

Šī struktūra ļauj ģenerēt aptuveni 4,096 unikālus ID katrā milisekundē katrai mašīnai.

Snoflake ID ģeneratora izmantošana

  1. (Pēc izvēles) Iestatiet pielāgotu laikmetu (noklusējums ir Twitter laikmets: 2010-11-04T01:42:54.657Z)
  2. Ievadiet mašīnas ID (0-31) un datu centra ID (0-31)
  3. Noklikšķiniet uz "Ģenerēt", lai izveidotu jaunu snoflake ID
  4. Tiks parādīts ģenerētais ID un tā komponenti

Lai analizētu esošo snoflake ID, ievadiet to laukā "Analizēt ID" un noklikšķiniet uz "Analizēt".

Formula

Snoflake ID tiek konstruēts, izmantojot bitu operācijas:

ID = (laika_zīmogs << 22) | (datu_centra_ID << 17) | (darba_ID << 12) | secība

Kur:

  • laika_zīmogs ir milisekundēs kopš laikmeta
  • datu_centra_ID ir 5 bitu vesels skaitlis (0-31)
  • darba_ID ir 5 bitu vesels skaitlis (0-31)
  • secība ir 12 bitu vesels skaitlis (0-4095)

Aprēķins

Snoflake ID ģenerators veic šādas darbības:

  1. Iegūst pašreizējo laika zīmogu milisekundēs
  2. Nodrošina, ka laika zīmogs ir lielāks par pēdējo izmantoto laika zīmogu (unikalitātei)
  3. Ja laika zīmogs ir tāds pats kā iepriekšējais, palielina secības numuru
  4. Ja secības numurs pārsniedz (sasniedz 4096), gaida nākamo milisekundi
  5. Apvieno komponentus, izmantojot bitu operācijas, lai izveidotu galīgo ID

Lietošanas gadījumi

Snoflake ID ir īpaši noderīgi:

  1. Izplatītajās sistēmās: ģenerēt unikālus ID vairākās mašīnās bez koordinācijas
  2. Augsta apjoma datiem: izveidot kārtotus ID lieliem datu kopām
  3. Mikroservisos: nodrošināt unikālus identifikatorus dažādām pakalpojumu daļām
  4. Datu bāzes dalīšanā: izmantot laika zīmoga vai mašīnas ID komponentu efektīvai dalīšanai

Alternatīvas

Lai gan snoflake ID ir jaudīgi, citi ID ģenerēšanas sistēmas ietver:

  1. UUID (Universāli unikāls identifikators): noderīgs, kad nepieciešama izplatīta ģenerēšana bez kārtotības
  2. Automātiski pieaugoši datu bāzes ID: vienkārši, bet ierobežoti uz vienas datu bāzes instancēm
  3. ULID (Universāli unikāli leksikogrāfiski kārtoti identifikatori): līdzīgi snoflake, bet ar citu struktūru

Malu gadījumi un ierobežojumi

  1. Pulksteņa sinhronizācija: snoflake ID paļaujas uz sistēmas laiku. Ja pulkstenis atgriežas atpakaļ NTP korekciju vai vasaras laika izmaiņu dēļ, tas var radīt problēmas ID ģenerēšanā.

    1. gada problēma: 41 bitu laika zīmogs pārsniegs 2079. gadā (pieņemot Twitter laikmetu). Sistēmām, kas izmanto snoflake ID, vajadzētu plānot šo iespēju.
  2. Mašīnas ID sadursmes: lielās izplatītās sistēmās unikālu mašīnas ID nodrošināšana var būt izaicinājums un var prasīt papildu koordināciju.

  3. Secības pārsniegšana: ārkārtīgi augstas caurlaidības scenārijos ir iespējams izsīkt 4096 secības katrā milisekundē, kas potenciāli var izraisīt kavēšanos.

  4. Ne-monotonitāte starp mašīnām: Lai gan ID ir monotoniski pieaugoši vienā mašīnā, tie var nebūt stingri monotoni vairākās mašīnās.

Vēsture

Snoflake ID tika ieviesti Twitter 2010. gadā, lai apmierinātu nepieciešamību pēc izplatītiem, laika kārtotiem unikāliem identifikatoriem. Tie kopš tā laika ir pieņemti un pielāgoti daudzu citu uzņēmumu un projektu.

Piemēri

Šeit ir snoflake ID ģeneratoru īstenojumi dažādās valodās:

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('Pulkstenis pārvietojies atpakaļ. Atsakāmies ģenerēt 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());
  }
}

// Lietošana
const generator = new SnowflakeGenerator();
const id = generator.nextId(1, 1);
console.log(`Ģenerētais snoflake 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("Pulkstenis pārvietojies atpakaļ. Atsakāmies ģenerēt 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

## Lietošana
generator = SnowflakeGenerator(datacenter_id=1, worker_id=1)
snowflake_id = generator.next_id()
print(f"Ģenerētais snoflake 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("datu_centra_ID nevar būt lielāks par maksimālo datu centra ID vai mazāks par 0");
        }
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("darba_ID nevar būt lielāks par maksimālo darba ID vai mazāks par 0");
        }
        this.datacenterId = datacenterId;
        this.workerId = workerId;
    }

    public long nextId() {
        lock.lock();
        try {
            long timestamp = timeGen();
            if (timestamp < lastTimestamp) {
                throw new RuntimeException("Pulkstenis pārvietojies atpakaļ. Atsakāmies ģenerēt 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("Ģenerētais snoflake 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)

    @lock = Mutex.new
  end

  def next_id
    @lock.synchronize do
      timestamp = (Time.now.to_f * 1000).to_i

      raise 'Pulkstenis pārvietojies atpakaļ' 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
  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

## Lietošana
generator = SnowflakeGenerator.new(1, 1)
snowflake_id = generator.next_id
puts "Ģenerētais snoflake 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("datu_centra_ID nevar būt lielāks par maksimālo datu centra ID vai mazāks par 0");
        }
        if ($workerId > $this->maxWorkerId || $workerId < 0) {
            throw new Exception("darba_ID nevar būt lielāks par maksimālo darba ID vai mazāks par 0");
        }
        $this->datacenterId = $datacenterId;
        $this->workerId = $workerId;
    }

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

        if ($timestamp < $this->lastTimestamp) {
            throw new Exception("Pulkstenis pārvietojies atpakaļ. Atsakāmies ģenerēt 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);
    }
}

// Lietošana
$generator = new SnowflakeGenerator(1, 1);
$id = $generator->nextId();
echo "Ģenerētais snoflake 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 readonly int _workerIdShift;
    private readonly int _datacenterIdShift;
    private readonly int _timestampLeftShift;
    private readonly long _sequenceMask;

    private readonly long _datacenterId;
    private readonly long _workerId;
    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($"datu_centra_ID nevar būt lielāks par {_maxDatacenterId} vai mazāks par 0");
        }
        if (workerId > _maxWorkerId || workerId < 0)
        {
            throw new ArgumentException($"darba_ID nevar būt lielāks par {_maxWorkerId} vai mazāks par 0");
        }
        _datacenterId = datacenterId;
        _workerId = workerId;
    }

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

            if (timestamp < _lastTimestamp)
            {
                throw new Exception("Pulkstenis pārvietojies atpakaļ. Atsakāmies ģenerēt 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();
    }
}

// Lietošana
class Program
{
    static void Main(string[] args)
    {
        var generator = new SnowflakeGenerator(1, 1);
        var id = generator.NextId();
        Console.WriteLine($"Ģenerētais snoflake 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("datu_centra_ID nevar būt lielāks par %d vai mazāks par 0", g.maxDatacenterId)
	}
	if workerId > g.maxWorkerId || workerId < 0 {
		return nil, fmt.Errorf("darba_ID nevar būt lielāks par %d vai mazāks par 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("pulkstenis pārvietojies atpakaļ, atsakāmies ģenerēt 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("Kļūda, veidojot ģeneratoru: %v\n", err)
		return
	}

	id, err := generator.NextId()
	if err != nil {
		fmt.Printf("Kļūda, ģenerējot ID: %v\n", err)
		return
	}

	fmt.Printf("Ģenerētais snoflake ID: %d\n", id)
}

Diagramma

Šeit ir vizuāla snoflake ID struktūras attēlošana:

Laika zīmogs (41 bits) Mašīnas ID (10 bits) Secība (12 bits)

64 bitu snoflake ID struktūra

Atsauces

  1. "Snoflake paziņojums." Twitter inženierijas blogs, https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake
  2. "Snoflake ID." Vikipēdija, https://en.wikipedia.org/wiki/Snowflake_ID
  3. "Izplatīta ID ģenerēšana mikroservisos." Medium, https://medium.com/swlh/distributed-id-generation-in-microservices-b6ce9a8dd93f
Atsauksmes