Trong kiến trúc microservices, các dịch vụ thường giao tiếp với nhau một cách không đồng bộ thông qua message queues hoặc event streaming platforms. Cách tiếp cận này không chỉ giúp hệ thống trở nên linh hoạt hơn mà còn tăng khả năng chịu lỗi. Tuy nhiên, điều này cũng mở ra một vấn đề: message duplication – tình trạng thông điệp bị gửi lại nhiều lần, có thể dẫn đến dữ liệu không nhất quán hoặc business logic sai lệch.
Hãy tưởng tượng khi một khách hàng nhấn nút thanh toán, nhưng do mạng chập chờn, yêu cầu được gửi lại nhiều lần. Nếu hệ thống không xử lý tốt, khách hàng có thể bị trừ tiền hai lần, không chỉ gây phiền toái mà còn làm mất niềm tin của người dùng.
Đây chính là lúc bạn cần biết đến Idempotent Consumer – một giải pháp tối ưu để xử lý thông điệp bị trùng lặp, giúp đảm bảo rằng dù một yêu cầu được gửi đi bao nhiêu lần, nó cũng chỉ được xử lý một lần duy nhất. Hệ thống sẽ không bị lỗi, dữ liệu vẫn nhất quán, và khách hàng không phải lo lắng về những sự cố không đáng có.
1. Vấn đề trùng lặp thông điệp
Việc trùng lặp thông điệp có thể xảy ra vì nhiều lý do:
- Sự cố mạng: Mạng không ổn định có thể khiến bộ điều phối thông điệp (message broker) gửi lại thông điệp.
- Cơ chế thử lại: Khi không nhận được phản hồi, hệ thống sẽ tự động retry.
- Quản lý giao dịch phân tán: Các giao thức như two-phase commit có thể dẫn đến việc gửi lặp thông điệp trong trường hợp bị lỗi.
Hậu quả của việc xử lý thông điệp trùng lặp không chỉ làm hệ thống phức tạp hơn mà còn gây ra những vấn đề nghiêm trọng ảnh hưởng trực tiếp đến hoạt động kinh doanh và trải nghiệm người dùng:
- Dữ liệu không nhất quán: Một yêu cầu thanh toán bị gửi lại hai lần có thể khiến khách hàng bị trừ tiền hai lần cho cùng một đơn hàng. Hoặc khách hàng nhận được nhiều mã giảm giá cho cùng một sự kiện, họ có thể lợi dụng để mua sắm nhiều lần dẫn đến thiệt hại tài chính cho doanh nghiệp.
- Tiêu tốn tài nguyên không cần thiết: Một thông điệp yêu cầu xử lý một bức ảnh bị gửi lại nhiều lần sẽ khiến hệ thống phải sử dụng CPU và bộ nhớ để xử lý lại, mặc dù kết quả cuối cùng không thay đổi.
2. Idempotent Consumer là gì?
Idempotent Consumer là mô hình thiết kế giúp đảm bảo rằng khi một thông điệp được xử lý nhiều lần, kết quả vẫn giống hệt như khi xử lý một lần duy nhất. Điều này giúp hệ thống giữ được tính toàn vẹn, không xảy ra lỗi hoặc dữ liệu không nhất quán.
Để đạt được điều này, bạn cần tuân theo các nguyên tắc cơ bản sau:
- Unique Message Identification: Mỗi thông điệp phải có một ID duy nhất, không thay đổi dù bị gửi đi bao nhiêu lần.
- State Tracking: Hệ thống cần theo dõi trạng thái các thông điệp đã xử lý để tránh xử lý lặp.
- Conditional Processing: Trước khi xử lý, kiểm tra xem thông điệp đã được xử lý chưa.
- Atomic Operations: Việc xử lý thông điệp và cập nhật trạng thái phải thực hiện đồng thời để tránh xung đột.
- Idempotent Actions: Business Logic phải được thiết kế sao cho không bị ảnh hưởng bởi việc thực thi lại.
3. Chiến lược triển khai Idempotent Consumer
3.1 Natural Idempotency
Có một số thao tác tự nhiên đã mang tính idempotent, lưu ý với trường hợp này thì bạn không cần xử lý phức tạp làm gì cả. Ví dụ:
- Thiết lập giá trị: Một API cập nhật trạng thái của một đơn hàng từ "Đang xử lý" thành "Hoàn thành". Dù gọi API này bao nhiêu lần, trạng thái vẫn là "Hoàn thành", không làm thay đổi thêm trạng thái của đơn hàng.
- Xóa dữ liệu: API xóa một tài khoản người dùng. Dù lệnh xóa được gửi nhiều lần, kết quả cuối cùng vẫn là tài khoản bị xóa.
3.2 Status Field
Thêm trường trạng thái vào đối tượng để xác định thông điệp đã được xử lý hay chưa.
Ví dụ: Khi khách hàng đặt hàng, hệ thống sẽ gửi email xác nhận. Tuy nhiên, do lỗi mạng hoặc message duplication, thông điệp yêu cầu gửi email có thể được gửi lại nhiều lần, dẫn đến việc khách hàng nhận nhiều email xác nhận không cần thiết.
- Thêm trường trạng thái vào cơ sở dữ liệu đơn hàng: Mỗi đơn hàng trong cơ sở dữ liệu sẽ có một trường trạng thái, ví dụ:
EmailStatus: NOT_SENT
- Kiểm tra trạng thái trước khi gửi email: Khi hệ thống nhận được thông điệp yêu cầu gửi email xác nhận cho
order_1234
, nó kiểm tra trạng thái trước khi thực hiện gửi.
3.4 Version Number
Gắn số phiên bản cho mỗi đối tượng và chỉ xử lý thông điệp nếu phiên bản trùng khớp.
Ví dụ: Một ứng dụng quản lý dự án sử dụng phiên bản để tránh ghi đè dữ liệu sai:
- Thông điệp cập nhật một nhiệm vụ đi kèm số phiên bản
v3
. - Nếu nhiệm vụ hiện tại đang ở phiên bản
v3
, thông điệp được xử lý và tăng phiên bản lênv4
. - Nếu nhiệm vụ đã ở phiên bản
v4
, thông điệp bị từ chối để tránh ghi đè dữ liệu cũ.
3.5 Idempotency Key
Tạo một khóa định danh duy nhất cho mỗi yêu cầu từ phía client.
Ví dụ: Trong ứng dụng đặt vé máy bay:
- Khi khách hàng đặt vé, hệ thống tạo một
Idempotency Key
dựa trên mã khách hàng và hành trình (cust123_flight789
) ở bước số 1 của quy trình đặt vé. - Nếu yêu cầu đặt vé bị gửi lại với cùng khóa trên, hệ thống nhận ra đó là yêu cầu trùng và bỏ qua việc đặt thêm vé (khách hàng đang ở bước 5 trong quy trình đặt vé).
3.6 Event Sourcing
Thay vì chỉ lưu trữ trạng thái hiện tại, hệ thống ghi nhận từng sự kiện dẫn đến trạng thái đó. Điều này giúp đảm bảo tính minh bạch, dễ dàng phục hồi dữ liệu và quan trọng nhất, các sự kiện có thể được thiết kế để idempotent.
- Khi nhận được thông điệp thanh toán, hệ thống tạo một sự kiện với thông tin:
{"eventId": "txn_5678", "type": "PaymentProcessed", "amount": 500,"orderId": "order_1234","status": "completed"}
. Sự kiện này được lưu vào event store. - Khi sự kiện
txn_5678
được gửi lại, hệ thống kiểm tra trong event store: Nếu sự kiện đã tồn tại, hệ thống không xử lý lại. Nếu sự kiện chưa tồn tại, hệ thống thực hiện xử lý và ghi sự kiện vào kho.
4. Kết luận
Bằng cách đảm bảo rằng một thao tác có thể lặp lại nhiều lần mà không gây ra hậu quả không mong muốn, mô hình Idempotent Consumer giúp duy trì tính nhất quán dữ liệu và tăng cường độ tin cậy của hệ thống.
Các bài viết liên quan:
Bài viết liên quan
Hướng dẫn tích hợp Redux và React Query trong dự án React Vite
Nov 22, 2024 • 8 min read
Giới thiệu Kiến trúc Backend for Frontend (BFF)
Nov 16, 2024 • 10 min read
Flask là gì? Hướng dẫn tạo Ứng dụng Web với Flask
Nov 15, 2024 • 7 min read
Webhook là gì? So sánh Webhook và API
Nov 15, 2024 • 8 min read
Spring Boot là gì? Hướng dẫn Khởi tạo Project Spring Boot với Docker
Nov 14, 2024 • 6 min read
Two-Factor Authentication (2FA) là gì? Vì sao chỉ Mật khẩu thôi là chưa đủ?
Nov 13, 2024 • 7 min read