logo

Lộ trình

Khóa học

Tài liệu

Mock Interview

Liên hệ

Quay lại
  • Trang chủ

    /

  • Tài liệu

    /

  • Tổng Hợp Các Cách Sinh ID Trong Hệ Phân Tán
Tài liệu

Tổng Hợp Các Cách Sinh ID Trong Hệ Phân Tán

Ronin Engineer

22 Tháng 5 2024

<h2 id="1-v%E1%BA%A5n-%C4%91%E1%BB%81">1. Vấn đề</h2><p>Thực thể (entity) trong cơ sở dữ liệu cần có tối thiểu 2 thuộc tính đó là <code>ID</code> và <code>status</code>. Trong ID là một giá trị duy nhất dùng để xác định một đối tượng cụ thể trong một hệ thống hoặc một bản ghi trong cơ sở dữ liệu. ID là trường thông tin rất quan trọng vì nó đảm bảo tính nhất quán (không trùng lặp) trong quản lý thông tin.</p><p>ID có thể là một chuỗi các ký tự (<code>550e8400-e29b-41d4-a716-446655440000</code>) hoặc một dãy số tăng dần (<code>1, 2, 3, …</code>). Ví dụ cột ID là primary key với thuộc tính <code>auto_increment</code> trong Database (DB). Khi có một bản ghi mới, DB sẽ tăng giá trị ID này lên 1 đơn vị.</p><p>Logic <code>auto_increment</code> rất đơn giản tuy nhiên <code>auto_increment</code> trên 1 DB server sẽ không phù hợp trong hệ phân tán.</p><figure class="kg-card kg-image-card"><img src="https://images.viblo.asia/d03ad9a1-2b29-49ba-a478-9bc20fbb624e.png" class="kg-image" alt="1.ticket-server.png" loading="lazy" width="825" height="321"></figure><p>Vì có một số vấn đề sau:</p><ul><li><strong>Performance</strong>: trong hệ phân tán, nhiều nodes có thể thực hiện thêm mới (insert) cùng lúc nên việc sinh ID được thực hiện trên 1 DB server có thể sẽ tăng độ trễ và ảnh hưởng tới hiệu năng (performance).</li><li><strong>Scalability</strong>: khả năng mở rộng không tốt vì việc sinh ID chỉ được thực hiện trên 1 DB server.</li><li><strong>Single point of failure</strong>: nếu DB server “toang", các node khác không thể gọi đến để sinh ID.</li></ul><h2 id="2-y%C3%AAu-c%E1%BA%A7u">2. Yêu Cầu</h2><p>Như vậy, chúng ta cần tìm một giải pháp sinh ID có thể giải quyết đc ít nhất 3 vấn đề trên để có thể chạy được trong hệ phân tán. Nhưng không chỉ có 3 yêu cầu trên, chúng ta còn cần phải làm rõ những yêu cầu khác. Dưới đây là tổng hợp một số yêu cầu của việc sinh ID:</p><ul><li>Một giá trị ID phải là <strong>duy nhất</strong>.</li><li>ID chỉ gồm số hay có cả chữ và <strong>ký tự</strong> khác? ví dụ: chỉ bao gồm số.</li><li><strong>Độ dài</strong> của ID là bao nhiêu bit? ví dụ: 64bit</li><li>ID <strong>có thể sắp xếp</strong> theo thứ tự thời gian không? ví dụ: có thể sắp xếp.</li><li><strong>Khả năng sinh</strong> bao nhiêu ID trong 1 giây? ví dụ: 10000 IDs trong vòng 1 giây.</li></ul><h2 id="3-c%C3%A1c-h%C6%B0%E1%BB%9Bng-ti%E1%BA%BFp-c%E1%BA%ADn">3. Các hướng tiếp cận</h2><p>Tiếp theo, chúng ta sẽ cùng đánh giá một số cách dưới đây để sinh ID trong hệ phân tán.</p><ul><li>Ticket Server</li><li>Multi-master</li><li>UUID</li><li>ULID</li><li>Twitter snowflake</li><li>…</li></ul><h3 id="31-ticket-server">3.1. Ticket Server</h3><p>Đây là một giải pháp mà Flicker đưa ra. Trong đó, ý tưởng vẫn là sử dụng tính năng <code>auto_increment</code> trên một DB server (Ticket Server) như đã đề cập ở phần <code>Vấn đề</code>. Các node web server sẽ gọi vào Ticket Server để lấy ID.</p><figure class="kg-card kg-image-card"><img src="https://images.viblo.asia/d03ad9a1-2b29-49ba-a478-9bc20fbb624e.png" class="kg-image" alt="1.ticket-server.png" loading="lazy" width="825" height="321"></figure><p>Ngoài ra, giải pháp của Flicker còn giải quyết được vấn đề tính duy nhất toàn cục (physical) và cục bộ (logical) trên các tập dữ liệu. Bằng cách sử dụng <code>REPLACE</code> thay vì <code>INSERT</code>. Nếu mọi người quan tâm tới vấn đề này thì có thể đọc thêm link dưới phần <code>Tham khảo</code>.</p><p><strong>Ưu điểm</strong>:</p><ul><li>Đảm bảo tính duy nhất.</li><li><strong>Đơn giản</strong>.</li><li><strong>Dễ triển khai, phù hợp với những hệ thống vừa và nhỏ.</strong></li><li>ID chỉ gồm số.</li></ul><p></p><p><strong>Nhược điểm</strong>: có 3 nhược điểm đã đề cập ở trên như</p><ul><li><strong>Hiệu năng</strong> chưa cao.</li><li><strong>Single point of failure</strong>. Nếu chạy thêm 1 ticker server phụ và có bộ đếm giống ticket server chính thì sẽ phát sinh thêm vấn đề về đồng bộ dữ liệu giữa 2 server.</li><li>Để cải thiện <strong>khả năng mở rộng</strong> thì chúng ta đến với cách tiếp theo.</li></ul><p>Bạn có thể tham khảo thêm một giải pháp tương tự, chúng ta có thể sử dụng lệnh <code>INCR</code> của Redis để sinh ID.</p><h3 id="32-multi-master">3.2. Multi-master</h3><p>Cách này cũng sử dụng tính năng <code>auto_increment</code>. Tuy nhiên, việc sinh ID sẽ được thực hiện trên nhiều DB server thay vì chạy trên 1 DB server. Để ID của các DB server sinh ra không bị trùng nhau thì trên mỗi DB server sẽ được cấu hình auto tăng thêm K đơn vị, trong đó K là số DB server.</p><figure class="kg-card kg-image-card"><img src="https://images.viblo.asia/f6d27d5d-d75b-402b-a6a7-9e3437cdf93e.png" class="kg-image" alt="2. multi-master.png" loading="lazy" width="825" height="418"></figure><p>Để cấu hình auto tăng thêm 2 đơn vị trên một bảng, đầu tiên ta tạo bảng có <code>auto_increment</code> như bình thường. Sau đó, xét thêm cấu hình <code>auto_increment_increment</code></p><pre><code>SET SESSION auto_increment_increment = 2; </code></pre><p><strong>Ưu điểm:</strong></p><ul><li>Đảm bảo tính duy nhất.</li><li>ID chỉ gồm số.</li><li>Dễ triển khai.</li><li>Tăng khả năng mở rộng</li></ul><p></p><p><strong>Nhược điểm:</strong></p><ul><li>Tuy tăng được khả khả mở rộng nhưng giải pháp này không thực sự linh hoạt. Khi thêm vào hoặc gỡ ra 1 DB server thì hệ thống sẽ phải down time một khoảng thời gian để cập nhật số lượng DB server.</li><li>IDs có thể không tăng đều giữa các DB server. Ví dụ: DB server A có 3 bản ghi có id 1, 3, 5. Tuy nhiên lúc này DB server B mới chỉ có 1 bản ghi id = 2.</li><li>Cần thêm giải pháp để hỗ trợ mở rộng ra nhiều data center.</li></ul><h3 id="33-uuid">3.3. UUID</h3><p>Sử dụng UUID là một cách khác để dễ dàng tạo ra nhiều unique IDs. UUID là một chuỗi gồm 128bit và thường được sửa dụng trong các hệ thống máy tính. Ví dụ: <code>550e8400-e29b-41d4-a716-446655440000</code></p><p><strong>UUID có tỉ lệ đụng đô (collusion), sinh ra 2 ID trùng nhau, là rất thấp</strong>. Với UUIDv4 thì ngay cả khi sinh 1 tỉ UUIDs trong 1 giây thì phải mất hơn 100 năm thì mới có khả năng sinh trùng ID.</p><p>Với cách này, chúng ta không cần server tập trung nào để sinh ID. Mà việc này sẽ được thực hiện ngay trên các ứng dụng và được thực hiện một cách độc lập.</p><figure class="kg-card kg-image-card"><img src="https://images.viblo.asia/29330e84-3403-4479-a70b-2764c5526424.png" class="kg-image" alt="3. uuid.png" loading="lazy" width="825" height="216"></figure><p><strong>Ưu điểm:</strong></p><ul><li>Siêu <strong>đơn giản.</strong></li><li><strong>Khả năng mở rộng cao.</strong></li><li>Độ dài cố định</li></ul><p><strong>Nhược điểm:</strong></p><ul><li>Vẫn có khả năng xảy ra đụng độ.</li><li>UUID <strong>không thể sắp xếp</strong> theo thời gian.</li><li>UUID <strong>dài</strong> nên tốn nhiều không gian lưu trữ hơn.</li><li>UUIDv4 chứa 1 chuỗi random nên gây ra <strong>khó khăn trong quá trình đánh index</strong> của DB.</li></ul><h4 id="331-%C4%91%E1%BB%8Dc-th%C3%AAm">3.3.1. Đọc thêm</h4><ul><li>Một số trường hợp người ta có thể gọi UUID là GUID (Globally Unique Identifier).</li><li>UUID có nhiều version, mỗi version đều có ưu nhược điểm riêng.</li><li>Bạn có thể đọc thêm về vấn đề khi sử dụng UUID với MySQL và một số cách khắc phục ở phần Tham khảo [4] bên dưới.</li></ul><h3 id="34-ulid">3.4. ULID</h3><p>ULID được thiết kế để có thể sắp xếp được về mặt cấu trúc. Cấu trúc của ULID gồm <strong>Timestamp (48 bits)</strong> và Randomness (80 bits). Nhưng sẽ được encode thành một chuỗi gồm 26 ký tự.</p><figure class="kg-card kg-image-card"><img src="https://images.viblo.asia/b3becd7f-f29d-4d17-a84c-e4faddd9e1e5.jpeg" class="kg-image" alt="4. ulid.jpeg" loading="lazy" width="581" height="301"></figure><p><strong>Ưu điểm:</strong></p><ul><li>Có chứa thông tin timestamp.</li><li>Có thể sắp xếp được theo thời gian.</li><li>Độ dài cố định</li><li>Không chứa ký tự đặc biệt (URL safe: thân thiện với web)</li><li>Case insentive</li></ul><p><strong>Nhược điểm:</strong></p><ul><li><strong>Xác suất xảy ra đụng độ thấp tuy nhiên vẫn có</strong> và cao hơn UUIDv4. ULID có 80 random bits trong khi UUIDv4 có 122 random bits.</li><li>Vẫn tương đối dài.</li></ul><h3 id="35-nanoid">3.5. NanoID</h3><p>Nếu bạn cần một cách sinh ID có <strong>khả năng tuỳ chỉnh cao</strong> thì bạn có thể tham khảo thêm về NanoID [7]. Ví dụ: <code>kw2c0khavhl</code>. Tuy nhiên, bạn cần lưu ý một số điểm sau: bản thân NanoID chưa có cơ chế sắp xếp và xác suất xảy ra <strong>đụng độ</strong> có thể cao.</p><h3 id="36-twitter-snowflake">3.6. Twitter snowflake</h3><p>Trong những cách trên, có trường hợp chưa đáp ứng được yêu cầu về khả năng mở rộng, có trường hợp lại chưa đáp ứng được yêu cầu về độ dài, … Nhưng còn một cách khác có thể đáp ứng những yêu cầu trên đó là Snowflake ID của Twitter.</p><p>Snowflake ID gồm 64 bit và có cấu trúc như sau:<br></p><figure class="kg-card kg-image-card"><img src="https://images.viblo.asia/57e7d2a6-95ac-489f-a5d3-e83a4b80cf74.png" class="kg-image" alt="5. snowflake-structure.png" loading="lazy" width="825" height="112"></figure><ul><li><strong>Sign Bit (1 bit)</strong>: luôn là 0</li><li><strong>Timestamp (41 bits)</strong>: số milisecond kể từ Twitter epoch (mốc 2010-11-04).</li><li><strong>Datacenter ID (5 bits)</strong>: mã định danh cho datacenter. Giá trị được khởi tạo khi bắt đầu chạy trình Snowflake generator.</li><li><strong>Machine ID (5 bits)</strong>: mã định danh cho máy trong datacenter. Giá trị được khởi tạo khi bắt đầu chạy trình Snowflake generator.</li><li><strong>Sequence Number (12 bits)</strong>: Số đếm tăng dần cho các ID được sinh trong một mili giây. Số đếm này sẽ reset về 0 khi sang milisecond khác. Dùng để đảm bảo tính duy nhất trong một mili giây. Như vậy, 1 máy có sinh 2 ^ 12 (=4096) IDs trong vòng 1 mili giây.</li></ul><p>Ví dụ một Snowflake ID (một số nguyên 64-bit): <code>689369924653004799</code>.</p><p>Khi triển khi hệ thống Snowflake ID generator sẽ như sau:<br></p><figure class="kg-card kg-image-card"><img src="https://images.viblo.asia/59baffc0-a089-4c8c-bdad-bd6825000eea.png" class="kg-image" alt="6. snowflake-system.png" loading="lazy" width="825" height="397"></figure><p><strong>Ưu điểm:</strong></p><ul><li>Đảm bảo được tính duy nhất.</li><li><strong>Có khả năng mở rộng tốt.</strong></li><li><strong>Có thể sắp xếp</strong> theo thứ tự thời gian.</li><li>Cấu trúc đơn giản.</li><li>Độ dài <strong>gọn nhẹ</strong> (1 số nguyên 64-bit) dễ dàng thao tác và lưu trữ.</li></ul><p><strong>Nhược điểm:</strong></p><ul><li>Để triển khai giải pháp này sẽ <strong>phức tạp và tốn kém.</strong></li><li><strong>Giới hạn về khoảng thời gian hoạt động</strong>. Phần timestamp có 41 bits nghĩa là Snowflake ID generator có thể hoạt đông trong vòng 2 ^ 41 miliseconds (~ <strong>69.7 năm</strong>). Do cấu trúc và logic sinh ID đơn giản, bạn có thể điểu chỉnh độ dài của phần timestamp, datacenter id, machine id và mốc epoch để khắc phục phần nào nhược điểm này.</li><li>Ta đang giả định các Snowflake ID Generator trong hệ thống chạy trên các máy khác nhau. Các máy có đồng hồ vật lý (lock) chạy giống nhau. Tuy nhiên, trên thực tế, đồng hồ vật lý của các máy có thể chạy khác nhau, tạo ra độ lệch giữa các đồng hồ. Nếu độ lệch này lớn có thể dẫn đến vấn đề xung độ khi sinh ID. Do đó, chúng ta cần thêm cơ chế đồng bộ đồng hồ để hạn chế độ lệch này.</li><li>Giá trị các ID không liên tục.</li><li>Một máy có thể sinh 4096 IDs trong vòng 1 mili giây. Đây là một con số tương đối lớn, tuy nhiên nó <strong>cũng là 1 giới hạn</strong> chúng ta cần để lưu ý.</li></ul><p>Dựa trên ý tưởng của Snowflake ID, Sony có một sinh ID khác (Sonyflake ID [8]), có khoảng thời gian hoạt động lâu hơn (~174 năm) nhưng sinh ID chậm hơn Snowflake ID.</p><h2 id="4-t%E1%BB%95ng-k%E1%BA%BFt">4. Tổng Kết</h2><ul><li>Đầu tiên chúng ta cần xác định hết yêu cầu và các yêu cầu tối thiểu của việc sinh ID.</li><li>Không có giải pháp nào có thể đáp ứng được tất cả yêu cầu. Đa số các trường hợp chúng ta chỉ cần có một tập con của các yêu cầu đề cập ở trên. Ví dụ trường hợp A, ta không yêu cầu có khả năng sort. Trường hợp B, chúng ta không yêu cầu ID chỉ có số. Trường hợp C, ta cần một giải pháp dễ triển khai, … Dựa vào yêu cầu cụ thể để ta chọn ra giải pháp phù hợp nhất.</li><li>Nếu các giải pháp trên chưa đáp được yêu cầu của bạn thì bạn hoàn toàn có thể tạo ra 1 cách sinh ID phục vụ riêng bài toán của bạn.</li></ul><p></p><p>Bạn đang sử dụng cách thức nào để sinh ID, hãy comment ở dưới giúp mình nhé.<br><br>Nếu mọi người thấy bài viết hữu ích thì nhờ mọi người share để nội dung của Ronin được nhiều người biết hơn.<br><br>Cám ơn mọi người. 🙏</p><h2 id="5-tham-kh%E1%BA%A3o">5. Tham khảo</h2><p>[1] Sách System Design Interview: An Insider’s Guide - Alex Xu</p><p>[2] Ticket Servers: <a href="https://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/?ref=roninhub.com">https://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/</a></p><p>[3] UUID: <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier?ref=roninhub.com">https://en.wikipedia.org/wiki/Universally_unique_identifier</a></p><p>[4] Vấn đề khi sử dụng UUID với MySQL: <a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql?ref=roninhub.com">https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql</a></p><p>[5] ULID: <a href="https://wiki.tcl-lang.org/page/ULID?ref=roninhub.com">https://wiki.tcl-lang.org/page/ULID</a></p><p>[6] Shopify dùng ULID sinh Idempotency Key: <a href="https://shopify.engineering/building-resilient-payment-systems?ref=roninhub.com">https://shopify.engineering/building-resilient-payment-systems</a></p><p>[7] NanoID: <a href="https://zelark.github.io/nano-id-cc/?ref=roninhub.com">https://zelark.github.io/nano-id-cc/</a></p><p>[8] SonyFlake ID: <a href="https://github.com/sony/sonyflake?ref=roninhub.com">https://github.com/sony/sonyflake</a></p><hr><p>📚️ System Design VN: <a href="https://fb.com/groups/systemdesign.vn?ref=roninhub.com">https://fb.com/groups/systemdesign.vn</a></p>

1. Vấn đề

Thực thể (entity) trong cơ sở dữ liệu cần có tối thiểu 2 thuộc tính đó là ID và status. Trong ID là một giá trị duy nhất dùng để xác định một đối tượng cụ thể trong một hệ thống hoặc một bản ghi trong cơ sở dữ liệu. ID là trường thông tin rất quan trọng vì nó đảm bảo tính nhất quán (không trùng lặp) trong quản lý thông tin.

ID có thể là một chuỗi các ký tự (550e8400-e29b-41d4-a716-446655440000) hoặc một dãy số tăng dần (1, 2, 3, …). Ví dụ cột ID là primary key với thuộc tính auto_increment trong Database (DB). Khi có một bản ghi mới, DB sẽ tăng giá trị ID này lên 1 đơn vị.

Logic auto_increment rất đơn giản tuy nhiên auto_increment trên 1 DB server sẽ không phù hợp trong hệ phân tán.

1.ticket-server.png

Vì có một số vấn đề sau:

  • Performance: trong hệ phân tán, nhiều nodes có thể thực hiện thêm mới (insert) cùng lúc nên việc sinh ID được thực hiện trên 1 DB server có thể sẽ tăng độ trễ và ảnh hưởng tới hiệu năng (performance).
  • Scalability: khả năng mở rộng không tốt vì việc sinh ID chỉ được thực hiện trên 1 DB server.
  • Single point of failure: nếu DB server “toang", các node khác không thể gọi đến để sinh ID.

2. Yêu Cầu

Như vậy, chúng ta cần tìm một giải pháp sinh ID có thể giải quyết đc ít nhất 3 vấn đề trên để có thể chạy được trong hệ phân tán. Nhưng không chỉ có 3 yêu cầu trên, chúng ta còn cần phải làm rõ những yêu cầu khác. Dưới đây là tổng hợp một số yêu cầu của việc sinh ID:

  • Một giá trị ID phải là duy nhất.
  • ID chỉ gồm số hay có cả chữ và ký tự khác? ví dụ: chỉ bao gồm số.
  • Độ dài của ID là bao nhiêu bit? ví dụ: 64bit
  • ID có thể sắp xếp theo thứ tự thời gian không? ví dụ: có thể sắp xếp.
  • Khả năng sinh bao nhiêu ID trong 1 giây? ví dụ: 10000 IDs trong vòng 1 giây.

3. Các hướng tiếp cận

Tiếp theo, chúng ta sẽ cùng đánh giá một số cách dưới đây để sinh ID trong hệ phân tán.

  • Ticket Server
  • Multi-master
  • UUID
  • ULID
  • Twitter snowflake
  • …

3.1. Ticket Server

Đây là một giải pháp mà Flicker đưa ra. Trong đó, ý tưởng vẫn là sử dụng tính năng auto_increment trên một DB server (Ticket Server) như đã đề cập ở phần Vấn đề. Các node web server sẽ gọi vào Ticket Server để lấy ID.

1.ticket-server.png

Ngoài ra, giải pháp của Flicker còn giải quyết được vấn đề tính duy nhất toàn cục (physical) và cục bộ (logical) trên các tập dữ liệu. Bằng cách sử dụng REPLACE thay vì INSERT. Nếu mọi người quan tâm tới vấn đề này thì có thể đọc thêm link dưới phần Tham khảo.

Ưu điểm:

  • Đảm bảo tính duy nhất.
  • Đơn giản.
  • Dễ triển khai, phù hợp với những hệ thống vừa và nhỏ.
  • ID chỉ gồm số.

Nhược điểm: có 3 nhược điểm đã đề cập ở trên như

  • Hiệu năng chưa cao.
  • Single point of failure. Nếu chạy thêm 1 ticker server phụ và có bộ đếm giống ticket server chính thì sẽ phát sinh thêm vấn đề về đồng bộ dữ liệu giữa 2 server.
  • Để cải thiện khả năng mở rộng thì chúng ta đến với cách tiếp theo.

Bạn có thể tham khảo thêm một giải pháp tương tự, chúng ta có thể sử dụng lệnh INCR của Redis để sinh ID.

3.2. Multi-master

Cách này cũng sử dụng tính năng auto_increment. Tuy nhiên, việc sinh ID sẽ được thực hiện trên nhiều DB server thay vì chạy trên 1 DB server. Để ID của các DB server sinh ra không bị trùng nhau thì trên mỗi DB server sẽ được cấu hình auto tăng thêm K đơn vị, trong đó K là số DB server.

2. multi-master.png

Để cấu hình auto tăng thêm 2 đơn vị trên một bảng, đầu tiên ta tạo bảng có auto_increment như bình thường. Sau đó, xét thêm cấu hình auto_increment_increment

SET SESSION auto_increment_increment = 2;

Ưu điểm:

  • Đảm bảo tính duy nhất.
  • ID chỉ gồm số.
  • Dễ triển khai.
  • Tăng khả năng mở rộng

Nhược điểm:

  • Tuy tăng được khả khả mở rộng nhưng giải pháp này không thực sự linh hoạt. Khi thêm vào hoặc gỡ ra 1 DB server thì hệ thống sẽ phải down time một khoảng thời gian để cập nhật số lượng DB server.
  • IDs có thể không tăng đều giữa các DB server. Ví dụ: DB server A có 3 bản ghi có id 1, 3, 5. Tuy nhiên lúc này DB server B mới chỉ có 1 bản ghi id = 2.
  • Cần thêm giải pháp để hỗ trợ mở rộng ra nhiều data center.

3.3. UUID

Sử dụng UUID là một cách khác để dễ dàng tạo ra nhiều unique IDs. UUID là một chuỗi gồm 128bit và thường được sửa dụng trong các hệ thống máy tính. Ví dụ: 550e8400-e29b-41d4-a716-446655440000

UUID có tỉ lệ đụng đô (collusion), sinh ra 2 ID trùng nhau, là rất thấp. Với UUIDv4 thì ngay cả khi sinh 1 tỉ UUIDs trong 1 giây thì phải mất hơn 100 năm thì mới có khả năng sinh trùng ID.

Với cách này, chúng ta không cần server tập trung nào để sinh ID. Mà việc này sẽ được thực hiện ngay trên các ứng dụng và được thực hiện một cách độc lập.

3. uuid.png

Ưu điểm:

  • Siêu đơn giản.
  • Khả năng mở rộng cao.
  • Độ dài cố định

Nhược điểm:

  • Vẫn có khả năng xảy ra đụng độ.
  • UUID không thể sắp xếp theo thời gian.
  • UUID dài nên tốn nhiều không gian lưu trữ hơn.
  • UUIDv4 chứa 1 chuỗi random nên gây ra khó khăn trong quá trình đánh index của DB.

3.3.1. Đọc thêm

  • Một số trường hợp người ta có thể gọi UUID là GUID (Globally Unique Identifier).
  • UUID có nhiều version, mỗi version đều có ưu nhược điểm riêng.
  • Bạn có thể đọc thêm về vấn đề khi sử dụng UUID với MySQL và một số cách khắc phục ở phần Tham khảo [4] bên dưới.

3.4. ULID

ULID được thiết kế để có thể sắp xếp được về mặt cấu trúc. Cấu trúc của ULID gồm Timestamp (48 bits) và Randomness (80 bits). Nhưng sẽ được encode thành một chuỗi gồm 26 ký tự.

4. ulid.jpeg

Ưu điểm:

  • Có chứa thông tin timestamp.
  • Có thể sắp xếp được theo thời gian.
  • Độ dài cố định
  • Không chứa ký tự đặc biệt (URL safe: thân thiện với web)
  • Case insentive

Nhược điểm:

  • Xác suất xảy ra đụng độ thấp tuy nhiên vẫn có và cao hơn UUIDv4. ULID có 80 random bits trong khi UUIDv4 có 122 random bits.
  • Vẫn tương đối dài.

3.5. NanoID

Nếu bạn cần một cách sinh ID có khả năng tuỳ chỉnh cao thì bạn có thể tham khảo thêm về NanoID [7]. Ví dụ: kw2c0khavhl. Tuy nhiên, bạn cần lưu ý một số điểm sau: bản thân NanoID chưa có cơ chế sắp xếp và xác suất xảy ra đụng độ có thể cao.

3.6. Twitter snowflake

Trong những cách trên, có trường hợp chưa đáp ứng được yêu cầu về khả năng mở rộng, có trường hợp lại chưa đáp ứng được yêu cầu về độ dài, … Nhưng còn một cách khác có thể đáp ứng những yêu cầu trên đó là Snowflake ID của Twitter.

Snowflake ID gồm 64 bit và có cấu trúc như sau:

5. snowflake-structure.png
  • Sign Bit (1 bit): luôn là 0
  • Timestamp (41 bits): số milisecond kể từ Twitter epoch (mốc 2010-11-04).
  • Datacenter ID (5 bits): mã định danh cho datacenter. Giá trị được khởi tạo khi bắt đầu chạy trình Snowflake generator.
  • Machine ID (5 bits): mã định danh cho máy trong datacenter. Giá trị được khởi tạo khi bắt đầu chạy trình Snowflake generator.
  • Sequence Number (12 bits): Số đếm tăng dần cho các ID được sinh trong một mili giây. Số đếm này sẽ reset về 0 khi sang milisecond khác. Dùng để đảm bảo tính duy nhất trong một mili giây. Như vậy, 1 máy có sinh 2 ^ 12 (=4096) IDs trong vòng 1 mili giây.

Ví dụ một Snowflake ID (một số nguyên 64-bit): 689369924653004799.

Khi triển khi hệ thống Snowflake ID generator sẽ như sau:

6. snowflake-system.png

Ưu điểm:

  • Đảm bảo được tính duy nhất.
  • Có khả năng mở rộng tốt.
  • Có thể sắp xếp theo thứ tự thời gian.
  • Cấu trúc đơn giản.
  • Độ dài gọn nhẹ (1 số nguyên 64-bit) dễ dàng thao tác và lưu trữ.

Nhược điểm:

  • Để triển khai giải pháp này sẽ phức tạp và tốn kém.
  • Giới hạn về khoảng thời gian hoạt động. Phần timestamp có 41 bits nghĩa là Snowflake ID generator có thể hoạt đông trong vòng 2 ^ 41 miliseconds (~ 69.7 năm). Do cấu trúc và logic sinh ID đơn giản, bạn có thể điểu chỉnh độ dài của phần timestamp, datacenter id, machine id và mốc epoch để khắc phục phần nào nhược điểm này.
  • Ta đang giả định các Snowflake ID Generator trong hệ thống chạy trên các máy khác nhau. Các máy có đồng hồ vật lý (lock) chạy giống nhau. Tuy nhiên, trên thực tế, đồng hồ vật lý của các máy có thể chạy khác nhau, tạo ra độ lệch giữa các đồng hồ. Nếu độ lệch này lớn có thể dẫn đến vấn đề xung độ khi sinh ID. Do đó, chúng ta cần thêm cơ chế đồng bộ đồng hồ để hạn chế độ lệch này.
  • Giá trị các ID không liên tục.
  • Một máy có thể sinh 4096 IDs trong vòng 1 mili giây. Đây là một con số tương đối lớn, tuy nhiên nó cũng là 1 giới hạn chúng ta cần để lưu ý.

Dựa trên ý tưởng của Snowflake ID, Sony có một sinh ID khác (Sonyflake ID [8]), có khoảng thời gian hoạt động lâu hơn (~174 năm) nhưng sinh ID chậm hơn Snowflake ID.

4. Tổng Kết

  • Đầu tiên chúng ta cần xác định hết yêu cầu và các yêu cầu tối thiểu của việc sinh ID.
  • Không có giải pháp nào có thể đáp ứng được tất cả yêu cầu. Đa số các trường hợp chúng ta chỉ cần có một tập con của các yêu cầu đề cập ở trên. Ví dụ trường hợp A, ta không yêu cầu có khả năng sort. Trường hợp B, chúng ta không yêu cầu ID chỉ có số. Trường hợp C, ta cần một giải pháp dễ triển khai, … Dựa vào yêu cầu cụ thể để ta chọn ra giải pháp phù hợp nhất.
  • Nếu các giải pháp trên chưa đáp được yêu cầu của bạn thì bạn hoàn toàn có thể tạo ra 1 cách sinh ID phục vụ riêng bài toán của bạn.

Bạn đang sử dụng cách thức nào để sinh ID, hãy comment ở dưới giúp mình nhé.

Nếu mọi người thấy bài viết hữu ích thì nhờ mọi người share để nội dung của Ronin được nhiều người biết hơn.

Cám ơn mọi người. 🙏

5. Tham khảo

[1] Sách System Design Interview: An Insider’s Guide - Alex Xu

[2] Ticket Servers: https://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/

[3] UUID: https://en.wikipedia.org/wiki/Universally_unique_identifier

[4] Vấn đề khi sử dụng UUID với MySQL: https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql

[5] ULID: https://wiki.tcl-lang.org/page/ULID

[6] Shopify dùng ULID sinh Idempotency Key: https://shopify.engineering/building-resilient-payment-systems

[7] NanoID: https://zelark.github.io/nano-id-cc/

[8] SonyFlake ID: https://github.com/sony/sonyflake


📚️ System Design VN: https://fb.com/groups/systemdesign.vn

beginner
noi-bat
systemdesign

Bài viết liên quan

Cache strategies - Lựa chọn chiến lược nào cho dự án của bạn?

I. Giới thiệu Bạn hẳn đã quen thuộc với khái niệm cache rồi nhỉ? Khi ứng dụng chạy chậm, giải pháp thường nghĩ đến là dùng cache – nghe có vẻ đơn giản. Nhưng triển khai cache như thế nào để vừa đạt hiệu quả cao, vừa đảm bảo tính chính xác của dữ liệu lại là một bài toán không hề dễ. Trong bài viết này, chúng ta sẽ cùng tìm hiểu 5 chiến lược caching phổ biến, phân tích ưu nhược điểm của từng chiến lược và khám phá cách áp dụng chúng vào các tình huống thực tế để tối ưu hiệu suất hệ thống nhé.

Lost Update: Tồn kho còn 1, nhiều người cùng order thì xử lý thế nào?

by @HieuHocCode I. Giới thiệu Hãy tưởng tượng bạn đang xây dựng một sàn thương mại điện tử và gặp phải tình huống sản phẩm chỉ còn 1, nhưng có đến 2 khách hàng đặt hàng cùng lúc. Làm thế nào để hệ thống xử lý tình huống này một cách chính xác, tránh sai sót? Đây chính là một thách thức phổ biến khi xử lý nhiều transaction đồng thời. Vấn đề này thường liên quan đến khái niệm race condition, trong đó các giao dịch song song sẽ tranh chấp quyền thao tác trên dữ liệu, dẫn đến những tình trạng sa

Log tập trung là gì? Tại sao cần trong microservices (phần 1)

by @hieuhoccode I. Giới thiệu Để đáp ứng nhu cầu phát triển nhanh chóng, dự án mình làm đã chuyển từ mô hình monolithic sang microservices. Tuy nhiên, hệ thống ngày càng phức tạp cũng đặt ra nhiều thách thức. Một trong số đó là vấn đề quản lý và giám sát log: * Khó khăn trong việc debug: Một request có thể đi qua 5-10 services khác nhau, mỗi service sinh ra log riêng biệt. Khi có lỗi, việc truy vết theo các service mất rất nhiều thời gian. * Chậm trễ trong phản ứng sự cố: Team thường phát

System Design (Thiết Kế Hệ Thống) là gì? Cần thu thập những thông tin gì để có thể bắt tay vào thiết kế?

System Design (thiết kế hệ thống) là quá trình xác định được kiểu kiến trúc (architecture), thành phần (module), giao diện (interfaces) và dữ liệu (data) cho một hệ thống để đáp ứng được một tập yêu cầu cụ thể (specified requirements).

Distributed Lock Là Gì? Tại Sao Nó Quan Trọng và Cách Triển Khai Với Redis

by @ AnhDH Mục lục 1. Đặt vấn đề 2. Distributed Lock 3. Triển khai Distributed Lock với Redis 4. Best practice 5. Tổng kết 6. Tham khảo Trong các hệ thống phân tán, việc đảm bảo tính nhất quán của dữ liệu (data consistency) và ngăn chặn tranh chấp tài nguyên (race condition) là một thách thức lớn, đặc biệt khi nhiều tiến trình hoặc service truy cập đồng thời vào các tài nguyên dùng chung. Một trong những giải pháp quan trọng để giải quyết vấn đề này chính là sử dụng distributed lock (k

Tất cả bài viết
logo

HỘ KINH DOANH LẬP VƯƠNG

Giấy chứng nhận đăng ký doanh nghiệp số: 8656162915-001. Cấp ngày 21/02/2024. Nơi cấp: Sở Kế hoạch và Đầu tư TP. Hà Nội

PHƯƠNG THỨC THANH TOÁN

vnpay

LIÊN HỆ

roninengineer88@gmail.com

0362228388

26 ngõ 156 Hồng Mai, Hai Bà Trưng, Hà Nội

THEO DÕI CHÚNG TÔI

Facebook

Youtube

Tiktok

CHÍNH SÁCH

Chính sách bảo mật

Chính sách thanh toán

Đổi trả/Hoàn tiền

Hướng dẫn thanh toán VNPAY

PHƯƠNG THỨC THANH TOÁN

vnpay

Ronin Engineer 2024