Kiến trúc Microservice và vấn đề Consistency
- Kiến trúc Microservice nói một cách đơn giản, là việc ta chia nhỏ một hệ thống lớn thành nhiều service nhỏ để xử lý các công việc riêng biệt với nhau. Việc chia nhỏ hệ thống lớn đem đến rất nhiều lợi ích, ví dụ như ta có thể có nhiều team dev làm việc trên nhiều phần khác nhau, giúp tăng hiệu suất. Ta có thể giải quyết các bài toán lớn khi mà quá nhiều request tập trung về 1 server, ….
- Tuy nhiên nó cũng đi kèm nhiều vấn đề:
- Ví dụ trong thực tế, trong quá trình ship hàng, một quá trình xảy ra như sau
- Cửa hàng giao đồ cần chuyển cho shipper
- Shipper giao hàng tới cho người nhận
- Shipper báo lại cho cửa hàng là hàng đã giao
- Các công việc này, nếu coi các chủ thể thành 1 kiến trúc monolith, hay “1 khối” sẽ làm hết mọi việc, thì phía cửa hàng sẽ chuẩn bị hàng, giao hàng, rồi thu tiền hết một mình. Nhưng lúc này, ta sẽ chia chúng thành 2 service “nhỏ hơn”
- Service cửa hàng: Chỉ chuẩn bị hàng
- Service vận chuyển: Vận chuyển hàng và giao hàng
- Khi chỉ 1 khối làm việc một mình, cửa hàng luôn biết là hàng đã giao chưa. Nhưng lúc này khi chia làm 2 service, bên vận chuyển phải báo lại cửa hàng để cửa hàng nắm lại thông tin, lúc này, nếu ví dụ bên vận chuyển đã giao hàng, mà không báo lại cho cửa hàng do quên, do lỗi, .. sẽ dẫn đến sự “bất đồng bộ”, một vấn đề rất thường gặp, hay là đảm bảo tính “Consistency” trong kiến trúc Microservice
Pattern Transactional Outbox
- Có nhiều giải pháp cho vấn đề này, một trong số đó là Transactional Outbox, ví dụ hình ảnh và từng bước ở dưới
- Một khách hàng đặt một đơn hàng mới
- Hệ thống Order Service quản lý đơn hàng cập nhật đơn hàng thành “ĐANG VẬN CHUYỂN” và tạo 1 event “OrderCreated”
- Event “OrderCreated” được cho vào message queue: như trong hình ảnh là Kafka
- Một hệ thống Outbox Processor khác sẽ liên tục gửi Event yêu cầu cập nhật đến Inventory Service và Notification Service cho đến khi thành công, nếu xảy ra lỗi, nó sẽ thử lại
Các cấu phần
- Outbox Entity/Outbox Table: Chứa các Event
- Outbox Persistence Mechanism: Một hệ thống bảo đảm event trong outbox table
- Outbox Processor: Một tác vụ background chịu trách nghiệm xử lý event khỏi Outbox
- Liên tục gọi vào table của table định kỳ xem có event nào mới không
- Nếu có, gửi event mới đến các message queue
- Xử lý việc thử lại nếu thất bại
Một số câu hỏi
- Q: Tại sao khi tạo một order ở phía order-service chẳng hạn, ta không gửi thẳng luôn 1 message đến message broker ở giữa là kafka, … rồi phía payment-service sẽ consume message đó thôi, tại sao cần 1 table outbox?
- A: Sẽ có 2 vấn đề khi làm như vậy
- 1. Ví dụ, nếu order-service chưa cập nhật trạng thái, mà đã send message cho message queue rồi, thì sẽ không đảm bảo tính nhất quán. Message chỉ nên được gửi khi transaction ở local database đã commit, còn nếu bị rollback theo ACID, thì message cũng không được gửi. Tức là message cũng nên nằm trong 1 transaction của Database. Ví dụ nó đã commit xuống database local rồi, nhưng mà crash nên không gửi được message
- 2. Message thì đã được gửi rồi, nhưng mà transaction ở local chưa run xong, và sau đó nó fail nên roll back lại, nhưng mà message đã gửi rồi
- Thì lúc này ta sẽ lưu các event, hay message chuẩn bị gửi đi ở phía local database. Điều này đảm bảo là toàn bộ transaction ở local đã hoàn thành, hoặc đã rollback hết, rồi mới sẽ gửi message đi, bằng việc liên tục gọi về local để gửi message đi
Ưu điểm/Nhược điểm
- Ưu điểm:
- Dữ liệu được đảm bảo nhất quán
- Giảm bớt sự phụ thuộc quá chặt giữa các service
- Nhược điểm:
- Tăng thêm độ phức tạp do có thêm việc scan bảng outbox, giảm hiệu năng
- Tăng thêm tài nguyên lưu trữ bảng outbox, ta lại phải làm thêm cơ chế cleanup event nếu nó grow quá lớn
- Có thể xảy ra trường hợp gửi nhiều hơn 1 message, vì vậy message broker hoặc phía nhận cần đảm bảo tính idempotent. (Tức là dù có gửi bao nhiêu request cùng 1 loại thì kết quả không đổi và như request đầu tiên). Ví dụ: lưu lại id message để không xử lý lại nếu đã xử lý rồi