EP7: The Tale of Exactly-Once Semantics

Hãy tìm hiểu câu chuyện đằng sau ngữ nghĩa exactly-once.

Chúng ta sẽ đề cập đến các nội dung sau:

  • Việc một thông điệp được gửi nhiều lần
  • Hệ quả ví dụ
  • Tránh việc gửi nhiều lần một thông điệp
  • Cách tiếp cận bằng các thao tác idempotent
  • Ví dụ về thao tác idempotent
  • Ví dụ về thao tác không idempotent
  • Cách tiếp cận khử trùng lặp (de-duplication)
  • Ví dụ
  • Sự khác biệt giữa việc gửi và xử lý
  • Các ngữ nghĩa gửi khác

Việc một thông điệp được gửi nhiều lần

Các nút khác nhau trong một hệ thống phân tán giao tiếp với nhau thông qua việc trao đổi các thông điệp.

Do mạng không đáng tin cậy, các thông điệp này có thể bị mất. Tất nhiên, để xử lý điều này, các nút có thể gửi lại với hy vọng rằng mạng sẽ phục hồi vào một thời điểm nào đó và chuyển được thông điệp.

Tuy nhiên, điều này đồng nghĩa với việc các nút có thể gửi cùng một thông điệp nhiều lần, bởi vì bên gửi không thể biết chính xác điều gì đã thực sự xảy ra.

Hình minh họa sau đây cho thấy điều gì xảy ra khi một nút không gửi được thông điệp nào cả.

Hệ quả ví dụ

Hãy nghĩ xem điều gì sẽ xảy ra nếu thông điệp này dùng để báo hiệu việc chuyển tiền giữa hai tài khoản ngân hàng như một phần của giao dịch mua hàng. Ngân hàng có thể tính tiền khách hàng hai lần cho cùng một sản phẩm.

Tránh việc gửi nhiều lần một thông điệp

Để xử lý các kịch bản như trên, chúng ta có thể áp dụng nhiều cách tiếp cận nhằm đảm bảo rằng các nút chỉ xử lý một thông điệp đúng một lần, ngay cả khi thông điệp đó được gửi đến nhiều lần. Hãy cùng xem các cách tiếp cận này.

Cách tiếp cận bằng các thao tác idempotent

Idempotent là một thao tác mà chúng ta có thể áp dụng nhiều lần mà không làm thay đổi kết quả ngoài lần áp dụng đầu tiên.

Ví dụ về thao tác idempotent

Một ví dụ về thao tác idempotent là thêm một giá trị vào một tập hợp. Ngay cả khi chúng ta áp dụng thao tác này nhiều lần, các lần thực thi sau lần đầu sẽ không có tác dụng gì, vì giá trị đó đã tồn tại trong tập hợp. Tất nhiên, ở đây chúng ta giả định rằng không có thao tác nào khác có thể xóa giá trị khỏi tập hợp. Nếu không, thao tác được thử lại có thể thêm lại một giá trị đã bị xóa.

Ví dụ về thao tác không idempotent

Một ví dụ về thao tác không idempotent là tăng một bộ đếm lên một đơn vị, trong đó mỗi lần áp dụng thao tác sẽ tạo ra thêm các tác động phụ.

Bằng cách sử dụng các thao tác idempotent, chúng ta có thể đảm bảo rằng ngay cả khi một nút gửi thông điệp nhiều lần và lặp lại thao tác, kết quả vẫn sẽ giống nhau.

Tuy nhiên, các thao tác idempotent thường áp đặt những ràng buộc chặt chẽ lên hệ thống. Vì vậy, trong nhiều trường hợp, chúng ta không thể xây dựng hệ thống sao cho tất cả các thao tác đều mang tính idempotent một cách tự nhiên. Trong những trường hợp này, chúng ta có thể sử dụng một cách tiếp cận khác: cách tiếp cận khử trùng lặp.

Cách tiếp cận khử trùng lặp (de-duplication)

Trong cách tiếp cận khử trùng lặp, mỗi thông điệp được gán một định danh duy nhất, và mọi thông điệp được gửi lại đều chứa cùng định danh với thông điệp ban đầu. Bằng cách này, phía nhận có thể ghi nhớ tập hợp các định danh mà nó đã nhận và đã thực thi. Đồng thời, nó sẽ tránh việc thực thi lại các thao tác đã được xử lý.

Điều quan trọng cần lưu ý là để làm được điều này, chúng ta phải kiểm soát cả hai phía của hệ thống: bên gửi và bên nhận. Lý do là việc tạo ID diễn ra ở phía gửi, trong khi quá trình khử trùng lặp diễn ra ở phía nhận.

Ví dụ

Hãy tưởng tượng một kịch bản trong đó một ứng dụng gửi email như một phần của một thao tác. Việc gửi email không phải là một thao tác idempotent. Nếu giao thức email không hỗ trợ khử trùng lặp ở phía nhận, chúng ta không thể đảm bảo rằng mỗi email chỉ được hiển thị đúng một lần cho người nhận.

Sự khác biệt giữa việc gửi và xử lý

Khi nói về ngữ nghĩa exactly-once, việc phân biệt giữa khái niệm gửi (delivery) và xử lý (processing) là rất hữu ích.

Trong ngữ cảnh của phần thảo luận ở trên, chúng ta xem việc gửi là sự kiện thông điệp đến được nút đích ở mức phần cứng.

Còn việc xử lý được hiểu là quá trình xử lý thông điệp này ở tầng ứng dụng phần mềm của nút đó.

Trong hầu hết các trường hợp, chúng ta quan tâm nhiều hơn đến số lần một nút xử lý một thông điệp, hơn là số lần nó nhận được thông điệp đó. Ví dụ, trong ví dụ email trước đây, điều chúng ta quan tâm chủ yếu là liệu ứng dụng có hiển thị cùng một email hai lần hay không, chứ không phải là nó có nhận email đó hai lần hay không.

Như các ví dụ trước đã chỉ ra, việc đảm bảo gửi exactly-once trong một hệ thống phân tán là điều không thể. Tuy nhiên, trong một số trường hợp, vẫn có thể đảm bảo xử lý exactly-once.

Cuối cùng, điều quan trọng là chúng ta phải hiểu rõ sự khác biệt giữa hai khái niệm này và làm rõ chúng ta đang đề cập đến điều gì khi nói về ngữ nghĩa exactly-once.

Các ngữ nghĩa gửi khác

Như một lưu ý cuối cùng, có thể dễ dàng nhận thấy rằng chúng ta hoàn toàn có thể triển khai ngữ nghĩa gửi at-most-once và ngữ nghĩa gửi at-least-once.

Chúng ta đạt được ngữ nghĩa at-most-once khi mỗi thông điệp chỉ được gửi đúng một lần, bất kể điều gì xảy ra. Ngược lại, chúng ta đạt được ngữ nghĩa