Sequencer

Motivation

Trong 1 hệ thống phân tán lớn, mỗi giây sẽ có hàng triệu events xảy ra.

Comments trong Facebook? 1 Tweet được share? 1 post Instagram?

Ta sẽ cần 1 cơ chế để có thể phân biệt hàng tấn events với nhau. 1 trong các cách đó là tạo 1 ID độc nhất cho mỗi event.

Các ID độc nhất sẽ giúp ta xác định đc luồng của events và giúp cho việc debug trở nên dễ dàng hơn.

How do we design a sequencer?

  1. Design of a Unique ID Generator

  2. Unique IDs with Causality

Design of a Unique ID Generator

Requirements for unique identifiers

First solution: UUID

Đây là 1 số 128 bit và được biểu diễn dưới dạng hexadecimal ví dụ như sau:

123e4567e89b12d3a456426614174000

Giải pháp này cho ta 10^38 số.

Mỗi server có thể tự gen ID của riêng no và gán ID vào các event liên quan tới nó. Vì UUID độc lập với server nên việc scale là rất dễ dàng. Ngoài ra chúng cũng cho độ sẵn sàng cao với tỷ lệ bị lặp rất thấp.

Hạn chế

Second solution: using a database

Sử dụng 1 database tập trung để gen unique IDs.

Nhằm tránh việc giải pháp này có thể trở thành 1 single point failure, ta có thể đối phó như sau:

Chỉnh sửa tính năng auto-increment của database. Thay vì cộng dồn 1 đơn vị như thường lệ, ta sẽ cộng dồn dựa theo 1 giá trị m, với m là số database servers mà ta có.

Mỗi server gen 1 ID, và cộng dồn ID tiếp theo với step là m.

Ví dụ nếu ta có 3 database servers, thì server đầu sẽ gen ID theo thứ tự 1 -> 4 -> 7 -> 10 -> ...

Hạn chế

Giải pháp này sẽ khá khó scale cho hệ thống được đặt ở nhiều data centers.

Third solution: using a range handler

Tưởng tượng trong 1 dãy số từ 1 đến 2 tỷ, ta có thể chia thành các khoảng nhỏ. Ví dụ như 1 -> 1000000, 1000001 -> 2000000, etc...

Ta có thể cho các server đăng ký từng khoảng một như vậy, và chúng sẽ sử dụng các đơn vị trong khoảng đó để làm ID. Khi 1 khoảng được dùng hết, server sẽ cần request để được cấp phát thêm 1 khoảng mới.

Như vậy, ta sẽ cần 1 service nhỏ để làm nhiệm vụ quản lý các khoảng ID được cấp phát cho các servers. Service này sẽ lưu các khoảng đang bận và thông tin cùng trạng thái của server đang sử dụng 1 khoảng nào đó.

Service quản lý các khoảng IDs cũng có thể trở thành single point of failure, nên ta cũng cần failover server cho service đó.

Ưu điểm

Scalable, available, unique IDs. Ta có thể bảo trì các khoảng với dạng số 64 bits.

Nhược điểm

Ta có thể bị mất 1 khoảng IDs lớn nếu 1 server đã đăng ký sử dụng 1 khoảng IDs bị oẳng giữa chừng.

Nhằm giảm thiểu thiệt hại nếu trường hợp này xảy ra, ta nên chi cấp pháp các khoảng IDs nhỏ vừa đủ cho các servers.

Use UNIX time stamps

UNIX time stamps được chia theo đơn vị millisecond và có thể sử dụng để xác định timeline của các events.

Ta có thể áp dụng UNIX time stamps để tạo 1 server gen IDs có thể gen 1 ID mỗi millisecond. Bất kỳ request gen ID đều sẽ được điều hướng tới server này.

Khả năng gen ID theo ms cho phép ta có thể gen 1000 IDs/s ⇒ Trong 1 ngày, ta có thể gen tới:

24(hour) * 60(min/hour) * 60*(sec/min) * 1000 (ID/sec) = 86400000 (IDs)

Nhằm tránh server gen ID có thể trở thành 1 single point of failure, ta có thể thêm nhiều server gen ID cùng lúc. Mỗi server đảm bảo việc gen ra unique ID bằng cách thêm 1 prefix độc nhất của server đó cùng với time stamps vào ID.

Ưu điểm

Cách tiếp cận này đơn giản, đảm bảo tính mở rộng, và cho phép nhiều servers handle requests đồng thời.

Nhược điểm

Nếu chỉ có 1 server gen ID, các request đồng thời sẽ nhận cùng 1 time stamps, dẫn tới việc ID ko còn độc nhất.

Twitter Snowflake

Twitter Snowflake illustration (Source: Educative.io)
Twitter Snowflake illustration (Source: Educative.io)

Chú thích

Ưu điểm

Nhược điểm

Using logical clocks

Lamport clocks

Trong Lamport clocks, mỗi node đều có bộ đếm riêng.

Trước khi thực thi 1 event ở 1 node, bộ đếm tăng giá trị lên 1 đơn vị. Message được gửi từ event này sẽ mang theo giá trị đó đến node mục tiêu.

Khi node mục tiêu nhận message, node đó sẽ update logical clock bằng cách so giá trị lớn nhất của bộ đếm trong node với giá trị của message. Node cập nhật bộ đếm xong rồi mới thực thi message.

Lamport clocks giúp sắp xếp thứ tự các sự kiện, nhưng chỉ cung cấp thứ tự tương đối mà ko thể hiện được sự kiện nào đã thực sự xảy ra trước sự kiện nào trên toàn hệ thống. Ta có thể giải quyết hạn chế này qua Vector clocks.

Vector clocks

Vector clocks duy trì thứ tự nhân quả của các sự kiện. Để thực hiện được nhiệm vụ này ta cũng cần 1 cấu trúc dữ liệu phù hợp để capture lại lịch sử nhân quả của mỗi sự kiện.

Để có thể capture toàn bộ chuỗi nhân quả của sự kiện, 1 vector clock phải chứa ít nhất n nodes. Hệ quả là tổng số nodes tham gia vào gen IDs tăng lên số lượng khổng lồ, dẫn đến vector clocks phải tiêu tốn 1 lượng lưu trữ rất lớn.

Ở một số hệ thống hiện nay, như web apps, coi mỗi browser của client như 1 node. Nhưng thông tin của browser có thể tăng độ dài của ID lên rất nhiều, làm cho việc xử lý, lưu trữ và mở rộng gặp nhiều khó khăn.

TrueTime API

Google's TrueTime API trong Google Spanner là 1 lựa chọn khá hay ho. Thay vì 1 time stamp cụ thể, nó sẽ report lại 1 interval of time.

Khi truy xuất thời điểm hiện tại, ta sẽ nhận đc 2 giá trị: sớm nhấtmuộn nhất, tượng trưng cho thời điểm sớm nhất và thời điểm muộn nhất mà Spanner nhận thức được với request.

Dựa vào 2 giá trị này, đồng hồ trong hệ thống của ta có thể nhận biết thời gian thực tế sẽ nằm trong khoảng giữa 2 giá trị.

Google deploys 1 GPS receiver hoặc atomic clock ở mỗi data center, và các đồng hồ được đồng bộ trong khoảng 7ms, cho phép Spanner tối thiểu mức sai lệch thời gian giữa các đồng hồ.

Nếu A[earliest] < A[latest] < B[earliest] < B[latest], thì B chắc chắn xảy ra sau A.

Để sử dụng TrueTime intervals cho unique ID, giả sử interval sớm nhất là TE, muộn nhất là TL, và sai số là ε. Ta sử dụng TE với đơn vị milliseconds biểu diễn cho time stamp trong ID:

Ưu điểm

Nhược điểm

Requirements filled by these options

UniqueScalableAvailable64-bit numeric ID
Using UUIDfalsetruetruefalse
Using database serversfalsetruetruetrue
Using a range handlertruetruetruetrue
Using UNIX time stampsfalseweaktruetrue
Using Twitter Snowflaketruetruetruetrue
Using vector clockstrueweaktruecan exceed
Using TrueTimetruetruetruetrue