גנרטור מזהי סנופלייק לניתוח ויצירת מזהים ייחודיים
צור ונתח מזהי סנופלייק של טוויטר, מזהים ייחודיים בגודל 64 ביט המשמשים במערכות מבוזרות. כלי זה מאפשר לך ליצור מזהי סנופלייק חדשים ולנתח קיימים, תוך מתן תובנות לגבי רכיבי הזמן, מזהה המחשב ומספר הרצף שלהם.
גנרטור מזהי סנופלייק
גנרטור מזהי סנופלייק
תיעוד
1## מחולל מזהי סנופלייק
2
3### הקדמה
4
5מזהה סנופלייק הוא מזהה ייחודי בשימוש במערכות מבוזרות, שפותח במקור על ידי טוויטר. כלי זה מאפשר לך לייצר ולנתח מזהי סנופלייק, שהם מספרים שלמים בני 64 סיביות המורכבים מזמן, מזהה מכונה ומספר רצף.
6
7### איך מזהי סנופלייק פועלים
8
9מזהי סנופלייק הם מספרים שלמים בני 64 סיביות, המובנים כך:
10
11- 41 סיביות: זמן (מילישניות מאז אפוק מותאם אישית)
12- 10 סיביות: מזהה מכונה (5 סיביות עבור מזהה מרכז נתונים, 5 סיביות עבור מזהה עובד)
13- 12 סיביות: מספר רצף
14
15מבנה זה מאפשר ייצור של כ-4,096 מזהים ייחודיים לכל מילישנייה לכל מכונה.
16
17### שימוש במחלל מזהי סנופלייק
18
191. (אופציונלי) הגדר אפוק מותאם אישית (ברירת המחדל היא אפוק של טוויטר: 2010-11-04T01:42:54.657Z)
202. הזן מזהה מכונה (0-31) ומזהה מרכז נתונים (0-31)
213. לחץ על "ייצר" כדי ליצור מזהה סנופלייק חדש
224. המזהה שנוצר ורכיביו יוצגו
23
24כדי לנתח מזהה סנופלייק קיים, הזן אותו בשדה "נתח מזהה" ולחץ על "נתח".
25
26### נוסחה
27
28מזהה הסנופלייק נבנה באמצעות פעולות בייט:
29
30
plaintext ID = (timestamp << 22) | (datacenterId << 17) | (workerId << 12) | sequence
1
2כאשר:
3- `timestamp` הוא מספר המילישניות מאז האפוק
4- `datacenterId` הוא מספר שלם בן 5 סיביות (0-31)
5- `workerId` הוא מספר שלם בן 5 סיביות (0-31)
6- `sequence` הוא מספר שלם בן 12 סיביות (0-4095)
7
8### חישוב
9
10מחולל מזהי הסנופלייק מבצע את הצעדים הבאים:
11
121. קבלת הזמן הנוכחי במילישניות
132. לוודא שהזמן גדול מהזמן האחרון בשימוש (למען הייחודיות)
143. אם הזמן זהה לאחרון, הגדל את מספר הרצף
154. אם מספר הרצף חורג (מגיע ל-4096), המתן למילישנייה הבאה
165. שילוב הרכיבים באמצעות פעולות בייט כדי ליצור את המזהה הסופי
17
18### שימושים
19
20מזהי סנופלייק שימושיים במיוחד ב:
21
221. מערכות מבוזרות: ייצור מזהים ייחודיים על פני מכונות מרובות ללא תיאום
232. נתוני נפח גבוה: יצירת מזהים ניתנים למיון עבור קבוצות נתונים גדולות
243. מיקרו-שירותים: הבטחת מזהים ייחודיים בין שירותים שונים
254. חיתוך בסיסי נתונים: שימוש ברכיב הזמן או מזהה המכונה לחיתוך יעיל
26
27#### אלטרנטיבות
28
29בעוד שמזהי סנופלייק הם חזקים, מערכות ייצור מזהים אחרות כוללות:
30
311. UUID (מזהה ייחודי אוניברסלי): שימושי כאשר נדרשת יצירה מבוזרת ללא יכולת למיון
322. מזהי בסיס נתונים שהולכים וגדלים: פשוטים אך מוגבלים לאינסטנציות בסיס נתונים בודדות
333. ULID (מזהה ייחודי אוניברסלי שניתן למיון): דומה לסנופלייק, אך עם מבנה שונה
34
35### מקרים קצה ומגבלות
36
371. סנכרון שעונים: מזהי סנופלייק תלויים בזמן המערכת. אם השעון זז אחורה עקב התאמות NTP או שינויים בשעון קיץ, זה יכול לגרום לבעיות בייצור מזהים.
38
392. בעיית השנה 2038: חותמת ה-41 סיביות תגרום ל-overflow בשנת 2079 (בהנחה שהאפוק של טוויטר). מערכות המשתמשות במזהי סנופלייק צריכות לתכנן את המקרה הזה.
40
413. התנגשויות במזהי מכונה: במערכות מבוזרות גדולות, הבטחת מזהי מכונה ייחודיים יכולה להיות מאתגרת ולדרוש תיאום נוסף.
42
434. חרגת רצף: בתרחישים עם קצב גבוה מאוד, יתכן שניתן יהיה למצות את 4096 הרצפים למילישנייה, מה שעלול לגרום לעיכובים.
44
455. אי-מונוטוניות בין מכונות: בעוד שהמזהים הולכים וגדלים במכונה אחת, הם עשויים שלא להיות מונוטוניים לחלוטין בין מכונות מרובות.
46
47### היסטוריה
48
49מזהי סנופלייק הוצגו על ידי טוויטר בשנת 2010 כדי לענות על הצורך במזהים ייחודיים, ניתנים למיון בזמן, מבוזרים. מאז הם אומצו והתאימו על ידי חברות ופרויקטים רבים אחרים.
50
51### דוגמאות
52
53הנה יישומים של מחוללי מזהי סנופלייק בשפות שונות:
54
55
javascript 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('השעון זז אחורה. מסרב לייצר מזהה');
}
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(מזהה סנופלייק שנוצר: ${id}
);
1
2
python 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("השעון זז אחורה. מסרב לייצר מזהה")
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}")
1
2
java 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("מזהה מרכז הנתונים לא יכול להיות גדול מהמקסימום או קטן מ-0");
}
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException("מזהה העובד לא יכול להיות גדול מהמקסימום או קטן מ-0");
}
this.datacenterId = datacenterId;
this.workerId = workerId;
}
public long nextId() {
lock.lock();
try {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException("השעון זז אחורה. מסרב לייצר מזהה");
}
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("מזהה סנופלייק שנוצר: " + id);
}
}
1
2
ruby 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}"
1
2
php
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("מזהה מרכז הנתונים לא יכול להיות גדול מהמקסימום או קטן מ-0"); } if ($workerId > $this->maxWorkerId || $workerId < 0) { throw new Exception("מזהה העובד לא יכול להיות גדול מהמקסימום או קטן מ-0"); } $this->datacenterId = $datacenterId; $this->workerId = $workerId; } public function nextId() { $timestamp = $this->timeGen(); if ($timestamp < $this->lastTimestamp) { throw new Exception("השעון זז אחורה. מסרב לייצר מזהה"); } 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 "מזהה סנופלייק שנוצר: " . $id . "\n";1
2
csharp 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($"מזהה מרכז הנתונים לא יכול להיות גדול מ{_maxDatacenterId} או קטן מ-0");
}
if (workerId > _maxWorkerId || workerId < 0)
{
throw new ArgumentException($"מזהה העובד לא יכול להיות גדול מ{_maxWorkerId} או קטן מ-0");
}
_datacenterId = datacenterId;
_workerId = workerId;
}
public long NextId()
{
lock (_lock)
{
var timestamp = TimeGen();
if (timestamp < _lastTimestamp)
{
throw new Exception("השעון זז אחורה. מסרב לייצר מזהה");
}
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($"מזהה סנופלייק שנוצר: {id}"); } }
1
2
go 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("מזהה מרכז הנתונים לא יכול להיות גדול מ%d או קטן מ-0", g.maxDatacenterId)
}
if workerId > g.maxWorkerId || workerId < 0 {
return nil, fmt.Errorf("מזהה העובד לא יכול להיות גדול מ%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("השעון זז אחורה, מסרב לייצר מזהה")
}
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("שגיאה ביצירת מזהה: %v\n", err)
return
}
fmt.Printf("מזהה סנופלייק שנוצר: %d\n", id)
}
1
2### דיאגרמה
3
4הנה ייצוג חזותי של מבנה מזהה הסנופלייק:
5
6<svg width="600" height="100" xmlns="http://www.w3.org/2000/svg">
7 <rect x="0" y="0" width="380" height="50" fill="#4299e1"/>
8 <text x="190" y="30" font-family="Arial" fontSize="14" fill="white" textAnchor="middle">זמן (41 סיביות)</text>
9
10 <rect x="380" y="0" width="90" height="50" fill="#48bb78"/>
11 <text x="425" y="30" font-family="Arial" fontSize="14" fill="white" textAnchor="middle">מזהה מכונה (10 סיביות)</text>
12
13 <rect x="470" y="0" width="130" height="50" fill="#ed8936"/>
14 <text x="535" y="30" font-family="Arial" fontSize="14" fill="white" textAnchor="middle">מספר רצף (12 סיביות)</text>
15
16 <text x="300" y="80" font-family="Arial" fontSize="16" fill="black" textAnchor="middle">מבנה מזהה סנופלייק בן 64 סיביות</text>
17</svg>
18
19### הפניות
20
211. "הכרזה על סנופלייק." בלוג ההנדסה של טוויטר, https://blog.twitter.com/engineering/en_us/a/2010/announcing-snowflake
222. "מזהה סנופלייק." ויקיפדיה, https://en.wikipedia.org/wiki/Snowflake_ID
233. "ייצור מזהים מבוזרים במיקרו-שירותים." מידיום, https://medium.com/swlh/distributed-id-generation-in-microservices-b6ce9a8dd93f
24
משוב
לחץ על טוסט המשוב כדי להתחיל לתת משוב על כלי זה
כלים קשורים
גלה עוד כלים שעשויים להיות מועילים עבור זרימת העבודה שלך