2024-07-15 22:28 — 5 phút đọc

Các vấn đề khi tạo ID trong giao dịch phân tán

#id-generation#distributed-systems


Trong hệ thống phân tán, việc tạo ID duy nhất là một vấn đề quan trọng. Việc tạo ID định danh duy nhất có thể được thực hiện bằng nhiều cách khác nhau, vậy thì những cách đó là gì? Hãy cùng tìm hiểu qua bài viết này.

1. UUID (Universally Unique Identifier)

UUID là các giá trị 128-bit và có thể được tạo độc lập bởi các nút khác nhau mà không cần phối hợp.

Ưu điểm:

  • Dễ dàng triển khai.
  • Không cần phối hợp giữa các nút.

Nhược điểm:

  • Kích thước lớn (128 bit) có thể không hiệu quả cho lưu trữ và lập chỉ mục.
  • Không có thứ tự tự nhiên.
import java.util.UUID;

public class UUIDGenerator {
    public static String generateUUID() {
        return UUID.randomUUID().toString();
    }
}

2. Snowflake ID

Snowflake ID, ban đầu được phát triển bởi Twitter, là một giải pháp phổ biến. Nó tạo ra các ID 64-bit duy nhất được sắp xếp theo thời gian và có thể được tạo hiệu quả trong hệ thống phân tán.

Cấu trúc của Snowflake ID:

  • 1 bit: Không sử dụng (bit dấu).
  • 41 bits: Timestamp (dấu thời gian) theo milliseconds.
  • 10 bits: Machine ID (ID máy).
  • 12 bits: Sequence number (số thứ tự).

Ưu điểm:

  • Tạo ra các ID duy nhất được sắp xếp theo thời gian.
  • Hiệu quả và có thể mở rộng.

Nhược điểm:

  • Cần cấu hình cẩn thận để đảm bảo tính duy nhất của các Machine ID.

Ví dụ triển khai bằng Java:

public class SnowflakeIdGenerator {
    private final long epoch = 1609459200000L; // 2021-01-01
    private final long machineId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long machineId) {
        this.machineId = machineId;
    }

    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id.");
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & 4095; // 12 bits sequence mask
            if (sequence == 0) {
                timestamp = waitNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - epoch) << 22) | (machineId << 12) | sequence;
    }

    private long waitNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}

3. Database Auto-Increment với Sharding

Trong cấu hình database sharding, mỗi shard có thể được cấu hình để tạo ID duy nhất một cách độc lập bằng cách sử dụng các khoảng hoặc dãy số khác nhau.

Ưu điểm:

  • Tận dụng cơ sở hạ tầng cơ sở dữ liệu hiện có.
  • Dễ dàng triển khai với cơ sở dữ liệu phân mảnh.

Nhược điểm:

  • Có thể trở thành nút cổ chai nếu không được quản lý cẩn thận.

Ví dụ triển khai sử dụng MySQL:

CREATE TABLE id_generator (
    id BIGINT NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (id)
) AUTO_INCREMENT=1;

-- Đối với shard 1
ALTER TABLE id_generator AUTO_INCREMENT = 1;

-- Đối với shard 2
ALTER TABLE id_generator AUTO_INCREMENT = 1000000;

4. Sử dụng Zookeeper hoặc Etcd để tạo ID

Sử dụng Zookeeper hoặc Etcd để tạo ID có thể cung cấp tính nhất quán mạnh mẽ và phối hợp giữa các nút phân tán.

Ưu điểm:

  • Đảm bảo tính nhất quán mạnh mẽ.
  • Phù hợp với môi trường phân tán phức tạp.

Nhược điểm:

  • Cần thêm cơ sở hạ tầng và quản lý.
  • Tiềm năng chi phí hiệu suất.

Ví dụ sử dụng Zookeeper:

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;

public class ZookeeperIdGenerator {
    private final CuratorFramework client;
    private final InterProcessMutex lock;
    private long currentId;

    public ZookeeperIdGenerator(CuratorFramework client, String lockPath) {
        this.client = client;
        this.lock = new InterProcessMutex(client, lockPath);
        this.currentId = 0;
    }

    public long nextId() throws Exception {
        try {
            lock.acquire();
            // Đọc currentId từ Zookeeper hoặc tăng và ghi lại
            currentId++;
            // Lưu currentId vào Zookeeper
            return currentId;
        } finally {
            lock.release();
        }
    }
}

5. Redis Atomic ID Generation

Redis có thể được sử dụng để tạo ID nguyên tử bằng cách sử dụng lệnh INCR. Đây là một giải pháp hiệu quả và đơn giản để tạo ID duy nhất trong môi trường phân tán.

Ưu điểm:

  • Hiệu quả và dễ dàng triển khai.
  • Đảm bảo tính nhất quán và nguyên tử.

Nhược điểm:

  • Cần cấu hình và quản lý Redis.

Ví dụ triển khai bằng Java:

import redis.clients.jedis.Jedis;

public class RedisIdGenerator {
    private final Jedis jedis;
    private final String key;

    public RedisIdGenerator(Jedis jedis, String key) {
        this.jedis = jedis;
        this.key = key;
    }

    public long nextId() {
        return jedis.incr(key);
    }
}

Kết luận

Mỗi phương pháp có những đánh đổi riêng. Hãy chọn phương pháp phù hợp nhất với yêu cầu hệ thống của bạn về khả năng mở rộng, độ phức tạp, và hiệu suất. Snowflake ID thường là sự cân bằng tốt giữa sự đơn giản và khả năng mở rộng, trong khi UUID dễ dàng triển khai nhưng có thể không hiệu quả cho một số trường hợp sử dụng. Các giải pháp dựa trên Zookeeper/Etcd cung cấp tính nhất quán mạnh mẽ nhưng đi kèm với chi phí cơ sở hạ tầng bổ sung. Redis là một giải pháp hiệu quả và dễ dàng triển khai cho việc tạo ID duy nhất.


aitu avatar

Hi! Tôi là Tuyên — Hiện tại tôi đang làm Software Architect, Senior developer tại một công ty nhỏ ở Hà Nội. Tôi cảm thấy thích thú, đam mê, yêu thích với việc viết lách và chia sẻ những kiến thức mà tôi biết. Hãy đọc nhiều hơn tại blogs và tới about để biết thêm về tôi nhé.