Giới Thiệu Về Ledger
Ledger, hay còn gọi là "sổ cái", là một khái niệm quan trọng trong hệ thống tài chính và công nghệ thông tin. Hãy tưởng tượng một Ledger giống như một bảng tính kế toán của một công ty, nơi mọi giao dịch đều được ghi lại. Điều này đảm bảo tính immutability (không thể thay đổi), một nguyên tắc quan trọng trong hệ thống tài chính. Để duy trì tính imutability, chúng ta thường chỉ thêm các mục mới mà không xóa hay sửa đổi các mục đã có.
Cách Triển Khai Ledger
Bước 1: Tạo Ledger Riêng Cho Mỗi Loại Tiền Tệ
Một trong những thực tiễn tốt nhất khi triển khai Ledger là duy trì một Ledger riêng biệt cho mỗi loại tiền tệ, chẳng hạn như VND hoặc USD. Điều này giúp đơn giản hóa logic kinh doanh và các phép toán. Đồng thời, để ngăn chặn việc xử lý trùng lặp trong hệ thống phân tán, mỗi giao dịch cần có một khóa idempotency duy nhất.
Bước 2: Quản Lý Tính Đồng Thời
Quản lý tính đồng thời là yếu tố then chốt để tránh những sai lệch trong số dư. Một phương pháp là sử dụng lock tối ưu (optimistic lock), trong đó sử dụng một trường như version
hoặc last_sequence
. Phương pháp thay thế là lock bi quan (pessimistic lock), đảm bảo quyền truy cập độc quyền vào Ledger thông qua một dịch vụ bên ngoài như Redis.
Persistencia và Tính Đồng Thời
Lưu Trữ Ledger Trong Cơ Sở Dữ Liệu
Ledger có thể được lưu trữ trong một cơ sở dữ liệu như MongoDB. Đối với việc xử lý lock tối ưu, có thể sử dụng một trường như last_sequence
trong tài liệu của Ledger. Đối với lock bi quan, khuyến nghị sử dụng một dịch vụ cache phân tán với độ trễ thấp, chẳng hạn như Redis.
Khóa Idempotency
Khóa idempotency thường được tạo ra từ sự kết hợp của các dữ liệu như timestamp + from + to + amount
và có thể được lưu trữ trong một cache phân tán với TTL (Thời gian sống).
Quản Lý Tiền Trong Hệ Thống Phân Tán
Tại Sao Nên Sử Dụng int
Thay Vì float
?
Khi làm việc với tiền, hãy luôn tránh sử dụng các kiểu số thực (float
). Thay vào đó, hãy sử dụng số nguyên, lưu trữ giá trị ở đơn vị nhỏ nhất của đồng tiền (ví dụ: R$ 123,45 được lưu trữ dưới dạng số nguyên 12345
). Tất cả các phép toán sẽ được thực hiện với số nguyên này, giúp loại bỏ lỗi về độ chính xác.
Cấu Trúc Đối Với Nhiều Loại Tiền Tệ
Giá trị tiền tệ nên được biểu diễn bởi một cấu trúc kết hợp giữa số tiền và loại tiền tệ, theo tiêu chuẩn ISO 4217. Cấu trúc này cần có amount
(số nguyên) và currency
(ví dụ: "VND"), ngăn chặn các lỗi như cộng trực tiếp đô-la với đồng Việt Nam.
Áp Dụng Lock Tối Ưu và Giao Dịch
Để đảm bảo tính nhất quán, quá trình ghi một giao dịch cần phải là nguyên tử. Dưới đây là hướng dẫn chi tiết về cách thực hiện quy trình này:
-
Kiểm Tra Idempotency (Fail Fast): Trước khi bắt đầu giao dịch, hãy kiểm tra khóa idempotency trong cache nhanh như Redis. Nếu khóa đã tồn tại, điều này có nghĩa là giao dịch đã được xử lý, bạn có thể trả về thành công ngay lập tức, tránh công việc không cần thiết.
-
Bắt Đầu Vòng Lặp Thử Nghiệm (Retry Loop): Vì lock tối ưu có thể thất bại, toàn bộ logic kinh doanh cần được thực hiện trong một vòng lặp sẽ thử lại một số lần (ví dụ: 3 lần) trước khi từ bỏ.
-
Đọc Trạng Thái Hiện Tại: Trong vòng lặp và trong một giao dịch mới từ MongoDB, đọc tài liệu của
Ledger
để lấybalance
vàlast_sequence
hiện tại. -
Tính Toán Trong Bộ Nhớ: Tính toán số dư mới trong ứng dụng của bạn:
new_balance = current_balance + transaction_value
. -
Thực Hiện Ghi Nhớ Nguyên Tử: Thực hiện hai thao tác ghi trong cùng một giao dịch:
insertOne
vào bộ sưu tậptransactions
với dữ liệu của giao dịch mới.updateOne
trong bộ sưu tậpledgers
. Cập nhật này là chìa khóa của lock tối ưu: nó phải tìm_id
vàlast_sequence
mà bạn đã đọc ở bước 3. Cập nhật sẽ sửa đổibalance
và tănglast_sequence
.
-
Kết Quả:
- Thành Công: Nếu
updateOne
tìm thấy tài liệu và giao dịch hoàn tất (commit), có nghĩa là không có xung đột. Bạn có thể thoát khỏi vòng lặp và trả về thành công. - Thất Bại: Nếu giao dịch thất bại vì
last_sequence
không khớp, có nghĩa là một quy trình khác đã thay đổi Ledger. Vòng lặp thử nghiệm sẽ tiếp tục cho lần lặp tiếp theo, bắt đầu lại từ bước 3. Lưu ý rằng nên thêm một khoảng thời gian chờ nhỏ (backoff) giữa các lần thử.
- Thành Công: Nếu
Nếu vòng lặp kết thúc mà không thành công, giao dịch đã thất bại và cần trả về lỗi cho khách hàng.
Cấu Trúc Dữ Liệu
Dữ Liệu Ledger
json
{
"_id": ObjectId(),
"balance": {
"amount": 12345,
"currency": "VND"
},
"last_sequence": 1,
"last_transactions":[
{
"_id": ObjectId(),
"ledger_id": ObjectId(),
"timestamp": UnixTime,
"sequence": 1,
"change": {
"amount": 5000,
"currency": "VND"
},
"idempotency_key": "timestamp-from-to-amount"
}
]
}
Dữ Liệu Giao Dịch
json
{
"_id": ObjectId(),
"ledger_id": ObjectId(),
"timestamp": UnixTime,
"sequence": 1,
"change": {
"amount": 5000,
"currency": "VND"
},
"idempotency_key": "timestamp-from-to-amount"
}
Kết Luận
Xây dựng một Ledger đáng tin cậy dựa trên bốn yếu tố thiết yếu:
Immutability - đảm bảo lịch sử giao dịch chỉ là bổ sung;
Độ Chính Xác - sử dụng số nguyên cho các phép toán tài chính để tránh lỗi số thực;
Tính Nhất Quán - thông qua giao dịch nguyên tử với quản lý đồng thời như lock tối ưu;
Idempotency - bảo vệ hệ thống khỏi các giao dịch trùng lặp.
Khi những nền tảng này được củng cố, bước tiếp theo trong sự phát triển của hệ thống là xem xét về mở rộng quy mô. Đối với các Ledger có hàng triệu giao dịch, các kỹ thuật như Snapshots (ảnh chụp định kỳ số dư) để tối ưu hóa hiệu suất đọc và lưu trữ các giao dịch cũ trở nên rất quan trọng để duy trì hiệu suất của hệ thống lâu dài.