Bài viết được sự cho phép của tác giả Võ Doãn Thành
1. Kể chuyện nè
Có 2 điều mình muốn kể cho bạn nghe. Một là tại sao mình viết topic này. Hai là hoàn cảnh nào mà mình nhận được câu hỏi như topic.
Thật ra, ban đầu mình không nghĩ sẽ chia sẽ topic này đâu. Do trước đó mình có viết 1 vài về “Data nên lưu vào database trước rồi mới lưu vào cache hay phải làm ngược lại?“.
Thì mình có lấy ví dụ để mô tả thì đa số các ví dụ đều dùng Microservice. Và có rất nhiều bạn đặt câu hỏi liên quan tới việc làm sao đảm bảo tính nhất quán (consitent) dữ liệu cho các services với nhau.
Lúc đó mình mới nghĩ ra được topic này. Vì nó khá liên quan tới việc quản lý transactions giữa các services.
Nguyên văn câu hỏi mà mình nhận được trước đó “How do you handle transactions in Microservice architecture? And if one service fails, what’s the process for recovering data changes made by preceding services?”.
Có thể bạn đã từng tìm hiểu những lợi ích hoặc những khó khăn khi áp dụng Microservice architecture. Và có thể bạn đã so sánh nó với Monolith để tìm ra những ưu và nhược điểm cho từng thiết kế. Việc tìm hiểu về quản lý transactions thì theo mình thấy rất ít được quan tâm.
Bài viết này sẽ mang lại về kiến thức tổng quan về vấn đề này. Và tình huống thực tế để có thể áp dụng vào nó. Hy vọng nó có ích cho bạn đọc. Mấy ông mà chém chém là rành Microservice mà không biết cái này thì nên xem kĩ nha.
Chứ vô phỏng vấn mà tui hỏi cái này trả lời không được thì coi như mất điểm nha.
2. Đặt tình huống
Mình thường lấy ví dụ về việc thanh toán đơn hàng trên eCommerce. Cái này chắc quen rồi. Cứ có events là ta chốt đơn thôi (được giảm giá mà).
Bạn cho mua 6 sản phẩm ví dụ bạn mua 6 cái nón lưỡi trai để về tặng quà cho các cháu vì hè này nắng dữ mang nó cho đỡ nắng – Tổng giá tầm 1tr2.
Sẽ có các bước để tiếp nhận order khi bạn click thanh toán – Thực tế có thể sẽ hiểu hơn thế nha.
- Inventory service – Kiểm tra tồn kho. Nếu số lượng đủ thì sẽ trừ kho, ngược lại thì sẽ thống báo hế hàng hoặc không đủ số lượng cho khách hàng biết.
- Coupon service – Kiểm tra coupon bạn dùng có hợp lệ hay đã dùng hay chưa.
- Payment service – Ví dụ bạn dùng thẻ để thanh toán, thì service này sẽ trừ tiền của bạn để đảm bảo chắc chắn bạn sẽ mua hàng.
- Delivery service – Tìm đơn vị ship hàng cho bạn. Nếu không có đơn vị nào ship thì coi như đơn hàng không thành công.
Tại sao qua mỗi bước chúng ta lại cần phải kiểm lại? Vì tránh trường hợp khi bạn thanh toán cũng có người khác thanh toán.
Lúc này nó sẽ quyết định theo thứ tự để tránh sai xót cho bên bán và bên mua. Nếu có 1 trong những bước bị lỗi thì sẽ revert lại hết các trạng thái của các bước trước.
Bài toán: Ví dụ ở bước kiểm tra thanh toán nhưng mà bạn không đủ tiền. Thì cần phải làm thế nào để đảm bảo 2 bước trước đó có thể revert lại được.
3. Cái khó của quản lý transactions
Ok, chúng ta đã nắm được tình huống và cũng hiểu vấn đề có thể xảy ra. Vậy giờ trước khi đi vào cách giải quyết ta xem sẽ có những khó khăn gì nha.
Với bài toàn này nếu chúng ta đang build trên Monolith thì nó sẽ nhẹ nhàng hơn ha?
Chúng ta sẽ bỏ các bước này vào 1 transaction. Nếu có 1 trong những bước fail thì đơn giản là bỏ vào try-catch block. Nếu mọi thứ diễn ra tốt đẹp thì sẽ commit transaction, còn không thì sẽ rollback lại.
Nhưng mà chúng ta muốn tận dụng nhiều lợi ích của Microservice như là chia nhỏ để trị, nhiều team có thể build các tính năng riêng biệt mà không ảnh hưởng lần nhau, có thể triển khai từng phần, tập trung nâng cấp hạ tầng hoặc cắt giảm theo từng service riêng, và có nhiều lợi ích khác,…
Giờ mỗi service sẽ có cách thiết kế database riêng, sẽ có những database storage khác nhau. Các service communicate sẽ thông qua HTTP request, Mesage queue, gRPC, …
Vậy giờ muốn consistent data thì cần thiết kế như thế nào?
4. Giải pháp
Giải pháp mà chúng ta có thể tiếp cập đó chính là Saga Pattern.
Trong Saga Pattern, nó lại chia ra 2 options để chúng ta có thể lựa chọn phù hợp với hệ thống của mình.
1. Orchestration (Điều phối) – Command based
– Trước khi hiểu option này sẽ làm những gì. Ta cần hiểu “Command” có nghĩa cụ thể là gì.
– Command được dịch ra chính là yêu cầu / ra lệnh. Cần có 1 người nắm toàn bộ process và qua mỗi step người đó sẽ ra lệnh / yêu cầu các service khác thực thi mệnh lệnh nào đó.
– Thì người nắm toàn bộ và điều phối tất cả hoạt động sẽ được gọi là “Orchestrator service”.
Giờ mình dùng chính bài toán để xem nó hoạt động sau nha.
- Khi user click thanh toán. Order service sẽ tạo 1 đơn hàng nháp (draft). Lúc này nó sẽ báo Orchestrator service là “Đơn hàng nháp đã tạo thành công.”.
- Khi này Orchestrator service yêu cầu Inventory service “Kiểm tra với đơn hàng này thì số lượng tồn kho còn đủ để bán không?”. Sau khi Inventory service kiểm tra thấy số lượng ok thì lại báo về cho Orchestrator service “Số lượng dư, đủ để bán rồi nha.”.
- Khi này Orchestrator service yêu cầu Coupon service “Kiểm tra lại với coupon này có dùng ở đâu chưa? Lỡ nó đặt 2 đơn cùng 1 coupon thì chết.”. Sau khi Coupon service kiểm tra và thấy ok thì sẽ báo lại cho Orchestrator service “Coupon này chưa dùng đâu. Cho nó apply để giảm giá đi.”.
- Khi này Orchestrator service yêu cầu Payment service “Sau khi tính toán xong thì tổng tiền cho user này là 1tr2 nha. Hãy lấy đúng số tiền thôi”. Payment service lấy được tiền nên rất vui vẻ và báo lại cho Orchestrator service “Đã lấy đủ tiền.”.
- Khi này Orchestrator service yêu cầu Delivery service “Đây là địa chỉ đơn hàng này nè. Liên hệ bên chỗ ship hàng cho user nha.” Delivery service kế nối được với bên giao hàng, xong báo lại cho “Đã book được shipper cho đơn này rồi. Họ hẹn mai mới giao.”.
- Tới bước này thì Orchestrator service khá hài lòng. Vì tất cả các bước đã hoàn thành. Xong giờ nó báo lại Order service “Tất cả các bước đã xong. Giờ có thể báo lại cho user biết thông tin rồi.”. Và Order service sẽ trả về cho user những thông tin cần thiết.
Đây là trường hợp happy case. Vậy nếu có xảy ra 1 trong các bước thật bại chúng ta.
- Retry lại theo từng service.
- Trường hợp không thể retry hoặc tới giới hạn retry. Thì ông Orchestrator service sẽ yêu cầu các service trước đó revert lại changes.
Ưu điểm:
– Như chúng ta cũng dễ thấy đó là việc quản lý workflow khá rõ ràng.
– Chỉ có mỗi Orchestrator service là cần nắm toàn bộ nghiệp vụ. Mỗi service chỉ quan tâm tới Command nào để cần thực hiện các hành động gì thôi.
Nhược điểm:
– Lúc này Orchestrator service chính là single-point failure. Nếu nó die thì toàn bộ worklflow coi như gãy. Nhưng đừng quá lo lắng là giờ chúng ta đa số deploy multi instances nếu nó die thì sẽ có instance khác thay thế.
– Vì tất cả các giao tiếp (communication) đều đổ dồn về cho Orchestrator service. Nó có thể sinh ra vấn đề về latency. (Tổng thời gian phải xử lý lâu hơn.)
– Nếu mà có thay đổi nào đó về bussiness thì Orchestrator service phải thay đổi nhiều.
2. Choreography (Tự biên tự diễn) – Event based
– Chúng ta cần hiểu “Event” là gì ha? Event đó chính là 1 điều gì đó đã được xảy ra. Nó như là thông báo. Ví dụ “Tôi đã tạo đơn hàng xong.”, “Tôi đã trừ tiền của khách hàng rồi.”, …
– Ở option này, thì sẽ không còn Orchestrator service nữa mà các service sẽ chủ động giao tiếp với nhau luôn.
– Thường với option này sẽ xử dụng Message Queue để giao tiếp mục đích là loose-coupling.
– Lúc này mỗi service vừa là publisher (để đẩy ra những events) vừa subscriber (để lắng nghe những events liên quan).
Ok, giờ mình mô ta với bài toán trên nha.
- Khi user click thanh toán, Order service sẽ tạo 1 đơn hàng nháp (draft). Lúc này nó sẽ publish ra event “Đơn hàng nháp đã tạo.”.
- Khi có được event này thì cả Inventory service và Coupon service đều subscribe. Và xử lý tiếp vụ validate data. Mỗi service sau khi xử lý xong nó sẽ publish những event sau “Đã trừ tồn kho.” và “Đã apply coupon vào đơn hàng.”.
- Order service sẽ lắng nghe và chờ để nhận được đầy đủ 2 events này. Khi đó nó sẽ publish ra 1 event “Đơn hàng đã xác thực được các thông tin cần thiết.”.
- Lúc này Payment service lắng nghe event này thực hiện quá trình trừ tiền cho khách hàng và sau đó publish event “Đã trừ tiền của chủ đơn hàng.”.
- Delivery service lắng nghe nhận event này và tìm đơn vị ship đơn. Sau đó publish event “Đã tìm được shipper cho đơn hàng này.”
- Khi nghe được event này thì Order service sẽ cập nhật lại trạng thái cho đơn hàng là “Đang giao”.
- Lúc này đã hết flow. Có thể trả về thông tin cuối cùng cho user.
Đây là trường hợp happy case. Vậy nếu có xảy ra 1 trong các bước thật bại chúng ta.
- Retry lại theo từng service.
- Nếu không thể retry, thì các service đang fail cần phải publish ngược lại 1 event (event thực hiện thất bại) để cho các service khác biết.
Ưu điểm:
– Nó không cần bất kỳ service nào quản lý workflow.
– Nó là loose-coupling nên các service hoạt động độc lập. Có gì cứ thông qua event mà xử lý thôi.
Nhược điểm:
– Nó sẽ hơi khó để hiểu toàn bộ workflow. Đặt biệt nếu có 1 service mới vào. Thì lúc này có thể phải thay đổi code của những service khác.
– Việc publish và quản lý các event không rõ ràng nó có thể tạo ra vòng lập event. Tức là nó chạy nhưng không có điểm đích, bị xoay vòng event.
Bạn có thể đọc thêm ở: https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/saga/saga
5. Kết luận
Việc quản lý các transactions trong Microservice architecture thật sự khó nhưng vẫn có 2 phương pháp mà chúng ta có thể tham khảo. Cân nhắc chọn lựa đúng theo nghiệp vụ hoặc thiết kế hệ thống để giúp nâng cao tính mở rộng nhé.
6. Câu hỏi mở rộng
Quay lại bài toán bài đầu. Trong trường hợp nếu mà mình đã trừ tồn kho rồi nhưng bước thanh toán bị thất bại thì lúc này ở Inventory service mình sẽ xóa lịch sử trừ tồn kho trước đó hay mình sẽ thêm lịch sử cho phần nhập kho vì thanh toán thất bại?
Bài viết gốc được đăng tải tại LinkedIn Thanh Vo
Xem thêm: