Chào bạn,
Trong bài học trước, chúng ta đã trang bị một kỹ năng tối quan trọng cho việc phát triển quy trình nghiệp vụ: kiểm thử tự động. Bạn đã học cách implement unit test cho các logic nghiệp vụ riêng lẻ và process test để xác thực toàn bộ luồng BPMN. Nền tảng vững chắc về kiểm thử này là điều kiện tiên quyết để chúng ta tự tin bước vào các kiến trúc phức tạp, nơi mà việc đảm bảo tính đúng đắn và nhất quán của dữ liệu trở nên thách thức hơn bao giờ hết.
Hôm nay, chúng ta sẽ khám phá một trong những mẫu thiết kế quan trọng nhất trong thế giới microservices. Mục tiêu của bài học này là giải thích Saga pattern và vai trò của nó trong việc đảm bảo tính nhất quán dữ liệu. Đây là một bài học mang tính khái niệm nhưng vô cùng cần thiết, đặt nền móng cho việc bạn sẽ tự tay xây dựng các quy trình điều phối microservices trong các bài học tiếp theo.
1. Thách thức của Tính nhất quán trong Kiến trúc Microservices
Trong một ứng dụng nguyên khối (monolithic) với một cơ sở dữ liệu duy nhất, việc đảm bảo các giao dịch (transactions) diễn ra một cách toàn vẹn là tương đối đơn giản nhờ vào các thuộc tính ACID (Atomicity, Consistency, Isolation, Durability). Nếu một chuỗi thao tác (ví dụ: tạo đơn hàng, trừ kho, thanh toán) gặp lỗi giữa chừng, toàn bộ giao dịch sẽ được rollback một cách tự động, đảm bảo dữ liệu không bao giờ ở trạng thái lửng lơ.
Tuy nhiên, trong kiến trúc microservices, mỗi service thường có cơ sở dữ liệu riêng. Điều này mang lại sự linh hoạt và độc lập, nhưng cũng tạo ra một thách thức lớn: làm thế nào để duy trì tính nhất quán dữ liệu trên nhiều service khác nhau? Một giao dịch truyền thống không thể kéo dài qua nhiều cơ sở dữ liệu.
Vậy tại sao chúng ta không sử dụng các cơ chế giao dịch phân tán như "two-phase commit" (2PC)?
Using sagas to maintain data consistency in a microservice architecture by Chris Richardson
Video của Chris Richardson, một chuyên gia hàng đầu về microservices, sẽ giải thích rất rõ ràng những hạn chế của các giao dịch ACID truyền thống và two-phase commit (2PC) trong kiến trúc microservices. Việc hiểu rõ vấn đề sẽ giúp bạn thấy được tại sao Saga pattern lại ra đời và trở nên cần thiết.
Hãy xem từ 03:50 đến 14:33. Đoạn video này sẽ giải thích: Cách một giao dịch đơn giản hoạt động trong môi trường monolithic. Tại sao việc chia nhỏ thành các microservices với cơ sở dữ liệu riêng lại làm cho giao dịch trở nên phức tạp. Những nhược điểm của two-phase commit (2PC) khiến nó không còn được ưa chuộng trong các ứng dụng hiện đại (dễ bị khóa, hiệu năng kém, không được nhiều công nghệ mới hỗ trợ).
Như bạn vừa xem, 2PC thường được coi là một "anti-pattern" trong microservices vì nó tạo ra sự phụ thuộc chặt chẽ (tight coupling) giữa các service và có thể gây ảnh hưởng lớn đến tính sẵn sàng (availability) của hệ thống. Chúng ta cần một giải pháp khác linh hoạt hơn, và đó chính là Saga.
2. Giới thiệu Saga Pattern: Chuỗi các Giao dịch Cục bộ
Saga pattern giải quyết vấn đề bằng cách thay thế một giao dịch dài, phân tán (distributed transaction) bằng một chuỗi các giao dịch cục bộ (local transactions). Mỗi giao dịch cục bộ sẽ cập nhật dữ liệu trong một service duy nhất.
Vậy nếu một bước trong chuỗi bị thất bại thì sao? Saga giới thiệu khái niệm giao dịch bù trừ (compensating transaction). Với mỗi giao dịch "xuôi" (forward transaction), chúng ta cần định nghĩa một giao dịch bù trừ tương ứng để "hoàn tác" lại những gì đã làm.
Hãy cùng xem một bài viết tiếng Việt để hiểu rõ hơn về cơ chế này.
Saga Transactions - Đặc điểm, cơ chế và ví dụ
Bài viết trên trang Software Design VN giải thích rất trực quan về cơ chế hoạt động của Saga. Hãy đọc để nắm được các khái niệm cốt lõi.
Hãy đọc phần '1. Tình huống đặt ra' và '2. Cơ chế hoạt động của Saga'. Chú ý đến các điểm sau: Ví dụ về việc tạo đơn hàng qua 3 service: Order, Inventory, Payment. Khái niệm 'Giao dịch bù trừ' (compensating transaction) và vai trò của nó. Phân biệt giữa kịch bản thành công ('happy case') và kịch bản thất bại cần rollback. Khái niệm 'giao dịch chốt' (pivot transaction) - điểm mà sau đó Saga được coi là thành công và không thể rollback.
Tóm lại, một Saga hoạt động như sau:
- Bắt đầu chuỗi các giao dịch cục bộ (T1, T2, ..., Tn).
- Nếu một giao dịch Ti thất bại, hệ thống sẽ thực thi các giao dịch bù trừ theo thứ tự ngược lại (C(i-1), ..., C2, C1) để hoàn tác các thay đổi đã được commit bởi T1, ..., T(i-1).
- Nếu tất cả các giao dịch từ T1 đến Tn đều thành công, Saga kết thúc thành công.
Điều quan trọng cần nhấn mạnh: việc hoàn tác này không phải là một rollback tự động ở cấp độ database. Nó là một logic nghiệp vụ mà bạn, với tư cách là developer, phải tự định nghĩa và implement. Ví dụ, giao dịch bù trừ cho "Trừ tiền khách hàng" là "Hoàn tiền cho khách hàng".
Trong Camunda, chúng ta có thể mô hình hóa Saga một cách trực quan bằng BPMN:

3. Các Kiểu Điều phối Saga: Orchestration vs. Choreography
Có hai cách chính để điều phối các bước trong một Saga. Việc lựa chọn giữa chúng là một quyết định quan trọng về mặt kiến trúc.

a. Orchestration (Điều phối tập trung)
- Cách hoạt động: Có một thành phần trung tâm gọi là Orchestrator chịu trách nhiệm điều khiển toàn bộ Saga. Orchestrator sẽ gửi lệnh cho từng service tham gia để thực thi giao dịch cục bộ, lắng nghe kết quả, và quyết định bước tiếp theo (tiếp tục, hoặc thực thi giao dịch bù trừ).
- Trong Camunda: Một quy trình BPMN chính là một Orchestrator hoàn hảo. Process Engine sẽ đóng vai trò trung tâm, điều khiển luồng đi qua các Service Task, mỗi task gọi đến một microservice.
- Ưu điểm:
- Logic quy trình được tập trung, dễ hiểu, dễ theo dõi và gỡ lỗi.
- Trách nhiệm của từng service tham gia rất đơn giản: chỉ cần thực thi tác vụ và báo cáo kết quả.
- Nhược điểm:
- Tạo ra sự phụ thuộc vào Orchestrator.
- Orchestrator có thể trở thành một điểm nghẽn nếu xử lý quá nhiều logic.
b. Choreography (Điều phối phi tập trung)
- Cách hoạt động: Không có một trình điều khiển trung tâm. Thay vào đó, các service giao tiếp với nhau thông qua việc phát và lắng nghe các sự kiện (events). Mỗi service sau khi hoàn thành công việc của mình sẽ phát ra một sự kiện. Các service khác sẽ lắng nghe sự kiện đó và tự biết mình cần phải làm gì tiếp theo.
- Ưu điểm:
- Các service thực sự độc lập và không phụ thuộc vào một thành phần trung tâm (loose coupling).
- Dễ dàng thêm các service mới vào chuỗi mà không cần sửa đổi logic trung tâm.
- Nhược điểm:
- Logic quy trình bị phân tán trên nhiều service, rất khó để theo dõi toàn bộ luồng.
- Có nguy cơ tạo ra các phụ thuộc vòng (cyclic dependencies) giữa các service.
Với vai trò là người học Camunda, chúng ta sẽ tập trung chủ yếu vào Orchestration Pattern, vì nó khớp một cách tự nhiên với mô hình quy trình BPMN.
Saga Pattern | Distributed Transactions | Microservices
Video này từ kênh ByteMonk đưa ra một so sánh rất rõ ràng giữa hai kiểu điều phối và minh họa bằng ví dụ đặt vé máy bay.
Hãy xem từ 07:32 đến 12:59. Tập trung vào: Cách Orchestrated Saga hoạt động với một 'Saga orchestrator' trung tâm. Cách Choreographed Saga hoạt động với các 'events'. Ưu và nhược điểm của mỗi phương pháp. Sơ đồ thiết kế hệ thống cấp cao cho một Orchestrated Saga.
4. Sự Đánh đổi: Nhất quán Cuối cùng và Thiếu tính Tách biệt
Saga pattern rất mạnh mẽ, nhưng nó đi kèm với những sự đánh đổi quan trọng mà bạn cần phải hiểu rõ. Saga không cung cấp các đảm bảo ACID mạnh mẽ như giao dịch truyền thống.
a. Eventual Consistency (Tính nhất quán cuối cùng)
Saga chỉ đảm bảo tính nhất quán cuối cùng. Điều này có nghĩa là sẽ có những "khoảnh khắc" mà dữ liệu trên toàn hệ thống ở trạng thái không nhất quán. Ví dụ, trong quy trình đặt hàng, có thể tồn kho đã bị trừ nhưng tiền chưa được thanh toán. Tuy nhiên, Saga đảm bảo rằng cuối cùng, hệ thống sẽ trở về trạng thái nhất quán: hoặc đơn hàng được hoàn tất (tồn kho trừ, tiền đã thanh toán), hoặc đơn hàng bị hủy (tồn kho được hoàn lại, tiền không bị trừ).
b. Lack of Isolation (Thiếu tính Tách biệt)
Đây là hệ quả quan trọng nhất. Vì mỗi giao dịch cục bộ tự commit ngay lập tức, các Saga khác đang chạy đồng thời có thể thấy được những thay đổi tạm thời này. Mức độ cô lập (isolation level) của Saga tương đương với Read Uncommitted trong database, có thể dẫn đến các hiện tượng bất thường như:
- Dirty Reads: Một Saga đọc dữ liệu được viết bởi một Saga khác chưa hoàn thành. Nếu Saga kia sau đó bị rollback, dữ liệu đã đọc sẽ trở nên không hợp lệ.
- Lost Updates: Hai Saga cùng đọc một bản ghi, sau đó lần lượt ghi đè lên nó, khiến cho cập nhật của Saga đầu tiên bị mất.
Giới thiệu về Saga Pattern trong microservices
Để hiểu sâu hơn về những thách thức này và cách giải quyết, hãy đọc tiếp một bài viết khác cũng bằng tiếng Việt. Bài viết này phân tích rất kỹ các hiện tượng bất thường và các biện pháp phòng ngừa.
Hãy tìm và đọc kỹ phần có tiêu đề bắt đầu bằng '1. Mô hình Saga có thể khá khó khăn ban đầu...'. Tập trung vào việc hiểu các hiện tượng bất thường như 'Lost updates', 'Dirty reads' và các biện pháp phòng ngừa được đề xuất như 'Semantic lock', 'Reread value' (tương tự Optimistic Locking).
Là một developer, bạn phải nhận thức được những vấn đề này và áp dụng các chiến lược phù hợp (như khóa ngữ nghĩa, versioning, thiết kế lại luồng Saga) để giảm thiểu rủi ro tùy theo yêu cầu của nghiệp vụ.
Tổng kết
Trong bài học hôm nay, chúng ta đã đi sâu vào một khái niệm nền tảng của kiến trúc microservices. Các điểm chính bạn cần ghi nhớ:
- Vấn đề: Các giao dịch ACID và 2PC không phù hợp với kiến trúc microservices do yêu cầu về sự độc lập và tính sẵn sàng cao.
- Giải pháp (Saga): Là một chuỗi các giao dịch cục bộ, sử dụng các giao dịch bù trừ để hoàn tác lại các thay đổi khi có lỗi, nhằm đảm bảo tính nhất quán dữ liệu.
- Các kiểu điều phối:
- Orchestration: Một bộ điều khiển trung tâm (như một quy trình BPMN) ra lệnh cho các service. Phù hợp khi logic phức tạp và cần sự minh bạch.
- Choreography: Các service giao tiếp phi tập trung qua sự kiện. Phù hợp khi cần sự độc lập cao.
- Sự đánh đổi: Saga chỉ cung cấp tính nhất quán cuối cùng và thiếu tính tách biệt (isolation), có thể dẫn đến các vấn đề về dữ liệu nếu không được xử lý cẩn thận.
Xem trước bài học tiếp theo
Với những kiến thức lý thuyết vững chắc về Saga pattern, trong bài học tiếp theo, chúng ta sẽ bắt tay vào thực hành. Bạn sẽ học cách thiết kế quy trình BPMN cho các giao dịch xuôi (forward transactions) của một Saga. Chúng ta sẽ bắt đầu xây dựng một quy trình Orchestration thực tế trong Camunda để điều phối một chuỗi các tác vụ qua nhiều service.