Trong bài này, mình sẽ giải thích cho các bạn hiểu các vấn đề liên quan đến Distributed Transactions trong microservice, các phương pháp triển khai và hạn chế & ưu điểm của từng phương pháp. Vì đây là kỹ thuật khá nâng cao nên mình xin phép dùng nhiều thuật ngữ chuyên ngành trong bài viết. Bởi vì, khi dịch sang tiếng Việt mình thấy khá “chuối” và chưa thực sự đúng ngữ cảnh lắm nên mình xin phép giữ nguyên từ gốc tiếng Anh trong bài viết. Bây giờ, hãy cùng mình tìm hiểu về Distributed Transactions nhé!
Tại Sao Cần Distributed Transactions?
Distributed transactions cần thiết khi một action cần phải thay đổi dữ liệu trên nhiều service khác nhau và đảm bảo rằng tất cả các thay đổi đều được thực hiện một cách đồng nhất: hoặc tất cả thay đổi hoặc không có gì thay đổi. Ví dụ, trong một ứng dụng thương mại điện tử, một giao dịch mua hàng có thể bao gồm các bước sau:
- Trừ tiền từ tài khoản của khách hàng. (customer service database)
- Giảm số lượng hàng tồn kho. (Inventory service database)
- Ghi lại thông tin đơn hàng trong hệ thống quản lý đơn hàng. (Order service database)
Nếu bất kỳ bước nào trong quy trình này thất bại, transaction phải được rollback cho tất database của các service để đảm bảo tính toàn vẹn của dữ liệu.
Không giống như kiến trúc monolithic, chúng ta không thể dựa vào việc rollback transaction của một database duy nhất(single database transaction rollback).
Lợi Ích của Distributed Transactions
- Tính Toàn Vẹn Dữ Liệu: Chắc chắn rồi, đây luôn là lợi ích tối cao nhất mỗi khi chúng ta sử dụng transactión. Distributed transactions giúp đảm bảo rằng tất cả các thao tác dữ liệu liên quan đến một transaction qua nhiều service/ database đều được hoàn thành hoặc không có thao tác nào được thực hiện, tránh tình trạng dữ liệu không nhất quán.
- Đơn Giản Hóa Logic của Application: Sử dụng distributed transactions có thể đơn giản hóa logic xử lý transaction trong Application, giúp các developers không phải viết quá nhiều code phức tạp để kiểm tra tính consitency của dữ liệu cũng như phải handle error quá nhiều.
- Handle error: Distributed transactions giúp handle error tốt hơn, đặc biệt là trong trường hợp một phần của transaction failed, toàn bộ transaction sẽ được rollback để đảm bảo tính toàn vẹn mà developer không cần phải xử lý quá nhiều.
Hạn Chế của Distributed Transactions
Cái gì cũng có 2 mặt của nó, Distributed Transactions giúp chúng ta kiểm soát lỗi cũng như đảm bảo tính toàn vẹn của dữ liệu nhưng cũng có nhiều mặt hạn chế:
- Performance: Distributed transactions có thể làm giảm hiệu suất của hệ thống do phải đảm bảo tính toàn vẹn dữ liệu qua nhiều service và database khác nhau.
- Độ Phức Tạp: Quản lý distributed transactions rất phức tạp, đòi hỏi phải xử lý nhiều trường hợp exception và lỗi có thể xảy ra trong quá trình xử lý, cũng không đơn giản để triển khai một cách trọn vẹn đầy đủ 4 tính chất của transaction (ACID) thông qua nhiều service.
- Khả Năng Mở Rộng: Sử dụng distributed transactions có thể làm giảm khả năng mở rộng của hệ thống vì nó đòi hỏi phải quản lý state của các service chặt chẽ và phối hợp giữa nhiều service.
Các Phương Pháp Để Triển Khai Distributed Transactions
Two-Phase Commit (2PC)
Two-Phase Commit (2PC) là một phương pháp cho phép nhiều service đồng ý về một kết quả chung trong một transaction. Nếu tất cả các service cùng agree to commit, transaction sẽ được commit, ngược lại nếu có bất kỳ servicen nào xảy ra lỗi, toàn bộ transaction sẽ được rollback.
Quy trình này bao gồm hai phases:
Phase 1 Prepare: Coordinator service yêu cầu tất cả các Participants service chuẩn bị cho việc commit transaction.
Mỗi Participant thực hiện các bước cần thiết và trả lời Coordinator là “Prepared” hoặc “Failed”.
Phase 2 Commit: Nếu tất cả Participants trả lời “Prepared”, Coordinator sẽ yêu cầu tất cả các Participants commit transaction.
Nếu bất kỳ Participant nào trả lời “Failed”, Coordinator sẽ yêu cầu tất cả Participants rollback transaction.
Ví dụ bằng Java
|
|
Trong ví dụ này, khi người dùng cố gắng đặt vé, Orchestrator bắt đầu quá trình 2PC để đảm bảo rằng cả việc đặt vé và thanh toán đều được hoàn thành hoặc cả hai đều bị hủy bỏ, duy trì tính nguyên tử(Atomicity) của transaction across services
Challenges with 2PC
Triển khai 2PC trong microservices có thể gặp một số thách thức:
- Synchronous Blocks:: 2PC yêu cầu các service chờ trong giai đoạn prepare, dẫn đến Synchronous Blocks và tăng thời gian response.
- Single Point of Failure: Orchestrator/Coordinator trở thành single block. Nếu nó gặp sự cố hoặc unavailable, toàn bộ transaction có thể bị ảnh hưởng.
- Data Inconsistency: Trong trường hợp service throw exception trong giai đoạn commit, việc đảm bảo tất cả các service rollback về trạng thái trước đó có thể khó khăn, dẫn đến tiềm ẩn không nhất quán dữ liệu.
- Complex Error Handling: Xử lý lỗi và triển khai cơ chế rollback trong các microservices có thể phức tạp do tính phân tán của các database và logic code không thống nhất ở một hoặc 1 vài điểm.
- Resource Locking: Trong giai đoạn prepare, các resource có thể bị khóa, làm giảm performance và khả năng phản hồi của hệ thống.
Trong kiến trúc microservice, 2PC ít được ưa chuộng do những challenges này. Thay vào đó, các phương pháp như Saga thường được ưu tiên, vì chúng cung cấp cách xử lý các distributed transactions mà không cần sự phối hợp chặt chẽ và lock resource, từ đó mang lại khả năng resilience và hiệu suất tốt hơn trong các services.
Saga Pattern
Saga pattern được tạo nên từ 1 chuỗi các “local transaction”(transaction trong Database của service đó mà thôi), trong đó mỗi service thực hiện phần action của mình trong toàn bộ process. Nếu bất kỳ step nào thất bại, các “event rollback” sẽ được push để hoàn tác các step trước đó, đảm bảo tính nhất quán dữ liệu mà không cần sử dụng đến two-phase commit.
Nguyên tắc triển khai của Saga pattern sẽ được dựa trên Event Driven Architechture, nghĩa là sẽ có các consumer/producer để consume/push các event khi có error xảy ra. Mình sẽ giải thích EDA trong một blog khác để mọi người hiểu rõ hơn về kiến trúc này.
Trong ví dụ trên, quá trình đặt chỗ cho một sự kiện bao gồm ba bước chính: : blocking the seat, receiving payment, and confirming the booking. Các bước này có thể được xử lý bởi các microservices khác nhau. Đây là cách áp dụng mẫu Saga:
- Block Seat: SeatReservationService cố gắng book ghế. Nếu thành công, nó gửi một thông báo sự kiện đến service tiếp theo.
- Receive Payment: PaymentService listens for the seat blocked event, cố gắng xử lý thanh toán, và nếu thành công, sends an event message để xác nhận đặt chỗ.
- Confirm Booking: BookingService listens for the payment received event and hoàn tất việc đặt chỗ, xác nhận đặt ghế.
Nếu bất kỳ step nào trong trình tự này thất bại, ví dụ như nếu quá trình thanh toán thất bại sau khi ghế đã bị book, compensating transactions sẽ được kích hoạt:
- Hoàn tiền: Nếu thanh toán ban đầu được ghi nhận nhưng thất bại sau đó, service thanh toán sẽ hoàn lại tiền.
- Bỏ chặn ghế: Đáp lại việc thanh toán thất bại, service đặt chỗ sẽ bỏ chặn ghế.
- Hủy đặt chỗ: Tương tự, nếu việc đặt chỗ ban đầu đã được ghi nhận nhưng không thể xác nhận, service đặt chỗ sẽ hủy việc đặt chỗ.
Chuỗi các compensating transactions này đảm bảo rằng hệ thống duy trì được tính nhất quán và tất cả các service quay trở lại trạng thái ban đầu trước khi Saga bắt đầu.
Ví dụ bằng Java
Seat Reservation Service
|
|
Payment Service
|
|
Booking Service
|
|
SagaCoordinator listener để handle rollback event cho từng step
|
|
Kết Luận
Distributed transactions trong microservice là một kỹ thuật khó và phức tạp nhưng cần thiết để đảm bảo tính toàn vẹn và nhất quán của dữ liệu trong hệ thống phân tán như microservice. Việc sử dụng các phương pháp triển khai như Two-Phase Commit (2PC) và Saga đều có ưu và nhược điểm riêng.
2PC cung cấp tính nguyên tử cao nhưng có thể gặp vấn đề về performance và single block. Ngược lại, Saga linh hoạt hơn, giảm tải cho hệ thống nhưng đòi hỏi quản lý phức tạp về compensating transactions.
Việc lựa chọn phương pháp phù hợp phụ thuộc vào yêu cầu cụ thể của hệ thống và khả năng quản lý của development team. Bằng cách hiểu rõ và áp dụng đúng các kỹ thuật này, chúng ta có thể xây dựng các hệ thống microservice mạnh mẽ và đáng tin cậy.
Hy vọng qua bài viết này, các bạn đã có cái nhìn rõ hơn về distributed transactions và cách áp dụng chúng trong kiến trúc microservice. Hãy thử nghiệm và chia sẻ kinh nghiệm của bạn để mình cùng biết và học hỏi nhé!
HAPPY CODING!
Nguồn các trích dẫn mình dùng trong blog:
*https://medium.com/@billa.anudeep143/distributed-transactions-in-spring-boot-microservices-simplified-guide-ce1e3a9191c0