Giới thiệu về Giao dịch Phân tán
Trong kiến trúc vi dịch vụ (Microservices), một quy trình nghiệp vụ thường phải trải qua nhiều dịch vụ khác nhau và thao tác trên các cơ sở dữ liệu riêng biệt. Điều này dẫn đến vấn đề về giao dịch phân tán, nơi dữ liệu nằm trên nhiều máy chủ tài nguyên. Mục tiêu cốt lõi là đảm bảo tính nhất quán toàn cục: hoặc tất cả các thao tác thành công, hoặc tất cả đều thất bại.
Các Kịch bản Ứng dụng Điển hình
- Giao dịch liên database: Một tính năng của ứng dụng yêu cầu truy cập vào nhiều hệ thống lưu trữ dữ liệu khác nhau để hoàn thành.
- Phân tách库 và bảng (Sharding): Khi khối lượng dữ liệu vượt quá ngưỡng cho phép của một kho đơn lẻ, việc chia nhỏ dữ liệu là cần thiết.
- Kiến trúc Vi dịch vụ: Dịch vụ A thực hiện thao tác DB đồng thời gọi sang Dịch vụ B và C. Nếu B và C cũng tương tác với nhiều cơ sở dữ liệu, hệ thống cần đảm bảo tính toàn vẹn của toàn bộ chuỗi gọi này.
Giao thức Cam kết Hai Giai đoạn (2PC)
2PC (Two Phase Commit) chia quy trình xác nhận (commit) thành hai bước:
Giai đoạn 1: Chuẩn bị (Prepare)
Trình quản lý giao dịch (TM) thông báo cho tất cả người tham gia (RM) chuẩn bị cam kết. Nếu RM xác định có thể hoàn thành, nó sẽ ghi nhớ trạng thái (persistent) và trả lời khẳng định. Nếu không, nó phản hồi phủ định.
Ví dụ với MySQL: Trình quản lý gửi yêu cầu "chuẩn bị". Các máy chủ cơ sở dữ liệu thực thi thay đổi dữ liệu và ghi log, nhưng chưa commit cuối cùng. Chúng chỉ chuyển trạng thái thành "sẵn sàng" và báo lại cho TM.
Giai đoạn 2: Cam kết hoặc Hoàn tác (Commit/Rollback)
Dựa trên kết quả từ Giai đoạn 1, TM ra quyết định cuối cùng.
- Nếu mọi RM đều báo sẵn sàng: TM phát lệnh commit. Tất cả RM chuyển trạng thái từ "sẵn sàng" sang "hoàn thành".
- Nếu có bất kỳ RM nào lỗi hoặc không phản hồi: TM phát lệnh rollback. Tất cả RM hủy bỏ các thay đổi tạm thời.
Tính chất ACID của giao dịch toàn cục phụ thuộc vào sự phối hợp của các RM. Các giao dịch con phải đạt được tính ACID để cấu thành nên tính chất của giao dịch lớn.
Hạn chế của 2PC
- Vấn đề chặn đồng bộ: Tài nguyên bị khóa trong suốt thời gian chờ đợi quyết định từ TM ở Giai đoạn 1 cho đến khi kết thúc Giai đoạn 2.
- Lỗi điểm đơn (SPOF): Nếu TM gặp sự cố, đặc biệt ở Giai đoạn 2, các RM sẽ bị treo ở trạng thái khóa mãi mãi.
- Mất nhất quán dữ liệu: Nếu TM gặp lỗi sau khi gửi lệnh commit cho một phần nhưng không kịp gửi phần còn lại, hệ thống sẽ rơi vào trạng thái không đồng nhất.
Giới thiệu về Seata
Seata là mã nguồn mở cung cấp giải pháp giao dịch phân tán hiệu suất cao và dễ sử dụng. Nó hỗ trợ nhiều chế độ như AT, TCC, SAGA và XA. Trong đó, chế độ AT là phương án ưu tiên được Alibaba quảng bá, tương tự với dịch vụ GTS thương mại trên đám mây.
Ba Vai trò Kiến trúc của Seata
- TC (Transaction Coordinator) - Bộ điều phối: Duy trì trạng thái toàn cục và nhánh giao dịch, điều khiển quy trình commit hay rollback.
- TM (Transaction Manager) - Quản lý giao dịch: Xác định phạm vi giao dịch toàn cục, khởi tạo hoặc kết thúc giao dịch.
- RM (Resource Manager) - Quản lý tài nguyên: Xử lý các nhánh giao dịch tại cơ sở dữ liệu, đăng ký với TC và báo cáo trạng thái.
TC thường được triển khai độc lập dưới dạng Server, trong khi TM và RM được nhúng vào các ứng dụng Client.
Cơ chế Thiết kế Chế độ AT
Chế độ AT được thiết kế để không xâm phạm đến logic nghiệp vụ, dựa trên cải tiến của 2PC.
Giai đoạn 1
Dữ liệu nghiệp vụ và nhật ký hoàn tác (undo log) được ghi trong cùng một giao dịch địa phương. Sau khi hoàn thành, khóa địa phương và kết nối được giải phóng ngay lập tức.
Giai đoạn 2
- Thành công: TC thông báo RM xóa bất đồng bộ các bản ghi undo log.
- Thất bại: TM yêu cầu TC rollback. Dựa vào XID và Branch ID, RM tìm lại undo log, tạo câu lệnh SQL đảo ngược và thực thi để khôi phục dữ liệu.
Bắt đầu nhanh với Seata
Hệ thống bao gồm Server (TC) và Client (TM + RM).
Cài đặt Môi trường Server (TC)
Store.mode hỗ trợ ba kiểu lưu trữ: file (mặc định, tốc độ cao), db (cao可用,performance thấp hơn), và redis.
Cấu trúc thư mục chính:
client: Script SQL và cấu hình cho phía client.server: Script database cho server container.config-center: Script để đưa cấu hình vào các trung tâm quản trị.
Bố trí với DB Storage và Nacos
Bước 1: Tải xuống
Truy cập kho lưu trữ chính thức để tải gói cài đặt.
Bước 2: Tạo bảng lưu trữ
Tạo database tên seata và chạy script SQL định nghĩa bảng global_transaction, branch_transaction...
Bước 3: Cấu hình Đăng ký (Registry) trên Nacos
Nacos đóng vai trò như sổ địa chỉ giúp Client tìm thấy Server.
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: DEFAULT_SEATA_GROUP
cluster: default
Lưu ý: Client và Server phải trùng nhau về namespace và group.
Bước 4: Cấu hình Trung tâm Cấu hình (Config Center)
Lưu trữ các tham số hoạt động của Seata.
seata:
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: DEFAULT_SEATA_GROUP
data-id: seata-group.properties
Tải nội dung file config.txt đã chỉnh sửa (chọn store.mode=db) lên Nacos với Data-id tương ứng.
Bước 5: Khởi động Server
Chạy script bin/seata-server.bat. Truy cập http://localhost:7091 để xem trạng thái.
Kết nối Client vào Seata
Thực tế tích hợp Spring Cloud Alibaba với AT Mode
Kịch bản: Đặt hàng hóa. Bao gồm Dịch vụ Kho (giảm stock), Dịch vụ Đơn hàng (tạo đơn), Dịch vụ Tài khoản (trừ tiền).
1. Chuẩn bị Môi trường
| Sản phẩm | Phiên bản Đề xuất |
|---|---|
| Spring Cloud Alibaba | 2.2.8.RELEASE |
| Spring Boot | 2.3.12.RELEASE |
| Seata | 1.5.1 |
Khởi động Nacos và Server Seata trước.
2. Thêm Dependency
Mô đun này tự động xử lý việc truyền tải XID giữa các dịch vụ.
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
3. Khởi tạo Bảng Undo Log
Mỗi database nghiệp vụ cần có bảng này để hỗ trợ chế độ AT.
CREATE TABLE IF NOT EXISTS `undo_log` (
`branch_id` BIGINT NOT NULL COMMENT 'ID nhánh giao dịch',
`xid` VARCHAR(128) NOT NULL COMMENT 'ID giao dịch toàn cầu',
`context` VARCHAR(128) NOT NULL,
`rollback_info` LONGBLOB NOT NULL COMMENT 'Thông tin hoàn tác',
`log_status` INT NOT NULL,
`log_created` DATETIME(6) NOT NULL,
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4. Cấu hình Application.yml
seata:
application-id: order-service-client
tx-service-group: my_test_tx_group
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: DEFAULT_SEATA_GROUP
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
data-id: seata-group.properties
5. Triển khai Nghiệp vụ Toàn cục
Sử dụng chú thích @GlobalTransactional tại phương thức khởi tạo luồng giao dịch.
@Override
@GlobalTransactional(name = "purchase-flow", rollbackFor = Exception.class)
public OrderResult createPurchase(CreatePurchaseReq req) {
log.info("Bắt đầu luồng mua hàng");
log.info("Current XID: {}", RootContext.getXID());
// Tạo mới đơn hàng
Order newOrder = new Order();
newOrder.setUserId(req.getUserId());
newOrder.setProductCode(req.getProductCode());
newOrder.setAmount(req.getAmount());
int insertStatus = orderRepository.save(newOrder);
// Gọi dịch vụ Kho trừ số lượng
inventoryClient.deductStock(req.getProductCode(), req.getAmount());
// Gọi dịch vụ Tiền trừ tiền
accountClient.debitBalance(req.getUserId(), req.getAmount());
// Cập nhật trạng thái đơn hàng thành công
orderRepository.updateStatus(newOrder.getId(), Status.COMPLETED);
return new OrderResult(SUCCESS_CODE, "Đặt hàng thành công");
}