Trình tạo UUID v1
Tạo UUID v1 dựa trên thời gian với dấu thời gian nhúng
…
Định dạng
UUID v1 là gì?
UUID v1 là phiên bản UUID gốc, được chuẩn hóa trong RFC 4122 (2005). UUID v1 tạo ra các định danh duy nhất bằng cách kết hợp timestamp độ chính xác cao với địa chỉ MAC của máy chủ tạo.
Vì timestamp được nhúng vào, các giá trị UUID v1 từ cùng máy chủ tăng đơn điệu theo thời gian — làm cho chúng tự nhiên có thứ tự.
Ngày nay UUID v1 phần lớn đã bị thay thế bởi UUID v7 (có thể sắp xếp, không rò rỉ MAC) và UUID v4 (hoàn toàn ngẫu nhiên, riêng tư). UUID v1 vẫn được sử dụng trong các hệ thống như Apache Cassandra.
Cấu trúc UUID v1
Chuỗi UUID v1 như 550e8400-e29b-11d4-a716-446655440000 mã hóa sáu trường khác nhau:
| Trường | Kích thước | Mô tả |
|---|---|---|
| time_low | 32 bits | Trường thấp 32-bit của timestamp Gregorian 60-bit (khoảng cách 100 nano giây kể từ ngày 15 tháng 10 năm 1582) |
| time_mid | 16 bits | Trường giữa 16-bit của timestamp 60-bit |
| time_hi_and_version | 16 bits | 12 bit trên của timestamp 60-bit cộng số phiên bản 4-bit (luôn là <code>1</code>) |
| clock_seq_hi_res | 8 bits | Trường cao 6-bit của chuỗi đồng hồ kết hợp với dấu biến thể RFC 4122 2-bit |
| clock_seq_low | 8 bits | 8 bit thấp hơn của chuỗi đồng hồ |
| node | 48 bits | Định danh node 48-bit — thường là địa chỉ MAC của giao diện mạng tạo |
Trường chuỗi đồng hồ là bộ đếm 14-bit. Nó được tăng lên mỗi khi đồng hồ hệ thống chạy lùi.
Giải mã Timestamp UUID v1
Timestamp 60-bit được phân tán qua ba trường trong UUID. Để tái tạo thời gian tạo:
- Trích xuất
time_low(byte 0–3),time_mid(byte 4–5), vàtime_hi(byte 6–7, trừ nibble phiên bản) - Lắp ráp lại:
(time_hi << 48) | (time_mid << 32) | time_low - Kết quả là số đếm 60-bit của
khoảng cách 100 nano giâykể từ ngày 15 tháng 10 năm 1582 (epoch lịch Gregorian) - Trừ độ lệch Gregorian-sang-Unix: 122.192.928.000.000.000
- Chia cho
10.000để chuyển đổi khoảng cách 100 nano giây sang mili giây - Sử dụng kết quả làm
timestamp mili giây Unixđể tạo đối tượng Date - Định dạng thành
ISO 8601để xuất ra dạng con người đọc được
Độ chính xác timestamp là 100 nano giây — tinh hơn nhiều so với độ chính xác mili giây của UUID v7.
Lo ngại Quyền riêng tư
Nhược điểm đáng kể nhất của UUID v1 là nó nhúng địa chỉ MAC của máy chủ tạo vào trường node.
Kẻ tấn công có được UUID v1 có thể xác định: (1) thời điểm tạo ID gần đúng, (2) địa chỉ MAC của máy chủ tạo, và (3) bằng cách phân tích nhiều UUID, tốc độ tạo ID.
Vì lý do này, UUID v1 không bao giờ nên được sử dụng làm định danh hướng công khai trừ khi bạn thoải mái khi tiết lộ thông tin này.
Khi nào UUID v1 Vẫn Phù hợp
UUID v1 so với UUID v7
UUID v7 là người kế thừa hiện đại của UUID v1 cho các định danh theo thứ tự thời gian:
| Khía cạnh | UUID v1 | UUID v7 |
|---|---|---|
| Epoch / Cơ sở Thời gian | Epoch Gregorian (15 tháng 10 năm 1582) | Epoch Unix (1 tháng 1 năm 1970) |
| Độ chính xác | 100 nano giây | 1 mili giây |
| Định danh Node | Địa chỉ MAC (rò rỉ danh tính máy chủ) | Ngẫu nhiên (riêng tư) |
| Quyền riêng tư | Rò rỉ địa chỉ MAC và timestamp tạo | Không nhúng thông tin máy chủ |
| Hiệu suất chỉ mục DB | Tốt — tuần tự theo máy chủ | Xuất sắc — k-sortable trên tất cả bộ tạo |
| Tiêu chuẩn | RFC 4122 (2005) | RFC 9562 (2024) |
Đối với các dự án mới, UUID v7 là sự thay thế được khuyến nghị cho UUID v1.
Ví dụ Mã
Tạo UUID v1 không có sẵn gốc trong trình duyệt hoặc Node.js. Sử dụng gói npm uuid:
// Generate a UUID v1 using the Web Crypto API
function generateUuidV1() {
const buf = new Uint8Array(16)
crypto.getRandomValues(buf)
const ms = BigInt(Date.now())
const gregorianOffset = 122192928000000000n
const t = ms * 10000n + gregorianOffset
const tLow = Number(t & 0xFFFFFFFFn)
const tMid = Number((t >> 32n) & 0xFFFFn)
const tHiVer = Number((t >> 48n) & 0x0FFFn) | 0x1000 // version 1
const clockSeq = (buf[8] & 0x3F) | 0x80 // variant 10xxxxxx
const clockSeqLow = buf[9]
const hex = (n, pad) => n.toString(16).padStart(pad, '0')
const node = [...buf.slice(10)].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex(tLow,8)}-${hex(tMid,4)}-${hex(tHiVer,4)}-${hex(clockSeq,2)}${hex(clockSeqLow,2)}-${node}`
}
// Extract the embedded timestamp from a UUID v1
function extractTimestamp(uuid) {
const parts = uuid.split('-')
const tHex = parts[2].slice(1) + parts[1] + parts[0]
const t = BigInt('0x' + tHex)
const ms = (t - 122192928000000000n) / 10000n
return new Date(Number(ms))
}
const id = generateUuidV1()
console.log(id) // e.g. "1eb5e8b0-6b4d-11ee-9c45-a1f2b3c4d5e6"
console.log(extractTimestamp(id)) // e.g. 2023-10-15T12:34:56.789Zimport uuid from datetime import datetime, timezone # Generate UUID v1 (uses MAC address by default) uid = uuid.uuid1() print(uid) # Extract embedded timestamp # uuid.time is 100-ns intervals since Oct 15, 1582 GREGORIAN_OFFSET = 122192928000000000 # 100-ns intervals ts_100ns = uid.time ts_ms = (ts_100ns - GREGORIAN_OFFSET) // 10000 dt = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc) print(dt.isoformat()) # e.g. "2023-10-15T12:34:56.789000+00:00"
package main
import (
"fmt"
"time"
"github.com/google/uuid" // go get github.com/google/uuid
)
func main() {
id, _ := uuid.NewUUID() // UUID v1
fmt.Println(id)
// Extract timestamp from UUID v1
// uuid.Time is 100-ns ticks since Oct 15, 1582
t := id.Time()
sec := int64(t)/1e7 - 12219292800 // convert to Unix seconds
nsec := (int64(t) % 1e7) * 100
ts := time.Unix(sec, nsec).UTC()
fmt.Println(ts.Format(time.RFC3339Nano))
}