1. Giới Thiệu Seata
Seata (Simple Extensible Autonomous Transaction Architecture) là một giải pháp mã nguồn mở dành cho quản lý giao dịch phân tán, với mục tiêu đơn giản hóa việc sử dụng giao dịch phân tán, làm cho chúng hiệu quả và dễ dàng như giao dịch cục bộ. Trong kiến trúc microservice, logic nghiệp vụ thường trải dài qua nhiều dịch vụ và cơ sở dữ liệu độc lập. Seata ra đời để giải quyết bài toán nhất quán dữ liệu trong những tình huống này, bằng cách điều phối và quản lý việc thực thi (commit) hoặc hủy bỏ (rollback) các giao dịch con (local transaction) dưới sự giám sát của một giao dịch toàn cục.
2. Các Tính Năng Nổi Bật
Seata cung cấp nhiều tính năng mạnh mẽ, nổi bật nhất là khả năng hỗ trợ đa dạng các chế độ giao dịch phân tán, phù hợp với các yêu cầu nghiệp vụ khác nhau:
- Chế độ AT (Automatic Transaction) - Mặc định:
- Mức độ can thiệp code: Rất thấp, chỉ cần sử dụng annotation
@GlobalTransactional. - Nguyên lý hoạt động: Đây là phiên bản cải tiến của giao thức Two-Phase Commit (2PC). Seata sử dụng proxy để can thiệp vào tầng dữ liệu, cho phép các giao dịch cục bộ được commit ở giai đoạn đầu tiên. Tính nhất quán được đảm bảo thông qua một global lock và việc ghi lại các bản ghi
undo_log. Khi cần rollback, Seata sẽ sử dụngundo_logđể khôi phục trạng thái dữ liệu trước đó. - Ưu điểm: Dễ triển khai, hiệu năng khá tốt.
- Hạn chế: Yêu cầu cơ sở dữ liệu hỗ trợ giao dịch cục bộ và khóa hàng (row lock). Việc sử dụng global lock có thể ảnh hưởng nhẹ đến hiệu suất.
- Mức độ can thiệp code: Rất thấp, chỉ cần sử dụng annotation
- Chế độ TCC (Try-Confirm-Cancel):
- Mức độ can thiệp code: Cao, đòi hỏi lập trình viên phải tự định nghĩa và triển khai ba phương thức:
Try(kiểm tra và đặt trước tài nguyên),Confirm(xác nhận thực thi) vàCancel(hủy bỏ/bồi hoàn). - Nguyên lý hoạt động: Cũng dựa trên 2PC, nhưng các bước thực thi được điều khiển hoàn toàn bởi logic nghiệp vụ do lập trình viên viết.
- Ưu điểm: Hiệu năng rất cao do không sử dụng global lock, tối ưu cho các hệ thống có yêu cầu về đồng thời và hiệu suất cao (ví dụ: tài chính, thương mại điện tử).
- Hạn chế: Can thiệp sâu vào code nghiệp vụ, tăng độ phức tạp trong thiết kế và phát triển.
- Mức độ can thiệp code: Cao, đòi hỏi lập trình viên phải tự định nghĩa và triển khai ba phương thức:
- Chế độ Saga:
- Mức độ can thiệp code: Trung bình, dựa trên định nghĩa luồng công việc (workflow) dưới dạng máy trạng thái hoặc các dịch vụ bồi hoàn cho từng bước.
- Nguyên lý hoạt động: Một giải pháp cho các giao dịch dài. Mỗi bước trong luồng Saga sẽ thực hiện và commit giao dịch cục bộ của riêng nó. Nếu một bước nào đó thất bại, hệ thống sẽ thực hiện các thao tác bồi hoàn (compensation actions) ngược lại các bước đã thành công trước đó để đưa hệ thống về trạng thái nhất quán.
- Ưu điểm: Phù hợp với các luồng nghiệp vụ dài, phức tạp, bao gồm nhiều dịch vụ (ví dụ: đặt vé máy bay, tour du lịch). Hỗ trợ thực thi bất đồng bộ, mang lại thông lượng cao.
- Hạn chế: Không đảm bảo tính cô lập (isolation) hoàn toàn, có thể xảy ra "dirty writes" trong quá trình chuyển tiếp. Yêu cầu logic nghiệp vụ phải xử lý được các trạng thái trung gian.
- Chế độ XA:
- Tiêu chuẩn: Dựa trên giao thức XA do tổ chức X/Open định nghĩa.
- Nguyên lý hoạt động: Cũng là một dạng 2PC. Transaction Manager (TM) và Resource Manager (RM) giao tiếp qua các API XA. Giai đoạn 1, tất cả các giao dịch nhánh thực thi nhưng chưa commit (
XA prepare). Giai đoạn 2, Transaction Coordinator (TC) chỉ thị tất cả các nhánh commit hoặc rollback đồng thời. - Ưu điểm: Đảm bảo tính nhất quán mạnh mẽ và cô lập tốt. Được hỗ trợ bởi hầu hết các hệ quản trị cơ sở dữ liệu lớn.
- Hạn chế: Hiệu suất kém do tài nguyên bị khóa trong suốt quá trình, đặc biệt là trong giai đoạn chuẩn bị cho đến khi hoàn tất giai đoạn 2.
- Tính sẵn sàng cao (High Availability): Transaction Coordinator (TC) hỗ trợ triển khai cụm (cluster) thông qua các dịch vụ đăng ký (như Nacos, Eureka) và cấu hình (như Apollo, Nacos) để tránh điểm lỗi duy nhất.
- Sẵn sàng sử dụng (Out-of-the-box): Tích hợp tốt với các framework microservice phổ biến (Spring Cloud, Dubbo), cũng như các giải pháp đăng ký/cấu hình dịch vụ.
3. Kiến Trúc Tổng Quan và Ba Thành Phần Chính
Kiến trúc của Seata bao gồm ba thành phần cốt lõi, tương tác với nhau như mô tả trong sơ đồ kiến trúc (hình ảnh về kiến trúc Seata):

3.1. Transaction Coordinator (TC) - Điều Phối Viên Giao Dịch (Seata Server)
- Vai trò: Là bộ não và chỉ huy của các giao dịch. Đây là một máy chủ middleware độc lập và cần được triển khai riêng.
- Chức năng:
- Quản lý trạng thái của các giao dịch toàn cục và giao dịch nhánh.
- Điều khiển việc commit hoặc rollback của giao dịch toàn cục.
- Quản lý global lock (đặc biệt trong chế độ AT).
- Triển khai: Thường được triển khai dưới dạng cụm (cluster) để đảm bảo tính sẵn sàng cao, sử dụng các dịch vụ đăng ký để cung cấp dịch vụ.
3.2. Transaction Manager (TM) - Quản Lý Giao Dịch (Tích hợp trong client)
- Vai trò: Là thành phần khởi tạo giao dịch toàn cục và xác định phạm vi của nó.
- Chức năng:
- Gửi yêu cầu tới TC để bắt đầu một giao dịch toàn cục mới.
- Gửi yêu cầu tới TC để commit hoặc rollback giao dịch toàn cục.
- Vị trí: Thường nằm trong ứng dụng microservice khởi tạo giao dịch toàn cục. Ví dụ, một phương thức tạo đơn hàng trong dịch vụ đặt hàng được đánh dấu bằng
@GlobalTransactionalsẽ đóng vai trò của TM.
3.3. Resource Manager (RM) - Quản Lý Tài Nguyên (Tích hợp trong client)
- Vai trò: Là thành phần thực thi các giao dịch nhánh và quản lý tài nguyên cục bộ (thường là cơ sở dữ liệu).
- Chức năng:
- Đăng ký giao dịch nhánh với TC và báo cáo trạng thái.
- Nhận lệnh từ TC và điều khiển việc commit hoặc rollback giao dịch nhánh cục bộ.
- Trong chế độ AT, RM sẽ ủy quyền (proxy) cho datasource, tự động tạo và thực thi các bản ghi
undo_logđể phục vụ cho việc khôi phục dữ liệu.
- Vị trí: Mỗi ứng dụng microservice tham gia vào giao dịch phân tán đều có một RM riêng, chịu trách nhiệm quản lý cơ sở dữ liệu của mình.
4. Luồng Hoạt Động Cụ Thể (Ví dụ với chế độ AT)
Hãy xem xét một luồng xử lý đơn hàng: Dịch vụ Đặt hàng -> Dịch vụ Kho hàng -> Dịch vụ Tài khoản.
- TM khởi tạo giao dịch toàn cục:
- Người dùng gọi API "tạo đơn hàng" trên Dịch vụ Đặt hàng.
- TM của Dịch vụ Đặt hàng (được đánh dấu bằng
@GlobalTransactional) yêu cầu TC mở một giao dịch toàn cục (XID). TC sẽ tạo một XID duy nhất và trả về.
- RM đăng ký giao dịch nhánh:
- Dịch vụ Đặt hàng thực thi giao dịch cục bộ. Trước khi chèn dữ liệu vào bảng đơn hàng, RM sẽ chặn SQL, tạo snapshot dữ liệu và bản ghi
undo_log, sau đó ghi vào cơ sở dữ liệu. - RM của Dịch vụ Đặt hàng đăng ký một giao dịch nhánh với TC, liên kết với XID đã nhận.
- Dịch vụ Đặt hàng commit giao dịch cục bộ của mình.
- Dịch vụ Đặt hàng thực thi giao dịch cục bộ. Trước khi chèn dữ liệu vào bảng đơn hàng, RM sẽ chặn SQL, tạo snapshot dữ liệu và bản ghi
- XID được truyền đi trong chuỗi gọi:
- Dịch vụ Đặt hàng gọi Dịch vụ Kho hàng thông qua Feign/Ribbon. Seata tự động truyền XID qua header yêu cầu (ví dụ:
tx-seata-xid) đến các dịch vụ hạ nguồn.
- Dịch vụ Đặt hàng gọi Dịch vụ Kho hàng thông qua Feign/Ribbon. Seata tự động truyền XID qua header yêu cầu (ví dụ:
- Các dịch vụ hạ nguồn thực thi:
- Dịch vụ Kho hàng nhận yêu cầu, RM của nó cũng nhận biết được XID.
- Dịch vụ Kho hàng thực hiện giao dịch cục bộ giảm số lượng tồn kho, cũng tạo
undo_logvà đăng ký giao dịch nhánh với TC, sau đó commit giao dịch cục bộ. - Dịch vụ Tài khoản thực hiện tương tự.
- Commit hoặc Rollback toàn cục:
- Trường hợp thành công: Tất cả các giao dịch nhánh đều thực thi và đăng ký thành công. TM (Dịch vụ Đặt hàng) sẽ gửi lệnh commit toàn cục đến TC. TC sẽ bất đồng bộ thông báo cho tất cả các RM xóa các bản ghi
undo_logcủa họ. Quá trình này diễn ra rất nhanh vì các giao dịch cục bộ đã được commit ở giai đoạn một. - Trường hợp thất bại: Nếu Dịch vụ Tài khoản không thể trừ tiền, nó sẽ ném ra một ngoại lệ. Ngoại lệ này sẽ được lan truyền ngược lên chuỗi gọi và cuối cùng được TM trong Dịch vụ Đặt hàng bắt được.
- TM sẽ gửi lệnh rollback toàn cục đến TC.
- TC dựa vào XID để tìm tất cả các giao dịch nhánh liên quan và ra lệnh rollback cho các RM tương ứng.
- Các RM nhận lệnh rollback sẽ sử dụng các bản ghi
undo_logtrong cơ sở dữ liệu cục bộ để tạo ra các câu lệnh SQL ngược (ví dụ: INSERT thành DELETE, UPDATE trở lại giá trị cũ) và thực thi, hoàn tác dữ liệu, sau đó xóaundo_log.
- Trường hợp thành công: Tất cả các giao dịch nhánh đều thực thi và đăng ký thành công. TM (Dịch vụ Đặt hàng) sẽ gửi lệnh commit toàn cục đến TC. TC sẽ bất đồng bộ thông báo cho tất cả các RM xóa các bản ghi
5. So Sánh Các Chế Độ Giao Dịch Phân Tán
| Đặc điểm / Chế độ | AT | TCC | Saga | XA |
|---|---|---|---|---|
| Can thiệp vào code | Thấp (chỉ annotation) | Cao (triển khai Try, Confirm, Cancel) | Trung bình (định nghĩa bù trừ) | Thấp (chỉ annotation) |
| Hiệu năng | Tốt | Rất tốt | Rất tốt (có thể bất đồng bộ) | Kém (khóa tài nguyên) |
| Tính cô lập | Read Committed (nhờ global lock) | Phụ thuộc vào logic nghiệp vụ | Không đảm bảo (có thể "dirty writes") | Cô lập mạnh |
| Kịch bản ứng dụng | Phần lớn các tình huống, ưu tiên đơn giản và hiệu quả | Yêu cầu hiệu năng cao, tài chính, thanh toán | Giao dịch dài, luồng nghiệp vụ phức tạp | Hệ thống ngân hàng truyền thống, yêu cầu nhất quán mạnh |
Seata, với việc phân chia vai trò rõ ràng và hỗ trợ đa dạng các chế độ, cung cấp giải pháp linh hoạt và mạnh mẽ để đảm bảo tính nhất quán trong môi trường hệ thống phân tán phức tạp, trở thành một thành phần quan trọng trong kiến trúc microservice hiện đại.
6. Minh Họa Các Chế Độ Seata Bằng Ví Dụ Chuyển Khoản Liên Ngân Hàng
Hãy tưởng tượng bạn muốn chuyển 100.000 VNĐ từ tài khoản tại Ngân hàng A sang tài khoản bạn bè tại Ngân hàng B. Đây chính là một giao dịch phân tán điển hình, liên quan đến hai hệ thống ngân hàng khác nhau (hai cơ sở dữ liệu độc lập).
6.1. Ba Vai Trò Của Seata Trong Giao Dịch "Chuyển Khoản" Này
- TC (Transaction Coordinator) - Trung tâm Thanh toán Bù trừ Liên ngân hàng / Ngân hàng Nhà nước:
- Là ai: Một cơ quan độc lập, không thuộc ngân hàng nào, nhưng nắm rõ mọi giao dịch liên ngân hàng.
- Nhiệm vụ: Ghi nhận trạng thái từng giao dịch chuyển khoản (đã bắt đầu? thành công? thất bại?), và ra quyết định cuối cùng: "Xác nhận chuyển khoản!" hoặc "Hủy bỏ giao dịch!".
- Đặc điểm: Phải cực kỳ tin cậy, thường triển khai dưới dạng cụm (cluster) để tránh việc một lỗi làm tê liệt toàn bộ hệ thống.
- TM (Transaction Manager) - Giao Dịch Viên / Ứng dụng Mobile Banking của Ngân hàng A:
- Là ai: Người/hệ thống khởi tạo toàn bộ giao dịch, nơi bạn trực tiếp thao tác.
- Nhiệm vụ: Báo cáo cho TC: "Tôi muốn bắt đầu một giao dịch chuyển tiền từ Ngân hàng A sang Ngân hàng B, mã số XXX". Nếu mọi việc suôn sẻ, TM yêu cầu TC "commit". Nếu có lỗi, TM yêu cầu TC "rollback".
- RM (Resource Manager) - Hệ thống Sổ sách / Cơ sở dữ liệu của Ngân hàng A và Ngân hàng B:
- Là ai: Từng hệ thống ngân hàng tham gia giao dịch (Ngân hàng A và Ngân hàng B).
- Nhiệm vụ: Ngân hàng A: tạm thời trừ 100.000 VNĐ khỏi tài khoản của bạn (nhưng chưa chuyển đi). Ngân hàng B: tạm thời ghi nhận có 100.000 VNĐ sắp đến (nhưng chưa cho phép sử dụng). Cả hai đều báo cáo cho TC: "Chúng tôi đã thực hiện tạm thời xong, sẵn sàng chờ lệnh cuối cùng". Cuối cùng, làm theo lệnh của TC để chuyển trạng thái "tạm thời" thành "chính thức" hoặc "hủy bỏ".
6.2. Bốn Chế Độ Giao Dịch Của Seata Là Bốn Chiến Lược Chuyển Khoản Khác Nhau
TC cung cấp các quy trình chuyển khoản khác nhau để lựa chọn:
6.2.1. Chế độ AT (Chế độ "Tự Động") - Chuyển khoản "Ủy quyền Trước"
Chế độ này phổ biến và tiện lợi nhất, giống như bạn sử dụng tính năng ủy quyền trước trên thẻ tín dụng.
- Giai đoạn 1 (Try - "Đặt trước"):
- RM Ngân hàng A: Tạm thời phong tỏa 100.000 VNĐ trong tài khoản của bạn (như khách sạn phong tỏa một khoản tiền trên thẻ tín dụng, tiền chưa bị trừ nhưng bạn không thể dùng).
- RM Ngân hàng B: Đánh dấu tài khoản bạn bè sẽ nhận 100.000 VNĐ (nhưng chưa thể sử dụng).
- Cả hai báo cáo cho TC: "Ủy quyền trước thành công".
- Giai đoạn 2 (Confirm/Cancel - "Xác nhận/Hủy"):
- Nếu thành công: TC ra lệnh "Xác nhận", hai ngân hàng sẽ biến giao dịch tạm thời thành trừ tiền và cộng tiền chính thức.
- Nếu thất bại: TC ra lệnh "Hủy", Ngân hàng A sẽ giải phong tỏa 100.000 VNĐ, Ngân hàng B sẽ xóa dấu hiệu nhận tiền.
- Ưu điểm: Lập trình viên ít phải can thiệp, Seata tự động xử lý mọi thứ.
- Hạn chế: Sử dụng "khóa toàn cục" (tương đương với việc phong tỏa tiền) có thể ảnh hưởng nhẹ đến hiệu suất.
6.2.2. Chế độ TCC (Chế độ "Thủ Công") - Chuyển khoản "Gọi Điện Xác Nhận Từng Bước"
Chế độ này yêu cầu cao hơn, lập trình viên phải can thiệp nhiều, giống như chuyển khoản giá trị lớn cần bạn tự mình gọi điện xác nhận từng bước.
- Giai đoạn Try (Thử):
- Bạn gọi Ngân hàng A: "Anh/chị vui lòng thử xem tài khoản của tôi có thể trừ 100.000 VNĐ không?" (kiểm tra và đặt trước nguồn lực).
- Bạn gọi Ngân hàng B: "Anh/chị vui lòng thử xem tài khoản bạn tôi có thể nhận 100.000 VNĐ không?".
- Giai đoạn Confirm (Xác nhận): Nếu cả hai ngân hàng đều báo "Ổn", bạn gọi điện lại: "Vâng, xin vui lòng thực hiện chính thức!".
- Giai đoạn Cancel (Hủy bỏ): Nếu Ngân hàng B báo "Tài khoản không đúng, không nhận được", bạn phải gọi lại Ngân hàng A: "Xin hủy bỏ giao dịch thử vừa rồi, giải phong tỏa giúp!".
- Ưu điểm: Hiệu năng tốt nhất, kiểm soát chặt chẽ nhất, không có global lock.
- Hạn chế: Rất phức tạp, đòi hỏi bạn phải viết nhiều code để triển khai ba phương thức Try, Confirm, Cancel.
6.2.3. Chế độ Saga - Chế độ "Tạm Ứng Rồi Hoàn Trả"
Áp dụng cho chuỗi giao dịch rất dài, ví dụ: công ty tổ chức teambuilding, bạn tự ứng tiền mua vé, đặt phòng, rồi sau đó được công ty hoàn trả.
- Luồng:
- Bạn tự chi tiền mua vé máy bay (commit giao dịch cục bộ).
- Bạn tự chi tiền đặt khách sạn (commit giao dịch cục bộ).
- Cuối cùng, bạn yêu cầu công ty hoàn trả.
- Kết quả:
- Nếu hoàn trả thành công: Tuyệt vời!
- Nếu hoàn trả thất bại: Công ty không chấp nhận chi phí. Lúc này, bạn phải thực hiện các thao tác ngược lại: hủy vé máy bay, hủy đặt phòng khách sạn (thao tác bù trừ).
- Ưu điểm: Thông lượng cao, phù hợp với các luồng nghiệp vụ dài.
- Hạn chế: Không đảm bảo tính cô lập (ví dụ, khi bạn hủy vé có thể đã hết hạn hủy), bạn có thể phải tự chịu rủi ro.
6.2.4. Chế độ XA - Chế độ "Đồng Bộ Tại Quầy, Cùng Nhau Đóng Dấu"
Phương pháp truyền thống của ngân hàng, cực kỳ nghiêm ngặt nhưng kém hiệu quả.
- Luồng:
- Giao dịch viên của Ngân hàng A và Ngân hàng B đều nhận được phiếu chuyển khoản của bạn.
- Họ gọi điện cho nhau xác nhận: "Tôi đã sẵn sàng, bạn thì sao?". "Tôi cũng đã sẵn sàng."
- Sau khi cả hai đều xác nhận đã sẵn sàng, họ cùng lúc đóng dấu để giao dịch có hiệu lực.
- Ưu điểm: Nhất quán mạnh, an toàn nhất.
- Hạn chế: Trong quá trình chuẩn bị và xác nhận, số tiền trong tài khoản của bạn và tài khoản người nhận đều bị khóa, không thể thực hiện bất kỳ thao tác nào khác, hiệu suất thấp nhất.
Tóm lại, Seata là một trung tâm điều phối giao dịch phân tán, cung cấp nhiều "quy trình làm việc" khác nhau để đảm bảo: hoặc tất cả cùng thành công, hoặc tất cả cùng thất bại, không bao giờ xảy ra tình trạng "tiền của tôi đã bị trừ, nhưng người kia lại không nhận được".
7. Triển Khai Chế Độ AT (Tự Động)
Chế độ AT là mặc định của Seata, với mức độ can thiệp vào mã nguồn rất thấp. Hãy xem cách triển khai trên nền tảng Spring Cloud + Spring Boot:
7.1. Cấu hình và Thêm Dependency
Thêm thư viện Seata vào các dịch vụ microservice của bạn (ví dụ: order-service, storage-service, account-service):
<!-- Trong pom.xml của các dịch vụ -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
Cấu hình địa chỉ của Seata Server (Transaction Coordinator - TC) trong file application.yml của mỗi dịch vụ:
seata:
application-id: order-service # Tên dịch vụ hiện tại
tx-service-group: nhom_giao_dich_order # Tên nhóm giao dịch, phải khớp với cấu hình Seata Server
registry:
type: nacos # Loại dịch vụ đăng ký (ví dụ: Nacos)
nacos:
server-addr: 127.0.0.1:8848 # Địa chỉ Nacos server
namespace: public
config:
type: nacos # Loại dịch vụ cấu hình
nacos:
server-addr: 127.0.0.1:8848
namespace: public
7.2. Tạo Bảng undo_log
Chế độ AT dựa vào bản ghi undo_log để khôi phục dữ liệu khi cần rollback. Bạn cần tạo bảng này trong mỗi cơ sở dữ liệu mà các dịch vụ của bạn tương tác. SQL để tạo bảng thường có sẵn trong tài liệu chính thức của Seata.
7.3. Sử dụng Annotation Giao Dịch Toàn Cục
Đánh dấu phương thức khởi tạo giao dịch toàn cục bằng annotation @GlobalTransactional:
@RestController
@RequestMapping("/don-hang")
public class QuanLyDonHangController {
private final DichVuDatHang dichVuDatHang;
private final DichVuKhoHang dichVuKhoHang;
private final DichVuTaiKhoan dichVuTaiKhoan;
public QuanLyDonHangController(DichVuDatHang dichVuDatHang,
DichVuKhoHang dichVuKhoHang,
DichVuTaiKhoan dichVuTaiKhoan) {
this.dichVuDatHang = dichVuDatHang;
this.dichVuKhoHang = dichVuKhoHang;
this.dichVuTaiKhoan = dichVuTaiKhoan;
}
@PostMapping("/tao-moi")
@GlobalTransactional(name = "taoDonHangMoi", rollbackFor = Exception.class) // Annotation thần kỳ!
public String xuLyTaoDonHang(@RequestBody ThongTinDatHang dataDatHang) {
// 1. Giảm số lượng tồn kho (gọi dịch vụ Kho hàng)
dichVuKhoHang.giamSoLuong(dataDatHang.getMaSanPham(), dataDatHang.getSoLuong());
// 2. Trừ tiền tài khoản (gọi dịch vụ Tài khoản)
dichVuTaiKhoan.truTien(dataDatHang.getMaNguoiDung(), dataDatHang.getTongTien());
// 3. Tạo bản ghi đơn hàng (giao dịch cục bộ)
dichVuDatHang.luuDonHang(dataDatHang);
return "Tạo đơn hàng thành công!";
}
}
Điều gì xảy ra khi code chạy?
- Seata sẽ tự động ủy quyền (proxy) cho datasource của bạn.
- Khi gọi
dichVuKhoHang.giamSoLuong, Seata sẽ chặn SQL, ghi lại dữ liệu tồn kho trước khi thay đổi vào bảngundo_log, sau đó mới thực hiện thao tác giảm. - Sau khi giảm thành công, nó sẽ đăng ký một giao dịch nhánh với TC, báo cáo: "Dịch vụ kho đã thực thi dự kiến thành công".
- Dịch vụ Tài khoản và Dịch vụ Đặt hàng cũng thực hiện tương tự.
- Nếu tất cả các nhánh thành công, TM (phương thức được gắn
@GlobalTransactional) thông báo cho TC, TC sau đó ra lệnh cho tất cả các RM xóa các bản ghiundo_logcủa họ. Giao dịch chính thức được commit. - Nếu bất kỳ bước nào thất bại (ví dụ: tài khoản không đủ tiền), TM sẽ thông báo cho TC. TC sẽ ra lệnh cho tất cả các RM: "Hãy sử dụng 'hộp đen'
undo_logcủa các bạn để khôi phục dữ liệu về trạng thái ban đầu!".
Tóm lại, triển khai chế độ AT đơn giản chỉ là: thêm dependency + cấu hình TC + tạo bảng undo_log + thêm một annotation.
8. Triển Khai Chế Độ TCC (Thủ Công)
Chế độ TCC yêu cầu bạn tự tay viết logic nghiệp vụ cho các giai đoạn "Try", "Confirm", "Cancel".
8.1. Định nghĩa Interface TCC
Tạo một interface cho tài nguyên nghiệp vụ của bạn, bao gồm ba phương thức try, confirm, cancel:
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
import java.math.BigDecimal;
@LocalTCC
public interface TacVuTaiKhoanTcc {
// Giai đoạn Try: Kiểm tra và đặt trước (đóng băng) tài nguyên
// @BusinessActionContextParameter dùng để truyền tham số sang giai đoạn confirm/cancel
@TwoPhaseBusinessAction(name = "tacVuGiamTruTcc", commitMethod = "xacNhanGiamTru", rollbackMethod = "huyGiamTru")
boolean thuGiamTru(@BusinessActionContextParameter(paramName = "maNguoiDung") String maNguoiDung,
@BusinessActionContextParameter(paramName = "soTien") BigDecimal soTien);
// Giai đoạn Confirm: Thực sự trừ tiền
boolean xacNhanGiamTru(BusinessActionContext context);
// Giai đoạn Cancel: Hoàn trả tiền đã đóng băng
boolean huyGiamTru(BusinessActionContext context);
}
8.2. Triển khai Interface TCC
Viết logic nghiệp vụ cho ba giai đoạn của thao tác trừ tiền:
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
@Service
public class TacVuTaiKhoanTccImpl implements TacVuTaiKhoanTcc {
private final TaiKhoanRepository taiKhoanRepository; // Giả sử có một Repository tương ứng
public TacVuTaiKhoanTccImpl(TaiKhoanRepository taiKhoanRepository) {
this.taiKhoanRepository = taiKhoanRepository;
}
@Override
@Transactional // Đảm bảo giao dịch cục bộ trong mỗi giai đoạn TCC
public boolean thuGiamTru(String maNguoiDung, BigDecimal soTien) {
// 1. Kiểm tra số dư khả dụng
TaiKhoan taiKhoan = taiKhoanRepository.findByMaNguoiDung(maNguoiDung);
if (taiKhoan == null || taiKhoan.getSoDuKhaDung().compareTo(soTien) < 0) {
throw new RuntimeException("Số dư tài khoản không đủ.");
}
// 2. Giai đoạn Try: Đóng băng số tiền (đặt trước tài nguyên)
// Cập nhật: giảm số dư khả dụng và tăng số dư đóng băng
taiKhoanRepository.capNhatSoDuDongBang(maNguoiDung, soTien);
// SQL giả định: UPDATE tai_khoan SET so_du_kha_dung = so_du_kha_dung - #{soTien}, so_du_dong_bang = so_du_dong_bang + #{soTien} WHERE ma_nguoi_dung = #{maNguoiDung}
return true;
}
@Override
@Transactional
public boolean xacNhanGiamTru(BusinessActionContext context) {
// Lấy tham số từ giai đoạn Try
String maNguoiDung = (String) context.getActionContext("maNguoiDung");
BigDecimal soTien = (BigDecimal) context.getActionContext("soTien");
// Giai đoạn Confirm: Trừ tiền chính thức từ số dư đóng băng
// Cập nhật: giảm số dư đóng băng và giảm tổng số dư
taiKhoanRepository.giamTruChinhThuc(maNguoiDung, soTien);
// SQL giả định: UPDATE tai_khoan SET tong_so_du = tong_so_du - #{soTien}, so_du_dong_bang = so_du_dong_bang - #{soTien} WHERE ma_nguoi_dung = #{maNguoiDung}
return true;
}
@Override
@Transactional
public boolean huyGiamTru(BusinessActionContext context) {
// Lấy tham số từ giai đoạn Try
String maNguoiDung = (String) context.getActionContext("maNguoiDung");
BigDecimal soTien = (BigDecimal) context.getActionContext("soTien");
// Giai đoạn Cancel: Giải phóng số tiền đã đóng băng
// Cập nhật: giảm số dư đóng băng và tăng số dư khả dụng
taiKhoanRepository.giaiPhongDongBang(maNguoiDung, soTien);
// SQL giả định: UPDATE tai_khoan SET so_du_kha_dung = so_du_kha_dung + #{soTien}, so_du_dong_bang = so_du_dong_bang - #{soTien} WHERE ma_nguoi_dung = #{maNguoiDung}
return true;
}
}
8.3. Gọi Dịch vụ TCC trong Logic Nghiệp vụ
Trong code nghiệp vụ chính, thay vì gọi dịch vụ tài khoản thông thường, bạn sẽ gọi dịch vụ TCC:
@RestController
@RequestMapping("/don-hang-tcc")
public class QuanLyDonHangTccController {
private final TacVuTaiKhoanTcc tacVuTaiKhoanTcc; // Inject dịch vụ TCC
public QuanLyDonHangTccController(TacVuTaiKhoanTcc tacVuTaiKhoanTcc) {
this.tacVuTaiKhoanTcc = tacVuTaiKhoanTcc;
}
@PostMapping("/tao-moi-tcc")
@GlobalTransactional(name = "taoDonHangTcc", rollbackFor = Exception.class)
public String xuLyTaoDonHangTcc(@RequestBody ThongTinDatHang dataDatHang) {
// ... (các bước khác như giảm kho hàng, có thể dùng AT hoặc TCC riêng)
// Gọi giai đoạn Try của dịch vụ TCC
tacVuTaiKhoanTcc.thuGiamTru(dataDatHang.getMaNguoiDung(), dataDatHang.getTongTien());
// ... (các bước tiếp theo, ví dụ: tạo đơn hàng cục bộ)
return "Đơn hàng tạo thành công với TCC!";
}
}
Lưu ý quan trọng:
- Empty Rollback (Hủy rỗng): Có thể xảy ra khi giai đoạn Try chưa kịp thực thi vì lỗi mạng, nhưng giai đoạn Cancel lại được gọi. Phương thức Cancel của bạn cần xử lý được tình huống này (ví dụ: kiểm tra xem có bản ghi đóng băng nào không, nếu không thì không làm gì).
- Idempotency (Tính Lũy Đẳng): Do sự cố mạng, phương thức Confirm hoặc Cancel có thể bị gọi lặp lại. Các phương thức của bạn cần đảm bảo rằng việc gọi nhiều lần sẽ cho cùng một kết quả cuối cùng (ví dụ: sử dụng trạng thái giao dịch để tránh xử lý trùng lặp).
9. Triển Khai Chế Độ Saga và XA
9.1. Chế độ Saga (Giao Dịch Dài)
Chế độ Saga lý tưởng cho các luồng nghiệp vụ rất dài, ví dụ như quy trình đặt tour du lịch bao gồm đặt vé máy bay, đặt phòng khách sạn, làm visa, mỗi bước có thể mất nhiều thời gian.
- Cách triển khai (chế độ máy trạng thái):
- Định nghĩa máy trạng thái JSON: Bạn cần một tệp JSON để mô tả chi tiết từng bước (
State) trong quy trình nghiệp vụ, cùng với các hành động bù trừ (Compensation) tương ứng nếu một bước nào đó thất bại. - Triển khai máy trạng thái: Tải tệp JSON này lên engine máy trạng thái Saga của Seata.
- Kích hoạt thực thi: Ứng dụng của bạn chỉ cần gửi một yêu cầu, engine máy trạng thái sẽ tự động thực thi từng bước theo định nghĩa. Nếu có bất kỳ bước nào thất bại, nó sẽ tự động thực hiện các thao tác bù trừ ngược lại cho tất cả các bước đã thành công trước đó.
- Định nghĩa máy trạng thái JSON: Bạn cần một tệp JSON để mô tả chi tiết từng bước (
- Đặc điểm:
- Commit ở giai đoạn đầu: Mỗi dịch vụ commit giao dịch cục bộ ngay lập tức, do đó không có khóa tài nguyên, hiệu suất cao.
- Dựa hoàn toàn vào bù trừ: Tính nhất quán được đảm bảo thông qua các thao tác bù trừ. Nếu thao tác bù trừ cũng thất bại, cần có sự can thiệp thủ công. Không đảm bảo tính cô lập hoàn toàn.
- Kịch bản ứng dụng: Thực hiện đơn hàng trong thương mại điện tử, hệ thống logistics, quy trình phê duyệt công việc.
9.2. Chế độ XA (Giao Dịch Nhất Quán Mạnh Truyền Thống)
Chế độ XA là một giao thức chuẩn được cung cấp ở cấp độ cơ sở dữ liệu, và Seata cũng hỗ trợ chế độ này.
- Cách triển khai:
- Chuyển đổi chế độ: Trong file
application.ymlcủa bạn, thay đổi chế độ proxy datasource thành XA.seata: data-source-proxy-mode: XA # Mặc định là AT - Sử dụng Annotation: Tương tự như chế độ AT, sử dụng annotation
@GlobalTransactionaltại điểm khởi tạo giao dịch.
- Chuyển đổi chế độ: Trong file
- Nguyên lý hoạt động:
- Giai đoạn 1 (Prepare): Seata gọi các lệnh
XA Start,XA End,XA Prepare. Tại thời điểm này, cơ sở dữ liệu sẽ thực thi các câu lệnh SQL nhưng chưa commit, đặt giao dịch vào trạng thái "sẵn sàng commit", dữ liệu bị khóa và không hiển thị cho các giao dịch khác. - Giai đoạn 2 (Commit/Rollback):
- Commit: TC thông báo cho tất cả các RM thực hiện
XA Commit, tất cả các cơ sở dữ liệu cùng commit đồng thời. - Rollback: TC thông báo cho tất cả các RM thực hiện
XA Rollback, tất cả các cơ sở dữ liệu cùng rollback đồng thời.
- Commit: TC thông báo cho tất cả các RM thực hiện
- Giai đoạn 1 (Prepare): Seata gọi các lệnh
- Đặc điểm:
- Nhất quán mạnh: Dữ liệu bị khóa cho đến khi toàn bộ giao dịch kết thúc, đảm bảo tính cô lập hoàn hảo.
- Hiệu suất kém: Do thời gian khóa tài nguyên dài, hiệu suất đồng thời là thấp nhất trong số các chế độ.
- Kịch bản ứng dụng: Các hệ thống tài chính truyền thống, ngân hàng yêu cầu tính nhất quán cực cao và lượng truy cập đồng thời không quá lớn.
9.3. Tóm Lược và Lựa Chọn Chế Độ Phù Hợp
| Chế độ | Điểm mấu chốt triển khai | Ví von | Kịch bản ứng dụng |
|---|---|---|---|
| AT | Thêm annotation @GlobalTransactional |
Lái xe số tự động | Phần lớn các trường hợp, ưu tiên sự đơn giản và hiệu quả. |
| TCC | Tự viết ba phương thức try, confirm, cancel |
Lái xe số sàn | Các tình huống yêu cầu hiệu năng và đồng thời cao, như giao dịch tài chính. |
| Saga | Định nghĩa luồng và hành động bù trừ bằng máy trạng thái JSON | Tạm ứng rồi hoàn trả | Luồng nghiệp vụ dài, phức tạp, ví dụ: xử lý đơn hàng, quy trình công việc. |
| XA | Cấu hình data-source-proxy-mode: XA và thêm annotation @GlobalTransactional |
Đồng bộ đóng dấu tại quầy | Các hệ thống truyền thống, yêu cầu nhất quán cực mạnh, với lượng truy cập đồng thời vừa phải. |