Chuyển đến nội dung

Trình tạo ULID - Tạo ID Duy Nhất Có Thể Sắp Xếp Trực Tuyến Miễn Phí

Công cụ tạo ULID miễn phí tạo ra các định danh duy nhất, có thể sắp xếp ngay lập tức. Tạo các ULID an toàn về mặt mật mã cho cơ sở dữ liệu, API và hệ thống phân tán trực tuyến.

Trình tạo ULID

Nhập giá trị để xem kết quả
Máy tính tải...
📚

Tài liệu hướng dẫn

ULID là gì?

ULID (Mã định danh có thứ tự từ điển và duy nhất trên toàn hệ thống) là mã gồm 26 ký tự, dùng để gắn nhãn cho bản ghi trong cơ sở dữ liệu hoặc một sự kiện trong hệ thống phân tán. Nó kết hợp thời gian hiện tại với dữ liệu ngẫu nhiên, vì vậy danh sách ULID được sắp xếp theo thứ tự chữ cái cũng sẽ được sắp xếp theo thời điểm tạo từng mã. Bộ tạo ULID là công cụ tạo ra các mã này.

Cấu trúc ULID

ULID gồm hai phần, được viết liền nhau không có dấu phân cách:

  • Dấu thời gian (10 ký tự): số mili giây kể từ 1 tháng 1, 1970 (kỷ nguyên Unix), chiếm tối đa 48 bit.
  • Dữ liệu ngẫu nhiên (16 ký tự): 80 bit dữ liệu ngẫu nhiên, được thêm vào để hai ULID tạo trong cùng một mili giây gần như chắc chắn vẫn khác nhau.

Cả hai phần đều được viết bằng bảng chữ cái Base32 của Crockford: các chữ số 0–9 và các chữ cái A-Z, ngoại trừ I, L, O và U. Bốn chữ cái này được loại bỏ vì có thể bị nhầm với các chữ số 0 và 1 hoặc nhầm lẫn với nhau. Kết quả là một chuỗi ngắn, dễ đọc thành tiếng và an toàn khi đặt trực tiếp trong URL.

Cách tính ULID

Bộ tạo ULID tạo mã định danh qua ba bước.

  1. Đọc thời gian hiện tại tính bằng mili giây kể từ kỷ nguyên Unix. Đây là một số 48 bit.
  2. Tạo 80 bit dữ liệu ngẫu nhiên, thường từ bộ sinh số ngẫu nhiên an toàn về mặt mật mã, tạo ra 10 byte ngẫu nhiên.
  3. Mã hóa riêng từng phần bằng bảng chữ cái Base32 của Crockford: dấu thời gian 48 bit trở thành 10 ký tự, còn giá trị ngẫu nhiên 80 bit trở thành 16 ký tự. Hai chuỗi đã mã hóa được nối lại để tạo thành ULID gồm 26 ký tự.

Cách mã hóa là đọc số theo từng nhóm 5 bit, vì mỗi ký tự Base32 biểu diễn một trong 32 giá trị có thể có (2^5 = 32). Mười ký tự chứa tối đa 50 bit, đủ chỗ cho dấu thời gian 48 bit. Mười sáu ký tự chứa chính xác 80 bit, khớp với phần dữ liệu ngẫu nhiên mà không còn bit dư.

Ví dụ minh họa

Giả sử một bộ tạo ULID chạy tại dấu thời gian 1712345678901 (mili giây kể từ kỷ nguyên) và lấy các byte ngẫu nhiên [12, 240, 88, 3, 199, 45, 6, 231, 128, 17].

Bước 1 – mã hóa dấu thời gian. Chia lặp 1712345678901 cho 32, mỗi lần lấy phần dư rồi đọc các chữ số từ cuối về đầu (đây là phép chuyển đổi cơ số tiêu chuẩn). Kết quả là chuỗi gồm 10 ký tự 01HTQW311N.

Bước 2 – mã hóa dữ liệu ngẫu nhiên. Đóng gói 10 byte ngẫu nhiên thành một luồng 80 bit, sau đó đọc từng nhóm 5 bit, chuyển mỗi nhóm 5 bit thành một ký tự Base32. Kết quả là chuỗi gồm 16 ký tự 1KR5G0Y75M3EF00H.

Bước 3 – nối các phần. ULID hoàn chỉnh là:

101HTQW311N 1KR5G0Y75M3EF00H
2

được viết không có khoảng trắng: 01HTQW311N1KR5G0Y75M3EF00H, tổng cộng 26 ký tự.

Sử dụng bộ tạo này

Bộ tạo chạy trong trình duyệt. Mỗi ULID được tạo từ đồng hồ của trình duyệt và 80 bit ngẫu nhiên mới lấy từ bộ sinh số ngẫu nhiên mật mã của trình duyệt, nên không có mã định danh nào được gửi đến hoặc lấy từ máy chủ.

  • Số lượng ULID chấp nhận một số nguyên từ 1 đến 50. Giá trị mặc định là 1.
  • Tạo ULID tạo một lô mới. Thay đổi số lượng cũng tạo một lô mới.
  • Sao chép trên một kết quả sẽ sao chép riêng ULID đó. Khi hiển thị nhiều ULID, Sao chép tất cả sẽ sao chép toàn bộ danh sách, mỗi ULID trên một dòng.
  • Đặt lại đưa số lượng về 1 và tạo một ULID mới.

Bên dưới các kết quả, bảng Cấu trúc ULID tách ULID đầu tiên trong lô thành hai phần: 10 ký tự đầu tiên (dấu thời gian) và 16 ký tự cuối (dữ liệu ngẫu nhiên).

ULID và UUID

Tính năngULIDUUID (phiên bản 4)
Độ dài26 ký tự36 ký tự (có dấu gạch nối)
Có thể sắp xếp theo thời điểm tạoCóKhông
Bộ mã hóaCrockford Base32Thập lục phân
Có chứa dấu thời gianCó (48 bit)Không
An toàn cho URL mà không cần thoátCóCó

Cả hai định dạng đều nhằm tạo ra các mã định danh duy nhất giữa các hệ thống mà không cần cơ quan trung tâm cấp số. Khác biệt thực tế chính là việc sắp xếp: vì ULID bắt đầu bằng dấu thời gian, việc sắp xếp ULID dưới dạng văn bản thuần túy cũng sắp xếp chúng theo thời gian. UUID ngẫu nhiên không có đặc điểm này, vì mọi phần của nó đều ngẫu nhiên.

Các ứng dụng phổ biến

  • Khóa chính cơ sở dữ liệu: ULID cho phép cơ sở dữ liệu lập chỉ mục các hàng mới theo gần đúng thứ tự chúng được chèn vào, điều mà nhiều công cụ cơ sở dữ liệu xử lý hiệu quả hơn so với các khóa hoàn toàn ngẫu nhiên.
  • Hệ thống phân tán: các máy chủ riêng biệt có thể tự tạo ULID mà không cần phối hợp với bộ đếm trung tâm, trong khi các kết quả vẫn gần như chắc chắn là duy nhất.
  • Nhật ký sự kiện: vì ULID được sắp xếp theo thời gian, danh sách ID sự kiện cũng có thể đóng vai trò như một dòng thời gian sơ bộ.
  • Mã định danh API và tệp: định dạng cố định gồm 26 ký tự, an toàn cho URL, hoạt động thuận tiện trong địa chỉ web và tên tệp.

Các định dạng mã định danh liên quan gồm KSUID, cũng mã hóa dấu thời gian để hỗ trợ sắp xếp, và ID Snowflake, được Twitter cùng các đơn vị khác sử dụng, kết hợp dấu thời gian, mã định danh máy và bộ đếm.

Ví dụ mã

Mỗi ví dụ dưới đây tạo ULID theo cách được đặc tả ULID mô tả: mã hóa dấu thời gian 48 bit thành 10 ký tự Crockford Base32, mã hóa 80 bit dữ liệu ngẫu nhiên thành thêm 16 ký tự, rồi nối chúng lại.

JavaScript

1const ENCODING_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
2
3function encodeULID(time, randomBytes) {
4  // Timestamp: 48 bits -> 10 characters
5  let timestampStr = '';
6  let t = time;
7  for (let i = 0; i < 10; i++) {
8    timestampStr = ENCODING_CHARS[t % 32] + timestampStr;
9    t = Math.floor(t / 32);
10  }
11
12  // Randomness: 80 bits -> 16 characters
13  let result = timestampStr;
14  let bits = 0;
15  let bitCount = 0;
16  for (let i = 0; i < 10; i++) {
17    bits = (bits << 8) | randomBytes[i];
18    bitCount += 8;
19    while (bitCount >= 5) {
20      bitCount -= 5;
21      result += ENCODING_CHARS[(bits >> bitCount) & 0x1f];
22    }
23  }
24  return result;
25}
26
27function generateULID() {
28  const randomBytes = crypto.getRandomValues(new Uint8Array(10));
29  return encodeULID(Date.now(), randomBytes);
30}
31
32console.log(generateULID());
33

Python

1import os
2import time
3
4ENCODING_CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
5
6def encode_ulid(time_ms, random_bytes):
7    # Timestamp: 48 bits -> 10 characters
8    chars = []
9    t = time_ms
10    for _ in range(10):
11        chars.append(ENCODING_CHARS[t % 32])
12        t //= 32
13    timestamp_part = "".join(reversed(chars))
14
15    # Randomness: 80 bits -> 16 characters
16    bits = 0
17    bit_count = 0
18    random_part = []
19    for byte in random_bytes:
20        bits = (bits << 8) | byte
21        bit_count += 8
22        while bit_count >= 5:
23            bit_count -= 5
24            random_part.append(ENCODING_CHARS[(bits >> bit_count) & 0x1F])
25
26    return timestamp_part + "".join(random_part)
27
28def generate_ulid():
29    return encode_ulid(int(time.time() * 1000), os.urandom(10))
30
31print(generate_ulid())
32

Java

1import java.security.SecureRandom;
2import java.time.Instant;
3
4public class ULIDGenerator {
5    private static final SecureRandom random = new SecureRandom();
6    private static final char[] ENCODING_CHARS =
7        "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
8
9    public static String generateULID() {
10        long timestamp = Instant.now().toEpochMilli();
11        byte[] randomBytes = new byte[10];
12        random.nextBytes(randomBytes);
13
14        // Timestamp: 48 bits -> 10 characters
15        char[] timestampChars = new char[10];
16        long t = timestamp;
17        for (int i = 9; i >= 0; i--) {
18            timestampChars[i] = ENCODING_CHARS[(int) (t % 32)];
19            t /= 32;
20        }
21
22        // Randomness: 80 bits -> 16 characters
23        StringBuilder result = new StringBuilder(new String(timestampChars));
24        long bits = 0;
25        int bitCount = 0;
26        for (byte b : randomBytes) {
27            bits = (bits << 8) | (b & 0xFF);
28            bitCount += 8;
29            while (bitCount >= 5) {
30                bitCount -= 5;
31                result.append(ENCODING_CHARS[(int) ((bits >> bitCount) & 0x1F)]);
32            }
33        }
34        return result.toString();
35    }
36
37    public static void main(String[] args) {
38        System.out.println(generateULID());
39    }
40}
41

Các câu hỏi thường gặp

ULID được dùng để làm gì? ULID được dùng làm mã định danh duy nhất cho một hàng trong cơ sở dữ liệu, một tài nguyên API hoặc một sự kiện được ghi nhật ký, trong những trường hợp việc sắp xếp theo thời điểm tạo cũng hữu ích.

ULID dài bao nhiêu? ULID luôn dài 26 ký tự: 10 ký tự dành cho dấu thời gian và 16 ký tự dành cho phần ngẫu nhiên.

ULID sử dụng kiểu mã hóa nào? ULID sử dụng bảng chữ cái Base32 của Crockford, gồm các chữ số 0–9 và các chữ cái A-Z sau khi loại bỏ I, L, O và U để tránh nhầm lẫn với các ký tự khác.

ULID khác UUID như thế nào? ULID được sắp xếp theo thời điểm tạo và dài 26 ký tự. UUID ngẫu nhiên (phiên bản 4) không được sắp xếp theo thời gian và dài 36 ký tự, bao gồm cả dấu gạch nối.

Khả năng xảy ra va chạm giữa hai ULID là bao nhiêu? 80 bit dữ liệu ngẫu nhiên khiến khả năng va chạm cực kỳ thấp. Cần tạo khoảng 1,3 nghìn tỷ ULID trong cùng một mili giây để đạt xác suất 50% có bất kỳ hai mã nào trùng nhau.

Có thể tạo ULID mà không cần kết nối Internet không? Có. ULID chỉ cần đồng hồ cục bộ và một bộ sinh số ngẫu nhiên, nên có thể được tạo hoàn toàn ngoại tuyến.

Tài liệu tham khảo

  1. "ULID Specification." GitHub, https://github.com/ulid/spec.
  2. "Crockford's Base32 Encoding." http://www.crockford.com/base32.html.